在 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 中)吗?