【FFmpeg视频播放器开发】解封装解码流程、常用API和结构体简介(一)

news2024/12/24 10:16:42

一、前言

在正式编写 FFmpeg 播放器前,我们需要先简单了解下所要用到的 FFmpeg 库、播放与解码流程、函数和相关结构体。

二、FFmpeg 库简介

介绍
avcodec音视频编解码核心库
avformat音视频容器格式的封装和解析
avutil核心工具库
swscal图像格式转换的模块
swresampel音频重采样
avfilter音视频滤镜库 如视频加水印、音频变声
avdevice输入输出设备库,提供设备数据的输入与输出

FFmpeg 就是依靠以上几个库,实现了强大的音视频编码、解码、编辑、转换、采集等能力。这里实现视频播放就除了 avfilter 库没用到。

三、FFmpeg播放流程

通常情况下,视频文件如 MP4,MKV、FLV 等都属于封装格式,就是将已经压缩编码的视频数据和音频数据按照一定的格式放到一起。当我们播放一个媒体文件时,通常需要经过以下几个步骤:

可以看到这个视频播放器的实现需要涉及到以下内容:

  • 解封装(Demuxing):就是将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码数据。例如,FLV 格式的数据,经过解封装操作后,输出 H.264 编码的视频码流和 AAC 编码的音频码流。

  • 软硬件解码(Decode):就是将视频/音频压缩编码数据,解码成为非压缩的视频/音频原始数据。通过解码,将压缩编码的视频数据 H.264,MPEG2 解码成为非压缩的颜色数据,例如 YUV 等等;将压缩编码的音频数据 AAC,MP3 解码成为非压缩的音频抽样数据,例如 PCM 数据。解码分为硬编码和软编码。

  • 像素格式转换:将 YUV 数据格式转换成 RGB 数据格式。

  • 重采样:对音频重新采样。

  • dts/pts:dts 是解码的时间戳,而 pts 是显示的时间戳。pts 用于获取当前播放进度。进度条移动需要用到av_seek_frame函数。

  • 音视频同步:就是根据解封装模块处理过程中获取到的参数信息,同步解码出来的音频和视频数据,并将音视频频数据送至系统的显卡和声卡播放出来(Render)。

其中解码是最重要的,下面介绍一下解码的流程以及用到的 API 和结构体。

★文末名片可以免费领取音视频开发学习资料,内容包括(C/C++,Linux 服务器开发,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

 

四、FFmpeg解码流程

五、使用到的FFmpeg API说明

5.1 av_register_all()

  • 注册 FFmpeg 的所有组件。

  • 在 4.0 版本以后已经被弃用,所以实际不加也可以正常编解码音视频。

5.2 avformat_alloc_context()

用于初始化 AVFormatContext 对象。其原型如下:

AVFormatContext *avformat_alloc_context(void)
  • 由于 AVFormatContext 必须初始化为 NULL 或者用avformat_alloc_context()进行初始化。

5.3 avformat_open_input()

打开媒体文件,并获得解封装上下文。其原型如下:

int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options)
  • ps:AVFormatContext 双重指针,函数调用成功之后将解封装上下文赋值给 ps。

  • url:可以是 rtsp、http 网络流地址,或者本地视频文件路径。

  • fmt:指定输入音视频的封装格式,一般情况下可以设置为 nullptr,则会自动探索。

  • fmt:强制指定 AVFormatContext 的成员 AVInputFormat,即输入音视频的封装格式。一般情况下可以设置为 NULL,这样会自动探索 AVInputFormat。

  • options:附加的一些选项,一般情况下可以设置为 nullptr,但有时候播放 rtsp 时需要设置下。

5.4 avformat_find_stream_info()

探测获取流信息。其原型如下:

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)
  • 因为在一些格式当中没有头部信息,如 flv 格式,h264 格式,调用avformat_open_input()在打开文件之后就没有参数,也就无法获取到里面的信息。

  • 这个时候就可以调用此函数,因为它会试着去探测文件的格式,但是如果格式当中没有头部信息,那么它只能获取到编码、宽高这些信息,还是无法获得总时长。

  • 如果总时长无法获取到,则需要把整个文件读一遍,获取一下它的总帧数来计算。

