FFmpeg从入门:FFmpeg框架

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结构体的基础上,阐述上述结构体的作用与区别。

3.AVFormatContext

AVFormatContext结构体描述了一个多媒体文件或流的构成和基本信息,是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

3.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;

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

AVInputFormat ff_aac_demuxer = {
    .name         = "aac",  // 指定解复用器名称
    .long_name    = NULL_IF_CONFIG_SMALL("raw ADTS AAC (Advanced Audio Coding)"),  // 指定AAC对应的文件格式
    .read_probe   = adts_aac_probe, // 探测函数
    .read_header  = adts_aac_read_header, // 读取头部数据函数
    .read_packet  = adts_aac_read_packet, // 读取数据包函数
    .flags        = AVFMT_GENERIC_INDEX,    
    .extensions   = "aac",  // 后缀
    .mime_type    = "audio/aac,audio/aacp,audio/x-aac",
    .raw_codec_id = AV_CODEC_ID_AAC,// AAC解码器ID
};

(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,
};

3.2输入/输出(I/O)

(1) AVIOContext结构体

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

读取数据:

写入数据:

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.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框架 

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
适合fresh man上手 ffmpeg 库。 目录 ------------------------- 目录 第一章 多媒体概念介绍 6 1.1 视频格式 6 1.1.1 常见格式 6 1.2 音频格式 8 1.2.1 常见格式 9 1.2.2 比较 14 1.3 字幕格式 14 1.3.1 外挂字幕与内嵌字幕的阐述 14 1.3.2 外挂字幕视频与内嵌字幕视频的画面比较 15 1.3.3 外挂字幕的三种格式 15 1.4 采集录制和播放渲染 15 1.4.1 视频采集 15 1.4.2 视频录制 16 1.4.3 视频渲染 16 1.5 编解码器 18 1.6 容器和协议 18 1.6.1 容器格式和编码格式 18 1.6.2 协议 24 1.6.2.1 视频协议 25 1.6.2.2 音频协议 25 1.6.2.3 上层通讯协议 25 1.7 常用概念介绍 26 1.7.1 硬解 26 1.7.2 IBP 帧 26 1.7.3 DTS 和PTS 30 1.7.4 分辨率 30 1.7.5 码率 30 1.7.6 帧率 30 1.7.7 RGB 和YUV 30 1.7.8 实时和非实时 30 1.7.9 复合视频和 s-video 31 1.7.10 硬件加速 31 1.7.11 FFmpeg Device 31 第二章 FFmpeg 框架 32 2.1 FFmpeg 概述 32 2.1.1 简介 32 2.1.2 功能 32 2.1.3 模块组成 33 2.1.4 命令集 33 2.2 媒体播放器三大底层框架 35 第三章 编译及简单应用 39 3.1 FFmpeg 库编译和入门介绍 41 39 3.2 流媒体数据流程讲解 40 3.3 简单应用 42 3.4 SDL( Simple Direct Layer) 45 3.4.1 SDL 显示视频 45 3.4.2 SDL 显示音频 46 3.5 ffmpeg 程序的使用(ffmpeg.exe,ffplay.exe,ffprobe.exe) 46 3.5.1 ffmpeg.exe 46 3.5.2 ffplay.exe 46 3.5.3 ffprobe.exe 46 第四章 数据结构 50 4.1 AVCodec 结构体 51 4.2 AVCodecContext 结构体 52 4.3 AVInputFormat 结构体 53 4.4 AVFormatContext 结构体 62 4.5 MovContext 结构体 63 4.6 URLProtocol 结构体 63 4.7 URLContext 结构体 64 4.8 AVIOContext 结构体(老版本为:ByteIOContext) 64 4.9 AVStream 结构体 65 4.10 MOVStreamContext 结 构体 66 4.11 AVPacket 结 构体 67 4.12 AVPacketList 结 构体 67 4.13 AVFrame 结构体 53 第五章 重要模块 68 5.1 libavutil 公共模块 68 1 文件列表 68 2 common.h 文件 68 3 bswap.h 文件 70 4 rational.h 文件 71 5 mathematics.h 文件 71 6 avutil.h 文件 72 5.2 libavcodec 编解码模块 73 1 文件列表 73 2 avcodec.h 文件 74 3 allcodec.c 文件 78 4 dsputil.h 文件 79 5 dsputil.c 文件 79 6 utils_codec.c 文件 80 7 imgconvert_template.h 文件 90 8 imgconvert.c 文件 110 9 msrle.c 文件 152 10 turespeech_data.h 文件 159 11 turespeech.c 文件 162 5.3 libavformat 容器模块 171 1 文件列表 171 2 avformat.h 文件 172 3 allformat.c 文件 177 4 cutils.c 文件 178 5 file.c 文件 179 6 avio.h 文件 182 7 avio.c 文件 184 8 aviobuf.c 文件 188 9 utils_format.c 文件 197 10 avidec.c 文件 208 5.4 libswscale 视频色彩空间转换 230 5.5 libswresample 音频重采样 230 5.6 libavfilter 音视频滤器 230 5.7 libavdevice 设备输入和输出容器 230 5.8 libpostproc 视频后期处理 230 第六章 播放器 230 6.1 视频播放器 230 6.1.1 ffmpeg 库的配置 230 6.1.2 一个简单的视频播放器 231 6.2 音频播放器 234 6.3 一个完整的播放器--ffplay 240 6.3.1 ffplay 流程图 240 6.3.2 ffplay 源码剖析 240 第七章 应用开发 262 7.1 ffmpeg 库的使用:编码 262 第八章 关键函数介绍 267 8.1 avformat_open_input 267 8.2 avcodec_register_all() 268 8.3 av_read_frame() 269 8.4 avcodec_decode_video2() 270 8.5 transcode_init() 270 8.6 transcode() 280 第九章 ffmpeg 相关工程 288 9.1 ffdshow 288 ffdshow 源代码分析 1 : 整体结构 288 ffdshow 源代码分析 2: 位图覆盖滤镜(对话框部分 Dialog) 290 ffdshow 源代码分析 3: 位图覆盖滤镜(设置部分Settings) 297 ffdshow 源代码分析 4: 位图覆盖滤镜(滤镜部分Filter) 301 ffdshow 源代码分析 5: 位图覆盖滤镜(总结) 306 ffdshow 源代码分析 6: 对解码器的 dll 的封装(libavcodec) 306 ffdshow 源代码分析 8: 视频解码器类(TvideoCodecDec) 328 ffdshow 源代码分析 9: 编解码器有关类的总结 335 9.2 LAV filters 340 LAV Filter 源代码分析 1: 总体结构 340 LAV Filter 源代码分析 2: LAV Splitter 341 LAV Filter 源代码分析 3: LAV Video (1) 364 LAV Filter 源代码分析 4: LAV Video (2) 382 9.3 MPlayer 408 9.3.1 Mplayer 支持的格式 408 9.3.2 Mplayer 中头文件的功能分析 408 9.3.3 MPlayer.main 主流程简要说明 408 9.3.4 Mplayer 源码分析 409 第十章 开发实例 416 第十一章 mp4 文件封装协议分析 416 11.1 概述 416 11.2 mp4 的物理结构 416 11.3 数据的组织结构 417 11.4 mp4 的时间结构 417 11.5 文件结构分析 418 11.5.1 File Type Box(ftyp) 418 11.5.2 Movie Box(moov) 418 第十二章 flv 文件格式分析 437 12.1 概述 437 12.2 文件总体结构 437 12.3 文件结构分析 438 12.3.1 flv 文件头的结构 438 12.3.2 body 主体结构 439 附录A:常见问题 444 1 ffmpeg 从内存中读取数据 444 2 MFC 中使用SDL 播放音频没有声音的解决方法 444 附录B:经典代码示例 445 附录 c:ffmpeg 参数中文详细解释 456 附录D:ffplay 的快捷键以及选项 458 附录E: ffmpeg 处理 rtmp 流媒体 459
FFmpeg 是一个开源的跨平台音视频处理工具,它包含了非常多的音视频编解码器、过滤器和工具,能够完成许多音视频处理任务。下面是 FFmpeg 的从入门到精通的学习路径: ## 入门篇 ### 1. 安装 FFmpeg FFmpeg 的官方网站是 https://ffmpeg.org/,你可以在这个网站上下载对应平台的 FFmpeg 安装包,也可以通过包管理器安装。 ### 2. 基本命令行使用 学习 FFmpeg 的第一步是掌握它的基本命令行使用方法。以下是一些常用的命令: - 查看 FFmpeg 版本信息: ``` ffmpeg -version ``` - 将一个视频文件转换为另一个格式: ``` ffmpeg -i input.mp4 output.avi ``` - 对视频进行裁剪: ``` ffmpeg -i input.mp4 -ss 00:01:00 -t 00:00:30 output.mp4 ``` - 对视频进行压缩: ``` ffmpeg -i input.mp4 -b:v 1M -b:a 128k output.mp4 ``` ### 3. 媒体格式和编解码器 FFmpeg 支持的媒体格式和编解码器非常多,学习 FFmpeg 的第二步是了解这些媒体格式和编解码器的基本知识。 - 媒体格式:FFmpeg 支持的媒体格式包括 AVI、MP4、FLV、MKV、MOV 等,其中 MP4 是最常见的一种格式。 - 编解码器:FFmpeg 支持的编解码器包括 H.264、HEVC、VP8、VP9、AAC、MP3 等,其中 H.264 是最常见的一种编解码器。 ### 4. FFmpeg 过滤器 FFmpeg 过滤器是 FFmpeg 中非常强大的一部分,可以实现各种音视频处理任务,例如裁剪、剪辑、旋转、调整音量等。 以下是一些常用的过滤器: - 翻转视频: ``` ffmpeg -i input.mp4 -vf "hflip" output.mp4 ``` - 裁剪视频: ``` ffmpeg -i input.mp4 -vf "crop=640:480:0:0" output.mp4 ``` - 调整音量: ``` ffmpeg -i input.mp3 -af "volume=2" output.mp3 ``` ## 进阶篇 ### 1. FFmpeg 的 API 如果你想在自己的程序中使用 FFmpeg,你需要了解它的 API。FFmpeg 提供了 C 语言的 API,可以让你在自己的程序中使用 FFmpeg 的各种功能。 以下是一些常用的 API: - AVFormatContext:用于打开媒体文件并获取媒体文件的基本信息。 - AVCodecContext:用于初始化编解码器并设置编解码器参数。 - AVPacket 和 AVFrame:用于处理音视频数据。 ### 2. FFmpeg 的源码结构 如果你想深入了解 FFmpeg,你需要了解它的源码结构。FFmpeg 的源码结构比较复杂,但是如果你掌握了它的源码结构,你就能更好地理解 FFmpeg 的运行机制。 以下是 FFmpeg 的一些核心模块: - libavutil:提供了 FFmpeg 的核心功能,包括数据类型、内存管理、工具函数等。 - libavcodec:提供了音视频编解码器的实现。 - libavformat:提供了媒体文件格式的实现。 - libswscale:提供了视频像素格式转换的实现。 ### 3. 自定义 FFmpeg 过滤器 如果你想进一步扩展 FFmpeg 的功能,你可以编写自己的 FFmpeg 过滤器。FFmpeg 过滤器是基于 C 语言编写的,可以实现各种自定义的音视频处理任务。 以下是 FFmpeg 自定义过滤器的一些步骤: - 编写过滤器的 C 代码。 - 在 FFmpeg 的 configure 文件中添加过滤器的编译选项。 - 在 FFmpeg 的命令行中使用自定义过滤器。 ## 精通篇 ### 1. FFmpeg 的优化 如果你想让 FFmpeg 的性能更好,你需要了解一些 FFmpeg 的优化技巧。以下是一些常用的优化技巧: - 使用硬件加速:FFmpeg 支持使用硬件加速来加速编解码器的运行。 - 使用多线程:FFmpeg 支持多线程编解码,可以更好地利用多核 CPU。 - 使用 SIMD 指令:FFmpeg 支持使用 SIMD 指令来加速一些计算密集型任务。 ### 2. FFmpeg 的扩展 如果你想让 FFmpeg 支持更多的功能,你可以使用 FFmpeg 的扩展。以下是一些常用的 FFmpeg 扩展: - FFmpeg-filters:提供了许多额外的过滤器,例如去噪、锐化、模糊等。 - FFmpeg-libs:提供了许多额外的代码库,例如 libavresample、libswresample 等。 - FFmpeg-tools:提供了许多额外的工具,例如 ffprobe、ffplay 等。 ## 总结 FFmpeg 是一个强大的音视频处理工具,学习 FFmpeg 的过程需要掌握一些基本的命令行使用、媒体格式和编解码器、过滤器等知识。如果想深入了解 FFmpeg,可以学习 FFmpeg 的 API、源码结构、自定义过滤器等知识,并且要了解一些优化技巧和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值