2013-06-13 181 views
21

私はh264にある一連のビットマップをエンコードしたいと思います。これはMediaEncoder経由で可能ですか?私はそれを行うためにいくつかのコードを書いていますが、出力は私が試したメディアプレーヤーで再生することはできません。ここで私が主にStackoverflowで見つけた他のソースから借りたコードのいくつかがあります。MediaCodecを使用してビットマップをビデオにエンコードする方法は?

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

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 
image.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); // image is the bitmap 
byte[] input = byteArrayOutputStream.toByteArray(); 

int inputBufferIndex = mMediaCodec.dequeueInputBuffer(-1); 
if (inputBufferIndex >= 0) { 
    ByteBuffer inputBuffer = mInputBuffers[inputBufferIndex]; 
    inputBuffer.clear(); 
    inputBuffer.put(input); 
    mMediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0); 
} 

どうすればよいですか?

答えて

12

MediaCodecの出力は、生のH.264エレメンタリストリームです。私は、Linux用のTotemメディアプレーヤーがそれらを再生できることを発見しました。

本当にやりたいことは、出力を.mp4ファイルに変換することです。 (更新:)Android 4.3(API 18)は、MediaMuxerクラスを導入しました。このクラスは、生データ(およびオプションのオーディオストリーム)を.mp4ファイルに変換する方法を提供します。

ByteBufferのデータのレイアウトは、Android 4.3以降、デバイスによって異なります。 (実際にはすべてのデバイスがCOLOR_FormatYUV420Planarをサポートしているわけではありません - 半平面のバリアントを好む人もいます)デバイスを知らずに正確なレイアウトを教えてもらえませんが、圧縮されていないデータデータは機能しません。

更新:)Android 4.3では、MediaCodecエンコーダへのSurface入力も可能で、OpenGL ESでレンダリングできるものはすべて記録できます。たとえば、EncodeAndMuxTestサンプルhereを参照してください。

+0

出力をPCに移動してmp4にエンコードすることは可能でしょうか?互換性のあるすべてのAndroid搭載デバイスの入力形式を一般的に判断する方法はありますか? – BVB

+1

私はあなたが変換オフデバイスを行うことができると思うが、私は推薦する特定のプログラムがありません。 Android 4.2以降では、サポートするフォーマットのコーデックを照会することはできますが、すべてのデバイスで同じようにフォーマットが処理されるわけではありません(一部のデバイスでは、クロマデータの開始位置の制限があります)。 – fadden

+1

Android 4.3ではサーフェス入力が追加され、MediaMuxerは.mp4に変換されました。回答が更新されました。 – fadden

4
    あなたが「H264」に、ファイル名の拡張子を設定し、mplayerのとそれを再生することができますので、
  1. エンコーダ出力は、すなわちmplayer ./your_output.h264
  2. もう一つの「生」H264です:あなたは、そのフレームがCOLOR_FormatYUV420Planar色になりますエンコーダすると言いますあなたが彼にPNGコンテンツを与えるように見えるので、出力ファイルにおそらく色の混乱が含まれます。エンコーダに入力する前に、PNGをyuv420(これは、たとえばhttps://code.google.com/p/libyuv/)に変換する必要があります。
+0

他の形式はサポートされていますか?そこに生のビットマップバイトを入れたいのであれば、私は何を使うでしょうか? – BVB

+1

サポートされているフォーマットは、開発者向けのドキュメントhttp://developer.android.com/reference/android/media/MediaCodecInfo.CodecCapabilities.htmlにあります。しかし、いくつかのカラーフォーマットを使用するようにエンコーダーを設定する前に、すべてのコーデックを繰り返し、その機能をチェックする必要があります(このhttp://developer.android.com/reference/android/media/MediaCodecList.htmlを使用)。 – user2399321

+1

エンコーダがCOLOR_Format32bitARGB8888またはCOLOR_Format32bitBGRA8888をサポートしている場合は、PNGコンテンツを使用することができます(ただし、このようなデバイスは見たことがありません)。それ以外の場合は、画像をPNGからyuvに変換する必要があります。変換がタイムクリティカルなプロセスでない場合は、独自のJavaコードを記述できますが、yuvのコンテンツを扱う必要があります(ここでは開始点 - http://www.fourcc.org/yuv.php)。 – user2399321

4

以下の手順を使用して、ビットマップをビデオファイルに変換しました。

ステップ1:私はこのようなエンコーダを用意しました

を準備します。私は、MediaMuxerを使ってmp4ファイルを作成します。

private void prepareEncoder() { 
    try { 
     mBufferInfo = new MediaCodec.BufferInfo(); 

     mediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); 
     mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT); 
     mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate()); 
     mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); 
     if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) { 
      mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar); 
     }else{ 
      mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); 
     } 
     //2130708361, 2135033992, 21 
     mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); 

     final MediaFormat audioFormat = MediaFormat.createAudioFormat(MIME_TYPE_AUDIO, SAMPLE_RATE, 1); 
     audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); 
     audioFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO); 
     audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); 
     audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); 

     mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 
     mediaCodec.start(); 

     mediaCodecForAudio = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO); 
     mediaCodecForAudio.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 
     mediaCodecForAudio.start(); 

     try { 
      String outputPath = new File(Environment.getExternalStorageDirectory(), 
        "test.mp4").toString(); 
      mediaMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 
     } catch (IOException ioe) { 
      throw new RuntimeException("MediaMuxer creation failed", ioe); 
     } 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 

