江苏省网站建设_网站建设公司_JavaScript_seo优化
2025/12/18 21:55:14 网站建设 项目流程

[[N_FFmpeg]]

一些重要的结构体

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

  1. 解协议(http,rtsp,rtmp,mms)

AVIOContext, URLProtocol, URLContext 主要存储视音频使用的协议的类型以及状态; URLProtocol存储输入视音频使用的封装格式; 每种协议都对应一个 URLProtocol 结构; (注意: FFMPEG中文件也被当做一种协议"file")

  1. 解封装(flv,avi,rmvb,mp4)

AVFormatContext 主要存储视音频封装格式中包含的信息; AVInputFormat存储输入视音频使用的封装格式; 每种视音频封装格式都对应一个AVInputFormat 结构;

  1. 解码(h264,mpeg2,aac,mp3)

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

  1. 存数据

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

参考雷神博客-FFMPEG中最关键的结构体之间的关系
ffmpeg库函数介绍
FFmpeg 学习整理总结

AVFormatContext

可以理解为整个统领上下文的大结构体, 包含: 多个AVStream(streams), AVCodec, AVCodecContext

对应封装格式例如 (mp3/mp4/avi...)
格式转换过程中实现输入和输出功能, 保存相关数据的主要结构, 描述了一个媒体文件或媒体流的构成和基本信息

nb_streams/streams: AVStream结构指针数组, 包含了所有内嵌媒体流的描述, 其内部有 AVInputFormat + AVOutputFormat 结构体, 来表示输入输出的文件格式
avformat_open_input: 创建并初始化部分值, 但其他一些值(如 mux_rate, key 等)需要手工设置初始值, 否则可能出现异常
avformat_alloc_output_context2: 根据文件的输出格式, 扩展名或文件名等分配合适的 AVFormatContext 结构

获取

AVFormatContext fmt_ctx = nullptr;
avformat_open_input(&fmt_ctx, "filename", nullptr, nullptr);//打开一个输入
//然后使用 读取
if((avformat_find_stream_info(avFormatCtx,nullptr))!=0){//reaad error
}//或者
AVFormatContext *avformat_alloc_context(void);

每一个 AVFormatContext 都有AVStream **streams;属性; 而nb_streams属性对应着索引

销毁

avformat_free_context()
API doc

pd 属性 (内存读写数据)

参考下 #ffmpeg内存编解码

AVStream

可理解为 "流通道", 例如视频一般会有两个, 音频流和视频流, 保存着解码方式的相关数据

AVStream -- 描述一个媒体流, 其大部分信息可通过 avformat_open_input 根据文件头信息确定, 其他信息可通过 avformat_find_stream_info 获取, 典型的有 视频流, 中英文音频流, 中英文字幕流(Subtitle), 可通过 av_new_stream, avformat_new_stream 等创建;

index: 在AVFormatContext中流的索引, 其值自动生成(AVFormatContext::streams[index])
nb_frames: 流内的帧数目
time_base: 流的时间基准, 是一个实数, 该流中媒体数据的pts和dts都将以这个时间基准为粒度; 通常, 使用av_rescale/av_rescale_q可以实现不同时间基准的转换
avformat_find_stream_info: 获取必要的编解码器参数(如 AVMediaType, CodecID ), 设置到 AVFormatContext::streams[i]::codec 中
av_read_frame: 从多媒体文件或多媒体流中读取媒体数据, 获取的数据由 AVPacket 来存放
av_seek_frame: 改变媒体文件的读写指针来实现对媒体文件的随机访问, 通常支持基于时间, 文件偏移, 帧号(AVSEEK_FLAG_FRAME)的随机访问方式

每个 AVStream, 下面都至少有一个可用的 AVCodec; 用属性名, video_codec,audio_codec,

获取

依赖于 AVCodecContext 或 AVCodecParameters 获取

而AVStream 对应一个 AVCodecContext(新版建议使用 AVCodecParameters), 存储该视频/音频流使用解码方式的相关数据

//必须先读取AVFormatContext
if((avformat_find_stream_info(avFormatCtx,nullptr))!=0){//reaad error
}
//流通道 一般两个; 音频/视频
for(unsigned int i=0; i<avFormatCtx->nb_streams; i++) {if(avFormatCtx->streams[i]->codecpar->codec_type==AVMediaType::AVMEDIA_TYPE_VIDEO){this->videoIndex =i;}else if(avFormatCtx->streams[i]->codecpar->codec_type==AVMediaType::AVMEDIA_TYPE_AUDIO){this->audioIndex =i}
}
//或者 avformat_new_stream 函数直接分配AVStream *out_stream = avformat_new_stream(avFormatCtx, coder);

