Android视频编解码之MediaCodec简单入门 - Go语言中文社区

Android视频编解码之MediaCodec简单入门


本篇只是简单入门,后面会继续写文章详细讲解:

由于MediaCodec涉及内容众多,原本想一篇文章把所有内容概括,但是后来发现不太可能,限于自己能力,想要考虑全面太难,我也是刚开始学习需要借助网上的代码进行一步步学习,音视频方面的很多知识也不完善,所以会把MediaCodec分成多个部分进行介绍,谢谢。
Developer:
https://developer.android.google.cn/reference/android/media/MediaCodec

编解码到底处理什么,前面通过编解码基础 音视频编解码基础已经回答了这个问题,编码相当于压缩数据。 Camera用法中onPreviewFrame可以获取Camera中的原始数据,类似NV21或者YV12等类型数据,编码就是可以把这些原始YUV格式数据编码为.h264或者.h265等类型数据,然后利用MediaMuxer把音频和视频数据最终合并成mp4文件等视频类型。解码就是把.h264的数据解码为YUV等原始格式数据。后面还会提到利用Surface作为原始数据源。
"PS:”把原始数据生成h264文件可以利用VLCMediaPlayer进行播放。

1 MediaCodec简介

硬编码和软编码:

硬编码:
用设备GPU去实现编解码,这样可以减轻CPU的压力。
软编码:
让CPU来进行编解码,在c层代码来进行编解码,因为c/c++有很多好的编解码库。

软硬编码对比:
硬编的好处主要在于速度快,而且系统自带不需要引入外部的库,但是特性支持有限,而且硬编的压缩率一般偏低,而对于软编码来说,虽然速度较慢,但是压缩率比较高,而且支持的H264特性也会比硬编码多很多,相对来说比较可控。硬编码会受硬件设备支持的影响。

在Android 4.1之前没有提供硬编解码的API,所以基本都是采用开源的那些库,比如著名的FFMpeg实现软编解码。但是通常情况下,同一平台同一硬件环境,硬编码的速度快于软件编码,软编码使用CPU来进行计算,会消耗一些app的运算效率。在Android4.1出来了一个新的API:MediaCodec可以支持硬编解码,MediaCodec可以支持对音频和视频的编解码.

MediaCodec从api16开始提供,它能够访问更底层的多媒体编解码器组件。MediaCodec是更底层的apis,是Android底层多媒体支持基础架构的一部分(通常与MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, 以及AudioTrack一起使用)。

2 MediaCodec的工作模型

在这里插入图片描述
编解码器工作过程为处理输入数据产生输出数据,MediaCodec采用异步方式处理数据,并且使用了一组输入输出缓存(input and output buffers)。

  • 首先请求或接收到一个空的输入缓存(input buffer),向其中填充满数据并将它传递给编解码器处理。
  • 编解码器处理完这些数据并将处理结果输出至一个空的输出缓存(output buffer)中。
  • 请求或接收到一个填充了结果数据的输出缓存(output buffer),使用完其中的数据,并将其释放给编解码器再次使用。

3 数据类型(Data Types)

编解码器可以处理三种类型的数据:

  • 压缩数据(即为经过H254. H265. 等编码的视频数据或AAC等编码的音频数据)、
  • 原始音频数据、
  • 原始视频数据。
    三种类型的数据均可以利用ByteBuffers进行处理,但是对于原始视频数据应提供一个Surface以提高编解码器的性能。Surface直接使用本地视频数据缓存(native video buffers),而没有映射或复制数据到ByteBuffers,因此,这种方式会更加高效。也就是说可以如果需要输入的是原始数据可以利用Surface作为源,这样效率更高。

压缩缓存(Compressed Buffers)

