Android实现录屏直播+远程控制之MediaCodec编码篇

前言

前面Android实现录屏直播+远程控制(一)Android实现录屏直播+远程控制(二)两篇文章说到了实现Android录屏的方法
接下来就讲讲录制音视频的幕后黑手,这里实现录制音视频也有两种方案,分别是MediaRecorder和MediaCodec

什么是MediaRecorder

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

MediaRecorder的优缺点

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

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

什么是MediaCodec

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

MediaCodec工作原理

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

MediaCodec生命周期中的状态

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


media_codec_2.png

Stopped状态包含三个子状态:Uninitialized, Configured和Error,Executing同样包含三个状态:Flushed, Running 和End-of-Stream。

在mediacodec的使用过程中必须遵守图里标出的流程,否则会发生错误。

以解码器为例,讲解一下使用流程。当使用工厂方法创建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参数配置

public static MediaCodec getVideoMediaCodec() {
        MediaFormat format = MediaFormat.createVideoFormat("video/avc", 720, 1280);
        //设置颜色格式
        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        //设置比特率(设置码率,通常码率越高,视频越清晰)
        format.setInteger(MediaFormat.KEY_BIT_RATE, 1000 * 1024);
        //设置帧率
        format.setInteger(MediaFormat.KEY_FRAME_RATE, 10);
        //关键帧间隔时间,通常情况下,你设置成多少问题都不大。
        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
        // 当画面静止时,重复最后一帧,不影响界面显示
        format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / 45);
        format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
        //设置复用模式
        format.setInteger(MediaFormat.KEY_COMPLEXITY, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);
        MediaCodec mediaCodec = null;
        try {
            mediaCodec = MediaCodec.createEncoderByType("video/avc");
            mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        } catch (Exception e) {
            e.printStackTrace();
            if (mediaCodec != null) {
                mediaCodec.reset();
                mediaCodec.stop();
                mediaCodec.release();
                mediaCodec = null;
            }
        }
        return mediaCodec;
    }

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

推荐阅读更多精彩内容

  • 打包 视音频在传输过程中需要定义相应的格式,这样传输到对端的时候才能正确地被解析出来。 1、HTTP-FLV We...
    韩瞅瞅阅读 1,615评论 2 5
  • 前言 本文主要介绍直播所需要的编解码基础,后续文章将继续介绍实际的运用。 什么是码? 这里的码指码流(Data R...
    罗拙呓阅读 19,087评论 5 33
  • 3、使用 MediaCodec创建之后,需要通过start()方法进行开启。MediaCodec有输入缓冲区队列和...
    韩瞅瞅阅读 1,292评论 0 1
  • MediaCodec的官方文档 一、Android MediaCodec简单介绍 Android中可以使用Medi...
    黄海佳阅读 6,149评论 1 16
  • 也许是自带忧郁,也许是无病呻吟,莫名其妙的,我慌了。 浑浑噩噩,伪学霸,伪文艺青年,从大学步入社会,慌慌张张,匆匆...
    何小墨阅读 463评论 0 1