销毁

AVStream的初始化函数是avformat_new_stream(), 销毁函数使用销毁 AVFormatContext 的 avformat_free_context() 就可以了;

建议使用 AVCodecParameters 代替 AVCodecContext

AVCodecContext 结构体在AVStream被声明为已过时, 使用另一个 AVCodecParameters 结构体代替;

注意只是在AVStream被声明为已过时! 但是某些地方该用还是得用..

解码例子


const char *filename = "a.mp4";
AVFormatContext fmt_ctx = nullptr;
avformat_open_input(&fmt_ctx, filename, nullptr, nullptr);//打开一个输入
avformat_find_stream_info(fmt_ctx, nullptr);//读取封装格式
for(size_t i = 0; i < fmt_ctx->nb_streams; ++i)//流通道
{AVStream *stream = fmt_ctx->streams[i];AVCodec *codec =  avcodec_find_decoder(stream->codecpar->codec_id);//依据ID找的解编码器AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);//需ctx中间过渡;  使用avcodec_free_context释放//包含了大部分解码器相关的信息, 这里是直接从AVCodecParameters复制到AVCodecContextavcodec_parameters_to_context(codec_ctx, stream->codecpar);av_codec_set_pkt_timebase(codec_ctx, stream->time_base);avcodec_open2(codec_ctx, codec, nullptr);
}

编码例子

const char *filename = "b.mp4";
AVFormatContext *fmt_ctx = nullptr;
avformat_alloc_output_context2(&fmt_ctx, nullptr, nullptr, filename); //需要调用avformat_free_context释放//new一个流并挂到fmt_ctx名下, 调用avformat_free_context时会释放该流
AVStream *stream = avformat_new_stream(fmt_ctx, nullptr);
AVCodec *codec = avcodec_find_encoder(fmt_ctx->oformat->video_codec);//音频为audio_codec
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);codec_ctx->video_type = AVMEDIA_TYPE_VIDEO;
codec_ctx->codec_id = m_fmt_ctx->oformat->video_codec;codec_ctx->width = 1280;//你想要的宽度
codec_ctx->height = 720;//你想要的高度
codec_ctx->format = AV_PIX_FMT_YUV420P;//受codec->pix_fmts数组限制codec_ctx->gop_size = 12;codec_ctx->time_base = AVRational{1, 25};//应该根据帧率设置
codec_ctx->bit_rate = 1400 * 1000;avcodec_open2(codec_ctx, codec, nullptr);
//将AVCodecContext的成员复制到AVCodecParameters结构体; 必须先open 再 copy
avcodec_parameters_from_context(stream->codecpar, codec_ctx);av_stream_set_r_frame_rate(stream, {1, 25});

用 AVCodecParameters 代替AVCodecContext

AVCodecContext

可以理解为 编解码器的(上下文或参数) 在AVStream中, 新API建议使用 AVCodecParameters 代替它!

解码器上下文,统领编码器基本结构体

AVFormatContext -- 格式转换过程中实现输入和输出功能, 保存相关数据的主要结构, 描述了一个媒体文件或媒体流的构成和基本信息

nb_streams/streams: AVStream结构指针数组, 包含了所有内嵌媒体流的描述, 其内部有 AVInputFormat + AVOutputFormat 结构体, 来表示输入输出的文件格式
avformat_open_input: 创建并初始化部分值, 但其他一些值(如 mux_rate, key 等)需要手工设置初始值, 否则可能出现异常
avformat_alloc_output_context2: 根据文件的输出格式, 扩展名或文件名等分配合适的 AVFormatContext 结构

avCodecContext.width(1080);
avCodecContext.height(1920);
avCodecContext.bit_rate(90000);//平均码率
//		avCodecContext.profile(FF_PROFILE_H264_HIGH_422);//profile-level-id=000042
avCodecContext.pix_fmt( AV_PIX_FMT_BGR24);//设置颜色格式//下面挑一些关键的变量来看看(这里只考虑解码); 
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差不太多)

参考 - 雷神博客

获取


//依据编码器获得
avCodecContext = avcodec_alloc_context3(avCodec);
//或者
avCodecContext = avcodec_alloc_context3(nullptr); //传nullptr 先分配一个空的
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoIndex]->codecpar);// 在通过AVStream 解析获得
//或者 如果情况允许 最好通过AVStream复制
AVStream *in_stream = ifmt_ctx->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);//根据输入流创建输出流
avcodec_copy_context(out_stream->codec, in_stream->codec);//复制 ctx

