Android 視頻中抓取圖像 - MediaMetadataRetriever

最近在side project中遇到了需要從視頻中抓取多張圖片的需求。安卓已經(jīng)提供了從視頻獲取預(yù)覽圖片的ThumbnailUtils, 但此類不能根據(jù)timestamp獲取bitmap。

以下記錄自己找出的解決方案。

需求

在本地視頻根據(jù)時(shí)間戳(timestamp)抓取bitmap圖像。

解決方案

配合使用這些flag可以達(dá)到不同的程度的時(shí)間精確度

  • OPTION_PREVIOUS_SYNC: 前一個(gè)i-frame
  • OPTION_NEXT_SYNC: 后一個(gè) i-frame
  • OPTION_CLOSEST_SYNC: 最近的i-frame,不管前后
  • OPTION_CLOSEST: 最近的frame,不一定是i-frame。上面三個(gè)flag只需要decode一張frame即可(而且是i-frame)。但這個(gè)flag需要decode多張frame才能接近輸入的timestamp,因此速度會慢些。

值得一提的是此類會用binder IBC給system media service發(fā)送請求, 因此decoder是在系統(tǒng)服務(wù)中進(jìn)行的,并非在我自己的app進(jìn)程中。
方法調(diào)用時(shí)logcat可看到如下log

  918 13498 I OMXMaster: makeComponentInstance(OMX.google.h264.decoder) in omx@1.0-service process
  918 28062 I OMXMaster: makeComponentInstance(OMX.google.h264.decoder) in omx@1.0-service process
  918  1854 I OMXMaster: makeComponentInstance(OMX.google.h264.decoder) in omx@1.0-service process
  918 13498 I OMXMaster: makeComponentInstance(OMX.google.h264.decoder) in omx@1.0-service process

具體實(shí)現(xiàn)可參考:http://androidxref.com/9.0.0_r3/xref/frameworks/av/media/libmedia/mediametadataretriever.cpp#154

需要注意的坑

