【问题标题】:How to write output of AUGraph to a file?如何将 AUGraph 的输出写入文件?
【发布时间】:2023-03-12 02:48:01
【问题描述】:

我正在尝试编写(应该是什么)一个简单的应用程序,它在 AUGraph 中按顺序包含一堆音频单元,然后将输出写入文件。我使用 AUGraphAddRenderNotify 添加了一个回调。这是我的回调函数:

OSStatus MyAURenderCallback(void *inRefCon,
                        AudioUnitRenderActionFlags *actionFlags,
                        const AudioTimeStamp *inTimeStamp,
                        UInt32 inBusNumber,
                        UInt32 inNumberFrames,
                        AudioBufferList *ioData) {
    if (*actionFlags & kAudioUnitRenderAction_PostRender) {
        ExtAudioFileRef outputFile = (ExtAudioFileRef)inRefCon;
        ExtAudioFileWriteAsync(outputFile, inNumberFrames, ioData);
    }
}

这类作品。该文件是可播放的,我可以听到我录制的内容,但有可怕的静电量使其几乎听不见。

有人知道这有什么问题吗?或者有人知道将 AUGraph 输出记录到文件的更好方法吗?

感谢您的帮助。

【问题讨论】:

  • 您的 AUGraph 和 ExtAudioFile(客户端数据格式)的格式是否匹配?还有,需要实时录制吗?
  • 我认为他们匹配。我使用相同的 AudioStreamBasicDescription 来配置所有音频单元并创建文件。我不确定如何实际检查。你知道怎么做吗?我想我会调查一下。我想我不需要实时记录,但还有其他方法可以做到吗?如果我不将数据实时保存在某个地方,那么它就会丢失。
  • 我相信你不想在图的头部重复调用AUGraphStart,而不是调用AudioUnitRender。我认为实时处理的问题是ExtAudioFile 的内部环形缓冲区填满并丢失数据。
  • 哦,这就是您所说的非实时。我认为我确实需要它是实时的,因为我正在从麦克风录制并将音频传递到扬声器。

标签: ios core-audio


【解决方案1】:

我刚才对音频单元有了一个顿悟,这帮助我解决了自己的问题。我对音频单元连接和渲染回调的工作方式有误解。我认为它们是完全独立的东西,但事实证明,连接只是渲染回调的简写。

从音频单元 A 的输出到音频单元 B 的输入执行 kAudioUnitProperty_MakeConnection 与在单元 B 的输入上执行 kAudioUnitProperty_SetRenderCallback 并在音频单元 A 的输出上调用 AudioUnitRender 的回调函数相同。

我在设置渲染回调后通过建立连接进行了测试,并且不再调用渲染回调。

因此,我能够通过执行以下操作来解决我的问题:

AURenderCallbackStruct callbackStruct = {0};
callbackStruct.inputProc = MyAURenderCallback;
callbackStruct.inputProcRefCon = mixerUnit;

AudioUnitSetProperty(ioUnit,
                     kAudioUnitProperty_SetRenderCallback,
                     kAudioUnitScope_Input,
                     0,
                     &callbackStruct,
                     sizeof(callbackStruct));

我的回调函数做了这样的事情:

OSStatus MyAURenderCallback(void *inRefCon,
                        AudioUnitRenderActionFlags *actionFlags,
                        const AudioTimeStamp *inTimeStamp,
                        UInt32 inBusNumber,
                        UInt32 inNumberFrames,
                        AudioBufferList *ioData) {

    AudioUnit mixerUnit = (AudioUnit)inRefCon;

    AudioUnitRender(mixerUnit,
                    actionFlags,
                    inTimeStamp,
                    0,
                    inNumberFrames,
                    ioData);

    ExtAudioFileWriteAsync(outputFile, 
                           inNumberFrames, 
                           ioData);

    return noErr;
}

这对我来说可能应该很明显,但我敢打赌还有其他人以同样的方式感到困惑,所以希望这对他们也有帮助。

我仍然不确定为什么我在使用 AUGraphAddRenderNotify 回调时遇到问题。稍后我将对此进行深入研究,但现在我找到了一个似乎可行的解决方案。

【讨论】:

  • 上面的代码真的有效吗?我认为它会引发错误,因为您将 refCon 设置为“self”,然后在回调中将其转换为 AUMixer。
  • 你是对的。我稍微简化了我项目中的代码。我传入了 self ,然后通过一个属性访问了 mixerUnit。我更新了我的帖子以传递混合器单元,它是输出单元之前的单元,我想在将其输出传递给输出单元之前将其输出写入文件。
  • 好的-想知道。您可能对我将图形输出保存到文件的解决方案感兴趣 - 在 remoteIO 实例上放置一个 renderCallback,然后在“post_Render”阶段获取渲染数据。不知道这是否是一种“好”的方法,但它确实有效。还可以实时保存为 ACC 压缩格式(耶!):stackoverflow.com/questions/10113977/…
  • 您的答案确实有效,但实际上是以破坏 AUGraph API 为代价的。请查看this SO post,作为回答,我使用AUGraphAddRenderNotify() 提供了一个可行的解决方案。
【解决方案2】:

以下是来自 Apple 的一些示例代码(该项目是 PlaySequence,但不是特定于 MIDI),可能会有所帮助:

{
    CAStreamBasicDescription clientFormat = CAStreamBasicDescription();
    ca_require_noerr (result = AudioUnitGetProperty(outputUnit,
                                                    kAudioUnitProperty_StreamFormat,
                                                    kAudioUnitScope_Output, 0,
                                                    &clientFormat, &size), fail);
    size = sizeof(clientFormat);
    ca_require_noerr (result = ExtAudioFileSetProperty(outfile, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat), fail);

    {
        MusicTimeStamp currentTime;
        AUOutputBL outputBuffer (clientFormat, numFrames);
        AudioTimeStamp tStamp;
        memset (&tStamp, 0, sizeof(AudioTimeStamp));
        tStamp.mFlags = kAudioTimeStampSampleTimeValid;
        int i = 0;
        int numTimesFor10Secs = (int)(10. / (numFrames / srate));
        do {
            outputBuffer.Prepare();
            AudioUnitRenderActionFlags actionFlags = 0;
            ca_require_noerr (result = AudioUnitRender (outputUnit, &actionFlags, &tStamp, 0, numFrames, outputBuffer.ABL()), fail);

            tStamp.mSampleTime += numFrames;

            ca_require_noerr (result = ExtAudioFileWrite(outfile, numFrames, outputBuffer.ABL()), fail);    

            ca_require_noerr (result = MusicPlayerGetTime (player, &currentTime), fail);
            if (shouldPrint && (++i % numTimesFor10Secs == 0))
                printf ("current time: %6.2f beats\n", currentTime);
        } while (currentTime < sequenceLength);
    }
}

【讨论】:

    【解决方案3】:

    也许试试这个。将音频单元回调中的数据复制到长缓冲区。播放缓冲区进行测试,然后在验证整个缓冲区正常后将整个缓冲区写入文件。

    【讨论】:

      猜你喜欢
      • 2016-03-18
      • 1970-01-01
      • 1970-01-01
      • 2014-07-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多