开发中会遇到在swift中播放内存中pcm音频的需求。常见的pcm格式有signed16bit,signed32bit,float32bit,在iOS和macOS中,有个avfundation下的core audio就是专门处理pcm格式音频的。
从文件中读取pcm文件
- 生成想要的pcm文件,参照我之前的文章
- 生成三个文件,分别对应signed16bit,signed32bit,float32bit。
- 开始一个swift项目,创建一个Controller,参照之前的文章
- 把文件拉入到xcode项目中,并添加到Copy Bundle Resources,如下图:
5.在Controller中添加以下代码:
guard let filefullpathstr = Bundle.main.path(forResource: "s32le", ofType: "pcm") else { return }
do {
let url = URL(fileURLWithPath: filefullpathstr)
let data = try Data(contentsOf: url)
}
添加player和engine
- 添加成员变量
private var audioPlayer = AVAudioPlayerNode()
private lazy var audioEngine: AVAudioEngine = {
let engine = AVAudioEngine()
// Must happen only once.
engine.attach(self.audioPlayer)
return engine
}()
- 初始化engine,在controller的viewDidLoad的函数里面,添加以下代码,因为可能会抛出异常,需要做一次catch。这里需要注意。audioFormat必须为standardFormatWithSampleRate,不然会直接crash。有兴趣可以试一下,然后查一下错误代码,就能解锁苹果官方说明文档。
audioEngine.connect(audioPlayer, to: audioEngine.mainMixerNode, format: audioFormat)
try audioEngine.start()
try audioEngine.start()
转换pcm的Data数据为AVAudioPCMBuffer
- google可得:这个函数扩展了Data类,因此在调用的时候只需要data.convertedTo即可。
extension Data {
func convertedTo(_ format: AVAudioFormat) -> AVAudioPCMBuffer? {
let streamDesc = format.streamDescription.pointee
let frameCapacity = UInt32(count) / streamDesc.mBytesPerFrame
guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCapacity) else { return nil }
buffer.frameLength = buffer.frameCapacity
let audioBuffer = buffer.audioBufferList.pointee.mBuffers
withUnsafeBytes { (bufferPointer) in
guard let addr = bufferPointer.baseAddress else { return }
audioBuffer.mData?.copyMemory(from: addr, byteCount: Int(audioBuffer.mDataByteSize))
}
return buffer
}
}
- 拿到数据后交给player播放
audioPlayer.scheduleBuffer(audioFileBuffer);
audioPlayer.play()
所有代码
//
// MainViewController.swift
// PlayPCM
//
// Created by Niap on 2021/4/15.
//
import Foundation
import UIKit
import AVFoundation
extension Data {
func convertedTo(_ format: AVAudioFormat) -> AVAudioPCMBuffer? {
let streamDesc = format.streamDescription.pointee
let frameCapacity = UInt32(count) / streamDesc.mBytesPerFrame
guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCapacity) else { return nil }
buffer.frameLength = buffer.frameCapacity
let audioBuffer = buffer.audioBufferList.pointee.mBuffers
withUnsafeBytes { (bufferPointer) in
guard let addr = bufferPointer.baseAddress else { return }
audioBuffer.mData?.copyMemory(from: addr, byteCount: Int(audioBuffer.mDataByteSize))
}
return buffer
}
}
class MainViewController : UIViewController{
private var audioFormat = AVAudioFormat(standardFormatWithSampleRate: 44100.0, channels: 1)
private var audioPlayer = AVAudioPlayerNode()
private lazy var audioEngine: AVAudioEngine = {
let engine = AVAudioEngine()
// Must happen only once.
engine.attach(self.audioPlayer)
return engine
}()
override func viewDidLoad() {
guard let filefullpathstr = Bundle.main.path(forResource: "f32le", ofType: "pcm") else { return }
guard let audioFormat = self.audioFormat else { return }
do {
let url = URL(fileURLWithPath: filefullpathstr)
let data = try Data(contentsOf: url)
guard let audioFileBuffer = data.convertedTo(audioFormat) else {return}
audioEngine.connect(audioPlayer, to: audioEngine.mainMixerNode, format: audioFormat)
try audioEngine.start()
audioPlayer.scheduleBuffer(audioFileBuffer);
//audioPlayer.volume = 1
audioPlayer.play()
} catch let error {
print("!! error \(error.localizedDescription)")
}
}
}