AVCodecParamteres

可以理解为 编解码器的(上下文或参数)

AVCodecParamteres 结构体是将AVCodecContext中编解码器参数抽取出而形成的新的结构体, 在新版本中的FFMPEG中, 有些结构体中的AVCodecContext已经被弃用, 取而代之的就是AVCodecParameters这个参数, 该结构体定义在libavcodec/codec_par.h文件中

enum AVMediaType codec_type;// 编解码器的类型 
const struct AVCodec *codec;// 编解码器,初始化后不可更改
enum AVCodecID codec_id;// 编解码器的id
int64_t bit_rate;// 平均比特率
uint8_t *extradata;// int extradata_size;// 针对特定编码器包含的附加信息
AVRational time_base;// 根据该参数可以将pts转化为实践
int width, height;// 每一帧的宽和高
int gop_size;// 一组图片的数量, 编码时用户设置, 解码时不使用
enum AVPixelFormat pix_fmt;// 像素格式, 编码时用户设置, 解码时可由用户指定, 但是在分析数据会被覆盖用户的设置
int refs;// 参考帧的数量
enum AVColorSpace colorspace;// YUV色彩空间类型
enum AVColorRange color_range;// MPEG JPEG YUV范围
int sample_rate;// 采样率 仅音频
int channels;// 声道数(音频)
enum AVSampleFormat sample_fmt;// //采样格式
int frame_size;// 每个音频帧中每个声道的采样数量
int profile;//配置类型
int level;//级别

分配
avcodec_parameters_alloc()

AVCodec

对应编码方式

每一个编码方式对应一个该结构体, 例如(H264, H265 ..等)

获取

codec_id 有哪些可以参考: AVCodecID 枚举
avCodec = avcodec_find_decoder(codec_id);

AVPacket

对应未解码的原始数据, 对视频(Video)来说, AVPacket通常包含一个压缩的Frame; 而音频(Audio)则有可能包含多个压缩的Frame;
AVPacket -- 暂存解码之前的媒体数据(一个音/视频帧, 一个字幕包等)及附加信息(解码时间戳, 显示时间戳, 时长等), 主要用于建立缓冲区并装载数据;

在 AVPacket 结构体中, 重要的变量:

data/size/pos: 数据缓冲区指针, 长度和媒体流中的字节偏移量
flags: 标志域的组合, 1(AV_PKT_FLAG_KEY)表示该数据是一个关键帧, 2(AV_PKT_FLAG_CORRUPT)表示该数据已经损坏
destruct: 释放数据缓冲区的函数指针, 其值可为 [av_destruct_packet]/av_destruct_packet_nofree, 会被 av_free_packet 调用

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所属的视频/音频流; 
flats// 标志, 其中最低为1表示该数据是一个关键帧// 判断是否I帧 if (pkt->flags & AV_PKT_FLAG_KEY) // is keyframe
duration//时长, 以所属媒体流的时间基准为单位

https://blog.csdn.net/leixiaohua1020/article/details/14215755

FFmpeg 5.0+ 开始,av_init_packet() 已经被 弃用(deprecated),在 FFmpeg 6.0 及以上版本 甚至直接 移除 了。
官方推荐改用新的 av_packet_alloc() / av_packet_unref() / av_packet_free() 接口。

AVPacket *pkt = av_packet_alloc();
if (!pkt) {LOGE("Could not allocate AVPacket");return;
}while (ctx->running && av_read_frame(fmt_ctx, pkt) >= 0) {if (pkt->stream_index == videoIndex) {}av_packet_unref(pkt);
}av_packet_free(&pkt);

获取

//分配
av_packet_alloc();//擦除数据
void av_packet_unref (AVPacket *pkt)//释放结构体
void av_packet_free (AVPacket**pkt)

API 文档

参考官方文档:

AVFrame

  • This structure describes decoded (raw) audio or video data.
  • AVFrame must be allocated using av_frame_alloc(). Note that this only
  • allocates the AVFrame itself, the buffers for the data must be managed
  • through other means (see below).
  • AVFrame must be freed with av_frame_free().

