ffmpeg音视频解码和渲染流程

news2024/7/6 19:24:57

背景:

随着游戏娱乐等直播业务的增长,在移动端观看直播的需求也日益迫切。但是移动端原生的播放器对各种直播流的支持却不是很好。Android 原生的 MediaPlayer 不支持 flv、hls 直播流,iOS 只支持标准的 HLS 流。本文介绍一种基于 ffplay 框架下的跨平台播放器的实现,且兼顾硬解码的实现。

播放器原理:

直观的讲,我们播放一个媒体文件一般需要5个基本模块,按层级顺序:文件读取模块(Source)、解复用模块(Demuxer)、视频频解码模块(Decoder)、色彩空间转换模块(Color Space Converter)、音视频渲染模块(Render)。数据的流向如下图所示,其中 ffmpeg 框架包含了文件读取、音视频解复用的模块。

  1. 文件读取模块(Source)的作用是为下级解复用模块(Demuxer)以包的形式源源不断的提供数据流,对于下一级的Demuxer来说,本地文件和网络数据是一样的。在ffmpeg框架中,文件读取模块可分为3层:

    • 协议层: pipe,tcp,udp,http等这些具体的本地文件或网络协议
    • 抽象层:URLContext结构来统一表示底层具体的本地文件或网络协议
    • 接口层用:AVIOContext结构来扩展URLProtocol结构成内部有缓冲机制的广泛意义上的文件,并且仅仅由最上层用AVIOContext对模块外提供服务,实现读媒体文件功能。
  2. 解复用模块(Demuxer):的作用是识别文件类型,媒体类型,分离出音频、视频、字幕原始数据流,打上时戳信息后传给下级的视频频解码模块(Decoder)。可以简单的分为两层,底层是 AVIContext,TCPContext,UDPContext 等等这些具体媒体的解复用结构和相关的基础程序,上层是 AVInputFormat 结构和相关的程序。上下层之间由 AVInputFormat 相对应的 AVFormatContext 结构的 priv_data 字段关联 AVIContext 或 TCPContext 或 UDPContext 等等具体的文件格式。AVInputFormat 和具体的音视频编码算法格式由 AVFormatContext 结构的 streams 字段关联媒体格式,streams 相当于 Demuxer 的 output pin,解复用模块分离音视频裸数据通过 streams 传递给下级音视频解码器。

  3. 视频频解码模块(Decoder)的作用就是解码数据包,并且把同步时钟信息传递下去。

  4. 色彩空间转换模块(Color Space Converter)颜色空间转换过滤器的作用是把视频解码器解码出来的数据转换成当前显示系统支持的颜色格式

  5. 音视频渲染模块(Render)的作用就是在适当的时间渲染相应的媒体,对视频媒体就是直接显示图像,对音频就是播放声音

跨平台实现

在播放器得5个模块中文件读取模块(Source)、解复用模块(Demuxer)和色彩空间转换模块(Color Space Converter)这三个模块都可以用 ffmpeg 的框架进行实现,而f fmpeg 本身就是跨平台的。因此,实现跨平台的播放器的就需要抽象一层平台无关的音视频解码、渲染接口。Android、iOS、Window 等平台只需要实现各自平台的渲染、硬件解码(如果支持的话)就可以构建一个标准的基于 ffmpeg 的播放器了。

下图是基于ffplay的基本播放流程图:

图中红色部分是需要抽象的接口的,结构如下:

 

其中 FF_Pipenode.run_sync 视频解码线程,默认有 libavcodec 的软解码实现,其他平台可以增加自己的硬解码实现。SDL_VideoOut 为视频渲染抽象层,这里 overlay 可以是 Android的 NativeWindow,或者是 OpenGL 的 Texture。SDL_AudioOut 是音频播放抽象层,可以直接操作声卡驱动,SDL2.0 里就支持 ALSA、OSS 接口,当然也可以用 Android、iOS SDK 中的音频 API 实现。

