2016-10-13 20:11:53 sz66cm 阅读数 3015
  • 知识总结

    通过FFmpeg实现万能播放器(直播美颜,拉流) 学会MediaCodec硬解码实现 抖音 视频编辑 Rtmp协议实现哔哩哔哩直播推流 OpenGl动手制作灵魂出窍,手写美颜特效

    27092课时 0分钟 3人学习 齐行超
    免费试看

Android 采集过程注意

  1. Camera.addCallbackBuffer(byte[] data)其中data的大小要紧密与采集数据的格式相关.
  2. 如采集到的数据格式yuv422i,那么data的大小应该为width * height * 2.
  3. Camera.setPreviewCallback(Camera.PreviewCallback cb)
  4. 每次调用onPreviewFrame(...)的末尾在添加一次Camera.addCallbackBuffer(byte[] data)

Android H264硬编码过程

  1. 生成编码器并且设置相关参数
    public synchronized void open() {
        //YUV420P的大小关系
        byte[] yuv420 = new byte[mWidth * mHeight * 3 / 2];
        //生成编码器并且设置相关参数
        mediaCodec = MediaCodec.createEncoderByType("video/avc");
        //编码格式参数设置,如果对yuv数据进行旋转以后,注意mWidth,
        //mHeight的在90度或270度会颠倒,不设置正确的话对端会花屏
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(
            "video/avc", mWidth, mHeight);
        //设置码率,码率越低,失真越厉害
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
        /**设置编码输入缓存大小,默认输入数据为yuv420大小,即1.5倍的宽高积,
        如果使用的是其他格式,如yuv422那么就要手动设置大小,不然塞数据时
        会报BufferOverflowException异常*/
        mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE,
                 mWidth * mHeight * 3 / 2);
        //设置帧率
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
        /**设置颜色格式(I420,YV12,NV21等)
            如:MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar*/
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
        //设置发送I帧的时间间隔
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);//单位:s(秒)
        //完成配置,启动
        mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAT_ENCODE);
        mediaCodec.start();
    }
  1. 进行编码
    public synchronized int encode(byte[] in, int offset, byte[] out, int length) {
        int pos = 0;
        byte[] inBuf = in;
        int l = length;
        /**由于Android 摄像头默认采集的数据是NV21格式,所以要
           转成MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar
           让编码器支持.*/
        NV21toYUV420SemiPlannr(in, offset, yuv420, mWidth, mHeight);
        try {
            /**
                    注意此处获取inputBuffer和outputBuffer的方法,在android LOLLIPOP之后
                    的版本要修改inputBuffer = MediaCodec.getInputBuffer(index);
            */
            ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
            ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
            /**
                解码的时候,如果此处TIME_OUT非0会有个大坑,很多机子在这句卡死
            */
            int inputBufferIndex = mediaCodec.dequeueInputBuffer(TIME_OUT);
            if(inputBufferIndex >= 0){
                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                inputBuffer.clear();
                inputBuffer.put(inBuf, offset, l);
                /**此处getMyTime()函数维护一个递增的时间戳
                    据说此处的第四个参数不传,第一个I帧以后,
                    mediaCodec.dequeueOutputBuffer()一直返回-1,
                    -1对应原生代码的再试一遍的意思*/
                mediaCodec.queueInputBuffer(inputBufferIndex, 0, l, getMyTime(), 0);
            }
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            /**
                此处的TIME_OUT是否要传,待研究.
             */
            int outBufferIndex = mediaCodec.dequeueInputBuffer(bufferInfo, TIME_OUT);
            //待理解继续
        }
    }

(待补充)

Android H264硬解码过程

  1. 生成MediaCodec对象并且设置好参数
    //H264解码器
    codec = MediaCodec.createDecoderByType("video/avc");
    MeidaFormat mediaFormat = MediaFormat.createVideoFormat(
        "video/avc", width, height);
    mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, width * height);
    codec.configure(mediaFormat, surface, null, 0);
    codec.start();
  1. 进行解码并且播放
    public void decodeAndPlayBack(byte[] in, int offset, int length) {
        //获取喂数据的ByteBuffer数组
        ByteBuffer[] inputBuffers = codec.getInputBuffers();
        /**以下特别注意,TIME_OUT建议设置成0,设置非0很多机子
            出现卡死在此句代码,设置成0的代价只是丢帧*/
        int inputBuffersIndex = codec.dequeueInputBuffer(TIME_OUT);
        if(inputBuffersIndex >= 0) {
            ByteBuffer inputBuffer = inputBuffers[inputBuffersIndex];
            inputBuffer.clear();
            inputBuffer.put(in, offset, length);
            //填充好数据以后,提交通知解码器解码,这几个参数待研究
            codec.queueInputBuffer(inputBuffersIndex, 0, length, 0, 0);
        }
        //释放缓存空间
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
        while (outputBufferIndex >=0) {
            codec.releaseOutputBuffer(outputBufferIndex, true);
            outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
        }
    }
2018-03-16 10:53:51 AndrExpert 阅读数 3916
  • 知识总结

    通过FFmpeg实现万能播放器(直播美颜,拉流) 学会MediaCodec硬解码实现 抖音 视频编辑 Rtmp协议实现哔哩哔哩直播推流 OpenGl动手制作灵魂出窍,手写美颜特效

    27092课时 0分钟 3人学习 齐行超
    免费试看

转载请声明出处:http://blog.csdn.net/AndrExpert/article/details/79578149

在“AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer)”一文中我们简单介绍了Android硬编解码接口MediaCodec的使用,本文将在该基础上对MediaCodec的基本工作原理和编解码过程进行剖析。

1. MediaCodec工作原理

MediaCodec类Android提供的用于访问低层多媒体编/解码器接口,它是Android低层多媒体架构的一部分,通常与MediaExtractor、MediaMuxer、AudioTrack结合使用,能够编解码诸如H.264、H.265、AAC、3gp等常见的音视频格式。广义而言,MediaCodec的工作原理就是处理输入数据以产生输出数据。具体来说,MediaCodec在编解码的过程中使用了一组输入/输出缓存区来同步或异步处理数据:首先,客户端向获取到的编解码器输入缓存区写入要编解码的数据并将其提交给编解码器,待编解码器处理完毕后将其转存到编码器的输出缓存区,同时收回客户端对输入缓存区的所有权;然后,客户端从获取到编解码输出缓存区读取编码好的数据进行处理,待处理完毕后编解码器收回客户端对输出缓存区的所有权。不断重复整个过程,直至编码器停止工作或者异常退出。
MediaCodec原理

2. MediaCodec编码过程

在整个编解码过程中,MediaCodec的使用会经历配置、启动、数据处理、停止、释放几个过程,相应的状态可归纳为停止(Stopped),执行(Executing)以及释放(Released)三个状态,而Stopped状态又可细分为未初始化(Uninitialized)、配置(Configured)、异常( Error),Executing状态也可细分为读写数据(Flushed)、运行(Running)和流结束(End-of-Stream)。MediaCodec整个状态结构图如下:
这里写图片描述
从上图可知,当MediaCodec被创建后会进入未初始化状态,待设置好配置信息并调用start()启动后,MediaCodec会进入运行状态,并且可进行数据读写操作。如果在这个过程中出现了错误,MediaCodec会进入Stopped状态,我们就是要使用reset方法来重置编解码器,否则MediaCodec所持有的资源最终会被释放。当然,如果MediaCodec正常使用完毕,我们也可以向编解码器发送EOS指令,同时调用stop和release方法终止编解码器的使用。

(1) 创建编/解码器

MediaCodec主要提供了createEncoderByType(String type)、createDecoderByType(String type)两个方法来创建编解码器,它们均需要传入一个MIME类型多媒体格式。常见的MIME类型多媒体格式如下:
● “video/x-vnd.on2.vp8” - VP8 video (i.e. video in .webm)
● “video/x-vnd.on2.vp9” - VP9 video (i.e. video in .webm)
● “video/avc” - H.264/AVC video
● “video/mp4v-es” - MPEG4 video
● “video/3gpp” - H.263 video
● “audio/3gpp” - AMR narrowband audio
● “audio/amr-wb” - AMR wideband audio
● “audio/mpeg” - MPEG1/2 audio layer III
● “audio/mp4a-latm” - AAC audio (note, this is raw AAC packets, not packaged in LATM!)
● “audio/vorbis” - vorbis audio
● “audio/g711-alaw” - G.711 alaw audio
● “audio/g711-mlaw” - G.711 ulaw audio
当然,MediaCodec还提供了一个createByCodecName (String name)方法,支持使用组件的具体名称来创建编解码器。但是该方法使用起来有些麻烦,且官方是建议最好是配合MediaCodecList使用,因为MediaCodecList记录了所有可用的编解码器。当然,我们也可以使用该类对传入的minmeType参数进行判断,以匹配出MediaCodec对该mineType类型的编解码器是否支持。以指定MIME类型为“video/avc”为例,代码如下:

 private static MediaCodecInfo selectCodec(String mimeType) {
     // 获取所有支持编解码器数量
     int numCodecs = MediaCodecList.getCodecCount();
     for (int i = 0; i < numCodecs; i++) {
        // 编解码器相关性信息存储在MediaCodecInfo中
         MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
         // 判断是否为编码器
         if (!codecInfo.isEncoder()) {
             continue;
         }
        // 获取编码器支持的MIME类型,并进行匹配
         String[] types = codecInfo.getSupportedTypes();
         for (int j = 0; j < types.length; j++) {
             if (types[j].equalsIgnoreCase(mimeType)) {
                 return codecInfo;
             }
         }
     }
     return null;
 }
(2) 配置、启动编/解码器

编解码器配置使用的是MediaCodec的configure方法,该方法首先对MediaFormat存储的数据map进行提取,然后调用本地方法native_configure实现对编解码器的配置工作。在配置时,configure方法需要传入format、surface、crypto、flags参数,其中format为MediaFormat的实例,它使用”key-value”键值对的形式存储多媒体数据格式信息;surface用于指明解码器的数据源来自于该surface;crypto用于指定一个MediaCrypto对象,以便对媒体数据进行安全解密;flags指明配置的是编码器(CONFIGURE_FLAG_ENCODE)。

MediaFormat mFormat = MediaFormat.createVideoFormat("video/avc", 640 ,480);     // 创建MediaFormat
mFormat.setInteger(MediaFormat.KEY_BIT_RATE,600);       // 指定比特率
mFormat.setInteger(MediaFormat.KEY_FRAME_RATE,30);  // 指定帧率
mFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,mColorFormat);  // 指定编码器颜色格式  
mFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,10); // 指定关键帧时间间隔
mVideoEncodec.configure(mFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE); 

