2013-06-13 2 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 기본 스트림입니다. 나는 리눅스 용 토템 미디어 플레이어가 그들을 재생할 수 있다는 것을 발견했다.

정말로 원하는 것은 출력을 .mp4 파일로 변환하는 것입니다. (업데이트 :) Android 4.3 (API 18)은 MediaMuxer 클래스를 도입했습니다.이 클래스는 원시 데이터 (선택 오디오 스트림)를 .mp4 파일로 변환하는 방법을 제공합니다.

ByteBuffer의 데이터 레이아웃은 Android 4.3부터 기기에 따라 다릅니다. 실제로 모든 장치가 COLOR_FormatYUV420Planar을 지원하는 것은 아닙니다. 일부는 반 평면 변형을 선호합니다. 장치를 모른 채 정확한 레이아웃을 말할 수는 없지만 압축되지 않은 데이터가 필요하므로 압축 된 PNG로 전달한다고 말할 수 있습니다. 데이터가 작동하지 않습니다.

(업데이트 :) Android 4.3에서는 MediaCodec 인코더로 표면 입력을 허용하므로 OpenGL ES로 렌더링 할 수있는 모든 항목을 기록 할 수 있습니다. 예를 들어, EncodeAndMuxTest 샘플 here을 참조하십시오. 당신이 즉, 파일 이름 확장자에 "H264"로 설정하고 MPlayer는 그것을 재생할 수 있습니다

+0

출력을 PC로 옮기고 mp4로 인코딩 할 수 있습니까? 호환되는 모든 Android 기기의 입력 형식을 보편적으로 결정할 수있는 방법이 있습니까? – BVB

+1

전환 기기를 오프라인에서 사용할 수 있다고 생각하지만 추천 할만한 특정 프로그램이 없습니다. Android 4.2부터 코덱에서 지원하는 형식을 쿼리 할 수 ​​있지만 일부 기기는 형식을 동일하게 취급하지는 않습니다 (일부는 채도 데이터의 시작에 정렬 제한이 있음). – fadden

+1

안드로이드 4.3은 서피스 인풋과'MediaMuxer' 변환을 .mp4로 추가했습니다. 답변이 업데이트되었습니다. – fadden

4
  1. 인코더 출력은 "원시"H264입니다 mplayer ./your_output.h264
  2. 이 한가지 더 : 당신이 그 프레임이 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로 변환되어야합니다. 전환이 시간이 중요한 프로세스가 아니라면 자신의 자바 코드를 작성할 수 있지만 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 단계 : 인코딩

이것은 당신이 놓친 가장 중요한 부분입니다. 이 부분에서는 출력 전에 입력 버퍼를 준비했습니다. 입력 버퍼가 큐에 넣어 졌을 때, 출력 버퍼는 encode 할 준비가되어 있습니다.

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