在回调函数中使用 ExtAudioFileWriteAsync()。无法到达 运行

Using ExtAudioFileWriteAsync() in callback function. Can't get to run

在 Core Audio 中似乎无法走得太远。我的目标是将捕获的音频数据从仪器单元写入文件。我已经在仪器单元上设置了对回调函数的调用:

CheckError(AudioUnitAddRenderNotify(player->instrumentUnit,
                                    MyRenderProc,
                                    &player),
           "AudioUnitAddRenderNotify Failed");

我用这个设置了文件和 AudioStreamBasicDescription:

#define FILENAME @"output_IV.aif"

NSString *fileName = FILENAME; // [NSString stringWithFormat:FILENAME_FORMAT, hz];
NSString *filePath = [[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent: fileName];
NSURL *fileURL = [NSURL fileURLWithPath: filePath];
NSLog (@"path: %@", fileURL);

AudioStreamBasicDescription asbd;
memset(&asbd, 0, sizeof(asbd));
asbd.mSampleRate = 44100.0;
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mFormatFlags = kAudioFormatFlagIsBigEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
asbd.mChannelsPerFrame = 2; // CHANGED FROM 1 (STEREO)
asbd.mFramesPerPacket = 1;
asbd.mBitsPerChannel = 16;
asbd.mBytesPerFrame = 4;
asbd.mBytesPerPacket = 4;

CheckError(ExtAudioFileCreateWithURL((__bridge CFURLRef)fileURL, kAudioFileAIFFType, &asbd, NULL, kAudioFileFlags_EraseFile, &testRecordFile), "ExtAudioFileCreateWithURL failed"); 
CheckError(ExtAudioFileSetProperty(testRecordFile, kExtAudioFileProperty_ClientDataFormat, (UInt32)sizeof(asbd), &asbd), "ExtAudioFileSetProperty failed"); 
CheckError(ExtAudioFileWriteAsync(testRecordFile, 0, NULL), "ExtAudioFileWriteAsync 1st time failed");

我确认文件确实已创建。 testRecordFile 是全局定义的(这是我目前可以将内容发送到 运行 的唯一方法):

ExtAudioFileRef testRecordFile;

我的回调函数是:

OSStatus MyRenderProc(void *inRefCon,
                  AudioUnitRenderActionFlags *ioActionFlags,
                  const AudioTimeStamp *inTimeStamp,
                  UInt32 inBusNumber,
                  UInt32 inNumberFrames,
                  AudioBufferList * ioData)
{
    if (*ioActionFlags & kAudioUnitRenderAction_PostRender){
    static int TEMP_kAudioUnitRenderAction_PostRenderError = (1 << 8);
        if (!(*ioActionFlags & TEMP_kAudioUnitRenderAction_PostRenderError)){
            CheckError(ExtAudioFileWriteAsync(testRecordFile, inNumberFrames, ioData), "ExtAudioFileWriteAsync failed");
        }
    }
return noErr;
}

当我 运行 执行此操作时,程序会转动并在 ExtAudioFileWriteAsync 调用中进入调试器模式 (lldb)。 inNumberFrames = 512 并且我已验证我正在 ioData 中获取 Float32 音频数据的立体声通道。

我在这里错过了什么?

首先,你的代码还是有点复杂,包含了CoreAudio和Obj-C的一些"dark corners"。这是一个更安全的选择,首先确保在实时线程上一切都按普通 C 中的预期工作。一旦你调试了那部分代码,你就可以根据需要轻松添加尽可能多的 Obj-C 优雅。

如果为简单起见忽略可能的字节顺序和文件格式转换问题,仍然存在 一个问题 您必须使用 API 实用程序自动解决,或者 "manually":

AFAIK,ExtAudioFileWriteAsync() 的数据格式必须交错,而 AUGraph 的流格式是 而不是 。假设我们不在这里处理字节序和格式转换,这就是您可以手动修复它的方法(单通道示例)。如果您的 asbd 流格式是非交错立体声,您可以像这样在缓冲区中交错数据:LRLRLRLRLR...

OSStatus MyRenderProc(void *inRefCon, 
                  AudioUnitRenderActionFlags *ioActionFlags,
                  const AudioTimeStamp *inTimeStamp,
                  UInt32 inBusNumber,
                  UInt32 inNumberFrames,
                  AudioBufferList * ioData) 
{
AudioBufferList bufferList;
Float32 samples[inNumberFrames+inNumberFrames];

bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mData = samples;
bufferList.mBuffers[0].mNumberChannels = 1;
bufferList.mBuffers[0].mDataByteSize = (inNumberFrames+inNumberFrames)*sizeof(Float32);
Float32 *data = (Float32 *)ioData->mBuffers[0].mData;

if (*ioActionFlags & kAudioUnitRenderAction_PostRender){
    static int TEMP_kAudioUnitRenderAction_PostRenderError = (1 << 8);
    if (!(*ioActionFlags & TEMP_kAudioUnitRenderAction_PostRenderError) {  
        for(UInt32 i = 0; i < inNumberFrames; i++)
            samples[i+i] = samples [i+i+1] = data[i];//copy buffer[0] to L & R            
        ExtAudioFileWriteAsync(testRecordFile, inNumberFrames, &bufferList);
    }
}
return noErr;
}

这只是展示其工作原理的一个例子。通过研究 asbd.mFormatFlags 并在以下位置设置正确的格式:

ExtAudioFileSetProperty(testRecordFile,
                        kExtAudioFileProperty_ClientDataFormat,
                        s,
                        &asbd);

你可以更优雅地实现它,但这远远超出了你这个 post 的范围。

这是 16 位线性大端立体声 AIF 文件的工作回调:

OSStatus MyRenderProc(void *inRefCon,
                  AudioUnitRenderActionFlags *ioActionFlags,
                  const AudioTimeStamp *inTimeStamp,
                  UInt32 inBusNumber,
                  UInt32 inNumberFrames,
                  AudioBufferList * ioData)
{
    SInt16 samples[inNumberFrames + inNumberFrames];
    AudioBufferList bufferList;
    bufferList.mNumberBuffers = 1;
    bufferList.mBuffers[0].mData = samples;
    bufferList.mBuffers[0].mNumberChannels = 1;
    bufferList.mBuffers[0].mDataByteSize = (inNumberFrames + inNumberFrames) * sizeof(SInt16);

    Float32 *leftData = (Float32 *)ioData->mBuffers[0].mData;
    Float32 *rightData = (Float32 *)ioData->mBuffers[1].mData;

    if (*ioActionFlags & kAudioUnitRenderAction_PostRender){
        static int TEMP_kAudioUnitRenderAction_PostRenderError = (1 << 8);
        if (!(*ioActionFlags & TEMP_kAudioUnitRenderAction_PostRenderError)){
            for (UInt32 i = 0; i < inNumberFrames; i++) {
                samples[i + i] = CFSwapInt16HostToBig((SInt16) SHRT_MAX * (leftData)[i]);
                samples[i + i + 1] = CFSwapInt16HostToBig((SInt16) SHRT_MAX * (rightData)[i]);
            }
        CheckError(ExtAudioFileWriteAsync(testRecordFile, inNumberFrames, &bufferList), "ExtAudioFileWriteAsync failed");
        }
    }
    return noErr;
}