以上代码是在编码H.264时的配置方法,createVideoFormat(“video/avc”, 640 ,480)为”video/avc”类型(即H.264)编码器的MediaFormat对象,需要指定视频数据的宽高,如果编解码音频数据,则调用MediaFormat的createAudioFormat(String mime, int sampleRate,int channelCount)的方法。除了一些诸如视频帧率、音频采样率等配置参数,这里需要着重讲解一下MediaFormat.KEY_COLOR_FORMAT配置属性,该属性用于指明video编码器的颜色格式,具体选择哪种颜色格式与输入的视频数据源颜色格式有关。比如,我们都知道Camera预览采集的图像流通常为NV21或YV12,那么编码器需要指定相应的颜色格式,否则编码得到的数据可能会出现花屏、叠影、颜色失真等现象。MediaCodecInfo.CodecCapabilities.存储了编码器所有支持的颜色格式,常见颜色格式映射如下:
原始数据 编码器
NV12(YUV420sp) ———> COLOR_FormatYUV420PackedSemiPlanar
NV21 ———-> COLOR_FormatYUV420SemiPlanar
YV12(I420) ———-> COLOR_FormatYUV420Planar
当编解码器配置完毕后,就可以调用MediaCodec的start()方法,该方法会调用低层native_start()方法来启动编码器,并调用低层方法ByteBuffer[] getBuffers(input)来开辟一系列输入、输出缓存区。start()方法源码如下:

public final void start() {
        native_start();
        synchronized(mBufferLock) {
            cacheBuffers(true /* input */);
            cacheBuffers(false /* input */);
        }
 }
(3) 数据处理

MediaCodec支持两种模式编解码器,即同步synchronous、异步asynchronous,所谓同步模式是指编解码器数据的输入和输出是同步的,编解码器只有处理输出完毕才会再次接收输入数据;而异步编解码器数据的输入和输出是异步的,编解码器不会等待输出数据处理完毕才再次接收输入数据。这里,我们主要介绍下同步编解码,因为这种方式我们用得比较多。我们知道当编解码器被启动后,每个编解码器都会拥有一组输入和输出缓存区,但是这些缓存区暂时无法被使用,只有通过MediaCodec的dequeueInputBuffer/dequeueOutputBuffer方法获取输入输出缓存区授权,通过返回的ID来操作这些缓存区。下面我们通过一段官方提供的代码,进行扩展分析:

 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();

从上面代码可知,当编解码器start后,会进入一个for(;;)循环,该循环是一个死循环,以实现不断地去从编解码器的输入缓存池中获取包含数据的一个缓存区,然后再从输出缓存池中获取编解码好的输出数据。

● 获取编解码器的输入缓存区,写入数据

首先,调用MediaCodec的dequeueInputBuffer(long timeoutUs)方法从编码器的输入缓存区集合中获取一个输入缓存区,并返回该缓存区的下标index,如果index=-1说明暂时可用缓存区,当timeoutUs=0时dequeueInputBuffer会立马返回。接着调用MediaCodec的getInputBuffer(int index),该方法会将index传入给本地方法getBuffer(true /* input */, index)返回该缓存区的ByteBuffer,并且将获得的ByteBuffer对象及其index存储到BufferMap对象中,以便输入结束后对该缓存区作释放处理,交还给编解码器。getInputBuffer(int index)源码如下:

    @Nullable
    public ByteBuffer getInputBuffer(int index) {
        ByteBuffer newBuffer = getBuffer(true /* input */, index);
        synchronized(mBufferLock) {
            invalidateByteBuffer(mCachedInputBuffers, index);
     // mDequeuedInputBuffers是BufferMap的实例
            mDequeuedInputBuffers.put(index, newBuffer);
        }
        return newBuffer;
    }

然后,在获得输入缓冲区后,将数据填入数据并使用queueInputBuffer将其提交到编解码器中处理,同时将输入缓存区释放交还给编解码器。queueInputBuffer源码如下:

    public final void queueInputBuffer(
            int index,
            int offset, int size, long presentationTimeUs, int flags)
        throws CryptoException {
        synchronized(mBufferLock) {
            invalidateByteBuffer(mCachedInputBuffers, index);
             // 移除输入缓存区
            mDequeuedInputBuffers.remove(index);
        }
        try {
            native_queueInputBuffer(
                    index, offset, size, presentationTimeUs, flags);
        } catch (CryptoException | IllegalStateException e) {
            revalidateByteBuffer(mCachedInputBuffers, index);
            throw e;
        }
    }

由上述代码可知,queueInputBuffer主要通过调用低层方法native_queueInputBuffer实现,该方法需要传入5个参数,其中index是输入缓存区的下标,编解码器就是通过index找到缓存区的位置;offset为有效数据存储在buffer中的偏移量;size为有效输入原始数据的大小;presentationTimeUs为缓冲区显示时间戳,通常为0;flags为输入缓存区标志,通常设置为 BUFFER_FLAG_END_OF_STREAM。

● 获取编解码器的输出缓存区,读出数据

首先,与上述通过dequeueInputBuffer和getInputBuffer获取输入缓存区类似,MediaCodec也提供了dequeueOutputBuffer和getOutputBuffer方法用来帮助我们获取编解码器的输出缓存区。但是与dequeueInputBuffer不同的是,dequeueOutputBuffer还需要传入一个MediaCodec.BufferInfo对象。MediaCodec.BufferInfo是MediaCodec的一个内部类,它记录了编解码好的数据在输出缓存区中的偏移量和大小。

  public final static class BufferInfo {
        public void set(
                int newOffset, int newSize, long newTimeUs, @BufferFlag int newFlags) {
            offset = newOffset;
            size = newSize;
            presentationTimeUs = newTimeUs;
            flags = newFlags;
        }
        public int offset // 偏移量
        public int size;    // 缓存区有效数据大小
        public long presentationTimeUs; // 显示时间戳
        public int flags;                   // 缓存区标志

        @NonNull
        public BufferInfo dup() {
            BufferInfo copy = new BufferInfo();
            copy.set(offset, size, presentationTimeUs, flags);
            return copy;
        }
    };

然后,通过dequeueOutputBuffer的源码可知,当dequeueOutputBuffer返回值>=0时,输出缓存区的数据才是有效的。当调用本地方法native_dequeueOutputBuffer返回INFO_OUTPUT_BUFFERS_CHANGED时,会调用cacheBuffers方法重新获取一组输出缓存区mCachedOutputBuffers(ByteBuffer[])。这就解释了如果我们使用getOutputBuffers方法(API21后被弃用,使用getOutputBuffer(index)代替)来获取编解码器的输出缓存区,那么就需要在调用dequeueOutputBuffer判断其返回值,如果返回值为MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED,则需要重新获取输出缓存区集合。此外,这里还要dequeueOutputBuffer的另外两个返回值:MediaCodec.INFO_TRY_AGAIN_LATER、MediaCodec.INFO_OUTPUT_FORMAT_CHANGED,前者表示获取编解码器输出缓存区超时,后者表示编解码器数据输出格式改变,随后输出的数据将使用新的格式。因此,我们需要在调用dequeueOutputBuffer判断返回值是否为INFO_OUTPUT_FORMAT_CHANGED,需要通过MediaCodec的getOutputFormat重新设置MediaFormt对象。

  public final int dequeueOutputBuffer(
            @NonNull BufferInfo info, long timeoutUs) {
        int res = native_dequeueOutputBuffer(info, timeoutUs);
        synchronized(mBufferLock) {
            if (res == INFO_OUTPUT_BUFFERS_CHANGED) {
            // 将会调用getBuffers()底层方法
                cacheBuffers(false /* input */);
            } else if (res >= 0) {
                validateOutputByteBuffer(mCachedOutputBuffers, res, info);
                if (mHasSurface) {
                    mDequeuedOutputInfos.put(res, info.dup());
                }
            }
        }
        return res;
    }

最后,当输出缓存区的数据被处理完毕后,通过调用MediaCodec的releaseOutputBuffer释放输出缓存区,并交还给编解码器,该输出缓存区将不能被使用,直到下一次通过dequeueOutputBuffer获取。releaseOutputBuffer方法接收两个参数:Index、render,其中,Index为输出缓存区索引;render表示当配置编码器时指定了surface,那么应该置为true,输出缓存区的数据将被传递到surface中。源码如下:

   public final void releaseOutputBuffer(int index, boolean render) {
        BufferInfo info = null;
        synchronized(mBufferLock) {
            invalidateByteBuffer(mCachedOutputBuffers, index);
            mDequeuedOutputBuffers.remove(index);
            if (mHasSurface) {
                info = mDequeuedOutputInfos.remove(index);
            }
        }
        releaseOutputBuffer(index, render, false /* updatePTS */, 0 /* dummy */);
    }
2018-05-06 22:30:56 LinChengChun 阅读数 0
  • 知识总结

    通过FFmpeg实现万能播放器(直播美颜,拉流) 学会MediaCodec硬解码实现 抖音 视频编辑 Rtmp协议实现哔哩哔哩直播推流 OpenGl动手制作灵魂出窍,手写美颜特效

    27092课时 0分钟 3人学习 齐行超
    免费试看

一、MediaCodec介绍

MediaCodec类可以用来访问底层媒体编解码器,即编码器/解码器的组件。 它是Android底层多媒体支持架构的一部分(通常与MediaExtractor,MediaSync,MediaMuxer,MediaCrypto,MediaDrm,Image,Surface和AudioTrack一起使用)。
这里写图片描述
In broad terms, a codec processes input data to generate output data. It processes data asynchronously and uses a set of input and output buffers. At a simplistic level, you request (or receive) an empty input buffer, fill it up with data and send it to the codec for processing. The codec uses up the data and transforms it into one of its empty output buffers. Finally, you request (or receive) a filled output buffer, consume its contents and release it back to the codec.
从广义上讲,一个编解码器处理输入数据以生成输出数据。 它异步地处理数据,并使用一组输入和输出缓冲器。 从一个简单的层面上看,可请求(或接收)一个空的输入缓冲器,然后用数据填满它,并将其发送到编解码器去处理。 编解码器使用这些数据并转换这些数据到它某个空的输出缓冲区。 最后,您请求(或接收)一个已填充数据的输出缓冲区,消耗其内容并将其释放回并回到编解码器。

1、Data Types (数据类型)

编解码器可以处理三类数据:压缩数据、原始音频数据、原始视频数据。所有这三类数据通过ByteBuffers来执行,当然为了提高编解码效果,你最好通过Surface来显示原始视频数据;因为surface使用native层的视频缓存区,没有通过映射或拷贝到JVM空间的缓冲区。正常情况下,当你使用surface的时候,不能访问原始视频数据,但是可以通过ImageRender类来获取原始视频帧。这是一种比ByteBuffers更高效的方法,因为许多本地缓冲区被映射到直接ByteBuffers。当你使用ByteBuffers模式,你可以通过Image类和 getInput/OutputImage(int)方法获取原始视频帧。
* Compressed Buffers压缩缓冲区
输入和输出缓冲区包含了对应类型的压缩数据;对于视频类型通常是简单的压缩视频帧;音频数据通常是一个单入单元,(一种编码格式典型的包含了许多ms的音频类型),但当一个缓冲区包含了多种编码音频进入单元,可以不需要。另一方面,缓冲区不能在任意字节边界开始或停止,但当标记了BUFFER_FLAG_PARTIAL_FRAME标记时,可以访问帧或进入单元边界。
* Raw Audio Buffers原始音频缓冲区
原始音频缓冲区包含完整的PCM格式的帧数据,一种通道对应一个采样率。每一种采样率是一个16位有符号整型在规定参数里面;下面是获取通道采样率的方法