5.5 avcodec_find_decoder()

查找解码器。函数的参数是所要用解码器的ID,成功返回查找到的解码器(没有找到就返回 NULL)。其原型如下:

AVCodec *avcodec_find_decoder(enum AVCodecID id);
  • id:查找到的解码器

5.6 avcodec_open2()

用于初始化一个音视频编解码器的 AVCodecContext,声明位于 libavcodec\utils.c。其原型如下:

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options)
  • avctx:需要初始化的 AVCodecContext。

  • codec:输入的AVCodec。

  • options:一些选项。例如使用 libx264 编码的时候,“preset”,“tune” 等都可以通过该参数设置。

5.7 av_read_frame()

读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码。其原型如下:

int av_read_frame(AVFormatContext *s, AVPacket *pkt)
  • s:解封装上下文。

  • pkt:存储一帧视频的压缩数据。

5.8 avcodec_decode_video2()

解码一帧视频数据。输入一个压缩编码的结构体 AVPacket,输出一个解码后的结构体 AVFrame。其原型如下:

int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
                         int *got_picture_ptr,
                         const AVPacket *avpkt);
  • avctx:需要初始化的 AVCodecContext。

  • codec:输入的AVCodec。

  • options:一些选项。例如使用libx264编码的时候,“preset”,“tune”等都可以通过该参数设置。

5.9 avformat_close_input()

关闭释放解封装上下文,并且设置为 0。其原型如下:

void avformat_close_input(AVFormatContext **s)
  • s:解封装上下文。

六、使用到的FFmpeg结构体说明

6.1 AVFormatContext

解封装上下文,是存储音视频封装格式中包含信息的结构体。

char filename[1024] // 保存打开的文件名,一般用在 rtsp、rtmp 断开重连
unsigned int nb_streams // 音视频流的个数
AVStream **streams // 存储视频流、音频流、字幕流信息
int64_t duration // 媒体文件的总时长,单位是把 1 秒切成 AV_TIME_BASE(1000000)份,即单位。为 us,注意不一定每个视频都能获取到 duration
int64_t bit_rate // 比特率(单位bps,转换为kbps需要除以1000)

6.2 AVStream

AVStream 是存储每一个音频/视频流信息的结构体。其重要的变量如下所示:

int index // 标识该视频/音频流
AVCodecContext *codec // 解码器,4.0 版本后已弃用
AVRational time_base // 时基。通过该值可以把PTS,DTS转化为实际的时间(单位为秒s)
int64_t duration // 该视频/音频流时长,单位为 ms
AVRational avg_frame_rate // 帧率(注:对视频来说,这个挺重要的)
AVPacket attached_pic // 附带的图片。比如说一些 MP3,AAC 音频文件附带的专辑封面
AVCodecParameters *codecpar // 音视频参数,新增用来替换AVCodecContext *codec

6.3 AVCodecContext

AVCodecContext 是一个描述编解码器上下文的结构体,包含了众多编解码器需要的参数信息。下面挑一些关键的变量来看看(这里只考虑解码)。

enum AVMediaType codec_type // 编解码器的类型(视频,音频...)
struct AVCodec  *codec // 采用的解码器AVCodec(H.264,MPEG2...)    
enum AVCodecID codec_id // 标示特定的编解码器(H.264,MPEG2...)
int format // 视频像素格式/音频采样数据格式
int width, height // 表示视频的宽和高
int bit_rate // 平均比特率    
int channels // 声道数(音频)
uint64_t channel_layout // 声道格式
int sample_rate // 采样率(音频)
AVRational time_base; // 时基。通过该值可以把PTS,DTS转化为实际的时间(单位为秒s)
uint8_t *extradata; int extradata_size; // 针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等)

6.4 AVCodec

AVCodec 是存储编码器信息的结构体。其重要的变量如下所示:

