2013-03-19 54 views
5

我的目標是使用Android MediaCodec解碼視頻流,然後使用輸出圖像進行本機代碼中的進一步圖像處理。使用硬件加速的本機代碼訪問衝突Android MediaCodec解碼器

平臺:華碩tf700t android 4.1.1。 測試流:H.264全高清@ 24 frm/s

隨着Tegra-3 SoC的內部,我計算硬件支持視頻解碼。在功能上,我的應用程序的行爲如預期:我確實可以訪問解碼器圖像 並正確處理它們。但是,我經歷了非常高的解碼器CPU負載。

在下面的實驗中,過程/螺紋加載通過adb shell中的「top -m 32 -t」來測量。爲了從「頂部」獲得可靠的輸出,通過運行幾個線程永久以最低優先級循環,所有4個cpu內核被強制激活。這通過反覆執行「cat/sys/devices/system/cpu/cpu [0-3]/online」來確認。爲了簡單起見,只有視頻解碼,沒有音頻;並且沒有時序控制,所以解碼器運行得儘可能快。

第一個實驗:運行應用程序,調用JNI處理函數,但所有進一步處理調用都被註釋掉。結果:

  • 吞吐量:25 FRM /秒
  • 1%應用過程/系統/ bin中的螺紋Binder_3的
  • 24%負載/媒體服務器

它的螺紋VideoDecoder的負載似乎解碼速度是CPU限制的(四核CPU的25%)... 啓用輸出處理時,解碼圖像是正確的,並且應用程序工作。唯一的問題:用於解碼的CPU負載過高。

經過大量的實驗後,我考慮給MediaCodec一個表面來繪製其結果。在所有其他方面,代碼是相同的。結果:

  • 吞吐量55 FRM /秒(很好!!)的應用程序的線程的VideoDecoder
  • 2%負載
  • 過程/系統/ bin中/媒體服務器的螺紋媒體服務器的
  • 1%負載

確實,視頻顯示在提供的Surface上。由於幾乎沒有任何CPU負載,這必須是硬件加速...

看來,MediaCodec是隻使用硬件加速,如果提供表面?

到目前爲止,這麼好。我已經傾向於將Surface用作解決方案(不是必需的,但在某些情況下甚至是非常好的)。但是,如果提供表面,我無法訪問輸出圖像!結果是本機代碼中的訪問衝突。

這真令我困惑!我沒有看到訪問限制的任何概念,或文檔http://developer.android.com/reference/android/media/MediaCodec.html中的任何內容。 也沒有在這方面提到在谷歌I/O介紹http://www.youtube.com/watch?v=RQws6vsoav8

所以:如何使用硬件加速的Android MediaCodec解碼器和訪問本地代碼中的圖像?如何避免訪問違規?任何幫助都附帶了!還有任何解釋或提示。

我很肯定MediaExtractor和MediaCodec使用得當,因爲應用程序 功能正常(只要我沒有提供Surface)。 它仍然是相當實驗性,和良好的API設計是待辦事項列表;-)

注意兩個實驗之間的唯一區別是可變的mSurface上:空或「mDecoder.configure的實際表面 (mediaFormat ,mSurface,null,0);「

初始化代碼:

mExtractor = new MediaExtractor(); 
mExtractor.setDataSource(mPath); 

// Locate first video stream 
for (int i = 0; i < mExtractor.getTrackCount(); i++) { 
    mediaFormat = mExtractor.getTrackFormat(i); 
    String mime = mediaFormat.getString(MediaFormat.KEY_MIME); 
    Log.i(TAG, String.format("Stream %d/%d %s", i, mExtractor.getTrackCount(), mime)); 
    if (streamId == -1 && mime.startsWith("video/")) { 
     streamId = i; 
    } 
} 

if (streamId == -1) { 
    Log.e(TAG, "Can't find video info in " + mPath); 
    return; 
} 

mExtractor.selectTrack(streamId); 
mediaFormat = mExtractor.getTrackFormat(streamId); 

mDecoder = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME)); 
mDecoder.configure(mediaFormat, mSurface, null, 0); 

