23

In my app I'm trying to upload some videos that the user picked from gallery. The problem is that usually the android video files are too big to upload and so- we want to compress them first by lower bitrate/ resolution.

I've just heard about the new MediaCodec api that introduce with API 16 (I perviously tried to do so with ffmpeg).

What I'm doing right now is the following: First decode the input video using a video decoder, and configure it with the format that was read from the input file. Next, I create a standard video encoder with some predefined parameters, and use it for encoding the decoder output buffer. Then I save the encoder output buffer to a file.

Everything looks good - the same number of packets are written and read from each input and output buffer, but the final file doesn't look like a video file and can't be opened by any video player.

Looks like the decoding is ok, because I test it by displaying it on Surface. I first configure the decoder to work with a Surface, and when we call releaseOutputBuffer we use the render flag, and we're able to see the video on the screen.

Here is the code I'm using:

    //init decoder
    MediaCodec decoder = MediaCodec.createDecoderByType(mime);
    decoder.configure(format, null , null , 0);
    decoder.start();
    ByteBuffer[] codecInputBuffers = decoder.getInputBuffers();
    ByteBuffer[] codecOutputBuffers = decoder.getOutputBuffers();

    //init encoder
    MediaCodec encoder = MediaCodec.createEncoderByType(mime);
    int width = format.getInteger(MediaFormat.KEY_WIDTH);
    int height = format.getInteger(MediaFormat.KEY_HEIGHT);
    MediaFormat mediaFormat = MediaFormat.createVideoFormat(mime, width, height);
    mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 400000);
    mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25);
    mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
    mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
    encoder.configure(mediaFormat, null , null , MediaCodec.CONFIGURE_FLAG_ENCODE);
    encoder.start();
    ByteBuffer[] encoderInputBuffers = encoder.getInputBuffers();
    ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();

    extractor.selectTrack(0);

    boolean sawInputEOS = false;
    boolean sawOutputEOS = false;
    boolean sawOutputEOS2 = false;
    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    BufferInfo encoderInfo = new MediaCodec.BufferInfo();

    while (!sawInputEOS || !sawOutputEOS || !sawOutputEOS2) {
        if (!sawInputEOS) {
            sawInputEOS = decodeInput(extractor, decoder, codecInputBuffers);
        }

        if (!sawOutputEOS) {
            int outputBufIndex = decoder.dequeueOutputBuffer(info, 0);
            if (outputBufIndex >= 0) {
                sawOutputEOS = decodeEncode(extractor, decoder, encoder, codecOutputBuffers, encoderInputBuffers, info, outputBufIndex);
            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                Log.d(LOG_TAG, "decoding INFO_OUTPUT_BUFFERS_CHANGED");
                codecOutputBuffers = decoder.getOutputBuffers();
            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                final MediaFormat oformat = decoder.getOutputFormat();
                Log.d(LOG_TAG, "decoding Output format has changed to " + oformat);
            } else if (outputBufIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                 Log.d(LOG_TAG, "decoding dequeueOutputBuffer timed out!");
            }
        }

        if (!sawOutputEOS2) {
            int encodingOutputBufferIndex = encoder.dequeueOutputBuffer(encoderInfo, 0);
            if (encodingOutputBufferIndex >= 0) {
                sawOutputEOS2 = encodeOuput(outputStream, encoder, encoderOutputBuffers, encoderInfo, encodingOutputBufferIndex);
            } else if (encodingOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                Log.d(LOG_TAG, "encoding INFO_OUTPUT_BUFFERS_CHANGED");
                encoderOutputBuffers = encoder.getOutputBuffers();
            } else if (encodingOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                final MediaFormat oformat = encoder.getOutputFormat();
                Log.d(LOG_TAG, "encoding Output format has changed to " + oformat);
            } else if (encodingOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                Log.d(LOG_TAG, "encoding dequeueOutputBuffer timed out!");
            }
        }
    }
            //clear some stuff here...