short[] getSamplesForChannel(MediaCodec codec, int bufferId, int channelIx) {
  ByteBuffer outputBuffer = codec.getOutputBuffer(bufferId);
  MediaFormat format = codec.getOutputFormat(bufferId);
  ShortBuffer samples = outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer();
  int numChannels = formet.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
  if (channelIx < 0 || channelIx >= numChannels) {
    return null;
  }
  short[] res = new short[samples.remaining() / numChannels];
  for (int i = 0; i < res.length; ++i) {
    res[i] = samples.get(i * numChannels + channelIx);
  }
  return res;
}
  • Raw Video Buffers原始视频缓冲区
    在ByteBuffer模式,视频缓冲区根据颜色格式;可以通过 getCodecInfo().getCapabilitiesForType(…).colorFormats 获取支持的颜色格式,视频编码支持三种类型的颜色格式:
    native raw video format: 标记COLOR_FormatSurface,可以配合输入输出surface使用
    flexible YUV buffers:COLOR_FormatYUV420Flexible,可以配合输入输出surface、在ByteBuffer模式,可以通过getInput/OutputImage(int)访问
    other, specific formats:这些格式只在ByteBuffer 模式支持。一些格式是厂商特有的,其他的定义在MediaCodecInfo.CodecCapabilities;
    自从5.1.1之后,所有的编解码器支持YUV 4:2:0 buffers。
  • Accessing Raw Video ByteBuffers on Older Devices在老的设备上面访问原始视频缓冲区
    ~~~

2、States状态

这里写图片描述
编解码器理论上存在三种状态:停止、执行、释放;停止状态也包含三种子状态:未初始化的、已配置的、错误;执行状态也包含三种子状态:已刷新、正在运行、流结束;
当你用工厂方法创建一个编解码器,是出于未初始化状态的。第一步,通过 configure(…)方法进入已配置状态;其次,通过start()方法进入执行状态;此时,你可以通过上面缓冲队列来处理数据了。
在start()之后,编解码出于已刷新子状态,此时持有所有的缓冲区;当第一个输入缓冲块被出队时,编解码器会耗费许多时间进入运行状态。当一个输入缓冲块被入队时(被标记流结束标记),编解码器进入流结束状态;此时,编解码器不在接收输入缓冲块,但是可以产生输出缓冲块,直到流结束块被出队。你可以在任意时刻,通过调用flush(),进入已刷新状态。
调用stop(),让其进入未初始化状态,如果需要使用,需要再配置一次。当你已经用完编解码器,你需要release();
某些情况下,编解码器会遭遇错误进入错误状态;可以根据不合法返回值或者异常来判断;调用reset()可以复位编码器,让其可以重新使用,并进入未初始化状态。调用releases()进入最终释放状态;

3、Creation创建

可以根据指定的MediaFormat通过MediaCodecList一个编解码器;可以根据 MediaExtractor.getTrackFormat来创建一个可以用于解码文件和流的编解码器;在引入其他格式之前,当你想MediaFormat.setFeatureEnabled,需要通过MediaCodecList.findDecoderForFormat获得与名字对应的特殊的媒体格式的编解码器; 最后通过createByCodecName(String)创建;
另外的,你可以通过MIME类型使用createDecoder/EncoderByType(String)来创建;
* Creating secure decoders
~~~

4、Initialization初始化

创建好之后,可以设置回调 setCallback来异步处理数据;然后configure配置指定的媒体格式。你可以为视频生成指定一个输出surface;也可以设置安全编码,参考MediaCrypto;最后编解码器运行在多个模式下,需要特殊指定在编码或解码状态;
如果你想处理原始输入视频缓冲区,可以在配置后通过createInputSurface()创建一个指定的Surface。也可以通过setInputSurface(Surface)设置编解码器使用指定的Surface。
* Codec-specific Data特定编解码源数据
一些格式,AAC audio and MPEG4, H.264 and H.265 video格式要求预置启动参数或者编解码特殊数据。当处理一些压缩格式时,这些数据必须在任意帧数据之前和start()之后提交到编解码器。这些数据在调用queueInputBuffer时需要被标记BUFFER_FLAG_CODEC_CONFIG。
这些数据也可以通过configure来配置,可以从MediaExtractor获取并放在MediaFromat里面。这些数据会在start()时提交到比爱你解码器里面。
编码器会在任何可用数据之前创建和返回特定标记了codec-config标记的编码参数,缓冲区包含了没有时间戳的codec-specific-data。

5、Data Processing 执行编解码

每一个编解码器在API调用时维持一系列根据buffer-ID对应的输入输出缓冲区;在成功start()编解码器之后,owns客户端既没有输入也没有输出缓冲区。在同步模式下,通过dequeueInput/OutputBuffer()方法从codec里面获取一个输入/输出缓冲区;在异步模式,你可以注册一个回调,通过MediaCodec.Callback.onInput/OutputBufferAvailable(…)自动接收缓冲区。
在获取一个输入缓冲区之后,填充数据,并通过queueInputBuffer提交到codec,不要提交多个同样时间戳一样的输入数据到codec。
codec处理完后,会返回一个只读输出缓冲区数据;异步模式可以通过 onOutputBufferAvailable读取,同步模式通过dequeuOutputBuffer读取;最后需要调用releaseOutputBuffer返回缓冲区到codec。
* Asynchronous Processing using Buffers异步模式使用缓冲数组处理
通常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();
  • Synchronous Processing using Buffers同步模式使用缓冲数组处理
    通常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();
  • Synchronous Processing using Buffer Arrays (deprecated)同步模式使用缓冲区数组处理
    官方例子
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
codec.start();
ByteBuffer[] inputBuffers = codec.getInputBuffers();
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
for (;;) {
  int inputBufferId = codec.dequeueInputBuffer(…);
  if (inputBufferId >= 0) {
    // fill inputBuffers[inputBufferId] with valid data
    …
    codec.queueInputBuffer(inputBufferId, …);
  }
  int outputBufferId = codec.dequeueOutputBuffer(…);
  if (outputBufferId >= 0) {
    // outputBuffers[outputBufferId] is ready to be processed or rendered.
    …
    codec.releaseOutputBuffer(outputBufferId, …);
  } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
    outputBuffers = codec.getOutputBuffers();
  } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    // Subsequent data will conform to new format.
    MediaFormat format = codec.getOutputFormat();
  }
}
codec.stop();
codec.release();
  • End-of-stream Handling如何实现结束流
    当你没有输入数据的时候,需要 queueInputBuffer一个BUFFER_FLAG_END_OF_STREAM标记的数组到codec。可以把这个标签放在设置在最后一个可用输入buffer里面,或者用一个额外的空输入数据里面设置这个标志位,如果是第二种方式,可以忽略时间戳。
    编解码器会继续返回输出数据,直到dequeueOutputBuffer或onOutputBufferAvailable返回的MediaCodec.BufferInfo集合出现同样的end-of-stream标志。
    除非codec已经处于已刷新、已停止或者重启状态,否则不要在end-of-stream之后另外提交输入数据。
  • Using an Output Surface 使用一个输出Surface
    当你使用输出到surface时,需要设置codec为ByteBuffer模式;另外,当使用一个输出surface时,你有以下三种选择:
    Do not render the buffer: Call releaseOutputBuffer(bufferId, false).
    Render the buffer with the default timestamp: Call releaseOutputBuffer(bufferId, true).
    Render the buffer with a specific timestamp: Call releaseOutputBuffer(bufferId, timestamp).
  • Transformations When Rendering onto Surface在渲染时改变参数
    当codec处于surface模式,任何改变矩形、角度、video大小模式会自动导致一个异常;
  • Using an Input Surface
    当你是用一个输入surface时,不允许访问输入buffers,因为buffers会自动从surface传送到codec。

6、Seeking & Adaptive Playback Support快进快退,自适应播放支持

视频解码的自适应播放支持只用工作在codec解码到一个surface。
* Stream Boundary and Key Frames流边界和关键帧
输入流的必须确保第一帧是一个关键帧,才能送到input data里面。
* For decoders that do not support adaptive playback (including when not decoding onto a Surface)
* For decoders that support and are configured for adaptive playback配置为支持自适应播放的解码器
为了对改变进度的数据进行解码,不需要刷新解码器。然而输入数据在非连续之后必须从一个可用的流起始/关键帧开始。
许多H.264, H.265, VP8 and VP9视频格式,很大可能中间改变图片大小和配置。所以需要打包新的codec-specific配置数据和关键帧到一个包含起始码的buffer里面,并提交到codec里面。
你会在图片大小发生改变之后,任何有效帧之前,并在dequeueOutputBuffer里面收到INFO_OUTPUT_FORMAT_CHANGED的数据。

7、Error handling 错误处理

二、MediaCodec与音视频基础知识点补充与编程例子

1、Activity测试页面,从mp4文件中读取音视频并播放出来,播放mp4文件

