在上小節中我們講解了,在AudioTrack建立過程中,他會選擇一個output,一個output對應一個播放裝置,他也對應着一個播放線程,該小節我們講解在這個線程之中,怎麼去建立一個Track,應用程式的AudioTrack與播放線程之中的Track是一一對應的,并且我們還會講解應用程式的AudioTrack與播放線程之中的Track他們之間是怎麼傳遞資料的,也就是說,他們是通過怎樣的記憶體傳遞資料的。
之前提到過,作為聲音的應用程式,他需要給playbackThread線程提供資料,他是怎麼提供的呢?APP給AudioTrack提供音頻資料有2種方式: 一次性提供(MODE_STATIC)、邊播放邊提供(MODE_STREAM)
對于簡單的提示音,其資料是十分的少的,我們可以把所有的資料,一次性提供給播放線程,那麼對于播放音樂,播放網絡傳遞過來的聲音,那麼我們需要邊播放,邊提供。
那麼有以下幾個問題:
1.音頻資料儲存在buffer中,這個buffer由誰提供呢?APP還是PlaybackTHread。
2.APP提供資料,PlaybackThread消耗資料,如何同步。
我們先來畫一個圖:
我們知道在android系統之中,其可能有多個聲霸卡,如上的聲霸卡1,聲霸卡2。每個聲霸卡對應一個output,同時每個output對應一個播放線程PlaybackThread,PlaybackThread中存在一個數組mTracks,其中包含一個或者多個Track,每一個Track都對應應用程式中建立的AudioTrack,正是因為應用程式APP建立了AudioTrack才會導緻PlaybackThread中Track的建立(注意:APP與PlaybackThread處于不同的程序)。
那麼他們是怎麼傳遞資料的呢?雖然可以使用binder通信,但是其通信的效率并不受很高,其實在聲音資料,APP與PlaybackThread聲音資料的傳遞是使用共享記憶體(即APP與PlaybackThread可以通路同一塊記憶體)。
現在我們先來解決第一個問題,這塊共享記憶體是由誰來建立的,是由APP還是由PlaybackThread呢?我們分為兩種情況
1.如果APP提供AudioTrack的資料是一次性提供(MODE_STATIC:一次性,提前提供資料),那麼顯然這個buffer當然是由應用程式提供。因為應用程式才知道這個buffer有多大。
2.如果應用程式,是一邊播放,一邊提供資料(MODE_STREAM),那麼就由PlaybackThread建立共享記憶體,因為這樣省事(為了簡單應用程式的程式設計)
那麼應用程式APP與PlaybackThread如何同步資料呢?
1.如果APP提供AudioTrack的資料是一次性提供(MODE_STATIC:一次性,提前提供資料),APP先構造,PlaybackThread再消費,則無需進行同步。
2.如果應用程式,是一邊播放,一邊提供資料(MODE_STREAM),使用環形緩沖區進行同步。
我們回答了以上問題,現在我們需要從源碼中,驗證以上結果。
先打開測試程式shared_mem_test.cpp檔案:
int AudioTrackTest::Test01() {
/*首先自己配置設定了buffer*/
iMem = heap->allocate(BUF_SZ*sizeof(short));
/*設定好資料*/
p = static_cast<uint8_t*>(iMem->pointer());
memcpy(p, smpBuf, BUF_SZ*sizeof(short));
/*建立AudioTrack,其會傳入共享記憶體iMem,iMem如果為NULL則MODE_STREAM模式,共享記憶體由PlaybackThread建立*/
sp<AudioTrack> track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type,
rate,
AUDIO_FORMAT_PCM_16_BIT,// word length, PCM
AUDIO_CHANNEL_OUT_MONO,
iMem);
我們打開AudioTrack.cpp檢視AudioTrack的構造函數:
AudioTrack::AudioTrack(
mStatus = set(streamType, sampleRate, format, channelMask.......
以上的是c++的測試程式,之前我們還提到過java的測試程式,打開MediaAudioTrackTest.java檔案,随便檢視一個函數如:
public void testSetStereoVolumeMax() throws Exception {
final int TEST_MODE = AudioTrack.MODE_STREAM;
AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, minBuffSize, TEST_MODE);
可以看到,其指定了TEST_MODE = AudioTrack.MODE_STREAM,即為一邊播放,一邊提供資料(MODE_STREAM)模式,對于C++中,我們是不需要指定的。
我們檢視java中AudioTrack的構造函數:
public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,int mode, int sessionId)
int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/);
看到native_setup,我們就知道,其會調用C++中的函數,我們在源碼中進行搜尋native_setup,可以找到android_media_AudioTrack.cpp檔案,可以找到其對應函數:
android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, jobject jaa,jintArray jSampleRate, jint channelPositionMask, jint channelIndexMask,jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession,jlong nativeAudioTrack) {
lpTrack = new AudioTrack();
case MODE_STREAM:
status = lpTrack->set()
case MODE_STATIC:
/*配置設定共享記憶體*/
if (!lpJniStorage->allocSharedMem(buffSizeInBytes)) {
status = lpTrack->set()
可以看出,如果為MODE_STREAM沒事,則APP不會建立共享記憶體,如果MODE_STATIC(一次性),則會建立共享記憶體。
其上C++與java的調用過程總結如下圖:
可以知道無論C++的AudioTrack還是javaAudioTrack最終都是建立了一個c++的AudioTrack對象,即c++的構造函數被調用,進而其中的set函數也被調用。
之前我們強調,建立AudioTrack對象,就會導緻某個PlaybackThread中建立一個Track對象,這裡截圖一下之前的時序圖:
其調用關系就不進行重複了。最後得出結論,APP建立一個AudioTrack對象,導緻PlaybackThread中new Trac被建立。打開Tracks.cpp:
AudioFlinger::PlaybackThread::Track::Track(
PlaybackThread *thread,
const sp<Client>& client,
audio_stream_type_t streamType,
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
size_t frameCount,
void *buffer,
const sp<IMemory>& sharedBuffer,//該為應用程式建立的共享記憶體
audio_session_t sessionId,
int uid,
audio_output_flags_t flags,
track_type type)
: TrackBase(thread, client, sampleRate, format, channelMask, frameCount,
(sharedBuffer != 0) ? sharedBuffer->pointer() : buffer,
sessionId, uid, true /*isOut*/,
(type == TYPE_PATCH) ? ( buffer == NULL ? ALLOC_LOCAL : ALLOC_NONE) : ALLOC_CBLK,
type),
我們可以看到sharedBuffer,其為應用程式建立的共享記憶體,其也有可能為空,我們來想象一下Track執行個體化對象中,他會做什麼事情,如果應用程式已經建構了sharedBuffer,那麼他自己則不會再去建構,如果應用程式沒有建構,即傳入的sharedBuffer為空,則其會配置設定sharedBuffer。
從上可以看出,Track由TrackBase派生出來,我們檢視一下TrackBase的構造函數,還是在Tracks.cpp檔案中:
AudioFlinger::ThreadBase::TrackBase::TrackBase(
/*如果buffer為空,并且alloc == ALLOC_CBLK*/
if (buffer == NULL && alloc == ALLOC_CBLK) {
/*size為一個頭部(起到控制作用),如果buffer為NULL,即應用程式沒有配置設定,則大小增加bufferSize*/
size += bufferSize;
/*配置設定記憶體*/
mCblkMemory = client->heap()->allocate(size);
//如果buffer為應用程式傳入
case ALLOC_NONE:
mBuffer = buffer; //則mBuffer指向應用程式提供的buffer
在TrackBase的構造函數被調用之後,會引起Track的構造函數被調用:
AudioFlinger::PlaybackThread::Track::Track(
if (sharedBuffer == 0) {
mAudioTrackServerProxy = new AudioTrackServerProxy(mCblk, mBuffer, frameCount,
mFrameSize, !isExternalTrack(), sampleRate);
} else {
mAudioTrackServerProxy = new StaticAudioTrackServerProxy(mCblk, mBuffer, frameCount,
mFrameSize);
}
mServerProxy = mAudioTrackServerProxy;
從上知道,如果應用程式沒有建立sharedBuffer,那麼氣會建立一個AudioTrackServerProxy,用來管理Buffer,如果提供了sharedBuffer,則會建立StaticAudioTrackServerProxy,用來管理者一個buffer,到底如何管理呢?下小節為大家講解。
同樣,在應用程式那邊,AudioTrack肯定也需要對buffer進行管理,打開AudioTrack.cpp:
status_t AudioTrack::set(
status_t status = createTrack_l();
/*使用audioFlinger的服務建立Track,會導緻播放線程中建立Track*/
sp<IAudioTrack> track = audioFlinger->createTrack()
/*如果沒有提供buffer,指向PlaybackThread提供的buffer*/
if (mSharedBuffer == 0) {
buffers = cblk + 1;
} else {
/*否則指向自己提供的buffer*/
buffers = mSharedBuffer->pointer();
if (buffers == NULL) {
ALOGE("Could not get buffer pointer");
return NO_INIT;
}
}
/*管理共享記憶體*/
// update proxy
if (mSharedBuffer == 0) {
mStaticProxy.clear();
mProxy = new AudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
} else {
mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
mProxy = mStaticProxy;
}
可以看看,其與Track中的是意義對應的,AudioTrackClientProxy對應AudioTrackServerProxy,StaticAudioTrackClientProxy對應StaticAudioTrackServerProxy。都是對buffer,共享記憶體的管理。
APP建立得到AudioTrack對象之後,引起PlaybackThread中Track的建立,具體流程如下: