如何使用 Swift 在 iOS 中同时录制和播放音频?

Posted

技术标签:

【中文标题】如何使用 Swift 在 iOS 中同时录制和播放音频?【英文标题】:How to record and play audio simultaneously in iOS using Swift? 【发布时间】:2016-08-26 09:55:54 【问题描述】:

在 Objective-C 中,同时录制和播放音频相当简单。互联网上有大量的示例代码。但我想在 Swift 中使用音频单元/核心音频同时录制和播放音频。使用 Swift 的帮助和示例代码很少。而且我找不到任何可以展示如何实现这一目标的帮助。

我正在为以下代码苦苦挣扎。

let preferredIOBufferDuration = 0.005 
let kInputBus  = AudioUnitElement(1)
let kOutputBus = AudioUnitElement(0)

init() 
    // This is my Audio Unit settings code.
    var status: OSStatus

    do 
        try AVAudiosession.sharedInstance().setPreferredIOBufferDuration(preferredIOBufferDuration)
     catch let error as NSError 
        print(error)
    


    var desc: AudioComponentDescription = AudioComponentDescription()
    desc.componentType = kAudioUnitType_Output
    desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO
    desc.componentFlags = 0
    desc.componentFlagsMask = 0
    desc.componentManufacturer = kAudioUnitManufacturer_Apple

    let inputComponent: AudioComponent = AudioComponentFindNext(nil, &desc)

    status = AudioComponentInstanceNew(inputComponent, &audioUnit)
    checkStatus(status)

    var flag = UInt32(1)
    status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, UInt32(sizeof(UInt32)))
    checkStatus(status)

    status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, UInt32(sizeof(UInt32)))
    checkStatus(status)

    var audioFormat: AudioStreamBasicDescription! = AudioStreamBasicDescription()
    audioFormat.mSampleRate = 8000
    audioFormat.mFormatID = kAudioFormatLinearPCM
    audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked
    audioFormat.mFramesPerPacket = 1
    audioFormat.mChannelsPerFrame = 1
    audioFormat.mBitsPerChannel = 16
    audioFormat.mBytesPerPacket = 2
    audioFormat.mBytesPerFrame = 2

    status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &audioFormat, UInt32(sizeof(UInt32)))
    checkStatus(status)


    try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord)
    status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, UInt32(sizeof(UInt32)))
    checkStatus(status)


    // Set input/recording callback
    var inputCallbackStruct: AURenderCallbackStruct! = AURenderCallbackStruct(inputProc: recordingCallback, inputProcRefCon: UnsafeMutablePointer(unsafeAddressOf(self)))
    inputCallbackStruct.inputProc = recordingCallback
    inputCallbackStruct.inputProcRefCon = UnsafeMutablePointer(unsafeAddressOf(self))
    status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &inputCallbackStruct, UInt32(sizeof(UInt32)))
    checkStatus(status)


    // Set output/renderar/playback callback
    var renderCallbackStruct: AURenderCallbackStruct! = AURenderCallbackStruct(inputProc: playbackCallback, inputProcRefCon: UnsafeMutablePointer(unsafeAddressOf(self)))
    renderCallbackStruct.inputProc = playbackCallback
    renderCallbackStruct.inputProcRefCon = UnsafeMutablePointer(unsafeAddressOf(self))
    status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &renderCallbackStruct, UInt32(sizeof(UInt32)))
    checkStatus(status)


    flag = 0
    status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_ShouldAllocateBuffer, kAudioUnitScope_Output, kInputBus, &flag, UInt32(sizeof(UInt32)))



func recordingCallback(inRefCon: UnsafeMutablePointer<Void>,
                      ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
                      inTimeStamp: UnsafePointer<AudioTimeStamp>,
                      inBufNumber: UInt32,
                      inNumberFrames: UInt32,
                      ioData: UnsafeMutablePointer<AudioBufferList>) -> OSStatus 

    print("recordingCallback got fired  >>>")


    return noErr




