2011-07-07 73 views
9

我正在爲audio library called XAL編寫CoreAudio後端。輸入緩衝器可以具有各種採樣率。我正在使用一個音頻單元來輸出。想法是轉換緩衝區並在將它們發送到音頻單元之前進行混合。OS X/iOS - 使用AudioConverterFillComplexBuffer對緩衝區進行採樣率轉換

只要輸入緩衝區具有與輸出音頻單元相同的屬性(採樣率,通道數等),一切都會正常工作。因此,混合部分起作用。

但是,我堅持採樣率和通道數轉換。從我發現的情況來看,這對於Audio Converter Services API來說是最簡單的。我設法構建了一個轉換器;其思想是輸出格式與輸出單元格式相同,但可能爲了轉換器的目的而進行調整。

音頻轉換器已成功構建,但撥打AudioConverterFillComplexBuffer()後,我得到輸出狀態錯誤-50。

如果我能在此代碼上獲得另一組眼球,我很樂意。問題可能在AudioConverterNew()以下。變量stream包含傳入(和傳出)緩衝區數據,並且streamSize包含傳入(和傳出)緩衝區數據的字節大小。

我做錯了什麼?

void CoreAudio_AudioManager::_convertStream(Buffer* buffer, unsigned char** stream, int *streamSize) 
{ 
    if (buffer->getBitsPerSample() != unitDescription.mBitsPerChannel || 
     buffer->getChannels() != unitDescription.mChannelsPerFrame || 
     buffer->getSamplingRate() != unitDescription.mSampleRate) 
    { 
     printf("INPUT STREAM SIZE: %d\n", *streamSize); 
     // describe the input format's description 
     AudioStreamBasicDescription inputDescription; 
     memset(&inputDescription, 0, sizeof(inputDescription)); 
     inputDescription.mFormatID = kAudioFormatLinearPCM; 
     inputDescription.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger; 
     inputDescription.mChannelsPerFrame = buffer->getChannels(); 
     inputDescription.mSampleRate = buffer->getSamplingRate(); 
     inputDescription.mBitsPerChannel = buffer->getBitsPerSample(); 
     inputDescription.mBytesPerFrame = (inputDescription.mBitsPerChannel * inputDescription.mChannelsPerFrame)/8; 
     inputDescription.mFramesPerPacket = 1; //*streamSize/inputDescription.mBytesPerFrame; 
     inputDescription.mBytesPerPacket = inputDescription.mBytesPerFrame * inputDescription.mFramesPerPacket; 
     printf("INPUT : %lu bytes per packet for sample rate %g, channels %d\n", inputDescription.mBytesPerPacket, inputDescription.mSampleRate, inputDescription.mChannelsPerFrame); 

     // copy conversion output format's description from the 
     // output audio unit's description. 
     // then adjust framesPerPacket to match the input we'll be passing. 

     // framecount of our input stream is based on the input bytecount. 
     // output stream will have same number of frames, but different 
     // number of bytes. 
     AudioStreamBasicDescription outputDescription = unitDescription; 
     outputDescription.mFramesPerPacket = 1; //inputDescription.mFramesPerPacket; 
     outputDescription.mBytesPerPacket = outputDescription.mBytesPerFrame * outputDescription.mFramesPerPacket; 
     printf("OUTPUT : %lu bytes per packet for sample rate %g, channels %d\n", outputDescription.mBytesPerPacket, outputDescription.mSampleRate, outputDescription.mChannelsPerFrame); 

     // create an audio converter 
     AudioConverterRef audioConverter; 
     OSStatus acCreationResult = AudioConverterNew(&inputDescription, &outputDescription, &audioConverter); 
     printf("Created audio converter %p (status: %d)\n", audioConverter, acCreationResult); 
     if(!audioConverter) 
     { 
      // bail out 
      free(*stream); 
      *streamSize = 0; 
      *stream = (unsigned char*)malloc(0); 
      return; 
     } 

     // calculate number of bytes required for output of input stream. 
     // allocate buffer of adequate size. 
     UInt32 outputBytes = outputDescription.mBytesPerPacket * (*streamSize/inputDescription.mBytesPerFrame); // outputDescription.mFramesPerPacket * outputDescription.mBytesPerFrame; 
     unsigned char *outputBuffer = (unsigned char*)malloc(outputBytes); 
     memset(outputBuffer, 0, outputBytes); 
     printf("OUTPUT BYTES : %d\n", outputBytes); 

     // describe input data we'll pass into converter 
     AudioBuffer inputBuffer; 
     inputBuffer.mNumberChannels = inputDescription.mChannelsPerFrame; 
     inputBuffer.mDataByteSize = *streamSize; 
     inputBuffer.mData = *stream; 

     // describe output data buffers into which we can receive data. 
     AudioBufferList outputBufferList; 
     outputBufferList.mNumberBuffers = 1; 
     outputBufferList.mBuffers[0].mNumberChannels = outputDescription.mChannelsPerFrame; 
     outputBufferList.mBuffers[0].mDataByteSize = outputBytes; 
     outputBufferList.mBuffers[0].mData = outputBuffer; 

     // set output data packet size 
     UInt32 outputDataPacketSize = outputDescription.mBytesPerPacket; 

     // convert 
     OSStatus result = AudioConverterFillComplexBuffer(audioConverter, /* AudioConverterRef inAudioConverter */ 
                  CoreAudio_AudioManager::_converterComplexInputDataProc, /* AudioConverterComplexInputDataProc inInputDataProc */ 
                  &inputBuffer, /* void *inInputDataProcUserData */ 
                  &outputDataPacketSize, /* UInt32 *ioOutputDataPacketSize */ 
                  &outputBufferList, /* AudioBufferList *outOutputData */ 
                  NULL /* AudioStreamPacketDescription *outPacketDescription */ 
                 ); 
     printf("Result: %d wheee\n", result); 

     // change "stream" to describe our output buffer. 
     // even if error occured, we'd rather have silence than unconverted audio. 
     free(*stream); 
     *stream = outputBuffer; 
     *streamSize = outputBytes; 

     // dispose of the audio converter 
     AudioConverterDispose(audioConverter); 
    } 
} 


