AudioUnit - 在 Swift 中控制左声道和右声道输出

Posted

技术标签:

【中文标题】AudioUnit - 在 Swift 中控制左声道和右声道输出【英文标题】:AudioUnit - Control left channel and right channel output in Swift 【发布时间】:2017-11-24 07:57:22 【问题描述】:

我正在尝试与 swift 同时录制和播放。我需要分别在左声道和右声道播放。我成功地使用 AudioUnit 在一个通道中录制和播放。但是在我尝试使用两个缓冲区来控制两个通道之后,它们都是静音的。我是这样设置格式的:

    var audioFormat = AudiostreamBasicDescription()
    audioFormat.mSampleRate = Double(sampleRate)
    audioFormat.mFormatID = kAudioFormatLinearPCM
    audioFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked
    audioFormat.mChannelsPerFrame = 2
    audioFormat.mFramesPerPacket = 1
    audioFormat.mBitsPerChannel = 32
    audioFormat.mBytesPerPacket = 8
    audioFormat.mReserved = 0

这是我的输入回调

    private let inputCallback: AURenderCallback = (
    inRefCon,
    ioActionFlags,
    inTimeStamp,
    inBusNumber,
    inNumberFrames,
    ioData) -> OSStatus in
    let audioRAP:AudioUnitSample = Unmanaged<AudioUnitSample>.fromOpaque(inRefCon).takeUnretainedValue()
    var status = noErr;
    var buf = UnsafeMutableRawPointer.allocate(bytes: Int(inNumberFrames * 4),
                                               alignedTo: MemoryLayout<Int8>.alignment)
    let bindptr = buf.bindMemory(to: Float.self,
                                 capacity: Int(inNumberFrames * 4))
    bindptr.initialize(to: 0)
    var buffer: AudioBuffer = AudioBuffer(mNumberChannels: 2,
                             mDataByteSize: inNumberFrames * 4,
                             mData: buf)

    memset(buffer.mData, 0, Int(buffer.mDataByteSize))
    var bufferList: AudioBufferList = AudioBufferList(mNumberBuffers: 1,
                                     mBuffers: buffer)(Int(bufferList.mBuffers.mDataByteSize))")

    status = AudioUnitRender(audioRAP.newAudioUnit!,
                             ioActionFlags,
                             inTimeStamp,
                             inBusNumber,
                             inNumberFrames,
                             &bufferList)
   audioRAP.audioBuffers.append((bufferList.mBuffers,Int(inNumberFrames * 4)))

    return status

这是我的输出回调:

    private let outputCallback:AURenderCallback = 
    (inRefCon,
    ioActionFlags,
    inTimeStamp,
    inBusNumber,
    inNumberFrames,
    ioData) -> OSStatus in
    let audioRAP:AudioUnitSample = Unmanaged<AudioUnitSample>.fromOpaque(inRefCon).takeUnretainedValue()
    if ioData == nil
        return noErr
    
    ioData!.pointee.mNumberBuffers = 2
    var bufferCount = ioData!.pointee.mNumberBuffers

        var tempBuffer = audioRAP.audioBuffers[0]

        var monoSamples = [Float]()
        let ptr1 = tempBuffer.0.mData?.assumingMemoryBound(to: Float.self)
        monoSamples.removeAll()
        monoSamples.append(contentsOf: UnsafeBufferPointer(start: ptr1, count: Int(inNumberFrames)))

        let abl = UnsafeMutableAudioBufferListPointer(ioData)
        let bufferLeft = abl![0]
        let bufferRight = abl![1]
        let pointerLeft: UnsafeMutableBufferPointer<Float32> = UnsafeMutableBufferPointer(bufferLeft)
        let pointerRight: UnsafeMutableBufferPointer<Float32> = UnsafeMutableBufferPointer(bufferRight)

        for frame in 0..<inNumberFrames 
            let pointerIndex = pointerLeft.startIndex.advanced(by: Int(frame))
            pointerLeft[pointerIndex] = monoSamples[Int(frame)]
        
        for frame in 0..<inNumberFrames 
            let pointerIndex = pointerRight.startIndex.advanced(by: Int(frame))
            pointerRight[pointerIndex] = monoSamples[Int(frame)]
        

        tempBuffer.0.mData?.deallocate(bytes:tempBuffer.1, alignedTo: MemoryLayout<Int8>.alignment)
        audioRAP.audioBuffers.removeFirst()
    return noErr

这里是audiobuffer的声明:

    private var audioBuffers = [(AudioBuffer, Int)]()

我错过了输出或输入部分的内容吗?任何帮助将不胜感激!

【问题讨论】:

在 2017 年 WWDC 会议上,关于 Core Audio 的新功能(视频可用),Apple 工程师建议不要在音频上下文中使用 Swift 或 Objective C 代码。直接的 C 代码(或者可能转换为没有内存管理的直接 C 代码的 Swift 代码)在音频单元回调中可能没问题。 是的,Core Audio 似乎不能很好地转换为 swift。但出于某种原因,我仍然需要找到一些方法。 【参考方案1】:

第一个大问题是你的代码在里面做内存分配 音频回调。 Apple 文档明确指出,不应该在音频上下文中完成内存管理、同步甚至对象消息传递。在音频回调中,您可能只想坚持只对预先分配的缓冲区进行音频样本的数据复制。其他一切(尤其是缓冲区创建和释放)都应该在音频回调之外完成。

【讨论】:

感谢您的回答,是的,您是对的。我不应该在音频回调中管理内存。我要做的是在 inputCallback 中执行 AudioUnitRender 之后。我可以从缓冲区列表中获取数据。当我在 outputcallback 中将它发送回 ioData 时,我确实听到了扬声器的声音。我猜ioData应该有两个可以控制左声道和右声道的缓冲区。所以我更改 ioData!.pointee.mNumberBuffers = 2,我发现第二个缓冲区没有分配。(这样我尝试管理内存)我相信 ioData 没有通过两个缓冲区将数据设置到左通道和右通道。 我还是不知道如何静音左声道或右声道?有什么提示吗? @hotpaw2

以上是关于AudioUnit - 在 Swift 中控制左声道和右声道输出的主要内容,如果未能解决你的问题,请参考以下文章

IOS AudioUnit播放爆裂问题(swift)

使用 MIDI 键盘 OSX 控制 AudioUnit

如何让 iOS 应用识别为 AudioUnit?

来自AudioUnitRender swift的paramErr -50

从命令行控制 AudioUnits?

找不到框架 AudioUnit