在 Objective C 中连接音频缓冲区

Posted

技术标签:

【中文标题】在 Objective C 中连接音频缓冲区【英文标题】:Concatenating Audio Buffers in ObjectiveC 【发布时间】:2014-10-31 14:26:39 【问题描述】:

首先,我是 c 和目标 c 的新手

我尝试 fft 一个音频缓冲区并绘制它的图表。 我使用音频单元回调来获取音频缓冲区。回调带来 512 帧,但在 471 帧之后它带来 0。(我不知道这是否正常。它曾经带来 471 帧充满数字。但现在不知何故 512 帧在 471 之后为 0。请让我知道这是否正常)

无论如何。我可以从回调中获取缓冲区,应用 fft 并绘制它。这很完美。这是下面的结果。只要我在每个回调中获得缓冲区,图形就非常平滑

但在我的情况下,我需要 3 秒的缓冲区才能应用 fft 和绘图。所以我尝试连接来自两个回调的缓冲区,然后应用 fft 并绘制它。但结果并不像我预期的那样。虽然上面的在记录过程中非常平滑和精确(只有 18 和 19 khz 的幅度变化),但当我连接两个缓冲区时,模拟器主要显示两个不同的视图,它们之间的交换速度非常快。它们显示在下面。当然,它们基本上显示 18 和 19 khz。但我需要精确的 khz,这样我就可以为我正在开发的应用程序应用更多算法。

这是我的回调代码

//FFTInputBufferLen, FFTInputBufferFrameIndex is gloabal
//also tempFilteredBuffer is allocated in global

//by the way FFTInputBufferLen = 1024;

static OSStatus performRender (void                         *inRefCon,
                           AudioUnitRenderActionFlags   *ioActionFlags,
                           const AudioTimeStamp         *inTimeStamp,
                           UInt32                       inBusNumber,
                           UInt32                       inNumberFrames,
                           AudioBufferList              *ioData)

    UInt32 bus1 = 1;
    CheckError(AudioUnitRender(effectState.rioUnit,
                           ioActionFlags,
                           inTimeStamp,
                           bus1,
                           inNumberFrames,
                           ioData), "Couldn't render from RemoteIO unit");


Float32 * renderBuff = ioData->mBuffers[0].mData;

ViewController *vc = (__bridge ViewController *) inRefCon;

    // inNumberFrames comes 512 as I described above
    for (int i = 0; i < inNumberFrames ; i++)        
    

        //I defined InputBuffers[5] in global. 
        //then added 5 Float32 InputBuffers and allocated in global

        InputBuffers[bufferCount][FFTInputBufferFrameIndex] = renderBuff[i];  
        FFTInputBufferFrameIndex ++;

        if(FFTInputBufferFrameIndex == FFTInputBufferLen)
        
            int bufCount = bufferCount;

            dispatch_async( dispatch_get_main_queue(), ^

                tempFilteredBuffer = [vc FilterData_rawSamples:InputBuffers[bufCount] numSamples:FFTInputBufferLen];
                [vc CalculateFFTwithPlotting_Data:tempFilteredBuffer NumberofSamples:FFTInputBufferLen ];

                free(InputBuffers[bufCount]);
                InputBuffers[bufCount] = (Float32*)malloc(sizeof(Float32) * FFTInputBufferLen);
            );

            FFTInputBufferFrameIndex = 0;
            bufferCount ++;
            if (bufferCount == 5)
            
                bufferCount = 0;
            
        

    

return noErr;

这是我的 AudioUnit 设置

- (void)setupIOUnit


AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;

AudioComponent comp = AudioComponentFindNext(NULL, &desc);
CheckError(AudioComponentInstanceNew(comp, &_rioUnit), "couldn't create a new instance of AURemoteIO");


UInt32 one = 1;
CheckError(AudioUnitSetProperty(_rioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof(one)), "could not enable input on AURemoteIO");

// I removed this in order to not getting recorded audio back on speakers! Am I right?
//CheckError(AudioUnitSetProperty(_rioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &one, sizeof(one)), "could not enable output on AURemoteIO");


UInt32 maxFramesPerSlice = 4096;
CheckError(AudioUnitSetProperty(_rioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(UInt32)), "couldn't set max frames per slice on AURemoteIO");

UInt32 propSize = sizeof(UInt32);
CheckError(AudioUnitGetProperty(_rioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, &propSize), "couldn't get max frames per slice on AURemoteIO");


AudioUnitElement bus1 = 1;

AudiostreamBasicDescription myASBD;

myASBD.mSampleRate = 44100;
myASBD.mChannelsPerFrame = 1;

myASBD.mFormatID = kAudioFormatLinearPCM;
myASBD.mBytesPerFrame = sizeof(Float32) * myASBD.mChannelsPerFrame ;
myASBD.mFramesPerPacket = 1;
myASBD.mBytesPerPacket = myASBD.mFramesPerPacket * myASBD.mBytesPerFrame;
myASBD.mBitsPerChannel = sizeof(Float32) * 8 ;
myASBD.mFormatFlags = 9 | 12 ;



 // I also remove this for not getting audio back!!

