Android Mediacodec硬解H264并显示

Android Mediacodec硬解H264并显示

从API 16(Android 4.1)开始,Android提供了Mediacodec类以便开发者更加灵活的处理音视频的编解码。
本文以rtsp流为例,指导如何使用该类进行H264的硬解码,并最终显示图片到指定的SurfaceView 上。

一、准备工作

  • SurfaceView:作为Mediacodec的图像显示的绑定对象,需要事先布局:
<SurfaceView         
    android:id="@+id/surface_view"       
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent"  
    android:layout_centerInParent="true"/>   

当Surface被创建成功后才能进行配置Mediacodec 解码器的动作

二、配置Mediacodec 解码器并启动解码器

  • 1.获取rtsp流Frame的宽高:frameWidth,frameHeight
  • 2.如果送来的流的第一帧Frame有pps和sps,可以不需要配置format.setByteBuffer的”csd-0” (sps) 和”csd-1”(pps);
    否则必须配置相应的pps和sps,通常情况下sps和pps如下:
    byte[] sps = { 0, 0, 0, 1, 103, 100, 0, 40, -84, 52, -59, 1, -32, 17, 31, 120, 11, 80, 16, 16, 31, 0, 0, 3, 3, -23, 0, 0, -22, 96, -108 };
    byte[] pps = { 0, 0, 0, 1, 104, -18, 60, -128 };。
  • 3.获取SurfaceView 的holder,SurfaceHolder surfaceHolder = surfaceView.getHolder();
    绑定显示的surfaceView并配置format。
  • 4.配置完成后,启动Mediacodec 解码线程。
MediaFormat format = MediaFormat.createVideoFormat("video/avc", frameWidth, frameHeight); 
format.setByteBuffer("csd-0"  , ByteBuffer.wrap(sps)));
format.setByteBuffer("csd-1", ByteBuffer.wrap(pps)));
/* create & config android.media.MediaCodec */
MediaCodec decoder;
try {
    decoder = MediaCodec.createDecoderByType(ret);
    } catch (IOException e) {
    // TODO Auto-generated catch block
     e.printStackTrace();
}
decoder.configure(format, surfaceHolder.getSurface(), null, 0);//blind surfaceView
decoder.start(); //start decode thread

三、Mediacodec 解码过程

新建立一个线程,专门用于解码。
其中myFrame = MyService.getNextVideoFrame(); 表示从rtsp的服务器取得一帧数据,再解析出其中的pts,size喂给解码器。
解码器decode的结果:int outIndex = decoder.dequeueOutputBuffer(info, outtime);
根据outIndex的值可以判断当前数据是否可以显示到surfaceView。

private class VideoThread extends Thread {
    private boolean done = false;
    private MediaCodec.BufferInfo info;

    VideoThread() {
        super();
        done = false;
    }

    @Override
    public void run() {
        ByteBuffer[] inputBuffers = decoder.getInputBuffers();
        info = new MediaCodec.BufferInfo();
        int inIndex = -1;
        int sampleSize = 0;
        long pts = 0;
        MyFrame myFrame;
        while (!done) {//init done = false;
        //get Frame data from service,you should customize this line...
            myFrame = MyService.getNextVideoFrame();
            if(myFrame == null){
                continue;
            }
            inIndex = decoder.dequeueInputBuffer(0);
            if (inIndex >= 0) {
                sampleSize = myFrame.getFrameSize();
                pts = (long) myFrame.getPresentationTime(); 
                ByteBuffer buffer = inputBuffers[inIndex];
                buffer.clear();
                buffer.rewind();
                buffer.put(myFrame.getBuffer(), 0, sampleSize);

                decoder.queueInputBuffer(inIndex, 0, sampleSize, pts, 0);
                dequeueAndRenderOutputBuffer(0);
            } else {
                if (!dequeueAndRenderOutputBuffer(timeout) && !dequeueAndRenderOutputBuffer(30 * 1000)) {
                }
            }

            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                break;
            }
        }
    }
    decoder.stop();
    decoder.release();
}


public boolean dequeueAndRenderOutputBuffer(int outtime) {
            int outIndex = decoder.dequeueOutputBuffer(info, outtime);
            switch (outIndex) {
                case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                    return false;
                case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                    return false;
                case MediaCodec.INFO_TRY_AGAIN_LATER:
                    return false;
                case MediaCodec.BUFFER_FLAG_SYNC_FRAME:
                    return false;
                default:
                    decoder.releaseOutputBuffer(outIndex, true);//show image right now
                    return true;
            }
        }

四、其他

  • 1.Mediacode 的h264视频解码不会收到一帧解出一帧,可能会缓冲几帧后才会显示一帧。
  • 2.dequeueAndRenderOutputBuffer 中进入default分支后:通过decoder.releaseOutputBuffer(outIndex, true),来显示到绑定的surfaceView上(注意第二个参数如果改为false,这表示释放数据,但是不显示到surfaceView)。

你可能感兴趣的:(android)