Unable to do low-level decoding of video on Android 4.2 without using media extractor

JavaAndroidVideo ProcessingDecodingAndroid Mediarecorder

Java Problem Overview


I wanted to decode video frames without using an extractor. So I just tried a small sample, where I use media extractor but I don't do extractor.readsample() to copy the bitstream data into the input buffer instead I use FFmpeg parser, inside JNI, where I memcopy the video frames into the input byte buffers and then queued the input buffer.

But when I call decoder.dequeueOutputBuffer(info, 10000):

  • It returns MediaCodec.INFO_TRY_AGAIN_LATER
  • While it works fine if I use extractor.readsample()

Java Side:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

import android.app.Activity;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class VideoBrowser extends Activity implements SurfaceHolder.Callback {
    private static final String SAMPLE = Environment.getExternalStorageDirectory() + "/obama.mp4";
    private PlayerThread mPlayer = null;
    private static native < jintArray > int AVinitializecntxt(String strl, jintArray arr);
    private native int AVREADVIDEO(byte[] array);
    public int FLAG = 0;
    public int jk = 0;
    File f1;
    FileOutputStream f;

    static {
        Log.i("ABCD", "BEFORE");
        System.loadLibrary("ffmpeg");
        System.loadLibrary("ffmpeg-test-jni");
        Log.i("ABCD", "Success");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SurfaceView sv = new SurfaceView(this);
        sv.getHolder().addCallback(this);
        setContentView(sv);
        int val;
        int[] array = new int[6];
        int END_OF_FILE = 0;
        int aud_stream = 0;
        int vid_stream = 0;
        String urlString = "/mnt/sdcard/obama.mp4";
        f1 = new File("/mnt/sdcard/t.h264");
        try {
            f = new FileOutputStream(f1);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        // This is where I call the function to initialize the ffmpeg inside JNI
        val = AVinitializecntxt(urlString, array);
        FLAG = val;
        Log.i("ABCD", "FLAG : " + FLAG + val);
    }

    protected void onDestroy() {
        super.onDestroy();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {}

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if (mPlayer == null) {
            mPlayer = new PlayerThread(holder.getSurface());
            mPlayer.start();
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mPlayer != null) {
            mPlayer.interrupt();
        }
    }

    private class PlayerThread extends Thread {
        private MediaExtractor extractor;
        private MediaCodec decoder;
        private Surface surface;
        // private VideoPlayer VideoPlayerAPIInterfaceClass = new VideoPlayer();

        public PlayerThread(Surface surface) {
            this.surface = surface;
        }

        @Override
        public void run() {
            if (FLAG == 1) {
                extractor = new MediaExtractor();
                extractor.setDataSource(SAMPLE);
                for (int i = 0; i < extractor.getTrackCount(); i++) {
                    MediaFormat format = extractor.getTrackFormat(i);
                    String mime = format.getString(MediaFormat.KEY_MIME);
                    if (mime.startsWith("video/")) {
                        extractor.selectTrack(i);
                        decoder = MediaCodec.createDecoderByType("video/avc");
                        // Log.i("ABCD", "MIME : " + mime);
                        decoder.configure(format, surface, null, 0);
                        break;
                    }
                }

                if (decoder == null) {
                    Log.e("DecodeActivity", "Can't find video info!");
                    return;
                }

                decoder.start();

                ByteBuffer[] inputBuffers = decoder.getInputBuffers();
                ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
                BufferInfo info = new BufferInfo();
                boolean isEOS = false;
                long startMs = System.currentTimeMillis();
                int outIndex1 = -1;

                while (outIndex1 < 0) {
                    outIndex1 = decoder.dequeueOutputBuffer(info, 10000);
                    Log.i("ABCD", "etgeuieoy");
                }

                while (!Thread.interrupted()) {
                    if (!isEOS) {
                        int inIndex = decoder.dequeueInputBuffer(10000);
                        if (inIndex >= 0) {
                            ByteBuffer buffer = inputBuffers[inIndex];
                            // int sampleSize = extractor.readSampleData(buffer, 0);
                            byte[] bytes = new byte[buffer.capacity()];
                            // This is where we call JNI function to memcopy the encoded bitstream into the input buffer
                            int sampleSize = [b] AVREADVIDEO[/b](bytes);
                            buffer.clear(); buffer.put(bytes, 0, sampleSize);
                            if (sampleSize < 0) {
                            // We shouldn't stop the playback at this point, just pass the EOS
                            // flag to decoder, we will get it again from the
                            // dequeueOutputBuffer
                            // Log.d("DecodeActivity", "InputBuffer BUFFER_FLAG_END_OF_STREAM");
                                decoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                                isEOS = true;
                            } else {
                                decoder.queueInputBuffer(inIndex, 0, sampleSize, 0, 0);
                                extractor.advance();
                            }
                         }
                     }
                     int outIndex = decoder.dequeueOutputBuffer(info, 10000);
                     switch (outIndex) {
                        case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                            Log.d("DecodeActivity", "INFO_OUTPUT_BUFFERS_CHANGED");
                            outputBuffers = decoder.getOutputBuffers();
                            break;
                        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                             Log.d("DecodeActivity", "New format " + decoder.getOutputFormat());
                             break;
                        case MediaCodec.INFO_TRY_AGAIN_LATER:
                             Log.d("DecodeActivity", "dequeueOutputBuffer timed out!");
                             break;
                       default:
                            ByteBuffer buffer = outputBuffers[outIndex];
                            Log.v("DecodeActivity", "We can't use this buffer but render it due to the API limit, " + buffer);
                            // We use a very simple clock to keep the video FPS, or the video
                            // playback will be too fast
                            while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {
                                try {
                                    sleep(10);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                    break;
                                }
                            }
                            //	Log.i("ABCD", "RELEASING OUTPUT BUFFER");
                            decoder.releaseOutputBuffer(outIndex, true);
                            //decoder.releaseOutputBuffer(outIndex, false);
                            break;
                        }

                        // All decoded frames have been rendered, we can stop playing now
                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                            Log.d("DecodeActivity", "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
                            break;
                        }
                    }

                    decoder.stop();
                    decoder.release();
                    extractor.release();
                }
            }
        }
    }
}

