2016-11-11 112 views
0

我正在做一個使用MediaCodec的代碼轉換器。通過MediaCodec轉碼h264視頻直接與紋理渲染

我創建了兩個mediacodec實例,一個用於解碼,另一個用於編碼。我試圖直接發送解碼器outputBuffer到編碼器inputBuffer。

編譯和執行似乎沒有問題,並且運行速度很快。 但輸出的視頻文件有什麼wrong.I檢查輸出視頻的元數據,並且他們都是對的:比特率,幀率,分辨率......只有在視頻圖像是錯誤這樣的:screen shot

我想它有一些錯誤,但我不知道它...

我搜索的庫和文件,我發現了一些示例代碼使用紋理表面渲染解碼器輸出數據並將數據傳輸到編碼器。但我認爲它不應該是我需要的。因爲我不需要編輯視頻的圖像。我只需要改變比特率和分辨率以使文件的尺寸更小。

這裏是我的項目中核心代碼:

private void decodeCore() { 
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 
    int frameCount = 0; 
    while (mDecodeRunning) { 

     int inputBufferId = mDecoder.dequeueInputBuffer(50); 
     if (inputBufferId >= 0) { 
      // fill inputBuffers[inputBufferId] with valid data 
      int sampleSize = mExtractor.readSampleData(mDecodeInputBuffers[inputBufferId], 0); 
      if (sampleSize >= 0) { 
       long time = mExtractor.getSampleTime(); 
       mDecoder.queueInputBuffer(inputBufferId, 0, sampleSize, time, 0); 
      } else { 
       mDecoder.queueInputBuffer(inputBufferId, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 
      } 

      mExtractor.advance(); 
     } 

     int outputBufferId = mDecoder.dequeueOutputBuffer(bufferInfo, 50); 
     if (outputBufferId >= 0) { 

      FrameData data = mFrameDataQueue.obtain(); 
      //wait until queue has space to push data 
      while (data == null) { 
       try { 
        Thread.sleep(20); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
       data = mFrameDataQueue.obtain(); 
      } 

      data.data.clear(); 
      data.size = 0; 
      data.offset = 0; 
      data.flag = 0; 
      data.frameTimeInUs = bufferInfo.presentationTimeUs; 

      // outputBuffers[outputBufferId] is ready to be processed or rendered. 
      if (bufferInfo.size > 0) { 
       ByteBuffer buffer = mDecodeOutputBuffers[outputBufferId]; 

       buffer.position(bufferInfo.offset); 
       buffer.limit(bufferInfo.offset + bufferInfo.size); 

       data.data.put(buffer); 
       data.data.flip(); 

       data.size = bufferInfo.size; 
       data.frameIndex = frameCount++; 

      } 

      data.flag = bufferInfo.flags; 

      if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) { 
       Log.d("bingbing_transcode", "decode over! frames:" + (frameCount - 1)); 
       mDecodeRunning = false; 
      } 

      mFrameDataQueue.pushToQueue(data); 
      mDecoder.releaseOutputBuffer(outputBufferId, false); 
      Log.d("bingbing_transcode", "decode output:\n frame:" + (frameCount - 1) + "\n" + "size:" + bufferInfo.size); 
     } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 
      mDecodeOutputBuffers = mDecoder.getOutputBuffers(); 
     } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 
      // Subsequent data will conform to new format. 
      mDecodeOutputVideoFormat = mDecoder.getOutputFormat(); 
      configureAndStartEncoder(); 
     } 
    } 

    mDecoder.stop(); 
    mDecoder.release(); 
} 

