FFmpeg从入门到入魔(1):初探FFmpeg框架

news2025/1/16 6:59:06

1. FFmpeg介绍与裁剪

1.1 FFmpeg简介


FFmpeg(Fast forword mpeg,音视频转换器)是一个开源免费跨平台的视频和音频流方案,它提供了录制/音视频编解码、转换以及流化音视频的完整解决方案。ffmpeg4.0.2源码目录结构如下:

目录说明:FFmpeg|—compat 该目录存放的是兼容文件,以便兼容早期版本|—doc 说明文档|—ffbuild|—libavcodec 音视频编解码核心库|—libavdevice 各种设备的输入输出,比如Video4Linux2, VfW, DShow以及 ALSA|—libavfilter 滤镜特效处理|—libavformat I/O操作和封装格式(muxer/demuxer)处理|—libavswresample 音频重采样,格式转换和混音|— (1) 重采样:改变音频的采样率,比如从44100HZ降低到8000HZ|— (2)重新矩阵化:改变音频通道数量,比如从立体声道(stereo )变为单身道(mono)|— (3)格式转换:改变音频采样大小,比如将每个样本大小从16bits降低到8bits|—libavutil 工具库,比如算数运算、字符操作等|—libpostproc 后期效果处理,如图像的去块效应|—libswscale 视频像素处理,包括缩放图像尺寸、色彩映射转换、像素颜色空间转换等|—presets|—tests 测试实例|—configure 配置文件,编译ffmpeg时用到

1.2 命令行工具


FFmpeg框架中还提供了几个用于执行命令行完成音视频数据处理工具,包括ffplay、ffprobe、ffserver,具体解释如下:

  • ffplay

Fast forword play,用ffmpeg实现的播放

  • ffserver

Fast forword server,用ffmpeg实现的rtsp服务器

  • ffprobe

Fat forword probe,用来输入分析输入流

2. FFmpeg架构分析


在1.1小节中,我们对FFmpeg整体架构进行了简单介绍,阐述了框架中各个模块的功能。本节将在此基础上,重点阐述在利用FFmpeg进行音视频开发中牵涉到的重要步骤,数据结构体以及相关函数。

2.1 FFmpeg处理要点


总体来说,FFmpeg框架主要的作用在于对多媒体数据进行解协议、解封装、解码以及转码等操作,为了对FFmpeg在视音频中的应用有个更直观理解,下面给出解析rtsp网络流的流程图,该图演示了从打开rtsp流,到最终提取出解码数据或转码的大概过程,如下所示:

术语解释:

  • muxer:视音频复用器(封装器),即将视频文件、音频文件和字幕文件(如果有的话)合并为某一个视频格式,比如讲a.avi、a.mp3、a.srt合并为mkv格式的视频文件;

  • demuxer:视音频分离器(解封装器),即muxer的逆过程;

  • transcode:转码,即将视音频数据从某一种格式转换成另一种格式;

  • RTP包:Real-time Transport Protocol,实时传输协议,是一种基于UDP的网络传输协议,它介于应用层和传输层之间,负责对流媒体数据进行封包并实现媒体流的实时传输;

  • ES流:Elementary Streams,即原始流,也称视/音频裸流,是直接从编码器输出的数据流,可为视频数据流(如H.264、MJPEG等)或音频数据流(如AAC等);

  • PES流:Packetized Elementary Streams,分组ES流,PES流是ES流经过PES打包器将ES分组、打包、加入包头信息等处理后形成的数据流,是用来传递ES的一种数据结构。

  • 解协议:取出网络数据流无关报文信息,以获取真正的视音频数据,常见的协议有rtsp、rtmp、http和mms等;

  • 解封装:即demuxer,封装格式可以为.mp4/.avi/.flv/.mkv等;

  • 解码:将编码数据还原成原始内容,比如将H.264解码为YUV、AAC解码为PCM等;

2.1 FFmpeg重要的结构体


FFmpeg中有很多比较重要的结构体,比如与输入输出(I/O)有关的结构体AVIOContext、URLContext、URLProtocol ,与封装格式有关的结构体AVFormatContext、AVInputFormat、AVOutputFormat,与编解码有关的结构体AVCodec、AVCodecContext,以及与音视频数据有关的结构体AVStream、AVPacket、AVFrame等等。刚开始接触FFmpeg时,个人感觉一时间要理解区分这些结构体还是有点困难的,好在这些结构体当中有个“老大哥”-AVFormatContext,AVFormatContext可以说是贯穿整个FFmpeg开发,"犹如神一般的存在"。下面我们就在分析AVFormatContext结构体的基础上,阐述上述结构体的作用与区别。AVFormatContextAVFormatContext结构体描述了一个多媒体文件或流的构成和基本信息,是FFmpeg中最为基本的一个结构体,也是其他所有结构的根。其中,成员变量iformat和oformat为指向对应的demuxing(解封装)和muxing(封装)指针,变量类型分别为AVInputFormat、AVOutputFormat;pb为指向控制底层数据读写的指针,变量类型为AVIOContext;nb_streams表示多媒体文件或多媒体流中数据流的个数;streams为指向所有流存储的二级指针,变量类型AVStream;video_codec和audio_codec分别表示视频和音频编解码器,变量类型为AVCodec等等。AVFormatContext结构体(位于libavformat/avformat.h中)部分源码如下:

typedef struct AVFormatContext {

const AVClass *av_class;

// 输入容器格式

// 只在调用avformat_open_input()时被设置,且仅限Demuxing

struct AVInputFormat *iformat;

// 输出容器格式

// 只在调用avformat_alloc_output_context2()函数时被设置,且仅限封装(Muxing)

struct AVOutputFormat *oformat;

/**

* Format private data. This is an AVOptions-enabled struct

* if and only if iformat/oformat.priv_class is not NULL.

*

* - muxing: set by avformat_write_header()

* - demuxing: set by avformat_open_input()

*/

void *priv_data;

// 输/入输出(I/O)的缓存

// 说明:解封装(demuxing):值由avformat_open_input()设置

// 封装(muxing): 值由avio_open2设置,需在avformat_write_header()之前

AVIOContext *pb;

// stream info

int ctx_flags;

// AVFormatContext.streams中数据流的个数

// 说明:值由avformat_new_stream()设置

unsigned int nb_streams;

// 文件中所有流stream列表。创建一个新stream,调用avformat_new_stream()函数实现

// 当调用avformat_free_context()后,streams所占资源被释放

// 说明:解封装(demuxing):当调用avformat_open_input()时,streams值被填充

// 封装(muxing):streams在调用avformat_write_header()之前被用户创建

//

AVStream **streams;

// 输入或输出文件名,如输入:rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov

// 说明:demuxing:当调用avformat_open_input()后被设置

// muxing: 当调用avformat_alloc_output_context2()后被设置,且需要调用avformat_write_header()之前

char filename[1024];

// component的第一帧位置,仅限Demuxing时由libavformat设置

int64_t start_time;

// stream的时长,仅限Demuxing时由libavformat设置

int64_t duration;

// 总比特率(bit/s),包括音频、音频

int64_t bit_rate;

...

// 视频编解码器ID

// 说明:Demuxing时由用户设置

enum AVCodecID video_codec_id;

// 音频编解码器ID

// 说明:Demuxing时由用户设置

enum AVCodecID audio_codec_id;

// 字幕(subtitle)编解码器ID

// 说明:Demuxing时由用户设置

enum AVCodecID subtitle_codec_id;

...

// 文件元数据,即Metadata

// 说明:demuxing:当调用avformat_open_input时被设置

// muxing:在调用avformat_write_header()之前被设置

// 注:当调用avformat_free_context()时metadata的资源被libavformat释放

AVDictionary *metadata;

// 实时流启动的真实时间

int64_t start_time_realtime;

...

// 视频编解码器,Demuxing时由用户指定

AVCodec *video_codec;

// 音频编解码器,Demuxing时由用户指定

AVCodec *audio_codec;

// 字幕编解码器,Demuxing时由用户指定

AVCodec *subtitle_codec;

// 数据编解码器,Demuxing时由用户指定

AVCodec *data_codec;

...

// 数据编解码器ID

enum AVCodecID data_codec_id;

...

} AVFormatContext

1. 复用(muxing)/解复用(demuxing)


(1) AVInputFormat结构体

AVInputFormat为解复用/解封装(demuxing)器对象,它包含了解复用器的相关信息和操作函数,比如name成员变量为指定封装格式的名称,如"aac"、"mov"等;read_header成员函数为读取封装头部数据;read_packet成员函数为读取一个AVPacket等等。AVInputFormat结构体(位于libavformat/avformat.h中)部分源码如下:

typedef struct AVInputFormat {

// 封装格式名称,如"mp4"、"mov"等

const char *name;

// 封装格式别称

const char *long_name;

int flags;

const char *extensions;

const struct AVCodecTag * const *codec_tag;

const AVClass *priv_class;

const char *mime_type;

//

struct AVInputFormat *next;

int raw_codec_id;

// 具体format对应Context的size,如MovContext

int priv_data_size;

int (*read_probe)(AVProbeData *);

// 读取format header,同时初始化AVFormatContext结构

// 若成功,返回0

int (*read_header)(struct AVFormatContext *);

// 读取packet大小数据,并将其存放到pkt指向的内存中

// 若成功,返回0;若失败,返回负数且pkt不会被分配内存

int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);

// 关闭流,但不释放AVFormatContext和AVStreams所占内存

int (*read_close)(struct AVFormatContext *);

/**

* seek相对于流索引中帧的时间戳

* @param stream_index 流index,不能为-1

* @param flags 用于方向,如果么有精确的匹配

* @return >= 0 操作成功

*/

int (*read_seek)(struct AVFormatContext *,

int stream_index, int64_t timestamp, int flags);

/**

* 获取流[stream_index]的下一个时间戳

* @return 时间戳或AV_NOPTS_VALUE(当发生错误时)

*/

int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index,

int64_t *pos, int64_t pos_limit);

// Start/resume playing -只适用于RTSP

int (*read_play)(struct AVFormatContext *);

// Pause playing - 只适用于RTSP

int (*read_pause)(struct AVFormatContext *);

// 获取设备列表,详解avdevice_list_devices()

int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);

// 初始化设备功能子模块,详见avdevice_capabilities_create()函数

int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);

// 释放设备功能子模块,详见avdevice_capabilities_free()函数

int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);

} AVInputFormat;

(2) AVOutputFormat结构体


与AVInputFormat相反,AVOtputFormat为复用/封装(muxing)器对象,它包含了复用器的相关信息和操作函数,比如name成员变量为指定封装格式的名称,如"mp4"、"3gp"等;write_header成员函数为读取封装头部数据;write_packet成员函数为写入一个AVPacket等等。AVOutputFormat结构体(位于libavformat/avformat.h中)部分源码如下:

typedef struct AVOutputFormat {

// 封装格式名称,如"mp4"

const char *name;

// 文件格式

const char *long_name;

// mime类型

const char *mime_type;

const char *extensions; /**< 逗号分隔的文件扩展名 */

/* output support */

enum AVCodecID audio_codec; /**< 默认音频codec(编解码器) */

enum AVCodecID video_codec; /**< 默认视频codec */

enum AVCodecID subtitle_codec; /**< 默认subtitle codec */

/**

* flags可取值:AVFMT_NOFILE, AVFMT_NEEDNUMBER,

* AVFMT_GLOBALHEADER, AVFMT_NOTIMESTAMPS, AVFMT_VARIABLE_FPS,

* AVFMT_NODIMENSIONS, AVFMT_NOSTREAMS, AVFMT_ALLOW_FLUSH,

* AVFMT_TS_NONSTRICT, AVFMT_TS_NEGATIVE

*/

int flags;

const struct AVCodecTag * const *codec_tag;

const AVClass *priv_class; ///< AVClass for the private context

struct AVOutputFormat *next;

// private data的大小

int priv_data_size;

// 写header

int (*write_header)(struct AVFormatContext *);

// 写一个packet。如果flags=AVFMT_ALLOW_FLUSH,pkt可为NULL,以便flush muxer中的缓冲数据

// 返回0,表示缓冲区仍还有数据可flush;返回1,表示缓冲区无可flush得数据

int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);

int (*write_trailer)(struct AVFormatContext *);

// 如果不是YUV420P,目前只支持设置像素格式

int (*interleave_packet)(struct AVFormatContext *, AVPacket *out,

AVPacket *in, int flush);

// 测试给定的编解码器是否可以存储在这个容器中

int (*query_codec)(enum AVCodecID id, int std_compliance);

void (*get_output_timestamp)(struct AVFormatContext *s, int stream,

int64_t *dts, int64_t *wall);

int (*control_message)(struct AVFormatContext *s, int type,

void *data, size_t data_size);

// 写未编码的AVFrame帧数据,详见av_write_uncoded_frame()

int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index,

AVFrame **frame, unsigned flags);

/**

* Returns device list with it properties.

* @see avdevice_list_devices() for more details.

*/

int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);

/**

* Initialize device capabilities submodule.

* @see avdevice_capabilities_create() for more details.

*/

int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);

// 释放设备功能子模块,详见avdevice_capabilities_free()

int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);

enum AVCodecID data_codec; /**< default data codec */

/**

* 初始化format. 分配数据内存,设置AVFormatContext或 AVStream参数,与deinit()配合使用,释放分配的内存资源

* 返回0,配置成功;返回1,配置失败。

*/

int (*init)(struct AVFormatContext *);

/** 释放init分配的内存资源,无论调用init()是否成功

*/

void (*deinit)(struct AVFormatContext *);

/** 检测比特流

* 如果返回0,表示需要检测流的更多packets;返回-1,则不需要

*/

int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);

} AVOutputFormat;

同样,通过调用av_register_all()函数,FFmpeg所有的复用器保存在以first_oformat为头部指针、last_oformat为尾部指针的链表中。这里以mp4(视频压缩编码格式)复用器为例,来分析AVOutputFormat结构体的初始化流程,相关源码详见libavformat/Movenc.c:

AVOutputFormat ff_mp4_muxer = {

.name = "mp4", //复用器名称

.long_name = NULL_IF_CONFIG_SMALL("MP4 (MPEG-4 Part 14)"), //mp4对应的文件格式

.mime_type = "video/mp4",// MIME类型

.extensions = "mp4", // 文件扩展名

.priv_data_size = sizeof(MOVMuxContext),

.audio_codec = AV_CODEC_ID_AAC,// 音频编码器ID

.video_codec = CONFIG_LIBX264_ENCODER ?

AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4,// 视频编码器ID

.init = mov_init, // 初始化函数

.write_header = mov_write_header, // 写入头部

.write_packet = mov_write_packet, // 写入Packet

.write_trailer = mov_write_trailer,

.deinit = mov_free, // 释放资源

.flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE,

.codec_tag = (const AVCodecTag* const []){ codec_mp4_tags, 0 },

.check_bitstream = mov_check_bitstream,

.priv_class = &mp4_muxer_class,

};

2. 输入/输出(I/O)


(1) AVIOContext结构体AVIOContext是FFmpeg管理输入输出(I/O)数据的结构体,它是协议(文件)操作的顶层结构,提供带缓冲的读写操作。有关读写操作和成员变量的含义,可见如下源码中给出的注释示意图:

  • 读取数据:

  • 写入数据:

AVIOContext结构体位于libavformat/avio.h中,部分源码如下:

typedef struct AVIOContext {

const AVClass *av_class;

unsigned char *buffer; // 数据缓冲区

int buffer_size; // 缓存的大小

unsigned char *buf_ptr; // 指针指向缓存区的当前位置,可小于buffer+buffer.size

unsigned char *buf_end; // 读取/写入缓存区数据的末尾位置

// 私有指针,关联URLContext结构,作为read/write/seek/...函数参数

// 用于完成对广义输入文件的读写等操作,指向一个URLContext对象

void *opaque;

// 读packet数据

int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);

// 写数据到packet

int (*write_packet)(void *opaque, uint8_t *buf, int buf_size);

// 定位

int64_t (*seek)(void *opaque, int64_t offset, int whence);

int64_t pos; // 当前缓存区域在文件中的位置

int eof_reached; // 是否到达文件末尾,true表示已经到末尾

int write_flag; // 是否可写标志,true表示open可写

int max_packet_size; // packet最大尺寸

unsigned long checksum;

unsigned char *checksum_ptr;

unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);

// 错误代码,0表示没有错误出现

int error;

//网络流媒体协议暂停或恢复播放

int (*read_pause)(void *opaque, int pause);

int64_t (*read_seek)(void *opaque, int stream_index,

int64_t timestamp, int flags);

// 0表示网络流不可seek

int seekable;

// 写入缓冲区中向后查找之前的最大到达位置,用于跟踪已写入的数据,以便稍后刷新

unsigned char *buf_ptr_max;

// packet最小尺寸

int min_packet_size;

// 以下字段大部分仅限libavformat内部使用或用的不多,这里不作解释

int64_t maxsize;

int direct;

int64_t bytes_read;

int seek_count;

int writeout_count;

int orig_buffer_size;

int short_seek_threshold;

const char *protocol_whitelist;

const char *protocol_blacklist;

int (*write_data_type)(void *opaque, uint8_t *buf, int buf_size,

enum AVIODataMarkerType type, int64_t time);

int ignore_boundary_point;

enum AVIODataMarkerType current_type;

int64_t last_time;

int (*short_seek_get)(void *opaque);

int64_t written;

} AVIOContext;

其中,AVIOContext的成员变量opaque指向一个URLContext对象,URLContext中是对具体资源文件进行操作的上下文,它包括一个URLProtocol结构体类型的指针变量prot。URLProtocol则是在将资源进行分类的基础上,对某一类资源操作的函数集。URLContext结构体源码如下:

typedef struct URLContext {

const AVClass *av_class;

// 关联/指向相应的广义输入文件

const struct URLProtocol *prot;

// 关联具体广义输入文件的句柄,如fd为文件句柄,socket为网络句柄

void *priv_data;

char *filename; // 指定的URL

int flags;

int max_packet_size;

int is_streamed; // true为流,默认为false

int is_connected;

AVIOInterruptCB interrupt_callback;

int64_t rw_timeout; // read/write操作超时时间

const char *protocol_whitelist;

const char *protocol_blacklist;

int min_packet_size;

} URLContext;

(2) URLProtocol结构体URLProtocol结构体表示广义的输入文件,是FFmpeg操作I/O的结构,包括文件(file)、网络数据流(tcp、rtp、... )等等,每种协议都对应着一个URLProtocol结构。该结构位于libavformat/url.h文件中,包括open、close、read、write、seek等操作,部分源码如下:

typedef struct URLProtocol {

// 协议名称

const char *name;

int (*url_open)( URLContext *h, const char *url, int flags);

int (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);

int (*url_accept)(URLContext *s, URLContext **c);

int (*url_handshake)(URLContext *c);

/**

* Read data from the protocol.

*/

int (*url_read)( URLContext *h, unsigned char *buf, int size);

int (*url_write)(URLContext *h, const unsigned char *buf, int size);

int64_t (*url_seek)( URLContext *h, int64_t pos, int whence);

int (*url_close)(URLContext *h);

int (*url_read_pause)(URLContext *h, int pause);

int64_t (*url_read_seek)(URLContext *h, int stream_index,

int64_t timestamp, int flags);

int (*url_get_file_handle)(URLContext *h);

int (*url_get_multi_file_handle)(URLContext *h, int **handles,

int *numhandles);

int (*url_get_short_seek)(URLContext *h);

int (*url_shutdown)(URLContext *h, int flags);

int priv_data_size;

const AVClass *priv_data_class;

int flags;

int (*url_check)(URLContext *h, int mask);

int (*url_open_dir)(URLContext *h);

int (*url_read_dir)(URLContext *h, AVIODirEntry **next);

int (*url_close_dir)(URLContext *h);

int (*url_delete)(URLContext *h);

int (*url_move)(URLContext *h_src, URLContext *h_dst);

const char *default_whitelist;

} URLProtocol;

接下来,这里以HTTP协议为例,阐述URLProtocol结构体的初始化流程,同时也证明了每一种协议(包括文件)相对应一个URLProtocol对象。具体源码如下,位于libavformat/Http.c:

const URLProtocol ff_http_protocol = {

.name = "http", //协议名称

.url_open2 = http_open, // open操作

.url_accept = http_accept,//accept操作

.url_handshake = http_handshake,// 握手操作

.url_read = http_read, // 读取数据操作

.url_write = http_write, // 写入数据操作

.url_seek = http_seek, // seek操作

.url_close = http_close, // close操作

.url_get_file_handle = http_get_file_handle,

.url_get_short_seek = http_get_short_seek,

.url_shutdown = http_shutdown,

.priv_data_size = sizeof(HTTPContext),

.priv_data_class = &http_context_class,

.flags = URL_PROTOCOL_FLAG_NETWORK,

.default_whitelist = "http,https,tls,rtp,tcp,udp,crypto,httpproxy"

};

3.编/解码


(1) AVCodec结构体AVCodec是与编解码器(codec)息息相关的数据结构体,它包含了与codec相关的属性参数以及编解码操作函数等,比如name为codec的名称、pix_fmts为codec的视频帧像素格式等等,每一个codec都对应着一个AVCodec结构体。 AVCodec结构体源码如下:

typedef struct AVCodec {

// 编解码器名称

const char *name;

// 描述编解码器的名称

const char *long_name;

// media type

enum AVMediaType type;

// 该codec的ID

enum AVCodecID id;

int capabilities;

// 该codec相关的参数

const AVRational *supported_framerates;

// 该codec支持的像素格式,针对视频帧/图像而言

const enum AVPixelFormat *pix_fmts;

// 该codec支持的采样率,针对音频而言

const int *supported_samplerates;

// 该codec支持的采样格式,针对音频而言

const enum AVSampleFormat *sample_fmts;

// 该codec的通道布局

const uint64_t *channel_layouts;

// 解码器支持的低分辨率的最大值

uint8_t max_lowres;

const AVClass *priv_class;

const AVProfile *profiles;

const char *wrapper_name;

int priv_data_size;

struct AVCodec *next;

int (*init_thread_copy)(AVCodecContext *);

int (*update_thread_context)(AVCodecContext *dst, const AVCodecContext *src);

const AVCodecDefault *defaults;

// 执行avcodec_register()函数被调用,

// 用于初始化codec的静态数据

void (*init_static_data)(struct AVCodec *codec);

// 初始化

int (*init)(AVCodecContext *);

int (*encode_sub)(AVCodecContext *, uint8_t *buf, int buf_size,

const struct AVSubtitle *sub);

/**

* 编码操作:将编码后的数据保存到AVPacket

*

* @param avctx codec上下文(context)

* @param avpkt 输出的AVPacket

* @param[in] frame AVFrame存储的是要被压缩编码的裸数据

* @param[out] got_packet_ptr 编码器设置为0或1,以指示avpkt中返回的非空包

* @return 0 操作成功

*/

int (*encode2)(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame,

int *got_packet_ptr);

// 解码操作

int (*decode)(AVCodecContext *, void *outdata, int *outdata_size, AVPacket *avpkt);

// 关闭codec

int (*close)(AVCodecContext *);

// Encode API with decoupled packet/frame dataflow.

int (*send_frame)(AVCodecContext *avctx, const AVFrame *frame);

int (*receive_packet)(AVCodecContext *avctx, AVPacket *avpkt);

// Decode API with decoupled packet/frame dataflow.

int (*receive_frame)(AVCodecContext *avctx, AVFrame *frame);

// flush缓冲区,执行seeking操作是被调用

void (*flush)(AVCodecContext *);

...

} AVCodec;