package com.cclin.jubaohe.activity.Media;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Bundle;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import com.cclin.jubaohe.R;
import com.cclin.jubaohe.base.BaseActivity;
import com.cclin.jubaohe.util.CameraUtil;
import com.cclin.jubaohe.util.LogUtil;
import com.cclin.jubaohe.util.SDPathConfig;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Created by LinChengChun on 2018/4/14.
*/
public class MediaTestActivity extends BaseActivity implements SurfaceHolder.Callback, View.OnClickListener {
    private final static String MEDIA_FILE_PATH = SDPathConfig.LIVE_MOVIE_PATH+"/18-04-12-10:47:06-0.mp4";
    private Surface mSurface;
    private SurfaceView mSvRenderFromCamera;
    private SurfaceView mSvRenderFromFile;
    Button mBtnCameraPreview;
    Button mBtnPlayMediaFile;
    private MediaStream mMediaStream;
    private Thread mVideoDecoderThread;
    private AudioTrack mAudioTrack;
    private Thread mAudioDecoderThread;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBtnCameraPreview = retrieveView(R.id.btn_camera_preview);
        mBtnPlayMediaFile = retrieveView(R.id.btn_play_media_file);
        mBtnCameraPreview.setOnClickListener(this);
        mBtnPlayMediaFile.setOnClickListener(this);
        mSvRenderFromCamera = retrieveView(R.id.sv_render);
        mSvRenderFromCamera.setOnClickListener(this);
        mSvRenderFromCamera.getHolder().addCallback(this);
        mSvRenderFromFile = retrieveView(R.id.sv_display);
        mSvRenderFromFile.setOnClickListener(this);
        mSvRenderFromFile.getHolder().addCallback(this);
        init();
    }
    @Override
    protected int initLayout() {
        return R.layout.activity_media_test;
    }
    private void init(){
        File file = new File(MEDIA_FILE_PATH);
        if (!file.exists()){
            LogUtil.e("文件不存在!!");
            return;
        }
        LogUtil.e("目标文件存在!!");
    }
    private void startVideoDecoder(){
        // fill inputBuffer with valid data
        mVideoDecoderThread = new Thread("mVideoDecoderThread"){
            @Override
            public void run() {
                super.run();
                MediaFormat mMfVideo = null, mMfAudio = null;
                String value = null;
                String strVideoMime = null;
                String strAudioMime = null;
                try {
                    MediaExtractor mediaExtractor = new MediaExtractor(); // 提取器用来从文件中读取音视频
                    mediaExtractor.setDataSource(MEDIA_FILE_PATH);
                    int numTracks = mediaExtractor.getTrackCount(); // 轨道数,一般为2
                    LogUtil.e("获取track数"+numTracks);
                    for (int i=0; i< numTracks; i++) { // 检索每个轨道的格式
                        MediaFormat mediaFormat = mediaExtractor.getTrackFormat(i);
                        LogUtil.e("单独显示track MF:"+mediaFormat);
                        value = mediaFormat.getString(MediaFormat.KEY_MIME);
                        if (value.contains("audio")){
                            mMfAudio = mediaFormat;
                            strAudioMime = value;
                        }else {
                            mMfVideo = mediaFormat;
                            strVideoMime = value;
                            mediaExtractor.selectTrack(i);
                        }
                    }
                    mSurface = mSvRenderFromFile.getHolder().getSurface();
                    MediaCodec codec = MediaCodec.createDecoderByType(strVideoMime); // 创建编解码器
                    codec.configure(mMfVideo, mSurface, null, 0); // 配置解码后的视频帧数据直接渲染到Surface
                    codec.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
                    codec.start(); // 启动编解码器,让codec进入running模式
                    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); //缓冲区信息
                    int size = -1, outputBufferIndex = -1;
                    LogUtil.e("开始解码。。。");
                    long previewStampUs = 0l;
                    do {
                        int inputBufferId = codec.dequeueInputBuffer(10);// 从编码器中获取 输入缓冲区
                        if (inputBufferId >= 0) {
                            ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId); // 获取该输入缓冲区
                            // fill inputBuffer with valid data
                            inputBuffer.clear(); // 清空缓冲区
                            size = mediaExtractor.readSampleData(inputBuffer, 0); // 从提取器中获取一帧数据填充到输入缓冲区
                            LogUtil.e("readSampleData: size = "+size);
                            if (size < 0)
                                break;
                            int trackIndex = mediaExtractor.getSampleTrackIndex();
                            long presentationTimeUs = mediaExtractor.getSampleTime(); // 获取采样时间
                            LogUtil.e("queueInputBuffer: 把数据放入编码器。。。");
                            codec.queueInputBuffer(inputBufferId, 0, size, presentationTimeUs, 0); // 将输入缓冲区压入编码器
                            mediaExtractor.advance(); // 获取下一帧
                            LogUtil.e("advance: 获取下一帧。。。");
                            outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 10000); // 从编码器中读取解码完的数据
                            LogUtil.e("outputBufferIndex = "+outputBufferIndex);
                            switch (outputBufferIndex) {
                                case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
//                                    MediaFormat mf = codec.getOutputFormat(outputBufferIndex); // 导致播放视频失败
                                    MediaFormat mf = codec.getOutputFormat();
                                    LogUtil.e("INFO_OUTPUT_FORMAT_CHANGED:"+mf);

                                    break;
                                case MediaCodec.INFO_TRY_AGAIN_LATER:
                                    LogUtil.e("解码当前帧超时");
                                    break;
                                case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                                    //outputBuffers = videoCodec.getOutputBuffers();
                                    LogUtil.e("output buffers changed");
                                    break;
                                default:
                                    //直接渲染到Surface时使用不到outputBuffer
                                    //ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                                    //延时操作
                                    //如果缓冲区里的可展示时间>当前视频播放的进度,就休眠一下
                                    boolean firstTime = previewStampUs == 0l;
                                    long newSleepUs = -1;
                                    long sleepUs = (bufferInfo.presentationTimeUs - previewStampUs);
                                    if (!firstTime) {
                                        long cache = 0;
                                        newSleepUs = CameraUtil.fixSleepTime(sleepUs, cache, -100000);
                                    }
                                    previewStampUs = bufferInfo.presentationTimeUs;
                                    //渲染
                                    if (newSleepUs < 0)
                                        newSleepUs = 0;
                                    Thread.sleep(newSleepUs / 1000);
                                    codec.releaseOutputBuffer(outputBufferIndex, true); // 释放输入缓冲区,并渲染到Surface
                                    break;
                            }
                        }
                    }while (!this.isInterrupted());
                    LogUtil.e("解码结束。。。");
                    codec.stop();
                    codec.release();
                    codec = null;
                    mediaExtractor.release();
                    mediaExtractor = null;
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        };
        mVideoDecoderThread.start();
    }

    private void startAudioDecoder(){
        mAudioDecoderThread = new Thread("AudioDecoderThread"){
            @Override
            public void run() {
                super.run();
                try {

                    MediaFormat mMfVideo = null, mMfAudio = null;
                    String value = null;
                    String strVideoMime = null;
                    String strAudioMime = null;

                    MediaExtractor mediaExtractor = new MediaExtractor();
                    mediaExtractor.setDataSource(MEDIA_FILE_PATH);
                    int numTracks = mediaExtractor.getTrackCount();
                    LogUtil.e("获取track数"+numTracks);
                    for (int i=0; i< numTracks; i++) {
                        MediaFormat mediaFormat = mediaExtractor.getTrackFormat(i);
                        LogUtil.e("单独显示track MF:"+mediaFormat);
                        value = mediaFormat.getString(MediaFormat.KEY_MIME);
                        if (value.contains("audio")){
                            mMfAudio = mediaFormat;
                            strAudioMime = value;
                            mediaExtractor.selectTrack(i);
                        }else {
                            mMfVideo = mediaFormat;
                            strVideoMime = value;
                        }
                    }
//                    mMfAudio.setInteger(MediaFormat.KEY_IS_ADTS, 1);
                    mMfAudio.setInteger(MediaFormat.KEY_BIT_RATE, 16000);
                    MediaCodec codec = MediaCodec.createDecoderByType(strAudioMime);
                    codec.configure(mMfAudio, null, null, 0);
                    codec.start();
                    ByteBuffer outputByteBuffer = null;
                    ByteBuffer[] outputByteBuffers = null;
                    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                    int size = -1, outputBufferIndex = -1;
                    long previewStampUs = 01;
                    LogUtil.e("开始解码。。。");
                    if (mAudioTrack == null){
                        int sample_rate = mMfAudio.getInteger(MediaFormat.KEY_SAMPLE_RATE);
                        int channels = mMfAudio.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
                        int sampleRateInHz = (int) (sample_rate * 1.004);
                        int channelConfig = channels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO;
                        int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
                        int bfSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat) * 4;
                        mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, channelConfig, audioFormat, bfSize, AudioTrack.MODE_STREAM);
                    }
                    mAudioTrack.play();

//                    outputByteBuffers = codec.getOutputBuffers();
                    do {

                        int inputBufferId = codec.dequeueInputBuffer(10);
                        if (inputBufferId >= 0) {
                            ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
                            // fill inputBuffer with valid data
                            inputBuffer.clear();
                            size = mediaExtractor.readSampleData(inputBuffer, 0);
                            if (size<0)
                                break;
                            long presentationTimeUs = mediaExtractor.getSampleTime();
//                            LogUtil.e("queueInputBuffer: 把数据放入编码器。。。");
                            codec.queueInputBuffer(inputBufferId, 0, size, presentationTimeUs, 0);
                            mediaExtractor.advance();
//                            LogUtil.e("advance: 获取下一帧。。。");
                            outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 50000);
                            switch (outputBufferIndex) {
                                case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
//                                    MediaFormat mf = codec.getOutputFormat(outputBufferIndex);
                                    MediaFormat mf = codec.getOutputFormat();
                                    LogUtil.e("INFO_OUTPUT_FORMAT_CHANGED:"+mf);
                                    break;
                                case MediaCodec.INFO_TRY_AGAIN_LATER:
                                    LogUtil.e( "解码当前帧超时");
                                    break;
                                case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
//                                    outputByteBuffer = codec.getOutputBuffers();
                                    LogUtil.e( "output buffers changed");
                                    break;
                                default:
                                    //直接渲染到Surface时使用不到outputBuffer
                                    //ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                                    //延时操作
                                    //如果缓冲区里的可展示时间>当前视频播放的进度,就休眠一下
                                    LogUtil.e("outputBufferIndex = "+outputBufferIndex);
//                                    outputByteBuffer = outputByteBuffers[outputBufferIndex];
                                    outputByteBuffer = codec.getOutputBuffer(outputBufferIndex); // 获取解码后的数据
                                    outputByteBuffer.clear();
                                    byte[] outData = new byte[bufferInfo.size];
                                    outputByteBuffer.get(outData);
                                    boolean firstTime = previewStampUs == 0l;
                                    long newSleepUs = -1;
                                    long sleepUs = (bufferInfo.presentationTimeUs - previewStampUs);
                                    if (!firstTime){
                                        long cache = 0;
                                        newSleepUs = CameraUtil.fixSleepTime(sleepUs, cache, -100000);
                                    }
                                    previewStampUs = bufferInfo.presentationTimeUs;
                                    //渲染
                                    if (newSleepUs < 0)
                                        newSleepUs = 0;
                                    Thread.sleep(newSleepUs/1000);
                                    mAudioTrack.write(outData, 0, outData.length); // 输出音频
                                    codec.releaseOutputBuffer(outputBufferIndex, false); // 释放输出缓冲区
                                    break;
                            }
                        }
                    }while (!this.isInterrupted());
                    LogUtil.e("解码结束。。。");
                    codec.stop();
                    codec.release();
                    codec = null;
                    mAudioTrack.stop();
                    mAudioTrack.release();
                    mAudioTrack = null;
                    mediaExtractor.release();
                    mediaExtractor = null;
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        mAudioDecoderThread.start();
    }

    @Override
    public void onClick(View view){
        switch (view.getId()){
            case R.id.sv_render:
                mMediaStream.getCamera().autoFocus(null);
                break;
            case R.id.sv_display:
                break;

            case R.id.btn_camera_preview:
                break;

            case R.id.btn_play_media_file:
                break;

            default:break;
        }
    }

    private int getDgree() {
        int rotation = getWindowManager().getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break; // Natural orientation
            case Surface.ROTATION_90:
                degrees = 90;
                break; // Landscape left
            case Surface.ROTATION_180:
                degrees = 180;
                break;// Upside down
            case Surface.ROTATION_270:
                degrees = 270;
                break;// Landscape right
        }
        return degrees;
    }

    private void onMediaStreamCreate(){
        if (mMediaStream==null)
            mMediaStream = new MediaStream(this, mSvRenderFromCamera.getHolder());
        mMediaStream.setDgree(getDgree());
        mMediaStream.createCamera();
        mMediaStream.startPreview();
    }

    private void onMediaStreamDestroy(){
        mMediaStream.release();
        mMediaStream = null;
    }

    @Override
    protected void onPause() {
        super.onPause();
        onMediaStreamDestroy();
        if (mVideoDecoderThread!=null)
            mVideoDecoderThread.interrupt();
        if (mAudioDecoderThread!=null)
            mAudioDecoderThread.interrupt();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (isSurfaceCreated && mMediaStream == null){
            onMediaStreamCreate();
        }
    }

    private boolean isSurfaceCreated = false;
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        LogUtil.e("surfaceCreated: "+holder);

        if (holder.getSurface() == mSvRenderFromCamera.getHolder().getSurface()){
            isSurfaceCreated = true;
            onMediaStreamCreate();
        }else if (holder.getSurface() == mSvRenderFromFile.getHolder().getSurface()){
            if (new File(MEDIA_FILE_PATH).exists()) {
                startVideoDecoder();
                startAudioDecoder();
            }
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        LogUtil.e("surfaceChanged: "
                +"\nholder = "+holder
                +"\nformat = "+format
                +"\nwidth = "+width
                +"\nheight = "+height);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        LogUtil.e("surfaceDestroyed: ");
        if (holder.getSurface() == mSvRenderFromCamera.getHolder().getSurface()) {
            isSurfaceCreated = false;
        }
    }
}

