Android OpenGL ES 十二.MediaCodec录制视频之MediaCodec(转载整理)

实现录制音视频也有两种方案,分别是MediaRecorder和MediaCodec

什么是MediaRecorder

MediaRecorder是安卓提供的一个用于音视频采集的类

MediaRecorder的优缺点

优点
可以实现直接录制视频 使用方便,得到就是编码和封装好的音视频文件,可以直接使用
缺点
无法获取原始数据,不能对每一帧数据进行处理,无法支持我们程序中自己需要的一些逻辑,比方需要录制灰度视频。

由于不满足我的需求,所以这里就不再对MediaRecorder讲解了,那接下来我们来说说MediaCodec


什么是MediaCodec

MediaCodec
类是Android平台提供的用于访问低层多媒体硬件编/解码器接口,它是Android低层多媒体架构的一部分,通常与MediaExtractor、MediaMuxer、AudioTrack结合使用,能够编解码诸如H.264、H.265、AAC、3gp等常见的音视频格式。一般来说H.264的AVC视频编码和AAC的音频编码是最常见的。

MediaCodec工作原理

MediaCodec的工作原理就是处理输入数据以产生输出数据。具体来说,MediaCodec在编解码的过程中使用了一组输入/输出缓存区来同步或异步处理数据:首先,客户端向获取到的编解码器输入缓存区写入要编解码的数据并将其提交给编解码器,待编解码器处理完毕后将其转存到编码器的输出缓存区,同时收回客户端对输入缓存区的所有权;然后,客户端从获取到编解码输出缓存区读取编码好的数据进行处理,待处理完毕后编解码器收回客户端对输出缓存区的所有权。不断重复整个过程,直至编码器停止工作或者异常退出。

MediaCodec生命周期中的状态

mediacodec分为三种状态,Stopped, ExecutingReleased。一张图表示(这张图是从网上直接下载下来使用的):

image

Stopped状态包含三个子状态:Uninitialized, ConfiguredError,Executing同样包含三个状态:Flushed, RunningEnd-of-Stream

在mediacodec的使用过程中必须遵守图里标出的流程,否则会发生错误。
比方,没有调用start()方法,就开始sotp()会报错。
以解码器为例,讲解一下使用流程。当使用工厂方法创建mediacodec并且指定为解码后,进入Uninitialized状态,调用configure方法后,进入Configured状态,然后调用start方法进入Executing状态。

进入Executing状态后,首先到达Flush状态,此时mediacodec会持有所有的数据,当第一个inputbufffer从队列中取出时,立即进入Running状态,这个时间很短。然后就可以调用dequeueInputBuffer和getInputBuffer来获取用户可用的缓冲区,用户填满数据后调用queueinputbuffer方法返回给解码器,解码器大部分时间都会工作在Running状态。当想inputbufferqueue中输入一帧标记EndOfStream的时候,进入End-of-Stream状态,在这种状态下,解码器不再接受任何新的数据输入,缓冲区中的数据和标记EndOfStream最终会执行完毕。在任何时候都可以调用flush方法回到Flush状态。

调用stop方法会使mediacode进入 Uninitialized状态,这时候可以执行configure方法来进入下一循环。当mediacodec使用完毕后必须调用release方法来释放所有的资源。

在某些情况下,例如取出缓冲区索引时,mediacodec会发生错误进入Error状态,此时调用reset方法来是mediacodec重新处于Uninitialized状态,或者调用release来结束解码。

MediaCodec API 说明

MediaCodec 主要的API做一个介绍:

  • MediaCodec创建:
    • createDecoderByType/createEncoderByType:根据特定MIME类型(如"video/avc")创建codec。
    • createByCodecName:知道组件的确切名称(如OMX.google.mp3.decoder)的时候,根据组件名创建codec。使用MediaCodecList可以获取组件的名称。
  • configure:配置解码器或者编码器。
  • start:成功配置组件后调用start。
  • buffer处理的接口
    • dequeueInputBuffer:从输入流队列中取数据进行编码操作。
    • queueInputBuffer:输入流入队列。
    • dequeueOutputBuffer:从输出队列中取出编码操作之后的数据。
    • releaseOutputBuffer:处理完成,释放ByteBuffer数据。
    • getInputBuffers:获取需要编码数据的输入流队列,返回的是一个ByteBuffer数组。
    • getOutputBuffers:获取编解码之后的数据输出流队列,返回的是一个ByteBuffer数组。
  • flush:清空的输入和输出端口。
  • stop:终止decode/encode会话
  • release:释放编解码器实例使用的资源。

MediaCodec创建编/解码器

MediaCodec主要提供了createEncoderByType(String type)、createDecoderByType(String type)两个方法来创建编解码器,它们均需要传入一个MIME类型多媒体格式。常见的MIME类型多媒体格式如下:
● “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/mp4v-es” - MPEG4 video
● “video/3gpp” - H.263 video
● “audio/3gpp” - AMR narrowband audio
● “audio/amr-wb” - AMR wideband audio
● “audio/mpeg” - MPEG1/2 audio layer III
● “audio/mp4a-latm” - AAC audio (note, this is raw AAC packets, not packaged in LATM!)
● “audio/vorbis” - vorbis audio
● “audio/g711-alaw” - G.711 alaw audio
● “audio/g711-mlaw” - G.711 ulaw audio

MediaCodec参数配置

