MediaCodec 硬编码

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MediaCodec 硬编码相关的知识,希望对你有一定的参考价值。

参考技术A MediaCodec编码视屏主要分三步:

1:打开相机,接收相机拿到的数据

2:初始化MediaCodec,将基本配置配给meidiacodec

3:开始编解码,建编解码好的数据进行保存(保存成文件/推送到网络)

本节主要介绍下第二步和第三步

打开相机拿到数据,目前android 提供的相机API主要有 Camera1 Camera2和CameraX,本Demo主要使用使用简单的Camera1取数据,主要逻辑即初始化camera和开始进入预览录制状态,如下:

经过上面mediacodec编码之后,保存的文件就可以用来保存成文件了,此时距离保存成MP4/AVI等其他格式的文件只剩下一步了,但是暂时先不管,我们先单纯的分析下H264 码流。首先使用16进制阅读器查看H264文件,如下

简单介绍下,H264 裸流由一个接一个NALU单元组成,一个接一个的NALU单元由startcode分割,startcode一般是:

0x000001(3Byte)或者0x00000001(4Byte),如果NALU对应的Slice为一帧的开始就用0x00000001,否则就用0x000001,如上图所示的startCode就是0x00 00 00 01,一般进行解码时就是从MP4或者其他视频封装格式中分解出来NALU单元,在从单元中分解出来各个信息进行解读,最后解析出来一帧一帧的数据,其中关键配置信息包括SPS PPS

I帧等,均由 startcode后面前2Byte的数据保存,如00 00 00 01第一个之后是67,67的二进制是 0 11 00111,对应包含的信息即:

知道裸流之后就知道 为了视频从哪一帧开始播放都可以播放增加了 一个操作就是在每一个I帧编码前就编码一组SPS和PPS,这样保证编码器至少隔一段时间差就可以读取到有效配置信息进行配置

Android 用MediaCodec实现视频硬解码

   

原文地址:http://blog.csdn.net/halleyzhang3/article/details/11473961


本文向你讲述如何用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, nullnull, 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, 00);  
  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, null0);  
  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, MediaCodec编码结合FFmpeg封装流

    Android 用MediaCodec实现视频硬解码

    Android 用MediaCodec实现视频硬解码

    研究Android音视频-3-在Android设备上采集音视频并使用MediaCodec编码为H.264

    研究Android音视频-3-在Android设备上采集音视频并使用MediaCodec编码为H.264

    研究Android音视频-3-在Android设备上采集音视频并使用MediaCodec编码为H.264