Android中使用MediaCodec视频编码异步实现

来源:互联网 发布:大数据选股app,数据宝 编辑:程序博客网 时间:2024/05/19 09:12

Android中使用MediaCodec进行视频编解码异步实现

简单的介绍一下MediaCodec:本文主要讲述的是博主自己在用MediaCodec进行编解码过程中分别用同步和异步两种方式实现了硬编解码的过程,因为之前自己在用异步实现的过程中经常会出现解码黑屏的情况,百度和博客上也没有相关的实例,找到的只有一个示例代码,并没有提供什么帮助。所以写了代码去实现,Mediacodec的简单使用我先介绍一下,并且把Mediacodec的异步实现的流程介绍一下。

这是谷歌官网给出的Mediacodec的编解码数据流向,Mediacodec类可用于访问底层的媒体编解码器,即编码器/解码器组件。这是Android底层的多媒体支持基础设施的一部分(通常与MediaExtractor,MediaSync,mediamuxer,mediacrypto,mediadrm)。
从广义上讲,一个编解码器处理输入数据来生成输出数据。它处理异步数据和使用一组输入和输出缓冲器。在一个简单的级别上,您请求(或接收)一个空的输入缓冲区,用数据填充它并将其发送到编解码器进行处理。该编解码器使用的数据并将其转换为它的一个空的输出缓冲器。最后,你请求(或接收)一个填充的输出缓冲区,消耗它的内容并将其释放回编解码器。

Mediacodec的基本使用介绍

  • MedicCodec硬编码的创建
      MediaCodec mediaCodec = MediaCodec.createDecoderByType("video/avc");      //实例化的首选解码器支持的MIME类型的输入数据,最好使用finddecoderforformat(mediaformat)和createbycodecname(字符串)来保证解码器能够处理一个给定的格式.      MediaFormat mediaFormat = android.media.MediaFormat.createVideoFormat("video/avc", 1280, 720);      mediaFormat.setInteger(android.media.MediaFormat.KEY_BIT_RATE, 512 * 2 * 1000);      //设置码率      mediaFormat.setInteger(android.media.MediaFormat.KEY_FRAME_RATE, 20);      //设置帧率      mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);      //关键帧间隔时间 单位s      mediaCodec.configure(mediaFormat, new Surface(mSurfaceView.getSurfaceTexture()), null, 0);      //配置mediaformat并且传入需要显示解码画面的SurfaceView      mediaCodec.start();      // start();
  • MediaCodec编码同步的官方实现
 MediaCodec codec = MediaCodec.createByCodecName(name); MediaFormat mOutputFormat; // member variable codec.setCallback(new MediaCodec.Callback() {   @Override   void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {     ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);     // fill inputBuffer with valid data     …     codec.queueInputBuffer(inputBufferId, …);   }   @Override   void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {     ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);     MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A     // bufferFormat is equivalent to mOutputFormat     // outputBuffer is ready to be processed or rendered.     …     codec.releaseOutputBuffer(outputBufferId, …);   }   @Override   void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {     // Subsequent data will conform to new format.     // Can ignore if using getOutputFormat(outputBufferId)     mOutputFormat = format; // option B   }   @Override   void onError(…) {     …   } }); codec.configure(format, …); mOutputFormat = codec.getOutputFormat(); // option B codec.start(); // wait for processing to complete codec.stop(); codec.release();
  • MediaCodec编码异步的官方实现
 MediaCodec codec = MediaCodec.createByCodecName(name); codec.configure(format, …); MediaFormat outputFormat = codec.getOutputFormat(); // option B codec.start(); for (;;) {   int inputBufferId = codec.dequeueInputBuffer(timeoutUs);   if (inputBufferId >= 0) {     ByteBuffer inputBuffer = codec.getInputBuffer(…);     // fill inputBuffer with valid data     …     codec.queueInputBuffer(inputBufferId, …);   }   int outputBufferId = codec.dequeueOutputBuffer(…);   if (outputBufferId >= 0) {     ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);     MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A     // bufferFormat is identical to outputFormat     // outputBuffer is ready to be processed or rendered.     …     codec.releaseOutputBuffer(outputBufferId, …);   } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {     // Subsequent data will conform to new format.     // Can ignore if using getOutputFormat(outputBufferId)     outputFormat = codec.getOutputFormat(); // option B   } } codec.stop(); codec.release();

介绍完官方的使用规范,下来我来讲讲自己的通过两个TextureView来异步实现硬编码的过程。

两个TextureView,一个通过系统Camera来获取摄像头的数据,然后通过同步和异步两种编解码方式,通过另一个TextureView来将画面呈现出来,这里只介绍异步编码的过程,解码原理相同。


xml配置