const char *name; // 编解码器的名字的简称
const char *long_name; // 编解码器名字的全称
enum AVMediaType type; // 指明了类型,是视频,音频,还是字幕
enum AVCodecID id; // ID,不重复
const AVRational *supported_framerates; // 支持的帧率(仅视频)
const enum AVPixelFormat *pix_fmts; // 支持的像素格式(仅视频),如RGB24、YUV420P等。
const int *supported_samplerates; // 支持的采样率(仅音频)
const enum AVSampleFormat *sample_fmts; // 支持的采样格式(仅音频)
const uint64_t *channel_layouts; // 支持的声道数(仅音频)
int priv_data_size; // 私有数据的大小

6.5 AVCodecParameters

新增用来替换AVCodecContext *codec。因为 AVCodecContext 结构体包含的参数太多,AVCodecParameters 将编码器的参数从 AVCodecContext 分离出来,AVCodecParameters 结构体中部分重要的参数如下:

enum AVMediaType codec_type // 编解码器的类型(视频,音频...)   
enum AVCodecID codec_id // 标示特定的编解码器(H.264,MPEG2...)
int format // 视频像素格式/音频采样数据格式
int width, height // 表示视频的宽和高
int bit_rate // 平均比特率    
int channels // 声道数(音频)
uint64_t channel_layout // 声道格式
int sample_rate // 采样率(音频)
AVRational time_base; // 时基。通过该值可以把PTS,DTS转化为实际的时间(单位为秒s)
uint8_t *extradata; int extradata_size; // 针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等)

可以看到两者的成员基本一致。

avcodec_decode_video2():解码一帧视频数据
sws_scale():转换视频数据格式    
av_frame_free():释放xx上下文申请的内存
avcodec_close():关闭解码器

6.6 AVPacket

AVPacket 是存储压缩编码数据相关信息的结构体。其重要的变量如下所示:

uint8_t *data; // 压缩编码的数据。
/* 例如对于H.264来说。1个AVPacket的data通常对应一个NAL。
注意:在这里只是对应,而不是一模一样。他们之间有微小的差别:使用FFMPEG类库分离出多媒体文件中的H.264码流。因此在使用FFMPEG进行音视频处理的时候,常常可以将得到的AVPacket的data数据直接写成文件,从而得到音视频的码流文件。*/
int size; // data的大小
int64_t pts; // 显示时间戳
int64_t dts; // 解码时间戳
int stream_index; // 标识该AVPacket所属的视频/音频流。

6.7 AVFrame

AVFrame 结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是 YUV,RGB,对音频来说是 PCM),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP 表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用 FFmpeg 进行码流分析的时候,AVFrame 是一个很重要的结构体。

下面看几个主要变量的作用(在这里考虑解码的情况):

uint8_t *data[AV_NUM_DATA_POINTERS]; // 解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM)
int linesize[AV_NUM_DATA_POINTERS]; // data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。
int width, height; // 视频帧宽和高(1920x1080,1280x720...)
int nb_samples; // 音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个
int format; // 解码后原始数据类型(YUV420,YUV422,RGB24...)
int key_frame; // 是否是关键帧
enum AVPictureType pict_type; // 帧类型(I,B,P...)
AVRational sample_aspect_ratio; // 宽高比(16:9,4:3...)
int64_t pts; // 显示时间戳
int coded_picture_number; // 编码帧序号
int display_picture_number; // 显示帧序号

参考:

【雷神 - 解码】

图解FFMPEG打开媒体的函数avformat_open_input

FFmpeg 源代码简单分析:avformat_open_input()

FFmpeg 源代码简单分析:avformat_find_stream_info()

FFmpeg 源代码简单分析:av_read_frame()

FFmpeg 源代码简单分析:avcodec_decode_video2()

FFmpeg 源代码简单分析:avformat_close_input()

【雷神 - FFmpeg结构体】

FFMPEG中最关键的结构体之间的关系

FFMPEG结构体分析:AVFrame

FFMPEG结构体分析:AVFormatContext

FFMPEG结构体分析:AVCodecContext

FFMPEG结构体分析:AVIOContext

FFMPEG结构体分析:AVCodec

FFMPEG结构体分析:AVStream

FFMPEG结构体分析:AVPacket

作者:fengMisaka