func playbackCallback(inRefCon: UnsafeMutablePointer<Void>,
                      ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
                      inTimeStamp: UnsafePointer<AudioTimeStamp>,
                      inBufNumber: UInt32,
                      inNumberFrames: UInt32,
                      ioData: UnsafeMutablePointer<AudioBufferList>) -> OSStatus 

    print("playbackCallback got fired  <<<")


    return noErr

使用该代码,只有recordingCallback 方法被调用。并且 playbackCallback 方法根本没有被解雇。我确定我在这里出了点问题。有人可以帮我解决这个问题。我正在努力解决这个问题。

【问题讨论】:

audioUnit 变量在哪里? 【参考方案1】:

您错误地设置了InputCallbackRenderCallback 方法。其他设置似乎还可以。所以你的init方法应该是这样的。

init() 

    var status: OSStatus

    do 
        try AVAudioSession.sharedInstance().setPreferredIOBufferDuration(preferredIOBufferDuration)
     catch let error as NSError 
        print(error)
    


    var desc: AudioComponentDescription = AudioComponentDescription()
    desc.componentType = kAudioUnitType_Output
    desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO
    desc.componentFlags = 0
    desc.componentFlagsMask = 0
    desc.componentManufacturer = kAudioUnitManufacturer_Apple

    let inputComponent: AudioComponent = AudioComponentFindNext(nil, &desc)

    status = AudioComponentInstanceNew(inputComponent, &audioUnit)
    checkStatus(status)

    var flag = UInt32(1)
    status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, UInt32(sizeof(UInt32)))
    checkStatus(status)

    status = AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &flag, UInt32(sizeof(UInt32)))
    checkStatus(status)

    var audioFormat: AudioStreamBasicDescription! = AudioStreamBasicDescription()
    audioFormat.mSampleRate = 8000
    audioFormat.mFormatID = kAudioFormatLinearPCM
    audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked
    audioFormat.mFramesPerPacket = 1
    audioFormat.mChannelsPerFrame = 1
    audioFormat.mBitsPerChannel = 16
    audioFormat.mBytesPerPacket = 2
    audioFormat.mBytesPerFrame = 2

    status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &audioFormat, UInt32(sizeof(UInt32)))
    checkStatus(status)


    try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord)
    status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioFormat, UInt32(sizeof(UInt32)))
    checkStatus(status)


    // Set input/recording callback
    var inputCallbackStruct = AURenderCallbackStruct(inputProc: recordingCallback, inputProcRefCon: UnsafeMutablePointer(unsafeAddressOf(self)))
    AudioUnitSetProperty(audioUnit, AudioUnitPropertyID(kAudioOutputUnitProperty_SetInputCallback), AudioUnitScope(kAudioUnitScope_Global), 1, &inputCallbackStruct, UInt32(sizeof(AURenderCallbackStruct)))


    // Set output/renderar/playback callback
    var renderCallbackStruct = AURenderCallbackStruct(inputProc: playbackCallback, inputProcRefCon: UnsafeMutablePointer(unsafeAddressOf(self)))
    AudioUnitSetProperty(audioUnit, AudioUnitPropertyID(kAudioUnitProperty_SetRenderCallback), AudioUnitScope(kAudioUnitScope_Global), 0, &renderCallbackStruct, UInt32(sizeof(AURenderCallbackStruct)))


    flag = 0
    status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_ShouldAllocateBuffer, kAudioUnitScope_Output, kInputBus, &flag, UInt32(sizeof(UInt32)))

尝试使用此代码,如果有帮助,请告诉我们。

【讨论】:

如何使用此代码进行录音和像麦克风一样播放,同时它会在扬声器中播放 在哪里可以指定要从哪个设备进行录制?即 iPhone 与耳机?

以上是关于如何使用 Swift 在 iOS 中同时录制和播放音频?的主要内容,如果未能解决你的问题,请参考以下文章

在 iOS 中使用耳机插孔引脚同时播放和录制音频?

在 iOS 中以不同的采样率同时录制和播放

同时播放和录制

如何同时录制使用 AVPlayer 和 wav 文件播放的歌曲?

如何在iOS中一次录制前后摄像头的视频

如何在 Swift 中连续录制音频并播放同一文件中的音频?