播放 iOS 的远程音频缓冲区时如何避免机器人声音?

Posted

技术标签:

【中文标题】播放 iOS 的远程音频缓冲区时如何避免机器人声音?【英文标题】:How to avoid robotic sound when play remote audio buffer for iOS? 【发布时间】:2012-11-22 15:31:04 【问题描述】:

我只是尝试开发一个 VOIP 应用程序,

从 RecordingCallBack 获取的音频缓冲区将被包装 到 NSData,然后通过 GCDAsyncSocket 发送到远程端

并且远程端将获取 NSData,解包为音频 缓冲区,然后 PlayingCallBack 将获取音频缓冲区。

到目前为止,我的计划正在运行,在本地运行良好(套接字将数据发送到本地,并在本地播放缓冲区)

但是当它在两台设备上运行时(一台真正的 iphone-4s,一台模拟器) 声音会变得陌生,听起来像机器人声音

有没有办法避免机器人音效?

这是我的 AudioUnit 设置:

#pragma mark - Init Methods

- (void)initAudioUint

    OSStatus status;

    // Describe audio component
    AudioComponentDescription desc;
    desc.componentType = kAudioUnitType_Output;
    desc.componentSubType = kAudioUnitSubType_RemoteIO;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;

    // Get component
    AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);

    // Get audio units
    status = AudioComponentInstanceNew(inputComponent, &audioUnit);
    checkStatus(status);

    // Enable IO for recording
    UInt32 flag = 1;
    status = AudioUnitSetProperty(audioUnit,
                                  kAudioOutputUnitProperty_EnableIO,
                                  kAudioUnitScope_Input,
                                  kInputBus,
                                  &flag,
                                  sizeof(flag));
    checkStatus(status);

    // Enable IO for playback
    status = AudioUnitSetProperty(audioUnit,
                                  kAudioOutputUnitProperty_EnableIO,
                                  kAudioUnitScope_Output,
                                  kOutputBus,
                                  &flag,
                                  sizeof(flag));
    checkStatus(status);

    // Describe format
    AudiostreamBasicDescription audioFormat;
    audioFormat.mSampleRate = 44100.0f; // FS
    audioFormat.mFormatID = kAudioFormatLinearPCM;
    audioFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
    audioFormat.mChannelsPerFrame = 1; // stereo output
    audioFormat.mFramesPerPacket = 1;
    audioFormat.mBitsPerChannel = sizeof(short) * 8; // 16-bit
    audioFormat.mBytesPerFrame = audioFormat.mBitsPerChannel / 8 * audioFormat.mChannelsPerFrame;
    audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;

    // Apply format
    status = AudioUnitSetProperty(audioUnit,
                                  kAudioUnitProperty_StreamFormat,
                                  kAudioUnitScope_Output,
                                  kInputBus,
                                  &audioFormat,
                                  sizeof(audioFormat));
    checkStatus(status);
    status = AudioUnitSetProperty(audioUnit,
                                  kAudioUnitProperty_StreamFormat,
                                  kAudioUnitScope_Input,
                                  kOutputBus,
                                  &audioFormat,
                                  sizeof(audioFormat));
    checkStatus(status);


    // Set input callback
    AURenderCallbackStruct callbackStruct;
    callbackStruct.inputProc = recordingCallback;
    callbackStruct.inputProcRefCon = (__bridge void*)self;
    status = AudioUnitSetProperty(audioUnit,
                                  kAudioOutputUnitProperty_SetInputCallback,
                                  kAudioUnitScope_Global,
                                  kInputBus,
                                  &callbackStruct,
                                  sizeof(callbackStruct));
    checkStatus(status);


    // Set output callback
    callbackStruct.inputProc = playbackCallback;
    callbackStruct.inputProcRefCon = (__bridge void*)self;
    status = AudioUnitSetProperty(audioUnit,
                                  kAudioUnitProperty_SetRenderCallback,
                                  kAudioUnitScope_Global,
                                  kOutputBus,
                                  &callbackStruct,
                                  sizeof(callbackStruct));
    checkStatus(status);


    /*
    // Disable buffer allocation for the recorder (optional - do this if we want to pass in our own)
    flag = 0;
    status = AudioUnitSetProperty(audioUnit,
                                  kAudioUnitProperty_ShouldAllocateBuffer,
                                  kAudioUnitScope_Output,
                                  kInputBus,
                                  &flag,
                                  sizeof(flag));

    // Allocate our own buffers (1 channel, 16 bits per sample, thus 16 bits per frame, thus 2 bytes per frame).
    // Practice learns the buffers used contain 512 frames, if this changes it will be fixed in processAudio.
    tempBuffer.mNumberChannels = 1;
    tempBuffer.mDataByteSize = 512 * 2;
    tempBuffer.mData = malloc( 512 * 2 );
    checkStatus(status);


    // Disable buffer allocation for the recorder (optional - do this if we want to pass in our own)
    flag = 0;
    status = AudioUnitSetProperty(audioUnit,
                                  kAudioUnitProperty_ShouldAllocateBuffer,
                                  kAudioUnitScope_Output,
                                  kInputBus,
                                  &flag,
                                  sizeof(flag));

    // TODO: Allocate our own buffers if we want
    */

    // Initialise
    status = AudioUnitInitialize(audioUnit);
    checkStatus(status);

    conversionBuffer = (SInt16 *) malloc(1024 * sizeof(SInt16));

顺便说一句,有没有办法设置 audioFormat.mFramesPerPacket > 1 ?

在我的情况下,如果参数 > 1,它将打印错误。

我正在考虑发送一个包含多帧的缓冲区(对于 获取更多的时间在远程播放),它应该比 为 VOIP 发送一帧一包?

【问题讨论】:

你解决了这个问题吗?提前感谢@PatrickSCLin 【参考方案1】:

由于两个设备的音频采样率时钟不会完全同步,您将不得不处理由于轻微的采样率不匹配以及网络延迟抖动而导致的缓冲区下溢和溢出。

还要注意,发送到 RemoteIO 回调的缓冲区大小可能不会保持不变,因此两个回调必须能够处理缓冲区大小不匹配的问题。

【讨论】:

我该怎么办?有什么可以参考的吗?【参考方案2】:

我刚刚解决了这个问题!

需要设置音频会话的属性,确保两台设备的BufferDuration相同

    // set preferred buffer size
    Float32 audioBufferSize = (set up the duration);
    UInt32 size = sizeof(audioBufferSize);
    result = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration,
                           size, &audioBufferSize);

【讨论】:

在应用程序的整个生命周期中分配的缓冲区大小是否保持不变?

以上是关于播放 iOS 的远程音频缓冲区时如何避免机器人声音?的主要内容,如果未能解决你的问题,请参考以下文章

如何正确使用 iOS AudioUnit 渲染回调

哪个 API 从 ios 和 osx 中的缓冲区播放音频?

Linux如何在RAM缓冲区中录制声音并以自定义延迟播放音频

收到远程通知时如何播放声音?

如何在应用程序处于后台模式时在 iOS 10 中播放自定义音频

iOS - 在录制视频时播放音频/声音