★文末名片可以免费领取音视频开发学习资料,内容包括(C/C++,Linux 服务器开发,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

 

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

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

相关文章

零基础如何学好Python?Python有哪些必须学的知识?

跟几个IT界的大佬提起Python,他们说零基础学好Python很简单,Python进阶需要花费些气力。那么零基础如何学好Python?Python有哪些必须学的知识?学习的策略技巧有哪些?今天小编整理的这篇文章将会给你启发。 Python上手…

vulnhub靶机DriftingBlue6

靶机下载地址DriftingBlues: 6 ~ VulnHub kali ip:192.168.174.128 靶机ip: 192.168.174.140 首先进行ip地址发现 进行端口扫描 这里进行目录扫描,我习惯性使用Linux里面的gobuster和Windows上的dirsearch一起进行扫描。 python dirsearc…

Linux | 常见指令和常用热键

啊我摔倒了..有没有人扶我起来学习.... 👱个人主页:《CGod的个人主页》\color{Darkorange}{《CGod的个人主页》}《CGod的个人主页》交个朋友叭~ 💒个人社区:《编程成神技术交流社区》\color{Darkorange}{《编程成神技术交流社区》…

升级Linux服务器你必须要知道的事

服务器安全是每个系统管理员的头等大事。除了拥有第三方安全工具(如防火墙,DDoS保护等)外,使生产服务器上的所有代码保持最新也是极为重要的。 当您的应用程序投入生产时,升级它的风险更大。如果您的服务器由Linux驱动…

超级简单基于nodejs电商管理系统的设计与实现.zip(论文+源码+ppt文档+视频录制)

相关资料下载地址:请点击下载》》》 该商城平台整体上实现了商品管理模块、订单交易模块,购物车模块、商品列表股那里模块、用户模块模块。在这些功能模块独立运行和相互配合下形成了一个电商商城系统。在商城实现的技术架构方面,该商城采用nodejs作为商城后台的底…

C++11标准模板(STL)- 算法(std::minmax)

定义于头文件 <algorithm> 算法库提供大量用途的函数&#xff08;例如查找、排序、计数、操作&#xff09;&#xff0c;它们在元素范围上操作。注意范围定义为 [first, last) &#xff0c;其中 last 指代要查询或修改的最后元素的后一个元素。 返回两个元素的较小和较大…

二次供水智慧改造,保障冬季热水稳定供给

冬季低温雨雪天气不仅仅影响到居民用水&#xff0c;也影响到二次供水泵站的维护检修。一旦设备出现故障&#xff0c;导致无法及时供水&#xff0c;居民可能喝不上一口热水&#xff0c;更不用提热水洗澡的难题了。因此&#xff0c;在冬季需要对二次供水泵站设备更加关注&#xf…

AD20和立创EDA设计(4)PCB设计

&#xff08;1&#xff09;本文主要介绍如何将从立创EDA导出的原理图&#xff0c;在AD20进行PCB设计。 &#xff08;2&#xff09;需提前观看&#xff1a;AD20和立创EDA设计&#xff08;3&#xff09;微调原理图和原理图检查&#xff1b; &#xff08;3&#xff09;邀请加入嵌入…

【数据结构Note6】-图-知识总结(图存储+BFS+DFS+最小生成树+最短路径+拓扑+逆拓扑)

文章目录6.1 图的定义及性质6.1.1 无向图和有向图6.1.2 简单图和多重图6.1.3 图的相关概念6.1.3.1 顶点的度6.1.3.2 顶点和顶点的关系6.1.3.3 子图6.1.3.4 连通分量6.1.3.5 强连通分量6.1.3.6 生成树6.1.3.7 生成森林6.1.3.8 边的权、带权图/网6.1.3.9 几种特殊的图6.2 图的存储…

课程试题库网站

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 模块划分&#xff1a;老师模块、班级模块、学生模块、课程模块、试题模块、试卷模块、组卷模块、考试模块、答题模) 管理…

零基础转行Python开发怎么学习呢?

转行零基础学Python编程开发难度大吗&#xff1f;从哪学起&#xff1f;近期很多小伙伴问我&#xff0c;如果自己转行学习Python&#xff0c;完全0基础能否学会呢&#xff1f;Python的难度到底有多大&#xff1f;今天&#xff0c;小编就来为大家详细解读一下这个问题。 学习 Py…

时间哈希+日期处理 睡大觉

E-睡大觉_2022河南萌新联赛第&#xff08;四&#xff09;场&#xff1a;郑州轻工业大学 (nowcoder.com) 题意&#xff1a; 思路&#xff1a; 这是一道模拟题&#xff0c;这里主要总结如何处理时间信息和日期信息 对于时间信息&#xff1a;把时分秒转化成秒&#xff0c;相当…

基于java(springboot)网吧管理系统(java毕业设计)

基于java&#xff08;springboot&#xff09;网吧管理系统 网吧管理系统是基于java编程语言&#xff0c;mysql数据库&#xff0c;springboot框架和idea开发工具开发 &#xff0c;本系统分为会员&#xff0c;网管&#xff0c;管理员三个角色&#xff0c;会员功能是注册登陆系统…

基于JAVA的网上订餐外卖系统(Java+MySQL)

美食是人类永恒的话题,无论是在古代还是现代人们对美食都有一种非常的热爱在里面,但是随着时代的发展,人们可能没有更多的时间去研究美食,很多时候人们在下班或者放学之后更希望通过网络来进行订餐,为此我开发了本基于JAVA的餐必达网上订餐系统 本餐必达网上订餐系统采用Java语…

在写vue的项目中为什么index不能做key使用?

前言 在我们写vue的的项目时&#xff0c;我们是否会疑惑后端数据为什么都会带一个Id&#xff0c;而这个Id一般都作为循环中的key来使用&#xff0c;我们为什么不直接用index来作为他的key呢&#xff1f;这样不是更方便吗&#xff1f;下面我就带大家解决解决这和疑惑吧。 key的…

深度学习-归一化输入,梯度消失爆炸,梯度检验

文章目录前言一、归一化输入1、均值方差归一化2、why normalize input?二、梯度消失&#xff0c;爆炸1.梯度2.深度网络学习初始化三、梯度检验梯度检验前言 吴恩达 week5 一、归一化输入 1、均值方差归一化 均值方差归一化。 要注意&#xff1a;我们要对训练数据集和测试数…

探花交友_第9章_小视频方案(新版)

探花交友_第9章_小视频方案(新版) 文章目录探花交友_第9章_小视频方案(新版)1. 我的访客1.1 需求分析1.1.1 功能说明1.1.2 数据库表1.2 记录访客数据tanhua-modeltanhua-app-servertanhua-dubbo-interfacetanhua-dubbo-mongo1.3 首页谁看过我需求分析tanhua-modeltanhua-app-se…

小学生 C++画图 Go C 编程 第7课 奇异的花朵

第一课 GoC简介和演示 第一课 GoC简介和演示_ahwhjt的博客-CSDN博客_goc输入图形数量 第二课 了解编程环境 第二课 了解编程环境_ahwhjt的博客-CSDN博客_goc编程环境 第三课 基本绘图命令 第三课 基本绘图命令_ahwhjt的博客-CSDN博客_电脑编程的pen.lt 第四课 变量的引入 第…

重写 Nacos 服务发现:多个服务器如何跨命名空间,访问公共服务?

一、问题背景 在开发某个公共应用时&#xff0c;笔者发现该公共应用的数据是所有测试环境&#xff08;假设存在 dev/dev2/dev3&#xff09;通用的。 这就意味着只需部署一个应用&#xff0c;就能满足所有测试环境的需求&#xff1b;也意味着所有测试环境都需要调用该公共应用…

匆匆遭遇猿如意

刚刚收到一条消息&#xff0c;说有一个csdn的猿如意可以测试了&#xff0c;我就下载了一个&#xff0c;根据提示下载了&#xff0c;然后开始体验。 一、ChatGPT 谁让这个东西最近这么热呢&#xff0c;所以&#xff0c;我第一个就体验这个东东了&#xff0c;结果&#xff0c;结…