使用AVAudioEngine播放AVAudioPCMBuffer中的音频

8

我有两个类,分别是MicrophoneHandlerAudioPlayer。我已经成功使用AVCaptureSession来获取麦克风数据,方法在这里可以找到,并使用以下函数将CMSampleBuffer转换为NSData

func sendDataToDelegate(buffer: CMSampleBuffer!)
{
    let block = CMSampleBufferGetDataBuffer(buffer)
    var length = 0
    var data: UnsafeMutablePointer<Int8> = nil

    var status = CMBlockBufferGetDataPointer(block!, 0, nil, &length, &data)    // TODO: check for errors

    let result = NSData(bytesNoCopy: data, length: length, freeWhenDone: false)

    self.delegate.handleBuffer(result)
}

现在我想通过将上面生成的NSData转换为AVAudioPCMBuffer并使用AVAudioEngine播放音频。我的AudioPlayer类如下:

var engine: AVAudioEngine!
var playerNode: AVAudioPlayerNode!
var mixer: AVAudioMixerNode!

override init()
{
    super.init()

    self.setup()
    self.start()
}

func handleBuffer(data: NSData)
{
    let newBuffer = self.toPCMBuffer(data)
    print(newBuffer)

    self.playerNode.scheduleBuffer(newBuffer, completionHandler: nil)
}

func setup()
{
    self.engine = AVAudioEngine()
    self.playerNode = AVAudioPlayerNode()

    self.engine.attachNode(self.playerNode)
    self.mixer = engine.mainMixerNode

    engine.connect(self.playerNode, to: self.mixer, format: self.mixer.outputFormatForBus(0))
}

func start()
{
    do {
        try self.engine.start()
    }
    catch {
        print("error couldn't start engine")
    }

    self.playerNode.play()
}

func toPCMBuffer(data: NSData) -> AVAudioPCMBuffer
{
    let audioFormat = AVAudioFormat(commonFormat: AVAudioCommonFormat.PCMFormatFloat32, sampleRate: 8000, channels: 2, interleaved: false)  // given NSData audio format
    let PCMBuffer = AVAudioPCMBuffer(PCMFormat: audioFormat, frameCapacity: UInt32(data.length) / audioFormat.streamDescription.memory.mBytesPerFrame)

    PCMBuffer.frameLength = PCMBuffer.frameCapacity

    let channels = UnsafeBufferPointer(start: PCMBuffer.floatChannelData, count: Int(PCMBuffer.format.channelCount))

    data.getBytes(UnsafeMutablePointer<Void>(channels[0]) , length: data.length)

    return PCMBuffer
}

当在上面的第一个代码片段中调用self.delegate.handleBuffer(result)时,缓冲区会到达handleBuffer:buffer函数。

我可以使用print(newBuffer)查看转换后缓冲区的内存位置,但是没有任何声音从扬声器中输出。我只能想象在NSData的转换过程中存在不一致的地方。有什么建议吗?先谢谢了。

1个回答

2

跳过原始的 NSData 格式

为什么不全部使用 AVAudioPlayer 呢?如果你确实需要 NSData,你可以从下面的 soundURL 中加载此类数据。在这个示例中,磁盘缓冲区类似于:

let soundURL = documentDirectory.URLByAppendingPathComponent("sound.m4a")

为了最优化内存和资源管理,直接将录音记录到文件中是有意义的。您可以通过以下方式获取NSData

let data = NSFileManager.defaultManager().contentsAtPath(soundURL.path())

以下代码就是你所需要的:
记录
if !audioRecorder.recording {
    let audioSession = AVAudioSession.sharedInstance()
    do {
        try audioSession.setActive(true)
        audioRecorder.record()
    } catch {}
}

播放

if (!audioRecorder.recording){
    do {
        try audioPlayer = AVAudioPlayer(contentsOfURL: audioRecorder.url)
        audioPlayer.play()
    } catch {}
}

安装

let audioSession = AVAudioSession.sharedInstance()
do {
    try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
    try audioRecorder = AVAudioRecorder(URL: self.directoryURL()!,
        settings: recordSettings)
    audioRecorder.prepareToRecord()
} catch {}

设置

let recordSettings = [AVSampleRateKey : NSNumber(float: Float(44100.0)),
    AVFormatIDKey : NSNumber(int: Int32(kAudioFormatMPEG4AAC)),
    AVNumberOfChannelsKey : NSNumber(int: 1),
    AVEncoderAudioQualityKey : NSNumber(int: Int32(AVAudioQuality.Medium.rawValue))]

下载 Xcode 项目:

您可以在 这里 找到此示例。从Swift Recipes下载完整项目,该项目可在模拟器和设备上记录和播放。


我们需要NSData原始格式以通过套接字进行传输。我们正在流式传输音频。 - Connor Hicks
将文件转换为NSData是开箱即用的。你能把文件看作一个缓冲区吗?它恰好是一个磁盘缓冲区,但它仍然只是一个缓冲区。 - SwiftArchitect
每次我们通过套接字发送NSData数据包时,如何清除缓冲区?@SwiftArchitect - Connor Hicks
你需要使用循环缓冲区进行流式传输:https://dev59.com/x17Va4cB1Zd3GeqPL6zn#8646156 - SwiftArchitect
@SwiftArchitect 我们可以使用上述代码从麦克风传输实时数据吗?因为我需要实时的NSData通过套接字传输并在另一台设备上播放。将其保存到文件中,我认为无法进行实时流传输。您有什么建议吗?谢谢 - Amey
这个问题很久了,我处于与@ConnorHicks相同的情况,你解决了上述问题吗? - Hattori Hanzō

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接