FFmpeg知识库

FFmpeg是一个开源免费跨平台的视频和音频流方案,采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。
最近做解码相关,需要用到FFmpeg的api。有幸拜读雷霄骅(leixiaohua1020)的专栏,以下大部分内容是阅读专栏摘录内容加上自己的整理和总结。缅怀雷神

常用API

获取库版本

方法:avcodec_version() swscale_version() avutil_version() avformat_version() swresample_version()

返回:十进制数字,如3835492

转换成版本号:3835492 -> 0x3a8664 -> 58 134 100 -> 58.134.100

参考:版本转换方法FFMPEG版本对应的库版本Library Version Macros

注册:av_register_all

注册所有的编解码器、复用/解复用组件等

1
2
3
av_register_all()

//arg和reval 均为void

注:FFmpeg4.0 以上的版本,这个函数已经被废弃。

打开文件:avformat_open_input

打开多媒体数据并且根据header获得一些相关的信息

1
2
3
4
5
6
7
8
int res = avformat_open_input(&pFormatCtx, filepath, fmt, options);

//arg1: AVFormatContext结构体
//arg2: 打开的文件名或视音频流的URL
//arg3: 强制指定AVFormatContext中AVInputFormat。默认为NULL,自动检测AVInputFormat
//agr4: 附加选项,默认为NULL

//reval: 成功返回0,错误返回负值AVERROR

需以 avformat_close_input() 方法结束

查找码流:avformat_find_stream_info

读取媒体文件的packets以获取stream信息

1
2
3
4
5
6
int res = avformat_find_stream_info(pFormatCtx, NULL);

//arg1: 输入的AVFormatContext
//arg2: 附加选项,默认为NULL

//reval: 成功返回>=0,错误返回负值AVERROR

查找解码器:avcodec_find_decoder

查找编码器

1
2
3
4
5
AVCodec *pCodec = avcodec_find_decoder(codec_id);

//arg1: 解码器的ID,可以通过pCodecCtx->codec_id 访问

//reval: 查找到的解码器(没有找到就返回NULL)

打开解码器:avcodec_open2

使用给定的编解码器初始化一个视音频编解码器的AVCodecContext

1
2
3
4
5
6
7
8
int res = avcodec_open2(pCodecCtx, pCodec, NULL);

//arg1: 需要初始化的AVCodecContext
//arg2: 输入的AVCodec
//arg3: 附加选项,默认为NULL。例如使用libx264编码的时候,“preset”,“tune”等都可以通过该参数设置

//reval: 成功返回0,错误返回负值ERROR

读取:av_read_frame

读取码流中的若干帧音频或者一帧视频

1
2
3
4
5
6
int res = av_read_frame(pFormatCtx, packet);

//arg1: 输入的AVFormatContext
//arg2: 输出的AVPacket

//reval: 成功返回0,负值代表ERROR或文件末尾

解码:avcodec_decode_video2

从AVPacket解码一帧视频数据到AVPFrame

1
2
3
4
5
6
7
8
9
int res = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);

//arg1: 输入的AVFormatContext
//arg2: 输出的AVPFrame
//arg3: 不能解码返回0,否则非0
//arg4: 输入的AVPacket

//reval: 成功返回0或使用的byte数,负值代表ERROR

缩放:sws_scale

将源图片进行缩放,存入目标图片

1
2
3
4
5
6
7
8
9
10
11
12
int res = sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, 
pFrameYUV->data, pFrameYUV->linesize);

//arg1: 通过sws_getContext()方法获得的context
//arg2: src的buffer
//arg3: src的stride
//arg4: 要处理的位置,默认为0,从头处理
//arg5: source slice 的高度
//arg6: dst的buffer
//arg7: dst的stride

//reval: 成功返回0或使用的byte数,负值代表ERROR

使用时,调用sws_getContext() 开始,调用sws_freeContext() 結束。

查找并跳转:av_seek_frame

视频跳转到指定时间戳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int res = av_seek_frame(pFormatCtx,videoindex,pos,AVSEEK_FLAG_ANY);

//arg1: 输入的AVFormatContext
//arg2: 流的index,若为-1,则使用默认流
//arg3: 时间戳
//arg4: 方向和seek模式的flag,可能同时包含以上的多个值

//flag值:
#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward
#define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes
#define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non-keyframes
#define AVSEEK_FLAG_FRAME 8 ///< seeking based on frame number

//reval: 成功返回>=0

结构体

FFmpeg 中结构体很多。最关键的结构体可以分成以下几类:

  1. 解协议(http, rtsp, rtmp, mms)
    AVIOContext,URLProtocol,URLContext 主要存储音视频使用的协议的类型以及状态。URLProtocol 存储输入音视频使用的封装格式。每种协议都对应一个 URLProtocol 结构。(注意:FFmpeg 中文件也被当做一种协议 “file”)

  2. 解封装(flv, avi, rmvb, mp4)
    AVFormatContext 主要存储音视频封装格式中包含的信息;AVInputFormat 存储输入音视频使用的封装格式。每种音视频封装格式都对应一个 AVInputFormat 结构。

  3. 解码(h264, mpeg2, aac, mp3)
    每个 AVStream 存储一个视频/音频流的相关数据;每个 AVStream 对应一个 AVCodecContext,存储该视频/音频流使用解码方式的相关数据;每个 AVCodecContext 中对应一个 AVCodec,包含该视频/音频对应的解码器。每种解码器都对应一个 AVCodec 结构。

  4. 存数据
    视频的话,每个结构一般是存一帧;音频可能有好几帧。解码前数据:AVPacket;解码后数据:AVFrame。

