频道栏目
首页 > 资讯 > Android > 正文

Android MediaCodec编码解码实例解析

17-05-03        来源:[db:作者]  
收藏   我要投稿

MediaCodec概述

MediaCodec是一个Android原生的编解码器。
简单的来说,MediaCodec可以把摄像头采集的数据流编码为H264格式,这个过程是压缩过程。也可以把H264格式解码在surface类的控件上显示。

我们先来看一下Android系统中解码器的命名,软解码器通常是以OMX.google开头的。硬解码器通常是以OMX.[hardware_vendor]开头的,比如TI的解码器是以OMX.TI开头的。当然还有一些不遵守这个命名规范的,不以OMX.开头的,那也会被认为是软解码器。

判断规则见frameworks/av/media/libstagefright/OMXCodec.cpp:

static bool IsSoftwareCodec(const char *componentName) {
    if (!strncmp("OMX.google.", componentName, 11)) {
        return true;
    }
    if (!strncmp("OMX.", componentName, 4)) {
        return false;
    }
    return true;
}

其实MediaCodec调用的是在系统中注册的解码器,系统中存在的解码器可以很多,但能够被应用使用的解码器是根据配置来的,即/system/etc/media_codecc.xml。这个文件一般由硬件或者系统的生产厂家在build整个系统的时候提供,一般是保存在代码的device/[company]/[codename]目录下的,例如device/samsung/tuna/media_codecs.xml。这个文件配置了系统中有哪些可用的codec以及,这些codec对应的媒体文件类型。在这个文件里面,系统里面提供的软硬codec都需要被列出来。

也就是说,如果系统里面实际上包含了某个codec,但是并没有被配置在这个文件里,那么应用程序也无法使用到。

在这个配置文件里面,如果出现多个codec对应同样类型的媒体格式的时候,这些codec都会被保留起来。当系统使用的时候,将会选择第一个匹配的codec。除非是指明了要软解码还是硬解码,但是Android的framework层为上层提供服务的AwesomePlayer中在处理音频和视频的时候,对到底是选择软解还是硬解的参数没有设置。所以虽然底层是支持选择的,但是对于上层使用MediaPlayer的Java程序来说,还是只能接受默认的codec选取规则。

但是Android提供的命令行程序/system/bin/stagefright在播放音频文件的时候,倒是可以根据参数来选择到底使用软解码还是硬解码,但是该工具只支持播放音频,不支持播放视频。

一般来说,如果系统里面有对应的媒体硬件解码器的话,系统开发人员应该是会配置在media_codecs.xml中,所以大多数情况下,如果有硬件解码器,那么我们总是会使用到硬件解码器。极少数情况下,硬件解码器存在,但不配置,我猜只可能是这个硬解码器还有bug,暂时还不适合发布,所以不用使用。

下面我们以摄像头采集数据通过MediaCodec编码为H264存入内存卡,在从内存卡读出数据显示为例子说明MediaCodec:

开始采集数据


1.编码过程:

初始化编码器:

private static final String MIME_TYPE = "video/avc"; // H.264的mime类型
MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);//选择系统用于编码H264的编码器信息,固定的调用
mColorFormat = selectColorFormat(codecInfo, MIME_TYPE);//根据MIME格式,选择颜色格式,固定的调用
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE,
        this.mWidth, this.mHeight);//根据MIME创建MediaFormat,固定
//以下参数的设置,尽量固定.当然,如果你非常了解,也可以自行修改
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);//设置比特率
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);//设置帧率
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat);//设置颜色格式
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);//设置关键帧的时间
try {
    mMediaCodec = MediaCodec.createByCodecName(codecInfo.getName());//这里就是根据上面拿到的编码器创建一个MediaCodec了;//MediaCodec还有一个方法可以直接用MIME类型,创建
} catch (IOException e) {
    e.printStackTrace();
}

//第二个参数用于播放MP4文件,显示图像的Surface;
//第四个参数,编码H264的时候,固定CONFIGURE_FLAG_ENCODE, 播放的时候传入0即可;API文档有解释
mMediaCodec.configure(mediaFormat, null, null,
        MediaCodec.CONFIGURE_FLAG_ENCODE);//关键方法
mMediaCodec.start();//必须

把摄像头采集的数据传给编码器编码,把返回的H264数据写进文件。进入文件管理器查看已经写入

@Override
    public void onPreviewFrame(byte[] data, Camera camera) 
    {
        long newTime = System.currentTimeMillis();
        long diff = newTime - lastTime;
        lastTime = newTime;
        //把摄像头的数据传给编码器
        int ret = avcCodec.offerEncoder(data,h264);

        if(ret > 0)
        {
            try {       
                byte[] length_bytes = intToBytes(ret);
                file.write(length_bytes);
                file.write(h264, 0, ret);

            } catch (IOException e)
            {
                Log.d("ws", "exception: " + e.toString());
            }
        }

    }

对于有些机器上报错,修改 MediaFormat.KEY_COLOR_FORMAT值即可,有以下参考

 /*
    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
        case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
        case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
        case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
        case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:*/

2.解码
初始化解码器,读取之前文件的数据,把数据传给surface类的控件上显示

MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, width);
            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 2500000);
            mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 20);
            try {//初始化解码器
                decoder = MediaCodec.createDecoderByType("video/avc");
            } catch (IOException e) {
                Log.d("Fuck", "Fail to create MediaCodec: " + e.toString());
            }
            //将解码出来的数据传给 surface进行显示
            decoder.configure(mediaFormat, surface, null, 0);
            //decoder.configure(mediaFormat, null, null, 0);
            decoder.start();

            // new BufferInfo();

            ByteBuffer[] inputBuffers = decoder.getInputBuffers();
            ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
            if (null == inputBuffers) {
                Log.d("Fuck", "null == inputBuffers");
            }
            if (null == outputBuffers) {
                Log.d("Fuck", "null == outbputBuffers 111");
            }

            FileInputStream file = null;
            try {
                file = new FileInputStream(fileString);
            } catch (FileNotFoundException e) {
                Log.d("Fuck", "open file error: " + e.toString());
                return;
            }
            int read_size = -1;
            int mCount = 0;

            for (;;) {
                byte[] h264 = null;
                try {
                    byte[] length_bytes = new byte[4];
                    read_size = file.read(length_bytes);
                    if (read_size < 0) {
                        Log.d("Fuck", "read_size<0 pos1");
                        break;
                    }
                    int byteCount = bytesToInt(length_bytes, 0);
                    Log.d("Fuck", "byteCount: " + byteCount);

                    h264 = new byte[byteCount];
                    read_size = file.read(h264, 0, byteCount);
                    // Log.d("Fuck", "read_size: " + read_size);
                    if (read_size < 0) {
                        Log.d("Fuck", "read_size<0 pos2");
                        break;
                    }
                    // Log.d("Fuck", "pos: " + file.)
                } catch (IOException e) {
                    Log.d("Fuck", "read_size 2: " + read_size);
                    Log.d("Fuck", "e.toStrinig(): " + e.toString());
                    break;
                }

                int inputBufferIndex = decoder.dequeueInputBuffer(-1);
                if (inputBufferIndex >= 0) {
                    ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                    inputBuffer.clear();
                    inputBuffer.put(h264);
                    // long sample_time = ;
                    decoder.queueInputBuffer(inputBufferIndex, 0, h264.length, mCount * 1000000 / 20, 0);
                    ++mCount;
                } else {
                    Log.d("Fuck", "dequeueInputBuffer error");
                }

                ByteBuffer outputBuffer = null;
                MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                int outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 0);
                while (outputBufferIndex >= 0) {
                    outputBuffer = outputBuffers[outputBufferIndex];
                    decoder.releaseOutputBuffer(outputBufferIndex, true);
                    outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 0);
                }

...

demo下载:https://github.com/WangShuo1143368701/VideoView/tree/master/mediacodecdemo

相关TAG标签
上一篇:安卓自定义View进阶-事件分发机制详解
下一篇:Android:手把手带你分析 Protocol Buffer使用 源码
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站