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中获取要转换的音频数据,然后进行转换,最终通过其倒数第二个参数输出输换后的音频数据。

未完待续!

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