Android 用MediaCodec实现视频硬解码

标签: android mediacodec 视频 | 发表时间:2014-10-25 23:19 | 作者:abc123456789cba
出处:http://www.iteye.com
http://blog.csdn.net/halleyzhang3/article/details/11473961

http://www.360doc.com/content/14/0119/10/8122810_346350456.shtml

 

Android 用MediaCodec实现视频硬解码

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

1、从摄像头采集视频

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

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

 

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

 

 

[java]  view plain copy
 
 
 
  1. buf = new byte[camWidth * camHeight * 3 / 2];  
  2. cam.addCallbackBuffer(buf);  
  3. cam.setPreviewCallbackWithBuffer(this);           
  4. cam.startPreview();   

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

 

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

 

[java]  view plain copy
 
 
 
  1. public void onPreviewFrame(byte[] data, Camera camera) {  
  2.     if (frameListener != null) {  
  3.         frameListener.onFrame(data, 0, data.length, 0);  
  4.     }  
  5.     cam.addCallbackBuffer(buf);  
  6. }  
2、编码

 

    首先要初始化编码器:

 

[java]  view plain copy
 
 
 
  1.       mediaCodec = MediaCodec.createEncoderByType("Video/AVC");  
  2. MediaFormat mediaFormat = MediaFormat.createVideoFormat(type, width, height);  
  3. mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);  
  4. mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);  
  5. mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);  
  6. mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);  
  7. mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);  
  8. mediaCodec.start();  

    然后就是给他喂数据了,这里的数据是来自摄像头的:

 

 

[java]  view plain copy
 
 
 
  1. public void onFrame(byte[] buf, int offset, int length, int flag) {  
  2.    ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();  
  3.    ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();  
  4.    int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);  
  5.    if (inputBufferIndex >= 0)  
  6.        ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];  
  7.        inputBuffer.clear();  
  8.        inputBuffer.put(buf, offset, length);  
  9.        mediaCodec.queueInputBuffer(inputBufferIndex, 0, length, 0, 0);  
  10.    }  
  11.    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();  
  12.    int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0);  
  13.    while (outputBufferIndex >= 0) {  
  14.        ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];  
  15.        if (frameListener != null)  
  16.            frameListener.onFrame(outputBuffer, 0, length, flag);  
  17.        mediaCodec.releaseOutputBuffer(outputBufferIndex, false);  
  18.        outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);  
  19.    }   
先把来自摄像头的数据喂给它,然后从它里面取压缩好的数据喂给解码器。

3、解码和显示

     首先初始化解码器:

 

[java]  view plain copy
 
 
 
  1. mediaCodec = MediaCodec.createDecoderByType("Video/AVC");  
  2. MediaFormat mediaFormat = MediaFormat.createVideoFormat(mime, width, height);  
  3. mediaCodec.configure(mediaFormat, surface, null, 0);  
  4. mediaCodec.start();  

 

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

     然后就是处理数据了:

 

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

 

 

好了,到现在,基本上就可以了。如果你运气够好,现在就能看到视频了,比如在我的三星手机上这样就可以了。但是,我试过几个其他平台,多数都不可以,总是有各种各样的问题,如果要开发一个不依赖平台的应用,还有很多的问题要解决。说说我遇到的一些情况:

 

1、视频尺寸

     一般都能支持176X144/352X288这种尺寸,但是大一些的,640X480就有很多机子不行了,至于为什么,我也不知道。当然,这个尺寸必须和摄像头预览的尺寸一致,预览的尺寸可以枚举一下。

2、颜色空间

    根据ANdroid SDK文档,确保所有硬件平台都支持的颜色,在摄像头预览输出是YUV12,在编码器输入是COLOR_FormatYUV420Planar,也就是前面代码中设置的那样。       不过,文档终究是文档,否则安卓就不是安卓。

    在有的平台上,这两个颜色格式是一样的,摄像头的输出可以直接作为编码器的输入。也有的平台,两个是不一样的,前者就是YUV12,后者等于I420,需要把前者的UV分量颠倒一下。下面的代码效率不高,可供参考。

 

[java]  view plain copy
 
 
 
  1. byte[] i420bytes = null;  
  2. private byte[] swapYV12toI420(byte[] yv12bytes, int width, int height) {  
  3.     if (i420bytes == null)  
  4.         i420bytes = new byte[yv12bytes.length];  
  5.     for (int i = 0; i < width*height; i++)  
  6.         i420bytes[i] = yv12bytes[i];  
  7.     for (int i = width*height; i < width*height + (width/2*height/2); i++)  
  8.         i420bytes[i] = yv12bytes[i + (width/2*height/2)];  
  9.     for (int i = width*height + (width/2*height/2); i < width*height + 2*(width/2*height/2); i++)  
  10.         i420bytes[i] = yv12bytes[i - (width/2*height/2)];  
  11.     return i420bytes;  
  12. }  
      这里的困难是,我不知道怎样去判断是否需要这个转换。据说,Android 4.3不用再从摄像头的PreView里面取图像,避开了这个问题。这里有个例子,虽然我没读,但看起来挺厉害的样子,应该不会有错吧(觉厉应然)。 http://bigflake.com/mediacodec/CameraToMpegTest.java.txt

 

 

3、输入输出缓冲区的格式

    SDK里并没有规定格式,但是,这种情况H264的格式基本上就是附录B。但是,也有比较有特色的,它就是不带那个StartCode,就是那个0x000001,搞得把他编码器编出来的东西送给他的解码器,他自己都解不出来。还好,我们可以自己加。

 

[java]  view plain copy
 
 
 
  1. ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];  
  2. byte[] outData = new byte[bufferInfo.size + 3];  
  3.        outputBuffer.get(outData, 3, bufferInfo.size);  
  4. if (frameListener != null) {  
  5.     if ((outData[3]==0 && outData[4]==0 && outData[5]==1)  
  6.     || (outData[3]==0 && outData[4]==0 && outData[5]==0 && outData[6]==1))  
  7.     {  
  8.         frameListener.onFrame(outData, 3, outData.length-3, bufferInfo.flags);  
  9.     }  
  10.     else  
  11.     {  
  12.      outData[0] = 0;  
  13.      outData[1] = 0;  
  14.      outData[2] = 1;  
  15.         frameListener.onFrame(outData, 0, outData.length, bufferInfo.flags);  
  16.     }  
  17. }  

4、有时候会死在dequeueInputBuffer(-1)上面

根据SDK文档,dequeueInputBuffer 的参数表示等待的时间(毫秒),-1表示一直等,0表示不等。按常理传-1就行,但实际上在很多机子上会挂掉,没办法,还是传0吧,丢帧总比挂掉好。当然也可以传一个具体的毫秒数,不过没什么大意思
 
http://blog.csdn.net/mirkerson/article/details/37694719


已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [android mediacodec 视频] 推荐:

Android 用MediaCodec实现视频硬解码

- - 移动开发 - ITeye博客
Android 用MediaCodec实现视频硬解码. 本文向你讲述如何用android标准的API (MediaCodec)实现视频的硬件编解码. 例程将从摄像头采集视频开始,然后进行H264编码,再解码,然后显示. 我将尽量讲得简短而清晰,不展示那些不相关的代码. 但是,我不建议你读这篇文章,也不建议你开发这类应用,而应该转而开发一些戳鱼、打鸟、其乐融融的程序.

Android视频录制

- - CSDN博客移动开发推荐文章
/*下面设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到用户面前*/. this.surfaceView.getHolder().setFixedSize(320, 240);//设置分辨率. mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); //从照相机采集视频.

Android 开发视频推荐

- - 大猫の意淫筆記
作为 Android 死忠粉,总是想自己来两手,无奈没摸过后台开发,面向的唯一对象就是马总. 花了个把星期入个门,发现还是挺好玩的. 市面上的 Android 入门书有2个主要问题,一个是都要求有 Java 基础,另一个是跟不上 Android 版本更新.所以并不推荐买书学习. 斯坦福大学公开课:编程方法学.

Google+ Android 应用已支持 NFC [视频]

- 太平犬 - 谷安——谷奥Android专题站
Google 喜欢将自己不同的技术和服务搞到一起,取长补短彼此促进,看起来 Google+ 也不例外. 通过带有 NFC 的手机,Google+ 应用可以读取 NFC 标签从而快速分享内容. 当然直到 NFC 普及之前估计难火热起来. Google 已经宣传 NFC(near-field communications/近场通信)有一段时间,他们还推出了基于 NFC 的 Google Wallet(Google 钱包)服务.

MegaPad,23 英寸的 Android 平板?[视频]

- SotongDJ - 谷安——谷奥Android专题站
你可能已经有了 10 英寸平板,不过你想要个更大的屏幕么. Android 改装发烧友 Martin Drashkov 就有这样的想法,因为它构建了一个 23 英寸的触屏 Android 设备. 他将这个设备称为 MegaPad,而且它只花了 600 美刀左右就炮制出来了. Martin 认为更大的屏幕可以让“两个人同时使用”而且“也有同时开启两个不同需求的应用的可能性”.

Android 4.0 首个使用视频曝光

- 猪头小队长 - 爱范儿 · Beats of Bits
这年头各个公司的曝光策略似乎都不一样,苹果那边是几年如一日的保护壳曝光、酒吧丢手机;Google 似乎更偏爱 Ebay 一点,有人从 Ebay 上买到了一台 Nexus S,然后他惊奇地发现,系统居然是即将发布的 Android 4.0 冰淇淋三明治. 他很快拍摄了一段视频,并发给 Engadget.

下一代Android运行视频曝光

- Adam - cnBeta.COM
不用像在酒吧里面等待遗失的新iPhone那样麻烦,代号冰淇淋三明治的下一代Android操作系统已经亮相,近日在eBay上有网友在出售一款Nexus S,特别之处就在于该机运行有最新的下一代Android操作系统.

Android 4.0 真实视频泄露

- 阳勇 - 36氪
Eric Schmidt在Dreamforce大会上曾表示,Android的下一代操作系统Ice Cream Sandwich将在10月或11月发布,今天有一位用户在eBay上买了一部Nexus S,他惊讶的发现,他的手机系统有一点不一样,打开关于菜单,他发现这部手机运行的竟然是Ice Cream Sandwich.

Android示例大全教学视频

- Sense - ITeye论坛最新讨论
Android示例大全教学视频. 共30集,更新信息请关注http://www.innovation-office.net/android-api-video.htm. 以下是56视频网的地址,分集观看. 作者: zhouxing . 声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任.

Android 4.0 SDK 移植到 Nexus One [视频]

- Will - 谷安——谷奥Android专题站
Youtube 上一名叫做 drl337md 的用户放出了一个 Nexus One 上运行 Android 4.0 的演示视频. 虽然 Nexus One 相比现在很多高端手机即将成为过去,但是似乎这个运行结果看起来不坏. 也许 Google 会优化后给 Nexus One 也带来 Ice Cream Sandwich.