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