【问题标题】:Realtime audio processing without output无输出的实时音频处理
【发布时间】:2013-03-21 15:18:37
【问题描述】:

我在看这个例子http://teragonaudio.com/article/How-to-do-realtime-recording-with-effect-processing-on-iOS.html

我想关闭我的输出。我尝试更改:kAudioSessionCategory_PlayAndRecordkAudioSessionCategory_RecordAudio 但这不起作用。我也试着摆脱:

  if(AudioUnitSetProperty(*audioUnit, kAudioUnitProperty_StreamFormat,
                            kAudioUnitScope_Output, 1, &streamDescription, sizeof(streamDescription)) != noErr) {
        return 1;
    }

因为我想从麦克风获取声音但不播放。但无论我做什么,当我的声音到达 renderCallback 方法时,都会出现 -50 错误。当音频在输出上自动播放时,一切正常...

更新代码:

using namespace std;

AudioUnit *audioUnit = NULL;

float *convertedSampleBuffer = NULL;

int initAudioSession() {
    audioUnit = (AudioUnit*)malloc(sizeof(AudioUnit));

    if(AudioSessionInitialize(NULL, NULL, NULL, NULL) != noErr) {
        return 1;
    }

    if(AudioSessionSetActive(true) != noErr) {
        return 1;
    }

    UInt32 sessionCategory = kAudioSessionCategory_PlayAndRecord;
    if(AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
                               sizeof(UInt32), &sessionCategory) != noErr) {
        return 1;
    }

    Float32 bufferSizeInSec = 0.02f;
    if(AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration,
                               sizeof(Float32), &bufferSizeInSec) != noErr) {
        return 1;
    }

    UInt32 overrideCategory = 1;
    if(AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryDefaultToSpeaker,
                               sizeof(UInt32), &overrideCategory) != noErr) {
        return 1;
    }

    // There are many properties you might want to provide callback functions for:
    // kAudioSessionProperty_AudioRouteChange
    // kAudioSessionProperty_OverrideCategoryEnableBluetoothInput
    // etc.

    return 0;
}

OSStatus renderCallback(void *userData, AudioUnitRenderActionFlags *actionFlags,
                        const AudioTimeStamp *audioTimeStamp, UInt32 busNumber,
                        UInt32 numFrames, AudioBufferList *buffers) {
    OSStatus status = AudioUnitRender(*audioUnit, actionFlags, audioTimeStamp,
                                      1, numFrames, buffers);

    int doOutput = 0;

    if(status != noErr) {
        return status;
    }

    if(convertedSampleBuffer == NULL) {
        // Lazy initialization of this buffer is necessary because we don't
        // know the frame count until the first callback
        convertedSampleBuffer = (float*)malloc(sizeof(float) * numFrames);
        baseTime = (float)QRealTimer::getUptimeInMilliseconds();
    }

    SInt16 *inputFrames = (SInt16*)(buffers->mBuffers->mData);

    // If your DSP code can use integers, then don't bother converting to
    // floats here, as it just wastes CPU. However, most DSP algorithms rely
    // on floating point, and this is especially true if you are porting a
    // VST/AU to iOS.

    int i;

    for( i = numFrames; i < fftlength; i++ )        // Shifting buffer
        x_inbuf[i - numFrames] = x_inbuf[i];

    for(  i = 0; i < numFrames; i++) {
        x_inbuf[i + x_phase] = (float)inputFrames[i] / (float)32768;
    }

    if( x_phase + numFrames == fftlength )
    {
        x_alignment.SigProc_frontend(x_inbuf);  // Signal processing front-end (FFT!)
        doOutput = x_alignment.Align();


        /// Output as text! In the real-time version,
        //      this is where we update visualisation callbacks and launch other services
        if ((doOutput) & (x_netscore.isEvent(x_alignment.Position()))
            &(x_alignment.lastAction()<x_alignment.Position()) )
        {
          //  here i want to do something with my input!
        }
    }
    else
        x_phase += numFrames;


   return noErr;
}


