CloudRTC Mac端音頻設備設置

前言

在之前的文章中已經將 CloudRTC的音頻採集流程講述清楚了,下面我們就來分析一下 CloudRTC Mac端的設備採集是如何設置的。

基本知識

HAL 層的 CoreAudio

在Mac下訪問音頻設備時,使用的是 HAL層的 CoreAudio。它們都是以 AudioObject開頭的函數。在這一系列函數中,最重要的就是 “音頻對象屬性地址”,通過設置"音頻對象屬性地址"對音頻設備做具體的操作。

AudioObjectPropertyAddress(音頻對象屬性地址),由三部分組成,分別是 selector, scope, element

  • selector :指明要做什麼操作。
  • scope:指明操作的範圍。
  • element: 含義不明。

其中,selector 可以是下面幾項,每一項的含義通過名子基本可能,所以不再做更進一步說明:

//scope global
kAudioHardwarePropertyRunLoop, 
kAudioHardwarePropertyBoxList,
kAudioHardwarePropertyDefaultInputDevice,
kAudioHardwarePropertyDefaultOutputDevice,
kAudioHardwarePropertyDefaultSystemOutputDevice,
kAudioHardwarePropertyDevices,
kAudioHardwarePropertyHogModeIsAllowed,
kAudioHardwarePropertyIsInitingOrExiting,
kAudioHardwarePropertyMixStereoToMono,
kAudioHardwarePropertyPlugInList,
kAudioHardwarePropertyPowerHint,
kAudioHardwarePropertyProcessIsAudible,
kAudioHardwarePropertyProcessIsMaster,
kAudioHardwarePropertyServiceRestarted,
kAudioHardwarePropertySleepingIsAllowed,
kAudioHardwarePropertyTranslateBundleIDToPlugIn,
kAudioHardwarePropertyTranslateBundleIDToTransportManager,
kAudioHardwarePropertyTranslateUIDToBox,
kAudioHardwarePropertyTranslateUIDToDevice,
kAudioHardwarePropertyTransportManagerList,
kAudioHardwarePropertyUnloadingIsAllowed,
kAudioHardwarePropertyUserIDChanged,
kAudioHardwarePropertyUserSessionIsActiveOrHeadless,
kAudioHardwarePropertyClockDeviceList,
kAudioHardwarePropertyTranslateUIDToClockDevice, 
kAudioDevicePropertyAvailableNominalSampleRates,
kAudioDevicePropertyClockDomain,
kAudioDevicePropertyConfigurationApplication,
kAudioDevicePropertyDeviceCanBeDefaultDevice,
kAudioDevicePropertyDeviceCanBeDefaultSystemDevice,
kAudioDevicePropertyDeviceIsAlive,
kAudioDevicePropertyDeviceIsRunning,
kAudioDevicePropertyDeviceUID,
kAudioDevicePropertyIcon,
kAudioDevicePropertyIsHidden,
kAudioDevicePropertyLatency,
kAudioDevicePropertyModelUID,
kAudioDevicePropertyNominalSampleRate,
kAudioDevicePropertyPreferredChannelLayout,
kAudioDevicePropertyPreferredChannelsForStereo,
kAudioDevicePropertyRelatedDevices,
kAudioDevicePropertySafetyOffset,
kAudioDevicePropertyStreams,
kAudioDevicePropertyTransportType,
kAudioObjectPropertyControlList,

//scope input/output
kAudioDevicePropertyBufferSize,
kAudioDevicePropertyBufferSizeRange,
kAudioDevicePropertyChannelCategoryName,
kAudioDevicePropertyChannelCategoryNameCFString,
kAudioDevicePropertyChannelName,
kAudioDevicePropertyChannelNameCFString,
kAudioDevicePropertyChannelNominalLineLevelID,
kAudioDevicePropertyChannelNumberName,
kAudioDevicePropertyChannelNumberNameCFString,
kAudioDevicePropertyClockSourceNameForID,
kAudioDevicePropertyDataSourceNameForID,
kAudioDevicePropertyDeviceManufacturer,
kAudioDevicePropertyDeviceManufacturerCFString,
kAudioDevicePropertyDeviceName,
kAudioDevicePropertyDeviceNameCFString,
kAudioDevicePropertyHighPassFilterSettingNameForID,
kAudioDevicePropertyPlayThruDestinationNameForID,
kAudioDevicePropertyRegisterBufferList,
kAudioDevicePropertyStreamFormat,
kAudioDevicePropertyStreamFormatMatch,
kAudioDevicePropertyStreamFormatSupported,
kAudioDevicePropertyStreamFormats,
kAudioDevicePropertySupportsMixing, 

