天天看點

Android深入淺出之Audio第一部分AudioTrack分析(2)Android深入淺出之Audio第一部分AudioTrack分析(2)

Android深入淺出之Audio第一部分AudioTrack分析(2)

2013-11-14 16:18 佚名 部落格園 字号:T | T

Android深入淺出之Audio第一部分AudioTrack分析(2)Android深入淺出之Audio第一部分AudioTrack分析(2)

本文的目的是通過從Audio系統來分析Android的代碼,包括Android自定義的那套機制和一些常見類的使用,比如Thread,MemoryBase等。

AD:51CTO 網+ 第十二期沙龍:大話資料之美_如何用資料驅動使用者體驗

下面是write。我們寫的是short數組,

  1. static jint 
  2. android_media_AudioTrack_native_write_short(JNIEnv *env,  jobject thiz, 
  3.                                                   jshortArray javaAudioData, 
  4.                                                   jint offsetInShorts, 
  5. jint sizeInShorts, 
  6.                                                   jint javaAudioFormat) { 
  7.     return (android_media_AudioTrack_native_write(env, thiz, 
  8.                                                  (jbyteArray) javaAudioData, 
  9.                                                  offsetInShorts*2, sizeInShorts*2, 
  10.                                                  javaAudioFormat) 
  11.             / 2); 
  12. 煩人,又根據Byte還是Short封裝了下,最終會調到重要函數writeToTrack去 
  13. jint writeToTrack(AudioTrack* pTrack, jint audioFormat, jbyte* data, 
  14.                   jint offsetInBytes, jint sizeInBytes) { 
  15.       ssize_t written = 0; 
  16.     // regular write() or copy the data to the AudioTrack's shared memory? 
  17. if (pTrack->sharedBuffer() == 0) { 
  18. //建立的是流的方式,是以沒有共享記憶體在track中 
  19. //還記得我們在native_setup中調用的set嗎?流模式下AudioTrackJniStorage可沒建立 
  20. //共享記憶體 
  21.         written = pTrack->write(data + offsetInBytes, sizeInBytes); 
  22.     } else { 
  23.         if (audioFormat == javaAudioTrackFields.PCM16) { 
  24.             // writing to shared memory, check for capacity 
  25.             if ((size_t)sizeInBytes > pTrack->sharedBuffer()->size()) { 
  26.                 sizeInBytes = pTrack->sharedBuffer()->size(); 
  27.             } 
  28.            //看見沒?STATIC模式的,就直接把資料拷貝到共享記憶體裡 
  29.           //當然,這個共享記憶體是pTrack的,是我們在set時候把AudioTrackJniStorage的 
  30. //共享設進去的 
  31.             memcpy(pTrack->sharedBuffer()->pointer(), 
  32. data + offsetInBytes, sizeInBytes); 
  33.             written = sizeInBytes; 
  34.         } else if (audioFormat == javaAudioTrackFields.PCM8) { 
  35.            PCM8格式的要先轉換成PCM16 
  36.     } 
  37.     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層調用的是最簡單的構造函數:

  1. AudioTrack::AudioTrack() 
  2.     : mStatus(NO_INIT) //把狀态初始化成NO_INIT。Android大量使用了設計模式中的state。 

接下來調用set。我們看看JNI那set了什麼

  1.  lpTrack->set( 
  2.             atStreamType, //應該是Music吧 
  3.             sampleRateInHertz,//8000 
  4.             format,// 應該是PCM_16吧 
  5.             channels,//立體聲=2 
  6.             frameCount,// 
  7.             0,// flags 
  8.             audioCallback, //JNI中的一個回調函數 
  9. &(lpJniStorage->mCallbackData),//回調函數的參數 
  10.             0,// 通知回調函數,表示AudioTrack需要資料,不過暫時沒用上 
  11.             0,//共享buffer位址,stream模式沒有 
  12.             true);//回調線程可以調JAVA的東西 
  13. 那我們看看set函數把。 
  14. status_t AudioTrack::set( 
  15.         int streamType, 
  16.         uint32_t sampleRate, 
  17.         int format, 
  18.         int channels, 
  19.         int frameCount, 
  20.         uint32_t flags, 
  21.         callback_t cbf, 
  22.         void* user, 
  23.         int notificationFrames, 
  24.         const sp<IMemory>& sharedBuffer, 
  25.         bool threadCanCallJava) 

...前面一堆的判斷,等以後講AudioSystem再說

  1. audio_io_handle_t output = 
  2. AudioSystem::getOutput((AudioSystem::stream_type)streamType, 
  3.             sampleRate, format, channels, (AudioSystem::output_flags)flags); 
  4.    //createTrack?看來這是真正幹活的 
  5.     status_t status = createTrack(streamType, sampleRate, format, channelCount, 
  6.                                   frameCount, flags, sharedBuffer, output); 
  7.   //cbf是JNI傳入的回調函數audioCallback 
  8.      if (cbf != 0) { //看來,怎麼着也要建立這個線程了! 
  9.         mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava); 
  10.        } 
  11.    return NO_ERROR; 

看看真正幹活的createTrack

  1. status_t AudioTrack::createTrack( 
  2.         int streamType, 
  3.         uint32_t sampleRate, 
  4.         int format, 
  5.         int channelCount, 
  6.         int frameCount, 
  7.         uint32_t flags, 
  8.         const sp<IMemory>& sharedBuffer, 
  9.         audio_io_handle_t output) 
  10. status_t status; 
  11. //啊,看來和audioFlinger挂上關系了呀。 
  12.     const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger(); 
  13.   //下面這個調用最終會在AudioFlinger中出現。暫時不管它。 
  14.     sp<IAudioTrack> track = audioFlinger->createTrack(getpid(), 
  15.                                                       streamType, 
  16.                                                       sampleRate, 
  17.                                                       format, 
  18.                                                       channelCount, 
  19.                                                       frameCount, 
  20.                                                       ((uint16_t)flags) << 16, 
  21.                                                       sharedBuffer, 
  22.                                                       output, 
  23.                                                       &status); 
  24.    //看見沒,從track也就是AudioFlinger那邊得到一個IMemory接口 
  25. //這個看來就是最終write寫入的地方 
  26.     sp<IMemory> cblk = track->getCblk(); 
  27.     mAudioTrack.clear(); 
  28.     mAudioTrack = track; 
  29.     mCblkMemory.clear();//sp<XXX>的clear,就看着做是delete XXX吧 
  30.     mCblkMemory = cblk; 
  31.     mCblk = static_cast<audio_track_cblk_t*>(cblk->pointer()); 
  32.     mCblk->out = 1; 
  33.     mFrameCount = mCblk->frameCount; 
  34. if (sharedBuffer == 0) { 
  35. //終于看到buffer相關的了。注意我們這裡的情況 
  36. //STREAM模式沒有傳入共享buffer,但是資料确實又需要buffer承載。 
  37. //反正AudioTrack是沒有建立buffer,那隻能是剛才從AudioFlinger中得到 
  38. //的buffer了。 
  39.         mCblk->buffers = (char*)mCblk + sizeof(audio_track_cblk_t); 
  40.     } 
  41.     return NO_ERROR; 

還記得我們說MemoryXXX沒有同步機制,是以這裡應該有一個東西能展現同步的,

那麼我告訴大家,就在audio_track_cblk_t結構中。它的頭檔案在

framework/base/include/private/media/AudioTrackShared.h

實作檔案就在AudioTrack.cpp中

  1. audio_track_cblk_t::audio_track_cblk_t() 
  2. //看見下面的SHARED沒?都是表示跨程序共享的意思。這個我就不跟進去說了 
  3. //等以後介紹同步方面的知識時,再細說 
  4.     : lock(Mutex::SHARED), cv(Condition::SHARED), user(0), server(0), 
  5.     userBase(0), serverBase(0), buffers(0), frameCount(0), 
  6.     loopStart(UINT_MAX), loopEnd(UINT_MAX), loopCount(0), volumeLR(0), 
  7.     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函數。

先看看構造函數

  1. AudioTrack::AudioTrackThread::AudioTrackThread(AudioTrack& receiver, bool bCanCallJava) 
  2.     : Thread(bCanCallJava), mReceiver(receiver) 
  3. {  //mReceiver就是AudioTrack對象 
  4.   // bCanCallJava為TRUE 

這個線程的啟動由AudioTrack的start函數觸發。

  1. void AudioTrack::start() 
  2.   //start函數調用AudioTrackThread函數觸發産生一個新的線程,執行mAudioTrackThread的 
  3. threadLoop 
  4.     sp<AudioTrackThread> t = mAudioTrackThread; 
  5. t->run("AudioTrackThread", THREAD_PRIORITY_AUDIO_CLIENT); 
  6. //讓AudioFlinger中的track也start 
  7.     status_t status = mAudioTrack->start(); 
  8. bool AudioTrack::AudioTrackThread::threadLoop() 
  9.   //太惡心了,又調用AudioTrack的processAudioBuffer函數 
  10. return mReceiver.processAudioBuffer(this); 
  11. bool AudioTrack::processAudioBuffer(const sp<AudioTrackThread>& thread) 
  12. Buffer audioBuffer; 
  13.     uint32_t frames; 
  14.     size_t writtenSize; 
  15.       ...回調1 
  16.          mCbf(EVENT_UNDERRUN, mUserData, 0); 
  17. ...回調2 都是傳遞一些資訊到JNI裡邊 
  18.          mCbf(EVENT_BUFFER_END, mUserData, 0); 
  19.          // Manage loop end callback 
  20.     while (mLoopCount > mCblk->loopCount) { 
  21.         mCbf(EVENT_LOOP_END, mUserData, (void *)&loopCount); 
  22.     } 
  23.   //下面好像有寫資料的東西 
  24.       do { 
  25.        audioBuffer.frameCount = frames; 
  26. //獲得buffer, 
  27.        status_t err = obtainBuffer(&audioBuffer, 1); 
  28.         size_t reqSize = audioBuffer.size; 
  29. //把buffer回調到JNI那去,這是單獨一個線程,而我們還有上層使用者在那不停 
  30. //地write呢,怎麼會這樣? 
  31.         mCbf(EVENT_MORE_DATA, mUserData, &audioBuffer); 
  32.          audioBuffer.size = writtenSize; 
  33.          frames -= audioBuffer.frameCount; 
  34.        releaseBuffer(&audioBuffer); //釋放buffer,和obtain相對應,看來是LOCK和UNLOCK 
  35. 操作了 
  36.     } 
  37.     while (frames); 
  38.    return true; 
  39. 難道真的有兩處在write資料?看來必須得到mCbf去看看了,傳的是EVENT_MORE_DATA标志。 
  40. mCbf由set的時候傳入C++的AudioTrack,實際函數是: 
  41. static void audioCallback(int event, void* user, void *info) { 
  42.     if (event == AudioTrack::EVENT_MORE_DATA) { 
  43.          //哈哈,太好了,這個函數沒往裡邊寫資料 
  44.         AudioTrack::Buffer* pBuff = (AudioTrack::Buffer*)info; 
  45.         pBuff->size = 0;  
  46.      } 

從代碼上看,本來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記憶體了

  1. do { 
  2.        audioBuffer.frameCount = userSize/frameSize(); 
  3.        status_t err = obtainBuffer(&audioBuffer, -1); 
  4.         size_t toWrite; 
  5.         toWrite = audioBuffer.size; 
  6.         memcpy(audioBuffer.i8, src, toWrite); 
  7.         src += toWrite; 
  8.        } 
  9.        userSize -= toWrite; 
  10.        written += toWrite; 
  11.        releaseBuffer(&audioBuffer); 
  12.    } while (userSize); 
  13.    return written; 

obtainBuffer太複雜了,不過大家知道其大概工作方式就可以了

  1. status_t AudioTrack::obtainBuffer(Buffer* audioBuffer, int32_t waitCount) 
  2.    //恕我中間省略太多,大部分都是和目前資料位置相關, 
  3.  uint32_t framesAvail = cblk->framesAvailable(); 
  4.      cblk->lock.lock();//看見沒,lock了 
  5.      result = cblk->cv.waitRelative(cblk->lock, milliseconds(waitTimeMs)); 
  6. //我發現很多地方都要判斷遠端的AudioFlinger的狀态,比如是否退出了之類的,難道 
  7. //沒有一個好的方法來集中處理這種事情嗎? 
  8.       if (result == DEAD_OBJECT) { 
  9.         result = createTrack(mStreamType, cblk->sampleRate, mFormat, mChannelCount, 
  10.           mFrameCount, mFlags, mSharedBuffer,getOutput()); 
  11.         } 
  12. //得到buffer 
  13.     audioBuffer->raw = (int8_t *)cblk->buffer(u); 
  14.   return active ? status_t(NO_ERROR) : status_t(STOPPED); 
  15. 在看看releaseBuffer 
  16. void AudioTrack::releaseBuffer(Buffer* audioBuffer) 
  17.     audio_track_cblk_t* cblk = mCblk; 
  18. cblk->stepUser(audioBuffer->frameCount); 
  19. uint32_t audio_track_cblk_t::stepUser(uint32_t frameCount) 
  20.     uint32_t u = this->user; 
  21.     u += frameCount; 
  22.      if (out) { 
  23.           if (bufferTimeoutMs == MAX_STARTUP_TIMEOUT_MS-1) { 
  24.             bufferTimeoutMs = MAX_RUN_TIMEOUT_MS; 
  25.         } 
  26.     } else if (u > this->server) { 
  27.          u = this->server; 
  28.     } 
  29.     if (u >= userBase + this->frameCount) { 
  30.         userBase += this->frameCount; 
  31.     } 
  32.    this->user = u; 
  33.   flowControlFlag = 0; 
  34.   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資料到音頻裝置中去。我們拭目以待。

【編輯推薦】

  1. MTP in Android詳解
  2. Android Audio系統變化說明
  3. Android rom移植知識
  4. Android深入淺出之Audio第三部分Audio Policy
  5. Android深入淺出之AudioFlinger分析

【責任編輯:張葉青 TEL:(010)68476606】