struct AVFrame

  • 存放从AVPacket中解码后的原始数据, 其必须通过av_frame_alloc()来创建, 通过av_frame_free()来释放;
  • 和AVPacket类似, AVFrame中也有一块数据缓存空间, 在调用av_frame_alloc的时候并不会为这块缓存区域分配空间,
  • 需要使用其他的方法;
  • 在解码的过程使用了两个AVFrame, 这两个AVFrame分配缓存空间的方法也不相同
  • AVFrame 通常分配一次,然后重复使用多次, 不同的数据(如一个AVFrame持有来自解码器的frames; )
  • 在再次使用时,av_frame_unref()将自由持有的任何之前的帧引用并重置它变成初始态;
  • 使用 av_frame_unref(packet);重置数据 和 av_frame_free(packet);来释放

data/linesize: FFMpeg内部以平面的方式存储原始图像数据, 即将图像像素分为多个平面(R/G/B或Y/U/V)数组
data 数组: 其中的指针指向各个像素平面的起始位置, 编码时需要用户设置数据
linesize数组: 存放各个存贮各个平面的缓冲区的行宽, 编码时需要用户设置数据
key_frame: 该图像是否是关键帧, 由 libavcodec 设置
pict_type: 该图像的编码类型: Intra(1)/Predicted(2)/Bi-dir(3) 等 (就是I帧/P帧/B帧), 默认值是 NONE(0), 其值由libavcodec设置
pts: 呈现时间, 编码时由用户设置
quality: 从1(最好)到FF_LAMBDA_MAX(256*128-1,最差), 编码时用户设置, 默认值是0
nterlaced_frame: 表明是否是隔行扫描的,编码时用户指定, 默认0

//分配
av_frame_alloc();

//擦除数据
av_frame_unref(pFrame);

//释放结构体
av_frame_free()

图像处理 行宽(linesize/步长(stride)/间距(pitch)

自定义编码时,AVFrame::data需要手动初始化分配内存, 这些参数很关键

cpu都是32位或者64位的cpu, 他们一次最少读取4, 8个字节, 如果少于这些, 反而要做一些额外的工作, 会花更长的时间; 所有会有一个概念叫做内存对齐, 将结构体的长度设为4, 8的倍数;

间距就是指图像中的一行图像数据所占的存储空间的长度, 它是一个大于等于图像宽度的内存对齐的长度; 这样每次以行为基准读取数据的时候就能内存对齐, 虽然可能会有一点内存浪费, 但是在内存充裕的今天已经无所谓了;

所以如果图像的宽度如果是内存对齐长度的整数倍, 那么间距就会等于宽度, 而现在的cpu通常一次读取都是4个字节, 而我们通常见到的分辨率都是4的整数倍, 所以我们通常发现间距和图像的宽度一样(通常rgb32格式或者以通道表示的yuv420p格式的y通道);
在用ffmpeg进行图像格式转换的时候, 需要传入一个参数 stride, 其实也是间距; 只不过这次不需要复杂的处理, 只需要知道传入ffmpeg进行转换的图像数据使用的间距, 然后传入就行, ffmpeg会自动根据这个值进行相应的处理;

图像处理, 显示中的行宽

给 AVFrame data分配内存

新APIavpicture_get_size->av_image_get_buffer_size, avpicture_fill>av_image_fill_arrays 就是增加间距以内存对齐?

//旧api
numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height,4);//新api
av_image_fill_arrays//更简单的
AVFrame * convert_frame= av_frame_alloc();
av_image_alloc(convert_frame->data, convert_frame->linesize, t_ctx->codecCtx->width, t_ctx->codecCtx->height,AV_PIX_FMT_YUV420P, 4);/**The following fields must be set on frame before calling this function:
(也行, 需设置)
format (pixel format for video, sample format for audio)
width and height for video
nb_samples and channel_layout for audio
*/
int av_frame_get_buffer	(	AVFrame * 	frame, int 	align  )	

注意: av_free() 并没有释放AVFrame中data[x]指向的 数据, 仅仅是把data本身指向的数据释放了, 但其作为二级指针指向的数据跳过了, 需要手动释放, 添加 av_free(AVFrame->data[0])后问题解决;

AVDictionary

封装的一个类似Map的结构体, 主要在配置选项是使用.

注意一下, 这个使用完也要释放?
av_dict_free(&dict);

关于有哪些配置项可以参考:官方文档 Options, 以及各编解码的 Options

关于怎么用可以参考: ffmpeg.exe源码

主要结构体的分配与销毁 表

结构体 初始化 销毁
AVFormatContext avformat_alloc_context() avformat_free_context()
AVIOContext avio_alloc_context()
AVStream avformat_new_stream()
AVCodecContext avcodec_alloc_context3() avcodec_close(vCodecCtx);
AVFrame av_frame_alloc(); 填充数据: av_image_fill_arrays(); av_frame_free()
AVPacket av_init_packet(); av_new_packet() av_free_packet已过时, av_packet_unref取代之

