Reputation: 15
I am trying to reduce video file size by decoding and re-encoding the video with MediaCodec. However, the new video file generated by the muxer is all messed up. Can you tell me what I've been doing wrong?
My code is modified from this CTS with two major differences:
I tried to decode the video into a SurfaceView by passing surfaceView.getHolder().getSurface() to decoder.configure(), and the video played correctly. I think this might be something wrong on the encoder/muxer part.
Here is my code:
The decoder callback:
private class VideoDecodeCallBack extends MediaCodec.Callback {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
ByteBuffer buffer = codec.getInputBuffer(index);
if (extractorDone) {
return;
}
int size = videoExtractor.readSampleData(buffer, 0);
long pts = videoExtractor.getSampleTime();
if (size >= 0) {
codec.queueInputBuffer(index, 0, size, pts, videoExtractor.getSampleFlags());
}
extractorDone = !videoExtractor.advance();
if (extractorDone) {
codec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
}
@Override
public void onOutputBufferAvailable(
MediaCodec codec, int index, MediaCodec.BufferInfo info) {
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
codec.releaseOutputBuffer(index, false);
return;
}
boolean render = info.size != 0;
codec.releaseOutputBuffer(index, render);
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
videoEncoder.signalEndOfInputStream();
}
}
@Override
public void onError(
MediaCodec codec, MediaCodec.CodecException e) {
}
@Override
public void onOutputFormatChanged(
MediaCodec codec, MediaFormat format) {
}
The encoder callback:
private class VideoEncodeCallBack extends MediaCodec.Callback {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
}
@Override
public void onOutputBufferAvailable(
MediaCodec codec, int index, MediaCodec.BufferInfo info) {
ByteBuffer buffer = codec.getOutputBuffer(index);
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
codec.releaseOutputBuffer(index, false);
return;
}
if (info.size != 0) {
muxer.writeSampleData(outputVideoTrack, buffer, info);
}
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
muxer.stop();
muxer.release();
}
codec.releaseOutputBuffer(index, false);
}
@Override
public void onError(
MediaCodec codec, MediaCodec.CodecException e) {
}
@Override
public void onOutputFormatChanged(
MediaCodec codec, MediaFormat format) {
outputVideoTrack = muxer.addTrack(format);
muxer.start();
}
}
How I configure the codecs:
public void configure(String input, String output) throws IOException {
// Extractors
videoExtractor = new MediaExtractor();
videoExtractor.setDataSource(input);
int videoTrack = getAndSelectVideoTrackIndex(videoExtractor);
MediaFormat videoFormat = videoExtractor.getTrackFormat(videoTrack);
String mime = videoFormat.getString(MediaFormat.KEY_MIME);
// Codecs
videoDecoder = MediaCodec.createDecoderByType(mime);
videoEncoder = MediaCodec.createEncoderByType(mime);
videoDecoder.setCallback(new VideoDecodeCallBack(), videoDecodeThreadHandler);
videoEncoder.setCallback(new VideoEncodeCallBack(), videoEncodeThreadHandler);
videoEncoder.configure(createOutputFormat(), null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
Surface surface = videoEncoder.createInputSurface();
videoDecoder.configure(videoFormat, surface, null, 0);
// Muxer
muxer = new MediaMuxer(output, OutputFormat.MUXER_OUTPUT_MPEG_4);
}
The desired output format:
private static MediaFormat createOutputFormat() {
MediaFormat result = MediaFormat.createVideoFormat("video/avc", 540, 960);
result.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
result.setInteger(MediaFormat.KEY_BIT_RATE, 1300000);
result.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
result.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
return result;
}
The complete code is here
Upvotes: 1
Views: 1270
Reputation: 189
I had this issue too. My guess is that Android cannot rescale the frame when the decoder render directly to the encoder's input surface.
The solution can be to use openGL for the rescaling.
This project does that. https://github.com/hoolrory/AndroidVideoSamples/blob/master/CommonVideoLibrary/src/com/roryhool/commonvideolibrary/VideoResampler.java
(I didn't run this project but I have implemented something similar so it should work)
Upvotes: 2