ステップ2:私は、バッファリングのための実行可能作成した

をバッファリング。

private void bufferEncoder() { 
     runnable = new Runnable() { 
      @Override 
      public void run() { 
       prepareEncoder(); 
       try { 
        while (mRunning) { 
         encode(); 
        } 
        encode(); 
       } finally { 
        release(); 
       } 
      } 
     }; 
     Thread thread = new Thread(runnable); 
     thread.start(); 
    } 

ステップ3:エンコーディング

これは、あなたが見逃している最も重要な部分です。このパートでは、出力前に入力バッファを準備しました。入力バッファがキューに入れられると、出力バッファはエンコード可能な状態になります。

public void encode() { 
      while (true) { 
       if (!mRunning) { 
        break; 
       } 
       int inputBufIndex = mediaCodec.dequeueInputBuffer(TIMEOUT_USEC); 
       long ptsUsec = computePresentationTime(generateIndex); 
       if (inputBufIndex >= 0) { 
        Bitmap image = loadBitmapFromView(captureImageView); 
        image = Bitmap.createScaledBitmap(image, WIDTH, HEIGHT, false); 
        byte[] input = getNV21(WIDTH, HEIGHT, image); 
        final ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufIndex); 
        inputBuffer.clear(); 
        inputBuffer.put(input); 
        mediaCodec.queueInputBuffer(inputBufIndex, 0, input.length, ptsUsec, 0); 
        generateIndex++; 
       } 
       int encoderStatus = mediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); 
       if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { 
        // no output available yet 
        Log.d("CODEC", "no output from encoder available"); 
       } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 
        // not expected for an encoder 
        MediaFormat newFormat = mediaCodec.getOutputFormat(); 
        mTrackIndex = mediaMuxer.addTrack(newFormat); 
        mediaMuxer.start(); 
       } else if (encoderStatus < 0) { 
        Log.i("CODEC", "unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus); 
       } else if (mBufferInfo.size != 0) { 
        ByteBuffer encodedData = mediaCodec.getOutputBuffer(encoderStatus); 
        if (encodedData == null) { 
         Log.i("CODEC", "encoderOutputBuffer " + encoderStatus + " was null"); 
        } else { 
         encodedData.position(mBufferInfo.offset); 
         encodedData.limit(mBufferInfo.offset + mBufferInfo.size); 
         mediaMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo); 
         mediaCodec.releaseOutputBuffer(encoderStatus, false); 
        } 
       } 
      } 
     } 
    } 

ステップ4:私たちはエンコードが終了した場合は最後に、その後、ミュクサーとエンコーダをリリース

を解放します。

private void release() { 
     if (mediaCodec != null) { 
      mediaCodec.stop(); 
      mediaCodec.release(); 
      mediaCodec = null; 
      Log.i("CODEC", "RELEASE CODEC"); 
     } 
     if (mediaMuxer != null) { 
      mediaMuxer.stop(); 
      mediaMuxer.release(); 
      mediaMuxer = null; 
      Log.i("CODEC", "RELEASE MUXER"); 
     } 
    } 

私はこれがあなたに役立つことを願っています。

+0

詳細な回答ありがとうございます! – BVB

関連する問題