API处理套路:
- 方法一般返回值小于0表示失败。
- 使用上下文连接多个API。
- 上下文包含大量相关信息。
- 上下文一般对应的创建与释放方法,且注释里有说明,例如:open-close、alloc-free。
- 要复用结构体时,调用对应的unref方法,以重置信息。
- 所有压缩包、未压缩帧操作都要循环操作。
日志系统
1 |
|
流
获取流信息
获取流信息基本是基于AVFormatContext
进行获取的。
1 | // 检查是否支持格式 |
基本流操作框架
- 创建并打开输入上下文。
avformat_open_input
- 检查输入格式。
avformat_find_stream_info
、av_dump_format
- 根据路径创建输出上下文。
avformat_alloc_output_context2
- 创建流,因为不参与编解码过程,所以拷贝编解码参数。`
avformat_new_stream
、avcodec_parameters_copy
、out_stream->codecpar->codec_tag = 0
- 检查输出格式。
av_dump_format
- 打开输出IO。
avio_open
- 写入头部。
avformat_write_header
- ==从输入流中读取并写入输出数据包。==
- 写入尾部。
av_write_trailer
- 释放上面创建的上下文。
avformat_close_input
、avio_close
、avformat_free_context
流的操作按照FFmpeg的API流程大多只需要更改高亮的步骤,其余的基本是固定流程。数据包读取与写入的过程一般是这样的:
- 创建数据包结构体。
av_packet_alloc
- 循环读取帧,进入帧处理。
av_read_frame
- 处理数据包细节:
pkt.pts
、pkt.dts
、pkt.duration
、pkt.pos
- 写入。
av_interleaved_write_frame
- 减少数据包引用。
av_packet_unref
- 处理数据包细节:
- 释放数据包结构体。
av_packet_unref
要点:
AVFormatContext
从输入URL得出格式信息:
- 建立输入格式上下文。
avformat_open_input
- 检查格式是否支持。
avformat_find_stream_info
、av_dump_format
从输出URL得出格式信息:
- 从输出URL猜测得出。
avformat_alloc_output_context2
AVStream
输入流信息是输入格式上下文的信息,且是完整的:
- 找到指定格式的流索引:
av_find_best_stream
- 直接取出:
fmt_ctx->streams[audio_idx]
- 外加异步格式检查:
assert_condition(in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO, "媒体类型不匹配");
输出流信息则是要自己创建的:
- 根据输出格式上下文创建输出流:
avformat_new_stream
- 从输入流拷贝编解码参数到输出流:
avcodec_parameters_copy
AVIOContext
存在输出格式上下文中,只需要在写入前后开启和关闭即可。avio_open
、avio_close
。
AVPacket
- 从输入格式上下文读取数据包:
av_read_frame
- 交错写入数据包到输出格式上下文:
av_interleaved_write_frame
应用:导出音频流/视频流
可以用两种方式:
- 读取数据包,写入数据包到文件,补充文件头(这里存在要自己实现文件头的逻辑)。
- 走FFmpeg整个流程。
在以上的基本流程上做修改:
- 获取指定媒体类型,并获取输入流。
av_find_best_stream
-> stream_id- in_stream =
fmt_ctx->streams[stream_idx]
- 循环读取帧,只处理stream_idx匹配的数据包。
应用:时间裁剪
- 读取数据包之前进行跳转,并获取跳转后pts、dts。
av_seek_frame
- 读取数据包时:
- 减去起始的pts、dts。
- 不处理结束时间之后的数据包。
av_q2d(in_stream->time_base) * pts <= end_time
编解码
编码
基本步骤:
- 打开编码器。
avcodec_find_encoder_by_name
- 设置编码参数。须手动设置,因为没有参照的来源。
- 打开编码器。
avcodec_open2
- 编码。
avcodec_encode_video2
具体步骤:
- 查询编码器,并创建编码上下文。
avcodec_find_encoder_by_name
、avcodec_alloc_context3
- 设置编码参数。
- 打开编码器。
avcodec_open2
- 创建文件、AVFrame并把编码上下文的参数设置到AVFrame中。
- AVFrame分配缓冲区空间。
av_frame_get_buffer
- 编码并写入数据。
- 发送帧。
avcodec_send_frame
- 循环接收数据包,并写入文件。
avcodec_send_frame
、fwrite
- 发送帧。
- 编码空的AVFrame以刷新编码器。
- 按需写入结束码。
- 关闭文件、释放相关资源。
编码细节:
1 | static void encode_to_file(AVCodecContext* enc_ctx, |
注意:这里是直接把编码后的数据包数据直接写到文件中,并没有写头尾。即没有使用格式上下文的AVIOContext。
要点:
AVCodec
通过id和名称查找,后续通过AVCodecContext进行管理。avcodec_find_encoder_by_name
AVCodecContext
- 通过AVCodec创建:
avcodec_alloc_context3
- 重点在于格式配置,都设置到该上下文中。
- 打开后才能使用编解码器:
avcodec_open2
- 使用完毕后释放:
avcodec_free_context
编码,两层循环:
- 给编码器上下文塞帧:
avcodec_send_frame
; - 循环从编码器上下文获取数据包:
avcodec_receive_packet
AVFrame
- 创建与释放:
av_frame_alloc
、av_frame_free
- 若是自己填充数据,则要先从codec上下文获取格式设置:
pix_fmt
、width
、height
- 填充数据前要分配空间:
av_frame_get_buffer
- 填充数据前要确认帧是否可写入:
av_frame_make_writable
设置了格式决定了分配空间的大小以及后续填充数据的方式。
解码
基本步骤:
- 查找解码器。
avcodec_find_decoder
- 从输入拷贝相关解码参数。
avcodec_parameters_to_context
- 打开解码器。
avcodec_open2
- 解码。
avcodec_decode_video2
具体步骤:
- 打开文件。
avformat_open_input
- 检查格式,获取视频流。
avformat_find_stream_info
、av_dump_format
、av_find_best_stream
- 根据读取的流信息查询编解码器并创建对应上下文。
avcodec_find_decoder
、avcodec_alloc_context3
- 从读取的流信息中拷贝相关的编解码器参数。
avcodec_parameters_to_context
- 打开编解码器。
avcodec_open2
- 若要转换图像格式,则创建
SwsContext
。 - 循环读取帧。
av_read_frame
- 循环解码。
- 发送数据包。
avcodec_send_packet
- 循环获得解码帧。
avcodec_receive_frame
- 发送数据包。
解码细节:
1 | static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt, |
与编码不同,解码的参数是由之前编码的决定的,所以这里直接把读取的流中拷贝编码器参数。