Android 用MediaCodec实现视频硬解码

本文向你讲述如何用android标准的API (MediaCodec)实现视频的硬件编解码。例程将从摄像头采集视频开始,然后进行H264编码,再解码,然后显示。我将尽量讲得简短而清晰,不展示那些不相关的代码。但是,我不建议你读这篇文章,也不建议你开发这类应用,而应该转而开发一些戳鱼、打鸟、其乐融融的程序。好吧,下面的内容是写给那些执迷不悟的人的,看完之后也许你会同意我的说法:Android只是一个玩具,很难指望它来做靠谱的应用。

1、从摄像头采集视频

可以通过摄像头Preview的回调,来获取视频数据。

首先创建摄像头,并设置参数:

cam = Camera.open();cam.setPreviewDisplay(holder);Camera.Parameters parameters = cam.getParameters();parameters.setFlashMode("off"); // 无闪光灯parameters.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);parameters.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);parameters.setPreviewFormat(ImageFormat.YV12);parameters.setPictureSize(camWidth, camHeight);parameters.setPreviewSize(camWidth, camHeight);//这两个属性 如果这两个属性设置的和真实手机的不一样时,就会报错cam.setParameters(parameters);宽度和高度必须是摄像头支持的尺寸,否则会报错。要获得所有支持的尺寸,可用getSupportedPreviewSizes,这里不再累述。据说所有的参数必须设全,漏掉一个就可能报错,不过只是据说,我只设了几个属性也没出错。 然后就开始Preview了:buf = new byte[camWidth * camHeight * 3 / 2];cam.addCallbackBuffer(buf);cam.setPreviewCallbackWithBuffer(this);cam.startPreview();

setPreviewCallbackWithBuffer是很有必要的,不然每次回调系统都重新分配缓冲区,效率会很低。

在onPreviewFrame中就可以获得原始的图片了(当然,this 肯定要implements PreviewCallback了)。这里我们是把它传给编码器:

public void onPreviewFrame(byte[] data, Camera camera) {if (frameListener != null) {frameListener.onFrame(data, 0, data.length, 0);}cam.addCallbackBuffer(buf);}2、编码

首先要初始化编码器:

mediaCodec = MediaCodec.createEncoderByType("Video/AVC");MediaFormat mediaFormat = MediaFormat.createVideoFormat(type, width, height);mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);mediaCodec.start(); 然后就是给他喂数据了,这里的数据是来自摄像头的: public void onFrame(byte[] buf, int offset, int length, int flag) {ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);if (inputBufferIndex >= 0)ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];inputBuffer.clear();inputBuffer.put(buf, offset, length);mediaCodec.queueInputBuffer(inputBufferIndex, 0, length, 0, 0);}MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0);while (outputBufferIndex >= 0) {ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];if (frameListener != null)frameListener.onFrame(outputBuffer, 0, length, flag);mediaCodec.releaseOutputBuffer(outputBufferIndex, false);outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);} 先把来自摄像头的数据喂给它,然后从它里面取压缩好的数据喂给解码器。

3、解码和显示

首先初始化解码器:

mediaCodec = MediaCodec.createDecoderByType("Video/AVC");MediaFormat mediaFormat = MediaFormat.createVideoFormat(mime, width, height);mediaCodec.configure(mediaFormat, surface, null, 0);mediaCodec.start();

这里通过给解码器一个surface,解码器就能直接显示画面。

然后就是处理数据了:

public void onFrame(byte[] buf, int offset, int length, int flag) {ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);if (inputBufferIndex >= 0) {ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];inputBuffer.clear();inputBuffer.put(buf, offset, length);mediaCodec.queueInputBuffer(inputBufferIndex, 0, length, mCount * 1000000 / FRAME_RATE, 0);mCount++;}MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0);while (outputBufferIndex >= 0) {mediaCodec.releaseOutputBuffer(outputBufferIndex, true);outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);}} queueInputBuffer第三个参数是时间戳,其实怎么写都无所谓,只要是按时间线性增加的就可以,这里就随便弄一个了。后面一段的代码就是把缓冲区给释放掉,因为我们直接让解码器显示,,就不需要解码出来的数据了,但是必须要这么释放一下,否则解码器始终给你留着,内存就该不够用了。

代替你主持夕阳的葬礼。

Android 用MediaCodec实现视频硬解码

相关文章:

你感兴趣的文章:

标签云: