You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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实现静音但无效,现提供有效解决方法:

问题根源

  1. 数据类型不匹配:音频格式配置的是32位浮点(kAudioFormatFlagsNativeFloatPacked),但代码中用SInt16*(16位整数)处理缓冲区,导致填充0的操作实际写入的是16位0,而音频硬件期望32位浮点数据,会被解析为非静音的错误值,引发爆音。
  2. 缓冲区剩余部分未处理:当可用数据不足时,仅复制了现有数据,剩余目标缓冲区区域保留内存垃圾数据,播放时产生爆音。

解决方法

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 07:36:02