2012-11-19 84 views
27

我試圖讓這個在Android 4.1上工作(使用升級的華碩變壓器平板電腦)。感謝Alex's response to my previous question,我已經能夠將一些原始的H.264數據寫入文件,但這個文件只能在ffplay -f h264下播放,並且好像丟失了有關幀速率(極快的播放)的所有信息。此外,顏色空間看起來不正確(在編碼器側使用相機的默認值atm)。使用Android MediaCodec從相機編碼H.264

public class AvcEncoder { 

private MediaCodec mediaCodec; 
private BufferedOutputStream outputStream; 

public AvcEncoder() { 
    File f = new File(Environment.getExternalStorageDirectory(), "Download/video_encoded.264"); 
    touch (f); 
    try { 
     outputStream = new BufferedOutputStream(new FileOutputStream(f)); 
     Log.i("AvcEncoder", "outputStream initialized"); 
    } catch (Exception e){ 
     e.printStackTrace(); 
    } 

    mediaCodec = MediaCodec.createEncoderByType("video/avc"); 
    MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 320, 240); 
    mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000); 
    mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15); 
    mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar); 
    mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); 
    mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 
    mediaCodec.start(); 
} 

public void close() { 
    try { 
     mediaCodec.stop(); 
     mediaCodec.release(); 
     outputStream.flush(); 
     outputStream.close(); 
    } catch (Exception e){ 
     e.printStackTrace(); 
    } 
} 

// called from Camera.setPreviewCallbackWithBuffer(...) in other class 
public void offerEncoder(byte[] input) { 
    try { 
     ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); 
     ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers(); 
     int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1); 
     if (inputBufferIndex >= 0) { 
      ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; 
      inputBuffer.clear(); 
      inputBuffer.put(input); 
      mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0); 
     } 

     MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 
     int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0); 
     while (outputBufferIndex >= 0) { 
      ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; 
      byte[] outData = new byte[bufferInfo.size]; 
      outputBuffer.get(outData); 
      outputStream.write(outData, 0, outData.length); 
      Log.i("AvcEncoder", outData.length + " bytes written"); 

      mediaCodec.releaseOutputBuffer(outputBufferIndex, false); 
      outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); 

     } 
    } catch (Throwable t) { 
     t.printStackTrace(); 
    } 

} 

更改編碼器類型爲「視頻/ MP4」顯然解決了幀率-問題,但由於主要目標是使流媒體服務,這不是一個很好的解決方案。

我知道我放棄了Alex的一些代碼,考慮到SPS和PPS NALU的代碼,但我希望這不是必須的,因爲這些信息也來自outData,我假定編碼器會正確格式化。如果情況並非如此,我應該如何在文件/流中安排不同類型的NALU?

那麼,我爲了製作一個有效的,正在運行的H.264數據流而錯過了什麼?我應該使用哪些設置來使相機的色彩空間和編碼器的色彩空間匹配?

我有一種感覺,這是一個比Android/MediaCodec主題更多的H.264相關問題。或者我仍然沒有正確使用MediaCodec API?

在此先感謝。

+0

我已經遇到了很多有關Android媒體播放器的問題,以及Android手機的不同行爲不同。你有可能做轉換服務器端嗎? –

+1

我正在處理類似的功能,但是當從Camera.setPreviewCallbackWithBuffer(...)調用offerEncoder(...)時,我當前正在獲取java.nio.BufferOverflowException,是否可以共享您在Camera.setPreviewCallbackWithBuffer中執行的方法(......)。非常感謝。 – lucasweb

+0

代碼似乎沒問題,除了發生在inputBuffer.put(input)上的java.nio.BufferOverflowException;聲明。試圖將字節拆分爲buffer.capacity()塊,最後在MediaCodec.queueInputBuffer調用時出現IllegalStateException錯誤。 任何想法如何可以糾正? –

回答

7

對於您的快速回放 - 幀速率問題,您無需在此處進行任何操作。由於它是一個流媒體解決方案,所以必須提前告知對方幀速率或每幀有時間戳。這兩個都不是基本流的一部分。要麼選擇預先確定的幀率,要麼傳遞某些sdp或類似的東西,或者使用現有的協議,如rtsp。在第二種情況下,時間戳是以類似rtp的形式發送的流的一部分。然後客戶端必須放棄rtp流並播放它。這是基本流媒體的工作原理。 [要麼固定你的幀速率,如果你有一個固定的速率編碼器或給時間戳]

本地PC播放將會很快,因爲它不會知道fps。通過在輸入前給出fps參數,例如

ffplay -fps 30 in.264 

您可以控制PC上的播放。

至於文件不可播放:是否有SPS和PPS。你也應該啓用NAL頭文件 - 附件b格式。我對android並不瞭解,但是這是任何h.264基本流都可以播放的要求,因爲它們不在任何容器中,需要稍後轉儲和播放。 如果android的默認值是mp4,但默認annexb標題將關閉,所以也許有一個開關來啓用它。或者,如果您逐幀獲取數據,請自行添加。