2、将摄像头的数据编码成h264数据

    final int millisPerframe = 1000 / 20;
    long lastPush = 0;
    @Override
    public void run() {
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        int outputBufferIndex = 0;
        byte[] mPpsSps = new byte[0];
        byte[] h264 = new byte[mWidth * mHeight];
        do {
            outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 10000); // 从codec中获取编码完的数据
            if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                // no output available yet
            } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                // not expected for an encoder
                outputBuffers = mMediaCodec.getOutputBuffers();
            } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                synchronized (HWConsumer.this) {
                    newFormat = mMediaCodec.getOutputFormat();
                    EasyMuxer muxer = mMuxer;
                    if (muxer != null) {
                        // should happen before receiving buffers, and should only happen once

                        muxer.addTrack(newFormat, true);
                    }
                }
            } else if (outputBufferIndex < 0) {
                // let's ignore it
            } else {
                ByteBuffer outputBuffer;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex);
                } else {
                    outputBuffer = outputBuffers[outputBufferIndex];
                }
                outputBuffer.position(bufferInfo.offset);
                outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
                EasyMuxer muxer = mMuxer;
                if (muxer != null) {
                    muxer.pumpStream(outputBuffer, bufferInfo, true);
                }

                boolean sync = false;
                if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {// codec会产生sps和pps
                    sync = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; // 标记是I帧还是同步帧
                    if (!sync) { // 如果是同步帧,也就是填充着pps和sps参数
                        byte[] temp = new byte[bufferInfo.size];
                        outputBuffer.get(temp);
                        mPpsSps = temp;
                        mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                        continue; // 等待下一帧
                    } else {
                        mPpsSps = new byte[0];
                    }
                }
                sync |= (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; // 标记是否是关键帧
                int len = mPpsSps.length + bufferInfo.size;
                if (len > h264.length) {
                    h264 = new byte[len];
                }
                if (sync) { // 如果是关键帧,需要在帧头添加pps sps参数
                    System.arraycopy(mPpsSps, 0, h264, 0, mPpsSps.length);
                    outputBuffer.get(h264, mPpsSps.length, bufferInfo.size);
                    mPusher.push(h264, 0, mPpsSps.length + bufferInfo.size, bufferInfo.presentationTimeUs / 1000, 1);
                    if (BuildConfig.DEBUG)
                        Log.i(TAG, String.format("push i video stamp:%d", bufferInfo.presentationTimeUs / 1000));
                } else { // 非I帧直接读取出来
                    outputBuffer.get(h264, 0, bufferInfo.size);
                    mPusher.push(h264, 0, bufferInfo.size, bufferInfo.presentationTimeUs / 1000, 1);
                    if (BuildConfig.DEBUG)
                        Log.i(TAG, String.format("push video stamp:%d", bufferInfo.presentationTimeUs / 1000));
                }
                mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
            }
        }
        while (mVideoStarted);
    }
    @Override
    public int onVideo(byte[] data, int format) {
        if (!mVideoStarted) return 0;
        try {
            if (lastPush == 0) {
                lastPush = System.currentTimeMillis();
            }
            long time = System.currentTimeMillis() - lastPush;
            if (time >= 0) {
                time = millisPerframe - time;
                if (time > 0) Thread.sleep(time / 2);
            }
            if (format == ImageFormat.YV12) {
                JNIUtil.yV12ToYUV420P(data, mWidth, mHeight);
            } else {
                JNIUtil.nV21To420SP(data, mWidth, mHeight);
            }
            int bufferIndex = mMediaCodec.dequeueInputBuffer(0);
            if (bufferIndex >= 0) {
                ByteBuffer buffer = null;
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                    buffer = mMediaCodec.getInputBuffer(bufferIndex);
                } else {
                    buffer = inputBuffers[bufferIndex];
                }
                buffer.clear();
                buffer.put(data);
                buffer.clear();
                mMediaCodec.queueInputBuffer(bufferIndex, 0, data.length, System.nanoTime() / 1000, MediaCodec.BUFFER_FLAG_KEY_FRAME); // 标记含有关键帧
            }
            if (time > 0) Thread.sleep(time / 2); // 添加延时,确保帧率
            lastPush = System.currentTimeMillis();
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        return 0;
    }

    /**
    * 初始化编码器
    */
    private void startMediaCodec() throws IOException {
            /*
        SD (Low quality) SD (High quality) HD 720p
1 HD 1080p
1
Video resolution 320 x 240 px 720 x 480 px 1280 x 720 px 1920 x 1080 px
Video frame rate 20 fps 30 fps 30 fps 30 fps
Video bitrate 384 Kbps 2 Mbps 4 Mbps 10 Mbps
        */
        int framerate = 20;
//        if (width == 640 || height == 640) {
//            bitrate = 2000000;
//        } else if (width == 1280 || height == 1280) {
//            bitrate = 4000000;
//        } else {
//            bitrate = 2 * width * height;
//        }

        int bitrate = (int) (mWidth * mHeight * 20 * 2 * 0.05f);
        if (mWidth >= 1920 || mHeight >= 1920) bitrate *= 0.3;
        else if (mWidth >= 1280 || mHeight >= 1280) bitrate *= 0.4;
        else if (mWidth >= 720 || mHeight >= 720) bitrate *= 0.6;
        EncoderDebugger debugger = EncoderDebugger.debug(mContext, mWidth, mHeight);
        mVideoConverter = debugger.getNV21Convertor();
        mMediaCodec = MediaCodec.createByCodecName(debugger.getEncoderName());
        MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, debugger.getEncoderColorFormat());
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
        mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mMediaCodec.start();

        Bundle params = new Bundle();
        params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            mMediaCodec.setParameters(params);
        }
    }

三、总结

MediaCodec编解码器h264算法生成的pps和sps是在同一个配置帧里面,如果需要分别使用的话需要分开解析。

inputBuffers = mMediaCodec.getInputBuffers();
outputBuffers = mMediaCodec.getOutputBuffers();
int bufferIndex = mMediaCodec.dequeueInputBuffer(0);
if (bufferIndex >= 0) {
    inputBuffers[bufferIndex].clear();
    mConvertor.convert(data, inputBuffers[bufferIndex]);
    mMediaCodec.queueInputBuffer(bufferIndex, 0, inputBuffers[bufferIndex].position(), System.nanoTime() / 1000, 0);

    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
    int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);

    while (outputBufferIndex >= 0) {
        ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];

//                        String data0 = String.format("%x %x %x %x %x %x %x %x %x %x ", outData[0], outData[1], outData[2], outData[3], outData[4], outData[5], outData[6], outData[7], outData[8], outData[9]);
//                        Log.e("out_data", data0);

        //记录pps和sps
        int type = outputBuffer.get(4) & 0x07; // 判断是什么帧

//                                LogUtil.e(TAG, String.format("type is %d", type));
        if (type == 7 || type == 8) {
            byte[] outData = new byte[bufferInfo.size];
            outputBuffer.get(outData);
            mPpsSps = outData;

            ArrayList<Integer> posLists = new ArrayList<>(2);
            for (int i=0; i<bufferInfo.size-3; i++){    // 找寻 pps sps
                if (outData[i]==0 && outData[i+1]==0&& outData[i+2]==0 && outData[i+3]==1){
                    posLists.add(i);
                }
            }
            int sps_pos = posLists.get(0);
            int pps_pos = posLists.get(1);
            posLists.clear();
            posLists = null;
            ByteBuffer csd0 = ByteBuffer.allocate(pps_pos);
            csd0.put(outData, sps_pos, pps_pos);
            csd0.clear();
            mCSD0 = csd0;
            LogUtil.e(TAG, String.format("CSD-0 searched!!!"));

            ByteBuffer csd1 = ByteBuffer.allocate(outData.length-pps_pos);
            csd1.put(outData, pps_pos, outData.length-pps_pos);
            csd1.clear();
            mCSD1 = csd1;
            LogUtil.e(TAG, String.format("CSD-1 searched!!!"));

            LocalBroadcastManager.getInstance(mApplicationContext).sendBroadcast(new Intent(ACTION_H264_SPS_PPS_GOT));
        } else if (type == 5) {
            //在关键帧前面加上pps和sps数据
            System.arraycopy(mPpsSps, 0, h264, 0, mPpsSps.length);
            outputBuffer.get(h264, mPpsSps.length, bufferInfo.size);
            if (isPushing)
                mEasyPusher.push(h264, 0,mPpsSps.length+bufferInfo.size, System.currentTimeMillis(), 1);
            if (mEasyMuxer !=null && !isRecordPause) {
                bufferInfo.presentationTimeUs = TimeStamp.getInstance().getCurrentTimeUS();
                mEasyMuxer.pumpStream(outputBuffer, bufferInfo, true);// 用于保存本地视频到本地
                isWaitKeyFrame = false; // 拿到关键帧,则清除等待关键帧的条件
//                                        LocalBroadcastManager.getInstance(mApplicationContext).sendBroadcast(new Intent(ACTION_I_KEY_FRAME_GOT));
            }
        } else {
            outputBuffer.get(h264, 0, bufferInfo.size);
            if (System.currentTimeMillis() - timeStamp >= 3000) {
                timeStamp = System.currentTimeMillis();
                if (Build.VERSION.SDK_INT >= 23) {
                    Bundle params = new Bundle();
                    params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
                    mMediaCodec.setParameters(params);
                }
            }
            if (isPushing)
                mEasyPusher.push(h264, 0, bufferInfo.size, System.currentTimeMillis(), 1);

            if (mEasyMuxer !=null && !isRecordPause && !isWaitKeyFrame) {
                bufferInfo.presentationTimeUs = TimeStamp.getInstance().getCurrentTimeUS();
                mEasyMuxer.pumpStream(outputBuffer, bufferInfo, true);// 用于保存本地视频到本地
            }
        }

        mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
        outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
    }

} else {
    Log.e(TAG, "No buffer available !");
}
2016-11-15 17:33:47 sz66cm 阅读数 604
  • 知识总结

    通过FFmpeg实现万能播放器(直播美颜,拉流) 学会MediaCodec硬解码实现 抖音 视频编辑 Rtmp协议实现哔哩哔哩直播推流 OpenGl动手制作灵魂出窍,手写美颜特效

    27092课时 0分钟 3人学习 齐行超
    免费试看

Demo项目地址:

https://github.com/sz66cm/CodecCamera.git

遇到问题

在海信终端android 4.4.2:可长期运行,预览,与编解码界面都正常.

在180终端以及186终端,都是android 4.4.4:开始一段时间正常,随后,预览界面正常,编解码界面停滞不动

在此过程中收集到的错误信息有如下
1. Stream not supported [dec->codec->decode]
2. ASYNC:error while processing buffers:OMX_ErrorNotImplemented.
3. Codec reported an error (omx error 0x80001006, internalError -2147483648)
4. MediaCodec dequeueoutputbuffer illegalstateexception (网上找到例子,部分机型没有配置SPS,PPS会报这个错误)