上面说了编解码器可以处理三种类型的数据,第一种就是压缩数据,压缩数据可以作为解码器的输入数据或者编码器的输出数据。压缩数据需要指定数据格式,这样编码/解码器才能知道如何处理这些压缩数据。
对于视频一般是包含完整的一帧数据,也就是要告诉解码器从哪到哪是一帧完整的数据或者从编码器得到一帧完整的数据。常用的就是让MediaCodec解码H264数据,必须将分割符和NALU单元作为一个完整的数据帧传给解码器才能正确解码。
对于音频数据通常是单个可访问单元(一个编码的音频片段,通常包含几毫秒的遵循特定格式类型的音频数据),但这种要求也不是十分严格,一个缓存内可能包含多个可访问的音频单元。在这两种情况下,缓存不会在任意的字节边界上开始或结束,而是在帧或可访问单元的边界上开始或结束。

一般都不要传递给MediaCodec不是完整帧的数据,除非是标记了BUFFER_FLAG_PARTIAL_FRAME的数据。`BUFFER_FLAG_PARTIAL_FRAME指示了缓冲区只包含帧的一部分,并且解码器应该对数据进行批处理,直到没有该标志的缓冲区在解码帧之前出现。但是这个标记是API26之后引入的,一般用不到。

原始音频缓存数据(Raw Audio Buffers)

原始的音频数据缓存包含完整的PCM(脉冲编码调制)音频数据帧,这是每一个通道按照通道顺序的一个样本。每一个样本是一个按照本机字节顺序的16位带符号整数(16-bit signed integer in native byte order)。
一段示例代码来获取采样数据:

short[] getSamplesForChannel(MediaCodec codec, int bufferId, int channelIx) {
   ByteBuffer outputBuffer = codec.getOutputBuffer(bufferId);
   MediaFormat format = codec.getOutputFormat(bufferId);
   ShortBuffer samples = outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer();
   int numChannels = formet.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
   if (channelIx < 0 || channelIx >= numChannels) {
     return null;
   }
   short[] res = new short[samples.remaining() / numChannels];
   for (int i = 0; i < res.length; ++i) {
     res[i] = samples.get(i * numChannels + channelIx);
   }
   return res;
 }

原始视频缓存(Raw Video Buffers)

在ByteBuffer模式下,视频缓存(video buffers)根据它们的颜色格式(color format)进行展现。你可以通过调用getCodecInfo().getCapabilitiesForType(…).colorFormats方法获得编解码器支持的颜色格式数组。视频编解码器可以支持三种类型的颜色格式:

本地原始视频格式(native raw video format):这种格式通过COLOR_FormatSurface标记,并可以与输入或输出Surface一起使用。
灵活的YUV缓存(flexible YUV buffers)(例如:COLOR_FormatYUV420Flexible):利用一个输入或输出Surface,或在在ByteBuffer模式下,可以通过调用getInput/OutputImage(int)方法使用这些格式。
其他,特定的格式(other, specific formats):通常只在ByteBuffer模式下被支持。有些颜色格式是特定供应商指定的。其他的一些被定义在 MediaCodecInfo.CodecCapabilities中。这些颜色格式同 flexible format相似,你仍然可以使用 getInput/OutputImage(int)方法。
  从Android 5.1(LOLLIPOP_MR1)开始,所有的视频编解码器都支持灵活的YUV4:2:0缓存(flexible YUV 4:2:0 buffers)。

4 MediaCodec的状态转换

在这里插入图片描述
在编解码器的生命周期内有三种理论状态:停止态-Stopped、执行态-Executing、释放态-Released,停止状态(Stopped)包括了三种子状态:未初始化(Uninitialized)、配置(Configured)、错误(Error)。执行状态(Executing)在概念上会经历三种子状态:刷新(Flushed)、运行(Running)、流结束(End-of-Stream)。

利用代码创建了一个编解码器,此时编解码器处于未初始化状态(Uninitialized)。首先,需要使用configure(…)方法对编解码器进行配置,这将使编解码器转为配置状态(Configured)。然后调用start()方法使其转入执行状态(Executing)。在这种状态下可以通过上述的缓存队列操作处理数据。
执行状态(Executing)包含三个子状态: 刷新(Flushed)、运行( Running) 以及流结束(End-of-Stream)。在调用start()方法后编解码器立即进入刷新子状态(Flushed),此时编解码器会拥有所有的缓存。一旦第一个输入缓存(input buffer)被移出队列,编解码器就转入运行子状态(Running),编解码器的大部分生命周期会在此状态下度过。当你将一个带有end-of-stream 标记的输入缓存入队列时,编解码器将转入流结束子状态(End-of-Stream)。在这种状态下,编解码器不再接收新的输入缓存,但它仍然产生输出缓存(output buffers)直到end-of- stream标记到达输出端。你可以在执行状态(Executing)下的任何时候通过调用flush()方法使编解码器重新返回到刷新子状态(Flushed)。
通过调用stop()方法使编解码器返回到未初始化状态(Uninitialized),此时这个编解码器可以再次重新配置 。当你使用完编解码器后,你必须调用release()方法释放其资源。
在极少情况下编解码器会遇到错误并进入错误状态(Error)。这个错误可能是在队列操作时返回一个错误的值或者有时候产生了一个异常导致的。通过调用 reset()方法使编解码器再次可用。你可以在任何状态调用reset()方法使编解码器返回到未初始化状态(Uninitialized)。否则,调用 release()方法进入最终的Released状态。

5 创建编解码器

两种方式创建编解码器:一种利用媒体格式name,一种利用媒体格式类型。
可以根据指定的MediaFormat使用MediaCodecList创建一个MediaCodec实例。在解码文件或数据流时,你可以通过调用MediaExtractor.getTrackFormat方法获得所期望的格式(media format),并调用MediaFormat.setFeatureEnabled方法注入任何你想要添加的特定属性,然后调用MediaCodecList.findDecoderForFormat方法获得可以处理指定的媒体格式的编解码器的名字,最后,通过调用createByCodecName(String)方法创建一个编解码器。

注意:在Android 5.0 (LOLLIPOP)上,传递给MediaCodecList.findDecoder/EncoderForFormat的格式不能包含帧率-frame rate。通过调用format.setString(MediaFormat.KEY_FRAME_RATE, null)方法清除任何存在于当前格式中的帧率。

也可以根据MIME类型利用createDecoder/EncoderByType(String)方法创建一个你期望的编解码器。然而,这种方式不能够给编解码器加入指定特性,而且创建的编解码器有可能不能处理你所期望的媒体格式。

创建安全的解码器(Creating secure decoders),所谓安全就是避免创建系统不支持的编解码器,所以Google推荐利用createByCodecName创建编解码器,但是一般我们为了简单可以利用type创建。

在Android 4.4(KITKAT_WATCH)及之前版本,安全的编解码器(secure codecs)没有被列在MediaCodecList中,但是仍然可以在系统中使用。安全编解码器只能够通过名字进行实例化,其名字是在常规编解码器的名字后附加.secure标识(所有安全编解码器的名字都必须以.secure结尾),调用createByCodecName(String)方法创建安全编解码器时,如果系统中不存在指定名字的编解码器就会抛出IOException异常。

从Android 5.0(LOLLIPOP)及之后版本,你可以在媒体格式中使用FEATURE_SecurePlayback属性来创建一个安全编解码器。

创建编解码器会用到MediaFormat,假如是本地文件或者网络流,可以用MediaExtractor.getTrackFormat这个方法来提取MediaFormat信息。如果是原始数据,就需要我们字节创建音频或者视频的MediaFormat。

创建代码示例:

上面提到创建方式有两种:

(1)根据名字创建:

MediaCodec createByCodecName (String name);

//步骤:
MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
mediaCodecList.findDecoderForFormat();
mediaCodecList.findEncoderForFormat();
//返回值为decoder 或者 encoder的名字
MediaCodec.createByCodecName();
MediaCodecList.REGULAR_CODECS:表示api21之前的
MediaCodecList.ALL_CODECS 表示所有的

从类型获取name

private static MediaCodecInfo selectCodec(String mimeType) {
     // 获取所有支持编解码器数量
     int numCodecs = MediaCodecList.getCodecCount();
     for (int i = 0; i < numCodecs; i++) {
        // 编解码器相关性信息存储在MediaCodecInfo中
         MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
         // 判断是否为编码器
         if (!codecInfo.isEncoder()) {
             continue;
         }
        // 获取编码器支持的MIME类型,并进行匹配
         String[] types = codecInfo.getSupportedTypes();
         for (int j = 0; j < types.length; j++) {
             if (types[j].equalsIgnoreCase(mimeType)) {
                 return codecInfo;
             }
         }
     }
     return null;
 }

(2)根据类型:

MediaCodec createXXXByType (String type);
MediaCodec.createDecoderByType();
MediaCodec.createEncoderByType()

MediaCodec支持的视频格式有vp8 、VP9 、H.264、H.265、MPEG4、H.263等。
MediaCodec支持的音频格式有3gpp、amr-wb、amr-wb、amr-wb、g711-A、g711-U 、AAC等。
类型:
“video/x-vnd.on2.vp8” - VP8 video (i.e. video in .webm)
“video/x-vnd.on2.vp9” - VP9 video (i.e. video in .webm)
“video/avc” - H.264/AVC video
“video/hevc” - H.265/HEVC video
“video/mp4v-es” - MPEG4 video
“video/3gpp” - H.263 video
“audio/3gpp” - AMR narrowband audio
“audio/amr-wb” - AMR wideband audio
“audio/amr-wb” - MPEG1/2 audio layer III
“audio/mp4a-latm” - AAC audio (note, this is raw AAC packets, not packaged in LATM!)
“audio/amr-wb” - vorbis audio
“audio/g711-alaw” - G.711 alaw audio
“audio/g711-mlaw” - G.711 ulaw audio
。。。。。。。

利用媒体类型创建编码器:

public AvcEncoder(int width, int height, int framerate, int bitrate) {
		m_width  = width;
		m_height = height;
		m_framerate = framerate;
		MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);//传入类型,和宽高
		mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);//设置颜色格式对应NV21
		mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);//帧传输速率
		mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);//设置fps,一般20 或者30
		mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);//关键帧的延时
		//mediaFormat.setInteger(MediaFormat.KEY_ROTATION,90);
		try {
			mediaCodec = MediaCodec.createEncoderByType("video/avc");
		} catch (IOException e) {
			e.printStackTrace();
		}
		mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
		mediaCodec.start();
	}

6 初始化

创建好编解码器后需要设置一系列的参数来初始化它,编解码器提供了处理数据的两种方式:同步和异步。假如是异步的方式的话,必须要在configure之前来为它设置回调函数,设置好回调函数后,就可以调用configure函数了。如果不利用回调函数的方式,则不需要设置。然后,使用指定的媒体格式配置编解码器。这时你可以为视频原始数据产生者(例如视频解码器)指定输出Surface。此时你也可以为secure 编解码器设置解密参数(详见MediaCrypto) 。最后,因为编解码器可以工作于多种模式,你必须指定是该编码器是作为一个解码器(decoder)还是编码器(encoder)运行。
看configure函数,上面提到了解密参数就对应第一个函数,这里不再展开讲。
void configure (MediaFormat format, Surface surface, MediaCrypto crypto, int flags)
void configure (MediaFormat format, Surface surface, int flags, MediaDescrambler descrambler)
在不解码生成outputbuffers的时候或者不想将生成的outputbuffers渲染到surface的时候,可以设置为null。flag有两种模式,CONFIGURE_FLAG_ENCODE(值等于1)为编码模式,其他的值为解码模式。

从API LOLLIPOP起,你可以在Configured 状态下查询输入和输出格式的结果。在开始编解码前你可以通过这个结果来验证配置的结果,例如,颜色格式。

假如想处理原始视频帧,需要将原始视频帧编码为类似于h264或者其他格式,需要调用createInputSurface()方法产生一个surface,并且必须在configure之后,这个surface上目前是空数据,然后调用start方法,当有数据注入到surface时,mediacodec就能立即获取到并解码。假如是api23之后,也可以使用Surface createPersistentInputSurface ()来创建一个surface,其他的编码器可以调用setInputSurface(Surface)方法来继续使用这个surface。

7 配置编解码器:

编解码器配置使用的是MediaCodec的configure方法,该方法首先对MediaFormat存储的数据map进行提取,然后调用本地方法native-configure实现对编解码器的配置工作。在配置时,configure方法需要传入format、surface、crypto、flags参数,其中format为MediaFormat的实例,它使用”key-value”键值对的形式存储多媒体数据格式信息;surface用于指明解码器的数据源来自于该surface;crypto用于指定一个MediaCrypto对象,以便对媒体数据进行安全解密;flags指明配置的是编码器(CONFIGURE_FLAG_ENCODE)。

// 创建MediaFormat
MediaFormat mFormat = MediaFormat.createVideoFormat("video/avc", 640 ,480);     
mFormat.setInteger(MediaFormat.KEY_BIT_RATE,600);       // 指定比特率
mFormat.setInteger(MediaFormat.KEY_FRAME_RATE,30);  // 指定帧率
mFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,mColorFormat); // 指定编码器颜色格式 ,这里的颜色格式在前面已经介绍 
mFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,10); // 指定关键帧时间间隔
mVideoEncodec.configure(mFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE); //最后一个参数指定是编码器还是解码器。

以上代码是在编码H.264时的配置方法,createVideoFormat(“video/avc”, 640 ,480)为”video/avc”类型(即H.264)编码器的MediaFormat对象,需要指定视频数据的宽高,如果编解码音频数据,则调用MediaFormat的createAudioFormat(String mime, int sampleRate,int channelCount)的方法。

当编解码器配置完毕后,就可以调用MediaCodec的start()方法,该方法会调用低层native_start()方法来启动编码器,并调用低层方法ByteBuffer[] getBuffers(input)来开辟一系列输入、输出缓存区。

如何指定颜色格式:
在ByteBuffer模式下,视频缓存(video buffers)根据它们的颜色格式(color format)进行展现。可以通过调用getCodecInfo().getCapabilitiesForType(…).colorFormats方法获得编解码器支持的颜色格式数组。
利用MediaFormat.KEY_COLOR_FORMAT配置颜色格式属性,该属性用于指明video编码器的颜色格式,具体选择哪种颜色格式与输入的视频数据源颜色格式有关。比如,Camera预览采集的图像流通常为NV21或YV12,那么编码器需要指定相应的颜色格式,否则编码得到的数据可能会出现花屏、叠影、颜色失真等现象。MediaCodecInfo.CodecCapabilities.存储了编码器所有支持的颜色格式。

视频编解码支持三种色彩格式:

  • native raw video format : COLOR_FormatSurface,可以用来处理surface模式的数据输入输出。
  • flexible YUV buffers : 例如COLOR_FormatYUV420Flexible,可以用来处理surface模式的输出输出,在使用ByteBuffer模式的时候可以用getInput/OutputImage(int)方法来获取image数据,都是以COLOR_FormatYUV420开头的。
  • specific formats: 支持ByteBuffer模式,有一些厂家会定制, 其他的在MediaCodecInfo.CodecCapabilities中可以看到,格式较多, 假如是flexible format, 同样可以使用Image来处理数据,getInput/OutputImage(int)。
    常见颜色格式映射如下:
    NV12(YUV420sp) ———> COLOR_FormatYUV420PackedSemiPlanar
    NV21 ———-> COLOR_FormatYUV420SemiPlanar
    YV12(I420) ———-> COLOR_FormatYUV420Planar

YUV420的颜色格式分类:
YUV420 数据在内存中的长度是 width * hight * 3 / 2
YUV420P:YV12、I420
YUV420SP:NV12、NV21
YUV420P 和 YUV420的区别 :
它们在存储格式上有区别?
YUV420P:yyyyyyyy uuuuuuuu vvvvv yuv420: yuv yuv yuv
YUV420P,Y,U,V三个分量都是平面格式,分为I420和YV12。I420格式和YV12格式的不同处在U平面和V平面的位置不同。在I420格式中,U平面紧跟在Y平面之后,然后才是V平面(即:YUV);但YV12则是相反(即:YVU)。
YUV420SP, Y分量平面格式,UV打包格式, 即NV12。 NV12与NV21类似,U 和 V 交错排列,不同在于UV顺序。
I420: YYYYYYYY UU VV =>YUV420P
YV12: YYYYYYYY VV UU =>YUV420P
NV12: YYYYYYYY UVUV =>YUV420SP
NV21: YYYYYYYY VUVU =>YUV420SP
 从Android 5.1(LOLLIPOP_MR1)开始,所有的视频编解码器都支持灵活的YUV4:2:0缓存(flexible YUV 4:2:0 buffers)。
 获取video/avc 对应的编码器的颜色格式

public void getMediaCodecList(){
        //获取所有的解码器
        int codecsNums = MediaCodecList.getCodecCount();
        MediaCodecInfo codecInfo = null;
        for(int i = 0; i < codecsNums && codecInfo == null ; i++){
            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
            if(info.isEncoder()){
                System.out.println("========这是一个编码器==========");
            }else{
                System.out.println("========这是一个解码器==========");
                continue;
            }
            String[] types = info.getSupportedTypes();
            boolean found = false;
            for(int j=0; j<types.length && !found; j++){
                if(types[j].equals("video/avc")){
                    found = true;
                }
            }
            if(!found){
                continue;
            }
            codecInfo = info;
        }
        Log.d(TAG, codecInfo.getName() + "对应" +" video/avc");

        //检查所支持的颜色格式
        int colorFormat = 0;
        MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType("video/avc");
        System.out.println("=============capabilities.colorFormats.length================="+capabilities.colorFormats.length);
        for(int i = 0; i < capabilities.colorFormats.length ; i++){
            int format = capabilities.colorFormats[i];
            System.out.println("============formatformat===================="+format);
            switch (format) {
                case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
                    System.out.println("=========COLOR_FormatYUV420Planar");
                    continue;
                case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
                    System.out.println("========COLOR_FormatYUV420PackedPlanar");
                    continue;
                case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
                    System.out.println("=======COLOR_FormatYUV420SemiPlanar");
                    continue;
                case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
                    System.out.println("=======COLOR_FormatYUV420PackedSemiPlanar");
                    continue;
                case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
                    colorFormat = format;
                    System.out.println("=======COLOR_TI_FormatYUV420PackedSemiPlanar");
                    continue;
                default:
                    continue;
            }
        }

result:
=capabilities.colorFormats.length=4
formatformat
2135033992
formatformat
==19
=COLOR_FormatYUV420Planar
formatformat
21
=COLOR_FormatYUV420SemiPlanar
formatformat
2130708361

关于颜色格式这部分其实没那么重要,只需要记住:
NV12(YUV420sp) ———> COLOR_FormatYUV420PackedSemiPlanar
NV21 ———-> COLOR_FormatYUV420SemiPlanar
YV12(I420) ———-> COLOR_FormatYUV420Planar

8 指定特殊数据

有些格式,特别是ACC音频和MPEG4、H.264和H.265视频格式要求实际数据以若干个包含配置数据或编解码器指定数据的缓存为前缀。当处理这种压缩格式的数据时,这些数据必须在调用start()方法后且在处理任何帧数据之前提交给编解码器。这些数据必须在调用queueInputBuffer方法时使用BUFFER_FLAG_CODEC_CONFIG进行标记。
  前缀信息通常包含在数据中,但是需要自己提取出来,在mediacodec执行start之后提交这些数据,比如h264的sps和pps,在queueinputbuffer的时候flag设置为BUFFER_FLAG_CODEC_CONFIG提交给解码器。
  Codec-specific数据也可以被包含在传递给configure方法的格式信息(MediaFormat)中,在ByteBuffer条目中以"csd-0", “csd-1"等key标记。这些keys一直包含在通过MediaExtractor获得的Audio Track or Video Track的MediaFormat中。一旦调用start()方法,MediaFormat中的Codec-specific数据会自动提交给编解码器;你不能显示的提交这些数据。如果MediaFormat中不包含编解码器指定的数据,你可以根据格式要求,按照正确的顺序使用指定数目的缓存来提交codec-specific数据。在H264 AVC编码格式下,你也可以连接所有的codec-specific数据并作为一个单独的codec-config buffer提交。
mediaFormat.setByteBuffer(“csd-0”, ByteBuffer.wrap(sps));//sps是一个包含sps信息的byte数组
mediaFormat.setByteBuffer(“csd-1”, ByteBuffer.wrap(pps));//pps是一个包含pps信息的byte数组
  
  Android 使用下列的codec-specific data buffers。对于适当的MediaMuxer轨道配置,这些也要在轨道格式中进行设置。每一个参数集以及被标记为(*)的codec-specific-data段必须以”x00x00x00x01"字符开头。
在这里插入图片描述
 注意:当编解码器被立即刷新或start之后不久刷新,并且在任何输出buffer或输出格式变化被返回前需要特别地小心,因为编解码器的codec specific data可能会在flush过程中丢失。为保证编解码器的正常运行,你必须在刷新后使用标记为BUFFER_FLAG_CODEC_CONFIG 的buffers再次提交这些数据。

编码器(或者产生压缩数据的编解码器)将会在有效的输出缓存之前产生和返回编解码器指定的数据,这些数据会以codec-config flag进行标记。包含codec-specific-data的Buffers没有有意义的时间戳。
   许多解码器要求实际压缩数据流以“codec specific data”为先导,也就是用于初始化codec的设置数据,例如AVC视频情况时的PPS/SPS,或vorbis音频情况时的code tables。MediaExtractor类提供codec specific data作为返回的track format的一部分,在命名为csd-0,csd-1的条目中。

通过指定BUFFER_FLAG_CODEC_CONFIG,这些buffers可以在start()或flush()后直接提交。然而,如果你使用包含这些keys的MediaFormat配置codec,他们将在start后自动地提交。因此,不鼓励使用BUFFER_FLAG_CODEC_CONFIG,仅推荐高级用户使用。

一般不需要自己生成设置“codec specific data”,只需要记录H264得编码首帧,内部存有SPS和PPS信息,需要保留起来,然后,加在每个H264关键帧的前面。

Thread EncoderThread = new Thread(new Runnable() {
			@SuppressLint("NewApi")
			@Override
			public void run() {
				isRuning = true;
				byte[] input = null;
				long pts =  0;
				long generateIndex = 0;

				while (isRuning) {
					if (YUVQueue.size() >0){
						input = YUVQueue.poll();
						byte []  tempinput = rotateYUV420Degree90(input, m_height, m_width);
						byte[] yuv420sp = new byte[m_width*m_height*3/2];
						NV21ToNV12(tempinput,yuv420sp,m_width,m_height);
						input = yuv420sp;
					}
					if (input != null) {
						try {
							long startMs = System.currentTimeMillis();
							ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
							ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
							int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
							if (inputBufferIndex >= 0) {
								pts = computePresentationTime(generateIndex);
								ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
								inputBuffer.clear();
								inputBuffer.put(input);
								mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, pts, 0);
								generateIndex += 1;
							}

							/*H264编码首帧,内部存有SPS和PPS信息,需要保留起来,然后,加在每个H264关键帧的前面。
							* 其中有个字段是flags,它有几种常量情况。
								flags = 4;End of Stream。
								flags = 2;首帧信息帧。
								flags = 1;关键帧。
								flags = 0;普通帧。*/
							MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
							int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_TIME);

							while (outputBufferIndex >= 0) {
								ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
								byte[] outData = new byte[bufferInfo.size];
								outputBuffer.get(outData);
								if(bufferInfo.flags == 2){//首帧,记录信息
									configbyte = new byte[bufferInfo.size];
									configbyte = outData;
								}else if(bufferInfo.flags == 1){
									byte[
                            
                            版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/u010126792/article/details/86580878
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