width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH); 
height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT); 
Log.i(TAG, String.format("Image size: %dx%d format: %s", width, height, mediaFormat.toString())); 
JniGlue.decoutStart(width, height); 

解碼器環路(在單獨的線程中運行):

ByteBuffer[] inputBuffers = mDecoder.getInputBuffers(); 
ByteBuffer[] outputBuffers = mDecoder.getOutputBuffers(); 

while (!isEOS && !Thread.interrupted()) { 
    int inIndex = mDecoder.dequeueInputBuffer(10000); 
    if (inIndex >= 0) { 
     // Valid buffer returned 
     int sampleSize = mExtractor.readSampleData(inputBuffers[inIndex], 0); 
     if (sampleSize < 0) { 
      Log.i(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM"); 
      mDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 
      isEOS = true; 
     } else { 
      mDecoder.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0); 
      mExtractor.advance(); 
     } 
    } 

    int outIndex = mDecoder.dequeueOutputBuffer(info, 10000); 
    if (outIndex >= 0) { 
     // Valid buffer returned 
     ByteBuffer buffer = outputBuffers[outIndex]; 
     JniGlue.decoutFrame(buffer, info.offset, info.size); 
     mDecoder.releaseOutputBuffer(outIndex, true); 
    } else { 
     // Some INFO_* value returned 
     switch (outIndex) { 
     case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: 
      Log.i(TAG, "RunDecoder: INFO_OUTPUT_BUFFERS_CHANGED"); 
      outputBuffers = mDecoder.getOutputBuffers(); 
      break; 
     case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: 
      Log.i(TAG, "RunDecoder: New format " + mDecoder.getOutputFormat()); 
      break; 
     case MediaCodec.INFO_TRY_AGAIN_LATER: 
      // Timeout - simply ignore 
      break; 
     default: 
      // Some other value, simply ignore 
      break; 
     } 
    } 

    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 
     Log.d(TAG, "RunDecoder: OutputBuffer BUFFER_FLAG_END_OF_STREAM"); 
     isEOS = true; 
    } 
} 
+0

仍然沒有解決方案。任何建議仍然受歡迎。此外還提供實驗以增加理解的建議。任何人使用MediaCodec獲得硬件解碼工作?也許在其他平臺上? – Bram 2013-03-25 10:53:51

+0

布拉姆,我試圖解決完全相同的問題。看起來這種放緩不是解碼緩衝區的多個副本。當解碼數據意圖呈現給本地表面時,它看起來有一些直接的數據路徑,它使用TILER(平鋪渲染)。當你需要訪問完整的YUV幀(例如你想訪問已解碼的緩衝區)時,解碼器需要完成一些額外的任務,比如將所有數據渲染到內存緩衝區並複製,這會使得緩慢。我從字面上浪費了一生的時間來解決這個問題,但似乎沒有什麼可以解決的。 – Pavel 2013-06-23 21:20:05

+0

更重要的是,在我的情況下,我有一個720p @ 30fps,我無法實時解碼,而本地播放器沒有問題付費。 – Pavel 2013-06-23 21:21:33

回答

3

如果配置的輸出表面,解碼的數據被寫入到該可被用作一個OpenGL ES紋理(通過「外部紋理」擴展名)的圖形緩衝器。硬件的各個部分以他們喜歡的格式獲取數據,並且CPU不必複製數據。

如果您未配置曲面,則輸出會進入java.nio.ByteBuffer。至少有一個緩衝區副本可以將數據從MediaCodec分配的緩衝區中獲取到您的ByteByffer,並且可能是另一個副本,以便將數據返回到您的JNI代碼中。我期望你看到的是開銷成本,而不是軟件解碼成本。

可能能夠通過輸出發送到SurfaceTexture,撕心裂肺到FBO或p緩衝器,然後用glReadPixels提取數據,以提高問題。如果您從本地代碼讀入「直接」ByteBuffer或調用glReadPixels,則可以減少JNI開銷。這種方法的缺點是您的數據將採用RGB而不是YCbCr。 (OTOH,如果您想要的轉換可以在GLES 2.0片段着色器來表達,你可以得到GPU做的工作,而不是CPU的。)

