FFmpeg中那个包含众多属性的关键结构体叫什么?

摘要:[[N_FFmpeg]] 一些重要的结构体 FFMPEG中结构体很多; 最关键的结构体可以分成以下几类: 解协议(http,rtsp,rtmp,mms) AVIOContext, URLProtocol, URLContext 主要存储视音
[[N_FFmpeg]] 一些重要的结构体 FFMPEG中结构体很多; 最关键的结构体可以分成以下几类: 解协议(http,rtsp,rtmp,mms) AVIOContext, URLProtocol, URLContext 主要存储视音频使用的协议的类型以及状态; URLProtocol存储输入视音频使用的封装格式; 每种协议都对应一个 URLProtocol 结构; (注意: FFMPEG中文件也被当做一种协议"file") 解封装(flv,avi,rmvb,mp4) AVFormatContext 主要存储视音频封装格式中包含的信息; AVInputFormat存储输入视音频使用的封装格式; 每种视音频封装格式都对应一个AVInputFormat 结构; 解码(h264,mpeg2,aac,mp3) 每个AVStream存储一个视频/音频流的相关数据;每个AVStream对应一个AVCodecContext, 存储该视频/音频流使用解码方式的相关数据;每个AVCodecContext中对应一个AVCodec, 包含该视频/音频对应的解码器; 每种解码器都对应一个AVCodec结构; 存数据 视频的话, 每个结构一般是存一帧; 音频可能有好几帧 解码前数据: 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复制到AVCodecContext avcodec_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]); }