programing

MediaCodec 및 MediaMuxer를 사용하여 비디오 인코딩 및 다중화

nasanasas 2020. 12. 2. 21:27
반응형

MediaCodec 및 MediaMuxer를 사용하여 비디오 인코딩 및 다중화


내가 비디오를 디코딩하고 특정 프레임 및 재 인코딩 사용을 대체 어디 앱을 개발하고 MediaMuxerMediaCodec. 프레임 (아래에서 설명하는 1080p 동영상 제외)을 교체하지 않으면 앱이 작동하지만 교체하면 교체 된 프레임 이후의 프레임이 픽셀 화되고 동영상이 고르지 않게됩니다.

또한 1920x1080 비디오로 내 앱을 시도하면 비디오의 시작 부분으로 스크롤 할 때까지 비디오가 아무것도 표시되지 않는 이상한 출력이 표시됩니다. 그런 다음 비디오가 표시되기 시작합니다 (하지만 이전에 언급 한 것과 동일한 문제가 있음). 편집 후 pixalation.

인코더를 구성하는 방법은 다음과 같습니다.

Video_format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, interval);
Video_format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
Video_format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
Video_format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);
int color_format=MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar;
Video_format.setInteger(MediaFormat.KEY_COLOR_FORMAT, color_format);

encoder.configure(Video_format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

요약하자면 두 가지 문제가 있습니다.

1- 픽셀 화 프레임 및 수정 된 프레임 후 비디오 끊김.

2- 처음으로 스크롤하지 않으면 손상된 1920x1080 비디오.

편집하다

다음은 편집되지 않은 1080p 비디오 샘플 입니다. VLC에서 재생할 때 녹색 화면이 나타나고 스크롤을 시작하지 않으면 전화기에서 잘못 재생되고 이제는 처음에 녹색 프레임을 제외하고 YouTube에서 이상하게 작동합니다.

다음은 시작 부분에 녹색 프레임을 사용하여 편집 한 샘플 720p 비디오 이며, 편집 후 픽셀 화 및 지연을 제거합니다.

다음은 다시 인코딩을 디코딩하는 데 사용하는 코드입니다.

do{
  Bitmap b1;

  if(edited_frames.containsKey(extractor.getSampleTime()))
    b1=BitmapFactory.decodeFile(edited_frames.get(extractor.getSampleTime()));
  else
    b1=decode(extractor.getSampleTime(),Preview_width,Preview_Height);

  if(b1==null) continue;

  Bitmap b_scal=Bitmap.createScaledBitmap(b1, Preview_width, Preview_Height, false);
  if(b_scal==null) continue;
  encode(b_scal, encoder, muxer, videoTrackIndex);
  lastTime=extractor.getSampleTime();
}while(extractor.advance());

디코딩 방법 :

private Bitmap decode(final long time,final int width,final int height){
  MediaFormat newFormat = codec.getOutputFormat();
  Bitmap b = null;
  final int TIMEOUT_USEC = 10000;
  ByteBuffer[] decoderInputBuffers = codec.getInputBuffers();
  MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();

  boolean outputDone = false;
  boolean inputDone = false;
  while (!outputDone) {
    if (!inputDone) {
      int inputBufIndex = codec.dequeueInputBuffer(TIMEOUT_USEC);
      if (inputBufIndex >= 0) {
        ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];

        int chunkSize = extractor.readSampleData(inputBuf, 0);
        if (chunkSize < 0) {
          codec.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
          inputDone = true;
        } else {
          long presentationTimeUs = extractor.getSampleTime();
          codec.queueInputBuffer(inputBufIndex, 0, chunkSize, presentationTimeUs, 0 );
        }
        inputBuf.clear();
        decoderInputBuffers[inputBufIndex].clear();
      } else {
      }
    }
    ByteBuffer[] outputBuffers;
    if (!outputDone) {
      int decoderStatus = codec.dequeueOutputBuffer(info, TIMEOUT_USEC);
      if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
      } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
        outputBuffers = codec.getOutputBuffers();
      } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
        newFormat = codec.getOutputFormat();
      } else if (decoderStatus < 0) {
      } else { 
        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
          outputDone = true;
        }

        boolean doRender = (info.size != 0);

        codec.releaseOutputBuffer(decoderStatus, false);
        if (doRender) {
          outputBuffers = codec.getOutputBuffers();
          ByteBuffer buffer = outputBuffers[decoderStatus];
          buffer = outputBuffers[decoderStatus];

          outputDone = true;

          byte[] outData = new byte[info.size];
          buffer.get(outData);
          buffer.clear();
          outputBuffers[decoderStatus].clear();
          try {
            int colr_format=-1;
            if(newFormat!=null && newFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT)==21){
              colr_format=ImageFormat.NV21;
            }else if(newFormat!=null && newFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT)!=21){            
              Toast.makeText(getApplicationContext(), "Unknown color format "+format.getInteger(MediaFormat.KEY_COLOR_FORMAT), Toast.LENGTH_LONG).show();
              finish();
              return null;
            }

            int[] arrrr=new int[format.getInteger(MediaFormat.KEY_WIDTH)* format.getInteger(MediaFormat.KEY_HEIGHT)];
            YUV_NV21_TO_RGB(arrrr, outData, format.getInteger(MediaFormat.KEY_WIDTH), format.getInteger(MediaFormat.KEY_HEIGHT));

            lastPresentationTimeUs = info.presentationTimeUs;

            b = Bitmap.createBitmap(arrrr, format.getInteger(MediaFormat.KEY_WIDTH), format.getInteger(MediaFormat.KEY_HEIGHT), Bitmap.Config.ARGB_8888);
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    }
  }
  return b;
}