如果只看文檔就會以為用getFrameAtTime + OPTION_CLOSEST就能非常精準(zhǔn)的返回在輸入timestamp附近的視頻幀,但在不同device上跑過代碼才發(fā)現(xiàn)其行為其實(shí)很不統(tǒng)一。:(

高端機(jī)基本都能做到預(yù)期效果,返回的bitmap在輸入timestamp附近。但低端機(jī)型直接無視OPTION_CLOSEST 返回附近I-frame的bitmap,因此有時(shí)得到的圖像跟輸入的timestamp相差甚遠(yuǎn)。例如輸入的是3.3sec,返回的有可能是2.8sec的i-frame圖像。

出于好奇,我在低端機(jī)上做了個(gè)小實(shí)驗(yàn)。掃描一個(gè)視頻文件的video timestamp,再把他們逐一用來調(diào)用getFrameAtTime,看看返回的bitmap有沒有重復(fù)。

很明顯的看到高端機(jī)基本無重復(fù)的bitmap,低端機(jī)卻有大把大把的重復(fù)。猜測應(yīng)該是廠商為了性能而只做了附近i-frame的解碼就返回了。有點(diǎn)坑。:(

如果需要在所有機(jī)器上返回精準(zhǔn)timestamp的bitmap,恐怕只能自己操作decoder了。估計(jì)非常復(fù)雜。

此外 API28+ 新增加了MediaMetadataRetriever.getFramesAtIndex() 。 這應(yīng)該是比pts更加穩(wěn)定的抓取方式,畢竟index是連續(xù)的int。但不知低端機(jī)型會不會繼續(xù)坑。


==高端
D/MediaMetadataRetrieverRunner: pts 33375    digest :ym71cu9iO1H94190FWVgeg==
D/MediaMetadataRetrieverRunner: pts 66625    digest :y9hG93MqABw3ILMta/noUg==
D/MediaMetadataRetrieverRunner: pts 100000   digest :0l2CkOYDNyxvZCfCeLuq9A==
D/MediaMetadataRetrieverRunner: pts 133375   digest :OHSVQQOxrO77mh9f7tTorQ==
D/MediaMetadataRetrieverRunner: pts 166625   digest :XZ8kDFxsdjgBkUzKl41dgA==
D/MediaMetadataRetrieverRunner: pts 200000   digest :0/13IPmw1gP9FLdpsJc/Wg==
D/MediaMetadataRetrieverRunner: pts 233375   digest :TeHChk9OWbb8nLbrFphlEg==
D/MediaMetadataRetrieverRunner: pts 266625   digest :GEzE3LtFzG88bT7WtMTbxA==
D/MediaMetadataRetrieverRunner: pts 300000   digest :QjdeoZYwYgNxV144kKLTNw==
D/MediaMetadataRetrieverRunner: pts 333375   digest :0DZCQer3BqMPfSYTJaafMg==
D/MediaMetadataRetrieverRunner: pts 366625   digest :mfjEap/awknngWIeyPAScg==
D/MediaMetadataRetrieverRunner: pts 400000   digest :Q1oSKau7n9boi0vDSFZJFA==
D/MediaMetadataRetrieverRunner: pts 433375   digest :kW0vjMra+boztvH+PXQrlw==
D/MediaMetadataRetrieverRunner: pts 466625   digest :a6C/BpHAcr4BxSDg7Gt4bQ==
D/MediaMetadataRetrieverRunner: pts 500000   digest :odAEN5ESKWrLmXwDu09arA==
D/MediaMetadataRetrieverRunner: pts 533375   digest :+zTTQsil/s6on0EVqFuH/Q==
D/MediaMetadataRetrieverRunner: pts 566625   digest :ZqsNHZd1r12UL7cFHdc9vw==
D/MediaMetadataRetrieverRunner: pts 600000   digest :CgUZWadeCe+S+e28C/4qkA==
D/MediaMetadataRetrieverRunner: pts 633375   digest :hUaJy2jWWa9RU4dR24Om7w==
D/MediaMetadataRetrieverRunner: pts 666625   digest :a5HlUYUJcz3xaQrn/hNsIg==
D/MediaMetadataRetrieverRunner: pts 700000   digest :YF70StAojixZxjk8epZL3w==
D/MediaMetadataRetrieverRunner: pts 733375   digest :rolF61sMxRMSP09ePtUGcQ==
D/MediaMetadataRetrieverRunner: pts 766625   digest :K+qZUFNP5EgzWPscmmnFew==
D/MediaMetadataRetrieverRunner: pts 800000   digest :d8iVGhHf3VpMn+vMnBdmng==
D/MediaMetadataRetrieverRunner: pts 833375   digest :cKREwaki8AJmOuSVa8Zvbw==
D/MediaMetadataRetrieverRunner: pts 866625   digest :b3QXkX+wTE1CuCe79JK7Ww==
D/MediaMetadataRetrieverRunner: pts 900000   digest :atzAyrZNcOf8Ghgf04lftw==
D/MediaMetadataRetrieverRunner: pts 933375   digest :hS9ukOCLMCobHplBeRNdOA==
D/MediaMetadataRetrieverRunner: pts 966625   digest :rZyr6vVt5ae+TiMMVTCRrg==




=====


==低端
D/MediaMetadataRetrieverRunner: pts 1601625      digest :Ba29dyKehU1o6EanhG0wvg==
D/MediaMetadataRetrieverRunner: pts 1635000      digest :Ba29dyKehU1o6EanhG0wvg==
D/MediaMetadataRetrieverRunner: pts 1668375      digest :Ba29dyKehU1o6EanhG0wvg==
D/MediaMetadataRetrieverRunner: pts 1701750      digest :Ba29dyKehU1o6EanhG0wvg==
D/MediaMetadataRetrieverRunner: pts 1735000      digest :Ba29dyKehU1o6EanhG0wvg==
D/MediaMetadataRetrieverRunner: pts 1768375      digest :Ba29dyKehU1o6EanhG0wvg==
D/MediaMetadataRetrieverRunner: pts 1801750      digest :Ba29dyKehU1o6EanhG0wvg==
D/MediaMetadataRetrieverRunner: pts 1835125      digest :Ba29dyKehU1o6EanhG0wvg==
D/MediaMetadataRetrieverRunner: pts 1868500      digest :Ba29dyKehU1o6EanhG0wvg==
D/MediaMetadataRetrieverRunner: pts 1901875      digest :Ba29dyKehU1o6EanhG0wvg==
D/MediaMetadataRetrieverRunner: pts 1935250      digest :Ba29dyKehU1o6EanhG0wvg==
D/MediaMetadataRetrieverRunner: pts 1968625      digest :Ba29dyKehU1o6EanhG0wvg==
D/MediaMetadataRetrieverRunner: pts 2002000      digest :dcMXdpTton6/YQHJA2zCjg==
D/MediaMetadataRetrieverRunner: pts 2035375      digest :830odrRgw9UyAhNKeBUWAA==
D/MediaMetadataRetrieverRunner: pts 2068750      digest :8WFabowQKX+I2q3XNC2HJg==
D/MediaMetadataRetrieverRunner: pts 2102125      digest :8WFabowQKX+I2q3XNC2HJg==
D/MediaMetadataRetrieverRunner: pts 2135500      digest :8WFabowQKX+I2q3XNC2HJg==
D/MediaMetadataRetrieverRunner: pts 2168875      digest :dcMXdpTton6/YQHJA2zCjg==
D/MediaMetadataRetrieverRunner: pts 2202250      digest :dcMXdpTton6/YQHJA2zCjg==
D/MediaMetadataRetrieverRunner: pts 2235500      digest :dcMXdpTton6/YQHJA2zCjg==
D/MediaMetadataRetrieverRunner: pts 2268875      digest :dcMXdpTton6/YQHJA2zCjg==
D/MediaMetadataRetrieverRunner: pts 2302250      digest :dcMXdpTton6/YQHJA2zCjg==
D/MediaMetadataRetrieverRunner: pts 2335625      digest :h5ZNxcxO6WrbYFQPsknjnw==
D/MediaMetadataRetrieverRunner: pts 2369000      digest :KXZQ788bP25Oqi7sHxWPLg==
D/MediaMetadataRetrieverRunner: pts 2402375      digest :KXZQ788bP25Oqi7sHxWPLg==
D/MediaMetadataRetrieverRunner: pts 2435750      digest :KXZQ788bP25Oqi7sHxWPLg==
D/MediaMetadataRetrieverRunner: pts 2469125      digest :KXZQ788bP25Oqi7sHxWPLg==
D/MediaMetadataRetrieverRunner: pts 2502500      digest :2h341j90Scn3kRtA4Fr+IA==
D/MediaMetadataRetrieverRunner: pts 2535875      digest :2h341j90Scn3kRtA4Fr+IA==
D/MediaMetadataRetrieverRunner: pts 2569250      digest :OOdPxTb4/NWbUrC8HDzpng==
D/MediaMetadataRetrieverRunner: pts 2602625      digest :OOdPxTb4/NWbUrC8HDzpng==
D/MediaMetadataRetrieverRunner: pts 2636000      digest :OOdPxTb4/NWbUrC8HDzpng==
D/MediaMetadataRetrieverRunner: pts 2669375      digest :OOdPxTb4/NWbUrC8HDzpng==
D/MediaMetadataRetrieverRunner: pts 2702750      digest :OOdPxTb4/NWbUrC8HDzpng==


相關(guān)代碼

public class MediaMetadataRetrieverRunner {

  private String TAG = MediaMetadataRetrieverRunner.class.getSimpleName();

  void scanAndDigest(String path) throws Exception {
    List<Long> ptsUsList = VideoPtsScanner.scanPts(path);
    Log.d(TAG, "pts list in micro sec: " + ptsUsList);

    MediaMetadataRetriever retriever = new MediaMetadataRetriever();
    retriever.setDataSource(path);

    for (long ptsUs : ptsUsList) {
      Bitmap bitmap = retriever.getFrameAtTime(ptsUs, MediaMetadataRetriever.OPTION_CLOSEST);

      if (bitmap == null) {
        Log.e(TAG, "got null in pts " + ptsUs);
        continue;
      }

      int size = bitmap.getAllocationByteCount();
      ByteBuffer buffer = ByteBuffer.allocateDirect(size);
      bitmap.copyPixelsToBuffer(buffer);
      buffer.flip();

      String digest = makeString(buffer);
      Log.d(TAG, "pts " + ptsUs + " \t digest :" + digest);
    }

    retriever.release();
  }

  private String makeString(ByteBuffer buffer) throws NoSuchAlgorithmException {
    MessageDigest md5 = MessageDigest.getInstance("MD5");
    md5.update(buffer);
    byte[] digest = md5.digest();
    // covert into base64 string, since it's already provided in android
    return Base64.encodeToString(digest, Base64.DEFAULT);
  }

}

-----
public class VideoPtsScanner {

  public static List<Long> scanPts(String path) throws IOException {
    MediaExtractor mediaExtractor = new MediaExtractor();
    mediaExtractor.setDataSource(path);
    mediaExtractor.selectTrack(getVideoTrack(mediaExtractor));

    List<Long> ret = new ArrayList();
    long pts = -1;
    while ((pts = mediaExtractor.getSampleTime()) >= 0) {
      ret.add(pts);
      mediaExtractor.advance();
    }

    mediaExtractor.release();

    return ret;
  }

  private static int getVideoTrack(MediaExtractor extractor) {
    for (int i = 0; i < extractor.getTrackCount(); i++) {
      MediaFormat format = extractor.getTrackFormat(i);
      if (isVideo(format)) {
        return i;
      }
    }

    return -1;
  }

  private static boolean isVideo(MediaFormat format) {
    return format.getString(MediaFormat.KEY_MIME).toLowerCase().contains("video");
  }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 053期,大底014 015 017 024 025 027 034 036 041 042 043 045 04...
    想我的D友你就進(jìn)閱讀 394評論 0 1
  • 很高興今天能夠參加信與行成長舍榜樣季的最后一堂課。今天回顧了十個(gè)最關(guān)鍵的種子。學(xué)習(xí)和鞏固了之前學(xué)習(xí)的內(nèi)容,也和大家...
    馬思月閱讀 646評論 0 7
  • 攝影器材:紅米note 攝影者:歸壹 所感: 最美如你,遇見是一種美麗的意外。或驚喜,或贊嘆,或回味。這一切的一切...
    夏小二兒閱讀 325評論 0 4

友情鏈接更多精彩內(nèi)容