OSStatus CoreAudio_AudioManager::_converterComplexInputDataProc(AudioConverterRef inAudioConverter, 
                   UInt32* ioNumberDataPackets, 
                   AudioBufferList* ioData, 
                   AudioStreamPacketDescription** ioDataPacketDescription, 
                   void* inUserData) 
{ 
    printf("Converter\n"); 
    if(*ioNumberDataPackets != 1) 
    { 
     xal::log("_converterComplexInputDataProc cannot provide input data; invalid number of packets requested"); 
     *ioNumberDataPackets = 0; 
     ioData->mNumberBuffers = 0; 
     return -50; 
    } 

    *ioNumberDataPackets = 1; 
    ioData->mNumberBuffers = 1; 
    ioData->mBuffers[0] = *(AudioBuffer*)inUserData; 

    *ioDataPacketDescription = NULL; 

    return 0; 
} 

回答

10

工作代碼核心音頻採樣率轉換和信道數的轉換,使用音頻轉換器服務(現在可作爲BSD-licensed XAL audio library的一部分):

void CoreAudio_AudioManager::_convertStream(Buffer* buffer, unsigned char** stream, int *streamSize) 
{ 
    if (buffer->getBitsPerSample() != unitDescription.mBitsPerChannel || 
     buffer->getChannels() != unitDescription.mChannelsPerFrame || 
     buffer->getSamplingRate() != unitDescription.mSampleRate) 
    { 
     // describe the input format's description 
     AudioStreamBasicDescription inputDescription; 
     memset(&inputDescription, 0, sizeof(inputDescription)); 
     inputDescription.mFormatID = kAudioFormatLinearPCM; 
     inputDescription.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger; 
     inputDescription.mChannelsPerFrame = buffer->getChannels(); 
     inputDescription.mSampleRate = buffer->getSamplingRate(); 
     inputDescription.mBitsPerChannel = buffer->getBitsPerSample(); 
     inputDescription.mBytesPerFrame = (inputDescription.mBitsPerChannel * inputDescription.mChannelsPerFrame)/8; 
     inputDescription.mFramesPerPacket = 1; //*streamSize/inputDescription.mBytesPerFrame; 
     inputDescription.mBytesPerPacket = inputDescription.mBytesPerFrame * inputDescription.mFramesPerPacket; 

     // copy conversion output format's description from the 
     // output audio unit's description. 
     // then adjust framesPerPacket to match the input we'll be passing. 

     // framecount of our input stream is based on the input bytecount. 
     // output stream will have same number of frames, but different 
     // number of bytes. 
     AudioStreamBasicDescription outputDescription = unitDescription; 
     outputDescription.mFramesPerPacket = 1; //inputDescription.mFramesPerPacket; 
     outputDescription.mBytesPerPacket = outputDescription.mBytesPerFrame * outputDescription.mFramesPerPacket; 

     // create an audio converter 
     AudioConverterRef audioConverter; 
     OSStatus acCreationResult = AudioConverterNew(&inputDescription, &outputDescription, &audioConverter); 
     if(!audioConverter) 
     { 
      // bail out 
      free(*stream); 
      *streamSize = 0; 
      *stream = (unsigned char*)malloc(0); 
      return; 
     } 

     // calculate number of bytes required for output of input stream. 
     // allocate buffer of adequate size. 
     UInt32 outputBytes = outputDescription.mBytesPerPacket * (*streamSize/inputDescription.mBytesPerPacket); // outputDescription.mFramesPerPacket * outputDescription.mBytesPerFrame; 
     unsigned char *outputBuffer = (unsigned char*)malloc(outputBytes); 
     memset(outputBuffer, 0, outputBytes); 

     // describe input data we'll pass into converter 
     AudioBuffer inputBuffer; 
     inputBuffer.mNumberChannels = inputDescription.mChannelsPerFrame; 
     inputBuffer.mDataByteSize = *streamSize; 
     inputBuffer.mData = *stream; 

     // describe output data buffers into which we can receive data. 
     AudioBufferList outputBufferList; 
     outputBufferList.mNumberBuffers = 1; 
     outputBufferList.mBuffers[0].mNumberChannels = outputDescription.mChannelsPerFrame; 
     outputBufferList.mBuffers[0].mDataByteSize = outputBytes; 
     outputBufferList.mBuffers[0].mData = outputBuffer; 

     // set output data packet size 
     UInt32 outputDataPacketSize = outputBytes/outputDescription.mBytesPerPacket; 

     // fill class members with data that we'll pass into 
     // the InputDataProc 
     _converter_currentBuffer = &inputBuffer; 
     _converter_currentInputDescription = inputDescription; 

     // convert 
     OSStatus result = AudioConverterFillComplexBuffer(audioConverter, /* AudioConverterRef inAudioConverter */ 
                  CoreAudio_AudioManager::_converterComplexInputDataProc, /* AudioConverterComplexInputDataProc inInputDataProc */ 
                  this, /* void *inInputDataProcUserData */ 
                  &outputDataPacketSize, /* UInt32 *ioOutputDataPacketSize */ 
                  &outputBufferList, /* AudioBufferList *outOutputData */ 
                  NULL /* AudioStreamPacketDescription *outPacketDescription */ 
                 ); 

     // change "stream" to describe our output buffer. 
     // even if error occured, we'd rather have silence than unconverted audio. 
     free(*stream); 
     *stream = outputBuffer; 
     *streamSize = outputBytes; 

     // dispose of the audio converter 
     AudioConverterDispose(audioConverter); 
    } 
} 


