前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android音视频硬编码与混合(三)

Android音视频硬编码与混合(三)

原创
作者头像
PengJie
修改2021-01-04 19:02:41
2.4K0
修改2021-01-04 19:02:41
举报
文章被收录于专栏:音视频修炼路

在本章开始之前我们先回顾一下什么是音视频软编码和硬编码。

软编码:使用CPU进行编码

硬编码:使用非CPU进行编码,如显卡GPU、专用的DSP、FPGA、ASIC芯片等

一般对于同一平台和硬件环境,硬编硬解的速度是快于软件编解码的。而且硬编码可以有效降低CPU占用率,所以在硬件支持的情况下,硬件编解码是我们的首选。

在Android 4.1以前,Android并没有提供硬编硬解的API,所以之前开发者都是使用FFMpeg来做视频软件编解码的,目前FFMpeg在Android的编解码上依旧广泛应用。Android 4.3之后增加了MediaCodec类用于进行硬件编解码的类,可以用于音频和视频编码。

MediaCodec工作原理

MediaCodec类Android提供的用于访问低层多媒体编/解码器接口,它是Android低层多媒体架构的一部分,通常与MediaExtractor、MediaMuxer、AudioTrack结合使用。我们可以简单的理解为它们共同组成了一个环形的传送带,客户端向获取到的编解码器输入缓存区写入要编解码的数据并将其提交给编解码器,待编解码器处理完毕后将其转存到编码器的输出缓存区,同时收回客户端对输入缓存区的所有权;然后,客户端从获取到编解码输出缓存区读取编码好的数据进行处理,待处理完毕后编解码器收回客户端对输出缓存区的所有权。不断重复整个过程,直至编码器停止工作或者异常退出。

MediaCodec API 主要方法

getInputBuffers:获取需要编码数据的输入流队列,返回的是一个ByteBuffer数组 queueInputBuffer:输入流入队列 dequeueInputBuffer:从输入流队列中取数据进行编码操作 getOutputBuffers:获取编解码之后的数据输出流队列,返回的是一个ByteBuffer数组 dequeueOutputBuffer:从输出队列中取出编码操作之后的数据 releaseOutputBuffer:处理完成,释放ByteBuffer数据

MediaCodec的使用流程:

1、创建MediaCodec实体和配置:

代码语言:txt
复制
    private void startMediaCodec() throws IOException {
        mMediaCodec = MediaCodec.createByCodecName(mCodecInfo.getName());
        mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mMediaCodec.start();
    }

MediaCodec的的configure方法:

代码语言:txt
复制
    public void configure(
            @Nullable MediaFormat format,
            @Nullable Surface surface, @Nullable MediaCrypto crypto,
            @ConfigureFlag int flags) {
        configure(format, surface, crypto, null, flags);
    }

MediaFormat format:输入数据的格式(解码器)或输出数据的所需格式(编码器)。

Surface surface:指定surface,一般用于解码器,设置后解码的内容会被渲染到所指定的surface上。无需要则传null

MediaCrypto crypto:指定一个crypto对象,用于对媒体数据进行安全解密。对于非安全的编解码器,传null。

int flags:当组件是编码器时,flags指定为常量CONFIGURE_FLAG_ENCODE。

前三个基本都是固定的,我们主要说一下MediaFormat,它主要用于设置编码的实体,它包含两个分别用于设置音频编码实体和视频编码实体方法:createVideoFormat/createAudioFormat

以音频为例:

代码语言:txt
复制
        MediaFormat audioFormat = MediaFormat.createAudioFormat(MIME_TYPE, SAMPLE_RATE, 1);
        audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
        audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
        audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);

以视频为例:

代码语言:txt
复制
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, this.mWidth,  this.mHeight);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,      
        MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); 

MediaFormat 可以为编码器设置一些特性参数,比如帧率,gop(关键帧 帧间 间隔)等等。

2、 数据处理:

  • 使用者从MediaCodec请求一个空的输入buffer(ByteBuffer),填充满数据后将它传递给MediaCodec处理

MediaCodec处理完这些数据并将处理结果输出至一个空的输出buffer(ByteBuffer)中。

以音频为例:

代码语言:txt
复制
        final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
        final int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC);

以视频为例:

代码语言:txt
复制
          ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
          ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();
          int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC);
  • MediaCodec处理完这些数据并将处理结果输出至一个空的输出buffer(ByteBuffer)中。

使用者从MediaCodec获取输出buffer的数据,消耗掉里面的数据dequeueOutputBuffer,使用完输出buffer的数据之后,将其释放回编解码器。

代码语言:txt
复制
    int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
    mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
  • 音视频数据混合

创建混合器实例

代码语言:txt
复制
        MediaMuxer mediaMuxer = new MediaMuxer(filePath,   
        MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

在混合之前需要通过编码器获取一个音轨视频轨的索引

以音频为例:

代码语言:txt
复制
    mediaMuxerRunnable.addTrackIndex(MediaMuxerThread.TRACK_AUDIO, format);

以视频为例:

代码语言:txt
复制
    mediaMuxer.addTrackIndex(MediaMuxerThread.TRACK_VIDEO, newFormat);

然后每次从编码器中取出分别音频和视频录制到的ByteBuffer,写入(writeSampleData)到编码器所在的轨道中

代码语言:txt
复制
  MuxerData data = muxerDatas.remove(0);
                        int track;
                        if (data.trackIndex == TRACK\_VIDEO) {
                            track = videoTrackIndex;
                        } else {
                            track = audioTrackIndex;
                        }
                        Log.e(TAG, "写入混合数据 " + data.bufferInfo.size);
                        try {
                            mediaMuxer.writeSampleData(track, data.byteBuf,  
                            data.bufferInfo);
                        } catch (Exception e) {
                            Log.e(TAG, "写入混合数据失败!" + e.toString());
                        }

循环上述输入buffer,消耗掉里面的数据,使用完输出buffer的数据之后,将其释放回编解码器,直到编码完成。

GitHub Demo完整源码链接

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • MediaCodec工作原理
  • MediaCodec API 主要方法
  • MediaCodec的使用流程:
    • 1、创建MediaCodec实体和配置:
      • 2、 数据处理:
      相关产品与服务
      图像处理
      图像处理基于腾讯云深度学习等人工智能技术,提供综合性的图像优化处理服务,包括图像质量评估、图像清晰度增强、图像智能裁剪等。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档