ExtAudioFileWrite 创建静音 m4a

ExtAudioFileWrite creates silent m4a

我正在制作一个需要将可变数量的音频文件合并为一个的应用程序。为了实现这一点,我正在使用多通道混音器音频单元。混音器成功地混合了音轨,并且,如果我将 AUGraph 中的 I/O 单元设置为 RemoteIO,它会成功播放声音。

不过,我想将新声音保存到文件中;我认为最好的方法是使用通用输出而不是 RemoteIO。文件已成功写入且长度正确(以秒为单位),但播放时无声。这是我创建 AUGraph 的方法(为简洁起见,我将尽可能多地删除)

- (void)createGraph
{
    OSStatus result = NewAUGraph(&graph);

    AudioComponentDescription mixerDescription;
    mixerDescription.componentType = kAudioUnitType_Mixer;
    mixerDescription.componentSubType = kAudioUnitSubType_MultiChannelMixer;
    mixerDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
    mixerDescription.componentFlags = 0;
    mixerDescription.componentFlagsMask = 0;

    AudioComponentDescription remoteIODescription;
    remoteIODescription.componentType = kAudioUnitType_Output;
    remoteIODescription.componentSubType = kAudioUnitSubType_GenericOutput;
    remoteIODescription.componentManufacturer = kAudioUnitManufacturer_Apple;
    remoteIODescription.componentFlags = 0;
    remoteIODescription.componentFlagsMask = 0;

    AUNode iONode, mixerNode;

    result = AUGraphAddNode(graph, &remoteIODescription, &iONode);
    result = AUGraphAddNode(graph, &mixerDescription, &mixerNode);
    result = AUGraphOpen(graph);
    result = AUGraphNodeInfo(graph, mixerNode, NULL, &mixerUnit);
    result = AUGraphNodeInfo(graph, iONode, NULL, &iOUnit);
    result = AUGraphConnectNodeInput(graph, mixerNode, 0, iONode, 0);

    UInt32 busCount = (UInt32)fileCount;

    result = AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &busCount, sizeof(busCount));

    UInt32 maximumFramesPerSlice = 4096;

    result = AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maximumFramesPerSlice, sizeof(maximumFramesPerSlice));

    for (UInt16 busNumber = 0; busNumber < busCount; busNumber++)
    {
        AURenderCallbackStruct renderCallback;
        renderCallback.inputProc = &inputRenderCallback;
        renderCallback.inputProcRefCon = (__bridge void *)self;

        result = AUGraphSetNodeInputCallback(graph, mixerNode, busNumber, &renderCallback);

        //sets certain parameters for the mixer; I don't believe this is the cause of the problem
        AudioUnitSetParameter(mixerUnit, kMultiChannelMixerParam_Pan, kAudioUnitScope_Input, busNumber, fileSettings[busNumber].pan, 0);
        AudioUnitSetParameter(mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Input, busNumber, fileSettings[busNumber].volume, 0);
        AudioUnitSetParameter(mixerUnit, kMultiChannelMixerParam_Enable, kAudioUnitScope_Input, busNumber, fileSettings[busNumber].enabled, 0);

        if (soundStructs[busNumber].isStereo == YES)
        {
            result = AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, busNumber, &stereoDescription, sizeof(stereoDescription));
        }
        else
        {
            result = AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, busNumber, &monoDescription, sizeof(monoDescription));
        }
    }

    Float64 sampleRate = SAMPLE_RATE; // 44100.0
    result = AudioUnitSetProperty(mixerUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &sampleRate, sizeof(sampleRate));

    CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:ofn];

    AudioStreamBasicDescription destinationFormat;
    memset(&destinationFormat, 0, sizeof(destinationFormat));
    destinationFormat.mChannelsPerFrame = 2;
    destinationFormat.mFormatID = kAudioFormatMPEG4AAC;
    destinationFormat.mFormatFlags = kMPEG4Object_AAC_Main;
    destinationFormat.mSampleRate = sampleRate;

    UInt32 size = sizeof(destinationFormat);
    result = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &size, &destinationFormat);

    result = ExtAudioFileCreateWithURL(url, kAudioFileM4AType, &destinationFormat, NULL, kAudioFileFlags_EraseFile, &outputFile);

    AudioStreamBasicDescription clientFormat;
    memset(&clientFormat, 0, sizeof(clientFormat));

    size = sizeof(clientFormat);
    result = AudioUnitGetProperty(iOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, & clientFormat, &size);

    UInt32 codec = kAppleHardwareAudioCodecManufacturer;
    ExtAudioFileSetProperty(outputFile, kExtAudioFileProperty_CodecManufacturer, sizeof(codec), &codec);

    ExtAudioFileSetProperty(outputFile, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat);

    result = AUGraphInitialize(graph);
}

这就是我保存文件的方式

- (void)startGraph
{
    AudioUnitRenderActionFlags flags = 0;
    AudioTimeStamp inTimeStamp;
    memset(&inTimeStamp, 0, sizeof(AudioTimeStamp));
    inTimeStamp.mFlags = kAudioTimeStampSampleTimeValid;
    UInt32 busNumber = 0;
    UInt32 numberFrames = 1024;
    inTimeStamp.mSampleTime = 0;
    int channelCount = 2;
    SInt64 totFrms = 0;

    for (int i = 0; i < fileCount; i++) //gets the length for the longest recording in the new track
    {
        SInt64 len = soundStructs[i].totalFrames;
        if (len > totFrms && fileSettings[i].enabled == YES)
            totFrms = len;
    }
    while (totFrms > 0)
    {
        if (totFrms < numberFrames)
            numberFrames = (UInt32)totFrms;
        else
            totFrms -= numberFrames;
        AudioBufferList *bufferList = (AudioBufferList*)malloc(sizeof(AudioBufferList)+sizeof(AudioBuffer)*(channelCount-1));
        bufferList->mNumberBuffers = channelCount;
        for (int j=0; j<channelCount; j++)
        {
            AudioBuffer buffer = {0};
            buffer.mNumberChannels = 1;
            buffer.mDataByteSize = numberFrames*sizeof(SInt32);
            buffer.mData = calloc(numberFrames, sizeof(SInt32));

            bufferList->mBuffers[j] = buffer;
        }
        AudioUnitRender(iOUnit, &flags, &inTimeStamp, busNumber, numberFrames, bufferList);

        OSStatus res = ExtAudioFileWrite(outputFile, numberFrames, bufferList);
        NSAssert(res == noErr, @"Res != noerr");
    }

    ExtAudioFileDispose(outputFile);
}

因为问题已经很长了,我不会添加混音器用于输入的回调函数或用于将文件加载到内存的方法,因为 RemoteIO 工作的事实让我相信有这些没有错。那么,为什么我的图创建的输出文件是无声的?

调用 AudioUnitRender 后,您需要为下一次迭代增加时间戳的采样时间。

AudioUnitRender(iOUnit, &flags, &inTimeStamp, busNumber, numberFrames, bufferList);
inTimeStamp.mSampleTime += numberFrames;