int initAudioStreams(AudioUnit *audioUnit) {
    UInt32 audioCategory = kAudioSessionCategory_PlayAndRecord;
    if(AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
                               sizeof(UInt32), &audioCategory) != noErr) {
        return 1;
    }

    UInt32 overrideCategory = 1;
    if(AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryDefaultToSpeaker,
                               sizeof(UInt32), &overrideCategory) != noErr) {
        // Less serious error, but you may want to handle it and bail here
    }

    AudioComponentDescription componentDescription;
    componentDescription.componentType = kAudioUnitType_Output;
    componentDescription.componentSubType = kAudioUnitSubType_RemoteIO;
    componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
    componentDescription.componentFlags = 0;
    componentDescription.componentFlagsMask = 0;
    AudioComponent component = AudioComponentFindNext(NULL, &componentDescription);
    if(AudioComponentInstanceNew(component, audioUnit) != noErr) {
        return 1;
    }

    UInt32 enable = 1;
    if(AudioUnitSetProperty(*audioUnit, kAudioOutputUnitProperty_EnableIO,
                            kAudioUnitScope_Input, 1, &enable, sizeof(UInt32)) != noErr) {
        return 1;
    }

    AURenderCallbackStruct callbackStruct;
    callbackStruct.inputProc = renderCallback; // Render function
    callbackStruct.inputProcRefCon = NULL;
    if(AudioUnitSetProperty(*audioUnit, kAudioUnitProperty_SetRenderCallback,
                            kAudioUnitScope_Input, 0, &callbackStruct,
                            sizeof(AURenderCallbackStruct)) != noErr) {
        return 1;
    }

    AudioStreamBasicDescription streamDescription;
    // You might want to replace this with a different value, but keep in mind that the
    // iPhone does not support all sample rates. 8kHz, 22kHz, and 44.1kHz should all work.
    streamDescription.mSampleRate = 44100;
    // Yes, I know you probably want floating point samples, but the iPhone isn't going
    // to give you floating point data. You'll need to make the conversion by hand from
    // linear PCM <-> float.
    streamDescription.mFormatID = kAudioFormatLinearPCM;
    // This part is important!
    streamDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger |
    kAudioFormatFlagsNativeEndian |
    kAudioFormatFlagIsPacked;
    streamDescription.mBitsPerChannel = 16;
    // 1 sample per frame, will always be 2 as long as 16-bit samples are being used
    streamDescription.mBytesPerFrame = 2;
    streamDescription.mChannelsPerFrame = 1;
    streamDescription.mBytesPerPacket = streamDescription.mBytesPerFrame *
    streamDescription.mChannelsPerFrame;
    // Always should be set to 1
    streamDescription.mFramesPerPacket = 1;
    // Always set to 0, just to be sure
    streamDescription.mReserved = 0;

    // Set up input stream with above properties
    if(AudioUnitSetProperty(*audioUnit, kAudioUnitProperty_StreamFormat,
                            kAudioUnitScope_Input, 0, &streamDescription, sizeof(streamDescription)) != noErr) {
        return 1;
    }

    // Ditto for the output stream, which we will be sending the processed audio to
    if(AudioUnitSetProperty(*audioUnit, kAudioUnitProperty_StreamFormat,
                            kAudioUnitScope_Output, 1, &streamDescription, sizeof(streamDescription)) != noErr) {
        return 1;
    }

    return 0;
}


int startAudioUnit(AudioUnit *audioUnit) {
    if(AudioUnitInitialize(*audioUnit) != noErr) {
        return 1;
    }

    if(AudioOutputUnitStart(*audioUnit) != noErr) {
        return 1;
    }

    return 0;
}

并从我的 VC 中调用:

  initAudioSession();
    initAudioStreams( audioUnit);
    startAudioUnit( audioUnit);