//scope output
kAudioDevicePropertyChannelNominalLineLevel,
kAudioDevicePropertyChannelNominalLineLevelNameForIDCFString,
kAudioDevicePropertyChannelNominalLineLevels,
kAudioDevicePropertyClipLight,
kAudioDevicePropertyClockSource,
kAudioDevicePropertyClockSourceKindForID,
kAudioDevicePropertyClockSourceNameForIDCFString,
kAudioDevicePropertyClockSources,
kAudioDevicePropertyDataSource,
kAudioDevicePropertyDataSourceKindForID,
kAudioDevicePropertyDataSourceNameForIDCFString,
kAudioDevicePropertyDataSources,
kAudioDevicePropertyHighPassFilterSetting,
kAudioDevicePropertyHighPassFilterSettingNameForIDCFString,
kAudioDevicePropertySolo,
kAudioDevicePropertyStereoPan,
kAudioDevicePropertyStereoPanChannels,
kAudioDevicePropertySubMute,
kAudioDevicePropertySubVolumeDecibels,
kAudioDevicePropertySubVolumeDecibelsToScalar,
kAudioDevicePropertySubVolumeRangeDecibels,
kAudioDevicePropertySubVolumeScalar,
kAudioDevicePropertySubVolumeScalarToDecibels,
kAudioDevicePropertyTalkback,
kAudioDevicePropertyVolumeDecibels,
kAudioDevicePropertyVolumeDecibelsToScalar,
kAudioDevicePropertyVolumeRangeDecibels,
kAudioDevicePropertyVolumeScalar,
kAudioDevicePropertyVolumeScalarToDecibels,
kAudioDevicePropertyHighPassFilterSettings,
kAudioDevicePropertyJackIsConnected,
kAudioDevicePropertyListenback,
kAudioDevicePropertyMute,
kAudioDevicePropertyPhantomPower,
kAudioDevicePropertyPhaseInvert,
kAudioDevicePropertyPlayThru,
kAudioDevicePropertyPlayThruDestination,
kAudioDevicePropertyPlayThruDestinationNameForIDCFString,
kAudioDevicePropertyPlayThruDestinations,
kAudioDevicePropertyPlayThruSolo,
kAudioDevicePropertyPlayThruStereoPan,
kAudioDevicePropertyPlayThruStereoPanChannels,
kAudioDevicePropertyPlayThruVolumeDecibels,
kAudioDevicePropertyPlayThruVolumeDecibelsToScalar,
kAudioDevicePropertyPlayThruVolumeRangeDecibels,
kAudioDevicePropertyPlayThruVolumeScalar,
kAudioDevicePropertyPlayThruVolumeScalarToDecibels,
kAudioDevicePropertySolo,
kAudioDevicePropertyStereoPan,
kAudioDevicePropertyStereoPanChannels,
kAudioDevicePropertySubMute,
kAudioDevicePropertySubVolumeDecibels,
kAudioDevicePropertySubVolumeDecibelsToScalar,
kAudioDevicePropertySubVolumeRangeDecibels,
kAudioDevicePropertySubVolumeScalar,
kAudioDevicePropertySubVolumeScalarToDecibels,
kAudioDevicePropertyTalkback,
kAudioDevicePropertyVolumeDecibels,
kAudioDevicePropertyVolumeDecibelsToScalar,
kAudioDevicePropertyVolumeRangeDecibels,
kAudioDevicePropertyVolumeScalar,
kAudioDevicePropertyVolumeScalarToDecibels,
kAudioDevicePropertyDataSource,

scope可以是下面項:

AudioObjectPropertyScopeGlobal,
AudioDevicePropertyScopeInput, 
AudioDevicePropertyScopeOutput,
AudioDevicePropertyScopePlayThrough

element 可以是下面的項:

kAudioObjectPropertyElementMaster = 0, 

