在 Android 上擷取 Video Frame 的方法

之前有需要很精準的在 Android 上擷取 video 中的 frames,並且轉成 Bitmap 來使用。一開始覺得是個很簡單的工作,因為 Android SDK 有個 class 叫做 MediaMetadataRetriever,裡面就有 getFrameAtTime 的 API 啊。真好。結果不是這麼一回事耶!想不到在 Multimedia 應用這麼盛行的時代了,Android Media 的 API 還這麼不堪用啊?
好吧,來簡單說說我測試的幾個方法:

Use MediaMetadataRetriever getFrameAtTime

這個 API 乍看之下超棒的,實際用起來其實滿令人挫折的,一開始的確很容易可以拿到一張 Bitmap 但是後來就會發現不管丟進去的時間參數是什麼其實它也只會吐給你同一張。有時候更糟的是吐回來的 Bitmap 直接就是 null 了,但至少 null 是死的比較乾脆一點。所以如果去找出 Android Framework 中對應的 code 來看就會發現: (ref: gitorious)

getFrameAtTime 最終是 call 到 stagefrighMedatadataRetriever 的 extractVideoFrameWithCodecFlags,而它根本不管你丟的時間啊。有 thumbnail time 的話就一直 decode 同一張給你,沒有的話就給你空的,完全符合結果,這 API 只能讓 Gallery 拿來展示 video thumbnail 用而已。這 API 從 Level 10 之後就開出來了,一直是這樣的 implementation 實在是 …

Use MediaPlayer

上述方法不 work ,於是轉向 MediaPlayer,方法是 create 一個 TextureView 傳給 MediaPlayer 畫,然後對 MediaPlayer seek 到我想要的時間點之後去 call TextureView 的 gitBitmap API 取得 Bitmap。這個方法基本上都沒問題,而且也都是走 hardware solution(除了 getBitmap 需要把 GPU memory copy 到 CPU memory 之外),唯一(也是致命)的問題是無法精準的控制時間! 設定一個 seek complete 的 listener 給 MediaPlayer 然後在收到 seek complete 之後去 getBitmap 發現 frame 沒動。如果讓 MP 開始播放然後定時去拿 frame 的話可以 work,但完全無法控制中間 delay 的時間,所以也是個不堪用的方法啊。

Use MediaCodec

Goolge 自從 JellyBean 之後在 Java 層開放了一個很低階的 decoder/encoder API,叫做 MediaCodec,去年的 Google I/O 2012 有一個 session 在介紹這個 API。大致上 android media 相關的都還是架構在 OpenMX 上,所以這個 MediaCodec API 底層實作還是走 OpenMX protocol,因此只要 device 有支援硬體解碼或壓縮的話,透過這個 API 都可以得到硬體解碼或壓縮的高效能好處。而這個 API 就用處而言都很好,唯一比較不好的一點是設計得很難用。我大致參考了這篇文章然後寫了一個可以播出影片的完整 Android Project 放在 GitHub上,有興趣可以參考看看。 這裡比較關鍵的是比較瞭解一下這邊這兩行,MediaCodec 完全的保護住 input / output buffer,而且這邊的 buffer 是 GPU memory,因此 MediaCodec 都期望我們透過 index 來 access 這些 buffer。
然後由於這一組 API 真的很低階,所以如果要用這個寫 Player 的話,Application 必須自己負責做 A/V sync … 這要做的好的話是要花一點功夫的,還好我只是要拿 video frame 而已 XD

由於我想要拿的是 RGB buffer,但通常 video frame decode 出來一定是 YUV 的 format,而且 YUV 還分超多種。不過基本上只要是公開的 YUV format 的話都可以找到方法自己寫 code 轉換。但還有一種 case 网络游戏 是硬體廠商自己訂的 YUV format,這種就要去翻一下 android framework source code,如前面提到的 MediaMetaDataretriever 也是透過 OMX decoder 拿到 video raw buffer 然後轉換成 RGB buffer,所以底層一定有 code 做這件事情。stagrfright 裡面有一個 ColorConverter 就是做這件事情的,但這邊的話是透果軟體作轉換所以效率會差一些(其實應該是很多)。有興趣的話可以參考我寫的一個 jni code 裡面會直接去使用 ColorConverter 去做 color conversion。

至於如果有人想要挑戰拿 MediaCodec 來做 transcoding 的話第一個必須要解決的問題是可能必須自己寫一個 MP4 muxer (或者是你 target 的 format 的 muxer),這一般人要做好像有點難度。如果有做出來的話記得分享一下 🙂

希望 Google 可以改善一下這個 API 讓它好用一點,然後也 release 一些真實的 Sample Code 放在 API demo project 裡面,不然這組 API 實在不怎麼 Google。



Comments
  1. Miles
    回覆
  2. 阿三 施
    回覆
    • kswlee
      回覆
  3. Kay Jean
    回覆
    • kswlee
      回覆
  4. howlingkaze
    回覆
    • kswlee
      回覆
      • howlingkaze
        回覆
        • kswlee
          回覆
  5. Ramus
    回覆
    • kswlee
      回覆
      • Ramus
        回覆
  6. gudao
    回覆
  7. Hung Tzeng
    回覆

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *