如何在AURenderCallback中将两个Audio Unit合并到AudioBufferList
解决方案:合并buffList1与buffList2到ioData
首先要明确你需要的是音频混音(叠加播放)还是音频拼接(先后播放),两种场景的实现逻辑不同,以下是具体实现:
前提确认
- 确保
buffList1、buffList2与ioData的音频格式完全一致(采样率、位深、通道数、字节序) - 保证
ioData的缓冲区有足够空间容纳合并后的数据,避免内存越界
场景1:音频混音(两个音频叠加)
如果需要同时播放两个音频,将波形叠加,需逐样本相加并处理溢出:
32位浮点PCM(iOS/macOS常见格式)
OSStatus PlayCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { ALNPlayer *player = (__bridge ALNPlayer *)inRefCon; OSStatus status; // 读取音频1数据到buffList1,检查错误 status = AudioConverterFillComplexBuffer(player->audioConverter1, lyInInputDataProcV1, inRefCon, &inNumberFrames, player->buffList1, NULL); if (status != noErr) return status; // 读取音频2数据到buffList2,检查错误 status = AudioConverterFillComplexBuffer(player->audioConverter2, lyInInputDataProcV2, inRefCon, &inNumberFrames, player->buffList2, NULL); if (status != noErr) return status; // 转换为浮点指针(单通道场景) float *ioPtr = (float *)ioData->mBuffers[0].mData; float *buff1Ptr = (float *)player->buffList1->mBuffers[0].mData; float *buff2Ptr = (float *)player->buffList2->mBuffers[0].mData; // 逐样本叠加,限幅防止失真(浮点范围[-1.0, 1.0]) for (UInt32 i = 0; i < inNumberFrames; i++) { float mixed = buff1Ptr[i] + buff2Ptr[i]; ioPtr[i] = fmaxf(fminf(mixed, 1.0f), -1.0f); } // 更新ioData的字节数 ioData->mBuffers[0].mDataByteSize = inNumberFrames * sizeof(float); return noErr; }
16位整型PCM
如果是16位整数格式,需用32位整型中转避免溢出:
// 转换为16位整型指针 SInt16 *ioPtr = (SInt16 *)ioData->mBuffers[0].mData; SInt16 *buff1Ptr = (SInt16 *)player->buffList1->mBuffers[0].mData; SInt16 *buff2Ptr = (SInt16 *)player->buffList2->mBuffers[0].mData; for (UInt32 i = 0; i < inNumberFrames; i++) { SInt32 sum = (SInt32)buff1Ptr[i] + (SInt32)buff2Ptr[i]; // 限幅到16位范围[-32768, 32767] sum = fmaxf(fminf(sum, 32767), -32768); ioPtr[i] = (SInt16)sum; } ioData->mBuffers[0].mDataByteSize = inNumberFrames * sizeof(SInt16);
场景2:音频拼接(先播放buffList1,再播放buffList2)
如果需要按顺序播放两个音频,直接拼接缓冲区即可:
OSStatus PlayCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { ALNPlayer *player = (__bridge ALNPlayer *)inRefCon; OSStatus status; // 读取音频1数据到buffList1 status = AudioConverterFillComplexBuffer(player->audioConverter1, lyInInputDataProcV1, inRefCon, &inNumberFrames, player->buffList1, NULL); if (status != noErr) return status; // 读取音频2数据到buffList2 status = AudioConverterFillComplexBuffer(player->audioConverter2, lyInInputDataProcV2, inRefCon, &inNumberFrames, player->buffList2, NULL); if (status != noErr) return status; UInt32 buff1Size = player->buffList1->mBuffers[0].mDataByteSize; UInt32 buff2Size = player->buffList2->mBuffers[0].mDataByteSize; // 先复制buffList1到ioData起始位置 memcpy(ioData->mBuffers[0].mData, player->buffList1->mBuffers[0].mData, buff1Size); // 再复制buffList2到buffList1的末尾 void *offsetPtr = (uint8_t *)ioData->mBuffers[0].mData + buff1Size; memcpy(offsetPtr, player->buffList2->mBuffers[0].mData, buff2Size); // 更新ioData的总字节数 ioData->mBuffers[0].mDataByteSize = buff1Size + buff2Size; return noErr; }
额外注意事项
- 多通道场景:如果是立体声或多通道,需要遍历
AudioBufferList中的每个mBuffers元素,分别处理每个通道的数据 - 缓冲区大小:拼接场景下,要确保
ioData的缓冲区大小≥buff1Size + buff2Size,否则会触发内存越界 - 错误处理:每次调用
AudioConverterFillComplexBuffer后必须检查返回状态,避免处理无效数据
内容的提问来源于stack exchange,提问作者Alan Luo
相关产品推荐
相关产品推荐