解决停滞不动的问题:

在编码发送数据时,遇到I帧,就在I帧前添加SPS,PPS.解决问题.

存在的问题,如果需要把编码后的文件写到本地,请注意

注意保存h264过程中,取数据的数组并不一定占满,所以要记住每一个帧的长度,写到本地的时候fileOutputStream.write(byte[] buffer, int offset, int frameLength);来保证传输过程中没有多余的0x00字节数据导致浪费带宽,甚至出现花屏.

Demo项目

这里写图片描述

编解码端花屏解决

原因:取出的H264数据的时候,长度取多了代码如下

    /**
     * 同步编码方法
     * @param dst
     * @param src
     * @return
     */
    public int syncEncode(byte[] src) {
        int len = -1;
        long startTime = SystemClock.elapsedRealtime();
        //喂数据
        int ii = encoder.dequeueInputBuffer(ENCODE_TIME_OUT);
        if(ii >= 0) {
            ByteBuffer inBuffer = encoder.getInputBuffers()[ii];
            inBuffer.clear();
            inBuffer.put(src, 0, src.length);
            encoder.queueInputBuffer(ii, 0, src.length, presentationTimeUs += 3600, MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
        }
        //取数据
        BufferInfo info = new BufferInfo();
        int oi = encoder.dequeueOutputBuffer(info, ENCODE_TIME_OUT);
        while (oi == -2) {
            oi = encoder.dequeueOutputBuffer(info, ENCODE_TIME_OUT);
        }
        if (oi >= 0) {
            //取数据
            ByteBuffer outBuffer = encoder.getOutputBuffers()[oi];
//这个长度是outBuffer的最大容量长度,并非H264数据实际长度.
//H264数据实际长度应该从MediaCodec.BufferInfo对象的成员变量size去取.
//如果使用了outBuffer.capacity()的长度,出现编解码端花屏
//并且十分的卡顿,打印发现H264的实际长度比outBuffer.capacity()小很多
//这个得十分注意,如下错误代码已注释
            len = outBuffer.capacity();
//          byte[] dst = new byte[len];
//          outBuffer.get(dst, 0, len);
            byte[] dst = new byte[info.size];
            outBuffer.get(dst);
            Log.i(TAG, "syncEncode() cost time = " + (SystemClock.elapsedRealtime() - startTime) + " ms" 
                    + " info.size = " + info.size + " outBuffer.capacity() = " + len);
            //传输数据
            prepareOffer(dst);
            //释放数据
            encoder.releaseOutputBuffer(oi, false);
        }
        return len;
    }
2018-07-09 16:53:49 tantion 阅读数 535
  • 知识总结

    通过FFmpeg实现万能播放器(直播美颜,拉流) 学会MediaCodec硬解码实现 抖音 视频编辑 Rtmp协议实现哔哩哔哩直播推流 OpenGl动手制作灵魂出窍,手写美颜特效

    27092课时 0分钟 3人学习 齐行超
    免费试看

使用异步读取编码(解码)后的数据,效率会大增。

可以直接起一个线程不断地读。

 

---------------------------------------------------------------------------------------------------------------

https://blog.csdn.net/u013028621/article/details/62417181

 

0、本文概述

MediaCodec是anroid api 16以后开发的硬编解码接口,英文文档参照这个链接,中文翻译可以参考这个链接。本文主要记录的是如何使用MediaCodec对视频进行编解码,最后会以实例的方式展示如何将Camera预览数据编码成H264,再把编码后的h264解码并且显示在SurfaceView中。本例不涉及音频的编解码。

1、MediaCodec编码视频

使用MediaCodec实现视频编码的步骤如下: 
1.初始化MediaCodec,方法有两种,分别是通过名称和类型来创建,对应的方法为:

MediaCodec createByCodecName (String name);
MediaCodec createDecoderByType (String type);
  • 具体可用的name和type参考文档即可。这里我们通过后者来初始化一个视频编码器。
mMC = MediaCodec.createDecoderByType(MIME_TYPE);
  •  

2.配置MediaCodec,这一步需要配置的是MediaFormat,这个类包含了比特率、帧率、关键帧间隔时间等,其中比特率如果太低就会造成类似马赛克的现象。

mMF = MediaFormat.createVideoFormat(MIME_TYPE, width, height);
mMF.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);  
mMF.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);  
if (mPrimeColorFormat != 0){
    mMF.setInteger(MediaFormat.KEY_COLOR_FORMAT, mPrimeColorFormat);  
}
mMF.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); //关键帧间隔时间 单位s
mMC.configure(mMF, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
  •  

其中mPrimeColorFormat为本机支持的颜色空间。一般是yuv420p或者yuv420sp,Camera预览格式一般是yv12或者NV21,所以在编码之前需要进行格式转换,实例可参照文末代码。代码是最好的老师嘛。 
3.打开编码器,获取输入输出缓冲区

mMC.start();
mInputBuffers = mMC.getInputBuffers();
mOutputBuffers = mMC.getOutputBuffers();
  •  

4.输入数据,过程可以分为以下几个小步: 
1)获取可使用缓冲区位置得到索引

int inputbufferindex = mMC.dequeueInputBuffer(BUFFER_TIMEOUT);
  •  

如果存在可用的缓冲区,此方法会返回其位置索引,否则返回-1,参数为超时时间,单位是毫秒,如果此参数是0,则立即返回,如果参数小于0,则无限等待直到有可使用的缓冲区,如果参数大于0,则等待时间为传入的毫秒值。 
2)传入原始数据

ByteBuffer inputBuffer = mInputBuffers[inputbufferindex];
inputBuffer.clear();//清除原来的内容以接收新的内容
inputBuffer.put(bytes, 0, len);//len是传进来的有效数据长度
mMC.queueInputBuffer(inputbufferindex, 0, len, timestamp, 0);
  •  

此缓冲区一旦使用,只有在dequeueInputBuffer返回其索引位置才代表它可以再次使用。 
5.获取其输出数据,获取输入原始数据和获取输出数据最好是异步进行,因为输入一帧数据不代表编码器马上就会输出对应的编码数据,可能输入好几帧才会输出一帧。获取输出数据的步骤与输入数据的步骤相似: 
1)获取可用的输出缓冲区

int outputbufferindex = mMC.dequeueOutputBuffer(mBI, BUFFER_TIMEOUT);
  •  

其中参数一是一个BufferInfo类型的实例,参数二为超时时间,负数代表无限等待(可见,不要在主线程进行操作)。 
2)获取输出数据

mOutputBuffers[outputbufferindex].get(bytes, 0, mBI.size);
  •  

3)释放缓冲区

mMC.releaseOutputBuffer(outputbufferindex, false);
  •  

2、MediaCodec解码视频

解码视频的步骤跟编码的类似,配置不一样: 
1.实例化解码器

mMC = MediaCodec.createDecoderByType(MIME_TYPE);
  •  

2.配置解码器,此处需要配置用于显示图像的Surface、MediaFormat包含视频的pps和sps(包含在编码出来的第一帧数据)

int[] width = new int[1];
int[] height = new int[1];  
AvcUtils.parseSPS(sps, width, height);//从sps中解析出视频宽高
mMF = MediaFormat.createVideoFormat(MIME_TYPE, width[0], height[0]);
mMF.setByteBuffer("csd-0", ByteBuffer.wrap(sps));
mMF.setByteBuffer("csd-1", ByteBuffer.wrap(pps));
mMF.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, width[0] * height[0]);
mMC.configure(mMF, surface, null, 0);
  •  

3.开启编码器并获取输入输出缓冲区

mMC.start();
mInputBuffers = mMC.getInputBuffers();
mOutputBuffers = mMC.getOutputBuffers();
  •  

4.输入数据 
1)获取可用的输入缓冲区

int inputbufferindex = mMC.dequeueInputBuffer(BUFFER_TIMEOUT);
  •  

返回值为可用缓冲区的索引

ByteBuffer inputBuffer = mInputBuffers[inputbufferindex];
inputBuffer.clear();
  •  

2)然后输入数据

inputBuffer.put(bytes, 0, len);
mMC.queueInputBuffer(inputbufferindex, 0, len, timestamp, 0);
  •  

5.获取输出数据,这一步与4同样应该异步进行,其具体步骤与上面解码的基本相同,在释放缓冲区的时候需要注意第二个参数设置为true,表示解码显示在Surface上

mMC.releaseOutputBuffer(outputbufferindex, true);
  •  

3、编解码实例

下面是一个MediaCodec编解码实例,此例子Camera预览数据(yv12)编码成H264,再把编码后的h264解码并且显示在SurfaceView中。

3.1布局文件

布局文件非常简单,两个SurfaceView分别用于显示编解码的图像,两个按钮控制开始和停止,一个TextView用于显示捕捉帧率。布局文件代码就不展示了,界面如下 

3.2编码器类Encoder

package com.example.mediacodecpro;

import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.util.Log;

import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * Created by chuibai on 2017/3/10.<br />
 */

public class Encoder {

    public static final int TRY_AGAIN_LATER = -1;
    public static final int BUFFER_OK = 0;
    public static final int BUFFER_TOO_SMALL = 1;
    public static final int OUTPUT_UPDATE = 2;

    private int format = 0;
    private final String MIME_TYPE = "video/avc";
    private MediaCodec mMC = null;
    private MediaFormat mMF;
    private ByteBuffer[] inputBuffers;
    private ByteBuffer[] outputBuffers;
    private long BUFFER_TIMEOUT = 0;
    private MediaCodec.BufferInfo mBI;

    /**
     * 初始化编码器
     * @throws IOException 创建编码器失败会抛出异常
     */
    public void init() throws IOException {
        mMC = MediaCodec.createEncoderByType(MIME_TYPE);
        format = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar;
        mBI = new MediaCodec.BufferInfo();
    }

    /**
     * 配置编码器,需要配置颜色、帧率、比特率以及视频宽高
     * @param width 视频的宽
     * @param height 视频的高
     * @param bitrate 视频比特率
     * @param framerate 视频帧率
     */
    public void configure(int width,int height,int bitrate,int framerate){
        if(mMF == null){
            mMF = MediaFormat.createVideoFormat(MIME_TYPE, width, height);
            mMF.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
            mMF.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
            if (format != 0){
                mMF.setInteger(MediaFormat.KEY_COLOR_FORMAT, format);
            }
            mMF.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, -1); //关键帧间隔时间 单位s
        }
        mMC.configure(mMF,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
    }

    /**
     * 开启编码器,获取输入输出缓冲区
     */
    public void start(){
        mMC.start();
        inputBuffers = mMC.getInputBuffers();
        outputBuffers = mMC.getOutputBuffers();
    }

    /**
     * 向编码器输入数据,此处要求输入YUV420P的数据
     * @param data YUV数据
     * @param len 数据长度
     * @param timestamp 时间戳
     * @return
     */
    public int input(byte[] data,int len,long timestamp){
        int index = mMC.dequeueInputBuffer(BUFFER_TIMEOUT);
        Log.e("...","" + index);
        if(index >= 0){
            ByteBuffer inputBuffer = inputBuffers[index];
            inputBuffer.clear();
            if(inputBuffer.capacity() < len){
                mMC.queueInputBuffer(index, 0, 0, timestamp, 0);
                return BUFFER_TOO_SMALL;
            }
            inputBuffer.put(data,0,len);
            mMC.queueInputBuffer(index,0,len,timestamp,0);
        }else{
            return index;
        }
        return BUFFER_OK;
    }

