如何录制混音器单元输出产生的声音(iOS Core Audio&Audio Graph)

我试图录制混音器单元输出产生的声音。

目前,我的代码基于苹果的MixerHost iOS应用程序演示: 混音器节点连接到audio广告的远程IO节点

我尝试在混音器输出上的远程IO节点input上设置一个inputcallback

我做错了什么,但是我找不到错误。

这是下面的代码。 这是在多声道混音器单元安装之后完成的:

UInt32 flag = 1; // Enable IO for playback result = AudioUnitSetProperty(iOUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, // Output bus &flag, sizeof(flag)); if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty EnableIO" withStatus: result]; return;} /* can't do that because *** AudioUnitSetProperty EnableIO error: -1073752493 00000000 result = AudioUnitSetProperty(iOUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 0, // Output bus &flag, sizeof(flag)); if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty EnableIO" withStatus: result]; return;} */ 

然后创build一个stream格式:

 // I/O stream format iOStreamFormat.mSampleRate = 44100.0; iOStreamFormat.mFormatID = kAudioFormatLinearPCM; iOStreamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; iOStreamFormat.mFramesPerPacket = 1; iOStreamFormat.mChannelsPerFrame = 1; iOStreamFormat.mBitsPerChannel = 16; iOStreamFormat.mBytesPerPacket = 2; iOStreamFormat.mBytesPerFrame = 2; [self printASBD: iOStreamFormat]; 

然后影响格式并指定采样率:

 result = AudioUnitSetProperty(iOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, // Input bus &iOStreamFormat, sizeof(iOStreamFormat)); if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty StreamFormat" withStatus: result]; return;} result = AudioUnitSetProperty(iOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, // Output bus &iOStreamFormat, sizeof(iOStreamFormat)); if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty StreamFormat" withStatus: result]; return;} // SampleRate I/O result = AudioUnitSetProperty (iOUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, 0, // Output &graphSampleRate, sizeof (graphSampleRate)); if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty (set I/O unit input stream format)" withStatus: result]; return;} 

然后,我尝试设置渲染callback。

解决scheme1 ​​>>>我的录音callback从未被调用

 effectState.rioUnit = iOUnit; AURenderCallbackStruct renderCallbackStruct; renderCallbackStruct.inputProc = &recordingCallback; renderCallbackStruct.inputProcRefCon = &effectState; result = AudioUnitSetProperty (iOUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, // Output bus &renderCallbackStruct, sizeof (renderCallbackStruct)); if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty SetRenderCallback" withStatus: result]; return;} 

解决scheme2 >>>我的应用程序在这个启动崩溃

 AURenderCallbackStruct renderCallbackStruct; renderCallbackStruct.inputProc = &recordingCallback; renderCallbackStruct.inputProcRefCon = &effectState; result = AUGraphSetNodeInputCallback (processingGraph, iONode, 0, // Output bus &renderCallbackStruct); if (noErr != result) {[self printErrorMessage: @"AUGraphSetNodeInputCallback (I/O unit input callback bus 0)" withStatus: result]; return;} 

如果有人有一个想法…

编辑解决scheme3(感谢阿罗anwser)>>现在有一个格式的问题

 AudioStreamBasicDescription dstFormat = {0}; dstFormat.mSampleRate=44100.0; dstFormat.mFormatID=kAudioFormatLinearPCM; dstFormat.mFormatFlags=kAudioFormatFlagsNativeEndian|kAudioFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked; dstFormat.mBytesPerPacket=4; dstFormat.mBytesPerFrame=4; dstFormat.mFramesPerPacket=1; dstFormat.mChannelsPerFrame=2; dstFormat.mBitsPerChannel=16; dstFormat.mReserved=0; result = AudioUnitSetProperty(iOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &stereoStreamFormat, sizeof(stereoStreamFormat)); if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty" withStatus: result]; return;} result = AudioUnitSetProperty(iOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &stereoStreamFormat, sizeof(stereoStreamFormat)); if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty" withStatus: result]; return;} AudioUnitAddRenderNotify( iOUnit, &recordingCallback, &effectState ); 

和文件设置:

 if (noErr != result) {[self printErrorMessage: @"AUGraphInitialize" withStatus: result]; return;} // On initialise le fichier audio NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString *destinationFilePath = [[[NSString alloc] initWithFormat: @"%@/output.caf", documentsDirectory] autorelease]; NSLog(@">>> %@", destinationFilePath); CFURLRef destinationURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)destinationFilePath, kCFURLPOSIXPathStyle, false); OSStatus setupErr = ExtAudioFileCreateWithURL(destinationURL, kAudioFileWAVEType, &dstFormat, NULL, kAudioFileFlags_EraseFile, &effectState.audioFileRef); CFRelease(destinationURL); NSAssert(setupErr == noErr, @"Couldn't create file for writing"); setupErr = ExtAudioFileSetProperty(effectState.audioFileRef, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), &stereoStreamFormat); NSAssert(setupErr == noErr, @"Couldn't create file for format"); setupErr = ExtAudioFileWriteAsync(effectState.audioFileRef, 0, NULL); NSAssert(setupErr == noErr, @"Couldn't initialize write buffers for audio file"); 

和录音callback:

 static OSStatus recordingCallback (void * inRefCon, AudioUnitRenderActionFlags * ioActionFlags, const AudioTimeStamp * inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList * ioData) { if (*ioActionFlags == kAudioUnitRenderAction_PostRender && inBusNumber == 0) { EffectState *effectState = (EffectState *)inRefCon; ExtAudioFileWriteAsync(effectState->audioFileRef, inNumberFrames, ioData); } return noErr; } 

输出文件output.caf中缺less一些东西:)。 我完全失去了申请的格式。

