iOS中使用Audio Unit播放BLE音频流出现爆音问题求助
解决BLE音频流播放时缓冲区空导致的爆音问题
在iOS应用中使用TPCircularBuffer和Audio Unit播放BLE音频流时,播放正常,但缓冲区无数据可播放时会出现爆音现象。
音频流配置
AudioStreamBasicDescription audioFormat; audioFormat.mSampleRate = 8000.00; audioFormat.mFormatID = kAudioFormatLinearPCM; audioFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsPacked; audioFormat.mFramesPerPacket = 1; audioFormat.mChannelsPerFrame = 1; audioFormat.mBitsPerChannel = 32; audioFormat.mBytesPerPacket = 4; audioFormat.mBytesPerFrame = 4;
当前播放回调函数
static OSStatus playbackCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { for (int i=0; i < ioData->mNumberBuffers; i++) { IosAudioController *THIS = (__bridge IosAudioController *)inRefCon; int bytesAskingByPlayback = ioData->mBuffers[i].mDataByteSize; SInt16 *targetBuffer = (SInt16*)ioData->mBuffers[i].mData; // Pull audio from playthrough buffer int32_t availableBytesFromBuffer; SInt16 *sBuffer = TPCircularBufferTail(THIS->addressOfTPBuffer, &availableBytesFromBuffer); int willRemainBytes = availableBytesFromBuffer - bytesAskingByPlayback; if (willRemainBytes > 0) { memcpy(targetBuffer, sBuffer, bytesAskingByPlayback); TPCircularBufferConsume(THIS->addressOfTPBuffer, bytesAskingByPlayback); } else { //Note: Mostly need to update code here memcpy(targetBuffer, sBuffer, availableBytesFromBuffer); TPCircularBufferConsume(THIS->addressOfTPBuffer, availableBytesFromBuffer); } } return noErr; }
缓冲区大小为16384,曾尝试填充0实现静音但无效,现提供有效解决方法:
问题根源
- 数据类型不匹配:音频格式配置的是32位浮点(
kAudioFormatFlagsNativeFloatPacked),但代码中用SInt16*(16位整数)处理缓冲区,导致填充0的操作实际写入的是16位0,而音频硬件期望32位浮点数据,会被解析为非静音的错误值,引发爆音。 - 缓冲区剩余部分未处理:当可用数据不足时,仅复制了现有数据,剩余目标缓冲区区域保留内存垃圾数据,播放时产生爆音。
解决方法
1. 修正数据类型
将所有缓冲区指针改为float*,匹配32位浮点的音频格式。
2. 处理数据不足的情况
当可用字节少于请求字节时,先复制现有数据,再将剩余部分填充为静音值(浮点0.0),或用最后一个有效采样值延续(避免音量突变,降低爆音感)。
修改后的回调函数
static OSStatus playbackCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { for (int i=0; i < ioData->mNumberBuffers; i++) { IosAudioController *THIS = (__bridge IosAudioController *)inRefCon; int bytesAskingByPlayback = ioData->mBuffers[i].mDataByteSize; int samplesAsking = bytesAskingByPlayback / sizeof(float); // 计算请求的采样数 float *targetBuffer = (float*)ioData->mBuffers[i].mData; // 改为float指针 // Pull audio from playthrough buffer int32_t availableBytesFromBuffer; float *sBuffer = (float*)TPCircularBufferTail(THIS->addressOfTPBuffer, &availableBytesFromBuffer); // 类型转换为float* int availableSamples = availableBytesFromBuffer / sizeof(float); int copiedSamples = 0; if (availableSamples > 0) { // 复制可用的采样数据 int copyCount = MIN(availableSamples, samplesAsking); memcpy(targetBuffer, sBuffer, copyCount * sizeof(float)); TPCircularBufferConsume(THIS->addressOfTPBuffer, copyCount * sizeof(float)); copiedSamples = copyCount; } // 处理剩余需要填充的部分 if (copiedSamples < samplesAsking) { float fillValue = 0.0f; // 静音值 // 如果需要避免突变,可以用最后一个有效采样值 // if (copiedSamples > 0) fillValue = targetBuffer[copiedSamples - 1]; // 填充剩余的缓冲区(静音方案) memset(&targetBuffer[copiedSamples], 0, (samplesAsking - copiedSamples) * sizeof(float)); // 或者用循环填充最后一个采样值(延续方案) // for (int j = copiedSamples; j < samplesAsking; j++) { // targetBuffer[j] = fillValue; // } } } return noErr; }
额外优化建议
- 调整TPCircularBuffer的大小,结合BLE传输延迟设置合适的预填充阈值,减少缓冲区空的触发频率。
- 在BLE数据接收端增加缓冲策略,确保音频数据稳定输入到TPCircularBuffer,降低断流概率。
内容的提问来源于stack exchange,提问作者Shivbaba's son
相关产品推荐
相关产品推荐