正如另一個答案指出,在不同的設備上輸出ByteBuffer數據解碼器以不同的格式,如果可移植性對您很重要,那麼在軟件中解釋數據可能不可行。

編輯:Grafika現在有一個使用GPU做圖像處理的例子。您可以看到演示視頻here

+0

謝謝!所以你用@fadden說,使用一個表面我們互斥來訪問ByteBuffer。缺少[dequeueOutputBuffer]中的文檔(http://developer.android.com/reference/android/media/MediaCodec.html#dequeueOutputBuffer%28android.media.MediaCodec.BufferInfo,%20long%29)?!?!那麼,對我而言,我需要YCbCr的本地代碼,所以移動到GLES並沒有幫助。此外,表面僅用於測試。關於複製數據?那需要40ms(25 frm/s)?在我的本地代碼中,我可以在11 ms內完成此操作。所以,仍然太多的CPU負載。對?順便說一句。便攜性不是我最關心的問題。 – Bram 2013-04-09 12:31:11

+1

'MediaCodec'文檔可能會更好。很難知道所有時間在哪裏都看不到你的設備正在做什麼 - 它們都有點不同。 Surface所使用的「原生」格式可能是瘋狂的,他們會在通往ByteBuffer的路上將其轉碼爲更簡單的YUV。從25fps到55fps是40-18 = 22ms的差異 - 兩個緩衝區副本。有時在logcat輸出中,您可以看到它打開(或不打開)硬件解碼器設備。無論如何,我沒有看到你在做什麼錯。 – fadden 2013-04-09 16:47:00

+0

檢查logcat輸出。與沒有表面的情況相比,它不會顯示任何差異(當啓動和停止解碼器時確實會顯示一些信息)。因此,這證實了您的陳述:我們的應用程序不會受軟件解碼的影響,而是來自開銷。所以這關閉了當前的話題。下一個話題顯然將是:如何減少這種開銷?如果android本身可以通過幾乎0的CPU負載將圖像顯示到顯示器上,那麼我怎樣才能在我的應用程序中使用幾乎0的cpu負載來獲取它們? – Bram 2013-04-10 13:24:43

0

我使用的Nexus 4 mediacodec API和得到QOMX_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka的輸出顏色格式。我認爲這種格式是一種硬件格式,只能通過硬件渲染呈現。有趣的是,當我使用null和實際Surface爲MediaCodec配置表面時,輸出緩衝區長度將分別變爲實際值和0。我不知道爲什麼。我認爲你可以在不同設備上做一些實驗以獲得更多結果。 關於硬件加速可以看到 http://www.saschahlusiak.de/2012/10/hardware-acceleration-on-sgs2-with-android-4-0/

+0

感謝您暗示色彩格式。沒有表面,我的'mDecoder.getOutputFormat()。getInteger(「color-format」)'是19(COLOR_FormatYUV420Planar in [link](http://developer.android.com/reference/android/media/MediaCodecInfo.CodecCapabilities.html ))。對於表面,它是256.不知道這意味着什麼......進一步,對於實際的Surface,info.size變爲0.顯然,我不應該嘗試在JniGlue.decoutFrame()中讀取大小爲0的緩衝區。這可以解釋這次事故。但仍然...提供表面只是解決方案,讓硬件解碼運行... – Bram 2013-04-04 16:02:34

+0

輸出到表面,你不會在'ByteBuffer'中得到任何數據。你仍然可以從'dequeueOutputBuffer()'得到一個索引,這樣你就可以得到一個框架可用的通知,並選擇是否用'render' arg把它渲染到'releaseOutputBuffer()'。無法渲染Surface的情況下無法觸摸實際位。 – fadden 2013-04-04 21:38:12

+0

是否設置了decoder.configure(format,null,null,0); OR decoder.configure(format,surface,null,0);我得到的顏色格式是相同的 - COLOR_QCOM_FormatYUV420SemiPlanar - 恆定值:2141391872(0x7fa30c00)Nexus 7和COLOR_TI_FormatYUV420PackedSemiPlanar - 恆定值:2130706688(0x7f000100)Galaxy Nexus。爲什麼? – Harkish 2013-10-14 20:47:25