至於顏色格式:我猜想默認應該工作。所以不要設置它。 如果不嘗試422平面或UVYV/VYUY交錯格式。通常相機就是其中之一。 (但不是必要的,這些可能是我經常遇到的那些)。

2

您可以查詢MediaCodec支持的位圖格式並查詢您的預覽。 問題是,一些MediaCodecs只支持預覽無法獲得的專有打包YUV格式。 特別是2130706688 = 0x7F000100 = COLOR_TI_FormatYUV420PackedSemiPlanar。用於預覽 默認格式是17 = NV21 = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV411Planar = 420的YCbCr半平面

+0

此外,他們中的一些人聲稱支持一種格式,但提供另一種格式(例如NV12/NV21在很多三星設備上都搞砸了,而有些三星設備在您要求NV格式時會提供一種YV格式)。這很令人沮喪。 –

6

您可以轉換色彩空間就是這樣,如果你已經設置預覽色彩空間YV12:

public static byte[] YV12toYUV420PackedSemiPlanar(final byte[] input, final byte[] output, final int width, final int height) { 
     /* 
     * COLOR_TI_FormatYUV420PackedSemiPlanar is NV12 
     * We convert by putting the corresponding U and V bytes together (interleaved). 
     */ 
     final int frameSize = width * height; 
     final int qFrameSize = frameSize/4; 

     System.arraycopy(input, 0, output, 0, frameSize); // Y 

     for (int i = 0; i < qFrameSize; i++) { 
      output[frameSize + i*2] = input[frameSize + i + qFrameSize]; // Cb (U) 
      output[frameSize + i*2 + 1] = input[frameSize + i]; // Cr (V) 
     } 
     return output; 
    } 

或者

public static byte[] YV12toYUV420Planar(byte[] input, byte[] output, int width, int height) { 
     /* 
     * COLOR_FormatYUV420Planar is I420 which is like YV12, but with U and V reversed. 
     * So we just have to reverse U and V. 
     */ 
     final int frameSize = width * height; 
     final int qFrameSize = frameSize/4; 

     System.arraycopy(input, 0, output, 0, frameSize); // Y 
     System.arraycopy(input, frameSize, output, frameSize + qFrameSize, qFrameSize); // Cr (V) 
     System.arraycopy(input, frameSize + qFrameSize, output, frameSize, qFrameSize); // Cb (U) 

     return output; 
    } 
+0

非常有幫助。你的第二個功能正是我所需要的。能夠獲取YV12預覽數據並將其轉換爲YUV420平面以輸入到MediaCodec編碼器。 –

+0

在這個答案中有些東西聽起來不正確。在剪切的第一個代碼中,方法名稱是「YV12toYUV420PackedSemiPlanar」。但是,您在下面提到「COLOR_TI_FormatYUV420PackedSemiPlanar是NV12」。 –

+0

@UkarshSinha http://www.fourcc.org/yuv.php一個很好的閱讀,NV12 _is_ YUV420。我的問題是節省太少的幀。現在我需要看太陽2moro來檢查顏色。 – John

2

如果您沒有明確要求的其他像素格式,相機預覽緩衝區將被稱爲NV21一個YUV格式420到達,爲此COLOR_FormatYCrYCb是MediaCodec等效。

不幸的是,正如本頁其他答案所述,不能保證在您的設備上,AVC編碼器支持這種格式。請注意,存在一些不支持NV21的奇怪設備,但我不知道任何可以升級到API 16的設備(因此具有MediaCodec)。

谷歌文檔中還稱,YV12平面YUV必須支持的相機預覽格式與API的所有設備> = 12,因此,它可能是有益的嘗試它(相當於MediaCodec是COLOR_FormatYUV420Planar您在您的代碼段使用)。

更新:正如Andrew Cottrell提​​醒我的那樣,YV12仍然需要色度交換才能成爲COLOR_FormatYUV420Planar。

+0

根據我的經驗,YV12預覽數據和YUV420Planar格式不相同。我必須使用本頁另一個答案中的YV12toYUV420Planar()函數來從一個轉換到另一個。 –

+1

@AndrewCottrell:感謝您的糾正! –

7

Android 4.3(API 18)提供了一個簡單的解決方案。 MediaCodec類現在接受來自Surfaces的輸入,這意味着您可以將相機的Surface預覽連接到編碼器並繞過所有奇怪的YUV格式問題。

還有一個新的MediaMuxer class將您的原始H.264流轉換爲.mp4文件(可選地在音頻流中混合)。

查看CameraToMpegTest source的一個例子。 (它還演示了使用OpenGL ES片段着色器在錄製視頻時對視頻執行簡單編輯。)

+0

我已將CameraToMpegTest兼容性測試套件示例修改爲常規活動。 https://github.com/OnlyInAmerica/HWEncoderExperiments – dbro

+1

現在在Grafika中有一個「show + capture camera」示例(https://github.com/google/grafika),它將相機預覽記錄爲.mp4,同時顯示它在屏幕上。 – fadden

+0

@fadden是這個mp4可以同時流式傳輸嗎? – ingsaurabh