这里顺便提下,随着 Android、iOS 平台的普及,ffmpeg 版本的也逐步支持了 Android、iOS 的硬件解码器,如f fmpeg 在很早之前就支持了 libstagefright,最新的 ffmpeg2.8 也已经支持了 iOS 的硬件解码库 VideoToolBox。从下面重点介绍下视频硬解码以及音视频渲染模块在移动平台上的实现。

Android

1.硬解码模块:

Android 的硬解码模块目前有 2 种实现方案:

libstagefright_h264:

libstagefright 是 Android2.3 之后版本的多媒体库,ffmpeg 早在 0.9 版本时就已经将libstagefright_h264 收录到自己的解码库中了,从 libstagefright.cpp 包括的头文件路径来看,是基于 Android2.3 版本的源码。因此编译 libstagefright 需要 Android2.3 的相关源码以及动态链接库。

ffmpeg 中的 libstagefright 目前只实现了 h264 格式的解码,由于 Android 机型、版本的碎片化相当严重,这种基于某个 Android 版本编译出来的 libstagefright 也存在很严重的兼容性问题,我在 Android4.4 的机型上就遇到无法解码的问题。

MediaCodec:

MediaCodec 是 Google 在 Android4.1(API16)以后新提供的硬件编解码 API,其工作原理如图所示:

以解码为例,先从 Codec 获取 inputBuffer,将待解码数据填充到 inputbuffer,再将 inputbuffer 交给Codec,接下来就可以从 Codec 的 outputBuffer 中拿到新鲜出炉的图像和声音信息了。下面的这段实例代码也许更能说明问题:

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();

令人沮丧的是,MediaCodec 只提供了 java 层的 API,而我们的播放器是基于 ffplay 架构的,核心的解码模块是不可能移到 java 层的。既然我们移不上去,就只能把 MediaCodec 拉到 Native 层了,通过 (*JNIEnv)->CallxxxMethod 的方式将 MediaCodec 相关的 API 在 Native 层做了一套接口。嗯,现在我们可以来实现视频的硬件解码了:

queue_picture 的实现如下图所示:

2.视频渲染模块:

在渲染之前,我们必须先指定一个渲染的画布,在android上这个画布可以是ImageView,SurfaceView,TextureView或者是GLSurfaceView。

关于在Native层渲染图片的方法,我曾看过一篇文章,文中介绍了四种渲染方法:

  • Java Surface JNI
  • OpenGL ES 2 Texture
  • NDK ANativeWindow API
  • Private C++ API

如果是用 ffmpeg 的 libavcodec 进行软解码,那么使用 NDK ANativeWindow API 将是最高效简单的方案,主要实现代码:

ANativeWindow* window = ANativeWindow_fromSurface(env, javaSurface);

ANativeWindow_Buffer buffer;
if (ANativeWindow_lock(window, &buffer, NULL) == 0) {
  memcpy(buffer.bits, pixels,  w * h * 2);
  ANativeWindow_unlockAndPost(window);
}

ANativeWindow_release(window);

示例代码中的 javaSurface 来自 java 层的 SurfaceHolder,pixels 指向 RGB 图像数据。

如果是使用了 MediaCodec 进行解码,那么视频渲染将变得异常简单,只需在 MediaCodec 配置时(MediaCodec.configure)指定图像渲染的 Surface,然后再解码完每一帧图像的时候调用 releaseOutputBuffer (index, true),MediaCodec 内部就会将图像渲染到指定的 Surface 上。

3.音频播放模块

Android 支持 2 套音频接口,分别是 AudioTrack 和 OpenSL ES,这里以 AudioTrack 为例介绍下音频的部分流程:

由于 AudioTrack 只有 java 层的 API,我们也得像 MediaCodec 一样在 Native 层重做一套 AudioTrack 的接口。

这里解码和播放是 2 个独立的线程,audioCallback 负责从 Audio Frame queue 中获取解码后的音频数据,如果解码后的音频采样率不是 AudioTrack 所支持的,就需要用 libswresample 进行重采样。

iOS

1. 硬解码模块

从 iOS8 开始,开放了硬解码和硬编码 API,就是名为 VideoToolbox.framework 的 API,支持 h264 的硬件编解码,不过需要 iOS 8 及以上的版本才能使用。这套硬解码 API 是几个纯 C 函数,在任何 OC 或者 C++ 代码里都可以使用。首先要把 VideoToolbox.framework 添加到工程里,并且包含以下头文件。

#include

解码主要需要以下四个函数:

  • VTDecompositionSessionCreate 创建解码session
  • VTDecompressionSessionDecodeFrame 解码一个frame
  • VTDecompressionOutputCallback 解码完一个frame后的回调
  • VTDecompressionSessionInvalidate 销毁解码session

解码流程如图所示:

2. 视频渲染模块

视频的渲染采用 OpenGL ES2 纹理贴图的形式。

3. 音频播放模块

采用 iOS 的 AudioToolbox.frameworks 进行播放。数据流程和 Android 平台是相同,不同的是,Android 平台把 PCM 数据喂给 AudioTrack,iOS 上把 PCM 数据喂给 AudioQueue。

总结

其实 ffpmeg 自带的播放器实例 ffplay 就是一个跨平台的播放器,得益于其依赖的多媒体库 SDL 实现了多平台的音视频渲染。但是 SDL 库过于庞大,并不适合整体移植到移动端。本文介绍的跨平台实现方案也是借鉴了 SDL2.0 的内部实现,只是重新设计了渲染接口。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/378455.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

挑选销售自动化工具应该关注什么功能?

销售自动化可以极大地提高你的生产力和效率,每周都为你节省时间。这样,你就可以把更多的时间用于完成交易,而减少用于行政任务的时间。市面上的销售自动化工具有很多,作为一般经验法则,以下是销售自动化工具中需要寻找…

智能家居Homekit系列一智能插座

WiFi智能插座对于新手接触智能家居产品更加友好,不需要额外购买网关设备 很多智能小配件也给我们得生活带来极大的便捷,智能插座就是其中之一,比如外出忘记关空调,可以拿起手机远程关闭。 简单说就是:插座可以连接wi…

【微信小程序】-- WXML 模板语法 - 列表渲染 -- wx:for wx:key(十二)

💌 所属专栏:【微信小程序开发教程】 😀 作  者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! &…

Web前端学习:四 - 练习

三九–四一:百度页面制作 1、左右居中: text-align: center; 2、去掉li默认的状态 list-style: none; li中有的有点,有的有序,此代码去掉默认状态 3、伪类:hovar 一般显示为color: #0f0e0f, 当鼠标接触时…

【JAVA程序设计】【C00107】基于SSM(非maven)的民宿短租管理系统——有文档

【C00107】基于SSM(非maven)的民宿短租管理系统——有文档项目简介项目获取开发环境项目技术运行截图项目简介 基于ssm框架非maven开发的民宿短租管理系统分为二种用户:系统管理员、用户 管理员角色包含以下功能: 用户管理、客房…

本地新项目上传到git的详细步骤

前提:你本地的项目目录里要记得添加.gitignore忽略文件,免得把一些无用的文件提交,内容如下,可直接粘贴: # Created by .ignore support plugin (hsz.mobi) ### Java template # Compiled class file *.class# Log fi…

Python不同版本之间的切换方法

在使用Python的过程中难免会遇到不同的项目使用不通同的Python环境,这就引出Python环境的切换问题 这篇文章以3.11.0与3.10.10之间的版本切换为列讲述 首先我自己的电脑上同时安装了这两个版本的Python,并且都已经配置了环境变量 1.两个版本的Python …

cast提前!最简单有效的神经网络优化方法,没有之一!

做优化有时候真的很头疼,绞尽脑汁的想怎么做算法等价,怎么把神经网络各层指令流水起来,在确保整网精度的同时,又有高性能。 但有时做了半天,却发现流水根本就流不起来,总是莫名其妙地被卡住。 真的是一顿…

[Java多线程 1] 多线程基础

在Java 技术中,多线程依旧是一个离不开的话题,掌握多线程才能对一些高并发技术理解透彻。同时多线程也需要有一定的操作系统基础,在其理论上进行学习,会对调度情况、线程情况有更多的了解。当然这一块也常常作为Java面试的重点&am…

HNU工训中心:元器件及测量基础实验报告

工训中心的牛马实验 1.实验目的 1.熟悉测量验证常用元器件参数、 并采用替代法(测量回路电流)测量其伏安特性的方法。 2.熟悉测量误差及减小测量误差注意事项 2.实验仪器和器材 1.实验仪器. 直流稳压电源型号:IT6302 台式多用表型号:UT805A 2.实验( 箱)器材 电路实验箱…

针对序列级和词元级应用微调BERT(需修改)

对于序列级和词元级自然语言处理应用,BERT只需要最小的架构改变(额外的全连接层),如单个文本分类(例如,情感分析和测试语言可接受性)、文本对分类或回归(例如,自然语言推…

sdut pta查找表

7-1 电话聊天狂人 给定大量手机用户通话记录,找出其中通话次数最多的聊天狂人。 输入格式: 输入首先给出正整数N(≤105),为通话记录条数。随后N行,每行给出一条通话记录。简单起见,这里只列出拨出方和接…

SpringBoot:SpringBoot整合Junit 和 MyBatis(3)

SpringBoot整合Junit 和 MyBatis1. SpringBoot整合Junit2. SpringBoot整合MyBatis2.1 定义SpringBoot项目2.2 定义dao接口2.3 定义service层2.4 定义controller层2.5 配置yml/yaml文件2.6 postman测试1. SpringBoot整合Junit 在com.example.service下创建BookService接口 publ…

华为OD机试题,用 Java 解【两数之和绝对值最小】问题

最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…

面向对象拓展贴

1. 类和对象的内存分配机制 1.1 分配机制  Java 内存的结构分析 栈: 一般存放基本数据类型(局部变量)堆: 存放对象(Cat cat , 数组等)方法区:常量池(常量,比如字符串), 类加载信息示意图 [Cat (name, age, price)]…

安全狗出席2023中国网络和数据安全产业高峰论坛

2月23日,由工业和信息化部、四川省人民政府主办的“2023中国网络和数据安全产业高峰论坛”在成都顺利开幕。 作为国内云原生安全领导厂商,安全狗也出席了此次活动。 在此次活动中,“2022年网络安全技术应用试点示范项目授牌仪式”环节引起业…

TwinCAT3第三方伺服电机——汇川SV660N使用

目录 一、第三方伺服在TC3中配置和使用 二、xml文件拷贝 ​编辑 三、IO中扫描伺服 四、工程测试 五、汇川伺服参数设置说明 一、第三方伺服在TC3中配置和使用 在倍福控制系统中使用第三方伺服可以参见本人另一篇博客,有详细教程说明。本文仅仅对SV660N伺服设置…

表格形式的Sarsa与Q_learning算法

环境如下: 这是一个简单的环境,绿色方块代表终点,白色方块代表可行点,灰色方块代表陷阱 用Sarsa算法和Q_learning算法训练得到value表格 代码如下: (jupyter notebook上的代码,所以顺序看起来有点儿奇怪) …

【博学谷学习记录】超强总结,用心分享丨人工智能 Linux常用软件安装 CenOS 7 arm 安装 MySQL8

目录环境说明虚拟机安装MySQL下载步骤1.卸载系统自带的mariadb-lib2.上传安装包并解压3.按顺序安装4.初始化数据库5.目录授权,否则启动失败6.启动msyql服务7.查看msyql服务的状态8.在/var/log/mysqld.log下查看临时密码9.用临时密码登录到数据库10.执行下列mysql命令…

给打算转行IC的同学几点建议,来听听工程师怎么说?

“我不是相关专业的,也没什么IC设计的基础,能转IC设计岗吗?” “感觉自己学比较乱,不知道到底怎么学?没有项目经验怎么办?” 每一个想转IC设计岗位的同学都或多或少地遇到过这样的问题,有着找…