ffmpeg解复用编解码 常用API大全给出详细中文解释

int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);

 将你给出的条目设置进入你给到的 pm 中 如果条目存在 则覆盖他 

小提示:如果AV_DICT_DONT_STRDUP_KEY宏和AV_DICT_DONT_STRDUP_VAL被设置了 这些参数会在出错时释放 

警告:添加一个全新的条目到pm会使所有已存在的条目失效 可以使用av_dict_get得到

参数 pm:一个指向AVDictionary结构体的二重指针 如果*pm为空 那么一个AVDictionay结构体会被分配然后使*pm等于他

参数 key:添加进入*pm的key值 (类似于词典 key-value的形式去设置一些参数)到底是会发生覆盖还是添加一个新key取决于你设置的第三个参数 flags

参数 value 添加进入*pm的value值 (类似于词典 key-value的形式去设置一些参数)到底是会发生覆盖还是添加一个新value 取决于你设置的第三个参数 flags 如果传递一个空值将会导致已经存在的条目被删除 

返回值: >=0表示成功 否则表示失败

首先讲这个的原因是许多ffmpeg 函数最后都能传入一个AVDictionary来做一些设置

tips:如何通过AVDictionary 设置ffmpeg各种参数 详见另一篇文章 FFMPEG Tips (5) 如何利用 AVDictionary 配置参数_Jhuster的专栏的技术博客_51CTO博客

 void av_log_set_callback(void (*callback)(void*, int, const char*, va_list));

设置超时回调 特别是网络流 经常是会超时的 这很常见 所以基本是需要设置的 这个东西

回调函数必须是线程安全的 即使你的程序运行在单线程环境下也必须是线程安全的 因为一些解码器内部工作时是多线程的 

 AVFormatContext *avformat_alloc_context(void);

 分配一个AVFormatContext结构体

avformat_free_context()可以用来释放这个结构体和任何一切AVFormatContext内分配的东西比如AVIOContext

 int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);

打开输入流并且读他的头部 编解码器此时并未打开 打开的流必须通过avformat_close_input()来关闭 通过读头的过程 解复用器就能正常工作了 以后就可以通过av_read_frame来读取AVPacket

参数 ps:指向用户刚刚通过avformat_alloc_context函数分配得来的AVFormatContext 如果用户还没通过avformat_alloc_context也没事内部会分配内存并写入ps

参数 url:打开流的URL

参数fmt:如果非空 则强制指定特定的输入格式 否则输入格式由ffmpeg内部确定

参数 options:包含了AVFormatContext和解复用器私有参数的dictionary 在返回时 这个参数会被销毁并且被替换为包含未找到选项的dictionary 也就是说如果你乱填一些ffmpeg不支持的选项 ffmpeg会直接给你返回回来  也许是空的

返回值:返回0表示成功 返回AVERROR表示失败

tips:如果你想使用自定义io输入 提前分配avio结构体 并且设置他的pb成员 关于如何自定义io输入模式 请看

ffmpeg avio 自定义读取AVPacket(aac数据)并写入文件_杀神李的博客-CSDN博客

 int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

函数内部会把整个解复用解码流程走一遍 读取数个AVPacket来拿到复用格式中流的信息 比如到底有几条流 音频流还是视频流还是字幕流 这些流是用的什么格式 比如h.264 h.265 aac 这些流适合用什么编码解码解码 

这个函数对于没有头的文件格式是非常有效的 比如MPEG

这个方法即使在MPEG-2重复帧模式下也会计算真实帧率

文件指针位置并不会因为这个函数读取了数个AVPacket而发生变化

读取过后的数个AVPacket可能会被缓冲用作之后的处理 比如av_read_frame

参数 ic:你刚刚打开的AVFormatContext

参数 options:如果非null,则为指向dictionary的指针的ic.nb_streams长数组,其中第i个成员包含对应于第i个流的编解码器选项。

返回值:>=0表示成功 AVERROR_xxx表示失败

这个方法不会保证打开所有的编解码器去读取数个AVPacket 所以在返回时 options非空是正常的

接下来 让用户以某种方式决定需要什么信息,这样ffmpeg内部就不会浪费时间去获取用户不需要的东西

 int av_find_best_stream(AVFormatContext *ic,
                        enum AVMediaType type,
                        int wanted_stream_nb,
                        int related_stream,
                        AVCodec **decoder_ret,
                        int flags);

 找到用户最希望的流的索引 因为没有索引的话 你后面通过av_read_frame读出来的AVPaket你就无法分辨到底是音频的Packet还是视频的Packet 所以必须通过这个函数去找到流索引 AVPacket里有一个字段来表明他是属于哪一个索引的

参数 ic:上文的AVFormatContext

参数 AVMediaType type:你希望找到的流的类型 比如音频视频字幕等等

参数 wanted_stream_nb:用户希望的流的索引 ffmpeg尽量满足 实在满足不了 也只能返回你不希望的 设置-1自动选择            

参数 related_stream:尝试去找到与你指定type相关的流 如果没有的话 设置-1

参数 decoder_ret:如果非空 返回你指定的流的编解码器 后面就可以直接打开了 不然的话你需要手动根据流信息去查找 通过调用avcodec_find_decoder这个函数

参数 flags:一些选项 

返回值:你指定的type的流的索引 返回AVERROR_STREAM_NOT_FOUND表示压根没找到你指定的类型 如果找到了流但是没有对应的解码器 一样是会返回AVERROR_STREAM_NOT_FOUND

tips:如果返回成功 并且decoder_ret非空 那么ffmpeg保证给你返回的decoder是有效的 是可以直接打开的

 AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

分配编解码器上下文结构体内存 然后设置为默认值 这个编解码器上下文必须通过avcodec_free_context()来释放

参数 codec:如果非空 特定于你传入的编解码器的内存被分配并且初始化 如果你传了codec 但是后面又使用avcodec_open2来打开其他编解码器是非法的 

如果空 然后特定于编解码器的默认值将不会被初始化,这可能会导致次优的默认设置,这可能会导致默认设置不理想(这主要对编码器来说很重要,例如libx264)。

返回值:充满默认值的编解码器上下文 或者空表示失败

 int avcodec_parameters_to_context(AVCodecContext *codec,
                                  const AVCodecParameters *par);

根据提供的编解码器参数的值填充编解码器上下文。在编解码器中,任何在par中有对应字段的已分配字段将被释放并替换为par中对应字段的副本。在编解码器中没有对应字段的字段将不会被修改。通过这个函数后

参数 codec:编解码器上下文

参数 par: 流中关于编解码器的信息 在上文 find_stream_info时候就已经填充了AVStream里的AVCodecParametes 所以这里可以直接传入 

返回值:>=0表示成功 或者返回AVERROR表示失败

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

初始化编解码器上下文来使用编解码器 在使用这个方法之前 确保你已经使用avcodec_alloc_context3()来分配内存了 通过以下四个函数来找到编解码器是非常简单的

avcodec_find_decoder_by_name()

avcodec_find_encoder_by_name()
 avcodec_find_decoder() 

avcodec_find_encoder()

警告:这个方法不是线程安全的

在调用这个方法之前请不要使用解码流程 如avcodec_send_packet和av_receive_frame

参数 avctx:需要初始化的编解码器上下文

参数 codec:需要打开的编解码器 如果之前你通过avcodec_alloc_context3这个函数来指定了需要打开什么编解码器 那么你就不能传入其他的编解码器

参数 options:你想要对上下文和编解码器所做的一些设置 在返回时没有找到的选项会给你返回出来 比如你乱填一些ffmpeg不支持的选项

返回值: 0表示成功 负数表示发生错误

AVFrame *av_frame_alloc(void);

 分配一个AVFrame的内存并且初始化为默认值 通过这个函数分配的内存必须通过av_frame_free()来释放掉

返回值:AVFrame

注意这个函数只会分配AVFrame本身的内存而不会去分配实际的data buffer的内存 缓冲区内存必须通过av_frame_get_buffer函数分配 或者手动分配 上一张图就懂我在说什么了 

具体实现类似于智能指针 内部会有一个引用计数去维护他 ffmpeg内存管理详见其他文章

ffmpeg解复用编解码 常用API大全给出详细中文解释_第1张图片

 


AVPacket *av_packet_alloc(void);

分配一个AVPacket的内存然后初始化为默认值 分配后的内存必须通过 av_packet_free来释放掉

返回值:AVPacket

注意这个方法和 av_frame_alloc一样只会初始化他本身的字段 而不会去初始化他的data buffer 如果你想同时初始化data buffer你应该选择 av_new_packet

 void av_init_packet(AVPacket *pkt);

初始化AVPacket的可选字段 

注意这个不会去改变 data和size字段 这两个字段需要分别初始化

int av_read_frame(AVFormatContext *s, AVPacket *pkt);

返回流中的下一个packet 他并不会保证这个AVPacket对于解码器是有效的 也就是说可能这个AVPacket你打开的编解码器根本解码不了 他会把流中的信息变成一个一个的AVPacket 你每调用一次返回一个AVPacket 并且他不会省略AVPacket中没用的信息(比如h.264中的sps pps sei等信息)来给编解码器提供最多的信息 

如果传入的pkt的buf是NULL的 那么在你下一次调用av_read_frame和直到你调用avformat_close_input的时候他都是有效的 并且是永远有效 buf不会被释放也不会被更改 

所以在你下一次调用av_read_frame的时候确保你已经使用av_packet_unref来释放掉了他的buf 

对于视频来说 一个AVPacket恰好就包含了完整的一帧数据(并且可能夹杂了一些其他的信息)关于这个还挺重要的 以后再写文章描述吧 包括AVPacket和NALU之间的关系

对于音频来说 如果他的帧长是固定的 那么有可能一个AVPacket包含了几个帧 如果他的帧长是可变的 那么一个AVPacket只包含一个音频帧 

tips:音频帧这个概念其实挺模糊的 他更像是一种规定 在发送方与接收方之间的一种约定 以后一起写个文章描述

其中 pkt的pts(显示时间戳) dts(解码时间戳) duration(建议的持续时间)这三个字段是基于AVStream中的time_base字段来计算的 如果你的视频中含有B帧 那么pkt中的pts是无效的 所以你最好是依赖dts来进行解码负荷

参数 s:上下文

参数pkt:经过你av_packet_alloc之后的pkt

返回值:0表示ok 负数表示失败

 void av_packet_free(AVPacket **pkt);

 释放packet内存 如果他拥有buf的引用计数 那么会首先释放掉他的引用计数 

AVPacket *av_packet_clone(const AVPacket *src); 

直接使用=是发生的浅拷贝 内部引用计数并不会加1 如果使用这个函数则是深拷贝 内部引用计数加1  

 int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

 给解码器提供原始的packet数据 就是把packet数据送去解码器解码 在内部,这个调用将复制相关的AVCodecContext字段,这可能会影响每个包的解码,并在包实际解码时应用它们 为什么可能会影响 举一个例子 比如

AVCodecContext.skip_frame字段 他复制过后就会去填充这个字段 这个字段指示解码器丢弃使用此函数发送的包所包含的帧。比如一个包可能包含多个音频帧 其中一些可能会丢弃

注意:在你传入的pkt的data buf字段必须是字节对齐的 不然的话就有可能会访问越界 因为一些比特流读取器是按照每一次4字节或者每一次8字节来读取的 如果字节不对齐 则有可能导致访问越界 

注意:在使用这个API的时候请不要和一些老的API混用同一个AVCodecContext 不然有可能会造成一些不可预料的结果 建议老API就不要再使用了

tips:在使用本API之前确保你已经使用avcodec_open2来打开了编解码器

参数 avctx:上下文

参数 avpkt:通常是一个视频帧或者是几个音频帧 这个函数并不会去改变你pkt的buf数据 但是他会创造一个引用计数去指向你的data buf 如果你传入的buf没有引用计数 就是说你传入了一个没有数据的packet 那么他会创造一个引用计数 不像老一代的API 这个data buf中的数据是会被完全读取的 如果AVPacket中包含了多个帧 那么在你下一次send packet之前 你是需要去调用多次avcodec_receive_frame去读取frame的而不是一次 这个参数可以是NULL 如果你传入NULL 那么就认为你传入了一个flush packet 代表已经读到了流的结尾 第一次发flush packet会成功 以后发的都返回AVERROR_EOF,如果你发送flush packet时 解码器中还有frame没有读取完成 那么这些东西会在下一次av_receive_frame的时候返回 

返回值:0表示成功 

 AVERROR(EAGAIN):现在解码器的状态不可以发送AVPacket 也就是说你还没有调用av_receive_frame去获取输出 那么你就不能再传入AVPacket 

 AVERROR_EOF:解码器已经被flush了 没有新的包可以被发送进去了 如果你发送了多个flush packet 那么也是返回这个错误

AVERROR(EINVAL):编解码器未打开 需要打开

AVERROR(ENOMEM):未知原因导致packet无法加入内部的队列 或者是一些解码错误导致的

 int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

返回解码后的视频帧或音频帧 

参数 avctx:编解码器上下文

参数 frame:内部会给你申请一个data buf并添加引用计数去装解码后的音频帧或者视频帧 如果你传入的frame拥有data buf的引用计数 那么在操作之前 会首先抹去你的引用计数

返回值:0表示成功

 AVERROR(EAGAIN):现在解码器的状态不可以接受AVFrame 也就是说你还没有调用av_send_packet去输入 那么你就不能再获取AVFrame 

 AVERROR_EOF:解码器已经被flush了 没有新的包可以被接收了 

AVERROR(EINVAL):编解码器未打开 需要打开

AVERROR_INPUT_CHANGED:当设置flag AV_CODEC_FLAG_DROPCHANGED时适用 表示当前frame的一些属性发生了变化

 

你可能感兴趣的:(音视频,c++,音视频)