小坑指南

AVDictionary

AVDictionary 不管成功失败都要释放av_dict_free(&options);

AVDictionary* options = nullptr;//一些 option
av_dict_set(&options, "rtsp_transport", "tcp", 0);//over tcp
// av_dict_set(&options, "buffer_size", "102400", 0); //设置缓存大小, 1080p可将值调大
av_dict_set(&options, "stimeout", "3000000", 0); //设置socket 超时断开连接时间, 单位微秒
av_dict_free(&options);//坑!
if((ret=avformat_open_input(&inAvFormatCtx,rtspUrl,nullptr,&options)) != 0){
}

av_write_trailer

//注意即使无限直播, 结束时也要写尾, 不然还有私有数据没有被释放, 有文件则会占用文件锁!
ret = av_write_trailer(outAvFormatCtx);

内存的释放

总之有调用 alloc 相关函数名的, 就一定要调用free 释放之, 参考 FFmpeg.exe 工具的源码:

static void ffmpeg_cleanup(int ret)
{int i, j;if (do_benchmark) {int maxrss = getmaxrss() / 1024;av_log(NULL, AV_LOG_INFO, "bench: maxrss=%ikB\n", maxrss);}for (i = 0; i < nb_filtergraphs; i++) {FilterGraph *fg = filtergraphs[i];avfilter_graph_free(&fg->graph);for (j = 0; j < fg->nb_inputs; j++) {InputFilter *ifilter = fg->inputs[j];struct InputStream *ist = ifilter->ist;while (av_fifo_size(ifilter->frame_queue)) {AVFrame *frame;av_fifo_generic_read(ifilter->frame_queue, &frame,sizeof(frame), NULL);av_frame_free(&frame);}av_fifo_freep(&ifilter->frame_queue);if (ist->sub2video.sub_queue) {while (av_fifo_size(ist->sub2video.sub_queue)) {AVSubtitle sub;av_fifo_generic_read(ist->sub2video.sub_queue,&sub, sizeof(sub), NULL);avsubtitle_free(&sub);}av_fifo_freep(&ist->sub2video.sub_queue);}av_buffer_unref(&ifilter->hw_frames_ctx);av_freep(&ifilter->name);av_freep(&fg->inputs[j]);}av_freep(&fg->inputs);for (j = 0; j < fg->nb_outputs; j++) {OutputFilter *ofilter = fg->outputs[j];avfilter_inout_free(&ofilter->out_tmp);av_freep(&ofilter->name);av_freep(&ofilter->formats);av_freep(&ofilter->channel_layouts);av_freep(&ofilter->sample_rates);av_freep(&fg->outputs[j]);}av_freep(&fg->outputs);av_freep(&fg->graph_desc);av_freep(&filtergraphs[i]);}av_freep(&filtergraphs);av_freep(&subtitle_out);/* close files */for (i = 0; i < nb_output_files; i++) {OutputFile *of = output_files[i];AVFormatContext *s;if (!of)continue;s = of->ctx;if (s && s->oformat && !(s->oformat->flags & AVFMT_NOFILE))avio_closep(&s->pb);avformat_free_context(s);av_dict_free(&of->opts);av_freep(&output_files[i]);}for (i = 0; i < nb_output_streams; i++) {OutputStream *ost = output_streams[i];if (!ost)continue;av_bsf_free(&ost->bsf_ctx);av_frame_free(&ost->filtered_frame);av_frame_free(&ost->last_frame);av_dict_free(&ost->encoder_opts);av_freep(&ost->forced_keyframes);av_expr_free(ost->forced_keyframes_pexpr);av_freep(&ost->avfilter);av_freep(&ost->logfile_prefix);av_freep(&ost->audio_channels_map);ost->audio_channels_mapped = 0;av_dict_free(&ost->sws_dict);av_dict_free(&ost->swr_opts);avcodec_free_context(&ost->enc_ctx);avcodec_parameters_free(&ost->ref_par);if (ost->muxing_queue) {while (av_fifo_size(ost->muxing_queue)) {AVPacket pkt;av_fifo_generic_read(ost->muxing_queue, &pkt, sizeof(pkt), NULL);av_packet_unref(&pkt);}av_fifo_freep(&ost->muxing_queue);}av_freep(&output_streams[i]);}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询