private void encodeCore() { 
    int trackIndex = 0; 
    boolean muxerStarted = false; 
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 
    int frameCount = 0; 
    while (mEncodeRunning) { 
     int inputBufferId = mEncoder.dequeueInputBuffer(50); 
     if (inputBufferId >= 0) { 
      FrameData data = mFrameDataQueue.pollFromQueue(); 
      //wait until queue has space to push data 
      while (data == null) { 
       try { 
        Thread.sleep(20); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
       data = mFrameDataQueue.obtain(); 
      } 

      if (data.size > 0) { 
       ByteBuffer inputBuffer = mEncodeInputBuffers[inputBufferId]; 
       inputBuffer.clear(); 
       inputBuffer.put(data.data); 
       inputBuffer.flip(); 
      } 
      mEncoder.queueInputBuffer(inputBufferId, 0, data.size, data.frameTimeInUs, data.flag); 
      mFrameDataQueue.recycle(data); 
     } 

     int outputBufferId = mEncoder.dequeueOutputBuffer(bufferInfo, 50); 
     if (outputBufferId >= 0) { 
      // outputBuffers[outputBufferId] is ready to be processed or rendered. 
      ByteBuffer encodedData = mEncodeOutputBuffers[outputBufferId]; 

      if (bufferInfo.size > 0) { 
       if (encodedData == null) { 
        throw new RuntimeException("encoderOutputBuffer " + outputBufferId + " was null"); 
       } 

       if (!muxerStarted) { 
        throw new RuntimeException("muxer hasn't started"); 
       } 

       frameCount++; 
      } 
      // adjust the ByteBuffer values to match BufferInfo (not needed?) 
      encodedData.position(bufferInfo.offset); 
      encodedData.limit(bufferInfo.offset + bufferInfo.size); 

      mMuxer.writeSampleData(trackIndex, encodedData, bufferInfo); 

      if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) { 
       Log.d("bingbing_transcode", "encode over! frames:" + (frameCount - 1)); 
       mEncodeRunning = false; 
      } 

      mEncoder.releaseOutputBuffer(outputBufferId, false); 
      Log.d("bingbing_transcode", "encode output:\n frame:" + (frameCount - 1) + "\n" + "size:" + bufferInfo.size); 

     } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 
      mEncodeOutputBuffers = mEncoder.getOutputBuffers(); 
     } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 
      // should happen before receiving buffers, and should only happen once 
      if (muxerStarted) { 
       throw new RuntimeException("format changed twice"); 
      } 

      MediaFormat newFormat = mEncoder.getOutputFormat(); 
      Log.d("bingbing_transcode", "encoder output format changed: " + newFormat); 

      // now that we have the Magic Goodies, start the muxer 
      trackIndex = mMuxer.addTrack(newFormat); 
      mMuxer.start(); 
      muxerStarted = true; 
      mEncodeOutputVideoFormat = newFormat; 
     } 
    } 


    mEncoder.stop(); 
    mEncoder.release(); 
    if (muxerStarted) { 
     mMuxer.stop(); 
     mMuxer.release(); 
    } 
} 

在兩個不同的線程運行這兩個功能。

FrameData是幀字節緩衝區和幀當前時間和東西的一個簡單的存儲需要

回答

0

當使用的ByteBuffer輸入,有幾個細節是未定義的關於輸入數據佈局。當寬度不是16的倍數時,一些編碼器想要將輸入數據行長度填充到16的倍數,而另一些編碼器將假定與寬度相等的行長度,沒有額外的填充。

對於bytebuffer輸入的編碼,Android CTS測試(它定義了所有設備可以預期的行爲)有意識地只測試16倍數的分辨率,因爲他們知道不同的硬件供應商的做法不同,我想要執行任何特定的處理。

您通常不能假定解碼器輸出會使用與編碼器消耗的行相似的行大小。解碼器可以自由地(實際上可以)返回比實際內容大小大得多的寬度,並且使用crop_left/crop_right字段來指示它的實際部分實際上是否可見。因此,如果解碼器是這樣做的,則不能將數據直接從解碼器傳送到編碼器,除非您將其逐行復制,並考慮到解碼器和編碼器使用的實際行大小。

此外,你甚至不能假設解碼器使用與編碼器類似的像素格式。許多Qualcomm設備使用特殊的平鋪像素格式作爲解碼器輸出,而編碼器輸入是正常的平面數據。在這些情況下,在將數據送入編碼器之前,必須先實現一個非常複雜的邏輯來解除數據混淆。

使用紋理表面作爲中間層可隱藏所有這些細節。對於您的用例來說,這聽起來可能不完全需要,但它確實隱藏瞭解碼器和編碼器之間緩衝區格式的所有變化。