使用 AudioObjectPropertyAddress 的函數有:

  • AudioObjectGetPropertyData
    OSStatus AudioObjectGetPropertyData(AudioObjectID inObjectID, 
                                        const AudioObjectPropertyAddress *inAddress, 
                                        UInt32 inQualifierDataSize, 
                                        const void *inQualifierData, 
                                        UInt32 *ioDataSize, 
                                        void *outData);
    
  • AudioObjectSetPropertyData
    OSStatus AudioObjectSetPropertyData(AudioObjectID inObjectID, 
                                        const AudioObjectPropertyAddress *inAddress, 
                                        UInt32 inQualifierDataSize, 
                                        const void *inQualifierData, 
                                        UInt32 inDataSize, 
                                        const void *inData);
    
  • AudioObjectRemovePropertyListener
    OSStatus AudioObjectRemovePropertyListener(AudioObjectID inObjectID, 
                                               const AudioObjectPropertyAddress *inAddress,  
                                               AudioObjectPropertyListenerProc inListener, 
                                               void *inClientData);
    
  • AudioObjectGetPropertyDataSize
    OSStatus AudioObjectGetPropertyDataSize(AudioObjectID inObjectID, 
                                            const AudioObjectPropertyAddress *inAddress, 
                                            UInt32 inQualifierDataSize, 
                                            const void *inQualifierData, 
                                            UInt32 *outDataSize);
    

其中,

  • AudioObjectID:也就是音頻設備ID, 默認設備爲 kAudioObjectSystemObject,即 1。

音頻參數結構體 AudioStreamBasicDescription

另外一個特別重要的結構體是:

typedef struct AudioStreamBasicDescription {
    ...
} AudioStreamBasicDescription;

您可以配置音頻流基本描述(ASBD)以指定具有相同大小的通道的線性PCM格式或恆定比特率(CBR)格式。對於可變比特率(VBR)音頻,以及對於信道具有不相等大小的CBR音頻,每個分組必須另外由AudioStreamPacketDescription結構描述。

字段值0表示該值未知或不適用於該格式。

始終將新音頻流基本描述結構的字段初始化爲零,如下所示:

AudioStreamBasicDescription myAudioDataFormat = {0};
要確定一個數據包表示的持續時間,請將mSampleRate字段與mFramesPerPacket字段一起使用,如下所示:

duration =(1 / mSampleRate)* mFramesPerPacket
在Core Audio中,以下定義適用:

音頻流是表示聲音的連續數據系列,例如歌曲。

聲道是單聲道音頻的離散軌道。單聲道流有一個channel;立體聲流有兩個channel。

樣本是音頻流中單個音頻通道的單個數值。

幀是時間重合樣本的集合。例如,線性PCM立體聲聲音文件每幀有兩個樣本,一個用於左聲道,一個用於右聲道。

數據包是一個或多個連續幀的集合。分組定義給定音頻數據格式的最小有意義的幀組,並且是可以測量時間的最小數據單元。在線性PCM音頻中,分組保持單個幀。在壓縮格式中,它通常擁有更多;在某些格式中,每個數據包的幀數會有所不同。

流的採樣率是未壓縮的每秒幀數(或者,對於壓縮格式,解壓縮的等效值)音頻。
ASBD 包括下面幾個字段:

mBitsPerChannel
每個音頻採樣的位數。

mBytesPerFrame
在音頻數組中開啓一個音頻幀到下一個音頻幀的字節數。對於壓縮格式,設置該域爲 0。

mBytesPerPacket
音頻數據在一個包中的字節數。如果包的大小是變化的,設置該域爲 0。在使用可變包大小的格式時,每個包的大小由AudioStreamPacketDescription結構指明。

mChannelsPerFrame
音頻數據在每一幀中的通道數。該值必須是非 0的。

mFormatFlags
指明格式細節的標誌位。設置爲 0表示沒有格式標誌。對於每種格式,可以查看 音頻數據格式標識。

mFormatID
一個標識符用於指明在流中的音頻數據格式。該值必須是非 0值。可以查看 音頻數據格式標識

mFramesPerPacket
音頻數據在一個包中幀的個數。對於非壓縮數據,該值爲 1。對於變化的比特率格式,該值是一個固定的很大的數,例如對於 AAC 是 1024。對於每個包中幀數變化的格式,如 Ogg,設置該域爲 0。