다음은 인코딩 방법입니다.

private void encode(Bitmap b, MediaCodec encoder, MediaMuxer muxer, int track_indx){
  MediaCodec.BufferInfo enc_info = new MediaCodec.BufferInfo();
  boolean enc_outputDone = false;
  boolean enc_inputDone = false;

  final int TIMEOUT_USEC = 10000;

  ByteBuffer[] encoderInputBuffers = encoder.getInputBuffers();
  ByteBuffer[] enc_outputBuffers = encoder.getOutputBuffers();

  while (!enc_outputDone) {
    if (!enc_inputDone) {
      int inputBufIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
      if (inputBufIndex >= 0) {
        ByteBuffer inputBuf = encoderInputBuffers[inputBufIndex];
        int chunkSize = 0;

        if(b==null){
        }else{
          int mWidth = b.getWidth();
          int mHeight = b.getHeight();

          byte [] yuv = new byte[mWidth*mHeight*3/2];
          int [] argb = new int[mWidth * mHeight];

          b.getPixels(argb, 0, mWidth, 0, 0, mWidth, mHeight);
          encodeYUV420SP(yuv, argb, mWidth, mHeight);

          b.recycle();
          b=null;
          inputBuf.put(yuv);
          chunkSize = yuv.length;
        }

        if (chunkSize < 0) {
          encoder.queueInputBuffer(inputBufIndex, 0, 0, 0L,
                            MediaCodec.BUFFER_FLAG_END_OF_STREAM);
        } else {
          long presentationTimeUs = extractor.getSampleTime();
          Log.i("Encode","Encode Time: "+presentationTimeUs);
          encoder.queueInputBuffer(inputBufIndex, 0, chunkSize, presentationTimeUs, 0);
          inputBuf.clear();

          encoderInputBuffers[inputBufIndex].clear();
          enc_inputDone=true;
        }
      }
    }
    if (!enc_outputDone) {
      int enc_decoderStatus = encoder.dequeueOutputBuffer(enc_info, TIMEOUT_USEC);
      if (enc_decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
      } else if (enc_decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
        enc_outputBuffers = encoder.getOutputBuffers();
      } else if (enc_decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
        MediaFormat newFormat = encoder.getOutputFormat();
      } else if (enc_decoderStatus < 0) {
      } else { 
        if ((enc_info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
          enc_outputDone = true;
      }

      boolean enc_doRender = (enc_info.size != 0);
      encoder.releaseOutputBuffer(enc_decoderStatus, false);
      if (enc_doRender) {
        enc_outputDone = true;
        ByteBuffer enc_buffer = enc_outputBuffers[enc_decoderStatus];

        try {
          muxer.writeSampleData(track_indx, enc_buffer, enc_info);
        } catch (Exception e) {
          e.printStackTrace();
        }
        enc_buffer.clear();
        enc_outputBuffers[enc_decoderStatus].clear();
      }
    }
  }
}

픽셀 화는 잘못된 프레임 타임 스탬프 때문일 가능성이 높으므로 프레임의 타임 스탬프가 단조롭게 증가하고 MediaCodec 및 MediaMuxer에 전달할 때 동일한 지 확인하십시오. 이 특정 경우에는 교체 할 프레임의 데이터 만 교체하면됩니다. 타임 스탬프는 원래 스트림과 동일합니다.

Make sure you're converting the bitmap to the YUV color space and you're using a correct pixel format. Android stores bitmaps in RGBA with 4 bytes per pixel, you need to convert this to YUV with Y value for each pixel and U and V values for a block of 2x2, then lay them out in separate planes in the byte array that goes into the codec.

Also, some time ago I made an example app that resizes videos using MediaCodec, it may help you as well: https://github.com/grishka/android-video-transcoder

참고URL : https://stackoverflow.com/questions/23984200/encoding-and-muxing-video-using-mediacodec-and-mediamuxer

반응형