使用MediaCodec和MediaMuxer编码和混合video
我正在开发一个应用程序,我解码一个video,并replace某些帧,并使用MediaMuxer
和MediaCodec
重新编码。 该应用程序的作品,如果我不replace任何帧(除了1080p的video,我下面解释),但是当我这样做,replace后的帧是像素化,video是断断续续的。
另外,当我用1920×1080的video试用我的应用程序时,我得到一个奇怪的输出,video没有显示任何东西,直到我滚动到video的开始,然后video开始出现(但与之前提到的相同的问题编辑后的pixalation。
以下是我如何configuration我的编码器:
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-像素化帧和修改后的帧video波涛汹涌。
2-破坏1920x1080video,除非我滚动到开始。
编辑
这是一个未经编辑的1080pvideo样本,当我在VLC上播放时,会出现一个绿色屏幕,并且在电话上播放不正确,除非我开始在YouTube上滚动并且现在奇怪地正常工作,除了开始时的绿框
这里是一个720p的video样本,编辑时也是一个绿色的框架和编辑后的清晰的像素化和滞后
这里是我用来解码重新编码的代码:
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时相同。 在这种特定情况下,您只需要replace要replace的帧的数据,并留下原始数据stream中的时间戳。
确保将位图转换为YUV色彩空间,并使用正确的像素格式。 Android将RGBA中的位图存储为每个像素4个字节,您需要将其转换为YUV,每个像素的Y值以及2×2块的U和V值,然后将它们放在字节数组中的单独平面中,编解码器。
此外,前段时间我做了一个示例应用程序,使用MediaCodec调整video,它也可以帮助你: https : //github.com/grishka/android-video-transcoder