mReserved
爲了8字節對齊使用的填充結構,必須設置爲 0。

mSampleRate
當流以正常速度播放時,流中數據的每秒幀數。對於壓縮格式,該字段表示每秒等效解壓縮數據的幀數。

設置音頻參數

在Mac下使用 AudioConverterNew 函數設置音頻設備的音頻參數。其作用是創建一個新的基於指定音頻格式的音頻轉換對象。其原型如下:

OSStatus AudioConverterNew(const AudioStreamBasicDescription *inSourceFormat, 
                           const AudioStreamBasicDescription *inDestinationFormat, 
                           AudioConverterRef  _Nullable *outAudioConverter);
  • inSourceFormat:要被轉換的原音頻格式。
  • inDestinationFormat:將被轉換的目的音頻格式。
  • outAudioConverter:返回一個新的音頻轉換對象。

幾個重要的函數

在 CloudRTC中有幾個重要的函數需要我們特別重點介紹一下:

  • InitRecording :錄製設備的初始化
  • InitPlayout:播放設備的初始化

重要函數詳細介紹

InitRecording

首先,我們來看一下 InitRecording 函數,它主要做了幾下幾件事兒:

  • 初始化錄製設備/初始化播放設備
  • 獲取現在設備的音頻參數,並做一些判斷。
  • 然後根據自己的需要,通過創建音頻轉換對象來重置音頻參數
  • 設備音頻緩衝區大小
  • 設備錄製與播放的回調函數

其基本邏輯如下:

int32_t AudioDeviceMac::InitRecording()
{
    CriticalSectionScoped lock(&_critSect);

    ...

    // Initialize the microphone (devices might have been added or removed)
    if (InitMicrophone() == -1)
    ...

    if (!SpeakerIsInitialized())
    ...

    // Get current stream description
    AudioObjectPropertyAddress propertyAddress = { kAudioDevicePropertyStreamFormat,
                                                   kAudioDevicePropertyScopeInput, 
                                                   0 };
    memset(&_inStreamFormat, 0, sizeof(_inStreamFormat));
    size = sizeof(_inStreamFormat);
    HJAV_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_inputDeviceID,
                                                     &propertyAddress, 
                                                     0, 
                                                     NULL, 
                                                     &size, 
                                                     &_inStreamFormat));

    ...

    //設置錄製時採集音頻的相關參數
    _inDesiredFormat.mSampleRate = N_REC_SAMPLES_PER_SEC;
    _inDesiredFormat.mBytesPerPacket = _inDesiredFormat.mChannelsPerFrame
        * sizeof(SInt16);
    _inDesiredFormat.mFramesPerPacket = 1;
    _inDesiredFormat.mBytesPerFrame = _inDesiredFormat.mChannelsPerFrame
        * sizeof(SInt16);
    _inDesiredFormat.mBitsPerChannel = sizeof(SInt16) * 8;

    _inDesiredFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger
        | kLinearPCMFormatFlagIsPacked;
#ifdef HJAV_ARCH_BIG_ENDIAN
    _inDesiredFormat.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