【问题讨论】:

    标签: ios objective-c core-audio


    【解决方案1】:

    如果你只想录制不回放,只需注释掉设置renderCallback的那一行:

    AURenderCallbackStruct callbackStruct;
    callbackStruct.inputProc = renderCallback; // Render function
    callbackStruct.inputProcRefCon = NULL;
    if(AudioUnitSetProperty(*audioUnit, kAudioUnitProperty_SetRenderCallback,
       kAudioUnitScope_Input, 0, &callbackStruct,
       sizeof(AURenderCallbackStruct)) != noErr) {
      return 1;
    }
    

    看到代码后更新:

    正如我所怀疑的,您缺少输入回调。添加这些行:

    // at top:
    #define kInputBus 1
    
    AURenderCallbackStruct callbackStruct;
    /**/
    callbackStruct.inputProc = &ALAudioUnit::recordingCallback;
    callbackStruct.inputProcRefCon = this;
    status = AudioUnitSetProperty(audioUnit,
                                  kAudioOutputUnitProperty_SetInputCallback,
                                  kAudioUnitScope_Global,
                                  kInputBus,
                                  &callbackStruct,
                                  sizeof(callbackStruct));
    

    现在在你的录音回调中:

    OSStatus ALAudioUnit::recordingCallback(void *inRefCon,
                                            AudioUnitRenderActionFlags *ioActionFlags,
                                            const AudioTimeStamp *inTimeStamp,
                                            UInt32 inBusNumber,
                                            UInt32 inNumberFrames,
                                            AudioBufferList *ioData)
    {
        // TODO: Use inRefCon to access our interface object to do stuff
        // Then, use inNumberFrames to figure out how much data is available, and make
        // that much space available in buffers in an AudioBufferList.
    
        // Then:
        // Obtain recorded samples
    
        OSStatus status;
    
        ALAudioUnit *pThis = reinterpret_cast<ALAudioUnit*>(inRefCon);
        if (!pThis)
            return noErr;
    
        //assert (pThis->m_nMaxSliceFrames >= inNumberFrames);
    
        pThis->recorderBufferList->GetBufferList().mBuffers[0].mDataByteSize = inNumberFrames * pThis->m_recorderSBD.mBytesPerFrame;
    
        status = AudioUnitRender(pThis->audioUnit,
                                 ioActionFlags,
                                 inTimeStamp,
                                 inBusNumber,
                                 inNumberFrames,
                                 &pThis->recorderBufferList->GetBufferList());
        THROW_EXCEPTION_IF_ERROR(status, "error rendering audio unit");
    
        // If we're not playing, I don't care about the data, simply discard it
        if (!pThis->playbackState || pThis->isSeeking) return noErr;
    
        // Now, we have the samples we just read sitting in buffers in bufferList
        pThis->DoStuffWithTheRecordedAudio(inNumberFrames, pThis->recorderBufferList, inTimeStamp);
    
        return noErr;
    }
    

    顺便说一句,我正在分配自己的缓冲区,而不是使用 AudioUnit 提供的缓冲区。如果您想使用 AudioUnit 分配的缓冲区,您可能需要更改这些部分。

    更新:

    如何分配自己的缓冲区:

    recorderBufferList = new AUBufferList();
    recorderBufferList->Allocate(m_recorderSBD, m_nMaxSliceFrames);
    recorderBufferList->PrepareBuffer(m_recorderSBD, m_nMaxSliceFrames);
    

    另外,如果您这样做,请告诉 AudioUnit 不要分配缓冲区:

    // Disable buffer allocation for the recorder (optional - do this if we want to pass in our own)
    flag = 0;
    status = AudioUnitSetProperty(audioUnit,
                                  kAudioUnitProperty_ShouldAllocateBuffer,
                                  kAudioUnitScope_Input,
                                  kInputBus,
                                  &flag,
                                  sizeof(flag));
    

    您需要包含CoreAudio utility classes

    【讨论】:

    • 不,我需要渲染输入。我不想马上被玩,但想要渲染方法。这里的问题是从麦克风获取声音并实时修改它。这就是 renderCallback 所做的。
    • 我不明白。我以为你只想要麦克风输入,没有输出。你想要输入/输出并想要实时修改输入并将其发送到输出?
    • 我想实时修改输入,但不发送到输出。所以我不需要输出,但需要渲染方法。
    • 该行设置回调以向输出总线提供输入。如果您不想为输出总线提供任何输入(即您不想要任何输出),请不要指定此回调。您将在使用 kAudioOutputUnitProperty_SetInputCallback 指定的回调中获得麦克风输入数据。我希望这能让它更清楚。
    • 那么渲染方法呢?如何修改我的输入?当我这样做时,我的 renderCallback 方法永远不会被调用。
    【解决方案2】:

    感谢@Mar0ux 的回答。来这里寻找完整示例代码的人可以在这里查看:

    https://code.google.com/p/ios-coreaudio-example/

    【讨论】:

      【解决方案3】:

      我正在使用相同的代码做一个类似的应用程序,我发现您可以通过将枚举 kAudioSessionCategory_PlayAndRecord 更改为 RecordAudio 来结束播放

      int initAudioStreams(AudioUnit *audioUnit) {
      UInt32 audioCategory = kAudioSessionCategory_RecordAudio;
      if(AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
                                 sizeof(UInt32), &audioCategory) != noErr) {
          return 1;
      }
      

      这停止了我硬件上麦克风和扬声器之间的反馈。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-07-18
        • 2017-08-02
        • 1970-01-01
        • 1970-01-01
        • 2013-06-23
        • 2011-01-16
        相关资源
        最近更新 更多