OSStatus CoreAudio_AudioManager::_converterComplexInputDataProc(AudioConverterRef inAudioConverter, 
                   UInt32* ioNumberDataPackets, 
                   AudioBufferList* ioData, 
                   AudioStreamPacketDescription** ioDataPacketDescription, 
                   void* inUserData) 
{ 
    if(ioDataPacketDescription) 
    { 
     xal::log("_converterComplexInputDataProc cannot provide input data; it doesn't know how to provide packet descriptions"); 
     *ioDataPacketDescription = NULL; 
     *ioNumberDataPackets = 0; 
     ioData->mNumberBuffers = 0; 
     return 501; 
    } 

    CoreAudio_AudioManager *self = (CoreAudio_AudioManager*)inUserData; 

    ioData->mNumberBuffers = 1; 
    ioData->mBuffers[0] = *(self->_converter_currentBuffer); 

    *ioNumberDataPackets = ioData->mBuffers[0].mDataByteSize/self->_converter_currentInputDescription.mBytesPerPacket; 
    return 0; 
} 

在標題中,作爲部分CoreAudio_AudioManager類,這裏有相關的實例變量:

AudioStreamBasicDescription unitDescription; 
    AudioBuffer *_converter_currentBuffer; 
    AudioStreamBasicDescription _converter_currentInputDescription; 

幾個月後,我看着這個,我意識到我沒有記錄這些變化。

如果你有興趣在什麼變化是:

  • 看回調函數CoreAudio_AudioManager::_converterComplexInputDataProc
  • 一個具有正確指定輸出數據包的數量爲ioNumberDataPackets
  • 這需要引入新的實例變量來保存緩衝區(前面的inUserData)和輸入描述(用於計算要送入Core Audio轉換器的數據包數量)
  • 「輸出」信息包(這些送入轉換器)的該計算是基於我們的回調接收的數據的量進行,並且每個數據包的字節數,所述輸入格式包含

希望這編輯將有助於未來的讀者(包括我自己)!

+1

在你的代碼中,我看到你有這個unitDescription.mSampleRate。我認爲這是你想要的輸出採樣率。但我似乎無法找到你告訴outputDescription所需採樣率的地方? – user523234

+0

該庫不需要也不需要進行任意轉換;它甚至進行轉換的唯一原因是它可以在「輸出音頻單元」上播放音頻 - 即揚聲器或耳機,大部分。因此,在進行轉換時,它將從輸出音頻單元中複製整個輸出音頻描述,並且只修改'mFramesPerPacket'。如果您需要獲得不同的速率,請嘗試更改'outputDescription'中的相應字段(這是一個'AudioStreamBasicDescription'類型的結構體)。並小心;在以後的實驗中,我有過很糟糕的時間。 –

+0

但是當輸入格式中每個數據包的字節包含= 0時會發生什麼情況,VBR格式通常會出現這種情況?你不能除以零,除非你是查克·諾里斯! :p – abbood

相關問題