and those are the method I use for decode/ encode:

    private boolean decodeInput(MediaExtractor extractor, MediaCodec decoder, ByteBuffer[] codecInputBuffers) {
        boolean sawInputEOS = false;
        int inputBufIndex = decoder.dequeueInputBuffer(0);
        if (inputBufIndex >= 0) {
            ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
            input1count++;

            int sampleSize = extractor.readSampleData(dstBuf, 0);
            long presentationTimeUs = 0;
            if (sampleSize < 0) {
                sawInputEOS = true;
                sampleSize = 0;
                Log.d(LOG_TAG, "done decoding input: #" + input1count);
            } else {
                presentationTimeUs = extractor.getSampleTime();
            }

            decoder.queueInputBuffer(inputBufIndex, 0, sampleSize, presentationTimeUs, sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
            if (!sawInputEOS) {
                extractor.advance();
            }
        }
        return sawInputEOS;
    }
    private boolean decodeOutputToFile(MediaExtractor extractor, MediaCodec decoder, ByteBuffer[] codecOutputBuffers,
            MediaCodec.BufferInfo info, int outputBufIndex, OutputStream output) throws IOException {
        boolean sawOutputEOS = false;

        ByteBuffer buf = codecOutputBuffers[outputBufIndex];
        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
            sawOutputEOS = true;
            Log.d(LOG_TAG, "done decoding output: #" + output1count);
        }

        if (info.size > 0) {
            output1count++;
            byte[] outData = new byte[info.size];
            buf.get(outData);
            output.write(outData, 0, outData.length);
        } else {
            Log.d(LOG_TAG, "no data available " + info.size);
        }
        buf.clear();
        decoder.releaseOutputBuffer(outputBufIndex, false);
        return sawOutputEOS;
    }

    private boolean encodeInputFromFile(MediaCodec encoder, ByteBuffer[] encoderInputBuffers, MediaCodec.BufferInfo info, FileChannel channel) throws IOException {
            boolean sawInputEOS = false;
            int inputBufIndex = encoder.dequeueInputBuffer(0);
            if (inputBufIndex >= 0) {
                ByteBuffer dstBuf = encoderInputBuffers[inputBufIndex];
                input1count++;

                int sampleSize = channel.read(dstBuf);
                if (sampleSize < 0) {
                    sawInputEOS = true;
                    sampleSize = 0;
                    Log.d(LOG_TAG, "done encoding input: #" + input1count);
                }

                encoder.queueInputBuffer(inputBufIndex, 0, sampleSize, channel.position(), sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
            }
            return sawInputEOS;
    }

Any suggestion on what I'm doing wrong?

I didn't find too much examples for encoding with MediaCodec just a few samples code for decoding... Thanks a lot for the help

shem
  • 4,686
  • 2
  • 32
  • 43
  • 1
    You don't need MediaCodec. It's dangerous to go alone, take this: http://stackoverflow.com/a/23815402 – Wim L. Oct 17 '14 at 13:07
  • 1
    As mentioned in comments- I ended up using ffmpeg – shem Oct 19 '14 at 12:02
  • @shem: Did you solve this issue? I am working on the same problem without success. Like @fadden, I agree that we need to format the output with a `MediaMuxer`. Please point me to an example that reads a file (`MediaExtractor`) and writes an encoded video of a different size (I am using API level 22, so an asynchronous example would be even better!) – David Manpearl Mar 10 '16 at 03:28
  • @DavidManpearl - As mentioned in comments- I ended up using ffmpeg – shem Mar 11 '16 at 09:39

1 Answers1

7

The output of MediaCodec is a raw elementary stream. You need to package it up into a video file format (possibly muxing the audio back in) before many players will recognize it. FWIW, I've found that the GStreamer-based Totem Movie Player for Linux will play "raw" video/avc files.

Update: The way to convert H.264 to .mp4 on Android is with the MediaMuxer class, introduced in Android 4.3 (API 18). There are a couple of examples (EncodeAndMuxTest, CameraToMpegTest) that demonstrate its use.

fadden
  • 51,356
  • 5
  • 116
  • 166
  • The raw stream is what I get after decoding, that's why I'm also encoding the video stream (I know it's without the sound stream). the encoded file should be a regular video, am I right? – shem Apr 18 '13 at 07:39
  • The encoded data coming out of a MediaCodec encoder is just the raw video elementary stream. Look at a hex dump of the file and compare it to the .mp4 that comes out of MediaRecorder, and you'll see structural differences immediately. – fadden Apr 18 '13 at 15:03
  • Thanks. So if I want to compress a video by decoding it and then encoding it with another bit rate/ size, how can I do it with the MediaCodec framework? Isn't encoding intended for transforming the decoded stream to a playable format? If not, how do you suggest I should compress videos on android? – shem Apr 18 '13 at 15:12
  • MediaCodec provides fairly low-level access to the media codecs. Muxing audio and/or transforming to .mp4 isn't currently supported by code in the framework (as of Jellybean 4.2.2), so there isn't really a good way to transcode video at this time without resorting to 3rd-party code. – fadden Apr 18 '13 at 21:48
  • 1
    I ended up compress my videos using ffmpeg, but will certainly wait for this to be supported in MediaCodec. thx @fadden! – shem May 30 '13 at 12:18
  • @shem can you please tell me how can i compile ffmpeg and use it into my android ...i am confused with tutorial available on google and stack overflow.. – Swap-IOS-Android Aug 27 '13 at 16:33
  • @Swap-IOS-Android Hi, there are lots of tutorials, and it's very confusing. I was using this one, It was the only one that worked for me: http://www.roman10.net/how-to-port-ffmpeg-the-program-to-androidideas-and-thoughts/ – shem Aug 27 '13 at 17:14
  • @shem did u manage to compress video using roman10 project ?? – Mr.G May 23 '14 at 06:11
  • Yep, but not as-is, I had to do some changes and recompile ffmpeg for me to work. but basically I used his guidelines and it worked. – shem May 23 '14 at 07:57
  • @shem i i get this error noexecstack: unknown -z option , can u share ur build_android.sh file . i cannot build ffmpeg with this issue – Mr.G May 23 '14 at 09:29
  • @shem can u share us the configuration ?? – Mr.G May 23 '14 at 14:33