AVFormatContext

AVFormatContext 是存储音视频封装格式中包含的信息的结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
AVIOContext *pb:输入数据的缓存

unsigned int nb_streams:视音频流的个数

AVStream **streams:视音频流

char filename[1024]:文件名

int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000)

int bit_rate:比特率(单位bps,转换为kbps需要除以1000)

AVDictionary *metadata:元数据

...

AVFrame

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int8_t *data[AV_NUM_DATA_POINTERS]:解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM)

int linesize[AV_NUM_DATA_POINTERS]:data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。

int width, height:视频帧宽和高(1920x1080,1280x720...)

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:显示帧序号

...

AVCodecContext

AVCodecContext 是一个描述编解码器上下文的结构体,包含了众多编解码器需要的参数信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
enum AVMediaType codec_type:编解码器的类型(视频,音频...)

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

int bit_rate:平均比特率

uint8_t *extradata; int extradata_size:针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等)

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

int width, height:如果是视频的话,代表宽和高

int refs:运动估计参考帧的个数(H.264的话会有多帧,MPEG2这类的一般就没有了)

int sample_rate:采样率(音频)

int channels:声道数(音频)

enum AVSampleFormat sample_fmt:采样格式

int profile:型(H.264里面就有,其他编码标准应该也有)

int level:级(和profile差不太多)

int has_b_frames:有无b帧,对于mpeg来说若为0:IP;若为1:IPB

...

AVCodec

AVCodec 是存储编码器信息的结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const char *name:编解码器的名字,比较短

const char *long_name:编解码器的名字,全称,比较长

enum AVMediaType type:指明了类型,是视频,音频,还是字幕

enum AVCodecID id:ID,不重复

const AVRational *supported_framerates:支持的帧率(仅视频)

const enum AVPixelFormat *pix_fmts:支持的像素格式(仅视频)

const int *supported_samplerates:支持的采样率(仅音频)

const enum AVSampleFormat *sample_fmts:支持的采样格式(仅音频)

const uint64_t *channel_layouts:支持的声道数(仅音频)

int priv_data_size:私有数据的大小

...

AVStream

AVStream是存储每一个视频/音频流信息的结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int index:标识该视频/音频流

AVCodecContext *codec:指向该视频/音频流的AVCodecContext(它们是一一对应的关系)

AVRational time_base:时基。通过该值可以把PTS,DTS转化为真正的时间。FFMPEG其他结构体中也有这个字段,但是根据我的经验,只有AVStream中的time_base是可用的。PTS*time_base=真正的时间

int64_t duration:该视频/音频流长度

AVDictionary *metadata:元数据信息

AVRational avg_frame_rate:帧率(注:对视频来说,这个挺重要的)

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

...

AVPacket

AVPacket是存储压缩编码数据相关信息的结构体。

1
2
3
4
5
6
7
8
9
10
11
uint8_t *data:压缩编码的数据。

int size:data的大小

int64_t pts:显示时间戳

int64_t dts:解码时间戳

int stream_index:标识该AVPacket所属的视频/音频流。

...

AVIOContext

AVIOContext 是 FFmpeg 管理输入输出数据的结构体。

1
2
3
4
5
6
7
8
9
10
11
unsigned char *buffer; // 缓存开始位置

int buffer_size; // 缓存大小(默认32768)

unsigned char *buf_ptr; // 当前指针读取到的位置

unsigned char *buf_end; // 缓存结束的位置

void *opaque; // URLContext结构体

...

编解码器常用命令

FFmpeg 的命令行参数可以分成五个部分

1
$ ffmpeg {1} {2} -i {3} {4} {5}

分别是:全局参数、输入文件参数、输入文件、输出文件参数、输出文件

控制b帧

(仅针对x264解码器)

去除b帧

1
2
ffmpeg -i test.mp4 -vcodec libx264 -bf 0 out.mp4
ffmpeg -i test.mp4 -vcodec libx264 -x264opts "bframes=0" out.mp4

参考:ffmpeg x264 选项指南

如果希望控制I帧P帧B帧的频率和规律,可以通过控制GOP中B帧的帧数来实现,P帧的频率可以通过x264的参数b-adapt进行设置。
利用sc_threshold解决场景切换会强制插入GOP问题。
例如设置GOP中,每2个P帧之间存放3个B帧:

1
ffmpeg -i input.mp4 -c:v libx264 -x264opts "bframes=3:b-adapt=0" -g 50 -sc_threshold 0  output.mp4

缩放视频

1
2
3
4
5
#普通缩放
ffmpeg -s 720x1280 -pix_fmt yuv420p -i out.yuv -vcodec libx264 output.mp4

#设置纵横比
ffmpeg -i input.mov -vf scale=720x406,setdar=16:9 output.mp4

显示媒体流信息

ffprobe 是ffmpeg的一个工具包,主要用于探测音视频文件的各种信息

1
2
3
4
5
6
7
8
9
10
11
#查看基本信息
ffprobe test.mp4

#查看封装格式
ffprobe -show_format test.mp4

#以XML格式显示媒体流信息(选择视频流)
ffprobe -show_frames -select_streams v -of xml test.mp4

#查看视频流的数据包信息 video视频,audio音频,s字幕
ffprobe -show_packets -select_streams video test.mp4