#endif
    _inDesiredFormat.mFormatID = kAudioFormatLinearPCM;

    //創建音頻轉換對象並設置新參數
    HJAV_CA_RETURN_ON_ERR(AudioConverterNew(&_inStreamFormat, &_inDesiredFormat,
            &_captureConverter));

    // First try to set buffer size to desired value (10 ms * N_BLOCKS_IO)
    // TODO(xians): investigate this block.
    UInt32 bufByteCount = (UInt32)((_inStreamFormat.mSampleRate / 1000.0)
        * 10.0 * N_BLOCKS_IO * _inStreamFormat.mChannelsPerFrame
        * sizeof(Float32));
    if (_inStreamFormat.mFramesPerPacket != 0)
    {
        if (bufByteCount % _inStreamFormat.mFramesPerPacket != 0)
        {
            bufByteCount = ((UInt32)(bufByteCount
                / _inStreamFormat.mFramesPerPacket) + 1)
                * _inStreamFormat.mFramesPerPacket;
        }
    }

    // Ensure the buffer size is within the acceptable range provided by the device.
    propertyAddress.mSelector = kAudioDevicePropertyBufferSizeRange;
    AudioValueRange range;
    size = sizeof(range);
    HJAV_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_inputDeviceID,
            &propertyAddress, 0, NULL, &size, &range));
    if (range.mMinimum > bufByteCount)
    {
        bufByteCount = range.mMinimum;
    } else if (range.mMaximum < bufByteCount)
    {
        bufByteCount = range.mMaximum;
    }

    propertyAddress.mSelector = kAudioDevicePropertyBufferSize;
    size = sizeof(bufByteCount);
    HJAV_CA_RETURN_ON_ERR(AudioObjectSetPropertyData(_inputDeviceID,
            &propertyAddress, 0, NULL, size, &bufByteCount));

    // Get capture device latency
    propertyAddress.mSelector = kAudioDevicePropertyLatency;
    UInt32 latency = 0;
    size = sizeof(UInt32);
    HJAV_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_inputDeviceID,
            &propertyAddress, 0, NULL, &size, &latency));
    _captureLatencyUs = (UInt32)((1.0e6 * latency)
        / _inStreamFormat.mSampleRate);

    // Get capture stream latency
    propertyAddress.mSelector = kAudioDevicePropertyStreams;
    AudioStreamID stream = 0;
    size = sizeof(AudioStreamID);
    HJAV_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_inputDeviceID,
            &propertyAddress, 0, NULL, &size, &stream));
    propertyAddress.mSelector = kAudioStreamPropertyLatency;
    size = sizeof(UInt32);
    latency = 0;
    HJAV_CA_RETURN_ON_ERR(AudioObjectGetPropertyData(_inputDeviceID,
            &propertyAddress, 0, NULL, &size, &latency));
    _captureLatencyUs += (UInt32)((1.0e6 * latency)
        / _inStreamFormat.mSampleRate);

    // Listen for format changes
    // TODO(xians): should we be using kAudioDevicePropertyDeviceHasChanged?
    propertyAddress.mSelector = kAudioDevicePropertyStreamFormat;
    HJAV_CA_RETURN_ON_ERR(AudioObjectAddPropertyListener(_inputDeviceID,
            &propertyAddress, &objectListenerProc, this));

    // Listen for processor overloads
    propertyAddress.mSelector = kAudioDeviceProcessorOverload;
    HJAV_CA_LOG_WARN(AudioObjectAddPropertyListener(_inputDeviceID,
            &propertyAddress, &objectListenerProc, this));

    //如果設備既是輸入設備又是輸出設備
    if (_twoDevices)
    {
        //設置設備的回調函數爲 inDeviceIOProc。
        HJAV_CA_RETURN_ON_ERR(AudioDeviceCreateIOProcID(_inputDeviceID,
                inDeviceIOProc, this, &_inDeviceIOProcID));
    } else if (!_playIsInitialized)
    {
        //如果只是輸出設備,則設備其回調函數爲 deviceIOProc
        HJAV_CA_RETURN_ON_ERR(AudioDeviceCreateIOProcID(_inputDeviceID,
                deviceIOProc, this, &_deviceIOProcID));
    }
    ...
}

錄製的回調函數

音頻錄製的回調函數設置爲,inDeviceIOProc,其代碼如下:

OSStatus AudioDeviceMac::inDeviceIOProc(AudioDeviceID, const AudioTimeStamp*,
                                        const AudioBufferList* inputData,
                                        const AudioTimeStamp* inputTime,
                                        AudioBufferList*,
                                        const AudioTimeStamp*, void* clientData)
{
    ...
    ptrThis->implInDeviceIOProc(inputData, inputTime);
    ...
}

OSStatus AudioDeviceMac::implInDeviceIOProc(const AudioBufferList *inputData,
                                            const AudioTimeStamp *inputTime)
{
    
    ...

    ring_buffer_size_t numSamples = inputData->mBuffers->mDataByteSize
        * _inStreamFormat.mChannelsPerFrame / _inStreamFormat.mBytesPerPacket;
    PaUtil_WriteRingBuffer(_paCaptureBuffer, inputData->mBuffers->mData,
                           numSamples);

    kern_return_t kernErr = semaphore_signal_all(_captureSemaphore);
    ...
}