(2) AVCodecContext结构体也许你会发现,对于编解码而言,除了AVCodec这个非常重要的结构体,在AVCodec的成员函数中还有一个出现频率非常高的结构体,可以这么说大部分与编解码有关的函数都需要传入一个结构体参数,这个结构体就是AVCodecContext。AVCodecContext结构体存储视频流或音频流使用的编解码相关信息,比如codec_type表示编解码器的类型、codec表示采用的编解码器等等。AVCodecContext结构体源码如下:

typedef struct AVCodecContext {

enum AVMediaType codec_type; /* 编解码器的类型(视频,音频...) */

const struct AVCodec *codec;// 采用的解码器AVCodec(H.264,MPEG2...)

enum AVCodecID codec_id; /* see AV_CODEC_ID_xxx */

// 比特率(音频和视频的平均比特率)

int64_t bit_rate;

// 压缩编码的等级

int compression_level;

// 针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等)

uint8_t *extradata;

int extradata_size;

// 时基

// 根据该参数,可以把PTS转化为实际的时间(单位为秒s)

AVRational time_base;

// 图像宽、高,针对视频而言

int width, height;

// 像素格式,针对视频而言

enum AVPixelFormat pix_fmt;

// 获取像素格式

enum AVPixelFormat (*get_format)(struct AVCodecContext *s, const enum AVPixelFormat * fmt);

// 非B帧之间的最大B帧数

int max_b_frames;

// I/P帧和B帧之间的qscale因子

float b_quant_factor;

// 采样纵横比

AVRational sample_aspect_ratio;

// 音频一帧采样样本个数

int frame_size;

// 音频通道布局

uint64_t channel_layout;

// 帧率

AVRational framerate;

...

} AVCodecContext;

4.数据相关结构体

(1) AVStream结构体

AVStream结构体用于存储一个视频或音频流信息,其中,字段nb_frames表示该流包含多少帧数据、字段duration表示该流的长度、字段index标志是音频流还是视频流等等。

typedef struct AVStream {

// 标志视频流或音频流,存储在AVFormatContext中

int index; /**< stream index in AVFormatContext */

// 指向该视频/音频流的AVCodecContext

// @deprecated use the codecpar struct instead

AVCodecContext *codec;

// 时基。通过该值可以把PTS,DTS转化为真正的时间

AVRational time_base;

// 该视频/音频流的长度

int64_t duration;

// 该视频/音频流的帧数

int64_t nb_frames;

// 元数据信息

AVDictionary *metadata;

// 帧率(对视频来说很重要)

AVRational avg_frame_rate;

// 附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面

AVPacket attached_pic;

...

// 与该视频流或音频流相关的Codec参数

// 由avformat_new_stream()分配、avformat_free_context()释放

AVCodecParameters *codecpar;

} AVStream;

(2) AVPacket结构体AVPacket结构体用于存储压缩编码的视频或音频数据相关信息,其中,字段stream_index标志AVPacket所属的是音频流还是视频流。比如对于H.264来说,通常一个AVPacket的data对应着一个NAL,而一个NAL存储着一帧图像。 AVPacket结构体源码如下:

typedef struct AVPacket {

AVBufferRef *buf;

/**

* Presentation timestamp in AVStream->time_base units; the time at which

* the decompressed packet will be presented to the user.

*/

// Presentation timestamp,即显示时间戳

int64_t pts;

/**

* Decompression timestamp in AVStream->time_base units; the time at which

* the packet is decompressed.

*/

// Decompression timestamp,即解码时间戳

int64_t dts;

// 压缩编码的视频或音频数据

uint8_t *data;

// data的大小

int size;

// 标志该AVPacket所属的是音频流还是视频流

int stream_index;

int flags;

AVPacketSideData *side_data;

int side_data_elems;

/**

* Duration of this packet in AVStream->time_base units, 0 if unknown.

* Equals next_pts - this_pts in presentation order.

*/

// 该AVPacket的长度

int64_t duration;

// 该AVPacket在流中的字节位置,-1表示未知

int64_t pos;

} AVPacket;

(3) AVFrame结构体AVFrame结构体用于存储解码后的视/音频数据相关信息,表示一帧数据。如果AVFrame为视频帧数据结构体,字段data数组存储的是一帧图像、字段width、height为图像的宽、高、key_frame为是否为关键帧标志等等;如果AVFrame为音频数据结构体,字段data数组存储的是音频数据,可包含多帧音频、字段sample_rate为音频的采样率、字段channels为音频通道数量等等。 AVFrame结构体源码如下:

typedef struct AVFrame {

// 解码后的原始数据(视频-YUV或RGB;音频-PCM)

// 对于packed格式的数据(如RGB24),会存储在data[0]

// 对于plannar格式的数据(如YUV420P),Y分量存储在data[0]、U分量存储在data[1]、V分量存储在data[2]

uint8_t *data[AV_NUM_DATA_POINTERS];

// data一行数据的长度

// 注意:如果是图像不一定等于图像的宽度,往往大于图像的宽

int linesize[AV_NUM_DATA_POINTERS];

// 视频帧的宽、高

int width, height;

// 该AVFrame包含几个音频帧

int nb_samples;

// 解码后原始数据类型,比如YUV420、RGB..

// 音频,详见AVSampleFormat

// 视频,详见AVPixelFormat

int format;

// 是否为关键帧,对视频来说非常重要

// 1 -> keyframe, 0-> not

int key_frame;

// 帧类型,比如I帧、B帧、P帧...

enum AVPictureType pict_type;

// 视频帧宽高比,如16:9、4:3...

AVRational sample_aspect_ratio;

// 显示时间戳

int64_t pts;

// 编码图像帧序号

int coded_picture_number;

// 显示图像帧序号

int display_picture_number;

// 音频采样率

int sample_rate;

// 音频通道layout

uint64_t channel_layout;

// YUV颜色空间类型

enum AVColorSpace colorspace;

// 元数据

AVDictionary *metadata;

// 音频通道数量

int channels;

...

} AVFrame;

至此,FFmpeg框架中最为重要的结构体,我们基本讲解梳理完毕。最后,再借用雷神的FFmpeg关键结构体关系图作为结尾,一是使得本文能够前后呼应,二是向大神致敬!

原文 https://juejin.cn/post/7031844610574024741#heading-1

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

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

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

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

相关文章

为什么IBDP的文凭更受美国大学的青睐?

家长们可以看到&#xff0c;不管是AP还是A-LEVEL这样的课程&#xff0c;都只是单科的课程&#xff08;A-LEVEL也是英国发展出来&#xff0c;AP是针对美国大学设计的&#xff09;&#xff0c;学生是可以针对他们的强项去做选修&#xff0c;比如我的化学很强&#xff0c;那我可以…

第十节 集合

集合 什么是集合 集合就是能储存一批元素的容器。 特征&#xff1a; 集合类型可以不固定&#xff0c;大小也是可变的。 ArrayList集合 ArrayList是集合中的一种&#xff0c;它支持索引。 ArrayList集合的对象获取 public ArrayList()创建一个空的集合对象 ArrayList集合的添加…

Hive 一文读懂

Hive 简介1.1 什么是Hive1&#xff09;hive简介Hive&#xff1a;由Facebook开源用于解决海量结构化日志的数据统计。Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张表&#xff0c;并提供类SQL查询功能。2&#xff09;Hive本质&#xff1a;将…

Goframe快速创建项目,并使用Cli工具创建dao、service、logic

GoFrame项目创建与Cli工具创建1.项目创建2.Mysql数据库配置3.Cli工具dao自动生成4.业务模型须知5.Cli工具service/logic自动生成 - 接口6.Controller/Api创建1.项目创建 官网 - 项目创建-init 开发文档 - 目录介绍 官网 - 示例项目 1.gf init 项目名 &#xff08;创建项目…

无法定位程序输入点kernel32.dll,如何修复kernel32.dll

kernel32.dll是Windows操作系统中非常重要的一个系统文件&#xff0c;如果它丢失或损坏可能会导致许多应用程序无法正常运行。今天小编就来给大家详细的讲解一下无法定位程序输入点kernel32.dll&#xff0c;我们要怎么修复这个kernel32.dll缺失的问题。 一.kernel32.dll时候什么…

前端开发环境配置,浏览器跨域配置,代码提交配置git等

这是我目前公司的开发配置文档大家可以参考&#xff1a; 前端文档 1 搭建前端环境 1.1 安装nodejs 1.1.1 nodejs下载地址 https://nodejs.org/dist/v10.15.3/node-v10.15.3-x64.msi&#xff08;win64&#xff09; https://nodejs.org/dist/v10.15.3/node-v10.15.3.pkg&…

查询性能较 Trino/Presto 3-10 倍提升!Apache Doris 极速数据湖分析深度解读

从上世纪 90 年代初 Bill Inmon 在《building the Data Warehouse》一书中正式提出数据仓库这一概念&#xff0c;至今已有超过三十年的时间。在最初的概念里&#xff0c;数据仓库被定义为「一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合&#xff0c;用于支持管理…

Python排序 -- 内附蓝桥题:错误票据,奖学金

排序 ~~不定时更新&#x1f383;&#xff0c;上次更新&#xff1a;2023/02/28 &#x1f5e1;常用函数&#xff08;方法&#xff09; 1. list.sort() --> sort 是 list 的方法&#xff0c;会直接修改 list 举个栗子&#x1f330; li [2,3,1,5,4] li.sort() print(li) …