<?xml version="1.0" encoding="utf-8"?><LinearLayout        xmlns:android="http://schemas.android.com/apk/res/android"        xmlns:tools="http://schemas.android.com/tools"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:orientation="vertical">    <TextView            android:textSize="18sp"            android:layout_marginTop="10dp"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerInParent="true"            android:text="预览图像:" />    <TextureView android:id="@+id/v1"            android:layout_marginTop="5dp"            android:layout_width="match_parent"            android:layout_height="200dp"/>    <TextView            android:textSize="18sp"            android:layout_marginTop="10dp"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerInParent="true"            android:text="解码后渲染图像:" />    <TextureView android:id="@+id/v2"            android:layout_marginTop="5dp"            android:layout_width="match_parent"            android:layout_height="200dp"/></LinearLayout>

把Camera获取到的数据呈现在TextureView上

  • MedicCodec的创建
      camera = Camera.open();      camera.setPreviewTexture(surfaceView1);      //显示camera的画面 surfaceView1 是xml声明的第一个TextureView 需要在前面绑定      Camera.Parameters cp= cam.getParameters();        cp.setFlashMode("off");       // 无闪光灯        cp.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);      //设置聚焦模式      cp.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);        cp.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);         cp.setPreviewFormat(ImageFormat.YV12);             cp.setPictureSize(camWidth, camHeight);        cp.setPreviewSize(camWidth, camHeight);       camera.setParameters(cp);      camera.startPreview();
  • 异步的方式进行硬编码
  asynccodec = MediaCodec.createEncoderByType("video/avc");              MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc",mWidth,mHeight);              mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);              mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);              mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);              mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);              asynccodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);              asynccodec.setCallback(new MediaCodec.Callback() {                 @Override                 public void onInputBufferAvailable(MediaCodec mc, int index) {                     ByteBuffer inputBuffer = asynccodec.getInputBuffer(index);                     byte[] tmpBuffer = camera.getInstance().getDequeueEncode();                     // tmpBuffer 主要用户获取camera中获取的到的一帧画面的数据                     // 博主通过一个encodeBuffer缓存队列进行数据的存储,这里是从缓存里面出一帧的数据                     //getDequeueEncode()函数是当缓存队列有的时候,会进行数据的出队列                     inputBuffer.clear();                     int length = 0;                     if(tmpBuffer != null) {                         inputBuffer.put(tmpBuffer);                         //将获取到的数据放入通过index获取的ByteBuffer中                         length = tmpBuffer.length;                         encodedFrameCount++;                    }                    asynccodec.queueInputBuffer(index, 0, length, 0, 0);                    //通过index获取ByteBuffer然后放入编码器中                }                @Override                public void onOutputBufferAvailable(MediaCodec codec, int outputBufferId, MediaCodec.BufferInfo info) {                //异步实现的关键!!!!                //onOutputBufferAvailable这个回调函数网上找不到一些实现的例子,主要的问题就出现在TextureView在呈现画面的时候出现掉帧和模糊的的情况。                //通过异步实现,当mediacodec有数据的时候,就会自动调用这个函数,把编码后的数据进行出队列操作                    ByteBuffer outputBuffer = asynccodec.getOutputBuffer(outputBufferId);                    MediaFormat bufferFormat = asynccodec.getOutputFormat(outputBufferId);                    if (outputBuffer != null && info.size > 0) {                        byte[] tmpBuffer = new byte[info.size];                        tmpBuffer = new byte[outputBuffer.remaining()];                 //ByteBuffer 转化为byte[]数组                        outputBuffer.get(tmpBuffer);                        mFrameBuffer.enqueue(tmpBuffer,tmpBuffer.length);                 //将异步编码后的数据存放到了自己定义的一个缓存队列中,后续TextureView进行显示也是通过从这个缓存队列中获取相应数据然后再进行解码然后呈现出来的                    }                    asynccodec.releaseOutputBuffer(outputBufferId, true);                 //释放当前outputBufferId上的数据                }                @Override                public void onError(MediaCodec codec, MediaCodec.CodecException e) {                //错误回调                    Log.d("error", e.getDiagnosticInfo());                }                @Override                public void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat format) {                }            });        } catch (IOException e) {            e.printStackTrace();        }        asynccodec.start();
  • 缓存队列FrameBuffer的介绍

    缓存队列的实现是用的java的数组,自己封装了出队列和进队列的方法。同时加入了ReentrantLock机制提供保证。还有对摄像头数据获取的前几个关键帧进行判断的操作,这里就不贴代码了。 其实不使用队列的话,数据量小的情况下也是可以流畅运行的。

解码的过程跟编码的过程很类似,也是通过MediaCodec来实现的,大家可以试试用同步的方式来把获取的数据来进行解码。

效率方面的话本地预览的情况下,同步和异步的区别不是很大,都可以基本保证画面流畅的预览。

但是在比如网络视频通话开发的过程中,不考虑丢帧而考虑画面流程性的条件下,异步的方式效率是远超于同步的效率。网络测试方面的代码后续有时间回再贴上来的。

Thanks.

原创粉丝点击