【问题标题】:How to record sound produced by mixer unit output (iOS Core Audio & Audio Graph)如何录制混音器单元输出产生的声音(iOS Core Audio & Audio Graph)
【发布时间】:2011-10-30 09:53:16
【问题描述】:

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

目前,我的代码基于 apple MixerHost iOS app 演示:混音器节点连接到音频图上的远程 IO 节点

我尝试在混音器输出的远程 IO 节点输入上设置 输入回调

我做错了,但我找不到错误。

这是下面的代码。这是在多通道混音器单元设置之后完成的:

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;}
*/

然后创建流格式:

// 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;}

然后,我尝试设置渲染回调。

解决方案 1 >>> 我的录音回调从未被调用

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;}

解决方案 2 >>> 我的应用在启动时崩溃

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;}

如果有人有想法...

编辑解决方案 3(感谢 arlo 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");

还有录音回调:

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 中缺少某些内容:)。 我完全迷失了应用的格式。

【问题讨论】:

  • 我正在尝试做同样的事情,但无法通过 MixerHost 实现您的代码示例,请您帮帮我..
  • 嗨 lefakir 和 arlomedia, 你们中的一个人可以发布 EffectState 类吗?我正在尝试使用 MixerHost 和以上来重现工作代码。最好的,格雷戈尔
  • 嗨 Gregor,你应该看这里stackoverflow.com/questions/7032468/…。此问题中声明了 EffectState 结构。

标签: iphone ios core-audio audio-recording audiounit


【解决方案1】:

我认为您不需要在 I/O 单元上启用输入。我还会注释掉您在 I/O 单元上所做的格式和采样率配置,直到您运行回调,因为不匹配或不受支持的格式会阻止音频单元链接在一起。

要添加回调,试试这个方法:

AudioUnitAddRenderNotify(
    iOUnit,
    &recordingCallback,
    self
);

显然其他方法将替换节点连接,但此方法不会 - 因此即使您添加了回调,您的音频单元也可以保持连接。

一旦您的回调运行,如果您发现缓冲区 (ioData) 中没有数据,请将此代码包装在您的回调代码周围:

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

这是必需的,因为以这种方式添加的回调会在音频单元呈现其音频之前和之后运行,但您只想在它呈现之后运行您的代码。

一旦回调运行,下一步就是确定它正在接收什么音频格式并适当地处理它。尝试将此添加到您的回调中:

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

这会大大降低您的应用程序的速度,以至于它可能会阻止任何音频实际播放,但您应该能够运行它足够长的时间来查看示例的外观。如果回调正在接收 16 位音频,则样本应该是介于 -32000 和 32000 之间的正整数或负整数。如果样本在看起来正常的数字和小得多的数字之间交替,请在回调中尝试以下代码:

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

这应该会显示完整的 8.24 示例。

如果您可以以回调接收的格式保存数据,那么您应该拥有所需的内容。如果您需要以不同的格式保存它,您应该能够在远程 I/O 音频单元中转换格式......但是当它连接到多通道混音器单元时,我 haven't been able to figure out how to do that。作为替代方案,您可以使用Audio Converter Services 转换数据。首先,定义输入输出格式:

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;

然后在回调之外的某个地方定义一个转换器,并创建一个临时缓冲区用于在转换期间处理数据:

AudioConverterRef formatConverterCanonicalTo16;
@property AudioConverterRef formatConverterCanonicalTo16;
@synthesize AudioConverterRef;
AudioConverterNew(
    &monoCanonicalFormat,
    &mono16Format,
    &formatConverterCanonicalTo16
);

SInt16 *data16;
@property (readwrite) SInt16 *data16;
@synthesize data16;
data16 = malloc(sizeof(SInt16) * 4096);

然后在保存数据之前将其添加到回调中:

UInt32 dataSizeCanonical = ioData->mBuffers[0].mDataByteSize;
SInt32 *dataCanonical = (SInt32 *)ioData->mBuffers[0].mData;
UInt32 dataSize16 = dataSizeCanonical;

AudioConverterConvertBuffer(
    effectState->formatConverterCanonicalTo16,
    dataSizeCanonical,
    dataCanonical,
    &dataSize16,
    effectState->data16
);

然后您可以保存 data16,它是 16 位格式,可能是您想要保存在文件中的内容。它将更加兼容,并且只有规范数据的一半。

完成后,您可以清理一些东西:

AudioConverterDispose(formatConverterCanonicalTo16);
free(data16);

【讨论】:

  • 我刚刚更新了我的答案以包含后续步骤,因为您已经运行了回调。
  • 我刚刚编辑了我的问题,以提供更多我正在尝试做的代码
  • 好的,我只是扩展了我的答案,包括一种对我有用的格式转换方法。 :)
  • 我一定是做错了什么,它对我不起作用。我绝对喜欢核心音频
  • 我专注于 ios 4.3 但我忘记了它有一点问题:lists.apple.com/archives/coreaudio-api/2011/Jun/msg00006.html 它在下面可以正常工作
猜你喜欢
  • 1970-01-01
  • 2011-04-30
  • 1970-01-01
  • 2013-01-03
  • 1970-01-01
  • 1970-01-01
  • 2011-06-26
  • 2012-09-13
  • 2021-06-22
相关资源
最近更新 更多