New Bing怼人、说谎、PUA,ChatGPT已经开始胡言乱语了

最近&#xff0c;来自大洋彼岸那头的ChatGPT科技浪潮席卷而来&#xff0c;微软将chatGPT整合搜索引擎Bing开启内测后&#xff0c;数百万用户蜂拥而至&#xff0c;都想试试这个「百事通」。 赶鸭子上架&#xff0c;“翻车”了&#xff1f; 但短短上线十几天&#xff0c;嵌入了…

5个开源的Java项目快速开发脚手架

概览 &#xff1a; GunspigRuoYiJeecg-bootiBase4J 一、Guns 推荐指数 &#xff1a;⭐⭐⭐⭐⭐ 简介 采用主流框架 &#xff1a; 基于 Spring Boot2.0版本开发&#xff0c;并且支持 Spring Cloud Alibaba 微服务。功能齐全 &#xff1a;包含系统管理&#xff0c;代码生成&a…

python线程池【ThreadPoolExecutor()】批量获取博客园标题数据

转载&#xff1a;蚂蚁学python 网址&#xff1a;【【2021最新版】Python 并发编程实战&#xff0c;用多线程、多进程、多协程加速程序运行】 https://www.bilibili.com/video/BV1bK411A7tV/?p8&share_sourcecopy_web&vd_sourced0ef3d08fdeef1740bab49cdb3e96467实战案…

SpringMVC 面试题

1、什么是SpringMVC&#xff1f; SpringMVC是一个基于Java的实现了MVC设计模式的“请求驱动型”的轻量级WEB框架&#xff0c;通过把model&#xff0c;view&#xff0c;controller 分离&#xff0c;将web层进行职责的解耦&#xff0c;把复杂的web应用分成逻辑清晰的几个部分&am…

Arduino-PWM调光

PWM调光实验什么是PWM&#xff1f;PWM是&#xff08;Pulse Width Modulation&#xff09;的简称&#xff0c;中文我们说这是脉冲宽度调制。脉冲宽度调制是一种模拟控制方式&#xff0c;根据相应载荷的变化来调制晶体管基极或MOS管栅极的偏置&#xff0c;来实现晶体管或MOS管导通…

react 函数式组件的hooks

目录 useState useEffect useCallback useMemo useRef useContext useReducer 自定义hooks useState 函数式组件的状态 &#xff0c;格式&#xff1a; const [value,setValue] useState( {xxxx} ) console.log([value, setValue])打印一下可以看到&#xff1a; value…

腾讯安全与锐捷网络战略合作,威胁情报能力“被集成”

2月28日&#xff0c;腾讯安全和锐捷网络在北京联合举办“威胁情报”战略合作发布会。双方发布了一款集成了腾讯安全威胁情报的新一代防火墙&#xff0c;并举办战略合作签约仪式。会上&#xff0c;锐捷网络安全产品事业部总经理项小升、腾讯安全总经理陈龙代表双方签署战略合作协…

taobao.user.avatar.get

&#xffe5;开放平台基础API不需用户授权 根据混淆nick查询用户头像 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 点击获取key和secret 请求参数 请求示例 TaobaoClient client new DefaultTaobaoClient(url, appkey,…

MyBatis中#{}和${}的区别

目录 前言 1、处理参数的方式不同 2、${}的优点 3、SQL注入问题 4、like查询问题 前言 #{}和${}都可以在MyBatis中用来动态地接收参数&#xff0c;但二者在本质上还是有很大的区别。 1、处理参数的方式不同 ${} &#xff1a;预编译处理 MyBatis在处理#{}时&#xff0c…

【Android】全局加载框的使用

项目需求 在网络加载中使用网络加载框&#xff0c;在请求数据或者其他耗时操作的时候&#xff0c;显示加载框&#xff0c;当数据返回之后或者操作完成的时候加载框取消。 效果如下&#xff1a; 需求实现 使用一个开源库【Gloading】实现 项目地址链接: Gloading 1.引入依赖…

umi 中如何使用 px2rem 插件做rem适配

umi 中如何使用 px2rem 插件做rem适配在umi的src下面创建两个文件app.ts和document.ejs下载lib-flexible和postcss-px2rem-exclude在.umirc.ts文件中进行配置和postcss-px2rem-exclude的引入在app.ts文件中引入lib-flexible如果px2rem配置无法生效&#xff0c;那么我们就要在do…

Javascript的API基本内容(五)

一、js组成 JavaScript的组成 ECMAScript: 规定了js基础语法核心知识。 比如&#xff1a;变量、分支语句、循环语句、对象等等 Web APIs : DOM 文档对象模型&#xff0c; 定义了一套操作HTML文档的API BOM 浏览器对象模型&#xff0c;定义了一套操作浏览器窗口的API 二、loc…