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.
- MediaPlayer no puede procesar en TextureView después de renderizar imágenes
- Capture la pantalla de Android y cree el video programáticamente
- ¿Cómo puedo capturar una grabación de vídeo en Android?
- Decodificación de flujo de vídeo en Android de DJI drone
- Android: Transmitir datos de la cámara y escribirlos en el servidor
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(); } } } }
- ¿Cómo reproducir varios vídeos en una vista de lista con MediaPlayer?
- Intención de tomar video en android
- Vídeo inverso en android
- Procesamiento de vídeo de Android marco por cuadro durante la grabación
- Picasso image loading library: cómo cargar miniaturas de video
- ¿Cómo podemos reproducir código incrustado de YouTube en una aplicación de Android mediante webview?
- Decodificación h264 ByteStream en Android
- Los videos de incrustación de YouTube no funcionan en la vista web. ¿Qué tiene de malo este código?
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
- Android "gps requiere ACCESS_FINE_LOCATION" error, aunque mi archivo de manifiesto contiene este
- ¿Cómo forzar la clase derivada a llamar al método super? (Al igual que Android)