我不认为你需要在I / O单元上启用input。 我还会将您在I / O单元上进行的格式和采样率configuration注释掉,直到您的callback运行,因为不匹配或不支持的格式会阻止将audio单元连接在一起。

要添加callback,请尝试以下方法:

 AudioUnitAddRenderNotify( iOUnit, &recordingCallback, self ); 

显然其他方法将取代节点连接,但这种方法不会 – 所以你的audio单元可以保持连接,即使你已经添加了callback。

一旦你的callback正在运行,如果你发现缓冲区中没有数据(ioData),请将这些代码包装在你的callback代码中:

 if (*ioActionFlags == kAudioUnitRenderAction_PostRender) { // your code } 

这是需要的,因为以这种方式添加的callback在audio单元呈现其audio之前和之后都会运行,但您只是想在呈现代码之后运行代码。

一旦callback正在运行,下一步就是找出它正在接收的audio格式并正确处理。 尝试添加到您的callback:

 SInt16 *dataLeftChannel = (SInt16 *)ioData->mBuffers[0].mData; for (UInt32 frameNumber = 0; frameNumber < inNumberFrames; ++frameNumber) { NSLog(@"sample %lu: %d", frameNumber, dataLeftChannel[frameNumber]); } 

这会让您的应用程序变慢,以至于可能会阻止任何audio播放,但您应该可以运行足够长的时间,以查看示例的样子。 如果callback正在接收16位audio,则样本应该是-32000到32000之间的正整数或负整数。如果样本在正常数字和更小数字之间交替,请在您的callback中尝试以下代码:

 SInt32 *dataLeftChannel = (SInt32 *)ioData->mBuffers[0].mData; for (UInt32 frameNumber = 0; frameNumber < inNumberFrames; ++frameNumber) { NSLog(@"sample %lu: %ld", frameNumber, dataLeftChannel[frameNumber]); } 

这应该显示你完整的8.24个样本。

如果你可以用callback所接收的格式保存数据,那么你应该有你所需要的。 如果您需要以不同的格式保存它,则应该能够转换远程I / Oaudio单元中的格式…但是,当它连接到多声道时,我无法弄清楚如何执行此操作混频器单元。 作为替代,您可以使用audio转换器服务转换数据。 首先,定义input和输出格式:

 AudioStreamBasicDescription monoCanonicalFormat; size_t bytesPerSample = sizeof (AudioUnitSampleType); monoCanonicalFormat.mFormatID = kAudioFormatLinearPCM; monoCanonicalFormat.mFormatFlags = kAudioFormatFlagsAudioUnitCanonical; monoCanonicalFormat.mBytesPerPacket = bytesPerSample; monoCanonicalFormat.mFramesPerPacket = 1; monoCanonicalFormat.mBytesPerFrame = bytesPerSample; monoCanonicalFormat.mChannelsPerFrame = 1; monoCanonicalFormat.mBitsPerChannel = 8 * bytesPerSample; monoCanonicalFormat.mSampleRate = graphSampleRate; AudioStreamBasicDescription mono16Format; bytesPerSample = sizeof (SInt16); mono16Format.mFormatID = kAudioFormatLinearPCM; mono16Format.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; mono16Format.mChannelsPerFrame = 1; mono16Format.mSampleRate = graphSampleRate; mono16Format.mBitsPerChannel = 16; mono16Format.mFramesPerPacket = 1; mono16Format.mBytesPerPacket = 2; mono16Format.mBytesPerFrame = 2; 

然后在callback之外的地方定义一个转换器,并在转换过程中创build一个临时缓冲区来处理数据:

 AudioConverterRef formatConverterCanonicalTo16; @property AudioConverterRef formatConverterCanonicalTo16; @synthesize AudioConverterRef; AudioConverterNew( &monoCanonicalFormat, &mono16Format, &formatConverterCanonicalTo16 ); SInt16 *data16; @property (readwrite) SInt16 *data16; @synthesize data16; data16 = malloc(sizeof(SInt16) * 4096); 

然后将其添加到您的callback中,然后保存数据:

 UInt32 dataSizeCanonical = ioData->mBuffers[0].mDataByteSize; SInt32 *dataCanonical = (SInt32 *)ioData->mBuffers[0].mData; UInt32 dataSize16 = dataSizeCanonical; AudioConverterConvertBuffer( effectState->formatConverterCanonicalTo16, dataSizeCanonical, dataCanonical, &dataSize16, effectState->data16 ); 

然后,您可以保存16位格式的data16,可能是您要保存在文件中的内容。 它将比标准数据更兼容和一半。

当你完成后,你可以清理一些事情:

 AudioConverterDispose(formatConverterCanonicalTo16); free(data16);