[深入理解Android卷一全文-第七章]深入理解Audio系統
由於《深入理解Android 卷一》和《深入理解Android卷二》不再出版,而知識的傳播不應該因為紙質媒介的問題而中斷,所以我將在CSDN部落格中全文轉發這兩本書的全部內容。
第7章 深入理解Audio系統
本章主要內容
· 詳細分析AudioTrack。
· 詳細分析AudioFlinger。
· 詳細分析AudioPolicyService。
本章涉及的原始碼檔名及位置
下面是本章分析的原始碼檔名及其位置。
· AudioTrack.java
framework/base/media/java/com/android/media/AudioTrack.java
· android_media_track.cpp
framework/base/core/jni/android_media_track.cpp
· MemoryHeapBase
framework/base/libs/binder/MemoryHeapBase.cpp
· MemoryBase.h
framework/base/include/binder/MemoryBase.h
· AudioTrack.cpp
framework/base/libmedia/AudioTrack.cpp
· audio_track_cblk_t宣告
framework/base/include/private/media/AudioTrackShared.h
· audio_track_cblk_t定義
framework/base/media/libmedia/AudioTrack.cpp
· Main_MediaServer.cpp
framework/base/media/mediaserver/Main_MediaServer.cpp
· AudioFlinger.cpp
framework/base/libs/audioFlinger/AudioFlinger.cpp
· AudioHardwareInterface.h
hardware/libhardware_legacy/include/hardware_legacy/AudioHardwareInterface.h
· AudioMixer.cpp
framework/base/libs/audioflinger/AudioMixer.cpp
· AudioSystem.h
framework/base/include/media/AudioSystem.h
· AudioSystem.cpp
framework/base/media/libmedia/AudioSystem.cpp
· AudioPolicyInterface.h
hardware/libhardware_legacy/include/hardware_legacy
· AudioPolicyManagerBase.cpp
framework/base/libs/audioflinger/AudioPolicyManagerBase.cpp
· AudioService.java
framework/base/media/java/com/android/media/AudioService.java
· Android_media_AudioSystem.cpp
framework/base/core/Jni/Android_media_AudioSystem.cpp
7.1 綜述
Audio系統是Android平臺的重要組成部分,它主要包括三方面的內容:
· AudioRcorder和AudioTrack:這兩個類屬於Audio系統對外提供的API類,通過它們可以完成Android平臺上音訊資料的採集和輸出任務。
· AudioFlinger:它是Audio系統的工作引擎,管理著系統中的輸入輸出音訊流,並承擔音訊資料的混音,以及讀寫Audio硬體以實現資料的輸入輸出等工作。
· AudioPolicyService,它是Audio系統的策略控制中心,具有掌管系統中聲音裝置的選擇和切換、音量控制等功能。
Android的Audio系統是我們分析的第一個具有相當難度的複雜系統。對於這種系統,我採取的學習方法是,以一個常見用例為核心,沿著重要函式呼叫的步驟逐步進行深入分析。中途若出現所需但並不熟悉的知識,則以此為契機,及時學習、思考、研究,當不熟悉的知識逐漸被自己瞭解掌握時,該系統的真面目也隨之清晰了。
下面是破解Audio系統的戰略步驟:
· 首先,從API類的AudioTrack開始,從Java層到Native層一步步瞭解其工作原理。其中AudioTrack和AudioFlinger有較多的互動工作,但在這一步中,我們暫時只集中關注AudioTrack的流程。
· 提煉上一步中AudioTrack和AudioFlinger的互動流程,以這個互動流程作為分析AudioFlinger的突破口。
· 在前面兩個步驟中還會有一些剩餘的“抵抗分子”,我們將在AudioPolicyService的破解過程中把它們徹底消滅掉。另外,在分析AudioPolicyService時,還會通過一個耳機插入事件的處理例項來幫助分析AudioPolicyService的工作流程。
· 最後,在本章的擴充部分,我們會介紹一下AudioFlinger中DuplicatingThread的工作原理。
說明:在下文中AudioTrack被簡寫為AT,AudioFlinger被簡寫為AF,AudioPolicyService被簡寫為AP。
讓我們整裝上陣,開始程式碼的征程吧!
7.2 AudioTrack的破解
AudioTrack屬於Audio系統對外提供的API類,所以它在Java層和Native層均有對應類,先從Java層的用例開始。
7.2.1 用例介紹
這個用例很簡單,但其中會有一些重要概念,應注意理解。
注意:要了解AudioTrack Java API的具體資訊,需要仔細閱讀Android API中的相關文件。閱讀API文件,是一個能快速掌握相關知識的好方法。
[-->AudioTrackAPI使用例子(Java層)]
//① 根據音訊資料的特性來確定所要分配的緩衝區的最小size
int bufsize =
AudioTrack.getMinBufferSize(8000,//取樣率:每秒8K個點
AudioFormat.CHANNEL_CONFIGURATION_STEREO,//聲道數:雙聲道
AudioFormat.ENCODING_PCM_16BIT//取樣精度:一個取樣點16位元,相當於2個位元組
);
//② 建立AudioTrack
AudioTrack trackplayer = new AudioTrack(
AudioManager.STREAM_MUSIC,//音訊流型別
8000,AudioFormat.CHANNEL_CONFIGURATION_ STEREO,
AudioFormat.ENCODING_PCM_16BIT, bufsize,
AudioTrack.MODE_STREAM//資料載入模式);
//③ 開始播放
trackplayer.play() ;
......
//④ 呼叫write寫資料
trackplayer.write(bytes_pkg, 0,bytes_pkg.length) ;//往track中寫資料
......
//⑤ 停止播放和釋放資源
trackplayer.stop();//停止播放
trackplayer.release();//釋放底層資源
上面的用例引入了兩個新的概念,一個是資料載入模式,另一個是音訊流型別。下面進行詳細介紹。
1. AudioTrack的資料載入模式
AudioTrack有兩種資料載入模式:MODE_STREAM和MODE_STATIC,它們對應著兩種完全不同的使用場景。
· MODE_STREAM:在這種模式下,通過write一次次把音訊資料寫到AudioTrack中。這和平時通過write系統呼叫往檔案中寫資料類似,但這種工作方式每次都需要把資料從使用者提供的Buffer中拷貝到AudioTrack內部的Buffer中,這在一定程度上會使引入延時。為解決這一問題,AudioTrack就引入了第二種模式。
· MODE_STATIC:這種模式下,在play之前只需要把所有資料通過一次write呼叫傳遞到AudioTrack中的內部緩衝區,後續就不必再傳遞資料了。這種模式適用於像鈴聲這種記憶體佔用量較小,延時要求較高的檔案。但它也有一個缺點,就是一次write的資料不能太多,否則系統無法分配足夠的記憶體來儲存全部資料。
這兩種模式中以MODE_STREAM模式相對常見和複雜,我們的分析將以它為主。
注意:如果採用STATIC模式,須先呼叫write寫資料,然後再呼叫play。
2. 音訊流的型別
在AudioTrack建構函式中,會接觸到AudioManager.STREAM_MUSIC這個引數。它的含義與Android系統對音訊流的管理和分類有關。
Android將系統的聲音分為好幾種流型別,下面是幾個常見的:
· STREAM_ALARM:警告聲
· STREAM_MUSIC:音樂聲,例如music等
· STREAM_RING:鈴聲
· STREAM_SYSTEM:系統聲音,例如低電提示音,鎖屏音等
· STREAM_VOCIE_CALL:通話聲
注意:上面這些型別的劃分和音訊資料本身並沒有關係。例如MUSIC和RING型別都可以是某首MP3歌曲。另外,聲音流型別的選擇沒有固定的標準,例如,鈴聲預覽中的鈴聲可以設定為MUSIC型別。
音訊流型別的劃分和Audio系統對音訊的管理策略有關。其具體作用,在以後的分析中再做詳細介紹。在目前的用例中,把它當做一個普通數值即可。
3. Buffer分配和Frame的概念
在用例中碰到的第一個重要函式就是getMinBufferSize。這個函式對於確定應用層分配多大的資料Buffer具有重要指導意義。先回顧一下它的呼叫方式:
[-->AudioTrackAPI使用例子(Java層)]
//注意這些引數的值。想象我們正在一步步的Trace,這些引數都會派上用場
AudioTrack.getMinBufferSize(8000,//每秒8K個點
AudioFormat.CHANNEL_CONFIGURATION_STEREO,//雙聲道
AudioFormat.ENCODING_PCM_16BIT);
來看這個函式的實現:
[-->AudioTrack.java]
static public int getMinBufferSize(intsampleRateInHz, int channelConfig,
intaudioFormat) {
int channelCount = 0;
switch(channelConfig) {
case AudioFormat.CHANNEL_OUT_MONO:
caseAudioFormat.CHANNEL_CONFIGURATION_MONO:
channelCount = 1;
break;
case AudioFormat.CHANNEL_OUT_STEREO:
case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
channelCount = 2;//目前最多支援雙聲道
break;
default:
return AudioTrack.ERROR_BAD_VALUE;
}
//目前只支援PCM8和PCM16精度的音訊資料
if((audioFormat != AudioFormat.ENCODING_PCM_16BIT)
&& (audioFormat != AudioFormat.ENCODING_PCM_8BIT)) {
return AudioTrack.ERROR_BAD_VALUE;
}
//對取樣頻率也有要求,太低或太高都不行。
if( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) )
return AudioTrack.ERROR_BAD_VALUE;
/*
呼叫Native函式,先想想為什麼,如果是簡單計算,那麼Java層做不到嗎?
原來,還需要確認硬體是否支援這些引數,當然得進入Native層查詢了
*/
int size = native_get_min_buff_size(sampleRateInHz,
channelCount,audioFormat);
if((size == -1) || (size == 0)) {
return AudioTrack.ERROR;
}
else {
return size;
}
}
Native的函式將查詢Audio系統中音訊輸出硬體HAL物件的一些資訊,並確認它們是否支援這些取樣率和取樣精度。
說明:HAL物件的具體實現和硬體廠商有關係,如果沒有特殊說明,我們則把硬體和HAL作為一種東西討論。
來看Native的native_get_min_buff_size函式。它在android_media_track.cpp中。
[-->android_media_track.cpp]
/*
注意我們傳入的引數是:
sampleRateInHertz = 8000,nbChannels = 2
audioFormat = AudioFormat.ENCODING_PCM_16BIT
*/
static jintandroid_media_AudioTrack_get_min_buff_size(
JNIEnv*env, jobject thiz,
jintsampleRateInHertz, jint nbChannels, jint audioFormat)
{
intafSamplingRate;
intafFrameCount;
uint32_t afLatency;
/*
下面這些呼叫涉及了AudioSystem,這個和AudioPolicy有關係。這裡僅把它們看成是
資訊查詢即可
*/
//查詢取樣率,一般返回的是所支援的最高取樣率,例如44100
if(AudioSystem::getOutputSamplingRate(&afSamplingRate) != NO_ERROR) {
return -1;
}
//① 查詢硬體內部緩衝的大小,以Frame為單位。什麼是Frame?
if(AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) {
return -1;
}
//查詢硬體的延時時間
if(AudioSystem::getOutputLatency(&afLatency) != NO_ERROR) {
return -1;
}
......
這裡有必要插入內容,因為程式碼中出現了音訊系統中的一個重要概念:Frame(幀)。
說明:Frame是一個單位,經多方查尋,最終在ALSA的wiki中找到了對它的解釋。Frame直觀上用來描述資料量的多少,例如,一幀等於多少位元組。1單位的Frame等於1個取樣點的位元組數×聲道數(比如PCM16,雙聲道的1個Frame等於2×2=4位元組)。
我們知道,1個取樣點只針對一個聲道,而實際上可能會有一或多個聲道。由於不能用一個獨立的單位來表示全部聲道一次取樣的資料量,也就引出了Frame的概念。Frame的大小,就是一個取樣點的位元組數×聲道數。另外,在目前的音效卡驅動程式中,其內部緩衝區也是採用Frame作為單位來分配和管理的。
OK,繼續native_get_min_buff_size函式。
......
// minBufCount表示緩衝區的最少個數,它以Frame作為單位
uint32_t minBufCount = afLatency / ((1000 *afFrameCount)/afSamplingRate);
if(minBufCount < 2) minBufCount = 2;//至少要兩個緩衝
//計算最小幀個數
uint32_tminFrameCount =
(afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate;
//下面根據最小的FrameCount計算最小的緩衝大小
intminBuffSize = minFrameCount //計算方法完全符合我們前面關於Frame的介紹
* (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1)
* nbChannels;
returnminBuffSize;
}
getMinBufSize會綜合考慮硬體的情況(諸如是否支援取樣率,硬體本身的延遲情況等)後,得出一個最小緩衝區的大小。一般我們分配的緩衝大小會是它的整數倍。
好了,介紹完一些基本概念後,開始要分析AudioTrack了。
7.2.2 AudioTrack(Java空間)的分析
注意:Java空間的分析包括JNI這一層,因為它們二者的關係最為緊密。
1. AudioTrack的構造
回顧一下用例中呼叫AudioTrack建構函式的程式碼:
AudioTrack trackplayer = new AudioTrack(
AudioManager.STREAM_MUSIC,
8000,AudioFormat.CHANNEL_CONFIGURATION_ STEREO,
AudioFormat.ENCODING_PCM_16BIT,bufsize,
AudioTrack.MODE_STREAM);
AudioTrack建構函式的實現在AudioTrack.java中。來看這個函式:
[-->AudioTrack.java]
public AudioTrack(int streamType, intsampleRateInHz, int channelConfig,
intaudioFormat,int bufferSizeInBytes, int mode)
throws IllegalArgumentException {
mState= STATE_UNINITIALIZED;
//檢查引數是否合法
audioParamCheck(streamType, sampleRateInHz, channelConfig,
audioFormat,mode);
//bufferSizeInBytes是通過getMinBufferSize得到的,所以下面的檢查肯定能通過
audioBuffSizeCheck(bufferSizeInBytes);
/*
呼叫native層的native_setup,構造一個WeakReference傳進去。
不瞭解Java WeakReference讀者可以上網查一下,很簡單
*/
int initResult = native_setup(new WeakReference<AudioTrack>(this),
mStreamType,//這個值是AudioManager.STREAM_MUSIC
mSampleRate, //這個值是8000
mChannels, //這個值是2
mAudioFormat,//這個值是AudioFormat.ENCODING_PCM_16BIT
mNativeBufferSizeInBytes,//這個值等於bufferSizeInBytes
mDataLoadMode);//DataLoadMode是MODE_STREAM
....
}
OK,native_setup對應的JNI層函式是android_media_AudioTrack_native_setup。一起來看:
[-->android_media_AudioTrack.cpp]
static int
android_media_AudioTrack_native_setup(JNIEnv*env, jobject thiz,
jobjectweak_this,jint streamType,
jintsampleRateInHertz, jint channels,
jintaudioFormat, jint buffSizeInBytes,
jintmemoryMode)
{
intafSampleRate;
intafFrameCount;
//進行一些資訊查詢
AudioSystem::getOutputFrameCount(&afFrameCount, streamType);
AudioSystem::getOutputSamplingRate(&afSampleRate, streamType);
AudioSystem::isOutputChannel(channels);
//popCount用於統計一個整數中有多少位為1,有很多經典的演算法
int nbChannels = AudioSystem::popCount(channels);
//Java層的值和JNI層的值轉換
if(streamType == javaAudioTrackFields.STREAM_MUSIC)
atStreamType = AudioSystem::MUSIC;
intbytesPerSample = audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1;
intformat = audioFormat == javaAudioTrackFields.PCM16 ?
AudioSystem::PCM_16_BIT : AudioSystem::PCM_8_BIT;
//計算以幀為單位的緩衝大小
intframeCount = buffSizeInBytes / (nbChannels * bytesPerSample);
//① AudioTrackJniStorage物件,它儲存了一些資訊,後面將詳細分析
AudioTrackJniStorage* lpJniStorage = new AudioTrackJniStorage();
......
//②建立Native層的AudioTrack物件
AudioTrack* lpTrack = new AudioTrack();
if(memoryMode == javaAudioTrackFields.MODE_STREAM) {
//③STREAM模式
lpTrack->set(
atStreamType,//指定流型別
sampleRateInHertz,
format,// 取樣點的精度,一般為PCM16或者PCM8
channels,
frameCount,
0,// flags
audioCallback, //該回撥函式定義在android_media_AudioTrack.cpp中
&(lpJniStorage->mCallbackData),
0,
0,// 共享記憶體,STREAM模式下為空。實際使用的共享記憶體由AF建立
true);//內部執行緒可以呼叫JNI函式,還記得“zygote偷樑換柱”那一節嗎?
} else if (memoryMode == javaAudioTrackFields.MODE_STATIC) {
//如果是static模式,需要先建立共享記憶體
lpJniStorage->allocSharedMem(buffSizeInBytes);
lpTrack->set(
atStreamType,// stream type
sampleRateInHertz,
format,// word length, PCM
channels,
frameCount,
0,// flags
audioCallback,
&(lpJniStorage->mCallbackData),
0,
lpJniStorage->mMemBase, //STATIC模式下,需要傳遞該共享記憶體
true);
}
......
/*
把JNI層中new出來的AudioTrack物件指標儲存到Java物件的一個變數中,
這樣就把JNI層的AudioTrack物件和Java層的AudioTrack物件關聯起來了,
這是Android的常用技法。
*/
env->SetIntField(thiz,javaAudioTrackFields.nativeTrackInJavaObj,
(int)lpTrack);
// lpJniStorage物件指標也儲存到Java物件中
env->SetIntField(thiz, javaAudioTrackFields.jniData,(int)lpJniStorage);
}
上邊的程式碼列出了三個要點,這一節僅分析AudioTrackJniStorage這個類,其餘的作為Native AudioTrack部分放在後面進行分析。
2. AudioTrackJniStorage分析
AudioTrackJniStorage是一個輔助類,其中有一些有關共享記憶體方面的較重要的知識,這裡先簡單介紹一下。
(1) 共享記憶體介紹
共享記憶體,作為程式間資料傳遞的一種手段,在AudioTrack和AudioFlinger中被大量使用。先簡單瞭解一下有關共享記憶體的知識:
· 每個程式的記憶體空間是4GB,這個4GB是由指標長度決定的,如果指標長度為32位,那麼地址的最大編號就是0xFFFFFFFF,為4GB。
· 上面說的記憶體空間是程式的虛擬地址空間。換言之,在應用程式中使用的指標其實是指向虛擬空間地址的。那麼,如何通過這個虛地址找到儲存在真實實體記憶體中的資料呢?
上面的問題,引出了記憶體對映的概念。記憶體對映讓虛擬空間中的記憶體地址和真實實體記憶體地址之間建立了一種對應關係。也就是說,程式中操作的0x12345678這塊記憶體的地址,在經過OS記憶體管理機制的轉換後,它實際對應的實體地址可能會是0x87654321。當然,這一切對程式來說都是透明的,這些活都由作業系統悄悄地完成了。這和我們的共享記憶體會有什麼關係嗎?
當然有,共享記憶體和記憶體對映有著重要關係。來看圖7-1“共享記憶體示意圖”:
圖7-1 共享記憶體示意圖
圖7-1提出了一個關鍵性問題,即真實記憶體中0x87654321標誌的這塊記憶體頁(OS的記憶體管理機制將實體記憶體分成了一個個的記憶體頁,一塊記憶體頁的大小一般是4KB)現在已經對映到了程式A中。可它能同時對映到程式B中嗎?如果能,那麼在程式A中,對這塊記憶體頁所寫的資料在程式B中就能看見了,這豈不就做到了記憶體在兩個程式間共享嗎?
事實確實如此,否則我們的生活就不會像現在這麼美好了。這個機制是由作業系統提供和實現的,原理很簡單,實現起來卻很複雜,這裡就不深究了。
如何建立和共享記憶體呢?不同系統會有不同的方法。Linux平臺的一般做法是:
· 程式A建立並開啟一個檔案,得到一個檔案描述符fd。
· 通過mmap呼叫將fd對映成記憶體對映檔案。在mmap呼叫中指定特定參數列示要建立程式間共享記憶體。
· 程式B開啟同一個檔案,也得到一個檔案描述符,這樣A和B就開啟了同一個檔案。
· 程式B也要用mmap呼叫指定參數列示想使用共享記憶體,並傳遞開啟的fd。這樣A和B就通過開啟同一個檔案並構造記憶體對映,實現了程式間記憶體共享。
注意,這個檔案也可以是裝置檔案。一般來說,mmap函式的具體工作由引數中的那個檔案描述符所對應的驅動或核心模組來完成。
除上述一般方法外,Linux還有System V的共享記憶體建立方法,這裡就不再介紹了。總之,AT和AF之間的資料傳遞,就是通過共享記憶體方式來完成的。這種方式對於跨程式的大資料量傳輸來說,是非常高效的。
(2) MemoryHeapBase和MemoryBase類介紹
AudioTrackJniStorage用到了Android對共享記憶體機制的封裝類。所以我們有必要先看看AudioTrackJniStorage的內容。
[-->android_media_AudioTrack.cpp::AudioTrackJniStorage相關]
//下面這個結構就是儲存一些變數,沒有什麼特別的作用
struct audiotrack_callback_cookie {
jclass audioTrack_class;
jobject audioTrack_ref;
};
class AudioTrackJniStorage {
public:
sp<MemoryHeapBase> mMemHeap;//這兩個Memory很重要
sp<MemoryBase> mMemBase;
audiotrack_callback_cookie mCallbackData;
int mStreamType;
boolallocSharedMem(int sizeInBytes) {
/*
注意關於MemoryHeapBase和MemoryBase的用法。
先new一個MemoryHeapBase,再以它為引數new一個MemoryBase
*/
//① MemoryHeapBase
mMemHeap = new MemoryHeapBase(sizeInBytes, 0,"AudioTrack Heap Base");
//②MemoryBase
mMemBase= new MemoryBase(mMemHeap, 0, sizeInBytes);
return true;
}
};
注意程式碼中所標識的地方,它們很好地展示了這兩個Memory類的用法。在介紹它們之前,先來看圖7-2中與這兩個Memory有關的家譜。
圖7-2 MemoryHeapBase和MemoryBase的家譜
MemoryHeapBase是一個基於Binder通訊的類,根據前面的Binder知識,BpMemoryHeapBase由客戶端使用,而MemoryHeapBase完成BnMemoryHeapBase的業務工作。
從MemoryHeapBase開始分析。它的使用方法是:
mMemHeap = new MemoryHeapBase(sizeInBytes, 0,"AudioTrack Heap Base");
它的程式碼在MemoryHeapBase.cpp中。
[-->MemoryHeapBase.cpp]
/*
MemoryHeapBase有兩個建構函式,我們用的是第一個。
size表示共享記憶體大小,flags為0,name為"AudioTrackHeap Base"
*/
MemoryHeapBase::MemoryHeapBase(size_t size,uint32_t flags,char const * name)
:mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags),
mDevice(0), mNeedUnmap(false)
{
constsize_t pagesize = getpagesize();//獲取系統中的記憶體頁大小,一般為4KB
size =((size + pagesize-1) & ~(pagesize-1));
/*
建立共享記憶體,ashmem_create_region函式由libcutils提供。
在真實裝置上將開啟/dev/ashmem裝置得到一個檔案描述符,在模擬器上則建立一個tmp檔案
*/
int fd= ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size);
//下面這個函式將通過mmap方式得到記憶體地址,這是Linux的標準做法,有興趣的讀者可以看看
mapfd(fd,size);
}
MemoryHeapBase構造完後,得到了以下結果:
· mBase變數指向共享記憶體的起始位置。
· mSize是所要求分配的記憶體大小。
· mFd是ashmem_create_region返回的檔案描述符。
另外,MemoryHeapBase提供了以下幾個函式,可以獲取共享記憶體的大小和位置。由於這些函式都很簡單,僅把它們的作用描述一下即可。
MemoryHeapBase::getBaseID() //返回mFd,如果為負數,表明剛才建立共享記憶體失敗了
MemoryHeapBase::getBase() //共享記憶體起始地址
MemoryHeapBase::getSize() //返回mSize,表示記憶體大小
MemoryHeapBase確實比較簡單,它通過ashmem_create_region得到一個檔案描述符。
說明:Android系統通過ashmem建立共享記憶體的原理,和Linux系統中通過開啟檔案建立共享記憶體的原理類似,但ashmem裝置驅動在這方面做了較大的改進,例如增加了引用計數、延時分配實體記憶體的機制(即真正使用的時候才去分配記憶體)等。這些內容,感興趣的讀者還可以自行對其研究。
那麼,MemoryBase是何物?它又有什麼作用?
MemoryBase也是一個基於Binder通訊的類,它比起MemoryHeapBase就更顯簡單了,看起來更像是一個輔助類。它的宣告在MemoryBase.h中。一起來看:
[-->MemoryBase.h::MemoryBase宣告]
class MemoryBase : public BnMemory
{
public:
MemoryBase(const sp<IMemoryHeap>& heap,ssize_t offset, size_tsize);
virtual sp<IMemoryHeap> getMemory(ssize_t* offset, size_t* size)const;
protected:
size_tgetSize() const { return mSize; }//返回大小
ssize_tgetOffset() const { return mOffset;}//返回偏移量
//返回MemoryHeapBase物件
constsp<IMemoryHeap>& getHeap() const { return mHeap;}
};
//MemoryBase的建構函式
MemoryBase::MemoryBase(constsp<IMemoryHeap>& heap,ssize_t offset, size_t size)
:mSize(size), mOffset(offset), mHeap(heap)
{
}
MemoryHeapBase和MemoryBase都夠簡單吧?總結起來不過是:
· 分配了一塊共享記憶體,這樣兩個程式可以共享這塊記憶體。
· 基於Binder通訊,這樣使用這兩個類的程式就可以互動了。
這兩個類在後續的講解中會頻繁碰到,但不必對它們做深入分析,只需把它當成普通的共享記憶體看待即可。
提醒:這兩個類沒有提供同步物件來保護這塊共享記憶體,所以後續在使用這塊記憶體時,必然需要一個跨程式的同步物件來保護它。這一點,是我在AT中第一次見到它們時想到的,不知道你是否注意過這個問題。
3. play和write的分析
還記得用例中的③和④關鍵程式碼行嗎?
//③ 開始播放
trackplayer.play() ;
//④ 呼叫write寫資料
trackplayer.write(bytes_pkg, 0,bytes_pkg.length) ;//往track中寫資料
現在就來分析它們。我們要直接轉向JNI層來進行分析。相信你,現在已有能力從Java層直接跳轉至JNI層了。
(1) play的分析
先看看play函式對應的JNI層函式,它是android_media_AudioTrack_start。
[-->android_media_AudioTrack.cpp]
static void
android_media_AudioTrack_start(JNIEnv *env,jobject thiz)
{
/*
從Java的AudioTrack物件中獲取對應Native層的AudioTrack物件指標。
從int型別直接轉換成指標,不過要是以後ARM平臺支援64位指標了,程式碼就得大修改了。
*/
AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
thiz,javaAudioTrackFields.nativeTrackInJavaObj);
lpTrack->start(); //很簡單的呼叫
}
play函式太簡單了,至於它呼叫的start,等到Native層進行AudioTrack分析時,我們再去觀察。
(2) write的分析
Java層的write函式有兩個:
· 一個是用來寫PCM16資料的,它對應的一個取樣點的資料量是兩個位元組。
· 另外一個用來寫PCM8資料的,它對應的一個取樣點的資料量是一個位元組。
我們的用例中採用的是PCM16資料。它對應的JNI層函式是android_media_AudioTrack_native_write_short,一起來看:
[-->android_media_AudioTrack.cpp]
static jint android_media_AudioTrack_native_write_short(
JNIEnv*env, jobject thiz,
jshortArrayjavaAudioData,jint offsetInShorts,
jintsizeInShorts,jint javaAudioFormat) {
return(android_media_AudioTrack_native_write(
env,thiz,(jbyteArray)javaAudioData,offsetInShorts*2,
sizeInShorts*2,javaAudioFormat)/ 2);
}
無論PCM16還是PCM8資料,最終都會呼叫writeToTrack函式。
[-->android_media_AudioTrack.cpp]
jint writeToTrack(AudioTrack* pTrack, jintaudioFormat,
jbyte*data,jint offsetInBytes, jint sizeInBytes) {
ssize_t written = 0;
/*
如果是STATIC模式,sharedBuffer()返回不為空
如果是STREAM模式,sharedBuffer()返回空
*/
if (pTrack->sharedBuffer() == 0) {
//我們的用例是STREAM模式,呼叫write函式寫資料
written = pTrack->write(data + offsetInBytes, sizeInBytes);
} else{
if (audioFormat == javaAudioTrackFields.PCM16){
if ((size_t)sizeInBytes > pTrack->sharedBuffer()->size()) {
sizeInBytes = pTrack->sharedBuffer()->size();
}
//在STATIC模式下,直接把資料memcpy到共享記憶體,記住在這種模式下要先呼叫write
//後呼叫play
memcpy(pTrack->sharedBuffer()->pointer(),
data+ offsetInBytes, sizeInBytes);
written = sizeInBytes;
}else if (audioFormat == javaAudioTrackFields.PCM8) {
//如果是PCM8資料,則先轉換成PCM16資料再拷貝
......
}
returnwritten;
}
看上去,play和write這兩個函式還真是比較簡單,須知,大部分工作還都是由Native的AudioTrack來完成的。繼續Java層的分析。
4. release的分析
當資料都write完後,需要呼叫stop停止播放,或者直接呼叫release來釋放相關資源。由於release和stop有一定的相關性,這裡只分析release呼叫。
[-->android_media_AudioTrack.cpp]
static voidandroid_media_AudioTrack_native_release(JNIEnv *env, jobject thiz) {
//呼叫android_media_AudioTrack_native_finalize真正釋放資源
android_media_AudioTrack_native_finalize(env, thiz);
//之前儲存在Java物件中的指標變數此時都要設定為零
env->SetIntField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, 0);
env->SetIntField(thiz, javaAudioTrackFields.jniData, 0);
}
[-->android_media_AudioTrack.cpp]
static voidandroid_media_AudioTrack_native_finalize(JNIEnv *env, jobject thiz) {
AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
thiz, javaAudioTrackFields.nativeTrackInJavaObj);
if(lpTrack) {
lpTrack->stop();//呼叫stop
delete lpTrack; //呼叫AudioTrack的解構函式
}
......
}
掃尾工作也很簡單,沒什麼需要特別注意的。
至此,在Java空間的分析工作就完成了。但在進入Native空間的分析之前,要總結一下Java空間使用Native的AudioTrack的流程,只有這樣,在進行Native空間分析時才能有章可循。
5. AudioTrack(Java空間)的分析總結
AudioTrack在JNI層使用了Native的AudioTrack物件,總結一下呼叫Native物件的流程:
· new一個AudioTrack,使用無參的建構函式。
· 呼叫set函式,把Java層的引數傳進去,另外還設定了一個audiocallback回撥函式。
· 呼叫了AudioTrack的start函式。
· 呼叫AudioTrack的write函式。
· 工作完畢後,呼叫stop。
· 最後就是Native物件的delete。
說明:為什麼要總結流程呢?
第一:控制了流程,就把握了系統工作的命脈,這一點至關重要。
第二:有些功能的實現縱跨Java/Native層,橫跨兩個程式,這中間有很多封裝、很多的特殊處理,但是其基本流程是不變的。通過精簡流程,我們才能把注意力集中在關鍵點上。
7.2.3 AudioTrack(Native空間)的分析
1. new AudioTrack和set分析
Native的AudioTrack程式碼在AudioTrack.cpp中。這一節,分析它的建構函式和set呼叫。
[-->AudioTrack.cpp]
AudioTrack::AudioTrack()//我們使用無參建構函式
:mStatus(NO_INIT)
{
//把狀態初始化成NO_INIT。Android的很多類都採用了這種狀態控制
}
再看看set呼叫,這個函式有很多內容。
[-->AudioTrack.cpp]
/*
還記得我們傳入的引數嗎?
streamType=STREAM_MUSIC,sampleRate=8000,format=PCM_16
channels=2,frameCount由計算得來,可以假設一個值,例如1024,不影響分析。
flags=0,cbf=audiocallback, user為cbf的引數,notificationFrames=0
因為是流模式,所以sharedBuffer=0。threadCanCallJava 為true
*/
status_t AudioTrack::set(int streamType,uint32_t sampleRate,int format,
int channels,int frameCount,uint32_t flags,callback_t cbf,void* user,
int notificationFrames,const sp<IMemory>& sharedBuffer,
boolthreadCanCallJava)
{
//前面有一些判斷,都是和AudioSystem有關的,以後再分析
......
/*
audio_io_handle_t是一個int型別,通過typedef定義,這個值的來歷非常複雜,
涉及AudioFlinger和AudioPolicyService, 後邊的分析試將其解釋清楚。
這個值主要被AudioFlinger使用,用來表示內部的工作執行緒索引號。AudioFlinger會根據
情況建立幾個工作執行緒,下面的AudioSystem::getOutput會根據流型別等其他引數最終選
取一個合適的工作執行緒,並返回它在AF中的索引號。
而AudioTrack一般使用混音執行緒(Mixer Thread)
*/
audio_io_handle_toutput = AudioSystem::getOutput(
(AudioSystem::stream_type)streamType,
sampleRate,format, channels,
(AudioSystem::output_flags)flags);
//呼叫creatTrack
status_t status = createTrack(streamType, sampleRate, format,channelCount,
frameCount,flags, sharedBuffer, output);
//cbf是JNI層傳入的回撥函式audioCallback,如果使用者設定了回撥函式,則啟動一個執行緒
if (cbf!= 0) {
mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);
}
returnNO_ERROR;
}
再看createTrack函式:
[-->AudioTrack.cpp]
status_t AudioTrack::createTrack(intstreamType,uint32_t sampleRate,
int format,int channelCount,int frameCount, uint32_t flags,
const sp<IMemory>& sharedBuffer, audio_io_handle_t output)
{
status_tstatus;
/*
得到AudioFlinger的Binder代理端BpAudioFlinger。
關於這部分內容,我們已經很熟悉了,以後的講解會跨過Binder,直接分析Bn端的實現
*/
constsp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
/*
向AudioFinger傳送createTrack請求。注意其中的幾個引數,
在STREAM模式下sharedBuffer為空
output為AudioSystem::getOutput得到一個值,代表AF中的執行緒索引號
該函式返回IAudioTrack(實際型別是BpAudioTrack)物件,後續AF和AT的互動就是
圍繞IAudioTrack進行的
*/
sp<IAudioTrack> track = audioFlinger->createTrack(getpid(),
streamType,sampleRate,format,channelCount,frameCount,
((uint16_t)flags) << 16,sharedBuffer,output,&status);
/*
在STREAM模式下,沒有在AT端建立共享記憶體,但前面提到了AT和AF的資料互動是
通過共享記憶體完成的,這塊共享記憶體最終由AF的createTrack建立。我們以後分析AF時
再做介紹。下面這個呼叫會取出AF建立的共享記憶體
*/
sp<IMemory> cblk = track->getCblk();
mAudioTrack.clear();//sp的clear
mAudioTrack= track;
mCblkMemory.clear();
mCblkMemory= cblk;//cblk是control block的簡寫
/*
IMemory的pointer在此處將返回共享記憶體的首地址,型別為void*,
static_cast直接把這個void*型別轉成audio_track_cblk_t,表明這塊記憶體的首部中存在
audio_track_cblk_t這個物件
*/
mCblk= static_cast<audio_track_cblk_t*>(cblk->pointer());
mCblk->out = 1;//out為1表示輸出,out為0表示輸入
mFrameCount = mCblk->frameCount;
if(sharedBuffer == 0) {
//buffers指向資料空間,它的起始位置是共享記憶體的首部加上audio_track_cblk_t的大小
mCblk->buffers= (char*)mCblk + sizeof(audio_track_cblk_t);
} else {
//STATIC模式下的處理
mCblk->buffers =sharedBuffer->pointer();
mCblk->stepUser(mFrameCount);//更新資料位置,後面要分析stepUser的作用
}
returnNO_ERROR;
}
(1)IAudioTrack和AT、AF的關係
上面的createTrack函式中突然冒出來一個新面孔,叫IAudioTrack。關於它和AT及AF的關係,我們用圖7-3來表示:
圖7-3 IAudioTrack和AT、AF的關係
從圖7-3中可以發現:
· IAudioTrack是聯絡AT和AF的關鍵紐帶。
至於IAudioTrack在AF端到底是什麼,在分析AF時會有詳細解釋。
(2)共享記憶體及其Control Block
通過前面的程式碼分析,我們發現IAudioTrack中有一塊共享記憶體,其頭部是一個audio_track_cblk_t(簡稱CB)物件,在該物件之後才是資料緩衝。這個CB物件有什麼作用呢?
還記得前面提到的那個深層次思考的問題嗎?即MemoryHeapBase和MemoryBase都沒有提供同步物件,那麼,AT和AF作為典型的資料生產者和消費者,如何正確協調二者生產和消費的步調呢?
Android為順應民意,便創造出了這個CB物件,其主要目的就是協調和管理AT和AF二者資料生產和消費的步伐。先來看CB都管理些什麼內容。它的宣告在AudioTrackShared.h中,而定義卻在AudioTrack.cpp中。
[-->AudioTrackShared.h::audio_track_cblk_t宣告]
struct audio_track_cblk_t
{
Mutex lock;
Condition cv;//這是兩個同步變數,初始化的時候會設定為支援跨程式共享
/*
一塊資料緩衝同時被生產者和消費者使用,最重要的就是維護它的讀寫位置了。
下面定義的這些變數就和讀寫的位置有關,雖然它們的名字並不是那麼直觀。
另外,這裡提一個擴充套件問題,讀者可以思考一下:
volatile支援跨程式嗎?要回答這個問題需要理解volatile、CPU Cache機制和共享記憶體的本質
*/
volatile uint32_t user; //當前寫位置(即生產者已經寫到什麼位置了)
volatile uint32_t server; //當前讀位置
/*
userBase和serverBase要和user及server結合起來用。
CB巧妙地通過上面幾個變數把一塊線性緩衝當做環形緩衝來使用,以後將單獨分析這個問題
*/
uint32_t userBase; //
uint32_t serverBase;
void* buffers; //指向資料緩衝的首地址
uint32_t frameCount;//資料緩衝的總大小,以Frame為單位
uint32_t loopStart; //設定打點播放(即設定播放的起點和終點)
uint32_t loopEnd;
int loopCount;//迴圈播放的次數
volatile union {
uint16_t volume[2];
uint32_t volumeLR;
}; //和音量有關係,可以不管它
uint32_t sampleRate;//取樣率
uint32_t frameSize;//一單位Frame的資料大小
uint8_t channels;//聲道數
uint8_t flowControlFlag;//控制標誌,見下文分析
uint8_t out; // AudioTrack為1,AudioRecord為0
uint8_t forceReady;
uint16_t bufferTimeoutMs;
uint16_t waitTimeMs;
//下面這幾個函式很重要,後續會詳細介紹它們
uint32_t stepUser(uint32_tframeCount);//更新寫位置
bool stepServer(uint32_tframeCount);//更新讀位置
void* buffer(uint32_toffset) const;//返回可寫空間起始位置
uint32_t framesAvailable();//還剩多少空間可寫
uint32_t framesAvailable_l();
uint32_t framesReady();//是否有可讀資料
}
關於CB物件,這裡要專門講解一下其中flowControlFlag的意思:
· 對於音訊輸出來說,flowControlFlag對應著underrun狀態,underrun狀態是指生產者提供資料的速度跟不上消費者使用資料的速度。這裡的消費者指的是音訊輸出裝置。由於音訊輸出裝置採用環形緩衝方式管理,當生產者沒有及時提供新資料時,輸出裝置就會迴圈使用緩衝中的資料,這樣就會聽到一段重複的聲音。這種現象一般被稱作“machinegun”。對於這種情況,一般的處理方法是暫停輸出,等資料準備好後再恢復輸出。
· 對於音訊輸入來說,flowControlFlag對於著overrun狀態,它的意思和underrun一樣,只是這裡的生產者變成了音訊輸入裝置,而消費者變成了Audio系統的AudioRecord。
說明:目前這個引數並不直接和音訊輸入輸出裝置的狀態有關係。它在AT和AF中的作用必須結合具體情況,才能分析。
圖7-4表示CB物件和它所駐留的共享記憶體間的關係:
圖7-4 共享記憶體和CB的關係
注意:CB實際是按照環形緩衝來處理資料讀寫的,所以user和server的真實作用還需要結合userBase和serverBase。圖7-4只是一個示意圖。
另外,關於CB,還有一個神祕的問題。先看下面這行程式碼:
mCblk =static_cast<audio_track_cblk_t*>(cblk->pointer());
這看起來很簡單,但仔細琢磨會發現其中有一個很難解釋的問題:
· cblk->pointer返回的是共享記憶體的首地址,怎麼把audio_track_cblk_t物件塞到這塊記憶體中呢?
這個問題將通過對AudioFlinger的分析,得到答案。
說明:關於audio_track_cblk_t的使用方式,後文會有詳細分析。
(3)資料的Push or Pull
在JNI層的程式碼中可以發現,在構造AudioTrack時,傳入了一個回撥函式audioCallback。由於它的存在,導致了Native的AudioTrack還將建立另一個執行緒AudioTrackThread。它有什麼用呢?
這個執行緒與外界資料的輸入方式有關係,AudioTrack支援兩種資料輸入方式:
· Push方式:使用者主動呼叫write寫資料,這相當於資料被push到AudioTrack。MediaPlayerService一般使用這種這方式提供資料。
· Pull方式:AudioTrackThread將利用這個回撥函式,以EVENT_MORE_DATA為引數主動從使用者那pull資料。ToneGenerator使用這種方式為AudioTrack提供資料。
這兩種方式都可以使用,不過回撥函式除了EVENT_MORE_DATA外,還能表達其他許多意圖,這是通過回撥函式的第一個引數來表明的。一起來看:
[-->AudioTrack.h::event_type]
enum event_type {
EVENT_MORE_DATA = 0, //表示AudioTrack需要更多資料
EVENT_UNDERRUN = 1,//這是Audio的一個術語,表示Audio硬體處於低負荷狀態
//AT可以設定打點播放,即設定播放的起點和終點,LOOP_END表示已經到達播放終點
EVENT_LOOP_END= 2,
/*
資料使用警戒通知。該值可通過setMarkerPosition ()設定。
當資料使用超過這個值時,AT會且僅通知一次,有點像WaterMarker。
這裡所說的資料使用,是針對消費者AF消費的資料量而言的
*/
EVENT_MARKER = 3,
/*
資料使用進度通知。進度通知值由setPositionUpdatePeriod()設定,
例如每使用500幀通知一次
*/
EVENT_NEW_POS = 4,
EVENT_BUFFER_END = 5 //資料全部被消耗
};
請看AudioTrackThread的執行緒函式threadLoop。
[-->AudioTrack.cpp]
bool AudioTrack::AudioTrackThread::threadLoop()
{
//mReceiver就是建立該執行緒的AudioTrack
returnmReceiver.processAudioBuffer(this);
}
[-->AudioTrack.cpp]
bool AudioTrack::processAudioBuffer(constsp<AudioTrackThread>& thread)
{
BufferaudioBuffer;
uint32_t frames;
size_twrittenSize;
//處理underun的情況
if(mActive && (mCblk->framesReady() == 0)) {
if(mCblk->flowControlFlag == 0) {
mCbf(EVENT_UNDERRUN, mUserData, 0);//under run 通知
if (mCblk->server == mCblk->frameCount) {
/*
server是讀位置,frameCount是buffer中的資料總和
當讀位置等於資料總和時,表示資料都已經使用完了
*/
mCbf(EVENT_BUFFER_END, mUserData, 0);
}
mCblk->flowControlFlag = 1;
if (mSharedBuffer != 0) return false;
}
}
// 迴圈播放通知
while(mLoopCount > mCblk->loopCount) {
int loopCount = -1;
mLoopCount--;
if(mLoopCount >= 0) loopCount = mLoopCount;
//一次迴圈播放完畢,loopCount表示還剩多少次
mCbf(EVENT_LOOP_END, mUserData, (void *)&loopCount);
}
if(!mMarkerReached && (mMarkerPosition > 0)) {
if(mCblk->server >= mMarkerPosition) {
//如果資料使用超過警戒值,則通知使用者
mCbf(EVENT_MARKER, mUserData, (void *)&mMarkerPosition);
//只通知一次,因為該值被設為true
mMarkerReached = true;
}
}
if(mUpdatePeriod > 0) {
while (mCblk->server >= mNewPosition) {
/*
進度通知,但它不是以時間為基準,而是以幀數為基準的。
例如設定每500幀通知一次,假設消費者一次就讀了1500幀,那麼這個迴圈會連續通知3次
*/
mCbf(EVENT_NEW_POS, mUserData, (void *)&mNewPosition);
mNewPosition += mUpdatePeriod;
}
}
if(mSharedBuffer != 0) {
frames = 0;
} else{
frames = mRemainingFrames;
}
do {
audioBuffer.frameCount = frames;
//得到一塊可寫的緩衝
status_t err = obtainBuffer(&audioBuffer, 1);
......
//從使用者那pull資料
mCbf(EVENT_MORE_DATA, mUserData, &audioBuffer);
writtenSize = audioBuffer.size;
......
if(writtenSize > reqSize) writtenSize = reqSize;
//PCM8資料轉PCM16
.......
audioBuffer.size = writtenSize;
audioBuffer.frameCount = writtenSize/mCblk->frameSize;
frames -= audioBuffer.frameCount;
releaseBuffer(&audioBuffer);//寫完畢,釋放這塊緩衝
}
while(frames);
......
returntrue;
}
關於obtainBuffer和releaseBuffer,後面再分析。這裡有一個問題值得思考:
· 用例會呼叫write函式寫資料,AudioTrackThread的回撥函式也讓我們提供資料。難道我們同時在使用Push和Pull模式?
這太奇怪了!來檢視這個回撥函式的實現,瞭解一下究竟是怎麼回事。該回撥函式是通過set呼叫傳入的,對應的函式是audioCallback。
[-->android_media_AudioTrack.cpp]
static void audioCallback(int event, void* user,void *info) {
if(event == AudioTrack::EVENT_MORE_DATA) {
//很好,沒有提供資料,也就是說,雖然AudioTrackThread通知了EVENT_MORE_DATA,
//但是我們並沒有提供資料給它
AudioTrack::Buffer* pBuff = (AudioTrack::Buffer*)info;
pBuff->size = 0;
}
......
懸著的心終於放下來了,還是老老實實地看Push模式下的資料輸入吧。
2. write輸入資料
write函式涉及Audio系統中最重要的關於資料如何傳輸的問題,在分析它的時候,不妨先思考一下它會怎麼做。回顧一下我們已瞭解的資訊:
· 有一塊共享記憶體。
· 有一個控制結構,裡邊有一些支援跨程式的同步變數。
有了這些東西,write的工作方式就非常簡單了:
· 通過共享記憶體傳遞資料。
· 通過控制結構協調生產者和消費者的步調。
重點強調:帶著問題和思考來分析程式碼相當於“智取”,它比一上來就直接扎入原始碼的“強攻”要高明得多。希望我們能掌握這種思路和方法。
好了,現在開始分析write,看看它的實現是不是如所想的那樣。
[-->AudioTrack.cpp]
ssize_t AudioTrack::write(const void* buffer,size_t userSize)
{
if(mSharedBuffer != 0) return INVALID_OPERATION;
if(ssize_t(userSize) < 0) {
returnBAD_VALUE;
}
ssize_t written = 0;
constint8_t *src = (const int8_t *)buffer;
BufferaudioBuffer; // Buffer是一個輔助性的結構
do {
//以幀為單位
audioBuffer.frameCount = userSize/frameSize();
//obtainBuffer從共享記憶體中得到一塊空閒的資料塊
status_terr = obtainBuffer(&audioBuffer, -1);
......
size_t toWrite;
if(mFormat == AudioSystem::PCM_8_BIT &&
!(mFlags &AudioSystem::OUTPUT_FLAG_DIRECT)) {
//PCM8資料轉PCM16
}else {
//空閒資料緩衝的大小是audioBuffer.size。
//地址在audioBuffer.i8中,資料傳遞通過memcpy完成
toWrite = audioBuffer.size;
memcpy(audioBuffer.i8, src, toWrite);
src += toWrite;
}
userSize -= toWrite;
written += toWrite;
//releaseBuffer更新寫位置,同時會觸發消費者
releaseBuffer(&audioBuffer);
}while (userSize);
returnwritten;
}
通過write函式,會發現資料的傳遞其實是很簡單的memcpy,但消費者和生產者的協調,則是通過obtainBuffer與releaseBuffer來完成的。現在來看這兩個函式。
3. obtainBuffer和releaseBuffer
這兩個函式展示了做為生產者的AT和CB物件的互動方法。先簡單看看,然後把它們之間互動的流程記錄下來,以後在CB物件的單獨分析部分,我們再來做詳細介紹。
[-->AudioTrack.cpp]
status_t AudioTrack::obtainBuffer(Buffer*audioBuffer, int32_t waitCount)
{
intactive;
status_t result;
audio_track_cblk_t* cblk = mCblk;
......
//①呼叫framesAvailable,得到當前可寫的空間大小
uint32_t framesAvail = cblk->framesAvailable();
if(framesAvail == 0) {
......
//如果沒有可寫空間,則要等待一段時間
result= cblk->cv.waitRelative(cblk->lock,milliseconds(waitTimeMs));
......
}
cblk->waitTimeMs = 0;
if(framesReq > framesAvail) {
framesReq = framesAvail;
}
//user為可寫空間起始地址
uint32_t u = cblk->user;
uint32_tbufferEnd = cblk->userBase + cblk->frameCount;
if (u+ framesReq > bufferEnd) {
framesReq = bufferEnd - u;
}
......
//②呼叫buffer,得到可寫空間的首地址
audioBuffer->raw = (int8_t *)cblk->buffer(u);
active= mActive;
returnactive ? status_t(NO_ERROR) : status_t(STOPPED);
}
obtainBuffer的功能,就是從CB管理的資料緩衝中得到一塊可寫空間,而releaseBuffer,則是在使用完這塊空間後更新寫指標的位置。
[-->AudioTrack.cpp]
void AudioTrack::releaseBuffer(Buffer*audioBuffer)
{
audio_track_cblk_t* cblk = mCblk;
cblk->stepUser(audioBuffer->frameCount);// ③呼叫stepUser更新寫位置
}
obtainBuffer和releaseBuffer與CB互動,一共會有三個函式呼叫,如下所示:
· framesAvailable判斷是否有可寫空間。
· buffer得到寫空間起始地址。
· stepUser更新寫位置。
請記住這些流程,以後在分析CB時會發現它們有重要作用。
4. delete AudioTrack
到這裡,AudioTrack的使命就進入倒數計時階段了。來看在它生命的最後還會做一些什麼工作。
[-->AudioTrack.cpp]
AudioTrack::~AudioTrack()
{
if(mStatus == NO_ERROR) {
stop();//呼叫stop
if(mAudioTrackThread != 0) {
//通知AudioTrackThread退出
mAudioTrackThread->requestExitAndWait();
mAudioTrackThread.clear();
}
mAudioTrack.clear();
//將殘留在IPCThreadState 傳送緩衝區的資訊傳送出去
IPCThreadState::self()->flushCommands();
}
}
如果不呼叫stop,解構函式也會先呼叫stop,這個做法很周到。
[-->AudioTrack.cpp]
void AudioTrack::stop()
{
sp<AudioTrackThread> t = mAudioTrackThread;
if (t!= 0) {
t->mLock.lock();
}
if(android_atomic_and(~1, &mActive) == 1) {
mCblk->cv.signal();
/*
mAudioTrack是IAudioTrack型別,其stop的最終處理在AudioFlinger端
*/
mAudioTrack->stop();
//清空迴圈播放設定
setLoop(0, 0, 0);
mMarkerReached = false;
if (mSharedBuffer != 0) {
flush();
}
if(t != 0) {
t->requestExit();//請求退出AudioTrackThread
}else {
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_NORMAL);
}
}
if (t!= 0) {
t->mLock.unlock();
}
}
stop的工作比較簡單,就是呼叫IAudioTrack的stop,並且還要求退出回撥執行緒。要重點關注IAudioTrack的stop函式,這個將做為AT和AF互動流程中的一個步驟來分析。
7.2.4 AudioTrack的總結
AudioTrack就這樣完了嗎?它似乎也不是很複雜。其實,在進行AT分析時,對於一些難度比較大的地方暫時沒做介紹。不過,在將AudioFlinger分析完之後,肯定不會怕它們的。
OK,在完成對AudioTrack的分析之前,應把它和AudioFlinger互動的流程總結下,如圖7-5所示。這些流程是以後攻克AudioFlinger的重要武器。
圖7-5 AT和AF的互動流程圖
7.3 AudioFlinger的破解
AudioFlinger是Audio系統的核心,來自AudioTrack的資料,最終在這裡得到處理並被寫入Audio HAL層。雖然AudioFlinger難度比較大,但既然已經攻破了橋頭堡AudioTrack,並掌握了重要的突破口,那麼對AudioFlinger的破解也就能手到擒來了。接下來,就是一步步地破解它了。
7.3.1 AudioFlinger的誕生
AudioFlinger駐留於MediaServer程式中。回顧一下它的程式碼,如下所示:
[-->Main_MediaServer.cpp]
int main(int argc, char** argv)
{
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager>sm = defaultServiceManager();
....
//很好,AF和APS都駐留在這個程式
AudioFlinger::instantiate();
AudioPolicyService::instantiate();
....
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
1. AudioFlinger的構造
[-->AudioFlinger.cpp]
void AudioFlinger::instantiate() {
defaultServiceManager()->addService( //把AF新增到ServiceManager中
String16("media.audio_flinger"), new AudioFlinger());
}
再來看它的建構函式:
[-->AudioFlinger.cpp]
AudioFlinger::AudioFlinger(): BnAudioFlinger(),
mAudioHardware(0), //代表Audio硬體的HAL物件
mMasterVolume(1.0f),mMasterMute(false), mNextThreadId(0)
{
mHardwareStatus= AUDIO_HW_IDLE;
//建立代表Audio硬體的HAL物件
mAudioHardware = AudioHardwareInterface::create();
mHardwareStatus = AUDIO_HW_INIT;
if(mAudioHardware->initCheck() == NO_ERROR) {
//設定系統初始化的一些值,有一部分通過Audio HAL設定到硬體中
setMode(AudioSystem::MODE_NORMAL);
setMasterVolume(1.0f);
setMasterMute(false);
}
}
AudioHardwareInterface是Android對代表Audio硬體的封裝,屬於HAL層。HAL層的具體功能,由各個硬體廠商根據所選硬體的情況來實現,多以動態庫的形式提供。這裡,簡單分析一下Audio HAL的介面,至於其具體實現就不做過多的探討了。
2. AudioHardwareInterface介紹
AudioHardwareInterface介面的定義在AudioHardwareInterface.h中。先看看它。
[-->AudioHardwareInterface.h::AudioHardwareInterface宣告]
class AudioHardwareInterface
{
public:
virtual ~AudioHardwareInterface() {}
//用於檢查硬體是否初始化成功,返回的錯誤碼定義在include/utils/Errors.h
virtual status_t initCheck() =0;
//設定通話音量,範圍從0到1.0
virtual status_t setVoiceVolume(float volume) = 0;
/*
設定除通話音量外的其他所有音訊流型別的音量,範圍從0到1.0,如果硬體不支援的話,
這個功能會由軟體層的混音器完成
*/
virtual status_t setMasterVolume(float volume) = 0;
/*
設定模式,NORMAL的狀態為普通模式,RINGTONE表示來電模式(這時聽到的聲音是來電鈴聲)
IN_CALL表示通話模式(這時聽到的聲音是手機通話過程中的語音)
*/
virtual status_t setMode(intmode) = 0;
// 和麥克相關
virtual status_t setMicMute(bool state) = 0;
virtual status_t getMicMute(bool* state) = 0;
// 設定/獲取配置引數,採用key/value的組織方式
virtual status_t setParameters(const String8& keyValuePairs) = 0;
virtual String8 getParameters(const String8& keys) = 0;
// 根據傳入的引數得到輸入緩衝的大小,返回0表示其中某個引數的值Audio HAL不支援
virtualsize_t getInputBufferSize(uint32_tsampleRate, int format,
int channelCount) = 0;
/*下面這幾個函式非常重要 */
/*
openOutputStream:建立音訊輸出流物件(相當於開啟音訊輸出裝置)
AF可以往其中write資料,指標型引數將返回該音訊輸出流支援的型別、聲道數、取樣率等
*/
virtual AudioStreamOut* openOutputStream(
uint32_tdevices,
int *format=0,
uint32_t*channels=0,
uint32_t*sampleRate=0,
status_t*status=0) = 0;
//關閉音訊輸出流
virtual void closeOutputStream(AudioStreamOut* out) = 0;
/* 建立音訊輸入流物件(相當於開啟音訊輸入裝置),AF可以read資料*/
virtual AudioStreamIn* openInputStream(
uint32_tdevices,
int *format,
uint32_t*channels,
uint32_t *sampleRate,
status_t*status,
AudioSystem::audio_in_acoustics acoustics) = 0;
virtual void closeInputStream(AudioStreamIn* in) =0;
//關閉音訊輸入流
virtual status_t dumpState(int fd, const Vector<String16>&args) = 0;
//靜態create函式,使用設計模式中的工廠模式,具體返回的物件由廠商根據硬體的情況決定
staticAudioHardwareInterface* create();
......
};
根據上面的程式碼,可以得出以下結論:
· AudioHardwareInterface管理音訊輸出裝置物件(AudioStreamOut)和音訊輸入裝置物件(AudioStreamIn)的建立。
· 通過AudioHardwareInterface可設定音訊系統的一些引數。
圖7-6表示AudioHardwareInterface和音訊輸入輸出物件之間的關係以及它們的派生關係:
圖7-6 AudioHardwareInterface關係圖
從圖7-6中還可看出:
· 音訊輸出/輸入物件均支援設定引數(由setParameters完成)。
說明:AudioHardwareInterface最重要的功能是建立AudioStreamOut 和AudioStreamIn,它們分別代表音訊輸出裝置和音訊輸入裝置。從這個角度說,是AudioHardwareInterface管理著系統中所有的音訊裝置。Android引入的HAL層,大大簡化了應用層的工作,否則不管是使用libasound(AlSA提供的使用者空間庫)還是ioctl來控制音訊裝置,都會非常麻煩。
7.3.2 通過流程分析AudioFlinger
圖7-5中說明的AT和AF互動的流程,對於分析AF來說非常重要。先來回顧一下圖7-5的流程:
· AT呼叫createTrack,得到一個IAudioTrack物件。
· AT呼叫IAudioTrack物件的start,表示準備寫資料了。
· AT通過write寫資料,這個過程和audio_track_cblk_t有著密切關係。
· 最後AT呼叫IAudioTrack的stop或delete IAudioTrack結束工作。
至此,上面的每一步都很清楚了。根據Binder知識,AT呼叫的這些函式最終都會在AF端得到實現,所以可直接從AF端開始。
1. createTrack的分析
按照前面的流程步驟,第一個被呼叫的函式會是createTrack,請注意在用例中傳的引數。
[-->AudioFlinger.cpp]
sp<IAudioTrack> AudioFlinger::createTrack(
pid_t pid,//AT的pid號
int streamType,//流型別,用例中是MUSIC型別
uint32_t sampleRate,//8000 取樣率
int format,//PCM_16型別
int channelCount,//2,雙聲道
int frameCount,//需要建立緩衝的大小,以幀為單位
uint32_t flags,
const sp<IMemory>& sharedBuffer,//AT傳入的共享buffer,這裡為空
int output,//這個值前面提到過,是AF中的工作執行緒索引號
status_t *status)
{
sp<PlaybackThread::Track> track;
sp<TrackHandle> trackHandle;
sp<Client> client;
wp<Client>wclient;
status_t lStatus;
{
Mutex::Autolock _l(mLock);
//output代表索引號,這裡根據索引號找到一個工作執行緒,它是一個PlaybackThread
PlaybackThread *thread = checkPlaybackThread_l(output);
//看看這個程式是否已經是AF的Client,AF根據程式pid來標識不同的Client
wclient = mClients.valueFor(pid);
if(wclient != NULL) {
}else {
//如果還沒有這個Client資訊,則建立一個,並加入到mClients中去
client = new Client(this, pid);
mClients.add(pid, client);
}
//在找到的工作執行緒物件中建立一個Track,注意它的型別是Track
track = thread->createTrack_l(client, streamType, sampleRate, format,
channelCount, frameCount, sharedBuffer, &lStatus);
}
/*
TrackHandle是Track物件的Proxy,它支援Binder通訊,而Track不支援Binder
TrackHandle所接收的請求最終會由Track處理,這是典型的Proxy模式
*/
trackHandle= new TrackHandle(track);
returntrackHandle;
}
這個函式相當複雜,主要原因之一,是其中出現了幾個我們沒接觸過的類。我剛接觸這個函式的時候,大腦也曾因看到這些眼生的東西而“當機”!不過暫時先不用去理會它們,等了解了這個函式後,再回過頭來收拾它們。先進入checkPlaybackThread_l看看。
(1) 選擇工作執行緒
checkPlaybackThread_l的程式碼如下所示:
[-->AudioFlinger.cpp]
AudioFlinger::PlaybackThread *
AudioFlinger::checkPlaybackThread_l(intoutput) const
{
PlaybackThread*thread = NULL;
//根據output的值找到對應的thread
if(mPlaybackThreads.indexOfKey(output) >= 0) {
thread = (PlaybackThread *)mPlaybackThreads.valueFor(output).get();
}
returnthread;
}
上面函式中傳入的output,就是之前在分析AT時提到的工作執行緒索引號。看到這裡,是否感覺有點困惑?困惑的原因可能有二:
· 目前的流程中尚沒有見到建立執行緒的地方,但在這裡確實能找到一個執行緒。
· Output含義到底是什麼?為什麼會把它作為index來找執行緒呢?
關於這兩個問題,待會兒再做解釋。現在只需知道AudioFlinger會建立幾個工作執行緒,AT會找到對應的工作執行緒即可。
(2) createTrack_l的分析
找到工作執行緒後,會執行createTrack_l函式,請看這個函式的作用:
[-->AudioFlinger.cpp]
// Android的很多程式碼都採用了內部類的方式進行封裝,看習慣就好了
sp<AudioFlinger::PlaybackThread::Track>
AudioFlinger::PlaybackThread::createTrack_l(
const sp<AudioFlinger::Client>& client,int streamType,
uint32_tsampleRate,int format,int channelCount,int frameCount,
const sp<IMemory>& sharedBuffer,//注意這個引數,從AT中傳入,為0
status_t *status)
{
sp<Track> track;
status_t lStatus;
{
Mutex::Autolock _l(mLock);
//建立Track物件
track= new Track(this, client, streamType, sampleRate, format,
channelCount, frameCount,sharedBuffer);
//將新建立的Track加入到內部陣列mTracks中
mTracks.add(track);
}
lStatus= NO_ERROR;
returntrack;
}
上面的函式呼叫傳入的sharedBuffer為空,那共享記憶體又是在哪裡建立的呢?可以注意到Track建構函式關於sharedBuffer這個引數的型別是一個引用,莫非是建構函式建立的?
(3) Track建立共享記憶體和TrackHandle
在createTrack_l中,會new出來一個Track,請看它的程式碼:
[-->AudioFlinger.cpp]
AudioFlinger::PlaybackThread::Track::Track(const wp<ThreadBase>& thread,
const sp<Client>& client,int streamType,uint32_t sampleRate,
int format,int channelCount,int frameCount,
const sp<IMemory>& sharedBuffer)
: TrackBase(thread, client, sampleRate, format, channelCount,
frameCount,0, sharedBuffer),//sharedBuffer仍然為空
mMute(false), mSharedBuffer(sharedBuffer), mName(-1)
{
// mCblk!=NULL? 什麼時候建立的呢?只能看基類TrackBase的建構函式了
if(mCblk != NULL) {
mVolume[0] = 1.0f;
mVolume[1] = 1.0f;
mStreamType = streamType;
mCblk->frameSize = AudioSystem::isLinearPCM(format) ?
channelCount * sizeof(int16_t): sizeof(int8_t);
}
}
對於這種重重繼承,我們只能步步深入分析,一定要找到共享記憶體建立的地方,繼續看程式碼:
[-->AudioFlinger.cpp]
AudioFlinger::ThreadBase::TrackBase::TrackBase(
const wp<ThreadBase>& thread,const sp<Client>&client,
uint32_t sampleRate,int format,int channelCount,int frameCount,
uint32_t flags,const sp<IMemory>& sharedBuffer)
: RefBase(), mThread(thread),mClient(client),mCblk(0),
mFrameCount(0),mState(IDLE),mClientTid(-1),mFormat(format),
mFlags(flags & ~SYSTEM_FLAGS_MASK)
{
size_tsize = sizeof(audio_track_cblk_t);//得到CB物件大小
//計算資料緩衝大小
size_tbufferSize = frameCount*channelCount*sizeof(int16_t);
if(sharedBuffer == 0) {
//還記得圖7-4嗎?共享記憶體最前面一部分是audio_track_cblk_t,後面才是資料空間
size+= bufferSize;
}
//根據size建立一塊共享記憶體。
mCblkMemory = client->heap()->allocate(size);
/*
pointer()返回共享記憶體的首地址, 並強制轉換void*型別為audio_track_cblk_t*型別。
其實把它強制轉換成任何型別都可以,但是這塊記憶體中會有CB物件嗎?
*/
mCblk= static_cast<audio_track_cblk_t *>(mCblkMemory->pointer());
//①下面這句程式碼看起來很獨特。什麼意思???
new(mCblk)audio_track_cblk_t();
mCblk->frameCount = frameCount;
mCblk->sampleRate = sampleRate;
mCblk->channels = (uint8_t)channelCount;
if (sharedBuffer == 0) {
//清空資料區
mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t));
// flowControlFlag初始值為1
mCblk->flowControlFlag = 1;
}
......
}
這裡需要重點講解下面這句話的意思。
new(mCblk) audio_track_cblk_t();
注意它的用法,new後面的括號裡是記憶體,緊接其後的是一個類的建構函式。
重點說明:這個語句就是C++語言中的placement new。其含義是在括號裡指定的記憶體中建立一個物件。我們知道,普通的new只能在堆上建立物件,堆的地址由系統分配。這裡採用placementnew將使得audio_track_cblk_t建立在共享記憶體上,它就自然而然地能被多個程式看見並使用了。關於placementnew較詳細的知識,還請讀者自己搜尋一下。
通過上面的分析,可以知道:
· Track建立了共享記憶體。
· CB物件通過placement new方法建立於這塊共享記憶體中。
AF的createTrack函式返回的是一個IAudioTrack型別的物件,可現在碰到的Track物件是IAudioTrack型別嗎?來看程式碼:
[-->AudioFlinger.cpp]
sp<IAudioTrack> AudioFlinger::createTrack(......)
{
sp<TrackHandle>trackHandle;
......
track= thread->createTrack_l(client, streamType, sampleRate,
format,channelCount,frameCount, sharedBuffer, &lStatus);
trackHandle= new TrackHandle(track);
return trackHandle;//① 這個trackHandle物件竟然沒有在AF中儲存!
}
原來,createTrack返回的是TrackHandle物件,它以Track為引數構造。這二者之間又是什麼關係呢?
Android在這裡使用了Proxy模式,即TrackHandle是Track的代理,TrackHandle代理的內容是什麼呢?分析TrackHandle的定義可以知道:
· Track沒有基於Binder通訊,它不能接收來自遠端程式的請求。
· TrackHandle能基於Binder通訊,它可以接收來自遠端程式的請求,並且能呼叫Track對應的函式。這就是Proxy模式的意思。
討論:Android為什麼不直接讓Track從IBinder派生,直接支援Binder通訊呢?關於這個問題,在看到後面的Track家族圖譜後,我們或許就明白了。
另外,注意程式碼中的註釋①:
· trackHandle被new出來後直接返回,而AF中並沒有儲存它,這豈不是成了令人聞之色變的野指標?
擴充思考:關於這個問題的答案,請讀者自己思考並回答。提示,可從Binder和RefBase入手。
分析完createTrack後,估計有些人會暈頭轉向的。確實,這個createTrack比較複雜。僅物件型別就層出不窮。到底它有多少種物件,它們之間又有怎樣的關係呢?下面就來解決這幾個問題。
2. 到底有多少種物件?
不妨把AudioFlinger中出現的物件總結一下,以瞭解它們的作用和相互之間的關係。
(1) AudioFlinger物件
作為Audio系統的核心引擎,首先要介紹AudioFlinger。它的繼承關係很簡單:
class AudioFlinger : public BnAudioFlinger,public IBinder::DeathRecipient
AudioFlinger的主要工作由其定義的許多內部類來完成,我們用圖7-7來表示。圖中大括號所指向的類為外部類,大括號所包含的為該外部類所定義的內部類。例如,DuplicatingThread、RecordThread和DirectOutputThread都包括在一個大括號中,這個大括號指向AudioFlinger,所以它們三個都是AudioFlinger的內部類,而AudioFlinger則是它們三個的外部類:
圖7-7 AF中的所有類
看,AF夠複雜吧?要不是使用了VisualStudio的程式碼段摺疊功能,我畫這個圖,也會破費周折的。
(2) Client物件
Client是AudioFlinger對客戶端的封裝,凡是使用了AudioTrack和AudioRecord的程式,都被會當做是AF的Client,並且Client用它的程式pid作為標識。程式碼如下所示:
class Client : public RefBase {
public:
Client(const sp<AudioFlinger>& audioFlinger, pid_t pid);
virtual ~Client();
const sp<MemoryDealer>& heap() const;
pid_t pid() const {return mPid; }
sp<AudioFlinger> audioFlinger() { return mAudioFlinger; }
private:
Client(constClient&);
Client&operator = (const Client&);
sp<AudioFlinger> mAudioFlinger;
sp<MemoryDealer> mMemoryDealer;//記憶體分配器
pid_t mPid;
};
Client物件比較簡單,因此就不做過多的分析了。
注意:一個Client程式可以建立多個AudioTrack,這些AudioTrack都屬於一個Client。
(3) 工作執行緒介紹
AudioFlinger中有幾種不同型別的工作執行緒,它們之間的關係如圖7-8所示:
圖7-8 AF中的工作執行緒家譜
下面來解釋圖7-8中各種型別工作執行緒的作用:
· PlaybackThread:回放執行緒,用於音訊輸出。它有一個成員變數mOutput,為AudioStreamOutput*型別,這表明PlaybackThread直接和Audio音訊輸出裝置建立了聯絡。
· RecordThread:錄音執行緒,用於音訊輸入,它的關係比較單純。它有一個成員變數mInput為AudioStreamInput*型別,這表明RecordThread直接和Audio音訊輸入裝置建立了聯絡。
從PlaybackThread的派生關係上可看出,手機上的音訊回放應該比較複雜,否則也不會派生出三個子類了。其中:
· MixerThread:混音執行緒,它將來自多個源的音訊資料混音後再輸出。
· DirectOutputThread:直接輸出執行緒,它會選擇一路音訊流後將資料直接輸出,由於沒有混音的操作,這樣可以減少很多延時。
· DuplicatingThread:多路輸出執行緒,它從MixerThread派生,意味著它也能夠混音。它最終會把混音後的資料寫到多個輸出中,也就是一份資料會有多個接收者。這就是Duplicate的含義。目前在藍芽A2DP裝置輸出中使用。
另外從圖7-8中還可以看出:
· PlaybackThread維護兩個Track陣列,一個是mActiveTracks,表示當前活躍的Track,一個是mTracks,表示這個執行緒建立的所有Track。
· DuplicatingThread還維護了一個mOutputTracks,表示多路輸出的目的端。後面分析DuplicatingThread時再對此進行講解。
說明:大部分常見音訊輸出使用的是MixerThread,後文會對此進行詳細分析。另外,在擴充內容中,也將深入分析DuplicatingThread的實現。
(4) PlaybackThread和AudioStreamOutput
從圖7-8中,可以發現一個PlaybackThread有一個AudioStreamOutput型別的物件,這個物件提供了音訊資料輸出功能。可以用圖7-9來表示音訊資料的流動軌跡。該圖以PlaybackThread最常用的子類MixerThread作為代表。
圖7-9 音訊資料的流動軌跡
根據圖7-9,就能明白MixerThread的大致工作流程:
· 接收來自AT的資料。
· 對這些資料進行混音。
· 把混音的結果寫到AudioStreamOut,這樣就完成了音訊資料的輸出。
(5) Track物件
前面所說的工作執行緒,其工作就是圍繞Track展開的,圖7-10展示了Track的家族:
注意:這裡把RecordTrack也統稱為Track。
圖7-10 Track家族
從圖7-10中可看出,TrackHandle和RecordHandle是基於Binder通訊的,它作為Proxy,用於接收請求並派發給對應的Track和RecordTrack。
說明:從圖7-10也能看出,之所以不讓Track繼承Binder框架,是因為Track本身的繼承關係和所承擔的工作已經很複雜了,如再讓它摻合Binder,只會亂上添亂。
Track類作為工作執行緒的內部類來實現,其中:
· TrackBase定義於ThreadBase中。
· Track定義於PlaybackThread中,RecordTrack定義於RecordThread中。
· OutputTrack定義於DuplicatingThread中。
根據前面的介紹可知,音訊輸出資料最後由Playback執行緒來處理,用例所對應的Playback執行緒,實際上是一個MixerThread,那麼它是如何工作的呢?一起來分析。
3. MixerThread分析
MixerThread是Audio系統中負擔最重的一個工作執行緒。先來了解一下它的來歷。
(1) MixerThread的來歷
前面,在checkplaybackThread_l中,有一個地方一直沒來得及解釋。回顧一下它的程式碼:
[-->AudioFlinger.cpp]
AudioFlinger::PlaybackThread *
AudioFlinger::checkPlaybackThread_l(intoutput) const
{
PlaybackThread*thread = NULL;
//根據output的值找到對應的thread
if(mPlaybackThreads.indexOfKey(output) >= 0) {
thread = (PlaybackThread *)mPlaybackThreads.valueFor(output).get();
}
returnthread;
}
上面這個函式的意思很明確:就是根據output值找到對應的回放執行緒。
但在前面的流程分析中,並沒有見到建立執行緒的地方,那這個執行緒又是如何得來的?它又是何時、怎樣建立的呢?
答案在AudioPolicyService中。提前看看AudioPolicyService,分析一下,它為什麼和這個執行緒有關係。
AudioPolicyService和AudioFlinger一樣,都駐留在MediaServer中,直接看它的建構函式:
[-->AudioPolicyService.cpp]
AudioPolicyService::AudioPolicyService()
:BnAudioPolicyService() , mpPolicyManager(NULL)
{
charvalue[PROPERTY_VALUE_MAX];
// Tone音播放執行緒
mTonePlaybackThread = new AudioCommandThread(String8(""));
// 命令處理執行緒
mAudioCommandThread = newAudioCommandThread(String8("ApmCommandThread"));
#if (defined GENERIC_AUDIO) || (defined AUDIO_POLICY_TEST)
//這裡屬於Generic的情況,所以構造AudioPolicyManagerBase,注意建構函式的引數
mpPolicyManager = new AudioPolicyManagerBase(this);
#else
......
//建立和硬體廠商相關的AudioPolicyManager
#endif
......
}
看AudioPolicyManagerBase的建構函式,注意傳給它的引數是this,即把AudioPolicyService物件傳進去了。
[-->AudioPolicyManagerBase.cpp]
AudioPolicyManagerBase::AudioPolicyManagerBase(
AudioPolicyClientInterface*clientInterface)
:mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0),
mMusicStopTime(0),mLimitRingtoneVolume(false)
{
mpClientInterface = clientInterface;
// 先把不相關的內容去掉
......
/*
返回來呼叫mpClientInterface的openOutput,實際就是AudioPolicyService。
注意openOutput函式是在AP的建立過程中呼叫的
*/
mHardwareOutput =mpClientInterface->openOutput(&outputDesc->mDevice,
&outputDesc->mSamplingRate,
&outputDesc->mFormat,
&outputDesc->mChannels,
&outputDesc->mLatency,
outputDesc->mFlags);
......
}
真是山不轉水轉!我們們還得回到AudioPolicyService中去看看:
[-->AudioPolicyService.cpp]
audio_io_handle_tAudioPolicyService::openOutput(uint32_t *pDevices,
uint32_t*pSamplingRate,
uint32_t*pFormat,
uint32_t*pChannels,
uint32_t*pLatencyMs,
AudioSystem::output_flags flags)
{
sp<IAudioFlinger>af = AudioSystem::get_audio_flinger();
//下面會呼叫AudioFlinger的openOutput,這個時候AF已經啟動了
returnaf->openOutput(pDevices, pSamplingRate, (uint32_t *)pFormat,
pChannels, pLatencyMs,flags);
}
真是曲折啊,又得到AF去看看:
[-->AudioFlinger.cpp]
int AudioFlinger::openOutput(
uint32_t *pDevices,uint32_t*pSamplingRate,uint32_t *pFormat,
uint32_t*pChannels,uint32_t *pLatencyMs,uint32_t flags)
{
......
Mutex::Autolock _l(mLock);
//建立Audio HAL的音訊輸出物件,和音訊輸出扯上了關係
AudioStreamOut *output = mAudioHardware->openOutputStream(*pDevices,
(int *)&format,
&channels,
&samplingRate,
&status);
mHardwareStatus = AUDIO_HW_IDLE;
if(output != 0) {
if((flags & AudioSystem::OUTPUT_FLAG_DIRECT) ||
(format != AudioSystem::PCM_16_BIT) ||
(channels != AudioSystem::CHANNEL_OUT_STEREO)) {
//如果標誌為OUTPUT_FLAG_DIRECT,則建立DirectOutputThread
thread = new DirectOutputThread(this, output, ++mNextThreadId);
} else {
//一般建立的都是MixerThread,注意代表AudioStreamOut物件的output也傳進去了
thread= new MixerThread(this, output, ++mNextThreadId);
}
//把新建立的執行緒加入執行緒組mPlaybackThreads中儲存, mNextThreadId是它的索引號
mPlaybackThreads.add(mNextThreadId, thread);
......
return mNextThreadId;//返回該執行緒的索引號
}
return0;
}
明白了嗎?是否感覺有點繞?可用一個簡單的示意圖來觀察三者的互動流程,如圖7-11所示:
圖7-11 MixerThread的曲折來歷示意圖
圖7-11表明:
· AF中的工作執行緒的建立,受到了AudioPolicyService的控制。從AudioPolicyService的角度出發,這也是應該的,因為APS控制著整個音訊系統,而AF只是管理音訊的輸入和輸出。
· 另外,注意這個執行緒是在AP的建立過程中產生的。也就是說,AP一旦建立完Audio系統,就已經準備好工作了。
關於AF和AP的恩恩怨怨,在後面APS的分析過程中再去探討。目前,讀者只需瞭解系統中第一個MixerThread的來歷即可。下面來分析這個來之不易的MixerThread。
(2) MixerThread的構造和執行緒啟動
[-->AudioFlinger.cpp]
AudioFlinger::MixerThread::MixerThread(
constsp<AudioFlinger>& audioFlinger,
AudioStreamOut*output, // AudioStreamOut為音訊輸出裝置的HAL抽象
intid)
: PlaybackThread(audioFlinger, output, id),mAudioMixer(0)
{
mType = PlaybackThread::MIXER;
//混音器物件,這個物件比較複雜,它完成多路音訊資料的混合工作
mAudioMixer = new AudioMixer(mFrameCount, mSampleRate);
}
再來看MixerThread的基類PlaybackThread的建構函式:
[-->AudioFlinger.cpp]
AudioFlinger::PlaybackThread::PlaybackThread(constsp<AudioFlinger>&
audioFlinger, AudioStreamOut* output, int id)
: ThreadBase(audioFlinger, id),
mMixBuffer(0),mSuspended(0), mBytesWritten(0),
mOutput(output), mLastWriteTime(0),mNumWrites(0),
mNumDelayedWrites(0), mInWrite(false)
{
//獲取音訊輸出HAL物件的一些資訊,包括硬體中音訊緩衝區的大小(以幀為單位)
readOutputParameters();
mMasterVolume= mAudioFlinger->masterVolume();
mMasterMute= mAudioFlinger->masterMute();
//設定不同型別音訊流的音量及靜音情況
for(int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++)
{
mStreamTypes[stream].volume=
mAudioFlinger->streamVolumeInternal(stream);
mStreamTypes[stream].mute= mAudioFlinger->streamMute(stream);
}
//傳送一個通知訊息給監聽者,這部分內容較簡單,讀者可自行研究
sendConfigEvent(AudioSystem::OUTPUT_OPENED);
}
此時,執行緒物件已經建立完畢。根據對Thread的分析,應該呼叫它的run函式才能真正建立新執行緒。在首次建立sp時呼叫了run,這裡利用了RefBase的onFirstRef函式。根據MixerThread的派生關係,該函式最終由父類PlaybackThread的onFirstRef實現:
[-->AudioFlinger.cpp]
void AudioFlinger::PlaybackThread::onFirstRef()
{
constsize_t SIZE = 256;
charbuffer[SIZE];
snprintf(buffer, SIZE, "Playback Thread %p", this);
//下面的run就真正建立了執行緒並開始執行threadLoop
run(buffer, ANDROID_PRIORITY_URGENT_AUDIO);
}
好,執行緒已經run起來了。繼續按流程分析,下一個輪到的呼叫函式是start。
4. start的分析
AT呼叫的是IAudioTrack的start函式,由於TrackHandle的代理作用,這個函式的實際處理會由Track物件來完成。
[-->AudioFlinger.cpp]
status_tAudioFlinger::PlaybackThread::Track::start()
{
status_t status = NO_ERROR;
sp<ThreadBase>thread = mThread.promote();
//該Thread是用例中的MixerThread
if(thread != 0) {
Mutex::Autolock _l(thread->mLock);
int state = mState;
if (mState == PAUSED) {
mState = TrackBase::RESUMING;
} else {
mState = TrackBase::ACTIVE;//設定Track的狀態
}
PlaybackThread *playbackThread =(PlaybackThread *)thread.get();
//addTrack_l把這個track加入到mActiveTracks陣列中
playbackThread->addTrack_l(this);
returnstatus;
}
看看這個addTrack_l函式,程式碼如下所示:
[-->AudioFlinger.cpp]
status_tAudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track)
{
status_t status = ALREADY_EXISTS;
//①mRetryCount:設定重試次數,kMaxTrackStartupRetries值為50
track->mRetryCount = kMaxTrackStartupRetries;
if(mActiveTracks.indexOf(track) < 0) {
//②mFillingUpStatus:緩衝狀態
track->mFillingUpStatus= Track::FS_FILLING;
//原來是把呼叫start的這個track加入到活躍的Track陣列中了
mActiveTracks.add(track);
status = NO_ERROR;
}
//廣播一個事件,一定會觸發MixerThread執行緒,通知它有活躍陣列加入,需要開工幹活
mWaitWorkCV.broadcast();
return status;
}
start函式把這個Track加入到活躍陣列後,將觸發一個同步事件,這個事件會讓工作執行緒動起來。雖然這個函式很簡單,但有兩個關鍵點必須指出,這兩個關鍵點其實指出了兩個問題的處理辦法:
· mRetryCount表示重試次數,它針對的是這樣一個問題:如果一個Track呼叫了start卻沒有write資料,該怎麼辦?如果MixerThread嘗試了mRetryCount次後還沒有可讀資料,工作執行緒就會把該Track從啟用佇列中去掉了。
· mFillingUpStatus能解決這樣的問題:假設分配了1MB的資料緩衝,那麼至少需要寫多少資料的工作執行緒才會讓Track覺得AT是真的需要它工作呢?難道AT寫一個位元組就需要工作執行緒興師動眾嗎?其實,這個狀態最初為Track::FS_FILLING,表示正在填充資料緩衝。在這種狀態下,除非AT設定了強制讀資料標誌(CB物件中的forceReady變數),否則工作執行緒是不會讀取該Track的資料的。該狀態還有其他的值,讀者可以自行研究。
說明:我們在介紹大流程的同時也把一些細節問題指出來,希望這些細節問題能激發讀者深入研究的慾望。
Track加入了工作執行緒的活躍陣列後,又觸發了一個同步事件,MixerThread是否真的動起來了呢?一起來看:
(1) MixerThread動起來
Thread類的執行緒工作都是在threadLoop中完成的,那麼MixerThread的執行緒又會做什麼呢?
[-->AudioFlinger.cpp]
bool AudioFlinger::MixerThread::threadLoop()
{
int16_t* curBuf = mMixBuffer;
Vector< sp<Track> > tracksToRemove;
uint32_t mixerStatus = MIXER_IDLE;
nsecs_tstandbyTime = systemTime();
......
uint32_t sleepTime = idleSleepTime;
while(!exitPending())
{
//① 處理一些請求和通知訊息,如之前在建構函式中發出的OUTPUT_OPEN訊息等
processConfigEvents();
mixerStatus = MIXER_IDLE;
{// scope for mLock
Mutex::Autolock _l(mLock);
//檢查配置引數,如有需要則重新設定內部引數值
if (checkForNewParameters_l()) {
mixBufferSize = mFrameCount * mFrameSize;
maxPeriod = seconds(mFrameCount) / mSampleRate * 3;
......
}
//獲得當前的已啟用track陣列
const SortedVector< wp<Track> >& activeTracks =mActiveTracks;
......
/*
②prepareTracks_l將檢查mActiveTracks陣列,判斷是否有AT的資料需要處理。
例如有些AudioTrack雖然呼叫了start,但是沒有及時write資料,這時就無須
進行混音工作。我們待會再分析prepareTracks_l函式
*/
mixerStatus = prepareTracks_l(activeTracks, &tracksToRemove);
}
//MIXER_TRACKS_READY表示AT已經把資料準備好了
if(LIKELY(mixerStatus == MIXER_TRACKS_READY)) {
//③ 由混音物件進行混音工作,混音的結果放在curBuf中
mAudioMixer->process(curBuf);
sleepTime = 0;//等待時間設定為零,表示需要馬上輸出到Audio HAL
standbyTime = systemTime() + kStandbyTimeInNsecs;
}
.......
if(sleepTime == 0) {
......
//④ 往Audio HAL的OutputStream中write混音後的資料,這是音訊資料的最終歸宿
int bytesWritten = (int)mOutput->write(curBuf, mixBufferSize);
if (bytesWritten < 0) mBytesWritten -= mixBufferSize;
......
mStandby = false;
}else {
usleep(sleepTime);
}
tracksToRemove.clear();
}
if(!mStandby) {
mOutput->standby();
}
returnfalse;
}
從上面的分析可以看出,MixerThread的執行緒函式大致工作流程是:
· 如果有通知資訊或配置請求,則先完成這些工作。比如向監聽者通知AF的一些資訊,或者根據配置請求進行音量控制,聲音裝置切換等。
· 呼叫prepareTracks _l函式,檢查活躍Tracks是否有資料準備好。
· 呼叫混音器物件mAudioMixer的process,並且傳入一個儲存結果資料的緩衝,混音後的結果就儲存在這個緩衝中。
· 呼叫代表音訊輸出裝置的AudioOutputStream物件的write,把結果資料寫入裝置。
其中,配置請求處理的工作將在AudioPolicyService的分析中,以一個耳機插入處理例項進行講解。這裡主要分析程式碼中②③兩個步驟。
(2) prepareTracks_l和process分析
prepareTracks_l函式檢查啟用Track陣列,看看其中是否有資料等待使用,程式碼如下所示:
[-->AudioFlinger.cpp]
uint32_tAudioFlinger::MixerThread::prepareTracks_l(
constSortedVector<wp<Track>>& activeTracks,
Vector<sp<Track>>*tracksToRemove)
{
uint32_t mixerStatus = MIXER_IDLE;
//啟用Track的個數
size_tcount = activeTracks.size();
floatmasterVolume = mMasterVolume;
bool masterMute = mMasterMute;
//依次查詢這些Track的情況
for(size_t i=0 ; i<count ; i++) {
sp<Track> t = activeTracks[i].promote();
if(t == 0) continue;
Track* const track = t.get();
//怎麼查?通過audio_track_cblk_t物件
audio_track_cblk_t* cblk = track->cblk();
/*
一個混音器可支援32個Track,它內部有一個32元素的陣列,name函式返回的就是Track在
這個陣列中的索引。混音器每次通過setActiveTrack設定一個活躍Track,
後續所有操作都會針對當前設定的這個活躍Track
*/
mAudioMixer->setActiveTrack(track->name());
//下面這個判斷語句決定了什麼情況下Track資料可用
if (cblk->framesReady() &&(track->isReady() || track->isStopped())
&& !track->isPaused()&& !track->isTerminated())
{
......
/*
設定活躍Track的資料提供者為Track本身,因為Track從AudioBufferProvider
派生。混音器工作時,需從Track得到待混音的資料,也就是AT寫入的資料由混音
器取出並消費
*/
mAudioMixer->setBufferProvider(track);
//設定對應Track的混音標誌
mAudioMixer->enable(AudioMixer::MIXING);
......
//設定該Track的音量等資訊,這在以後的混音操作中會使用
mAudioMixer->setParameter(param, AudioMixer::VOLUME0, left);
mAudioMixer->setParameter(param,AudioMixer::VOLUME1, right);
mAudioMixer->setParameter(
AudioMixer::TRACK,
AudioMixer::FORMAT, track->format());
......
mixerStatus = MIXER_TRACKS_READY;
}else {//如果不滿足上面的條件,則走else分支
if (track->isStopped()) {
track->reset();//reset會清零讀寫位置,表示沒有可讀資料
}
//如果處於這三種狀態之一,則加入移除佇列
if (track->isTerminated() || track->isStopped()
|| track->isPaused()) {
tracksToRemove->add(track);
mAudioMixer->disable(AudioMixer::MIXING);
} else {
//不處於上面三種狀態時,表示暫時沒有可讀資料,則重試mRetryCount次
if (--(track->mRetryCount) <= 0) {
tracksToRemove->add(track);
} else if (mixerStatus != MIXER_TRACKS_READY) {
mixerStatus =MIXER_TRACKS_ENABLED;
}
//禁止這個Track的混音
mAudioMixer->disable(AudioMixer::MIXING);
......
}
}
}
//對那些被移除的Track做最後的處理
......
returnmixerStatus;
}
當所有Track準備就緒後,最重要的工作就是混音。混音物件的process就派上了用場。來看這個process函式,程式碼如下所示:
[-->AudioMixer.cpp]
void AudioMixer::process(void* output)
{
mState.hook(&mState, output);//hook?這是一個函式指標
}
hook是函式指標,它根據Track的個數和它的音訊資料格式(取樣率等)等情況,使用不同的處理函式。為進一步瞭解混音器是如何工作的,需要先分析AudioMixer物件。
(3) AudioMixer物件的分析
AudioMixer實現AudioMixer.cpp中,先看建構函式:
[-->AudioMixer.cpp]
AudioMixer::AudioMixer(size_t frameCount,uint32_t sampleRate)
: mActiveTrack(0), mTrackNames(0),mSampleRate(sampleRate)
{
mState.enabledTracks= 0;
mState.needsChanged = 0;
mState.frameCount = frameCount;//這個值等於音訊輸出物件的緩衝大小
mState.outputTemp = 0;
mState.resampleTemp= 0;
//hook初始化的時候為process__nop,這個函式什麼都不會做
mState.hook = process__nop;
track_t*t = mState.tracks;//track_t是和Track相對應的一個結構
//最大支援32路混音,也很不錯了
for(int i=0 ; i<32 ; i++) {
......
t->channelCount = 2;
t->enabled = 0;
t->format = 16;
t->buffer.raw = 0;
t->bufferProvider = 0; // bufferProvider為這一路Track的資料提供者
t->hook = 0;//每一個Track也有一個hook函式
......
}
int mActiveTrack;
uint32_t mTrackNames;
constuint32_t mSampleRate;
state_t mState
}
其中,mState是在AudioMixer類中定義的一個資料結構。
struct state_t {
uint32_t enabledTracks;
uint32_t needsChanged;
size_t frameCount;
mix_t hook;
int32_t *outputTemp;
int32_t *resampleTemp;
int32_t reserved[2];
/*
aligned表示32位元組對齊,由於source insight不認識這個標誌,導致
state_t不能被解析。在看程式碼時,可以註釋掉後面的attribute,這樣source insight
就可以識別state_t結構了
*/
track_t tracks[32]; __attribute__((aligned(32)));
};
AudioMixer為hook準備了多個實現函式,來看:
· process__validate:根據Track的格式、數量等資訊選擇其他的處理函式。
· process__nop:什麼都不做。
· process__genericNoResampling:普通無需重取樣。
· process__genericResampling:普通需重取樣。
· process__OneTrack16BitsStereoNoResampling:一路音訊流,雙聲道,PCM16格式,無需重取樣。
· process__TwoTracks16BitsStereoNoResampling:兩路音訊流,雙聲道,PCM16格式,無需重取樣。
hook最初的值為process__nop,這一定不會是混音中最終使用的處理函式,難道有動態賦值的地方?是的。一起來看:
(4) 殺雞不用宰牛刀
在AF的prepare_l中,會為每一個準備好的Track使能混音標誌:
mAudioMixer->setBufferProvider(track);
mAudioMixer->enable(AudioMixer::MIXING);//使能混音
請看enable的實現:
[-->AudioMixer.cpp]
status_t AudioMixer::enable(int name)
{
switch(name) {
case MIXING: {
if (mState.tracks[ mActiveTrack ].enabled != 1) {
mState.tracks[ mActiveTrack ].enabled = 1;
//注意這個invalidateState呼叫
invalidateState(1<<mActiveTrack);
}
}break;
default:
return NAME_NOT_FOUND;
}
returnNO_ERROR;
}
[-->AudioMixer.cpp]
void AudioMixer::invalidateState(uint32_t mask)
{
if(mask) {
mState.needsChanged |= mask;
mState.hook = process__validate;//將hook設定為process_validate
}
}
process_validate會根據當前Track的情況選擇不同的處理函式,所以不會出現殺雞卻用災牛刀的情況。
[-->AudioMixer.cpp]
void AudioMixer::process__validate(state_t*state, void* output)
{
uint32_t changed = state->needsChanged;
state->needsChanged = 0;
uint32_t enabled = 0;
uint32_t disabled = 0;
......
if(countActiveTracks) {
if(resampling) {
......
//如果需要重取樣,則選擇process__genericResampling
state->hook = process__genericResampling;
}else {
......
state->hook = process__genericNoResampling;
if (all16BitsStereoNoResample && !volumeRamp) {
if (countActiveTracks == 1) {
//如果只有一個Track,則使用process__OneTrack16BitsStereoNoResampling
state->hook =process__OneTrack16BitsStereoNoResampling;
}
}
}
}
state->hook(state, output);
......
}
假設用例執行時,系統只有這麼一個Track,那麼hook函式使用的就是process__OneTrack16BitsStereoNoResampling處理。process_XXX函式會涉及很多數字音訊處理的專業知識,先不用去討論它。資料緩衝的消費工作是在這個函式中完成的,因此應重點關注它是如何通過CB物件使用資料緩衝的。
說明:在這個資料消費和之前破解AT的過程中所講的資料生產是對應的,先來提煉AT和AF在生產和消費這兩個環節上與CB互動的流程。
(5) 怎麼消費資料
在AudioTrack中,曾講到資料的生產流程:
· ObtainBuffer,得到一塊資料緩衝。
· memcpy資料到該緩衝。
· releaseBuffer,釋放這個緩衝。
那麼做為消費者,AudioFlinger是怎麼獲得這些資料的呢?
[-->AudioMixer.cpp]
voidAudioMixer::process__OneTrack16BitsStereoNoResampling(
state_t*state, void* output)
{
//找到被啟用的Track,此時只能有一個Track,否則就不會選擇這個process函式了
constint i = 31 - __builtin_clz(state->enabledTracks);
consttrack_t& t = state->tracks[i];
AudioBufferProvider::Buffer& b(t.buffer);
......
while(numFrames) {
b.frameCount = numFrames;
//BufferProvider就是Track物件,呼叫它的getNextBuffer獲得可讀資料緩衝
t.bufferProvider->getNextBuffer(&b);
int16_t const *in = b.i16;
......
size_t outFrames = b.frameCount;
do {//資料處理,也即是混音
uint32_t rl = *reinterpret_cast<uint32_t const *>(in);
in += 2;
int32_t l = mulRL(1, rl, vrl) >> 12;
int32_t r = mulRL(0, rl, vrl) >> 12;
//把資料複製給out緩衝
*out++ = (r<<16) | (l & 0xFFFF);
} while (--outFrames);
}
numFrames -= b.frameCount;
//呼叫Track的releaseBuffer釋放緩衝
t.bufferProvider->releaseBuffer(&b);
}
}
bufferProvider就是Track物件,總結一下它使用資料緩衝的呼叫流程:
· 呼叫Track的getNextBuffer,得到可讀資料緩衝。
· 呼叫Track的releaseBuffer,釋放資料緩衝。
現在來分析上面這兩個函式:getNextBuffer和releaseBuffer。
(6) getNextBuffer和releaseBuffer的分析
先看getNextBuffer。它從資料緩衝中得到一塊可讀空間:
[-->AudioFlinger.cpp]
status_tAudioFlinger::PlaybackThread::Track::getNextBuffer(
AudioBufferProvider::Buffer*buffer)
{
audio_track_cblk_t*cblk = this->cblk();//通過CB物件完成
uint32_t framesReady;
//frameCount為AudioOutput音訊輸出物件的緩衝區大小
uint32_t framesReq = buffer->frameCount;
......
//根據CB的讀寫指標計算有多少幀資料可讀
framesReady = cblk->framesReady();
if (LIKELY(framesReady)){
uint32_t s = cblk->server; //當前讀位置
//可讀的最大位置,為當前讀位置加上frameCount
uint32_tbufferEnd = cblk->serverBase + cblk->frameCount;
//AT可以通過setLooping設定播放的起點和終點,如果有終點的話,需要以loopEnd
//作為資料緩衝的末尾
bufferEnd = (cblk->loopEnd < bufferEnd) ? cblk->loopEnd :bufferEnd;
if(framesReq > framesReady) {
//如果要求的讀取幀數大於可讀幀數,則只能選擇實際可讀的幀數
framesReq = framesReady;
}
//如果可讀幀數的最後位置超過了AT設定的末端點,則需要重新計算可讀幀數
if(s + framesReq > bufferEnd) {
framesReq = bufferEnd - s;
}
//根據讀起始位置得到資料緩衝的起始地址,framesReq引數用來做內部檢查,防止出錯
buffer->raw = getBuffer(s, framesReq);
if (buffer->raw == 0) goto getNextBuffer_exit;
buffer->frameCount = framesReq;
return NO_ERROR;
}
getNextBuffer_exit:
buffer->raw = 0;
buffer->frameCount = 0;
return NOT_ENOUGH_DATA;
}
getNextBuffer非常簡單,不過就是根據CB記錄的讀寫位置等計算可讀的緩衝位置。下面來看releaseBuffer的操作。
[-->AudioFlinger.cpp]
void AudioFlinger::ThreadBase::TrackBase::releaseBuffer(
AudioBufferProvider::Buffer*buffer)
{
buffer->raw = 0;
mFrameCount = buffer->frameCount;//frameCount為getNextBuffer中分配的可讀幀數
step();//呼叫step函式
buffer->frameCount = 0;
}
[-->AudioFlinger.cpp]
bool AudioFlinger::ThreadBase::TrackBase::step(){
boolresult;
audio_track_cblk_t* cblk = this->cblk();
//呼叫stepServer更新讀位置
result= cblk->stepServer(mFrameCount);
if(!result) {
mFlags |= STEPSERVER_FAILED;
}
returnresult;
}
getNextBuffer和releaseBuffer這兩個函式相對比較簡單。把它和CB互動的流程總結一下,為後面進行CB物件的分析做鋪墊:
· getNextBuffer通過frameReady得到可讀幀數。
· getBuffer函式將根據可讀幀數等資訊得到可讀空間的首地址。
· releaseBuffer通過stepServer更新讀位置。
5. stop的分析
(1) TrackHandle和Track的回收
來自AT的stop請求最終會通過TrackHandle這個Proxy交給Track的stop處理。請直接看Track的stop:
[-->AudioFlinger.cpp]
void AudioFlinger::PlaybackThread::Track::stop()
{
sp<ThreadBase> thread = mThread.promote();
if(thread != 0) {
Mutex::Autolock _l(thread->mLock);
int state = mState;//儲存舊的狀態
if(mState > STOPPED) {
mState = STOPPED;//設定新狀態為STOPPED
PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
if (playbackThread->mActiveTracks.indexOf(this) < 0) {
reset();//如果該執行緒的活躍陣列中沒有Track,則重置讀寫位置
}
}
//和APS相關,我們不在這裡討論,它不直接影響AudioFlinger
if(!isOutputTrack() && (state == ACTIVE || state == RESUMING)) {
thread->mLock.unlock();
AudioSystem::stopOutput(thread->id(),
(AudioSystem::stream_type)mStreamType);
thread->mLock.lock();
}
}
}
如果Track最初處於活躍陣列,那麼這個stop函式無非是把mState設定為STOPPED了,但播放該怎麼停止呢?請再回頭看prepareTrack_l中的那個判斷:
if (cblk->framesReady() &&(track->isReady() || track->isStopped())
&& !track->isPaused() &&!track->isTerminated())
假設AT寫資料快,而AF消耗資料慢,那麼上面這個判斷語句在一定時間內是成立的,換言之,如果僅僅呼叫了stop,還是會聽到聲音,該怎麼辦?在一般情況下,AT端stop後會很快被delete,這將導致AF端的TrackHandle也被delete。
說明:在介紹Track和TrackHandle一節中,曾在最後提到了那個野指標問題。相信讀者這時候會知道那個問題的答案了,是嗎?
看TrackHandle的解構函式:
[-->AudioFlinger.cpp]
AudioFlinger::TrackHandle::~TrackHandle() {
mTrack->destroy();
}
[-->AudioFlinger.cpp]
voidAudioFlinger::PlaybackThread::Track::destroy()
{
sp<Track> keep(this);
{
sp<ThreadBase> thread = mThread.promote();
if(thread != 0) {
if (!isOutputTrack()) {
//和AudioSystem相關,以後再分析
if (mState == ACTIVE || mState == RESUMING) {
AudioSystem::stopOutput(thread->id(),
(AudioSystem::stream_type)mStreamType);
}
AudioSystem::releaseOutput(thread->id());
}
Mutex::Autolock _l(thread->mLock);
PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
//呼叫回放執行緒物件的destroyTrack_l
playbackThread->destroyTrack_l(this);
}
}
}
[-->AudioFlinger.cpp]
voidAudioFlinger::PlaybackThread::destroyTrack_l(const sp<Track>& track)
{
track->mState = TrackBase::TERMINATED;//設定狀態為TERMINATED
if(mActiveTracks.indexOf(track) < 0) {
mTracks.remove(track);//如果不在mActiveTracks陣列中,則把它從mTracks中去掉。
//由PlaybackThread的子類實現,一般就是回收一些資源等工作
deleteTrackName_l(track->name());
}
}
TrackHandle的delete最後會導致它所代理的Track物件也被刪除,那麼Client物件什麼時候被回收呢?
(2) Client的回收
直接看TrackBase的析構,因為Track的析構會導致它的基類TrackBase解構函式被呼叫,程式碼如下所示:
[-->AudioFlinger.cpp]
AudioFlinger::ThreadBase::TrackBase::~TrackBase()
{
if (mCblk) {
//placementnew出來的物件需要顯示呼叫的解構函式
mCblk->~audio_track_cblk_t();
if(mClient == NULL) {
delete mCblk;//先呼叫析構,再釋放記憶體,這是placement new的用法
}
}
mCblkMemory.clear();
if(mClient != NULL) {
Mutex::Autolock _l(mClient->audioFlinger()->mLock);
mClient.clear();//如果mClient的強弱引用計數都為0,則會導致該Client被delete
}
}
資源回收的工作相對比較簡單,這裡就不做過多的討論了,讀者可自行分析研究。
說明:其實,要找到TrackHandle是什麼時候被delete,會更有難度。
7.3.3 audio_track_cblk_t的分析
前面講解了AudioFlinger的工作方式,但AT和AF以及那個神祕的CB物件的工作原理,一直都還沒能講解。對於Audio系統來說,如果最終也解決不了這個,真會有當年岳飛在朱仙鎮被十二道金牌召回時一樣的悲憤心情。幸好我們沒遇到秦檜,那就奮力窮追猛打,去解決這個CB物件吧。
解決問題要有好的對策。還是從AT和AF兩端關於CB物件的呼叫流程開始分析,這一招可是屢試不爽啊!
1. AT端的流程
AT端作為資料的生產者,可稱它為寫者,它在CB物件中用user表示。它的呼叫流程是:
· 呼叫framesAvailable,看看是否有空餘的可寫空間。
· 呼叫buffer,獲得寫空間起始地址。
· 呼叫stepUser,更新user的位置。
一起來分析一下,由於這幾個函式都相當簡單,力爭一氣呵成。
先呼叫framesAvailable,看看當前剩餘多少可寫空間。假設是第一次進來,讀者還在那等待資料,這樣就不用考慮競爭等問題了,程式碼如下所示:
[-->AudioTrack.cpp::audio_track_cblk_t的framesAvailable()及相關]
uint32_t audio_track_cblk_t::framesAvailable()
{
Mutex::Autolock _l(lock);
returnframesAvailable_l();//呼叫framesAvailable_l
}
int32_t audio_track_cblk_t::framesAvailable_l()
{
uint32_t u = this->user; //當前寫者位置,此時也為0
uint32_t s = this->server; //當前讀者位置,此時為0
if(out) { //對於音訊輸出,out為1
uint32_t limit = (s < loopStart) ? s : loopStart;
//由於不設定播放端點,所以loopStart是初始值INT_MAX, limit=0
return limit + frameCount - u;
//返回0+frameCount-0,也就是資料緩衝的全部大小。假設frameCount=1024幀
}
}
然後,呼叫buffer獲得起始位置,buffer返回一個地址。
[-->AudioTrack.cpp]
void* audio_track_cblk_t::buffer(uint32_toffset) const
{
//buffers是資料緩衝的起始位置,offset是計算出來的基於userBase的偏移。
//通過這種方式巧妙地把資料緩衝當做環形緩衝來處理
return(int8_t *)this->buffers + (offset - userBase) * this->frameSize;
}
當把資料寫到緩衝後,呼叫stepUser。
[-->AudioTrack.cpp]
uint32_t audio_track_cblk_t::stepUser(uint32_tframeCount)
{
/*
framecount,表示寫了多少幀,前面分配了1024幀,但寫的資料可以比這個少
假設這一次寫了512幀
*/
uint32_t u = this->user;//user位置還沒更新,此時u=0;
u +=frameCount;//u更新了,u=512
......
/*
userBase還是初始值0。可惜只寫了1024的一半,所以userBase加不了。
但這句話很重要,還記得前面的buffer呼叫嗎?取資料地址的時候用offset-userBase,
一旦user位置到達緩衝的尾部,則userBase也會更新,這樣offset-userBase的位置就會
回到緩衝的頭部,從頭到尾這麼反覆迴圈,不就是一個環形緩衝了嗎?非常巧妙!
*/
if (u>= userBase + this->frameCount) {
userBase += this->frameCount;
}
this->user = u;//喔,user位置也更新為512了,但是useBase還是0
returnu;
}
假設寫者這時因某種原因停止了寫資料,而讀者卻會被喚醒。
2 AF端的流程
AF端作為資料的消費者,它在CB中的表示是server,可稱它為讀者。讀者的使用流程是:
· 呼叫framesReady看是否有可讀資料。
· 獲得可讀資料的起始位置,這個和上面的buffer呼叫基本一樣,都是根據offset和serverBase來獲得可讀資料塊的首地址。
· 呼叫stepServer更新讀位置。
現在來分析framesReady和stepServer這兩個函式,framesReady的程式碼如下所示:
[-->AudioTrack.cpp]
uint32_t audio_track_cblk_t::framesReady()
{
uint32_t u = this->user; //u為512
uint32_ts = this->server;//還沒讀呢,s為零
if(out) {
if(u < loopEnd) {
return u - s;//loopEnd也是INT_MAX,所以這裡返回512,表示有512幀可讀了
}else {
Mutex::Autolock _l(lock);
if (loopCount >= 0) {
return (loopEnd - loopStart)*loopCount + u - s;
} else {
return UINT_MAX;
}
}
} else{
return s - u;
}
}
可讀資料地址的計算方法和前面的buffer呼叫一樣,都是通過server和serverBase來計算的。接著看stepServer,程式碼如下所示:
[-->AudioTrack.cpp]
bool audio_track_cblk_t::stepServer(uint32_tframeCount)
{
status_t err;
err = lock.tryLock();
uint32_t s = this->server;
s +=frameCount; //讀了512幀了,所以s=512
......
//沒有設定迴圈播放,所以不走這個
if (s>= loopEnd) {
s =loopStart;
if (--loopCount == 0) {
loopEnd = UINT_MAX;
loopStart = UINT_MAX;
}
}
//和userBase一樣的處理
if (s>= serverBase + this->frameCount) {
serverBase += this->frameCount;
}
this->server = s; //server為512了
cv.signal(); //讀者讀完了,觸發一個同步訊號,因為讀者可能在等待可寫的資料緩衝
lock.unlock();
returntrue;
}
3. 真的是環形緩衝?
滿足下面場景的緩衝可稱為環形緩衝(假設資料緩衝最大為1024幀):
· 寫者先寫1024幀,此後便無剩餘空間可寫。
· 讀者讀了前面的512幀,那麼這512幀的資料空間就空餘出來了。
· 所以,寫者就可以重新利用這空餘512幀的空間了。
關鍵是第三步,寫者是否跟蹤了讀者的位置,並充分利用了讀者已使用過的資料空間。所以得回頭看看寫者AT是否把這512幀利用了。
先看寫者寫完1024幀後的情況,stepUser中會有下面幾句話:
if (u >= userBase + this->frameCount) {
//u為1024,userBase為0,frameCount為1024
userBase += this->frameCount;//好,userBase也為1024了
}
此時userBase更新為1024幀。再看寫者獲取可寫空間的framesAvailable_l函式,按照以前的假設,應該返回512幀可寫空間,程式碼如下所示:
[-->AudioTrack.cpp]
uint32_t audio_track_cblk_t::framesAvailable_l()
{
uint32_t u = this->user; //1024,寫者上一次寫完了整個1024幀空間
uint32_t s = this->server;//512,讀者當前讀到的位置
if(out) {
uint32_t limit = (s < loopStart) ? s : loopStart;
return limit + frameCount - u;//返回512
}
}
framesAvailable返回了512幀,但可寫空間的地址是否是從頭開始的呢?要是從其他地方開始的,情況就慘了。來看buffer中最後返回的可寫空間地址:
return (int8_t *)this->buffers + (offset -userBase) * this->frameSize;
//offset是外界傳入的基於userBase的一個偏移量,它的值是userBase+512,所以
//offset-userBase將得到從頭開始的那段資料空間。真的是一個環形緩衝。
從上面的分析中看出,CB物件通過userBase和user等幾個變數,將一段有限長度的線性緩衝變成了一段無限長的緩衝,這不正是環形緩衝的精髓嗎!
7.3.4 AudioFlinger總結
總體來說,AF比較複雜,再加上其他一些輔助類,cpp檔案中的程式碼有近7000行。其中AudioFlinger.cpp就有4000多行。這僅是從程式碼量來看,而使AF複雜的另外一個重要因素是它定義的內部類和它們之間的關係。
不過,從生產者和消費者的角度來看,AF的工作還是比較簡單明瞭:
· MixerThread獲取Track的資料,混音後寫入音訊輸出裝置。
關於AudioFlinger的學習和理解,有幾個建議供大家參考:
· 首先要搞清資料傳輸的流程。雖然這隻涉及AT和AF兩個程式,但可以只在一端使用流程進行分析,例如AF的start、stop等。AT和AF的工作流程也是它們的工作步驟,流程分析在AT和AF的破解過程中起到了重要作用,希望大家能掌握這個方法。
· 搞清AF中各個類的作用和派生關係。這樣,在分析時就能準確定位到具體的實現函式。
· 搞清CB物件的工作原理和方式。如自己覺得只理解AF工作流程即可,CB物件就不必過於深究。
7.4 AudioPolicyService的破解
前面,關於AudioTrack和AudioFlinger的分析,主要是針對Audio系統中資料傳輸方面的,它們是Audio系統中不可或缺的部分。但Audio系統僅限於此嗎?如果是這樣,那麼AudioPolicyService又是怎麼一回事?另外,還要問幾個實際問題:插入耳機後,聲音是怎麼從最開始的聽筒輸出變成從耳機輸出的呢?音量又是怎麼控制的?MixerThread的來歷和AudioPolicy有怎樣的關係?這些都與後面要分析的AudioPolicyService有關。
顧名思義,AudioPolicyService,是和Audio策略有關的,依本人對AudioPolicy的理解,策略比流程更要複雜和難懂,對APS與對AT及AF的分析不同,因此對其不宜採用固定流程分析法,而應從下面三個步驟入手:
· 在分析AudioPolicyService的建立過程中,會講解一些重要的概念和定義。
· 重新回到AudioTrack的分析流程,介紹其中和AudioPolicy有關的內容。
· 以一個耳機插入事件為例項,講解AudioPolicy的處理。
7.4.1 AudioPolicyService的建立
AudioPolicyService和AudioFlinger都駐留於一個程式,之前在MixerThread來歷一節中,曾簡單介紹過APS的建立,現在需要仔細觀察其中的內容。
1. 建立 AudioPolicyService
AudioPolicyService的程式碼如下所示:
[-->AudioPolicyService.cpp]
AudioPolicyService::AudioPolicyService()
:BnAudioPolicyService() ,
//mpPolicyManager是Audio系統中的另一種HAL物件,它的型別是AudioPolicyInterface
mpPolicyManager(NULL)
{
char value[PROPERTY_VALUE_MAX];
//TonePlayback用於播放Tone音,Tone包括按鍵音等
mTonePlaybackThread = new AudioCommandThread(String8(""));
//用於處理控制命令,例如路由切換、音量調節等
mAudioCommandThread = newAudioCommandThread(String8("ApmCommandThread"));
#if (defined GENERIC_AUDIO) || (definedAUDIO_POLICY_TEST)
//注意AudioPolicyManagerBase的建構函式,把this傳進去了。
mpPolicyManager = new AudioPolicyManagerBase(this);
#else
...
//使用硬體廠商實現的AudioPolicyInterface
mpPolicyManager= createAudioPolicyManager(this);
#endif
//根據系統屬性來判斷照相機拍照時是否強制發聲。為了防止偷拍,強制按快門的時候必須發出聲音。
property_get("ro.camera.sound.forced",value, "0");
mpPolicyManager->setSystemProperty("ro.camera.sound.forced",value);
}
和AudioFlinger中的AudioHardwareInterface一樣,在APS中可以見到另外一個HAL層物件AudioPolicyInterface,為什麼在APS中也會存在HAL物件呢?
如前所述,APS主要是用來控制Audio系統的,由於各個硬體廠商的控制策略不可能完全一致,所以Android把這些內容抽象成一個HAL物件。下面來看這個AudioPolicyInterface。
2. 對AudioPolicyInterface的分析
AudioPolicyInterface比AudioHardwareInterface簡單直接。這裡,只需看幾個重點函式即可,程式碼如下所示:
[-->AudioPolicyInterface.h]
class AudioPolicyInterface
{
public:
......
//設定裝置的連線狀態,這些裝置有耳機、藍芽等
virtualstatus_t setDeviceConnectionState(
AudioSystem::audio_devicesdevice,
AudioSystem::device_connection_state state,
const char *device_address) = 0;
//設定系統Phone狀態,這些狀態包括通話狀態、來電狀態等
virtual void setPhoneState(int state) = 0;
//設定force_use的config策略,例如通話中強制使用揚聲器
virtualvoid setForceUse(AudioSystem::force_use usage,
AudioSystem::forced_config config) = 0;
/*
audio_io_handle_t是int型別。這個函式的目的是根據傳入的引數型別
找到合適的輸出控制程式碼。這個控制程式碼,在目前的Audio系統代表AF中的某個工作執行緒。
還記得建立AudioTrack的時候傳入的那個output值嗎?它就是通過這個函式得來的。
關於這個問題,馬上會分析到
*/
virtualaudio_io_handle_t getOutput(
AudioSystem::stream_typestream,
uint32_t samplingRate = 0,
uint32_t format = AudioSystem::FORMAT_DEFAULT,
uint32_t channels = 0,
AudioSystem::output_flagsflags =
AudioSystem::OUTPUT_FLAG_INDIRECT)= 0;
//在下面這兩個函式後會介紹。它們的第二個參數列示使用的音訊流型別,
virtualstatus_t startOutput(audio_io_handle_t output,
AudioSystem::stream_type stream) = 0;
virtual status_t stopOutput(audio_io_handle_toutput,
AudioSystem::stream_type stream) = 0;
......
//音量控制:設定不同音訊流的音量級別範圍,例如MUSIC有15個級別的音量
virtual void initStreamVolume(AudioSystem::stream_type stream,
intindexMin,
intindexMax) = 0;
//設定某個音訊流型別的音量級,例如覺得music聲音太小時,可以呼叫這個函式提高音量級
virtualstatus_t setStreamVolumeIndex(AudioSystem::stream_type stream,
int index) = 0;
}
從上面的分析中可知,AudioPolicyInterface主要提供了一些裝置切換管理和音量控制的介面。每個廠商都有各自的實現方式。目前,Audio系統提供了一個通用的實現類AudioPolicyManagerBase,以前這個類是放在hardware目錄下的,現在是放到framework目錄中了。圖7-12展示了AP和HAL類之間的關係:
圖7-12 AudioPolicy和AudioPolicyInterface的關係
其中:
· AudioPolicyService有一個AudioPolicyInterface型別的物件。
· AudioPolicyManagerBase有一個AudioPolicyClientInterace的物件。
AudioPolicyInterface中的一些函式後面會分析到,這些函式中有很多引數都是以AudioSystem::xxx方式出現的,那麼AudioSystem又是什麼呢?
3. AudioSystem的介紹
AudioSystem是一個Native類,這個類在Java層有對應的Java類,其中定義了一些重要的型別,比如音訊流流程、音訊裝置等,這些都在AudioSystem.h中。下面來看其中的一些定義。
(1)stream type(音訊流型別)
音訊流型別,我們已在AudioTrack中見識過了,其完整定義如下:
enum stream_type {
DEFAULT =-1,//預設
VOICE_CALL = 0,//通話聲
SYSTEM = 1,//系統聲,例如開關機提示
RING = 2,//來電鈴聲
MUSIC = 3,//媒體播放聲
ALARM = 4,//鬧鐘等的警告聲
NOTIFICATION = 5,//簡訊等的提示聲
BLUETOOTH_SCO = 6,//藍芽SCO
ENFORCED_AUDIBLE = 7,//強制發聲,照相機的快門聲就屬於這個型別
DTMF = 8,//DTMF,撥號盤的按鍵聲
TTS = 9,//文字轉語音,Text to Speech
NUM_STREAM_TYPES
};
音訊流型別有什麼用呢?為什麼要做這種區分呢?它主要與兩項內容有關:
· 裝置選擇:例如,之前在建立AudioTrack時,傳入的音訊流型別是MUSIC,當插上耳機時,這種型別的聲音只會從耳機中出來,但如果音訊流型別是RING,則會從耳機和揚聲器中同時出來。
· 音量控制:不同流型別音量級的個數不同,例如,MUSIC型別有15個級別可供使用者調節,而有些型別只有7個級別的音量。
(2)audio mode(聲音模式)
audio mode和電話的狀態有直接關係。先看它的定義:
enum audio_mode {
MODE_INVALID = -2,
MODE_CURRENT = -1,
MODE_NORMAL = 0, //正常,既不打電話,也沒有來電
MODE_RINGTONE,//有來電
MODE_IN_CALL,//通話狀態
NUM_MODES
};
為什麼Audio需要特別強調Phone的狀態呢?這必須和智慧手機的硬體架構聯絡上。先看智慧手機的硬體架構,如圖7-13所示:
圖7-13 智慧手機的硬體架構圖
從圖7-13中看出了什麼?
· 系統有一個音訊DSP,聲音的輸入輸出都要經過它(不考慮藍芽的情況)。但它處理完的數字訊號,需通過D/A(數/模)轉換後輸出到最終的裝置上,這些裝置包括揚聲器、聽筒、耳機等。
注意:所謂的裝置切換,是指諸如揚聲器切換到聽筒的情況,而前面常提到的音訊輸出裝置,應該指的是DSP。
· 系統有兩個核心處理器,一個是應用處理的核心,叫AP(Application Processor),可把它當做桌上型電腦上的CPU,在這上面可以執行作業系統。另一個和手機通訊相關,一般叫BP(Baseband Processor 基帶處理器),可把它當做桌上型電腦上的“貓”。
· 從圖7-13中可看出,AP和BP都能向音訊DSP傳送資料,它們在硬體上通路上互不干擾。於是就出現了一個問題,即如果兩個P同時往DSP傳送資料,而互相之間沒有協調,就可能出現通話聲和音樂聲混雜的情況。誰還會用這樣的手機?所以打電話時,將由AP上的Phone程式主動設定Audio系統的mode,在這種mode下,Audio系統會做一些處理,例如把music音量調小等。
· 注意圖中的藍芽了嗎?它沒有像AP那樣直接和音訊DSP的相連,所以音訊資料需要單獨發給藍芽裝置。如果某種聲音要同時從藍芽和揚聲器發出,亦即一份資料要往兩個地方傳送,便滿足了AudioFlinger中DuplicatingThread出現的現實要求。
注意:藍芽裝置實際上會建立兩條資料通路:SCO和A2DP。A2DP和高質量立體聲有關,且必須由AudioFlinger向它傳送資料。所以“音訊資料需要單獨傳送給藍芽裝置”,這個裝置實際上是指藍芽的A2DP裝置。藍芽技術很複雜,有興趣的讀者可以自行研究。
(3)force use和config(強制使用及配置)
大家知道,手機通話時可以選擇揚聲器輸出,這就是強制使用的案例。Audio系統對此有很好的支援。它涉及到兩個方面:
· 強制使用何種裝置,例如使用揚聲器、聽筒、耳機等。它由forced_config控制,程式碼如下所示:
enum forced_config {
FORCE_NONE,
FORCE_SPEAKER, //強制使用揚聲器
FORCE_HEADPHONES,
FORCE_BT_SCO,
FORCE_BT_A2DP,
FORCE_WIRED_ACCESSORY,
FORCE_BT_CAR_DOCK,
FORCE_BT_DESK_DOCK,
NUM_FORCE_CONFIG,
FORCE_DEFAULT = FORCE_NONE
}
· 在什麼情況下需要強制使用,是通話的強制使用,還是聽音樂的強制使用?這須由force_use控制,程式碼如下所示:
enumforce_use {
FOR_COMMUNICATION,//通話情況,注意字首,是FOR_XXX
FOR_MEDIA,//聽音樂等媒體相關的情況
FOR_RECORD,
FOR_DOCK,
NUM_FORCE_USE
}
所以,AudioPolicyInterface的setForceUse函式,就是設定在什麼情況下強制使用什麼裝置:
virtual void setForceUse(AudioSystem::force_useusage,//什麼情況
AudioSystem::forced_configconfig //什麼裝置
)= 0;
(4)輸出裝置的定義
前面曾反覆提到輸出裝置。這些裝置在軟體中是怎麼表示的呢?Audio定義了很多輸出裝置,來看其中幾個:
enum audio_devices {
//output devices
DEVICE_OUT_EARPIECE = 0x1, //聽筒
DEVICE_OUT_SPEAKER = 0x2, //揚聲器
DEVICE_OUT_WIRED_HEADSET = 0x4, //耳機
DEVICE_OUT_WIRED_HEADPHONE = 0x8, //另外一種耳機
DEVICE_OUT_BLUETOOTH_SCO = 0x10, //藍芽相關,SCO用於通話的語音傳輸
DEVICE_OUT_BLUETOOTH_SCO_HEADSET = 0x20,
DEVICE_OUT_BLUETOOTH_SCO_CARKIT= 0x40,
DEVICE_OUT_BLUETOOTH_A2DP = 0x80, //藍芽相關,A2DP用於立體聲傳輸
DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100,
DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200,
DEVICE_OUT_AUX_DIGITAL = 0x400,
DEVICE_OUT_DEFAULT= 0x8000,
......
}
至此,AudioSystem中常用的定義都已見過了,現在要回到APS的建立上了。對這個例子,將使用Generic的裝置,所以會直接建立AudioPolicyManagerBase物件,這個物件實現了AudioPolicyInterface的所有功能。一起來看。
說明:實際上很多硬體廠商實現的AudioPolicyInterface,基本上是直接使用這個AudioPolicyManagerBase。
4. AudioPolicyManagerBase的分析
AudioPolicyManagerBase類在AudioPolicyManagerBase.cpp中實現,先來看它的建構函式:
[-->AudioPolicyManagerBase.cpp]
AudioPolicyManagerBase::AudioPolicyManagerBase(
AudioPolicyClientInterface*clientInterface)
:mPhoneState(AudioSystem::MODE_NORMAL),mRingerMode(0),
mMusicStopTime(0),mLimitRingtoneVolume(false)
{
//APS實現了AudioPolicyClientInterface介面
mpClientInterface= clientInterface;//這個clientInterface就是APS物件
//清空強制使用配置
for(int i = 0; i < AudioSystem::NUM_FORCE_USE; i++) {
mForceUse[i] = AudioSystem::FORCE_NONE;
}
//輸出裝置有聽筒和揚聲器
mAvailableOutputDevices = AudioSystem::DEVICE_OUT_EARPIECE |
AudioSystem::DEVICE_OUT_SPEAKER;
//輸入裝置是內建的麥克(學名叫傳聲器)
mAvailableInputDevices = AudioSystem::DEVICE_IN_BUILTIN_MIC;
#ifdef WITH_A2DP //和藍芽立體聲有關。
mA2dpOutput = 0;
mDuplicatedOutput = 0;
mA2dpDeviceAddress = String8("");
#endif
mScoDeviceAddress = String8(""); //SCO主要用於通話
/*
①建立一個AudioOutputDescriptor物件,這個物件用來記錄並維護與
輸出裝置(相當於硬體的音訊DSP)相關的資訊,例如使用該裝置的流個數、各個流的音量、
該裝置所支援的取樣率、取樣精度等。其中,有一個成員mDevice用來表示目前使用的輸出裝置,
例如耳機、聽筒、揚聲器等
*/
AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
outputDesc->mDevice= (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER;
/*
②還記得MixerThread的來歷嗎?openOutput導致AF建立了一個工作執行緒。
該函式返回的是一個工作執行緒索引號
*/
mHardwareOutput =mpClientInterface->openOutput(&outputDesc->mDevice,
&outputDesc->mSamplingRate,
&outputDesc->mFormat,
&outputDesc->mChannels,
&outputDesc->mLatency,
outputDesc->mFlags);
......
//AMB維護了一個與裝置相關的key/value集合,下面將對應資訊加到該集合中。
addOutput(mHardwareOutput,outputDesc);
//③設定輸出裝置,就是設定DSP的資料流到底從什麼裝置出去,這裡設定的是從揚聲器出去
setOutputDevice(mHardwareOutput,
(uint32_t)AudioSystem::DEVICE_OUT_SPEAKER,true);
}
//④更新不同策略使用的裝置
updateDeviceForStrategy();
}
關於AMB這個小小的建構函式,有幾個重要點需要介紹:
(1)AudioOutputDescriptor和openOutput
AudioOutputDescriptor物件,是AMB用來控制和管理音訊輸出裝置的,從硬體上看,它代表的是DSP裝置。關於這一點已在註釋中做出說明,這裡就不再贅述。
另一個重要點是openOutput函式。該函式的實現由APS來完成。之前曾分析過,它最終會在AF中建立一個混音執行緒(不考慮DirectOutput的情況),該函式返回的是該執行緒在AF中的索引號,亦即
mHardwareOutput =mpClientInterface->openOutput(......)
mHardwareOutput表示的是AF中一個混音執行緒的索引號。這裡涉及到一個非常重要的設計問題:AudioFlinger到底會建立多少個MixerThread?有兩種設計方案:
· 一種是一個MixerThread對應一個Track。如果這樣,AMB僅使用一個mHardwareOutput恐怕還不夠用。
· 另一種是用一個MixerThread支援32路的Track資料,多路資料通過AudioMixer混音物件在軟體層面進行混音。
這裡用的是第二種,當初設計時為何不用一個MixerThread支援一路Track,然後把混音的工作交給硬體來完成呢?我覺得,原因之一是如採用一個執行緒一個Track的方式,就非常難於管理和控制,另一個原因是多執行緒比較浪費資源。
如採用第二種方法(也就是現有的方案),就極大簡化了AMB的工作量。圖7-14展示了AMB和AF及MixerThread之間的關係:
圖7-14 AF、AMB及MixerThread之間的關係
圖7-14表明:
· AMB中除了mHardwareOutput外,還有一個mA2dpOutput,它對應的MixerThread,專往代表藍芽A2DP裝置的AudioStreamOut上傳送資料。關於這個問題,在後面分析DuplicatingThread時可以見到。
注意:使用mA2dpOutput需要藍芽裝置連線上才會有意義。
· 除了藍芽外,系統中一般也就只有圖7-14右邊這麼一個MixerThread了,所以AMB通過mHardwareOutput就能控制整個系統的聲音,這真是一勞永逸。
說明:關於這一點,現在通過setOutputDevice來分析。
(2)setOutputDevice
現在要分析的呼叫是setOutputDevice,目的是為DSP選擇一個合適的輸出裝置。注意它的第一個引數是傳入的mHardwareOutput,它最終會找到代表DSP的AudioStreamOut物件,第二個引數是一個裝置號。
[-->AudioPolicyManagerBase.cpp]
void AudioPolicyManagerBase::setOutputDevice(audio_io_handle_toutput,
uint32_tdevice, bool force, int delayMs)
{
AudioOutputDescriptor*outputDesc = mOutputs.valueFor(output);
//判斷是否是Duplicate輸出,和藍芽A2DP有關,後面再做分析
if(outputDesc->isDuplicated()) {
setOutputDevice(outputDesc->mOutput1->mId, device, force,delayMs);
setOutputDevice(outputDesc->mOutput2->mId, device, force,delayMs);
return;
}
// 初始設定的輸出裝置為聽筒和揚聲器
uint32_tprevDevice = (uint32_t)outputDesc->device();
if ((device == 0 || device == prevDevice)&& !force) {
return;
}
//現在設定新的輸出裝置為揚聲器,注意這是軟體層面上的設定
outputDesc->mDevice = device;
......
/*
還需要硬體也做相應設定,主要是告訴DSP把它的輸出切換到某個裝置上,根據之前的分析,
這個請求要傳送到AF中的MixerThread上,因為只有它擁有代表輸出裝置的AudioStreamOut
物件
*/
AudioParameter param = AudioParameter();
param.addInt(String8(AudioParameter::keyRouting),(int)device);
/*
上面的配置引數將投遞到APS的訊息佇列,而APS中建立的AudioCommandThread
會取出這個配置引數,再投遞給AF中對應的MixerThread,最終由MixerThread處理。
這個流程,將在耳機插拔事件處理中進行分析
*/
mpClientInterface->setParameters(mHardwareOutput,
param.toString(),delayMs);
......
}
setOutputDevice要實現的目的已很明確,只是實現的過程比較繁瑣而已。其間沒有太多複雜之處,讀者可自行研究,以加深對Audio系統的瞭解。
(3)Audio Strategy
現呼叫的函式是updateDeviceForStrategy,這裡會引出一個strategy的概念。先看updataDeviceForStrategy函式:
[-->AudioPolicyManagerBase.cpp]
voidAudioPolicyManagerBase::updateDeviceForStrategy()
{
for(int i = 0; i < NUM_STRATEGIES; i++) {
mDeviceForStrategy[i] =
getDeviceForStrategy((routing_strategy)i,false);
}
}
關於getDeviceForStrategy,在耳機插拔事件中再做分析,現在先看routing_stratgy的定義,程式碼如下所示:
[-->getDeviceForStrategy.h::routing_strategy]
//routing_strategy:路由策略
enum routing_strategy {
STRATEGY_MEDIA,
STRATEGY_PHONE,
STRATEGY_SONIFICATION,
STRATEGY_DTMF,
NUM_STRATEGIES
}
它是在AudioPolicyManagerBase.h中定義的,一般的應用程式不會使用這個標頭檔案。這個routing_strategy有什麼用處呢?從名字上看,似乎和路由的選擇有關係,但AudioSystem定義的是stream type,這兩者之間會有什麼關係嗎?有,而且還很緊密。這個關係通過AMB的getStrategy就可以看出來。它會從指定的流型別得到對應的路由策略,程式碼如下所示:
[-->AudioPolicyManagerBase.cpp]
AudioPolicyManagerBase::getStrategy(AudioSystem::stream_typestream)
{
switch(stream) {
caseAudioSystem::VOICE_CALL:
caseAudioSystem::BLUETOOTH_SCO:
return STRATEGY_PHONE; //PHONE路由策略
caseAudioSystem::RING:
caseAudioSystem::NOTIFICATION:
caseAudioSystem::ALARM:
caseAudioSystem::ENFORCED_AUDIBLE:
return STRATEGY_SONIFICATION; //SONIFICATION路由策略
caseAudioSystem::DTMF:
return STRATEGY_DTMF; //DTMF路由策略
default:
LOGE("unknown stream type");
caseAudioSystem::SYSTEM:
caseAudioSystem::TTS:
caseAudioSystem::MUSIC:
return STRATEGY_MEDIA;//media 路由策略
}
}
從這個函式中可看出,AudioSystem使用的流型別並不是和路由直接相關的,AMB或AudioPolicy內部,是使用routing_strategy來控制路由策略的。
5. 小結
這一節涉及到不少新東西,但本人覺得,最重要的還是圖7-13和圖7-14。其中:
· 圖7-13展示了智慧手機的硬體架構,通過和Audio相關的架構設計,我們能理解Audio系統設計的緣由。
· 圖7-14展示了APS和AF內部聯絡的紐帶,後續APS的控制無非就是找到對應的MixerThread,給它傳送控制訊息,最終由MixerThread將控制資訊傳給對應的代表音訊輸出裝置的HAL物件。
7.4.2 重回AudioTrack
按照前文所介紹的內容可知,AudioTrack在呼叫createTrack時,會傳入一個audio_handle_t,這個值表示AF中某個工作執行緒的索引號,而它又是從APS中得到的。那麼,這中間又有哪些曲折的經歷呢?
先回顧一下AudioTrack的set函式。
1. 重回set
先來看相應的程式碼,如下所示:
[--->AudioTrack.cpp]
status_t AudioTrack::set(int streamType,uint32_tsampleRate,int format,
int channels,intframeCount,uint32_t flags,
callback_t cbf,void*user,int notificationFrames,
constsp<IMemory>& sharedBuffer, bool threadCanCallJava)
{
......
//得到AF中一個工作執行緒的索引號
audio_io_handle_toutput = AudioSystem::getOutput(
(AudioSystem::stream_type)streamType,
sampleRate,format, channels,
(AudioSystem::output_flags)flags);
......
//建立Track,最終會調到AF的createTrack
status_t status = createTrack(streamType,sampleRate, format, channelCount,
frameCount,flags, sharedBuffer, output);
再看AudioSystem是如何實現getOutput的,程式碼如下所示:
[-->AudioSystem.cpp]
audio_io_handle_tAudioSystem::getOutput(stream_type stream,
uint32_tsamplingRate,
uint32_tformat,
uint32_tchannels,
output_flagsflags)
{
audio_io_handle_t output = 0;
......
if(output == 0) {
const sp<IAudioPolicyService>& aps =
AudioSystem::get_audio_policy_service();
if(aps == 0) return 0;
//呼叫AP的getOutput函式
output = aps->getOutput(stream, samplingRate, format, channels,flags);
if((flags & AudioSystem::OUTPUT_FLAG_DIRECT) == 0) {
Mutex::Autolock _l(gLock);
//把這個stream和output的對應關係儲存到map中
AudioSystem::gStreamOutputMap.add(stream, output);
}
}
returnoutput;
}
這裡呼叫了AP的getOutput,來看:
[-->AudioPolicyService.cpp]
audio_io_handle_t AudioPolicyService::getOutput(
AudioSystem::stream_typestream, uint32_t samplingRate,
uint32_tformat,uint32_t channels,
AudioSystem::output_flagsflags)
{
//和硬體廠商的實現相關,所以交給AudioPolicyInterface處理
//這裡將由AudioPolicyManagerBase處理
Mutex::Autolock _l(mLock);
returnmpPolicyManager->getOutput(stream, samplingRate, format, channels,
flags);
}
[->AudioPolicyManagerBase.cpp]
audio_io_handle_tAudioPolicyManagerBase::getOutput(
AudioSystem::stream_typestream, uint32_t samplingRate,
uint32_t format,uint32_tchannels,
AudioSystem::output_flagsflags)
{
audio_io_handle_t output = 0;
uint32_tlatency = 0;
//根據流型別得到對應的路由策略,這個我們已經見過了,MUSIC型別返回MUSIC策略
routing_strategystrategy = getStrategy((AudioSystem::stream_type)stream);
//根據策略得到使用這個策略的輸出裝置(指揚聲器之類的),以後再看這個函式
uint32_tdevice = getDeviceForStrategy(strategy);
......
//看這個裝置是不是與藍芽的A2DP相關
uint32_ta2dpDevice = device & AudioSystem::DEVICE_OUT_ALL_A2DP;
if(AudioSystem::popCount((AudioSystem::audio_devices)device) == 2) {
#ifdef WITH_A2DP
//對於有A2DP支援,a2dpUsedForSonification函式直接返回true
if (a2dpUsedForSonification() &&a2dpDevice != 0) {
//和DuplicatingThread相關,以後再看
output = mDuplicatedOutput;
} else
#endif
{
output = mHardwareOutput; //使用非藍芽的混音輸出執行緒
}
} else{
#ifdef WITH_A2DP
if(a2dpDevice != 0) {
//使用藍芽的混音輸出執行緒
output = mA2dpOutput;
}else
#endif
{
output = mHardwareOutput;
}
}
returnoutput;
}
終於明白了!原來,AudioSystem的getOutput就是想找到AF中的一個工作執行緒。為什麼這個執行緒號會由AP返回呢?是因為Audio系統需要:
· 根據流型別找到對應的路由策略。
· 根據該策略找到合適的輸出device(指揚聲器、聽筒之類的)。
· 根據device選擇AF中合適的工作執行緒,例如是藍芽的MixerThread,還是DSP的MixerThread,或者是DuplicatingThread。
· AT根據得到的工作執行緒索引號,最終將在對應的工作執行緒中建立一個Track。之後,AT的資料將由該執行緒負責處理。
下面用圖7-15來回顧一下上面AT、AF、AP之間的互動關係。
圖7-15 Audio三巨頭的互動關係
圖7-15充分展示了AT、AF和AP之間複雜微妙的關係。關係雖複雜,但目的卻單純。讀者在分析時一定要明確目的。下面從目的開始,反推該流程:
· AT的目的是把資料傳送給對應的裝置,例如是藍芽、DSP等。
· 代表輸出裝置的HAL物件由MixerThread執行緒持有,所以要找到對應的MixerThread。
· AP維護流型別和輸出裝置(耳機、藍芽耳機、聽筒等)之間的關係,不同的輸出裝置使用不同的混音執行緒。
· AT根據自己的流型別,向AudioSystem查詢,希望得到對應的混音執行緒號。
這樣,三者精妙配合,便達到了預期目的。
2. 重回start
現在要分析的就是start函式。AT的start雖沒有直接與AP互動,但在AF的start中卻和AP有著互動關係。其程式碼如下所示:
[-->AudioFlinger.cpp]
status_tAudioFlinger::PlaybackThread::Track::start()
{
status_t status = NO_ERROR;
sp<ThreadBase> thread = mThread.promote();
......
if(!isOutputTrack() && state != ACTIVE && state != RESUMING) {
thread->mLock.unlock();
//呼叫AudioSystem的startOutput
status = AudioSystem::startOutput(thread->id(),
(AudioSystem::stream_type)mStreamType);
thread->mLock.lock();
}
PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
playbackThread->addTrack_l(this);//把這個Track加入到活躍Track陣列中
returnstatus;
}
下面來看AudioSystem的startOutput,程式碼如下所示:
[-->AudioSystem.cpp]
status_tAudioSystem::startOutput(audio_io_handle_t output,
AudioSystem::stream_typestream)
{
constsp<IAudioPolicyService>& aps =
AudioSystem::get_audio_policy_service();
if (aps== 0) return PERMISSION_DENIED;
//呼叫AP的startOutput,最終由AMB完成實際功能
returnaps->startOutput(output, stream);
}
[-->AudioPolicyManagerBase.cpp]
status_tAudioPolicyManagerBase::startOutput(audio_io_handle_t output,
AudioSystem::stream_typestream)
{
//根據output找到對應的AudioOutputDescriptor
ssize_t index = mOutputs.indexOfKey(output);
AudioOutputDescriptor*outputDesc = mOutputs.valueAt(index);
//找到對應流使用的路由策略
routing_strategy strategy =getStrategy((AudioSystem::stream_type)stream);
//增加outputDesc中該流的使用計數,1表示增加1
outputDesc->changeRefCount(stream, 1);
//getNewDevice將得到一個裝置,setOutputDevice將使用這個裝置進行路由切換。
//至於setOutputDevice,我們在分析耳機插入事件時再來講解
setOutputDevice(output, getNewDevice(output));
//設定音量,讀者可自行分析
checkAndSetVolume(stream,mStreams[stream].mIndexCur, output,
outputDesc->device());
returnNO_ERROR;
}
再看getNewDevice,它和音訊流的使用計數有關係:
[-->AudioPolicyManagerBase.cpp]
uint32_tAudioPolicyManagerBase::getNewDevice(audio_io_handle_t output,
bool fromCache)
{
uint32_t device = 0;
AudioOutputDescriptor*outputDesc = mOutputs.valueFor(output);
/*
isUsedByStrategy判斷某個策略是否正在被使用,之前曾通過changeRefCount為
MUSIC流使用計數增加了1,所以使用MUSIC策略的個數至少為1,這表明,此裝置正在使用該策略。
一旦得到當前outputDesc使用的策略,便可根據該策略找到對應的裝置。
注意if和else的順序,它代表了系統優先使用的策略,以第一個判斷為例,
假設系統已經插上耳機,並且處於通話狀態時,而且強制使用了揚聲器,那麼聲音都從揚聲器出。
這時,如果想聽音樂的話,則應首先使用STRATEGY_PHONE的對應裝置,此時就是揚聲器。
所以音樂將從揚聲器出來,而不是耳機。上面僅是舉例,具體的情況還要綜合考慮Audio
系統中的其他資訊。另外如果fromCache為true,將直接從內部儲存的舊資訊中得到裝置,
關於這個問題,在後面的耳機插入事件處理中再做分析
*/
if(mPhoneState == AudioSystem::MODE_IN_CALL ||
outputDesc->isUsedByStrategy(STRATEGY_PHONE)) {
device = getDeviceForStrategy(STRATEGY_PHONE, fromCache);
} elseif (outputDesc->isUsedByStrategy(STRATEGY_SONIFICATION)) {
device = getDeviceForStrategy(STRATEGY_SONIFICATION, fromCache);
} elseif (outputDesc->isUsedByStrategy(STRATEGY_MEDIA)) {
device = getDeviceForStrategy(STRATEGY_MEDIA, fromCache);
} elseif (outputDesc->isUsedByStrategy(STRATEGY_DTMF)) {
device = getDeviceForStrategy(STRATEGY_DTMF, fromCache);
}
returndevice;
}
這裡,有一個問題需要關注:
· 為什麼startOutput函式會和裝置切換有關係呢?
僅舉一個例子,幫助理解這一問題。AudioTrack建立時可設定音訊流型別,假設第一個AT建立時使用的是MUSIC型別,那麼它將使用耳機出聲(假設耳機已經連線上)。這時第二個AT建立了,它使用的是RING型別,它對應的策略應是SONIFACATION,這個策略的優先順序比MUSIC要高(因為getNewDevice的判斷語句首先會判斷isUsedByStrategy(STRATEGY_SONIFICATION)),所以這時需要把裝置切換為耳機加揚聲器(假設這種型別的聲音需要從耳機和揚聲器同時輸出)。startOutput的最終結果,是這兩路的Track聲音都將從耳機和揚聲器中聽到。當第二路AT呼叫stop時,對應音訊流型別使用計數會減一,這會導致新的路由切換,並重新回到只有耳機的情況,這時第一路AT的聲音會恢復為只從耳機輸出。
提醒:讀者可自行分析stop的處理方式,基本上是start的逆向處理過程。
3. 本節小結
這一節主要講解了AudioTrack和AP之間的互動,總結為以下兩點:
· AT通過AP獲取AF中的工作執行緒索引號,這決定了資料傳輸的最終目標是誰,比如是音訊DSP或是藍芽。
· AT的start和stop會影響Audio系統的路由切換。
讀完這一節,讀者可能只會對與工作執行緒索引有關的內容印象較深刻,畢竟這個決定了資料傳輸的目的地。至於與路由切換有關的知識,可能就還不太瞭解了。下面,通過分析一個應用場景來啟發、加深對它的理解。
7.4.3 聲音路由切換例項分析
路由這個詞聽上去很專業,其實它的目的很簡單,就是為DSP選擇資料出口,例如是從耳機、聽筒還是揚聲器傳出。下面分析這樣一個場景:
· 假設我們在用揚聲器聽歌,這時把耳機插上,會發生什麼呢?
1. 耳機插拔事件處理
耳機插上後,系統會發一個廣播,Java層的AudioService會接收這個廣播,其中的內部類AudioServiceBroadcastReceiver會處理該事件,處理函式是onReceive。
這段程式碼在AudioSystem.java中。一起來看:
(1)耳機插拔事件接收
看這段程式碼,如下所示:
[-->AudioSystem.java::AudioServiceBroadcastReceiver的onReceive()]
private class AudioServiceBroadcastReceiverextends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
......
//如果該事件是耳機插拔事件
elseif (action.equals(Intent.ACTION_HEADSET_PLUG)) {
//取得耳機的狀態
int state = intent.getIntExtra("state", 0);
int microphone =intent.getIntExtra("microphone", 0);
if (microphone != 0) {
//察看已連線裝置是不是已經有了耳機,耳機的裝置號為0x4,
//這個和AudioSystem.h定義的裝置號是一致的
boolean isConnected =mConnectedDevices.containsKey(
AudioSystem.DEVICE_OUT_WIRED_HEADSET);
//如果之前有耳機而現在沒有,則認為是耳機拔出事件
if (state == 0 &&isConnected) {
//設定Audio系統的裝置連線狀態,耳機為Unavailable
AudioSystem.setDeviceConnectionState(
AudioSystem.DEVICE_OUT_WIRED_HEADSET,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
"");
//從已連線裝置中去掉耳機裝置
mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
} //如果state為1,並且之前沒有耳機連線,則處理這個耳機插入事件
else if (state == 1 && !isConnected){
//設定Audio系統的裝置連線狀態,耳機為Available
AudioSystem.setDeviceConnectionState(
AudioSystem.DEVICE_OUT_WIRED_HEADSET,
AudioSystem.DEVICE_STATE_AVAILABLE,
"");
//已連線裝置中增加耳機
mConnectedDevices.put(
new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADSET),
"");
}
}
......
從上面的程式碼中可看出,不論耳機插入還是拔出,都會呼叫AudioSystem的setDeviceConnectionState函式。
(2)setDeviceConnectionState:設定裝置連線狀態
這個函式被定義為Native函式。下面是它的定義:
[-->AudioSystem.java]
publicstatic native int setDeviceConnectionState(int device, int state,
String device_address);
//注意我們傳入的引數,device為0X4表示耳機,state為1,device_address為””
該函式的Native實現,在android_media_AudioSystem.cpp中,對應函式是:
[->android_media_AudioSystem.cpp]
static int android_media_AudioSystem_setDeviceConnectionState(
JNIEnv*env, jobject thiz, jint
device,jint state, jstring device_address)
{
constchar *c_address = env->GetStringUTFChars(device_address, NULL);
intstatus = check_AudioSystem_Command(
//呼叫Native AudioSystem的setDeviceConnectionState
AudioSystem::setDeviceConnectionState(
static_cast<AudioSystem::audio_devices>(device),
static_cast<AudioSystem::device_connection_state>(state),
c_address));
env->ReleaseStringUTFChars(device_address, c_address);
returnstatus;
}
從AudioSystem.java轉入到AudioSystem.cpp,現在來看Native的對應函式:
[-->AudioSystem.cpp]
status_tAudioSystem::setDeviceConnectionState(audio_devices device,
device_connection_state state,
const char *device_address)
{
constsp<IAudioPolicyService>& aps =
AudioSystem::get_audio_policy_service();
if(aps == 0) return PERMISSION_DENIED;
//轉到AP去,最終由AMB處理
returnaps->setDeviceConnectionState(device, state, device_address);
}
Audio程式碼不厭其煩地把函式呼叫從這一類轉移到另外一類,請直接看AMB的實現:
[-->AudioPolicyManagerBase.cpp]
status_tAudioPolicyManagerBase::setDeviceConnectionState(
AudioSystem::audio_devicesdevice,
AudioSystem::device_connection_statestate,
const char *device_address)
{
//一次只能設定一個裝置
if(AudioSystem::popCount(device) != 1) return BAD_VALUE;
......
//根據裝置號判斷是不是輸出裝置,耳機肯定屬於輸出裝置
if(AudioSystem::isOutputDevice(device)) {
switch (state)
{
case AudioSystem::DEVICE_STATE_AVAILABLE:
//處理耳機插入事件,mAvailableOutputDevices儲存已連線的裝置
//這個耳機是剛連上的,所以不走下面if分支
if (mAvailableOutputDevices & device) {
//啟用過了,就不再啟用了。
return INVALID_OPERATION;
}
//現在已連線裝置中多了一個耳機
mAvailableOutputDevices |= device;
....
}
//① getNewDevice之前已分析過了,這次再看
uint32_t newDevice =getNewDevice(mHardwareOutput, false);
//②更新各種策略使用的裝置
updateDeviceForStrategy();
//③設定新的輸出裝置
setOutputDevice(mHardwareOutput,newDevice);
......
}
這裡面有三個比較重要的函式,前面也已提過,現將其再進行一次較深入的分析,旨在加深讀者對它的理解。
(3)getNewDevice
來看程式碼,如下所示:
[->AudioPolicyManagerBase.cpp]
uint32_tAudioPolicyManagerBase::getNewDevice(audio_io_handle_t output,
bool fromCache)
{ //注意我們傳入的引數,output為mHardwardOutput,fromCache為false
uint32_tdevice = 0;
//根據output找到對應的AudioOutputDescriptor,這個物件儲存了一些資訊
AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output);
if(mPhoneState == AudioSystem::MODE_IN_CALL ||
outputDesc->isUsedByStrategy(STRATEGY_PHONE))
{
device = getDeviceForStrategy(STRATEGY_PHONE, fromCache);
}
elseif (outputDesc->isUsedByStrategy(STRATEGY_SONIFICATION))
{
device = getDeviceForStrategy(STRATEGY_SONIFICATION, fromCache);
}
elseif (outputDesc->isUsedByStrategy(STRATEGY_MEDIA))
{
//應用場景是正在聽歌,所以會走這個分支
device= getDeviceForStrategy(STRATEGY_MEDIA, fromCache);
}
elseif (outputDesc->isUsedByStrategy(STRATEGY_DTMF))
{
device = getDeviceForStrategy(STRATEGY_DTMF, fromCache);
}
return device;
}
策略是怎麼和裝置聯絡起來的呢?祕密就在getDeviceForStrategy中,來看:
[-->AudioPolicyManagerBase.cpp]
uint32_tAudioPolicyManagerBase::getDeviceForStrategy(
routing_strategystrategy, bool fromCache)
{
uint32_t device = 0;
if (fromCache){//如果為true,則直接取之前的舊值
return mDeviceForStrategy[strategy];
}
//如果fromCache為false,則需要重新計算策略所對應的裝置
switch(strategy) {
caseSTRATEGY_DTMF://先處理DTMF策略的情況
if(mPhoneState != AudioSystem::MODE_IN_CALL) {
//如果不處於電話狀態,則DTMF的策略和MEDIA策略對應同一個裝置
device = getDeviceForStrategy(STRATEGY_MEDIA, false);
break;
}
//如果處於電話狀態,則DTMF策略和PHONE策略用同一個裝置
caseSTRATEGY_PHONE:
//是PHONE策略的時候,先要考慮是不是使用者強制使用了某個裝置,例如強制使用揚聲器
switch (mForceUse[AudioSystem::FOR_COMMUNICATION]) {
......
case AudioSystem::FORCE_SPEAKER:
...... //如果沒有藍芽,則選擇揚聲器
device = mAvailableOutputDevices &
AudioSystem::DEVICE_OUT_SPEAKER;
break;
}
break;
caseSTRATEGY_SONIFICATION://SONIFICATION策略
if(mPhoneState == AudioSystem::MODE_IN_CALL) {
/*
如果處於來電狀態,則和PHONE策略用同一個裝置。例如通話過程中我們強制使用
揚聲器,那麼這個時候按撥號鍵,則按鍵聲也會從揚聲器出來
*/
device = getDeviceForStrategy(STRATEGY_PHONE, false);
break;
}
device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER;
//如果不處於電話狀態,則SONIFICATION和MEDIA策略用同一個裝置
case STRATEGY_MEDIA: {
//AUX_DIGITAL值為0x400,耳機不滿足該條件
uint32_t device2 = mAvailableOutputDevices &
AudioSystem::DEVICE_OUT_AUX_DIGITAL;
if(device2 == 0) {
//也不滿足WIRED_HEADPHONE條件
device2 = mAvailableOutputDevices &
AudioSystem::DEVICE_OUT_WIRED_HEADPHONE;
}
if(device2 == 0) {
//滿足這個條件,所以device2為0x4,WIRED_HEADSET
device2 = mAvailableOutputDevices &
AudioSystem::DEVICE_OUT_WIRED_HEADSET;
}
if(device2 == 0) {
device2 = mAvailableOutputDevices &
AudioSystem::DEVICE_OUT_SPEAKER;
}
device |= device2; //最終device為0x4,WIRED_HEADSET
}break;
default:
break;
}
returndevice;
}
getDeviceForStrategy是一個比較複雜的函式。它的複雜,在於選取裝置時,需考慮很多情況。簡單的分析僅能和讀者一起領略一下它的風采,在實際工作中反覆琢磨,或許才能掌握其中的奧妙。
好,getNewDevice將返回耳機的裝置號0x4。下一個函式是updateDeviceForStrategy。這個函式和getNewDevice沒有什麼關係,因為它沒用到getNewDevice的返回值。
(4)updateDeviceForStrategy
同樣是來看相應的程式碼,如下所示:
[-->AudioPolicyManagerBase.cpp]
voidAudioPolicyManagerBase::updateDeviceForStrategy()
{
for(int i = 0; i < NUM_STRATEGIES; i++) {
//重新計算每種策略使用的裝置,並儲存到mDeviceForStrategy中,起到了cache的作用
mDeviceForStrategy[i] =
getDeviceForStrategy((routing_strategy)i,false);
}
}
updateDeviceForStrategy會重新計算每種策略對應的裝置。
另外,如果updateDeviceForStrategy和getNewDevice互換位置,就會節省很多不必要的呼叫。如:
updateDevicdForStrategy();//先更新策略
//使用cache中的裝置,節省一次重新計算
uint32_t newDevice =getNewDevice(mHardwareOutput, true);
OK,不必討論這位碼農的功過了,現在看最後一個函式setOutputDevice。它會對新選出來的裝置做如何處理呢?
(5)setOutputDevice
繼續看setOutputDevice的程式碼,如下所示:
[-->AudioPolicyManagerBase.cpp]
void AudioPolicyManagerBase::setOutputDevice(audio_io_handle_toutput,
uint32_t device,bool force, int delayMs)
{
......
//把這個請求要傳送到output對應的AF工作執行緒中
AudioParameterparam = AudioParameter();
//引數是key/vlaue鍵值對的格式
param.addInt(String8(AudioParameter::keyRouting),(int)device);
//mpClientInterface是AP物件,由它處理
mpClientInterface->setParameters(mHardwareOutput,
param.toString(),delayMs);
//設定音量,不做討論,讀者可自行分析
applyStreamVolumes(output, device, delayMs);
}
setParameters最終會呼叫APS的setParameters,程式碼如下所示:
[-->AudioPolicyService.cpp]
voidAudioPolicyService::setParameters(audio_io_handle_t ioHandle,
constString8& keyValuePairs, int delayMs)
{
//把這個請求加入到AudioCommandThread處理
mAudioCommandThread->parametersCommand((int)ioHandle,
keyValuePairs, delayMs);
}
AudioPolicyService建立時會同時建立兩個執行緒,其中一個用於處理各種請求。現在看看它是怎麼做的。
2. AudioCommandThread
AudioCommandThread有一個請求處理佇列,AP負責往該佇列中提交請求,而AudioCommandThread在它的執行緒函式threadLoop中處理這些命令。請直接看命令是如何處理的。
說明:這種通過一個佇列來協調兩個執行緒的方法,在多執行緒程式設計中非常常見,它也屬於生產者/消費者模型。
(1)AudioCommandThread中的處理
[-->AudioPolicyService.cpp]
boolAudioPolicyService::AudioCommandThread::threadLoop()
{
nsecs_twaitTime = INT64_MAX;
mLock.lock();
while(!exitPending())
{
while(!mAudioCommands.isEmpty()) {
nsecs_t curTime = systemTime();
if (mAudioCommands[0]->mTime <= curTime) {
AudioCommand *command = mAudioCommands[0];
mAudioCommands.removeAt(0);
mLastCommand = *command;
switch (command->mCommand) {
case START_TONE:
......
case STOP_TONE:
...... //TONE處理
mLock.lock();
}break;
case SET_VOLUME: {
//設定音量
delete data;
}break;
case SET_PARAMETERS: {
//處理路由設定請求
ParametersData *data =(ParametersData *)command->mParam;
//轉到AudioSystem處理,mIO的值為mHardwareOutput
command->mStatus =AudioSystem::setParameters(
data->mIO,
data->mKeyValuePairs);
if(command->mWaitStatus) {
command->mCond.signal();
mWaitWorkCV.wait(mLock);
}
delete data;
}break;
......
default:
}
}
Audio系統真是非常繞!先看AudioSystem的setParameters。
(2)AudioSystem的setParameters
AudioSystem將設定請求轉移給AudioFlinger處理,程式碼如下所示:
[-->AudioSystem.cpp]
status_tAudioSystem::setParameters(audio_io_handle_t ioHandle,
constString8& keyValuePairs)
{
constsp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
//果然是交給AF處理,ioHandle看來一定就是工作執行緒索引號了
returnaf->setParameters(ioHandle, keyValuePairs);
}
離真相越來越近了,接著看程式碼,如下所示:
[-->AudioFlinger.cpp]
status_t AudioFlinger::setParameters(intioHandle,
constString8& keyValuePairs)
{
status_t result;
// ioHandle == 0 表示和混音執行緒無關,需要直接設定到HAL物件中。
if(ioHandle == 0) {
AutoMutex lock(mHardwareLock);
mHardwareStatus = AUDIO_SET_PARAMETER;
//呼叫AudioHardwareInterface的引數設定介面
result = mAudioHardware->setParameters(keyValuePairs);
mHardwareStatus = AUDIO_HW_IDLE;
return result;
}
sp<ThreadBase> thread;
{
Mutex::Autolock _l(mLock);
//根據索引號找到對應混音執行緒。
thread = checkPlaybackThread_l(ioHandle);
}
//我們只有一個MixerThread,交給它處理,這又是一個命令處理佇列
result = thread->setParameters(keyValuePairs);
returnresult;
}
returnBAD_VALUE;
}
好了,最終的請求處理在MixerThread的執行緒函式中,來看:
(3)MixerThread最終處理
程式碼如下所示:
[-->AudioFlinger.cpp]
bool AudioFlinger::MixerThread::threadLoop()
{
....
while(!exitPending())
{
processConfigEvents();
mixerStatus = MIXER_IDLE;
{// scope for mLock
Mutex::Autolock _l(mLock);
// checkForNewParameters_l最有嫌疑
if (checkForNewParameters_l()) {
...
}
......//其他處理
}
[-->AudioFlinger.cpp]
boolAudioFlinger::MixerThread::checkForNewParameters_l()
{
boolreconfig = false;
while(!mNewParameters.isEmpty()) {
status_t status = NO_ERROR;
String8 keyValuePair = mNewParameters[0];
AudioParameter param = AudioParameter(keyValuePair);
int value;
......
//路由設定需要硬體參與,所以直接交給代表音訊輸出裝置的HAL物件處理
status = mOutput->setParameters(keyValuePair);
return reconfig;
}
至此,路由設定所經歷的一切軌跡,我們都已清晰地看到了,可總還有點意猶未盡的感覺,HAL的setParameters到底是怎麼工作的呢?不妨再來看一個實際的HAL物件處理例子。
(4)真實裝置的處理
這個實際的Hardware,位於hardware/msm7k/libaudio-qsd8k的Hardware.cpp中,它提供了一個實際的音訊處理例子,這個Hardware針對的是高通公司的硬體。直接看它是怎麼處理音訊輸出物件setParameters的,程式碼如下所示:
[-->AudioHardware.cppAudioStreamOutMSM72xx::setParameters()]
status_tAudioHardware::AudioStreamOutMSM72xx::setParameters(
const String8& keyValuePairs)
{
AudioParameter param = AudioParameter(keyValuePairs);
String8 key = String8(AudioParameter::keyRouting);
status_tstatus = NO_ERROR;
intdevice;
if(param.getInt(key, device) == NO_ERROR) {
mDevices = device;
//呼叫doRouting,mHardware就是AudioHardware物件
status = mHardware->doRouting();
param.remove(key);
}
......
returnstatus;
}
[-->AudioHardware.cpp]
status_t AudioHardware::doRouting()
{
Mutex::Autolock lock(mLock);
uint32_t outputDevices = mOutput->devices();
status_t ret = NO_ERROR;
intsndDevice = -1;
......
//做一些判斷,最終由doAudioRouteOrMute處理
if((vr_mode_change) || (sndDevice != -1 && sndDevice != mCurSndDevice)) {
ret = doAudioRouteOrMute(sndDevice);
mCurSndDevice = sndDevice;
}
returnret;
}
[-->AudioHardware.cpp]
status_t AudioHardware::doAudioRouteOrMute(uint32_tdevice)
{
uint32_t rx_acdb_id = 0;
uint32_t tx_acdb_id = 0;
//只看看就行,對應硬體相關的程式碼,我們們就是打打醬油
returndo_route_audio_dev_ctrl(device,
mMode== AudioSystem::MODE_IN_CALL, rx_acdb_id, tx_acdb_id);
}
[-->AudioHardware.cpp]
static status_t do_route_audio_dev_ctrl(uint32_tdevice, bool inCall,
uint32_t rx_acdb_id, uint32_t tx_acdb_id)
{
uint32_t out_device = 0, mic_device = 0;
uint32_t path[2];
int fd= 0;
//開啟音訊控制裝置
fd =open("/dev/msm_audio_ctl", O_RDWR);
path[0]= out_device;
path[1]= rx_acdb_id;
//通過ioctl切換裝置,一般系統呼叫都是返回-1表示出錯,這裡返回0表示出錯
if(ioctl(fd, AUDIO_SWITCH_DEVICE, &path)) {
close(fd);
return -1;
}
......
}
7.4.4 AudioPolicy總結
AudioPolicy是Audio系統中最難懂的內容,重要原因之一是,它不像AT和AF那樣有比較固定的工作流程,所以對它的把握和理解,一定要結合具體的使用場景,尤其是路由切換這一塊,涉及很多方面的知識,如音訊流型別、當前可用裝置等。本人希望讀者至少要理解以下兩點:
· AP和AF的關係,關於這一部分的內容,讀者應徹底弄懂圖7-13。
· 關於裝置連線導致的路由切換處理,讀者要理解這期間的處理流程。
7.5 擴充思考
7.5.1 DuplicatingThread破解
DuplicatingThread需要與藍芽結合起來使用,它的存在與Audio硬體結構息息相關。讀者可參考圖7-12“智慧手機硬體架構圖”來理解。當一份資料同時需要傳送給DSP和藍芽A2DP裝置時,DuplicatingThread就派上用場了。在分析DuplicatingThread前,還是應該瞭解一下它的來龍去脈。
1. DuplicatingThread的來歷
DuplicatingThread和藍芽的A2DP裝置有關係。可先假設有一個藍芽立體聲耳機已經連線上了,接著從setDeviceConnectionState開始分析,程式碼如下所示:
[-->AudioPolicyManagerBase.cpp]
status_t AudioPolicyManagerBase::setDeviceConnectionState(
AudioSystem::audio_devicesdevice,
AudioSystem::device_connection_state state,
const char *device_address)
{
......
switch (state)
{
case AudioSystem::DEVICE_STATE_AVAILABLE:
mAvailableOutputDevices |= device;
#ifdef WITH_A2DP
if (AudioSystem::isA2dpDevice(device)) {
//專門處理A2DP裝置的連線
status_t status = handleA2dpConnection(device, device_address);
}
#endif
......
對於A2DP裝置,有專門的函式handleA2dpConnection處理,程式碼如下所示:
[-->AudioPolicyManagerBase.cpp]
status_tAudioPolicyManagerBase::handleA2dpConnection(
AudioSystem::audio_devicesdevice,
const char*device_address)
{
AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
outputDesc->mDevice= device;
//先為mA2dpOutput建立一個MixerThread,這個和mHardwareOutput一樣
mA2dpOutput =mpClientInterface->openOutput(&outputDesc->mDevice,
&outputDesc->mSamplingRate,
&outputDesc->mFormat,
&outputDesc->mChannels,
&outputDesc->mLatency,
outputDesc->mFlags);
if (mA2dpOutput) {
/*
a2dpUsedForSonification永遠返回true,表示屬於SONIFCATION策略的音訊流聲音需要
同時從藍芽和DSP中傳出。屬於SONIFCATION策略的音訊流型別可檢視前面關於getStrategy的
分析,來電鈴聲、簡訊通知等屬於這一類
*/
if(a2dpUsedForSonification()) {
/*
建立一個DuplicateOutput,注意它的引數,第一個是藍芽MixerThread
第二個是DSPMixerThread
*/
mDuplicatedOutput = mpClientInterface->openDuplicateOutput(
mA2dpOutput, mHardwareOutput);
}
if(mDuplicatedOutput != 0 ||
!a2dpUsedForSonification()) {
if (a2dpUsedForSonification()) {
//建立一個AudioOutputDescriptor物件
AudioOutputDescriptor *dupOutputDesc = new
AudioOutputDescriptor();
dupOutputDesc->mOutput1 = mOutputs.valueFor(mHardwareOutput);
dupOutputDesc->mOutput2 = mOutputs.valueFor(mA2dpOutput);
......
//儲存mDuplicatedOutput和dupOutputDesc鍵值對
addOutput(mDuplicatedOutput, dupOutputDesc);
......
}
}
}
......
這裡,最重要的函式是openDuplicateOutput。它和openOutput一樣,最終的處理都是在AF中。去那裡看看,程式碼如下所示:
[-->AudioFlinger.cpp]
int AudioFlinger::openDuplicateOutput(intoutput1, int output2)
{
Mutex::Autolock_l(mLock);
//output1對應藍芽的MixerThread
MixerThread*thread1 = checkMixerThread_l(output1);
//output2對應DSP的MixerThread
MixerThread *thread2 = checkMixerThread_l(output2);
//①建立DuplicatingThread,注意它第二個引數使用的,是代表藍芽的MixerThread
DuplicatingThread *thread = new DuplicatingThread(this,
thread1,++mNextThreadId);
//②加入代表DSP的MixerThread
thread->addOutputTrack(thread2);
mPlaybackThreads.add(mNextThreadId, thread);
returnmNextThreadId;//返回DuplicatingThread的索引
}
從現在起,MixerThread要簡寫為MT,而DuplicatingThread則簡寫為DT。
OK,這裡面有兩個重要的函式呼叫,一起來看。
2. DuplicatingThread和OutputTrack
先看DT的建構函式,程式碼如下所示:
[-->AudioFlinger.cpp]
AudioFlinger::DuplicatingThread::DuplicatingThread(constsp<AudioFlinger>&
audioFlinger, AudioFlinger::MixerThread*mainThread,int id)
: MixerThread(audioFlinger,mainThread->getOutput(), id),
mWaitTimeMs(UINT_MAX)
{
//DT是MT的派生類,所以先要完成基類的構造,還記得MT的構造嗎?它會建立一個AudioMixer物件
mType =PlaybackThread::DUPLICATING;
//把代表DSP的MT加入進來,我們們看看
addOutputTrack(mainThread);
}
[-->AudioFlinger.cpp]
voidAudioFlinger::DuplicatingThread::addOutputTrack(MixerThread *thread)
{
intframeCount = (3 * mFrameCount * mSampleRate) / thread->sampleRate();
//構造一個OutputTrack,它的第一個引數是MT
OutputTrack *outputTrack = new OutputTrack((ThreadBase *)thread,
this, mSampleRate, mFormat,
mChannelCount,frameCount);
if(outputTrack->cblk() != NULL) {
thread->setStreamVolume(AudioSystem::NUM_STREAM_TYPES, 1.0f);
//把這個outputTrack加入到mOutputTracks陣列儲存
mOutputTracks.add(outputTrack);
updateWaitTime();
}
}
此時,當下面兩句程式碼執行完:
DuplicatingThread *thread = newDuplicatingThread(this,
thread1,++mNextThreadId);
thread->addOutputTrack(thread2);
DT分別構造了兩個OutputTrack,一個對應藍芽的MT,一個對應DSP的MT。現在來看OutputTrack為何方神聖,程式碼如下所示:
[-->AudioFlinger.cpp]
AudioFlinger::PlaybackThread::OutputTrack::OutputTrack(
const wp<ThreadBase>& thread, DuplicatingThread*sourceThread,
uint32_t sampleRate, int format,int channelCount,int frameCount)
:Track(thread,NULL, AudioSystem::NUM_STREAM_TYPES, sampleRate,
format, channelCount, frameCount, NULL),//最後這個引數為NULL
mActive(false),mSourceThread(sourceThread)
{
/*
OutputTrack從Track派生,所以需要先呼叫基類的構造,還記得Track建構函式
中的事情嗎?它會建立一塊記憶體,至於是不是共享記憶體,由Track建構函式的最後一個引數決定。
如果該值為NULL,表示沒有客戶端參與,則會在本程式內建立一塊記憶體,這塊記憶體的結構如
圖7-4所示,前邊為CB物件,後邊為資料緩衝
*/
//下面的這個thread物件為MT
PlaybackThread *playbackThread = (PlaybackThread *)thread.unsafe_get();
if(mCblk != NULL) {
mCblk->out = 1;//表示DT將往MT中寫資料
//和前面所分析的AT、AF中的處理何其相似!
mCblk->buffers = (char*)mCblk + sizeof(audio_track_cblk_t);
mCblk->volume[0] = mCblk->volume[1] = 0x1000;
mOutBuffer.frameCount = 0;
//把這個Track加到MT的Track中
playbackThread->mTracks.add(this);
}
明白了嗎?圖7-16表示的是openDuplicateOutput的結果:
圖7-16 openDuplicateOutput的結果示意圖
圖7-16說明(以藍芽MT為例):
· 藍芽MT的Track中有一個成員為OutputTrack0。
· DT的mOutputTracks也有一個成員指向OutputTrack0。這就好像DT是MT的客戶端一樣,它和前面分析的AT是AF的客戶端類似。
· 紅色部分代表資料傳遞用的緩衝。
3. DT的客戶端AT
DT是從MT中派生的,根據AP和AT的互動流程,當AT建立的流型別對應策略為SONIFACATION時,它會從AP中得到代表DT的執行緒索引號。由於DT沒有過載createTrack_l,所以這個過程也會建立一個Track物件(和MT建立Track物件一樣)。此時的結果,將導致圖7-16變成圖7-17。
圖7-17 有AT的DT全景圖
圖7-17把DT的工作方式表達得非常清晰了。一個DT配合兩個OutputTrack中的程式內緩衝,把來自AT的資料原封不動地發給藍芽MT和DSP MT,這簡直就是個資料中繼器!。不過俗話說得好,道理雖簡單,實現卻複雜。來看DT是如何完成這一複雜而艱鉅的任務的吧。
4. DT的執行緒函式
DT的執行緒函式程式碼如下所示:
[-->AudioFlinger.cpp]
boolAudioFlinger::DuplicatingThread::threadLoop()
{
int16_t* curBuf = mMixBuffer;
Vector< sp<Track> > tracksToRemove;
uint32_t mixerStatus = MIXER_IDLE;
nsecs_t standbyTime = systemTime();
size_tmixBufferSize = mFrameCount*mFrameSize;
SortedVector< sp<OutputTrack> > outputTracks;
while(!exitPending())
{
processConfigEvents();
mixerStatus = MIXER_IDLE;
{
......
//處理配置請求,和MT處理一樣
const SortedVector< wp<Track> >& activeTracks =mActiveTracks;
for (size_t i = 0; i < mOutputTracks.size(); i++) {
outputTracks.add(mOutputTracks[i]);
}
//如果AT的Track停止了,則需要停止和MT共享的OutputTrack
ifUNLIKELY((!activeTracks.size() && systemTime() > standbyTime)
|| mSuspended) {
if (!mStandby) {
for (size_t i = 0; i <outputTracks.size(); i++) {
outputTracks[i]->stop();
}
mStandby = true;
mBytesWritten = 0;
}
......
//DT從MT派生,天然具有混音的功能,所以這部分功能和MT一致
mixerStatus = prepareTracks_l(activeTracks, &tracksToRemove);
}
if(LIKELY(mixerStatus == MIXER_TRACKS_READY)) {
//outputsReady將檢查OutputTracks對應的MT狀態
if (outputsReady(outputTracks)) {
mAudioMixer->process(curBuf);//使用AudioMixer物件混音
} else {
memset(curBuf, 0, mixBufferSize);
}
sleepTime = 0;
writeFrames = mFrameCount;
}
......
if (sleepTime == 0) {
standbyTime = systemTime() +kStandbyTimeInNsecs;
for (size_t i = 0; i < outputTracks.size(); i++) {
//將混音後的資料寫到outputTrack中
outputTracks[i]->write(curBuf, writeFrames);
}
mStandby = false;
mBytesWritten += mixBufferSize;
}else {
usleep(sleepTime);
}
tracksToRemove.clear();
outputTracks.clear();
}
returnfalse;
}
現在,來自遠端程式AT的資料已得到了混音,這一份混音後的資料還將通過呼叫OutputTrack的write完成DT到其他兩個MT的傳輸。注意,這裡除了AT使用的Track外,還有DT和兩個MT共享的OutputTrack。AT呼叫的start,將導致DT的Track加入到活躍陣列中,但另外兩個OutputTrack還沒呼叫start。這些操作又是在哪裡做的呢?來看write函式:
[-->AudioFlinger.cpp]
boolAudioFlinger::PlaybackThread::OutputTrack::write(int16_t* data,
uint32_t frames)
{
//注意,此處的OutputTrack是DT和MT共享的
Buffer *pInBuffer;
BufferinBuffer;
uint32_t channels = mCblk->channels;
booloutputBufferFull = false;
inBuffer.frameCount = frames;
inBuffer.i16 = data;
uint32_t waitTimeLeftMs = mSourceThread->waitTimeMs();
if(!mActive && frames != 0) {
//如果此Track沒有活躍,則呼叫start啟用
start();
......
}
/*
現在,AF中的資料傳遞有三個執行緒:一個DT,兩個MT。MT作為DT的二級消費者,
可能由於某種原因來不及消費資料,所以DT中提供了一個緩衝佇列mBufferQueue,
把MT來不及消費的資料儲存在這個緩衝佇列中。注意這個緩衝佇列容納的臨時緩衝
個數是有限制的,其限制值由kMaxOverFlowBuffers控制,初始化為10個
*/
while(waitTimeLeftMs) {
//先消耗儲存在緩衝佇列的資料
if(mBufferQueue.size()) {
pInBuffer = mBufferQueue.itemAt(0);
}else {
pInBuffer = &inBuffer;
}
......
//獲取可寫緩衝,下面這句程式碼是否和AT中對應的程式碼很相似?
if(obtainBuffer(&mOutBuffer, waitTimeLeftMs) ==
(status_t)AudioTrack::NO_MORE_BUFFERS){
......
break;
}
uint32_toutFrames = pInBuffer->frameCount > mOutBuffer.frameCount ?
mOutBuffer.frameCount: pInBuffer->frameCount;
//將資料拷貝到DT和MT共享的那塊緩衝中去
memcpy(mOutBuffer.raw, pInBuffer->raw,
outFrames * channels * sizeof(int16_t));
//更新寫位置
mCblk->stepUser(outFrames);
pInBuffer->frameCount-= outFrames;
pInBuffer->i16 += outFrames * channels;
mOutBuffer.frameCount -= outFrames;
mOutBuffer.i16 += outFrames * channels;
......
}//while 結束
if(inBuffer.frameCount) {
sp<ThreadBase> thread = mThread.promote();
if(thread != 0 && !thread->standby()) {
if (mBufferQueue.size() < kMaxOverFlowBuffers) {
pInBuffer = new Buffer;
pInBuffer->mBuffer = new int16_t[inBuffer.frameCount * channels];
pInBuffer->frameCount = inBuffer.frameCount;
pInBuffer->i16 = pInBuffer->mBuffer;
//拷貝舊資料到新的臨時緩衝
memcpy(pInBuffer->raw, inBuffer.raw,
inBuffer.frameCount *channels * sizeof(int16_t));
//儲存這個臨時緩衝
mBufferQueue.add(pInBuffer);
}
}
}
//如果資料全部寫完
if(pInBuffer->frameCount == 0) {
if (mBufferQueue.size()) {
mBufferQueue.removeAt(0);
delete [] pInBuffer->mBuffer;
delete pInBuffer;//釋放緩衝佇列對應的資料緩衝
} else {
break;
}
}
}
......
return outputBufferFull;
}
資料就這樣從AT通過DT的幫助,傳輸到藍芽的MT和DSP的MT中了。這種方式繼資料傳輸比直接使用MT傳輸要緩慢。
到這裡,對DT的講解就告一段落了。本人覺得,DT的實現是AF程式碼中最美妙的地方,多學習這些優秀程式碼,有助於提高學習者的水平。
說明:DT還有別的一些細節本書中沒有涉及,讀者可以結合自己的情況進行分析和理解。
7.5.2 題外話
下面,說一點題外話,希望藉此和讀者一起探討交流,共同進步。開源世界,開放平臺,關鍵就是能做到相容幷包。
1. CTS和單元測試
瞭解一點驅動開發的人可能都會知道,晶片的型別太多了,操作起來也不一樣。這必然導致,一個東西就得寫一套程式碼,非常繁瑣,並可能反覆創造低價值。
做為一個應用層開發者,我非常不希望的是,下層驅動的變動影響上層的應用(在工作中經常發現問題像病毒一樣隨意擴散)。當然,Android已經提供了HAL層,任何硬體廠商都需要實現這些介面。硬體廠商的這些程式碼是需要編譯成程式來進行驗證的,可我不想拿應用層程式來做測試程式。因為應用層程式有自己的複雜邏輯,可能觸發一個聲音的bug,需要滿足很多預期的條件,否則會非常影響HAL的測試。有沒有辦法解決這一問題呢?像Google這樣的公司,面臨著很多硬體廠商,它又是怎麼解決的呢?
我從CTS上看到了希望。CTS是Google為Android搞的一個相容性測試,即不管是怎麼實現硬體驅動的,反正得通過我的CTS測試。當然,CTS並不是用來測試硬體的,但是它的這種思想可以參考和借鑑。
我很羨慕iOS的應用開發者,他們面臨的由於硬體變化導致的問題要少得多。
其實,如果做驅動移植的同仁們能以測試驅動開發的態度來嚴格測試,可能我們這些上層開發人員就不會總懷疑是驅動的問題了。
2. ALSA——Advanced Linux Sound Architecture
ALSA是什麼,大夥兒可以網上google之。現在在大力推廣ALSA,但在Android這塊,我個人感覺它還不是很好用。“不好用”,是從上層使用者的角度說的,ALSA提供了一個使用者空間的libasound庫,而這個庫的確比較難用。不過有了Audio HAL的幫助,應用層就不用做改動了,但是實現HAL層的廠商要做的改動就比較大了。相比較而言,我覺得現在的原始碼中使用的open/ioctl方法更為方便。
說明:這可能和我做過的應用太簡單有一定關係(就是ffmpeg編解碼MP3,然後播出來即可)。而libasound提供的API較多,在權衡各種情況後,我覺得它不適合快速簡單應用的開發。
3. Desktop Check
Desktop Check雖然是一種行為,但我更覺得它的產生是基於了一種態度。DesktopCheck本意是桌面檢查。起因是在計算機技術剛興起時,程式設計師除錯程式碼非常費勁,因為那時機器配置很差,除錯工具也不像現在這麼發達,有時要跑到機房,預約機器然後啟動偵錯程式,所需時間遠遠多於坐在電腦前修改一個bug的時間。對於這種情況怎麼辦?為什麼不像考試那樣對自己的程式碼多檢查幾遍呢?自己虛擬一些應用場景,結合引數代入程式,在大腦中Trace豈不更好?這正是DesktopCheck行為的本意。
今天,很多開發人員不厭其煩得新增log,然後執行看輸出。當然,這是解決問題的一種比較好的辦法,但是在時間充裕的情況下,我還是希望開發人員能像我們前輩那樣,用Desktop Check的這種方式先反覆閱讀和檢查程式,爭取在大腦中模擬程式的執行,最後才用列印log的方法來驗證自己的想法。
另外,Desktop check對提升閱讀程式碼的能力有重要幫助。
說明:已記不得第一次接觸Desktop Check一詞是什麼時候了,或許當時還不叫Desktop Check,但我覺得它所蘊含的思想是正確的,是頗有價值的。
7.6 本章小結
Audio是本書碰到的第一個複雜系統,這個系統整體示意圖如圖7-18所示:
圖7-18 Audio系統大家族
從圖7-18中可以看出:
· 音訊資料的輸入輸出不論是Java層和Native層,都是通過AudioTrack和AudioRecord類完成的。事實上,Audio系統提供的I/O介面就是AudioTrack和AudioRecord類。音訊I/O是Audio系統最重要的部分。建議讀者反覆閱讀,加深理解。
· AudioManager用來做音量調節、audio模式的選擇、裝置連線控制等。這些都會和Native的AP互動。從我個人部落格和其他技術論壇的統計來看,較少有人關注AudioPolicy,畢竟在這一塊Android已提供了一個足夠好用的AudioPolicyManagerBase類。不過作為Audio系統不可或缺的一部分,AudioPolicy的重要性是不言而喻的。
建議:無論怎麼說,資料I/O畢竟是Audio系統中關鍵之關鍵,所以請讀者一定要仔細閱讀,體會其中精妙所在。
Audio系統中還有其他部分(例如AudioRecord、Java層的AudioSystem,AudioService等),本書沒有涉及。讀者可結合個人需要自行分析。在現有的基礎上,要學習,掌握這些內容都不會太難。
相關文章
- 深入理解分散式系統分散式
- 深入理解Flutter UI系統FlutterUI
- 深入理解計算機系統計算機
- Android 深入理解 Notification 機制Android
- 深入理解計算機系統:程式計算機
- 深入理解Android逆向除錯原理Android除錯
- 深入理解Android訊息機制Android
- Linux作業系統分析 | 深入理解系統呼叫Linux作業系統
- 深入理解Java反射(一)Java反射
- 推薦系統一——深入理解YouTube推薦系統演算法演算法
- 深入理解 Android 中的各種 ContextAndroidContext
- 深入理解Android 之 Activity啟動流程(Android 10)Android
- 深入理解JSXJS
- 深入理解Isolate
- 深入理解 JVMJVM
- 深入理解HashMapHashMap
- 深入理解ThreadLocalthread
- 深入理解TransformORM
- 深入理解JVMJVM
- 深入理解 SynchronizationContextContext
- 深入理解AQSAQS
- 深入理解margin
- 深入理解ReactReact
- 深入理解KVO
- 深入理解 ReentrantLockReentrantLock
- 深入理解 PWA
- 深入理解BFC
- 深入理解volatile
- 深入理解 GitGit
- 深入理解MVCMVC
- 深入理解 TypeScriptTypeScript
- 深入理解JSCoreJS
- 深入理解JavaScriptCoreJavaScript
- BFC深入理解
- 深入理解paddingpadding
- 深入理解reduxRedux
- 深入理解Plasma(一)Plasma 框架ASM框架
- 深入理解 tcp 協議(一)TCP協議
- 作業系統——深入理解程式和執行緒作業系統執行緒