在回调函数中使用 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;
}
在 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;
}