    /**
     * 输出编码后的数据
     * @param data 数据
     * @param len 有效数据长度
     * @param ts 时间戳
     * @return
     */
    public int output(/*out*/byte[] data,/* out */int[] len,/* out */long[] ts){
        int i = mMC.dequeueOutputBuffer(mBI, BUFFER_TIMEOUT);
        if(i >= 0){
            if(mBI.size > data.length) return BUFFER_TOO_SMALL;
            outputBuffers[i].position(mBI.offset);
            outputBuffers[i].limit(mBI.offset + mBI.size);
            outputBuffers[i].get(data, 0, mBI.size);
            len[0] = mBI.size ;
            ts[0] = mBI.presentationTimeUs;
            mMC.releaseOutputBuffer(i, false);
        } else if (i == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
            outputBuffers = mMC.getOutputBuffers();
            return OUTPUT_UPDATE;
        } else if (i == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            mMF = mMC.getOutputFormat();
            return OUTPUT_UPDATE;
        } else if (i == MediaCodec.INFO_TRY_AGAIN_LATER) {
            return TRY_AGAIN_LATER;
        }

        return BUFFER_OK;
    }

    public void release(){
        mMC.stop();
        mMC.release();
        mMC = null;
        outputBuffers = null;
        inputBuffers = null;
    }

    public void flush() {
        mMC.flush();
    }
}
  •  

3.3解码器类Decoder

package com.example.mediacodecpro;

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.view.Surface;
import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * Created by chuibai on 2017/3/10.<br />
 */

public class Decoder {

    public static final int TRY_AGAIN_LATER = -1;
    public static final int BUFFER_OK = 0;
    public static final int BUFFER_TOO_SMALL = 1;
    public static final int OUTPUT_UPDATE = 2;

    private final String MIME_TYPE = "video/avc";
    private MediaCodec mMC = null;
    private MediaFormat mMF;
    private long BUFFER_TIMEOUT = 0;
    private MediaCodec.BufferInfo mBI;
    private ByteBuffer[] mInputBuffers;
    private ByteBuffer[] mOutputBuffers;

    /**
     * 初始化编码器
     * @throws IOException 创建编码器失败会抛出异常
     */
    public void init() throws IOException {
        mMC = MediaCodec.createDecoderByType(MIME_TYPE);
        mBI = new MediaCodec.BufferInfo();
    }

    /**
     * 配置解码器
     * @param sps 用于配置的sps参数
     * @param pps 用于配置的pps参数
     * @param surface 用于解码显示的Surface
     */
    public void configure(byte[] sps, byte[] pps, Surface surface){
        int[] width = new int[1];
        int[] height = new int[1];
        AvcUtils.parseSPS(sps, width, height);//从sps中解析出视频宽高
        mMF = MediaFormat.createVideoFormat(MIME_TYPE, width[0], height[0]);
        mMF.setByteBuffer("csd-0", ByteBuffer.wrap(sps));
        mMF.setByteBuffer("csd-1", ByteBuffer.wrap(pps));
        mMF.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, width[0] * height[0]);
        mMC.configure(mMF, surface, null, 0);
    }

    /**
     * 开启解码器,获取输入输出缓冲区
     */
    public void start(){
        mMC.start();
        mInputBuffers = mMC.getInputBuffers();
        mOutputBuffers = mMC.getOutputBuffers();
    }

    /**
     * 输入数据
     * @param data 输入的数据
     * @param len 数据有效长度
     * @param timestamp 时间戳
     * @return 成功则返回{@link #BUFFER_OK} 否则返回{@link #TRY_AGAIN_LATER}
     */
    public int input(byte[] data,int len,long timestamp){
        int i = mMC.dequeueInputBuffer(BUFFER_TIMEOUT);
        if(i >= 0){
            ByteBuffer inputBuffer = mInputBuffers[i];
            inputBuffer.clear();
            inputBuffer.put(data, 0, len);
            mMC.queueInputBuffer(i, 0, len, timestamp, 0);
        }else {
            return TRY_AGAIN_LATER;
        }
        return BUFFER_OK;
    }

    public int output(byte[] data,int[] len,long[] ts){
        int i = mMC.dequeueOutputBuffer(mBI, BUFFER_TIMEOUT);
        if(i >= 0){
            if (mOutputBuffers[i] != null)
            {
                mOutputBuffers[i].position(mBI.offset);
                mOutputBuffers[i].limit(mBI.offset + mBI.size);

                if (data != null)
                    mOutputBuffers[i].get(data, 0, mBI.size);
                len[0] = mBI.size;
                ts[0] = mBI.presentationTimeUs;
            }
            mMC.releaseOutputBuffer(i, true);
        }else{
            return TRY_AGAIN_LATER;
        }
        return BUFFER_OK;
    }

    public void flush(){
        mMC.flush();
    }

    public void release() {
        flush();
        mMC.stop();
        mMC.release();
        mMC = null;
        mInputBuffers = null;
        mOutputBuffers = null;
    }
}
  • 3.4MainAcitivity
package com.example.mediacodecpro;

import android.content.pm.ActivityInfo;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.IOException;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import butterknife.BindView;
import butterknife.ButterKnife;

public class MainActivity extends AppCompatActivity implements View.OnClickListener, Camera.PreviewCallback {

    @BindView(R.id.surfaceView_encode)
    SurfaceView surfaceViewEncode;
    @BindView(R.id.surfaceView_decode)
    SurfaceView surfaceViewDecode;
    @BindView(R.id.btnStart)
    Button btnStart;
    @BindView(R.id.btnStop)
    Button btnStop;
    @BindView(R.id.capture)
    TextView capture;
    private int width;
    private int height;
    private int bitrate;
    private int framerate;
    private int captureFrame;
    private Camera mCamera;
    private Queue<PreviewBufferInfo> mPreviewBuffers_clean;
    private Queue<PreviewBufferInfo> mPreviewBuffers_dirty;
    private Queue<PreviewBufferInfo> mDecodeBuffers_clean;
    private Queue<PreviewBufferInfo> mDecodeBuffers_dirty;
    private int PREVIEW_POOL_CAPACITY = 5;
    private int format;
    private int DECODE_UNI_SIZE = 1024 * 1024;
    private byte[] mAvcBuf = new byte[1024 * 1024];
    private final int MSG_ENCODE = 0;
    private final int MSG_DECODE = 1;
    private String TAG = "MainActivity";
    private long mLastTestTick = 0;
    private Object mAvcEncLock;
    private Object mDecEncLock;
    private Decoder mDecoder;
    private Handler codecHandler;
    private byte[] mRawData;
    private Encoder mEncoder;
    private CodecThread codecThread;
    private DatagramSocket socket;
    private DatagramPacket packet;
    private byte[] sps_pps;
    private byte[] mPacketBuf = new byte[1024 * 1024];


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        //初始化参数
        initParams();