//    CheckError(AudioUnitSetProperty (_rioUnit,
//                                     kAudioUnitProperty_StreamFormat,
//                                     kAudioUnitScope_Input,
//                                     bus0,
//                                     &myASBD,
//                                     sizeof (myASBD)), "Couldn't set ASBD for RIO on input scope / bus 0");


CheckError(AudioUnitSetProperty (_rioUnit,
                                 kAudioUnitProperty_StreamFormat,
                                 kAudioUnitScope_Output,
                                 bus1,
                                 &myASBD,
                                 sizeof (myASBD)), "Couldn't set ASBD for RIO on output scope / bus 1");



effectState.rioUnit = _rioUnit;

AURenderCallbackStruct renderCallback;
renderCallback.inputProc = performRender;
renderCallback.inputProcRefCon = (__bridge void *)(self);
CheckError(AudioUnitSetProperty(_rioUnit,
                                kAudioUnitProperty_SetRenderCallback,
                                kAudioUnitScope_Input,
                                0,
                                &renderCallback,
                                sizeof(renderCallback)), "couldn't set render callback on AURemoteIO");

CheckError(AudioUnitInitialize(_rioUnit), "couldn't initialize AURemoteIO instance");


我的问题是:为什么会发生这种情况,为什么当我连接两个缓冲区时输出有两个主要的不同视图。还有另一种收集缓冲区和应用 DSP 的方法吗?我做错了什么!如果我连接的方式是正确的,我的逻辑不正确吗? (虽然我检查了很多次)

在这里我想说:我怎样才能获得完美状态的 3 sn 缓冲区

我真的需要帮助,最好的问候

【问题讨论】:

这听起来你的渲染回调中有太多的计算步骤。只有两个提示:降低采样率或用简单的东西替换dispatch_async 部分,看看我是对还是错。 嗨,迈克尔,感谢您的评论。我需要 44100 的采样率,而且我是新手,老实说,我除了 dispatch_async 什么都不知道 【参考方案1】:

您的渲染回调可能会将数据写入另一个线程(主队列)正在处理的同一个缓冲区,从而覆盖和更改正在处理的部分数据。

尝试使用多个缓冲区。不要写入仍在处理的缓冲区(通过您的过滤器和 fft 方法)。或许在 FFT 计算方法完成后回收缓冲区以供重复使用。

【讨论】:

非常感谢 hotpaw2 的贡献。我已经添加了缓冲区,现在它更好了,但我仍然得到了 18 和 19 khz 声音的相同图表。当声音变得稳定时,情节不稳定。我从 512 怀疑来自每个渲染的帧。正如我提到的,它们有 512 个数据,但只有前 471 个有数据。剩下的是 0.0000 。我不知道为什么?我不知道这是否正常?我还添加了 setupAudioUnit。我只需要录制,所以我将会话类别设置为 AVAudioSessionCategoryRecord 。非常感谢您的帮助 我发现了问题。当我将会话类别设置为 AVAudioSessionCategoryPlayAndRecord 然后当我注释掉上面代码中提到的 2 AuidoUnitSetProperty 行时,连接缓冲区效果很好。它又像我在上面添加的第一张图。在这种情况下,每次渲染带来 470-471 帧。所以添加它们是有效的。但现在它播放录制的音频。我不希望它播放录制的音频。我很高兴让它工作,但也很困惑为什么当我让它 playAndRecord 时它会起作用?我相信你对此有所了解。 :) 提前致谢,问候【参考方案2】:

我已经成功地连接了缓冲区,没有任何不稳定的图形。我是怎么做的是将 AVAudioSession 类别从 Record 转换为 PlayAndRecord。然后我注释掉了两条 AudioUnitSetProperty 行。然后我开始每次渲染获得 470~471 帧。然后我像在我发布的代码上一样取消它们。我也在代码中使用了缓冲区。现在它起作用了。但现在它通过声音播放。为了关闭它,我应用了下面的代码

for (UInt32 i=0; i<ioData->mNumberBuffers; ++i)

    memset(ioData->mBuffers[i].mData, 0, ioData->mBuffers[i].mDataByteSize);

然后我开始获得 3 秒的缓冲区。当我在屏幕上绘制它时,我得到了第一张图的类似视图

【讨论】:

以上是关于在 Objective C 中连接音频缓冲区的主要内容,如果未能解决你的问题,请参考以下文章

我可以同时从音频流缓冲区写入和播放(在 NAudio 中)吗?

C# Naaudio BufferedWaveProvider 缓冲区满异常

使用 ExoPlayer 缓冲音频直播

访问 AVAudioSession 的音频缓冲区

获取系统音频流缓冲区以进行可视化

如何从音频队列缓冲区中提取整数样本并将修改后的样本写回?