JNI Side:

JNIEXPORT jint JNICALL
Java_com_alldigital_videoplayer_VideoBrowser_AVREADVIDEO(JNIEnv * pEnv, 
    jobject pObj, jbyteArray array) {

    AV_ctxt * avctxt = & aud_vid_ctxt;
    jbyte * buf = ( * pEnv) - > GetByteArrayElements(pEnv, array, NULL);
    if (buf == NULL) {
        LOGERR(10, "AVVIDEOREAD", "Bytes null");
    }

    AVPacket * packet;
    packet = av_malloc(sizeof(AVPacket));
    av_init_packet(packet);
    int avread_res = av_read_frame(avctxt - > gFormatCtx, packet);
    int size = packet - > size;
    if (avread_res >= 0) {
        if (packet - > stream_index == avctxt - > gVideoStreamIndex) {
            // packet->size,packet->
            if (NULL == memcpy(buf,(char * ) packet - > data, packet - > size))
                LOGERR(10, "AV_AUDIO_DECODE", "memcpy for audio buffer failed");
            }
        }
        ( * pEnv) - > ReleaseByteArrayElements(pEnv, array, buf, 0);
        av_free_packet(packet);
        packet = NULL;
        return size;
    }
}

Even though I am copying the encoded data of each frame through FFmpeg without calling extractor, I am getting this outputbuffer timeout issue - why?

Java Solutions


Solution 1 - Java

try {
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  FileInputStream fis = new FileInputStream(new File(
  "ur file path"));

  byte[] buf = new byte[1024];
  int n;
  while (-1 != (n = fis.read(buf))) {
  baos.write(buf, 0, n);
  }

  byte[] videoBytes = baos.toByteArray();

  // use this videoBytes which is low level of original video 

} catch (Exception e) {
  e.printStackTrace();
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPujariView Question on Stackoverflow
Solution 1 - JavaVaishali SutariyaView Answer on Stackoverflow