Codificación y muxing de vídeo mediante MediaCodec y MediaMuxer

Estoy desarrollando una aplicación donde descodificar un video y reemplazar ciertos fotogramas y volver a codificar con MediaMuxer y MediaCodec . La aplicación funciona si no reemplazo los fotogramas (excepto los videos de 1080p como explico a continuación), pero cuando lo hago, los fotogramas después de los reemplazados son pixelados y el video es intermitente.

Además, cuando pruebo mi aplicación con videos de 1920×1080, obtengo una extraña salida, donde el video no muestra nada, hasta que me desplazo hasta el principio del video, entonces el video empieza a aparecer (pero con el mismo problema mencionado anteriormente de Pixalación después de la edición.

Aquí es cómo configuro mi codificador:

 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); 

Así que para resumir, tengo dos problemas:

1- Cuadros pixelados y video intermitente después de marcos modificados.

2- Corrompido videos 1920×1080 a menos que me desplace al principio.

Editar

Aquí hay un ejemplo de video 1080p sin editar, que da una pantalla verde cuando juego en VLC y juega incorrectamente en el teléfono a menos que me desplace para empezar y ahora extrañamente funciona normalmente en YouTube, a excepción de un marco verde en el inicio

Aquí hay un ejemplo de video 720p editado con un marco verde al inicio y borrado de pixelación y retraso después de la edición

Aquí está el código que utilizo para decodificar un re-encode:

 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()); 

El método de decodificación:

 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; } 

Y aquí está el método de codificación:

 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(); } } } } 

La pixelación es más probable debido a marcas de tiempo de marco incorrectas, así que asegúrese de que las marcas de tiempo de sus marcos aumentan de forma monótona y son las mismas cuando las pasa a MediaCodec y MediaMuxer. En este caso específico, sólo necesita reemplazar los datos del marco que va a reemplazar, dejando su marca de tiempo tal como estaba en el flujo original.

Asegúrese de que está convirtiendo el mapa de bits en el espacio de color YUV y está utilizando un formato de píxeles correcto. Android almacena bitmaps en RGBA con 4 bytes por píxel, necesita convertirlo en YUV con valor Y para cada píxel y valores U y V para un bloque de 2×2, luego los coloca en planos separados en el array de bytes que va al directorio Codec

También, hace algún tiempo hice una aplicación de ejemplo que cambia el tamaño de los videos con MediaCodec, también puede ayudarte: https://github.com/grishka/android-video-transcoder

  • Marco de vídeo de captura de android
  • Reproduce vídeos HTML5 almacenados localmente en Android
  • ¿Por qué cambia la resolución de vídeo al transmitir desde Android a través de WebRTC
  • ¿Por qué mi videoview no está reproduciendo?
  • Utilice Video chat en segundo plano como Skype usando WebRTC en Android
  • Android 3.1 / Galaxy Tab / VideoView no se reproducirá
  • Descifrar audio / video sobre la marcha a MediaPlayer
  • MediaCodec problemas de muxing de audio / video y Android
  • Android no puede usar hdmi en la pestaña creativa y necesita ser notificado en todos los dispositivos (máximo) tan pronto como hdmi esté conectado
  • Captura de video de MediaRecorder en modo retrato
  • MediaMuxer no puede hacer MP4 que son streamable
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.