记录FFmpeg的相关结构体与函数。
libavcodec: 提供了一系列编码器的实现
libavformat: 实现在流协议,容器格式及基本IO访问
libavutil: 包含了hash器,解码器和各种函数
libavfilter: 提供了各种音视频过滤器
libavdevice: 提供了访问捕获设备和回放设备的接口
libswresample: 实现了混音和重采样
libswscale: 实现了色彩转换和缩放功能
结构体
AVFormatContext 格式上下文
是一个贯穿全局的数据结构,此结构包含一个视频流的格式内容。其中存有AVInputFormat(或AVOutputFormat)、AVStream、AVPacket等数据结构以及其它的相关信息。
AVFormatContext *formatContext = nullptr;
// 格式上下文 初始化
formatContext = avformat_alloc_context(); // 堆区创建
AVFormatContext | 描述 |
---|---|
formatContext->nb_streams | 流的个数 |
formatContext->streams[i] | 获取媒体流(视频 、 音频) - AVStream |
formatContext->duration | 获取Stream总时长 |
AVInputFormat 输入文件容器格式
一个文件容器格式对应一个AVInputFormat 结构,在程序运行时有多个实例。
AVStream 媒体流(视频、音频)
存储每一个视频/音频流信息的结构体。
AVStream | 描述 |
---|---|
stream->codecpar | 从AVStream 获取 编码解码的参数 - AVCodecParameters |
AVCodecParameters 编码解码的参数
AVCodecParameters | 描述 |
---|---|
parameters->codec_id | 获取编解码器ID - AVCodecID |
parameters->codec_type | 从编解码器参数中,获取流的类型(音频 或 视频)AVMediaType::AVMEDIA_TYPE_AUDIO、AVMediaType::AVMEDIA_TYPE_VIDEO |
AVCodecContext 编解码器上下文
描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息,如果是单纯地使用libavcodec,这部分信息需要调用者进行初始化;如果使用整个FFmpeg库,这部分信息在调用av_open_input_file 和 av_find_stream_info的过程中会根据文件的头部信息及媒体流内头部信息完成初始化。
AVCodecContext | 描述 |
---|---|
codecContext->width | 视频的宽 |
codecContext->height | 视频的高 |
codecContext->pix_fmt | 视频的像素格式 |
AVPacket 压缩数据包
FFmpeg用AVPacket来存放编码后的视频帧数据,AVPacket保存解复用只有、解码之前的数据(仍然是压缩之后的数据) 和关于这些数据的一些附加信息,如显示时间戳(PTS)、解码时间戳(DTS)、数据时长、所在媒体流的索引等。
对于视频(Video)来说,AVPacket通常包含一个压缩的帧,而音频(Audio)则有可能包含多个压缩的帧。
一个Packet有可能是空的,不包含任何压缩数据,只含有side data(即附加信息)。
对于多个Packet共享一个缓存空间,FFmpeg使用了引用计数(reference-count)机制。当有新的Packet引用共享的缓存空间时,就将引用计数+1,当释放引用共享空间的Packet时,就将引用计数-1,当引用计数为0时,释放引用的缓存空间。
AVPacket中的AVBuffer *buf用来管理引用计数。
AVPacket通常将Demuxer导出的数据包作为解码器的输入数据,或是收到来自编解码器的数据包,通过Muxer进入输出数据。
AVPacket | 描述 |
---|---|
packet->stream_index | 获取压缩数据包中的流的下标,可能是视频的,或者音频,或者字幕的 |
AVCodec 编解码器
是存储编解码器信息的结构体。
SwsContext 格式转换上下文
SwrContext 重采样上下文
AVRational 时间基
AVFrame 音视频数据结构体
一般用于存储原始数据(非压缩数据,如对于视频来说是YUV、RGB,对于音频是PCM)。
AVFrame存放从AVPacket中解码出来的原始数据,其必须通过av_frame_alloc来创建,通过av_frame_free释放。
AVFrame有一块数据缓存空间,在调用av_frame_alloc的时候并不会为这块缓存区域分配空间,需要使用其他的方法。在解码的过程中使用了两个AVFrame,这两个AVFrame分配缓存空间的方法并不相同。
一个AVFrame用来存放从AVPacket中解码出来的原始数据,这个AVFrame的数据缓存空间通过调用avcodec_decode_video来分配和填充。
另一个AVFrame用来存放将解码出来的原始数据变换为需要的数据格式(如RGB、RGBA)的数据,这个AVFrame需要手动分配数据缓存空间。
AVFrame | 描述 |
---|---|
frame->best_effort_timestamp | 当前的时间戳 |
stream->time_base | 获取到时间基 |
stream->avg_frame_rate | 获取fps,需要使用av_q2d转换 |
frame->best_effort_timestamp | 视频延迟多少 |
函数
avformat_alloc_context 初始化格式上下文AVFormatContext
给AVFormatContext分配内存空间。
AVFormatContext *avformat_alloc_context(void);
使用:
AVFormatContext * formatContext = nullptr;
formatContext = avformat_alloc_context();
avformat_open_input 打开媒体文件
打开媒体文件,读取header,不涉及打开解码器
/***
* AVFormatContext **ps:格式上下文
* const char *url:文件地址
* ff_const59 AVInputFormat *fmt:输入格式,如麦克风、摄像头,Android中用不到
* AVDictionary **options:各种设置,如:Http连接超时等,它是个二级指针
* @return 0 on success, a negative AVERROR on failure. 返回0就是成功了
*/
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);
使用:
// 格式上下文初始化
AVFormatContext * formatContext = nullptr;
formatContext = avformat_alloc_context(); // 堆区创建
// 字典初始化
AVDictionary * dictionary = nullptr;
av_dict_set(&dictionary, "timeout", "5000000", 0); // 单位微秒
// 打开文件
int r = avformat_open_input(&formatContext, data_source, nullptr, &dictionary);
// 释放字典
av_dict_free(&dictionary);
if (r) {
LOGE("avformat_open_input error =====================");
return;
}
avformat_close_input 关闭媒体文件
void avformat_close_input(AVFormatContext **s);
avformat_find_stream_info 查找媒体中的音视频流的信息
查找媒体中的音视频流的信息
/***
* 查找媒体中的音视频流的信息
* @return >=0 if OK, AVERROR_xxx on error
*/
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
使用:
r = avformat_find_stream_info(formatContext, nullptr); // >=0 if OK
if (r < 0){
LOGE("avformat_find_stream_info error =====================");
return;
}
avcodec_find_decoder 获取编解码器
根据 编解码器ID 查找编解码器
AVCodec *avcodec_find_decoder(enum AVCodecID id);
avcodec_alloc_context3 获取编解码器上下文
/***
* 获取 编解码器上下文
* @return An AVCodecContext filled with default values or NULL on failure.
*/
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
使用:
codecContext = avcodec_alloc_context3(codec);
if (!codecContext) {
LOGE("avcodec_alloc_context3 error =====================");
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
return;
}
avcodec_parameters_to_context 把 AVCodecParameters 复制给 AVCodecContext
/***
* 把 AVCodecParameters 复制给 AVCodecContext
* @return >= 0 on success, a negative AVERROR code on failure.
*/
int avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *par);
使用:
r = avcodec_parameters_to_context(codecContext, parameters);
if (r < 0) {
LOGE("avcodec_parameters_to_context error =====================");
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
return;
}
avcodec_open2 打开解码器
/***
* 打开解码器
* @return zero on success, a negative value on error
*/
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
使用:
r = avcodec_open2(codecContext, codec, nullptr);
if (r) {
LOGE("avcodec_open2 error =====================");
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
return;
}
av_packet_alloc 初始化压缩包
// 压缩包 可能是音频,或者视频
AVPacket *packet = av_packet_alloc();
av_read_frame 读取音频包/视频包
读取码流中的若干音频帧 或 1帧视频。
/***
* 从格式上下文中读取音频包/视频包
* AVFormatContext *s: 格式上下文
* AVPacket *pkt: 压缩包
* @return 0 if OK, < 0 on error or end of file
*/
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
使用:
int ret = av_read_frame(formatContext, packet);
if (!ret) {
// TODO 成功
} else if (ret == AVERROR_EOF) { // 读到文件末尾
// TODO 文件结尾
} else {
break; // 错误
}
av_packet_free 释放数据包
void av_packet_free(AVPacket **pkt);
avcodec_send_packet 发送压缩包给缓冲区
/***
* 发送压缩包给缓冲区
* AVCodecContext *avctx: 编解码器上下文
* AVPacket *avpkt: 压缩数据包
* @return 0 on success, otherwise negative error code
*/
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
使用:
ret = avcodec_send_packet(codecContext, pkt);
// av_packet_free(&pkt); // FFmpeg源码缓存一份pkt,可以释放
if (ret) {
break; // 失败
}
av_frame_alloc 初始化AVFrame
AVFrame *frame = av_frame_alloc();
av_frame_free 释放AVFrame
void av_frame_free(AVFrame **frame);
avcodec_receive_frame 从缓冲区拿到原始包
/***
* 从缓冲区拿到原始包
* AVCodecContext *avctx: 编解码器上下文
* AVFrame *frame: 原始包AVFrame
* @return
0: success, a frame was returned
AVERROR(EAGAIN): output is not available in this state - user must try to send new input
*/
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
使用:
ret = avcodec_receive_frame(codecContext, frame);
if (ret == AVERROR(EAGAIN)) {
continue; // B帧 参考后面的帧失败等
} else if (ret != 0) {
if (frame) {
av_frame_free(&frame);
}
break; // 错误
}
avcodec_free_context 释放AVCodecContext
void avcodec_free_context(AVCodecContext **avctx);
av_packet_unref 释放AVPacket成员指向的堆空间
void av_packet_unref(AVPacket *pkt);
sws_getContext 初始化格式转换上下文SwsContext
/**
* 格式转换上下文
* @return a pointer to an allocated context, or NULL in case of error
*/
struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags, SwsFilter *srcFilter,
SwsFilter *dstFilter, const double *param);
使用:
SwsContext *sws_ctx = sws_getContext(
// 输入
codecContext->width, // 视频的宽
codecContext->height, // 视频的高
codecContext->pix_fmt, // 视频的像素格式
// 输出
codecContext->width,
codecContext->height,
AV_PIX_FMT_RGBA,
SWS_BILINEAR,
NULL, NULL, NULL
);
sws_scale 格式转换 yuv --> rgba
/***
* 格式转换 yuv --> rgba
*/
int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
const int srcStride[], int srcSliceY, int srcSliceH,
uint8_t *const dst[], const int dstStride[]);
使用:
uint8_t *dst_data[4]; // RGBA
int dst_linesize[4]; // RGBA
// 给dst_data申请内存
av_image_alloc(dst_data, dst_linesize, codecContext->width, codecContext->height, AV_PIX_FMT_RGBA, 1);
sws_scale(sws_ctx,
// 输入
frame->data,
frame->linesize,
0,
codecContext->height,
// 输出
dst_data,
dst_linesize);
sws_freeContext 释放格式转换上下文SwsContext
void sws_freeContext(struct SwsContext *swsContext);
swr_alloc_set_opts 分配SwrContext并设置/重置公共参数
struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
int64_t in_ch_layout, enum AVSampleFormat in_sample_fmt, int in_sample_rate,
int log_offset, void *log_ctx);
swr_init 初始化重采样上下文SwrContext
int swr_init(struct SwrContext *s);
swr_convert 每个通道输出的样本数
int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
const uint8_t **in , int in_count);
swr_free 释放重采样上下文
void swr_free(struct SwrContext **s);
av_get_channel_layout_nb_channels 返回通道布局中的通道数。
av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
av_get_bytes_per_sample 返回每个样本的字节数。
av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
av_err2str 转换成错误详情
int r = avformat_open_input(&formatContext, data_source, nullptr, &dictionary);
if (r) {
char * errorInfo = av_err2str(r);
LOGE("FFMPEG_CAN_NOT_OPEN_URL errorInfo: %s \n", errorInfo);
}
av_rescale_rnd 获取单通道的样本数
int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const;
使用:
int dst_nb_samples = av_rescale_rnd(
swr_get_delay(swr_ctx, frame->sample_rate) + frame->nb_samples,
out_sample_rate,
frame->sample_rate,
AV_ROUND_UP);
av_q2d 转换AVRational 为 double
// 获取fps
AVRational fps_rational = stream->avg_frame_rate;
int fps = av_q2d(fps_rational);
av_seek_frame 跳转到指定时间
/**
* AVFormatContext *s:
* int stream_index: 为-1时,ffmpeg自动选择音频或视频
* int64_t timestamp: 时间戳
* int flags: seek的模式,如:AVSEEK_FLAG_FRAME 找关键帧;
* AVSEEK_FLAG_BACKWARD 找附近的关键帧,找不到花屏了
* AVSEEK_FLAG_ANY 精确跳转,花屏
*/
int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags);
使用:
av_seek_frame(formatContext, -1, progress * AV_TIME_BASE, AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD);
参考:《FFmpeg从入门到精通》、《Android音视频开发》