通過上面的代碼可以清楚的知道,在音頻設備採集到音頻數據後,會寫入到緩衝隊列中。這裏需要特別注意的是,音頻採集的數據是因設備而固定的,最後還需要通地 AudioConvert進行一次轉換才能拿到用戶在初始化時設備的音頻參數數據,這一點特別重要。

那麼,AudioConvert是在什麼時候調用的呢?
通過查看代碼,我們可以知道,該函數是在 CaptureThread線程中調用的。現在我們就來看一下這個邏輯。

首先,我們先看一下 CaptureThread是在什麼時候創建的吧:

int32_t AudioDeviceMac::Init()
{

    CriticalSectionScoped lock(&_critSect);

    ...
    if (!_renderWorkerThread)
    {
        _renderWorkerThread
            = ThreadWrapper::CreateThread(RunRender, this,
                                          "RenderWorkerThread");
    }

    if (!_captureWorkerThread)
    {
        _captureWorkerThread
            = ThreadWrapper::CreateThread(RunCapture, this,
                                          "CaptureWorkerThread");
    }

    kern_return_t kernErr = KERN_SUCCESS;
    kernErr = semaphore_create(mach_task_self(), &_renderSemaphore,
                               SYNC_POLICY_FIFO, 0);
    ...
    kernErr = semaphore_create(mach_task_self(), &_captureSemaphore,
                               SYNC_POLICY_FIFO, 0);
    ...
    // Setting RunLoop to NULL here instructs HAL to manage its own thread for
    // notifications. This was the default behaviour on OS X 10.5 and earlier,
    // but now must be explicitly specified. HAL would otherwise try to use the
    // main thread to issue notifications.
    AudioObjectPropertyAddress propertyAddress = {
            kAudioHardwarePropertyRunLoop,
            kAudioObjectPropertyScopeGlobal,
            kAudioObjectPropertyElementMaster };
    CFRunLoopRef runLoop = NULL;
    UInt32 size = sizeof(CFRunLoopRef);
    HJAV_CA_RETURN_ON_ERR(AudioObjectSetPropertyData(kAudioObjectSystemObject,
            &propertyAddress, 0, NULL, size, &runLoop));

    // Listen for any device changes.
    propertyAddress.mSelector = kAudioHardwarePropertyDevices;
    HJAV_CA_LOG_ERR(AudioObjectAddPropertyListener(kAudioObjectSystemObject,
            &propertyAddress, &objectListenerProc, this));

    ...

    int intErr = sysctlbyname("hw.model", buf, &length, NULL, 0);
    ...
}

通過上面的代碼可以看到 CaptureThread 和RenderThread兩個線程是在音頻設備初始化的時候創建的,在音頻設備被初始化時,做了以下幾件事兒:

  • 創建錄製線程
  • 創建音頻渲染線程
  • 創建錄製線程的信號量
  • 創建渲染線程的信號量
  • 設置 RunLoop 爲 NULL,以便 HAL自己管理它自己的線程。
  • 設置設備變化的偵聽
  • 最後是獲取音頻設備名子

下面我們來看一下 CaptureThread 都做些什麼事兒:

bool AudioDeviceMac::RunCapture(void* ptrThis)
{
    return static_cast<AudioDeviceMac*> (ptrThis)->CaptureWorkerThread();
}

bool AudioDeviceMac::CaptureWorkerThread()
{
    ...
    AudioBufferList engineBuffer;
    engineBuffer.mNumberBuffers = 1; // Interleaved channels.
    engineBuffer.mBuffers->mNumberChannels = _inDesiredFormat.mChannelsPerFrame;
    engineBuffer.mBuffers->mDataByteSize = _inDesiredFormat.mBytesPerPacket
        * noRecSamples;
    engineBuffer.mBuffers->mData = recordBuffer;
    //通過該函數,會將採集到的數據轉成希望的數據格式最終放到 recordBuffer中
    err = AudioConverterFillComplexBuffer(_captureConverter, inConverterProc,
                                          this, &size, &engineBuffer, NULL);
   
    // TODO(xians): what if the returned size is incorrect?
    if (size == ENGINE_REC_BUF_SIZE_IN_SAMPLES)
    {
        ...
        // store the recorded buffer (no action will be taken if the
        // #recorded samples is not a full buffer)
        _ptrAudioBuffer->SetRecordedBuffer((int8_t*) &recordBuffer,
                                           (uint32_t) size);
         ...
        // deliver recorded samples at specified sample rate, mic level etc.
        // to the observer using callback
        _ptrAudioBuffer->DeliverRecordedData();
        ...
    }

    return true;
}

