Android MediaCodec: Reduce mp4 video size

Ricardo Cion picture Ricardo Cion · Feb 27, 2014 · Viewed 11.6k times · Source

I am looking for a way to "compress" an mp4 video. To achieve this, I want to reduce the resolution of the video and / or reduce FPS.

After long research, I think the way to do it is to use MediaCodec and related APIs as follows :

-> MediaExtractor to extract encoded media data from a mp4 file. -> MediaCodec (Decoder): Decode each frame for later processing. -> MediaCodec (Encoder): At this point, I guess that's where we should establish MediaFormat parameters such as video resolution, which should help us to reduce the final size of the mp4 file. -> MediaMuxer for generate mp4 file (MinSDK 18, this might be a problem, but for now... ok) .

I don't want to use a SurfaceView/TextureView. Regarding the audio, I don't want to process it to reduce quality.

I've been looking at all the examples here: http://bigflake.com/mediacodec/

I've also seen CTS source code modules.

And I've been looking at several projects like these: https://github.com/vecio/MediaCodecDemo/blob/master/src/io/vec/demo/mediacodec/DecodeActivity.java

And of course I have read dozens of posts on stackoverflow.

The problem is that I have not yet found any code example that works well for this porpuse. I tried to take various pieces of code and put them together on a project, but does not really work.

Thanks.

Answer

fadden picture fadden · Feb 28, 2014

The closest thing to what you want is probably the DecodeEditEncodeTest on bigflake. You don't need SurfaceView or TextureView, since you're not trying to display the video, but you will need to use a SurfaceTexture.

The test code generates a video, then decodes / edits / encodes, then tests the encoded output. You want the decode-edit-encode part. When creating the encoder, give it whatever size you want. The input surface it creates will be sized to match. The scaling is performed by the GPU as it renders the GL texture (from the SurfaceTexture) onto the encoder's input surface. You'll have to decide whether GLES linear filtering provides the level of quality you need.

(You can see something similar in ExtractMpegFramesTest, which scales everything to 640x480 as it's doing the extraction. In this case it's converting the frames to PNG rather than feeding them into an encoder, so it's not as applicable to your situation.)

Dropping frames is theoretically easy: just don't submit it to the encoder. In practice it's a bit harder if the input has irregularly-spaced frames. If the input is "frame1 frame2 ... pause ... frame4 frame5", and you drop frame2, you'll end up paused on frame1, which might look weird. You can use the presentation time stamps to figure out the timing.

Surface input and MediaMuxer (required for saving H.264 as .mp4) require API 18+. For older versions of Android you'll probably want to use a third-party library like ffmpeg.