private void initVideoCodec(int width, int height) {
    try {
        // https://developer.android.google.cn/reference/android/media/MediaCodec mediacodec官方介绍
        // 比方MediaCodec的几种状态
        // avc即h264编码
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,width,height);
        // 设置颜色格式
        // 本地原始视频格式(native raw video format):这种格式通过COLOR_FormatSurface标记,并可以与输入或输出Surface一起使用
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        // 设置码率,通常码率越高,视频越清晰,但是对应的视频也越大
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE,width * height * 4);

        // 设置帧率 三星s21手机camera预览时,支持的帧率为10-30
        // 通常这个值越高,视频会显得越流畅,一般默认设置成30,你最低可以设置成24,不要低于这个值,低于24会明显卡顿,微信为28
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE,30);
        // 设置 I 帧间隔的时间
        // 通常的方案是设置为 1s,对于图片电影等等特殊情况,这里可以设置为 0,表示希望每一帧都是 KeyFrame
        // IFRAME_INTERVAL是指的帧间隔,这是个很有意思的值,它指的是,关键帧的间隔时间。通常情况下,你设置成多少问题都不大。
        // 比如你设置成10,那就是10秒一个关键帧。但是,如果你有需求要做视频的预览,那你最好设置成1
        // 因为如果你设置成10,那你会发现,10秒内的预览都是一个截图
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,5);

        // 创建编码器
        // https://www.codercto.com/a/41316.html MediaCodec 退坑指南
        mMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
        mMediaCodec.configure(mediaFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);

        // 相机的像素数据绘制到该 surface 上面
        mSurface = mMediaCodec.createInputSurface();

        videoEncoderThread = new VideoEncoderThread(videoRecorderReference);
    } catch (Exception e) {
        e.printStackTrace();
    }

}

可以用微信录制一个短视频,然后看下参数:



比较有参照意思的参数:

//录制了4.917秒
Duration/String                  : 4 秒 917 毫秒
//每秒一帧
Format_Settings_RefFrames/String : 4 帧
//视频宽高
Width/String                     : 288 像素
Height/String                    : 640 像素
//1601*1024除以288*640=8.9,KEY_BIT_RATE和宽高比接近9
OverallBitRate/String            : 1 601 kb/s
//格式avc,和MIMETYPE_VIDEO_AVC对应
Format/String                    : AVC
"FileExtension"                  : "mp4",
//avc也是mpeg-4
"Format"                         : "MPEG-4",
//帧率
FrameRate/String                 : 28.067 FPS
//yuv420,在camera预览时设定的nv21对应
ColorSpace                       : YUV
ChromaSubsampling/String         : 4:2:0
//声音的格式,再补充
Format/String                    : AAC LC
Channel(s)/String                : 1 声道
SamplingRate/String              : 44.1 kHz
FrameRate/String                 : 43.066 FPS (1024 SPF)

configure

    public void configure(
            MediaFormat format,
            Surface surface, MediaCrypto crypto, int flags);

  • MediaFormat format:输入数据的格式(解码器)或输出数据的所需格式(编码器)。传null等同于传递MediaFormat#MediaFormat作为空的MediaFormat。
  • Surface surface:指定Surface,用于解码器输出的渲染。如果编解码器不生成原始视频输出(例如,不是视频解码器)和/或想配置解码器输出ByteBuffer,则传null。
  • MediaCrypto crypto:指定一个crypto对象,用于对媒体数据进行安全解密。对于非安全的编解码器,传null。
  • int flags:当组件是编码器时,flags指定为常量CONFIGURE_FLAG_ENCODE。

MediaFormat:封装描述媒体数据格式的信息(包括音频或视频),以及可选的特性元数据。

  • 媒体数据的格式指定为key/value对。key是字符串。值可以integer、long、float、String或ByteBuffer。
  • 特性元数据被指定为string/boolean对。

开始录制

我们通过对MediaCodec参数进行配置,然后得到一个MediaCodec

 mMediaCodec.start();

结束录制

这里需要注意一下释放的顺序,一定得是按照下面的顺序进行资源释放的

 mMediaCodec.signalEndOfInputStream();
 mMediaCodec.stop();
 mMediaCodec.release();

● 下面看一段源码:当编解码器start后,会进入一个for(;;)循环,该循环是一个死循环,以实现不断地去从编解码器的输入缓存池中获取包含数据的一个缓存区,然后再从输出缓存池中获取编解码好的输出数据。

 MediaCodec codec = MediaCodec.createByCodecName(name);
 codec.configure(format, …);
 MediaFormat outputFormat = codec.getOutputFormat(); // option B
 codec.start();
 for (;;) {
   int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
   if (inputBufferId >= 0) {
     ByteBuffer inputBuffer = codec.getInputBuffer(…);
     // fill inputBuffer with valid data
     …
     codec.queueInputBuffer(inputBufferId, …);
   }
   int outputBufferId = codec.dequeueOutputBuffer(…);
   if (outputBufferId >= 0) {
     ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
     MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
     // bufferFormat is identical to outputFormat
     // outputBuffer is ready to be processed or rendered.
     …
     codec.releaseOutputBuffer(outputBufferId, …);
   } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
     // Subsequent data will conform to new format.
     // Can ignore if using getOutputFormat(outputBufferId)
     outputFormat = codec.getOutputFormat(); // option B
   }
 }
 codec.stop();
 codec.release();

上层数据获取

  do {
      if (mMediaCodec != null) {
          int outBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, -1);
          if (outBufferIndex >= 0) {
              ByteBuffer bb = mMediaCodec.getOutputBuffer(outBufferIndex);
              //这里获取到原始数据,然后根据相关需求可对数据进行处理
          }
          if (outBufferIndex >= 0) {
              mMediaCodec.releaseOutputBuffer(outBufferIndex, false);
          }
      }
  }
  while (isStarted);

具体使用代码如:https://github.com/445979241/opengles

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,176评论 5 469
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,190评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,232评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,953评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,879评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,177评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,626评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,295评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,436评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,365评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,414评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,096评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,685评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,771评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,987评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,438评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,032评论 2 341

推荐阅读更多精彩内容