在 CaptureWorkerThread線程中,首先會調用 AudioConverterFillComplexBuffer 函數進行音頻數據轉換,最終通過 DeliverRecordedData 函數傳遞出去。

AudioConverterFillComplexBuffer原型如下:

OSStatus AudioConverterFillComplexBuffer(AudioConverterRef inAudioConverter, 
                                         AudioConverterComplexInputDataProc inInputDataProc, 
                                         void *inInputDataProcUserData, 
                                         UInt32 *ioOutputDataPacketSize, 
                                         AudioBufferList *outOutputData, 
                                         AudioStreamPacketDescription *outPacketDescription);

轉換由回調函數提供的音頻數據,支持非交錯和分組格式。另外,此功能用於所有音頻數據格式轉換,但從一種線性PCM格式轉換爲另一種沒有採樣率轉換的特殊情況除外。

  • inAudioConverter: 音頻轉換器對象,用於音頻格式轉換。

  • inInputDataProc: 提供轉換音頻數據的回調函數。當轉換器爲新輸入數據做好準備時,將重複調用此回調。

  • inInputDataProcUserData:當收到一個回調時,由應用程序使用的特定數據。

  • ioOutputDataPacketSize:輸入時,輸出緩衝區的大小(在outOutputData參數中),以音頻轉換器輸出格式的數字包表示。輸出時,寫入輸出緩衝區的轉換數據包的數量。

  • outOutputData:輸出時表式轉換後的音頻數據。

  • outPacketDescription:在輸入時,必須指向能夠保存ioOutputDataPacketSize參數中指定的數據包描述數的內存塊。 (有關可確定音頻格式是否使用數據包描述的函數,請參閱音頻格式服務參考)。如果輸出不爲NULL,並且音頻轉換器的輸出格式使用數據包描述,則此參數包含數據包描述數組。

在我們這個代碼中,通過 AudioConverterFillComplexBuffer 轉換後,就可以從 engineBuffer 獲到 size 大小的轉換後的音頻數據了。

下面我們再來看一下 AudioConverterFillComplexBuffer 中設備的回調函數都做了哪些事兒:

OSStatus AudioDeviceMac::inConverterProc(
    AudioConverterRef,
    UInt32 *numberDataPackets,
    AudioBufferList *data,
    AudioStreamPacketDescription ** /*dataPacketDescription*/,
    void *userData)
{
    AudioDeviceMac *ptrThis = static_cast<AudioDeviceMac*> (userData);
    assert(ptrThis != NULL);

    return ptrThis->implInConverterProc(numberDataPackets, data);
}

OSStatus AudioDeviceMac::implInConverterProc(UInt32 *numberDataPackets,
                                             AudioBufferList *data)
{
    ...
    
    while (PaUtil_GetRingBufferReadAvailable(_paCaptureBuffer) < numSamples)
    {
        ...

        kern_return_t kernErr = semaphore_timedwait(_captureSemaphore, timeout);
        ...
    }

    // Pass the read pointer directly to the converter to avoid a memcpy.
    void* dummyPtr;
    ring_buffer_size_t dummySize;
    PaUtil_GetRingBufferReadRegions(_paCaptureBuffer, numSamples,
                                    &data->mBuffers->mData, &numSamples,
                                    &dummyPtr, &dummySize);
    PaUtil_AdvanceRingBufferReadIndex(_paCaptureBuffer, numSamples);

    data->mBuffers->mNumberChannels = _inStreamFormat.mChannelsPerFrame;
    *numberDataPackets = numSamples / _inStreamFormat.mChannelsPerFrame;
    data->mBuffers->mDataByteSize = *numberDataPackets
        * _inStreamFormat.mBytesPerPacket;

    return 0;
}

通過上面的代碼可以知道轉換器通過該回調從RingBuffer中獲取要轉換的音頻數據,然後進行轉換,最終通過其倒數第二個參數輸出輸換後的音頻數據。

未完待續!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章