前言
在之前的文章中已經將 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中獲取要轉換的音頻數據,然後進行轉換,最終通過其倒數第二個參數輸出輸換後的音頻數據。
未完待續!