        //设置监听事件
        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);
    }

    /**
     * 初始化参数,包括帧率、颜色、比特率,视频宽高等
     */
    private void initParams() {
        width = 352;
        height = 288;
        bitrate = 1500000;
        framerate = 30;
        captureFrame = 0;
        format = ImageFormat.YV12;
        mAvcEncLock = new Object();
        mDecEncLock = new Object();
    }

    @Override
    protected void onResume() {
        if (getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        }
        super.onResume();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btnStart:
                mCamera = Camera.open(0);
                initQueues();
                initEncoder();
                initCodecThread();
                startPreview();
                break;
            case R.id.btnStop:
                releaseCodecThread();
                releseEncoderAndDecoder();
                releaseCamera();
                releaseQueue();
                break;
        }
    }

    /**
     * 释放队列资源
     */
    private void releaseQueue() {
        if (mPreviewBuffers_clean != null){
            mPreviewBuffers_clean.clear();
            mPreviewBuffers_clean = null;
        }
        if (mPreviewBuffers_dirty != null){
            mPreviewBuffers_dirty.clear();
            mPreviewBuffers_dirty = null;
        }
        if (mDecodeBuffers_clean != null){
            mDecodeBuffers_clean.clear();
            mDecodeBuffers_clean = null;
        }
        if (mDecodeBuffers_dirty != null){
            mDecodeBuffers_dirty.clear();
            mDecodeBuffers_dirty = null;
        }
    }

    /**
     * 释放摄像头资源
     */
    private void releaseCamera() {
        if(mCamera != null){
            mCamera.setPreviewCallbackWithBuffer(null);
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }

    private void releseEncoderAndDecoder() {
        if(mEncoder != null){
            mEncoder.flush();
            mEncoder.release();
            mEncoder = null;
        }
        if(mDecoder != null){
            mDecoder.release();
            mDecoder = null;
        }
    }

    private void releaseCodecThread() {
        codecHandler.getLooper().quit();
        codecHandler = null;
        codecThread = null;
    }

    private void initCodecThread() {
        codecThread = new CodecThread();
        codecThread.start();
    }

    /**
     * 开启预览
     */
    private void startPreview() {
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setPreviewFormat(format);
        parameters.setPreviewFrameRate(framerate);
        parameters.setPreviewSize(width,height);
        mCamera.setParameters(parameters);
        try {
            mCamera.setPreviewDisplay(surfaceViewEncode.getHolder());
        } catch (IOException e) {
            e.printStackTrace();
        }
        mCamera.setPreviewCallbackWithBuffer(this);
        mCamera.startPreview();
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        /** 预览的data为null */
        if(data == null) {
            Log.e(TAG,"预览的data为null");
            return;
        }

        long curTick = System.currentTimeMillis();
        if (mLastTestTick == 0) {
            mLastTestTick = curTick;
        }
        if (curTick > mLastTestTick + 1000) {
            setCaptureFPSTextView(captureFrame);
            captureFrame = 0;
            mLastTestTick = curTick;
        } else
            captureFrame++;
        synchronized(mAvcEncLock) {
            PreviewBufferInfo info = mPreviewBuffers_clean.poll();    //remove the head of queue
            info.buffer = data;
            info.size = getPreviewBufferSize(width, height, format);
            info.timestamp = System.currentTimeMillis();
            mPreviewBuffers_dirty.add(info);
            if(mDecoder == null){
                codecHandler.sendEmptyMessage(MSG_ENCODE);
            }
        }
    }

    private void setCaptureFPSTextView(int captureFrame) {
        capture.setText("当前帧率:" + captureFrame);
    }

    private void initEncoder() {
        mEncoder = new Encoder();
        try {
            mEncoder.init();
            mEncoder.configure(width,height,bitrate,framerate);
            mEncoder.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 初始化各种队列
     */
    private void initQueues() {
        if (mPreviewBuffers_clean == null)
            mPreviewBuffers_clean = new LinkedList<>();
        if (mPreviewBuffers_dirty == null)
            mPreviewBuffers_dirty = new LinkedList<>();
        int size = getPreviewBufferSize(width, height, format);
        for (int i = 0; i < PREVIEW_POOL_CAPACITY; i++) {
            byte[] mem = new byte[size];
            mCamera.addCallbackBuffer(mem);    //ByteBuffer.array is a reference, not a copy
            PreviewBufferInfo info = new PreviewBufferInfo();
            info.buffer = null;
            info.size = 0;
            info.timestamp = 0;
            mPreviewBuffers_clean.add(info);
        }
        if (mDecodeBuffers_clean == null)
            mDecodeBuffers_clean = new LinkedList<>();
        if (mDecodeBuffers_dirty == null)
            mDecodeBuffers_dirty = new LinkedList<>();
        for (int i = 0; i < PREVIEW_POOL_CAPACITY; i++) {
            PreviewBufferInfo info = new PreviewBufferInfo();
            info.buffer = new byte[DECODE_UNI_SIZE];
            info.size = 0;
            info.timestamp = 0;
            mDecodeBuffers_clean.add(info);
        }
    }

    /**
     * 获取预览buffer的大小
     * @param width 预览宽
     * @param height 预览高
     * @param format 预览颜色格式
     * @return 预览buffer的大小
     */
    private int getPreviewBufferSize(int width, int height, int format) {
        int size = 0;
        switch (format) {
            case ImageFormat.YV12: {
                int yStride = (int) Math.ceil(width / 16.0) * 16;
                int uvStride = (int) Math.ceil((yStride / 2) / 16.0) * 16;
                int ySize = yStride * height;
                int uvSize = uvStride * height / 2;
                size = ySize + uvSize * 2;
            }
            break;

            case ImageFormat.NV21: {
                float bytesPerPix = (float) ImageFormat.getBitsPerPixel(format) / 8;
                size = (int) (width * height * bytesPerPix);
            }
            break;
        }

        return size;
    }

    private void swapYV12toI420(byte[] yv12bytes, byte[] i420bytes, int width, int height) {
        System.arraycopy(yv12bytes, 0, i420bytes, 0, width * height);
        System.arraycopy(yv12bytes, width * height + width * height / 4, i420bytes, width * height, width * height / 4);
        System.arraycopy(yv12bytes, width * height, i420bytes, width * height + width * height / 4, width * height / 4);
    }

    private class PreviewBufferInfo {
        public byte[] buffer;
        public int size;
        public long timestamp;
    }

    private class CodecThread extends Thread {
        @Override
        public void run() {
            Looper.prepare();
            codecHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case MSG_ENCODE:
                            int res = Encoder.BUFFER_OK;
                            synchronized (mAvcEncLock) {
                                if (mPreviewBuffers_dirty != null && mPreviewBuffers_clean != null) {
                                    Iterator<PreviewBufferInfo> ite = mPreviewBuffers_dirty.iterator();
                                    while (ite.hasNext()) {
                                        PreviewBufferInfo info = ite.next();
                                        byte[] data = info.buffer;
                                        int data_size = info.size;
                                        if (format == ImageFormat.YV12) {
                                            if (mRawData == null || mRawData.length < data_size) {
                                                mRawData = new byte[data_size];
                                            }
                                            swapYV12toI420(data, mRawData, width, height);
                                        } else {
                                            Log.e(TAG, "preview size MUST be YV12, cur is " + format);
                                            mRawData = data;
                                        }
                                        res = mEncoder.input(mRawData, data_size, info.timestamp);
                                        if (res != Encoder.BUFFER_OK) {
//                                            Log.e(TAG, "mEncoder.input, maybe wrong:" + res);
                                            break;        //the rest buffers shouldn't go into encoder, if the previous one get problem
                                        } else {
                                            ite.remove();
                                            mPreviewBuffers_clean.add(info);
                                            if (mCamera != null) {
                                                mCamera.addCallbackBuffer(data);
                                            }
                                        }
                                    }
                                }
                            }


                            while (res == Encoder.BUFFER_OK) {
                                int[] len = new int[1];
                                long[] ts = new long[1];
                                synchronized (mAvcEncLock) {
                                    res = mEncoder.output(mAvcBuf, len, ts);
                                }

                                if (res == Encoder.BUFFER_OK) {
                                    //发送h264
                                    if(sps_pps != null){
                                        send(len[0]);
                                    }

                                    if (mDecodeBuffers_clean != null && mDecodeBuffers_dirty != null) {
                                        synchronized (mAvcEncLock) {
                                            Iterator<PreviewBufferInfo> ite = mDecodeBuffers_clean.iterator();
                                            if (ite.hasNext()) {
                                                PreviewBufferInfo bufferInfo = ite.next();
                                                if (bufferInfo.buffer.length >= len[0]) {
                                                    bufferInfo.timestamp = ts[0];
                                                    bufferInfo.size = len[0];
                                                    System.arraycopy(mAvcBuf, 0, bufferInfo.buffer, 0, len[0]);
                                                    ite.remove();
                                                    mDecodeBuffers_dirty.add(bufferInfo);
                                                } else {
                                                    Log.e(TAG, "decoder uni buffer too small, need " + len[0] + " but has " + bufferInfo.buffer.length);
                                                }
                                            }
                                        }
                                        initDecoder(len);
                                    }
                                }

                            }
                            codecHandler.sendEmptyMessageDelayed(MSG_ENCODE, 30);
                            break;

                        case MSG_DECODE:
                            synchronized (mDecEncLock) {
                                int result = Decoder.BUFFER_OK;

                                //STEP 1: handle input buffer
                                if (mDecodeBuffers_dirty != null && mDecodeBuffers_clean != null) {
                                    Iterator<PreviewBufferInfo> ite = mDecodeBuffers_dirty.iterator();
                                    while (ite.hasNext()) {
                                        PreviewBufferInfo info = ite.next();
                                        result = mDecoder.input(info.buffer, info.size, info.timestamp);
                                        if (result != Decoder.BUFFER_OK) {
                                            break;        //the rest buffers shouldn't go into encoder, if the previous one get problem
                                        } else {
                                            ite.remove();
                                            mDecodeBuffers_clean.add(info);
                                        }
                                    }
                                }

                                int[] len = new int[1];
                                long[] ts = new long[1];
                                while (result == Decoder.BUFFER_OK) {
                                    result = mDecoder.output(null, len, ts);
                                }
                            }
                            codecHandler.sendEmptyMessageDelayed(MSG_DECODE, 30);
                            break;
                    }
                }
            };
            Looper.loop();
        }
    }

    private void send(int len) {
        try {
            if(socket == null) socket = new DatagramSocket();
            if(packet == null){
                packet = new DatagramPacket(mPacketBuf,0,sps_pps.length + len);
                packet.setAddress(InetAddress.getByName("192.168.43.1"));
                packet.setPort(5006);
            }
            if(mAvcBuf[4] == 0x65){
                System.arraycopy(sps_pps,0,mPacketBuf,0,sps_pps.length);
                System.arraycopy(mAvcBuf,0,mPacketBuf,sps_pps.length,len);
                len += sps_pps.length;
            }else{
                System.arraycopy(mAvcBuf,0,mPacketBuf,0,len);
            }
            packet.setLength(len);
            socket.send(packet);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void initDecoder(int[] len) {
        if(sps_pps == null){
            sps_pps = new byte[len[0]];
            System.arraycopy(mAvcBuf,0,sps_pps,0,len[0]);
        }
        if(mDecoder == null){
            mDecoder = new Decoder();
            try {
                mDecoder.init();
            } catch (IOException e) {
                e.printStackTrace();
            }
            byte[] sps_nal = null;
            int sps_len = 0;
            byte[] pps_nal = null;
            int pps_len = 0;
            ByteBuffer byteb = ByteBuffer.wrap(mAvcBuf, 0, len[0]);
            //SPS
            if (true == AvcUtils.goToPrefix(byteb)) {
                int sps_position = 0;
                int pps_position = 0;
                int nal_type = AvcUtils.getNalType(byteb);
                if (AvcUtils.NAL_TYPE_SPS == nal_type) {
                    Log.d(TAG, "OutputAvcBuffer, AVC NAL type: SPS");
                    sps_position = byteb.position() - AvcUtils.START_PREFIX_LENGTH - AvcUtils.NAL_UNIT_HEADER_LENGTH;
                    //PPS
                    if (true == AvcUtils.goToPrefix(byteb)) {
                        nal_type = AvcUtils.getNalType(byteb);
                        if (AvcUtils.NAL_TYPE_PPS == nal_type) {
                            pps_position = byteb.position() - AvcUtils.START_PREFIX_LENGTH - AvcUtils.NAL_UNIT_HEADER_LENGTH;
                            sps_len = pps_position - sps_position;
                            sps_nal = new byte[sps_len];
                            int cur_pos = byteb.position();
                            byteb.position(sps_position);
                            byteb.get(sps_nal, 0, sps_len);
                            byteb.position(cur_pos);
                            //slice
                            if (true == AvcUtils.goToPrefix(byteb)) {
                                nal_type = AvcUtils.getNalType(byteb);
                                int pps_end_position = byteb.position() - AvcUtils.START_PREFIX_LENGTH - AvcUtils.NAL_UNIT_HEADER_LENGTH;
                                pps_len = pps_end_position - pps_position;
                            } else {
                                pps_len = byteb.position() - pps_position;
                                //pps_len = byteb.limit() - pps_position + 1;
                            }
                            if (pps_len > 0) {
                                pps_nal = new byte[pps_len];
                                cur_pos = byteb.position();
                                byteb.position(pps_position);
                                byteb.get(pps_nal, 0, pps_len);
                                byteb.position(cur_pos);
                            }
                        } else {
                            //Log.d(log_tag, "OutputAvcBuffer, AVC NAL type: "+nal_type);
                            throw new UnsupportedOperationException("SPS is not followed by PPS, nal type :" + nal_type);
                        }
                    }
                } else {
                    //Log.d(log_tag, "OutputAvcBuffer, AVC NAL type: "+nal_type);
                }

                //2. configure AVC decoder with SPS/PPS
                if (sps_nal != null && pps_nal != null) {

                    int[] width = new int[1];
                    int[] height = new int[1];
                    AvcUtils.parseSPS(sps_nal, width, height);
                    mDecoder.configure(sps_nal, pps_nal,surfaceViewDecode.getHolder().getSurface());
                    mDecoder.start();
                    if (codecHandler != null) {
                        codecHandler.sendEmptyMessage(MSG_DECODE);
                    }
                }

            }
        }
    }

}
  •  

上面的send方法可以把手机捕捉并编码的视频数据发送到电脑上使用ffplay播放,使用ffplay的时候记得加上参数-analyzeduration 200000减小视频延迟。

4.要点总结

个人总结使用MediaCodec编解码的时候主要需要注意以下事项: 
- 数据的输入和输出要异步进行,不要采用阻塞的方式等待输出数据 
- 编码Camera预览数据的时候使用带buffer的预览回调,避免帧率过低 
- 适当使用缓存队列,不要每一帧都new一个byte数组,避免频繁的GC 
- 不要在主线程进行操作,在我学习的过程中看到有些朋友直接在预览回调里进行编解码,在dequeueOutputBuffer方法还传递-1,也就是无限等待程序返回 
上述实例地址:点击这里,由于我一直没有积分去下载东西,所以下载收1个积分。如果有任何问题,欢迎指正,相互学习