Android深入淺出之Audio第一部分AudioTrack分析(2)
2013-11-14 16:18 佚名 部落格園 字号:T | T
本文的目的是通過從Audio系統來分析Android的代碼,包括Android自定義的那套機制和一些常見類的使用,比如Thread,MemoryBase等。
AD:51CTO 網+ 第十二期沙龍:大話資料之美_如何用資料驅動使用者體驗
下面是write。我們寫的是short數組,
- static jint
- android_media_AudioTrack_native_write_short(JNIEnv *env, jobject thiz,
- jshortArray javaAudioData,
- jint offsetInShorts,
- jint sizeInShorts,
- jint javaAudioFormat) {
- return (android_media_AudioTrack_native_write(env, thiz,
- (jbyteArray) javaAudioData,
- offsetInShorts*2, sizeInShorts*2,
- javaAudioFormat)
- / 2);
- }
- 煩人,又根據Byte還是Short封裝了下,最終會調到重要函數writeToTrack去
- jint writeToTrack(AudioTrack* pTrack, jint audioFormat, jbyte* data,
- jint offsetInBytes, jint sizeInBytes) {
- ssize_t written = 0;
- // regular write() or copy the data to the AudioTrack's shared memory?
- if (pTrack->sharedBuffer() == 0) {
- //建立的是流的方式,是以沒有共享記憶體在track中
- //還記得我們在native_setup中調用的set嗎?流模式下AudioTrackJniStorage可沒建立
- //共享記憶體
- written = pTrack->write(data + offsetInBytes, sizeInBytes);
- } else {
- if (audioFormat == javaAudioTrackFields.PCM16) {
- // writing to shared memory, check for capacity
- if ((size_t)sizeInBytes > pTrack->sharedBuffer()->size()) {
- sizeInBytes = pTrack->sharedBuffer()->size();
- }
- //看見沒?STATIC模式的,就直接把資料拷貝到共享記憶體裡
- //當然,這個共享記憶體是pTrack的,是我們在set時候把AudioTrackJniStorage的
- //共享設進去的
- memcpy(pTrack->sharedBuffer()->pointer(),
- data + offsetInBytes, sizeInBytes);
- written = sizeInBytes;
- } else if (audioFormat == javaAudioTrackFields.PCM8) {
- PCM8格式的要先轉換成PCM16
- }
- return written;
- }
到這裡,似乎很簡單啊,JAVA層的AudioTrack,無非就是調用write函數,而實際由JNI層的C++ AudioTrack write資料。反正JNI這層是再看不出什麼有意思的東西了。
四 AudioTrack(C++層)
接上面的内容,我們知道在JNI層,有以下幾個步驟:
l new了一個AudioTrack
l 調用set函數,把AudioTrackJniStorage等資訊傳進去
l 調用了AudioTrack的start函數
l 調用AudioTrack的write函數
那麼,我們就看看真正幹活的的C++AudioTrack吧。
AudioTrack.cpp位于framework/base/libmedia/AudioTrack.cpp
4.1 new AudioTrack()和set調用
JNI層調用的是最簡單的構造函數:
- AudioTrack::AudioTrack()
- : mStatus(NO_INIT) //把狀态初始化成NO_INIT。Android大量使用了設計模式中的state。
- {
- }
接下來調用set。我們看看JNI那set了什麼
- lpTrack->set(
- atStreamType, //應該是Music吧
- sampleRateInHertz,//8000
- format,// 應該是PCM_16吧
- channels,//立體聲=2
- frameCount,//
- 0,// flags
- audioCallback, //JNI中的一個回調函數
- &(lpJniStorage->mCallbackData),//回調函數的參數
- 0,// 通知回調函數,表示AudioTrack需要資料,不過暫時沒用上
- 0,//共享buffer位址,stream模式沒有
- true);//回調線程可以調JAVA的東西
- 那我們看看set函數把。
- 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,
- bool threadCanCallJava)
- {
...前面一堆的判斷,等以後講AudioSystem再說
- audio_io_handle_t output =
- AudioSystem::getOutput((AudioSystem::stream_type)streamType,
- sampleRate, format, channels, (AudioSystem::output_flags)flags);
- //createTrack?看來這是真正幹活的
- status_t status = createTrack(streamType, sampleRate, format, channelCount,
- frameCount, flags, sharedBuffer, output);
- //cbf是JNI傳入的回調函數audioCallback
- if (cbf != 0) { //看來,怎麼着也要建立這個線程了!
- mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);
- }
- return NO_ERROR;
- }
看看真正幹活的createTrack
- status_t AudioTrack::createTrack(
- int streamType,
- uint32_t sampleRate,
- int format,
- int channelCount,
- int frameCount,
- uint32_t flags,
- const sp<IMemory>& sharedBuffer,
- audio_io_handle_t output)
- {
- status_t status;
- //啊,看來和audioFlinger挂上關系了呀。
- const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
- //下面這個調用最終會在AudioFlinger中出現。暫時不管它。
- sp<IAudioTrack> track = audioFlinger->createTrack(getpid(),
- streamType,
- sampleRate,
- format,
- channelCount,
- frameCount,
- ((uint16_t)flags) << 16,
- sharedBuffer,
- output,
- &status);
- //看見沒,從track也就是AudioFlinger那邊得到一個IMemory接口
- //這個看來就是最終write寫入的地方
- sp<IMemory> cblk = track->getCblk();
- mAudioTrack.clear();
- mAudioTrack = track;
- mCblkMemory.clear();//sp<XXX>的clear,就看着做是delete XXX吧
- mCblkMemory = cblk;
- mCblk = static_cast<audio_track_cblk_t*>(cblk->pointer());
- mCblk->out = 1;
- mFrameCount = mCblk->frameCount;
- if (sharedBuffer == 0) {
- //終于看到buffer相關的了。注意我們這裡的情況
- //STREAM模式沒有傳入共享buffer,但是資料确實又需要buffer承載。
- //反正AudioTrack是沒有建立buffer,那隻能是剛才從AudioFlinger中得到
- //的buffer了。
- mCblk->buffers = (char*)mCblk + sizeof(audio_track_cblk_t);
- }
- return NO_ERROR;
- }
還記得我們說MemoryXXX沒有同步機制,是以這裡應該有一個東西能展現同步的,
那麼我告訴大家,就在audio_track_cblk_t結構中。它的頭檔案在
framework/base/include/private/media/AudioTrackShared.h
實作檔案就在AudioTrack.cpp中
- audio_track_cblk_t::audio_track_cblk_t()
- //看見下面的SHARED沒?都是表示跨程序共享的意思。這個我就不跟進去說了
- //等以後介紹同步方面的知識時,再細說
- : lock(Mutex::SHARED), cv(Condition::SHARED), user(0), server(0),
- userBase(0), serverBase(0), buffers(0), frameCount(0),
- loopStart(UINT_MAX), loopEnd(UINT_MAX), loopCount(0), volumeLR(0),
- flowControlFlag(1), forceReady(0)
- {
- }
到這裡,大家應該都有個大概的全景了。
l AudioTrack得到AudioFlinger中的一個IAudioTrack對象,這裡邊有一個很重要的資料結構audio_track_cblk_t,它包括一塊緩沖區位址,包括一些程序間同步的内容,可能還有資料位置等内容
l AudioTrack啟動了一個線程,叫AudioTrackThread,這個線程幹嘛的呢?還不知道
l AudioTrack調用write函數,肯定是把資料寫到那塊共享緩沖了,然後IAudioTrack在另外一個程序AudioFlinger中(其實AudioFlinger是一個服務,在mediaservice中運作)接收資料,并最終寫到音頻裝置中。
那我們先看看AudioTrackThread幹什麼了。
調用的語句是:
mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);
AudioTrackThread從Thread中派生,這個内容在深入淺出Binder機制講過了。
反正最終會調用AudioTrackAThread的threadLoop函數。
先看看構造函數
- AudioTrack::AudioTrackThread::AudioTrackThread(AudioTrack& receiver, bool bCanCallJava)
- : Thread(bCanCallJava), mReceiver(receiver)
- { //mReceiver就是AudioTrack對象
- // bCanCallJava為TRUE
- }
這個線程的啟動由AudioTrack的start函數觸發。
- void AudioTrack::start()
- {
- //start函數調用AudioTrackThread函數觸發産生一個新的線程,執行mAudioTrackThread的
- threadLoop
- sp<AudioTrackThread> t = mAudioTrackThread;
- t->run("AudioTrackThread", THREAD_PRIORITY_AUDIO_CLIENT);
- //讓AudioFlinger中的track也start
- status_t status = mAudioTrack->start();
- }
- bool AudioTrack::AudioTrackThread::threadLoop()
- {
- //太惡心了,又調用AudioTrack的processAudioBuffer函數
- return mReceiver.processAudioBuffer(this);
- }
- bool AudioTrack::processAudioBuffer(const sp<AudioTrackThread>& thread)
- {
- Buffer audioBuffer;
- uint32_t frames;
- size_t writtenSize;
- ...回調1
- mCbf(EVENT_UNDERRUN, mUserData, 0);
- ...回調2 都是傳遞一些資訊到JNI裡邊
- mCbf(EVENT_BUFFER_END, mUserData, 0);
- // Manage loop end callback
- while (mLoopCount > mCblk->loopCount) {
- mCbf(EVENT_LOOP_END, mUserData, (void *)&loopCount);
- }
- //下面好像有寫資料的東西
- do {
- audioBuffer.frameCount = frames;
- //獲得buffer,
- status_t err = obtainBuffer(&audioBuffer, 1);
- size_t reqSize = audioBuffer.size;
- //把buffer回調到JNI那去,這是單獨一個線程,而我們還有上層使用者在那不停
- //地write呢,怎麼會這樣?
- mCbf(EVENT_MORE_DATA, mUserData, &audioBuffer);
- audioBuffer.size = writtenSize;
- frames -= audioBuffer.frameCount;
- releaseBuffer(&audioBuffer); //釋放buffer,和obtain相對應,看來是LOCK和UNLOCK
- 操作了
- }
- while (frames);
- return true;
- }
- 難道真的有兩處在write資料?看來必須得到mCbf去看看了,傳的是EVENT_MORE_DATA标志。
- mCbf由set的時候傳入C++的AudioTrack,實際函數是:
- static void audioCallback(int event, void* user, void *info) {
- if (event == AudioTrack::EVENT_MORE_DATA) {
- //哈哈,太好了,這個函數沒往裡邊寫資料
- AudioTrack::Buffer* pBuff = (AudioTrack::Buffer*)info;
- pBuff->size = 0;
- }
從代碼上看,本來google考慮是異步的回調方式來寫資料,可惜發現這種方式會比較複雜,尤其是對使用者開放的JAVA AudioTrack會很不好處理,是以嘛,偷偷摸摸得給繞過去了。
太好了,看來就隻有使用者的write會真正的寫資料了,這個AudioTrackThread除了通知一下,也沒什麼實際有意義的操作了。
讓我們看看write吧。
4.2 write
ssize_t AudioTrack::write(const void* buffer, size_t userSize)
{
夠簡單,就是obtainBuffer,memcpy資料,然後releasBuffer
眯着眼睛都能想到,obtainBuffer一定是Lock住記憶體了,releaseBuffer一定是unlock記憶體了
- do {
- audioBuffer.frameCount = userSize/frameSize();
- status_t err = obtainBuffer(&audioBuffer, -1);
- size_t toWrite;
- toWrite = audioBuffer.size;
- memcpy(audioBuffer.i8, src, toWrite);
- src += toWrite;
- }
- userSize -= toWrite;
- written += toWrite;
- releaseBuffer(&audioBuffer);
- } while (userSize);
- return written;
obtainBuffer太複雜了,不過大家知道其大概工作方式就可以了
- status_t AudioTrack::obtainBuffer(Buffer* audioBuffer, int32_t waitCount)
- {
- //恕我中間省略太多,大部分都是和目前資料位置相關,
- uint32_t framesAvail = cblk->framesAvailable();
- cblk->lock.lock();//看見沒,lock了
- result = cblk->cv.waitRelative(cblk->lock, milliseconds(waitTimeMs));
- //我發現很多地方都要判斷遠端的AudioFlinger的狀态,比如是否退出了之類的,難道
- //沒有一個好的方法來集中處理這種事情嗎?
- if (result == DEAD_OBJECT) {
- result = createTrack(mStreamType, cblk->sampleRate, mFormat, mChannelCount,
- mFrameCount, mFlags, mSharedBuffer,getOutput());
- }
- //得到buffer
- audioBuffer->raw = (int8_t *)cblk->buffer(u);
- return active ? status_t(NO_ERROR) : status_t(STOPPED);
- }
- 在看看releaseBuffer
- void AudioTrack::releaseBuffer(Buffer* audioBuffer)
- {
- audio_track_cblk_t* cblk = mCblk;
- cblk->stepUser(audioBuffer->frameCount);
- }
- uint32_t audio_track_cblk_t::stepUser(uint32_t frameCount)
- {
- uint32_t u = this->user;
- u += frameCount;
- if (out) {
- if (bufferTimeoutMs == MAX_STARTUP_TIMEOUT_MS-1) {
- bufferTimeoutMs = MAX_RUN_TIMEOUT_MS;
- }
- } else if (u > this->server) {
- u = this->server;
- }
- if (u >= userBase + this->frameCount) {
- userBase += this->frameCount;
- }
- this->user = u;
- flowControlFlag = 0;
- return u;
- }
奇怪了,releaseBuffer沒有unlock操作啊?難道我失誤了?
再去看看obtainBuffer?為何寫得這麼晦澀難懂?
原來在obtainBuffer中會某一次進去lock,再某一次進去可能就是unlock了。沒看到obtainBuffer中到處有lock,unlock,wait等同步操作嗎。一定是這個道理。難怪寫這麼複雜。還使用了少用的goto語句。
唉,有必要這樣嗎!
五 AudioTrack總結
通過這一次的分析,我自己覺得有以下幾個點:
l AudioTrack的工作原理,尤其是資料的傳遞這一塊,做了比較細緻的分析,包括共享記憶體,跨程序的同步等,也能解釋不少疑惑了。
l 看起來,最重要的工作是在AudioFlinger中做的。通過AudioTrack的介紹,我們給後續深入分析AudioFlinger提供了一個切入點
工作原理和流程嘛,再說一次好了,JAVA層就看最前面那個例子吧,實在沒什麼說的。
l AudioTrack被new出來,然後set了一堆資訊,同時會通過Binder機制調用另外一端的AudioFlinger,得到IAudioTrack對象,通過它和AudioFlinger互動。
l 調用start函數後,會啟動一個線程專門做回調處理,代碼裡邊也會有那種資料拷貝的回調,但是JNI層的回調函數實際并沒有往裡邊寫資料,大家隻要看write就可以了
l 使用者一次次得write,那AudioTrack無非就是把資料memcpy到共享buffer中咯
l 可想而知,AudioFlinger那一定有一個線程在memcpy資料到音頻裝置中去。我們拭目以待。
【編輯推薦】
- MTP in Android詳解
- Android Audio系統變化說明
- Android rom移植知識
- Android深入淺出之Audio第三部分Audio Policy
- Android深入淺出之AudioFlinger分析
【責任編輯:張葉青 TEL:(010)68476606】