Android 用MediaCodec实现视频硬解码
Posted YongHui_Luo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 用MediaCodec实现视频硬解码相关的知识,希望对你有一定的参考价值。
原文地址:http://blog.csdn.net/halleyzhang3/article/details/11473961
本文向你讲述如何用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);
[java] view plain copy
- buf = new byte[camWidth * camHeight * 3 / 2];
- cam.addCallbackBuffer(buf);
- cam.setPreviewCallbackWithBuffer(this);
- cam.startPreview();
setPreviewCallbackWithBuffer是很有必要的,不然每次回调系统都重新分配缓冲区,效率会很低。
在onPreviewFrame中就可以获得原始的图片了(当然,this 肯定要 implements PreviewCallback了)。这里我们是把它传给编码器:
[java] view plain copy
- public void onPreviewFrame(byte[] data, Camera camera)
- if (frameListener != null)
- frameListener.onFrame(data, 0, data.length, 0);
- cam.addCallbackBuffer(buf);
首先要初始化编码器:
[java] view plain copy
- 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();
然后就是给他喂数据了,这里的数据是来自摄像头的:
[java] view plain copy
- 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、解码和显示
首先初始化解码器:
[java] view plain copy
- mediaCodec = MediaCodec.createDecoderByType("Video/AVC");
- MediaFormat mediaFormat = MediaFormat.createVideoFormat(mime, width, height);
- mediaCodec.configure(mediaFormat, surface, null, 0);
- mediaCodec.start();
这里通过给解码器一个surface,解码器就能直接显示画面。
然后就是处理数据了:
[java] view plain copy
- 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);
好了,到现在,基本上就可以了。如果你运气够好,现在就能看到视频了,比如在我的三星手机上这样就可以了。但是,我试过几个其他平台,多数都不可以,总是有各种各样的问题,如果要开发一个不依赖平台的应用,还有很多的问题要解决。说说我遇到的一些情况:
1、视频尺寸
一般都能支持176X144/352X288这种尺寸,但是大一些的,640X480就有很多机子不行了,至于为什么,我也不知道。当然,这个尺寸必须和摄像头预览的尺寸一致,预览的尺寸可以枚举一下。
2、颜色空间
根据ANdroid SDK文档,确保所有硬件平台都支持的颜色,在摄像头预览输出是YUV12,在编码器输入是COLOR_FormatYUV420Planar,也就是前面代码中设置的那样。 不过,文档终究是文档,否则安卓就不是安卓。
在有的平台上,这两个颜色格式是一样的,摄像头的输出可以直接作为编码器的输入。也有的平台,两个是不一样的,前者就是YUV12,后者等于I420,需要把前者的UV分量颠倒一下。下面的代码效率不高,可供参考。
[java] view plain copy
- byte[] i420bytes = null;
- private byte[] swapYV12toI420(byte[] yv12bytes, int width, Android 用MediaCodec实现视频硬解码