使用 AVAudioEngine 从 AVAudioPCMBuffer 播放音频

Posted

技术标签:

【中文标题】使用 AVAudioEngine 从 AVAudioPCMBuffer 播放音频【英文标题】:Play audio from AVAudioPCMBuffer with AVAudioEngine 【发布时间】:2016-02-27 15:51:58 【问题描述】:

我有两个班级,MicrophoneHandlerAudioPlayer。我已经设法使用AVCaptureSession 使用批准的答案here 挖掘麦克风数据,并使用此功能将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 播放它来通过扬声器播放音频。我的AudioPlayerclass如下:

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

当在上面的第一个 sn-p 中调用 self.delegate.handleBuffer(result) 时,缓冲区到达 handleBuffer:buffer 函数。

我可以print(newBuffer),看到转换后的缓冲区的内存位置,但扬声器什么也没有。我只能想象与NSData 之间的转换之间存在不一致的地方。有任何想法吗?提前致谢。

【问题讨论】:

【参考方案1】:

跳过原始的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 项目:

你可以找到这个例子here。从Swift Recipes下载完整的项目,它可以在模拟器和设备上录制和播放。

【讨论】:

我们需要 NSData 原始格式来通过套接字传输它。我们正在流式传输音频。 文件到NSData 开箱即用。你能把文件想象成一个缓冲区吗?恰好是磁盘缓冲区,但它仍然只是一个缓冲区? 如何在每次通过套接字发送 NSData 数据包时清除缓冲区? @SwiftArchitect 您需要一个循环缓冲区用于流式传输:***.com/a/8646156/218152 @SwiftArchitect 我们可以使用上面的 Coe 来传输来自麦克风的实时数据,因为我需要实时 nsdata 来通过套接字传输并在另一台设备上播放并且通过将其保存到文件中,我想我将无法直播你的建议?谢谢

以上是关于使用 AVAudioEngine 从 AVAudioPCMBuffer 播放音频的主要内容,如果未能解决你的问题,请参考以下文章

在 AVAudioEngine 中从网络流式传输数据,这可能吗?

我可以使用 AVAudioEngine 从文件中读取,使用音频单元处理并写入文件,比实时更快吗?

在 WatchOS 上使用 AVAudioEngine 录制时阻止播放

AvaudioEngine - 以特定采样率录制语音 AvaudioEngine for Analysis

使用 AVAudioEngine 录制音频文件

设置 AVAudioEngine 输入和输出设备