ffmpeg 滤波器
AVFilterContext
一个滤波器实例,即使是同一个滤波器,但是在进行实际的滤波时,也会由于输入的参数不同而有不同的滤波效果。 AVFilterContext就是在实际进行滤波时用于维护滤波相关信息的实体。
AVFilterLink
滤波器链,作用主要是用于连接相邻的两个AVFilterContext。 为了实现一个滤波过程,可能会需要多个滤波器协同完成,即一个滤波器的输出可能会是另一个滤波器的输入。 AVFilterLink的作用是串联两个相邻的滤波器实例,形成两个滤波器之间的通道。
AVFilterPad
滤波器的输入输出端口,一个滤波器可以有多个输入以及多个输出端口,相邻滤波器之间是通过AVFilterLink来串联的。 而位于AVFilterLink两端的分别就是前一个滤波器的输出端口以及后一个滤波器的输入端口。
buffersrc
一个特殊的滤波器,这个滤波器的作用就是充当整个滤波过程的入口,通过调用该滤波器提供的函数(如av_buffersrc_add_frame)可以把需要滤波的帧传输进入滤波过程。 在创建该滤波器实例的时候需要提供一些关于所输入的帧的格式的必要参数(如:time_base、图像的宽高、图像像素格式等)。
buffersink
一个特殊的滤波器,这个滤波器的作用就是充当整个滤波过程的出口,通过调用该滤波器提供的函数(如av_buffersink_get_frame)可以提取出被滤波过程滤波完成后的帧。
使用方式
avfilter_register_all():注册所有AVFilter; avfilter_graph_alloc():创建AVFilterGraph ; avfilter_graph_create_filter():创建并向AVFilterGraph中添加一个AVFilter;
链接滤波器
avfilter_graph_config():检查AVFilterGraph的配置,并使其生效;
通过av_buffersrc_add_frame()向src中放入一个AVFrame,通过av_buffersink_get_frame()从sink中取出一个处理后AVFrame。
一、音频
const char *filter_descr = "atempo=2.0,aformat=sample_fmts=s16:channel_layouts=stereo"; static int init_filters(const char *filters_descr, AudioState *audio) {
int ret = 0;
char args[512];
AVFilter *abuffersrc = avfilter_get_by_name("abuffer");
AVFilter *abuffersink = avfilter_get_by_name("abuffersink");
AVFilterInOut *outputs = avfilter_inout_alloc();
AVFilterInOut *inputs = avfilter_inout_alloc();
static const enum AVSampleFormat out_sample_fmts[] = { AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_NONE };
static const int64_t out_channel_layouts[] = { AV_CH_LAYOUT_STEREO, -1 };
static const int out_sample_rates[] = { audio->audio_ctx->sample_rate, -1 };
AVRational time_base = audio->stream->time_base;
AVCodecContext *dec_ctx = audio->audio_ctx;
do {
audio->filter_graph = avfilter_graph_alloc();
if (!outputs || !inputs || !audio->filter_graph) {
ret = AVERROR(ENOMEM);
break;
}
/* buffer audio source: the decoded frames from the decoder will be inserted here. */
if (!dec_ctx->channel_layout)
dec_ctx->channel_layout = av_get_default_channel_layout(dec_ctx->channels);
snprintf(args, sizeof(args),
"time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%x",
time_base.num, time_base.den, dec_ctx->sample_rate,
av_get_sample_fmt_name(dec_ctx->sample_fmt), dec_ctx->channel_layout);
ret = avfilter_graph_create_filter(&audio->buffersrc_ctx, abuffersrc, "in",
args, NULL, audio->filter_graph);
if (ret < 0) {
printf("Cannot create audio buffer source\n");
break;
}
/* buffer audio sink: to terminate the filter chain. */
ret = avfilter_graph_create_filter(&audio->buffersink_ctx, abuffersink, "out",
NULL, NULL, audio->filter_graph);
if (ret < 0) {
printf("Cannot create audio buffer sink\n");
break;
}
ret = av_opt_set_int_list(audio->buffersink_ctx, "sample_fmts", out_sample_fmts, -1,
AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
printf( "Cannot set output sample format\n");
break;
}
ret = av_opt_set_int_list(audio->buffersink_ctx, "channel_layouts", out_channel_layouts, -1,
AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
printf( "Cannot set output channel layout\n");
break;
}
ret = av_opt_set_int_list(audio->buffersink_ctx, "sample_rates", out_sample_rates, -1,
AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
printf( "Cannot set output sample rate\n");
break;
}
outputs->name = av_strdup("in");
outputs->filter_ctx = audio->buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = audio->buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
if ((ret = avfilter_graph_parse_ptr(audio->filter_graph, filters_descr,
&inputs, &outputs, NULL)) < 0)
break;
if ((ret = avfilter_graph_config(audio->filter_graph, NULL)) < 0)
break;
} while(0);
avfilter_inout_free(&outputs);
avfilter_inout_free(&inputs);
return ret;
}
static int audio_decode_frame(AudioState *audio_state, uint8_t *audio_buf)
{
......省略
int ret = avcodec_send_packet(audio_state->audio_ctx, &pkt);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
{
av_frame_free(&frame);
return -1;
}
while(avcodec_receive_frame(audio_state->audio_ctx, frame) >= 0)
{
/* push the audio data from decoded frame into the filtergraph */
if (av_buffersrc_add_frame_flags(audio_state->buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0)
{
av_log(NULL, AV_LOG_ERROR, "Error while feeding the audio filtergraph\n");
break;
}
/* pull filtered audio from the filtergraph */
while (1)
{
ret = av_buffersink_get_frame(audio_state->buffersink_ctx, filt_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
if (ret < 0)
{
av_frame_free(&frame);
av_frame_free(&filt_frame);
return -1;
}
、、、(其他代码)
}
av_frame_unref(frame);
}
av_frame_free(&frame);
av_frame_free(&filt_frame);
return data_size;
}
二、视频倍速
const char *filter_descr = "setpts=0.5*PTS,scale=720:480";
static int init_filters(const char *filters_descr, VideoState *video) {、
char args[512];
int ret = 0;
AVFilter *buffersrc = avfilter_get_by_name("buffer");
AVFilter *buffersink = avfilter_get_by_name("buffersink");
AVFilterInOut *outputs = avfilter_inout_alloc();
AVFilterInOut *inputs = avfilter_inout_alloc();
AVRational time_base = video->stream->time_base;
enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_RGB32, AV_PIX_FMT_NONE };
AVCodecContext *dec_ctx = video->video_ctx;
video->filter_graph = avfilter_graph_alloc();
do
{
if (!outputs || !inputs || !video->filter_graph)
{
ret = AVERROR(ENOMEM);
break;
}
/* buffer video source: the decoded frames from the decoder will be inserted here. */
snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
time_base.num, time_base.den,
dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);
ret = avfilter_graph_create_filter(&video->buffersrc_ctx, buffersrc, "in",
args, NULL, video->filter_graph);
if (ret < 0)
{
printf("Cannot create buffer source\n");
break;
}
/* buffer video sink: to terminate the filter chain. */
ret = avfilter_graph_create_filter(&video->buffersink_ctx, buffersink, "out",
NULL, NULL, video->filter_graph);
if (ret < 0)
{
printf( "Cannot create buffer sink\n");
break;
}
ret = av_opt_set_int_list(video->buffersink_ctx, "pix_fmts", pix_fmts,
AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
printf( "Cannot set output pixel format\n");
break;
}
outputs->name = av_strdup("in");
outputs->filter_ctx = video->buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = video->buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
if ((ret = avfilter_graph_parse_ptr(video->filter_graph, filters_descr,
&inputs, &outputs, NULL)) < 0)
break;
if ((ret = avfilter_graph_config(video->filter_graph, NULL)) < 0)
break;
}
while(0);
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
return ret;
}
static int vdecode_thread(void *arg)
{
VideoState *video = (VideoState *)arg;
AVFrame *frame = av_frame_alloc();
AVFrame *filt_frame = av_frame_alloc();
AVPacket packet;
double pts;
while (!quit)
{
....省略
int ret = avcodec_send_packet(video->video_ctx, &packet);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
{
continue;
}
while(avcodec_receive_frame(video->video_ctx, frame) >= 0)
{
...省略
/* push the decoded frame into the filtergraph */
if (av_buffersrc_add_frame_flags(video->buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0)
{
printf("Error while feeding the filtergraph\n");
break;
}
/* pull filtered frames from the filtergraph */
while (1)
{
ret = av_buffersink_get_frame(video->buffersink_ctx, filt_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
else if (ret < 0)
{
printf("Error while av_buffersink_get_frame\n");
break;
}
......省略
av_frame_unref(frame);
}
usleep(10);
}
av_frame_free(&frame);
av_frame_free(&filt_frame);
return 0;
}
}
ffmpeg常见错误
解析NAL失败
1.问题描述:
nal_unit_type: 1, nal_ref_idc: 3 non-existing PPS 0 referenced decode_slice_header error no frame!
2.出现该问题的原因有两个
1.初始化解码器的时候extdata内容为空,导致解析SPS/PPS失败,这种情况下,如果收到一个不是I帧的数据包,解码出错提示如上。 2.收到的I帧,没有包含SPS/PPS,无法初始化解码器。
3.解决方案
解码时就开始报错了:
non-existing PPS 0 referenced decode_slice_header error non-existing PPS 0 referenced decode_slice_header error no frame!
报这个错的原因是发送的H264 AVPacket里面没有包含SPS(Sequence Parameter Sets)和PPS(Picture Parameter Set)。 在H264中,SPS和PPS存在于NALU header中,而在MP4容器中,SPS和PPS存在于AVCDecoderConfigurationRecord中。 MP4容器中的H264 AVPacket不包含SPS和PPS,这些信息被放置到了容器头部里面,所有解码器缺少这些参数无法解码。
有资料都提到了使用"h264_mp4toannexb"这个filter来处理AVPacket,可以使它重新带上SPS和PPS。 于是在发送端的代码里面,在发送之前用"h264_mp4toannexb"对AVPacket进行了一次处理,但处理时总是报错:
Packet header is not contained in global extradata, corrupted stream or invalid MP4/AVCC bitstream
有些资料提到SPS和PPS信息存在于AVCodecContext的extradata字段中, 于是尝试着把AVCodecContext的extradata直接复制到了发送出去的AVPacket的字节流前面,这个时候接收端不报解码错误了。 接收端又报出了这个错误:
concealing 655 DC, 655 AC, 655 MV errors in P frame
这个错误大意是P帧也就是关键帧有错误… 因此又做了无用功。
几乎要放弃的时候,看着之前的错误信息:“Packet header is not contained in global extradata”, 想起前面的AVCodecContext不都设置过一个GLOBAL HEADER吗?!
pCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
那把这行代码去掉会怎么样呢?
去掉之后,果然,接收端解码全部成功了! 只是发送端会报个warning,容器格式要求使用Global Header。但流的AVCodecContext上未设置Global Header, 但生成的视频完全不受影响仍然可以正常播放!
解决方法
1、 获取相应的比特流过滤器
FLV/MP4/MKV等结构中,h264需要h264_mp4toannexb处理。添加SPS/PPS等信息。
FLV封装时,可以把多个NALU放在一个VIDEO TAG中,结构为4B NALU长度 + NALU1 + 4B NALU长度 + NALU2+…, 需要做的处理把4B长度换成00000001或者000001
annexb模式: startcode 00000001 AVCC模式: 无startcode (mp4 flv mkv)
const AVBitStreamFilter *bsfilter = av_bsf_get_by_name(“h264_mp4toannexb”); AVBSFContext *bsf_ctx = NULL;
2、初始化过滤器上下文
av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;
3、添加解码器属性
void setParme() {
avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
av_bsf_init(bsf_ctx);
}
4、发送和接受
// bitstreamfilter内部去维护内存空间
if (av_bsf_send_packet(bsf_ctx, pkt) != 0) {
// 把资源释放掉
av_packet_unref(pkt);
// 继续送
continue;
}
// 释放资源
av_packet_unref(pkt);
// 接收数据
while(av_bsf_receive_packet(bsf_ctx, pkt) == 0) {
out_pkt_count++;
//printf("fwrite size:%d\n", pkt->size);
av_packet_unref(pkt);
}
丢帧
1.问题描述
用 libavcodec 内核的播放器(比如 mpv/IINA,VLC)播放RTMP直播视频,报出多条 “h264: co located POCs unavailable” 错误。
错误原因是视频流丢帧导致解码 B 帧时找不到对应的 co-located 图像。
Co-located
B 帧 Direct 预测模式有两种,Temporal 和 Spatial (时间/空间), SBTV 的 H.264 流只用了后者。
Co-located 图像指的是 B 帧在 direct 模式下向后参考图像列表的第一帧, 即refList[1][0]。
一般认为这一帧的运动特征是和当前帧最接近的。
Spatial 模式下宏块的运动向量通常由同一帧内的几个邻块推算出,但是为了处理邻块运动而当前块静止的情况,
需要先访问 co-located 图像中的对应宏块来判断当前块是否静止,如果静止则直接把运动向量赋值为0。
POC 是 Picture Order Count(图像序列号)的缩写, H.264 中有 POC 和 FrameNum 两个概念
FrameNum:解码顺序, 对于 IDR 帧 FrameNum 重置为0,其他帧 FrameNum 是上一参考帧的值加1。
POC:播放顺序。IDR 帧 POC = 0。POC 有三种计算方式,SBTV 只用了 Type 0,表示 POC 的 LSB 部分(pic_order_cnt_lsb)显式在 slice header 中发送。
POC 的计算对于 temporal direct 模式比较重要,见 H.264 标准 Figure 8-2。在 spatial direct 模式下 POC 只是在计算 H264SliceContext.col_parity 的时候用到。
这是一个和场编码相关的概念,但是 SBTV 的编码器只用了帧编码,也就是说我们计算 B 帧的运动向量实际上并不需要 POC 的值。
“POCs unavailable” 更重要的意义是指示 co-located 图像不可用。
2.错误原因
当播放端输出 “co located POCs unavailable” 错误日志的时候有两种情况:
刚刚连接上RTMP流。这种情况一般是正常的,播放端可能一开始就收到 B 帧,该帧所参考的 co-located 帧并没有发送,所以提示无效。
有些 RTMP 服务器会重传参考帧,但是通常不会重传整个 GOP,尤其是 SBTV 这种 IDR 间隔很长的情况。
在拉流的过程中间出现。假设编码器没有问题,就说明视频流在转发过程中有丢帧现象。
为什么 RTMP 也会丢帧
RTMP 基于 TCP,在传输过程中不会丢包。但是因为 RTMP 不支持变码率,当带宽不够的时候,服务器的发送队列将不断增长,
只能选择丢弃队列中时间戳较早或包含非参考帧的 RTMP 包。
如何知道出现了丢帧
正常情况下,H.264 每一帧的 frame_num 字段应该是上一参考帧加1(IDR 的时候重置0)。
如果我们发现 frame_num 增量超过1,就说明有丢帧发生。
这种情况在 H.264 标准里也有说明(对于 SBTV 的编码器 gaps_in_frame_num_value_allowed_flag 总是为0):
可以用 h264bitstream 这个工具输出 frame_num:
curl http://$STREAM_ADDR.flv -o $MOVIE.flv # HTTP-FLV
ffmpeg -i $MOVIE.flv -c copy -bsf:v -an h264_mp4toannexb $VIDEO.264
$H264BITSTREAM/h264_analyze -v 1 $VIDEO.264 | rg ‘frame_num:’
SBTV 的一个示例输出:
…
1.1: sh->frame_num: 11
1.1: sh->frame_num: 11
2.7: sh->frame_num: 12
1.1: sh->frame_num: 15 // <– 丢帧
1.1: sh->frame_num: 15
1.1: sh->frame_num: 15
1.1: sh->frame_num: 16
1.1: sh->frame_num: 16
…
FFmpeg 在解码过程中如果发现 FrameNum 不连续,会输出 DEBUG 信息 “Frame num gap curr_frame_num prev_frame_num”,
然后执行 Error Concealment 逻辑,把前一帧复制一份代替丢失的帧,
h264_slice.c:h264_field_start, 但是该帧的 POC(严格的说是 field_poc 字段)还是初始值,
所以在执行到 direct 预测的时候就会报错。
示例日志
以下日志由 ffmpeg -loglevel trace -debug mmco -i $VIDEO -f null /dev/null 输出(删除了无用或者有误导性的部分)。
================================
h264_decode_frame
nal_unit_type: 9, nal_ref_idc: 0 // AUD
nal_unit_type: 7, nal_ref_idc: 3 // SPS
nal_unit_type: 8, nal_ref_idc: 3 // PPS
nal_unit_type: 6, nal_ref_idc: 0 // SEI
nal_unit_type: 1, nal_ref_idc: 2 // NonIDR,nal_ref_idc = 2 表示被其他帧参考的 P 帧
h264_slice_header_parse
Decode slice, frame_num = 12
Set pic_field_poc[0] = 65569, [1] = 65569 // 设置当前的 POC(初始值是65536)
// 这里打印出短期参考图像列表。SBTV 的流没有用到长期参考列表。
short term list:
0 fn:10 poc:65563 0x10fee3000 // 位于列表的索引0处,FrameNum = 10,POC = 65563
1 fn:9 poc:65560 0x10fae4000
2 fn:8 poc:65557 0x110f10000
3 fn:7 poc:65555 0x110ae0000
// SBTV 的流没有任何显式 MMCO 操作。当 H.264 slice header 中没有 MMCO 字段的时候,用 FIFO 队列自动管理参考图像列表。这里的 mmco 是由 FFmpeg 的 generate_sliding_window_mmcos
函数生成的等效MMCO = 1 操作序列,目的是复用同一套处理逻辑。
mmco:1 7 0 // 删除 FrameNum = 7 的短期参考
mmco: unref short 7 count 4
remove short 7 count 4
fn 7 removed
short term list:
0 fn:10 poc:65563 0x10fee3000
1 fn:9 poc:65560 0x10fae4000
2 fn:8 poc:65557 0x110f10000
// 当前帧的 FrameNum = 12,加入列表
short term list:
0 fn:12 poc:65569 0x1106e1000
1 fn:10 poc:65563 0x10fee3000
2 fn:9 poc:65560 0x10fae4000
3 fn:8 poc:65557 0x110f10000
// 到此为止是正常的
================================
h264_decode_frame
nal_unit_type: 9, nal_ref_idc: 0 // AUD
nal_unit_type: 6, nal_ref_idc: 0 // SEI
nal_unit_type: 1, nal_ref_idc: 0 // NonIDR,nal_ref_idc = 0 表示非参考帧
h264_slice_header_parse
Decode slice, frame_num = 15
// (!) FrameNum 从12跳到15。引用帧丢失,FFmpeg 执行 Error concealment 逻辑。
Frame num gap 15 12
mmco:1 8 0
mmco: unref short 8 count 4
remove short 8 count 4
fn 8 removed
short term list:
0 fn:12 poc:65569 0x1106e1000
1 fn:10 poc:65563 0x10fee3000
2 fn:9 poc:65560 0x10fae4000
// 加入了 FrameNum = 13 的帧。该帧事实上已经丢失,是通过复制第12帧得到的
short term list:
0 fn:13 poc:0 0x110ae0000
1 fn:12 poc:65569 0x1106e1000
2 fn:10 poc:65563 0x10fee3000
3 fn:9 poc:65560 0x10fae4000
// (!) FrameNum = 13,和15的差为2,说明还是有帧缺失
Frame num gap 15 13
mmco:1 9 0
mmco: unref short 9 count 4
remove short 9 count 4
fn 9 removed
short term list:
0 fn:13 poc:65571 0x110ae0000
1 fn:12 poc:65569 0x1106e1000
2 fn:10 poc:65563 0x10fee3000
// 加入了 FrameNum = 14 的帧。该帧复制自第13(12)帧
short term list:
0 fn:14 poc:0 0x110f10000
1 fn:13 poc:65571 0x110ae0000
2 fn:12 poc:65569 0x1106e1000
3 fn:10 poc:65563 0x10fee3000
// 现在不再存在 Frame num gap,可以继续往下处理
Set pic_field_poc[0] = 65573, [1] = 65573
short term list:
0 fn:14 poc:65573 0x110f10000
1 fn:13 poc:65571 0x110ae0000 // 这里打出的是 `short_ref` 中的 POC,不是 `ref_list` 中的 POC
2 fn:12 poc:65569 0x1106e1000
3 fn:10 poc:65563 0x10fee3000
// List0(List1):向前(向后)参考图像列表。
List0: ST fn:14 0x0x110f10000
List1: ST fn:13 0x0x110ae0000
// (!) 报出错误:List1[0] (frame_num = 13) 并没有收到,POC 无效
co located POCs unavailable
// `col_poc[0]` 和 `col_poc[1]` 是指顶场和底场,对帧编码来说二者相同
cur_poc = 65573, col_poc[0] = 2147483647, col_poc[1] = 2147483647, ref_count[1] = 1
cur frame_num = 15, col frame_num = 13
在直播 + 高码率 + 带宽有限的情况下丢帧是不可避免的。流媒体服务器可以去监测这个指标作为视频质量的一个度量。
编码程序崩溃
[rtsp @ 0x1e8c140] Timestamps are unset in a packet for stream 0. This is deprecated and will stop working in the future. Fix your code to set the timestamps properly [rtsp @ 0x1e8c140] Encoder did not produce proper pts, making some up.
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff6f29473 in av_freep () from /usr/ffmpeg/lib/libavutil.so.56
Missing separate debuginfos, use: debuginfo-install bzip2-libs-1.0.6-13.el7.x86_64 glibc-2.17-326.el7_9.x86_64 libgcc-4.8.5-44.el7.x86_64 libstdc++-4.8.5-44.el7.x86_64 zlib-1.2.7-20.el7_9.x86_64
(gdb) BT
#0 0x00007ffff6f29473 in av_freep () from /usr/ffmpeg/lib/libavutil.so.56
#1 0x00007ffff5755c9c in av_packet_free_side_data () from /usr/ffmpeg/lib/libavcodec.so.58
#2 0x00007ffff57565f9 in av_packet_unref () from /usr/ffmpeg/lib/libavcodec.so.58
#3 0x00007ffff584ff46 in avcodec_receive_packet () from /usr/ffmpeg/lib/libavcodec.so.58
#4 0x000000000040374a in encode (ctx=0x663f00, frame=0x6072c0, pkt=0x7fffffffdd50) at ffmpeg-filter.c:1272
#5 0x0000000000403b69 in main (argc=2, argv=0x7fffffffdf68) at ffmpeg-filter.c:1399
// libavcodec\encode.c
int attribute_align_arg avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt) {
av_packet_unref(avpkt);
if (!avcodec_is_open(avctx) || !av_codec_is_encoder(avctx->codec))
return AVERROR(EINVAL);
if (avctx->codec->receive_packet) {
if (avctx->internal->draining && !(avctx->codec->capabilities & AV_CODEC_CAP_DELAY))
return AVERROR_EOF;
return avctx->codec->receive_packet(avctx, avpkt);
}
// Emulation via old API.
if (!avctx->internal->buffer_pkt_valid) {
int got_packet;
int ret;
if (!avctx->internal->draining)
return AVERROR(EAGAIN);
ret = do_encode(avctx, NULL, &got_packet);
if (ret < 0)
return ret;
if (ret >= 0 && !got_packet)
return AVERROR_EOF;
}
av_packet_move_ref(avpkt, avctx->internal->buffer_pkt);
avctx->internal->buffer_pkt_valid = 0;
return 0;
}
void av_packet_unref(AVPacket *pkt)
{
av_packet_free_side_data(pkt);
av_buffer_unref(&pkt->buf);
av_init_packet(pkt);
pkt->data = NULL;
pkt->size = 0;
}
FFmpeg花屏解决(修改源码,丢弃不完整帧)
linux下模拟丢帧的命令,因为帧之间的参考关系,实测如果是1%几乎没有完好的帧。
sudo tc qdisc add dev enp0s31f6 root netem loss 0.1%
删除上面的设置
sudo tc qdisc del dev enp0s31f6 root
在头文件libavformat/avformat.h中av_read_frame函数后添加av_deviser_flag函数:
//extern int deviser_flag;
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
int av_deviser_flag();
在头文件libavformat/utils.c ff_read_packet函数前添加int deviser_flag = 666666;
int deviser_flag = 666666;
int av_deviser_flag()
{
return deviser_flag;
}
int ff_read_packet(AVFormatContext *s, AVPacket *pkt)
同样在这个头文件中,ff_read_packet函数内添加deviser_flag = 666666;,这个函数是被av_read_frame函数调用的,可以看出这个函数内循环读取pkt,并对pkt处理。
int ff_read_packet(AVFormatContext *s, AVPacket *pkt)
{
int ret, i, err;
AVStream *st;
pkt->data = NULL;
pkt->size = 0;
av_init_packet(pkt);
for (;;) {
deviser_flag = 666666;
AVPacketList *pktl = s->internal->raw_packet_buffer;
const AVPacket *pkt1;
}
}
同样在这个头文件中,ff_read_packet函数内添加deviser_flag = 111111;
注意看这段函数检测包是否完整,也就是AV_PKT_FLAG_CORRUPT这个标记。还有一个标记可以了解一下AVFMT_FLAG_DISCARD_CORRUPT,这个标记是在av_dict_set(&options, “fflags”, “discardcorrupt”, 0);时添加到流的,这段代码的意思如果丢包了,并且添加了discardcorrupt的fflags那么这个包直接丢弃,不会被av_read_frame函数取出来。实测下面的pkt只包含一帧视频帧。用命令行设置的话,就是-fflags discardcorrupt。
if (pkt->flags & AV_PKT_FLAG_CORRUPT) {
av_log(s, AV_LOG_WARNING,
"Packet corrupt (stream = %d, dts = %s)",
pkt->stream_index, av_ts2str(pkt->dts));
deviser_flag = 111111;
if (s->flags & AVFMT_FLAG_DISCARD_CORRUPT) {
av_log(s, AV_LOG_WARNING, ", dropping it.\n");
av_packet_unref(pkt);
continue;
}
av_log(s, AV_LOG_WARNING, ".\n");
}
因此在我们的代码中在av_read_frame函数后:
if ((av_deviser_flag()) == 111111) {
//丢弃这帧视频,如果是参考帧(I和P帧),后续的gop帧都要丢掉,直到下个I帧到来。如果是B帧的话,直接丢弃就好。
}
这样再解码测试,还有花屏,看到ffmpeg报的错误是解码错误,推测这是因为不是所有的不完整帧ffmpeg都能检测出来,导致一些不完整帧解码时出错导致。
根据出错的地方打印日志:
在AVFrame结构体中有这个一个标志,专门记录这一帧在解码时出的错误,并不是就解码出错这帧就解不出来了,和其正常帧一样出来,只是做了记录。
/**
* decode error flags of the frame, set to a combination of
* FF_DECODE_ERROR_xxx flags if the decoder produced a frame, but there
* were errors during the decoding.
* - encoding: unused
* - decoding: set by libavcodec, read by user.
*/
int decode_error_flags;
#define FF_DECODE_ERROR_INVALID_BITSTREAM 1
#define FF_DECODE_ERROR_MISSING_REFERENCE 2
#define FF_DECODE_ERROR_CONCEALMENT_ACTIVE 4
#define FF_DECODE_ERROR_DECODE_SLICES 8
经过实测,不仅这几个,decode_error_flags为12时也是解码出错,正确时decode_error_flags为0。
这些记录标志的代码在下面文件中。
libavcodec/h264_slice.c
libavcodec/h264_parse.c
libavcodec/error_resilience.c
libavcodec/h264_cabac.c
打印日志发现每次花屏都是I帧解码出错,并且这时decode_error_flags为4或12,因此这个I帧和后面的gop都需要丢掉。
if(ic->streams[video_index]->codec->codec_id == AV_CODEC_ID_H264 && this->frame_v->key_frame && (this->frame_v->decode_error_flags == 12 || this->frame_v->decode_error_flags == 4))
{
//丢帧,直到下个无错的I帧到,这里是h264解码。
}
重新编译ffmpeg,到这里测试发现视频虽然卡顿但不会花屏了,以上h264帧结构是I和P,不包含B帧。
如果使用h265时,解码错误,但并不会记录在AVFrame的decode_error_flags中,这就需要自己在源码中记录了,可以使用下面的方法。
在libavcodec/avcodec.h头文件中添加:
extern int deviser_1;
int av_deviser_1();
int av_set_deviser_1(int temp_pram);
在libavcodec/h264_parse.c //定义全局变量av_set_deviser_1,和函数
int av_deviser_1()
{
return deviser_1;
}
int deviser_1 = 666666;
int av_set_deviser_1(int temp_pram)
{
deviser_1 = temp_pram;
return deviser_1;
}
int ff_h264_check_intra4x4_pred_mode(int8_t *pred_mode_cache, void *logctx,
int top_samples_available, int left_samples_available)
同样的文件里,在ff_h264_check_intra4x4_pred_mode函数中,也有一个花屏常见的错误,left block unavailable for requested intra4x4 mode,例如你想在这里做个标记,添加deviser_1 = 222222;
if (status < 0) {
av_log(logctx, AV_LOG_ERROR,
"left block unavailable for requested intra4x4 mode %d\n",
status);
deviser_1 = 222222;
return AVERROR_INVALIDDATA;
}
那么在avcodec_receive_frame函数得到解码数据后检查:
if (av_deviser_1() == 555555) {
//处理
}
同时处理后应该在下次解码前复位标志,因为这个函数是在解码时调用的:
av_set_deviser_1(666666);
如果在libavcodec文件价内其他文件标记,只需要包含avcodec.h,然后直接使用deviser_1变量即可。注意不可非libavcodec模块内使用全局变量因为编译时不再同一个.so文件中,访问不到。
ic->flags |= AVFMT_FLAG_NOBUFFER;
re = avformat_find_stream_info(ic, NULL);
码流探测阶段也有时有不完整的帧,这部分帧需要舍弃,不能放到接收缓存中,因此需要加上AVFMT_FLAG_NOBUFFER。
同时需要注意h265的一些码流结构如GPB模式,avformat_open_input函数打开
在h265帧结构为I和P,不含B,以及含I,P,B但是闭合GOP时可用,如果是低延时模式GPB(广义B帧,帧结构参考文末图片),目前还未找到可行的方法。
附录用ffplay播放花屏时常见报错:
libavcodec/h264_parse.c
[h264 @ 0x7f8af0045780] left block unavailable for requested intra mode 111111
libavcodec/h264_parse.c
left block unavailable for requested intra4x4 mode 222222
//libavcodec/h264_slice.c
[h264 @ 0x7f8af0045780] error while decoding MB 0 30, bytestream 80303 333333
libavcodec/error_resilience.c
[h264 @ 0x7f8af0045780] concealing 4609 DC, 4609 AC, 4609 MV errors in I frame 444444
libavcodec/h264_cabac.c
[h264 @ 0x7fa298c462c0] cabac decode of qscale diff failed at 62 59 555555 这个目前没见到
[hevc @ 0x7fcc0cb82b40] The cu_qp_delta 52 is outside the valid range [-26, 25]. 这个解码出来花屏
[mpegts @ 0x7fcc0c000940] Packet corrupt (stream = 0, dts = 47665500).
[mpegts @ 0x7fcc0c000940] PES packet size mismatch
libavcodec/hevc_refs.c
[hevc @ 0x7fcc0c59dc00] Could not find ref with POC 14052 //999999
[hevc @ 0x7fcc0c57cc40] CABAC_MAX_BIN : 7
[NULL @ 0x7f3ef02d1500] Invalid NAL unit 35, skipping.
send_error
/usr/app/EncParams/enc_2160p_422_10_50_swap_sq_2chaac/EHParam.sh 设备密码:root hotdog –enable-parser=hevc
libavformat/hevcdec.c probe
libavcodec/hevcdec.c -=-=-=HEVC_NAL_SPS
hevc_refs.c 里面 add_candidate_ref 函数 if(!ref){ //return AV_ERROR_INVALIDDATE; //以下不产生纠错,就不会错了,注释掉下面的,直接返回,就不会灰了 ret = generate_missing_ref(s,poc); if(!ref) return AVERROR(ENOMEM); }
你可能会遇到的error:
[image2 @ 000002159e25b4c0] Could not get frame filename number 2 from pattern '0.jpg'.
Use '-frames:v 1' for a single image,
or '-update' option,
or use a pattern such as %03d within the filename.
av_interleaved_write_frame(): Invalid argument
原因:
你截图没有选定操作的个数, 加上-frames:v 1选项或者-t 0.01即可.
ffmpeg -err_detect aggressive -fflags discardcorrupt -buff_size 1024000 -rtsp_transport tcp -i rtsp:[ip]:554 -r 1 -an -f image2 test.jpg
-err_detect aggressive
用于错误检测的标志
检测ffmpeg处理中的错误可以通过表中描述的-err_detect选项指定:
描述 检测一个错误,该标志指定了哪种类型
语法 -err_detect[:stream_specifier] flag
可用标志的描述
aggressive 考虑一个理智的编码器不应该做的错误
bitstream 检测比特流指定偏差
buffer 检测不合适的比特流长度
careful 考虑违反规范并且没有被视为错误的东西
compliant 将所有规范不合规视为错误
crccheck 验证嵌入式CRC
explode 终止对较小错误检测的解码
例如,要检测不正确的比特流长度,我们可以使用以下命令:
ffmpeg -report -err_detect buffer -i input.avi output.mp4
-fflags discardcorrupt
丢弃损坏的帧
-buff_size 1024000
内存缓存区
-rtsp_transport tcp
以tcp方式拉取视频流
-r 1
帧率,此处一张图片是一帧
时间戳
tbr: 是我们通常所说的帧率。time base of rate
tbn: 视频流的时间基。 time base of stream
tbc: 视频解码的时间基。time base of codec
tbn = the time base in AVStream that has come from the container
tbc = the time base in AVCodecContext for the codec used for a particular stream
tbr = tbr is guessed from the video stream and is the value users want to see when they look for the video frame rate.
ffmpeg中的内部计时单位(时间基),ffmepg中的所有时间都是于它为一个单位。
比如AVStream中的duration,即这个流的长度为duration个AV_TIME_BASE.
#define AV_TIME_BASE 1000000
它还有一种分数所表式法:
#define AV_TIME_BASE_Q (AVRational){1, AV_TIME_BASE}
在 ffmpeg中进行换算,将不同时间基的值转成按秒为单位的值计算如下:
timestamp(秒) = pts * av_q2d(time_base)
AVRatioal的定义如下:
typedef struct AVRational {
int num; //numerator
int den; //denominator
} AVRational;
AVRatioal结构转double的函数:
static inline double av_q2d(AVRational a) {
return a.num / (double) a.den;
}
av_get_time_base_q 函数
返回内部时基的小数表示.
AVRational av_get_time_base_q(void);
av_rescale_q 函数
av_rescale_q(a,b,c)的作用是,把时间戳从一个时基调整到另外一个时基时候用的函数。ss其中,a 表式要换算的值;b 表式原来的时间基;c表式要转换的时间基。其计算公式为 a * b / c。
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;
用于计算Packet中的pts:
if (pkt->pts != AV_NOPTS_VALUE) {
pkt->pts = av_rescale_q(pkt->pts, mCtx->streams[pkt->stream_index]->time_base, av_get_time_base_q());
}
if (pkt->dts != AV_NOPTS_VALUE) {
pkt->dts = av_rescale_q(pkt->dts, mCtx->streams[pkt->stream_index]->time_base, av_get_time_base_q());
}
根据pts来计算一桢在整个视频中的时间位置
timestamp(秒) = pts * av_q2d(st->time_base)
计算视频长度的方法
time(秒) = st->duration * av_q2d(st->time_base)
这里的st是一个AVStream对象指针。
时间基转换公式
timestamp(ffmpeg内部时间戳) = AV_TIME_BASE * time(秒) time(秒) = AV_TIME_BASE_Q * timestamp(ffmpeg内部时间戳)
示例:
int64_t timestamp = N * AV_TIME_BASE;
av_seek_frame(fmtctx, index_of_video, timestamp, AVSEEK_FLAG_BACKWARD);
//ffmpeg同样为我们提供了不同时间基之间的转换函数:
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)
//时间戳转秒
time_in_seconds = av_q2d(AV_TIME_BASE_Q) * timestamp
//秒转时间戳
timestamp = AV_TIME_BASE * time_in_seconds
AVStream
AVStream是存储每一个视频/音频流信息的结构体。本文分析一下该结构体里重要变量的含义和作用。
该结构体位于avformat.h中
int index; 标识该音频/视频流
int id; 流的标识,依赖于具体的容器格式。解码libavformat,编码由用户设置,用户不设置则使用libavformat
AVCodecContext *codec; 流对应与AVCodecContext结构,调用avformat_open_input设置
AVRational time_base; 表示帧时间戳的基本单位。通过该值可以把PTS,DTS转化为真正的时间,其他结构体中也有这个字段,只有AVStream 中的time_base为真正的时间。
int64_t start_time; 流的起始时间,以流的基准时间为单位
int64_t duration; 流的持续时间,如果源文件未指定持续时间,但指定了比特率,则将根据比特率和文件大小估计该值。
int64_t nb_frames; 流中帧的数据,为0或者已知
AVDictionary *metadata; 元数据
AVRational sample_aspect_ratio; 样本长宽比
AVRational avg_frame_rate; 评价帧率,解封装时:在创建流时设置或者在avformat_find_stream_info()中设置;封装时调用avformat_write_header()设置
AVCodecParameters *codecpar; 编解码器参数
int64_t first_dts; 第一个dts
int64_t cur_dts; 当前dts
int probe_packets; 编码器用户probe的包的个数
int codec_info_nb_frames; 在avformat_find_stream_info()期间已经解封装的帧数
AVPacket attached_pic;附带的一些图片
int request_probe; 流探测状态,-1表示完成,0表示没有探测请求,rest执行探测
int skip_to_keyframe; 是否丢弃所有内容直接跳到关键帧
int skip_samples;在下一个数据表解码的帧开始要跳过的采样数
int64_t start_skip_samples;从流的开始要跳过的采样的数量
int64_t first_discard_sample; 如果不是0,从流中丢弃第一个音频的样本
int nb_decoded_frames; 内部解码的帧数
int64_t pts_reorder_error[MAX_REORDER_DELAY+1];
uint8_t pts_reorder_error_count[MAX_REORDER_DELAY+1]; 内部数据从pts到dts
int64_t last_dts_for_order_check; 内部数据用于分析dts和探测mpeg流的错误
uint8_t dts_ordered;
uint8_t dts_misordered;
AVRational display_aspect_ratio; 显示的宽高比
AVPacket
AVPacket是存储压缩编码数据相关信息的结构体。 AVPacket是FFmpeg中很重要的一个结构体,它保存了压缩后的数据以及这些数据的信息。 比如显示时间戳pts,解码时间戳dts,每一帧持续的时间duration以及媒体流的索引等。
对于视频而言一个AVPacket通常是一个压缩的AVFrame,而一个音频AVPacket可能包含若干个AVFrame。 当然也有可能AVPacket是一个空的,不包含任何数据仅仅包含sider data。(例如在编码结束时更新一些流的参数)。s
AVPacket相比其他的结构体而言简单,但却是非常常用的一个。在AVPacket结构体中,重要的变量有以下几个:
AVBufferRef *buf; 指向引用的数据,如果如NULL,那么packet中的data没被使用
int64_t pts; 显示时间戳
int64_t dts; 解码时间戳
uint8_t *data; 压缩的编码数据
int size; data的大小
int stream_index; packet所在的流索引
int flags; 标志,其中最低为1表示该数据是一个关键帧
AVPacketSideData *side_data; Packet是一个容器,此字段说明额外的数据可以被packet提供
int64_t duration; 数据的持续时长
int64_t pos; 数据在流中位置,-1表示未知
AVFrame
AVFrame结构体是存储音视频原始数据(即未被编码的数据)的结构体。该结构体位于libavutil/frame.h中。 AVFrame结构体一般是用来存储原始数据(视频数据是YUV 和RGB,音频数据是PCM)。 编码的时候也存储了一些相关的信息,在使用ffmpeg进行开发的时候,AVFrame是一个很重要的结构体。
接下来我们看下结构体中重要的变量和作用:
uint8_t *data[AV_NUM_DATA_POINTERS]; 解码后的原始数据,视频数据是YUV 和RGB,音频数据是PCM)
int linesize[AV_NUM_DATA_POINTERS]; data中“一行“数据的大小,一般大于图像的宽
int width, height; 对视频而言指视频的宽和高
int nb_samples; 音频中一个AVFrame包含多个音频帧,表示音频帧的数量
int format; 帧的格式,原始数据的类型
int key_frame; 是否为关键帧
enum AVPictureType pict_type; 帧的类型
AVRational sample_aspect_ratio; 宽高比
int64_t pts; 显示时间戳
int64_t pkt_dts; 每一帧的时间
int coded_picture_number; 编码帧的序号
int display_picture_number; 显示帧的序号
int sample_rate; 采样率(音频)
uint64_t channel_layout; 声道格式(音频)
int channels; 声道数量(编码未使用),解码只读
int interlaced_frame; 是否隔行扫描
int8_t *qscale_table; QP表
int quality; 帧的质量,1表示好 FF_LAMBDA_MAX表示bad
enum AVColorRange color_range; MPEG,JPEG,YUV的色彩范围
enum AVColorSpace colorspace; YUV的色彩空间
int64_t pkt_duration; 相关包的流逝时间
AVDictionary *metadata; 元数据
1.uint8_t *data[AV_NUM_DATA_POINTERS]
对于packed格式的数据会存到data[0]中,例如RGB24
对于plannar格式的数据会存到data[0],data[1]…,例如YUV420P,会分开存到 Y:data[0], U:data[1], V:data[2]
2.enum AVPictureType pict_type;包含以下类型:
enum AVPictureType {
AV_PICTURE_TYPE_NONE = 0, ///< Undefined
AV_PICTURE_TYPE_I, ///< Intra
AV_PICTURE_TYPE_P, ///< Predicted
AV_PICTURE_TYPE_B, ///< Bi-dir predicted
AV_PICTURE_TYPE_S, ///< S(GMC)-VOP MPEG-4
AV_PICTURE_TYPE_SI, ///< Switching Intra
AV_PICTURE_TYPE_SP, ///< Switching Predicted
AV_PICTURE_TYPE_BI, ///< BI type
};
3.AVRational sample_aspect_ratio;
宽高比是一个分数,FFMPEG中使用AVRational表示分数
typedef struct AVRational{
int num; ///< Numerator
int den; ///< Denominator
} AVRational;
4.int8_t *qscale_table
QP表指向一块内存,里面存储的是每个宏块的QP值。宏块的标号是从左往右,一行一行的来的。每个宏块对应1个QP。 qscale_table[0]就是第1行第1列宏块的QP值;qscale_table[1]就是第1行第2列宏块的QP值;qscale_table[2]就是第1行第3列宏块的QP值。以此类推…
宏块的个数用下式计算:
注:宏块大小是16x16的。
每行宏块数:
int mb_stride = pCodecCtx->width/16+1
宏块的总数:
int mb_sum = ((pCodecCtx->height+15)»4)*(pCodecCtx->width/16+1)
5.uint64_t channel_layout; 声道格式(音频)
#define AV_CH_FRONT_LEFT 0x00000001
#define AV_CH_FRONT_RIGHT 0x00000002
#define AV_CH_FRONT_CENTER 0x00000004
#define AV_CH_LOW_FREQUENCY 0x00000008
#define AV_CH_BACK_LEFT 0x00000010
#define AV_CH_BACK_RIGHT 0x00000020
#define AV_CH_FRONT_LEFT_OF_CENTER 0x00000040
#define AV_CH_FRONT_RIGHT_OF_CENTER 0x00000080
#define AV_CH_BACK_CENTER 0x00000100
#define AV_CH_SIDE_LEFT 0x00000200
#define AV_CH_SIDE_RIGHT 0x00000400
#define AV_CH_TOP_CENTER 0x00000800
#define AV_CH_TOP_FRONT_LEFT 0x00001000
#define AV_CH_TOP_FRONT_CENTER 0x00002000
#define AV_CH_TOP_FRONT_RIGHT 0x00004000
#define AV_CH_TOP_BACK_LEFT 0x00008000
#define AV_CH_TOP_BACK_CENTER 0x00010000
#define AV_CH_TOP_BACK_RIGHT 0x00020000
#define AV_CH_STEREO_LEFT 0x20000000 ///< Stereo downmix.
#define AV_CH_STEREO_RIGHT 0x40000000 ///< See AV_CH_STEREO_LEFT.
#define AV_CH_WIDE_LEFT 0x0000000080000000ULL
#define AV_CH_WIDE_RIGHT 0x0000000100000000ULL
#define AV_CH_SURROUND_DIRECT_LEFT 0x0000000200000000ULL
#define AV_CH_SURROUND_DIRECT_RIGHT 0x0000000400000000ULL
#define AV_CH_LOW_FREQUENCY_2 0x0000000800000000ULL
#define AV_CH_LAYOUT_NATIVE 0x8000000000000000ULL
#define AV_CH_LAYOUT_MONO (AV_CH_FRONT_CENTER)
#define AV_CH_LAYOUT_STEREO (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT)
#define AV_CH_LAYOUT_2POINT1 (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY)
#define AV_CH_LAYOUT_2_1 (AV_CH_LAYOUT_STEREO|AV_CH_BACK_CENTER)
#define AV_CH_LAYOUT_SURROUND (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER)
#define AV_CH_LAYOUT_3POINT1 (AV_CH_LAYOUT_SURROUND|AV_CH_LOW_FREQUENCY)
#define AV_CH_LAYOUT_4POINT0 (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_CENTER)
#define AV_CH_LAYOUT_4POINT1 (AV_CH_LAYOUT_4POINT0|AV_CH_LOW_FREQUENCY)
#define AV_CH_LAYOUT_2_2 (AV_CH_LAYOUT_STEREO|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)
#define AV_CH_LAYOUT_QUAD (AV_CH_LAYOUT_STEREO|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
#define AV_CH_LAYOUT_5POINT0 (AV_CH_LAYOUT_SURROUND|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)
#define AV_CH_LAYOUT_5POINT1 (AV_CH_LAYOUT_5POINT0|AV_CH_LOW_FREQUENCY)
#define AV_CH_LAYOUT_5POINT0_BACK (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
#define AV_CH_LAYOUT_5POINT1_BACK (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_LOW_FREQUENCY)
#define AV_CH_LAYOUT_6POINT0 (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_CENTER)
#define AV_CH_LAYOUT_6POINT0_FRONT (AV_CH_LAYOUT_2_2|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
#define AV_CH_LAYOUT_HEXAGONAL (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_BACK_CENTER)
#define AV_CH_LAYOUT_6POINT1 (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_CENTER)
#define AV_CH_LAYOUT_6POINT1_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_BACK_CENTER)
#define AV_CH_LAYOUT_6POINT1_FRONT (AV_CH_LAYOUT_6POINT0_FRONT|AV_CH_LOW_FREQUENCY)
#define AV_CH_LAYOUT_7POINT0 (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
#define AV_CH_LAYOUT_7POINT0_FRONT (AV_CH_LAYOUT_5POINT0|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
#define AV_CH_LAYOUT_7POINT1 (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
#define AV_CH_LAYOUT_7POINT1_WIDE (AV_CH_LAYOUT_5POINT1|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
#define AV_CH_LAYOUT_7POINT1_WIDE_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)
#define AV_CH_LAYOUT_OCTAGONAL (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_CENTER|AV_CH_BACK_RIGHT)
#define AV_CH_LAYOUT_HEXADECAGONAL (AV_CH_LAYOUT_OCTAGONAL|AV_CH_WIDE_LEFT|AV_CH_WIDE_RIGHT|AV_CH_TOP_BACK_LEFT|AV_CH_TOP_BACK_RIGHT|AV_CH_TOP_BACK_CENTER|AV_CH_TOP_FRONT_CENTER|AV_CH_TOP_FRONT_LEFT|AV_CH_TOP_FRONT_RIGHT)
#define AV_CH_LAYOUT_STEREO_DOWNMIX (AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT)
AVFormatContext
AVFormatContext对开发者开放,是连接开发者与FFmpeg内部的桥梁。
结构体内部包含有AVInputFormat、AVOutputFormat、AVCodec、AVStream、AVDictionary、AVClass等。支持设置自定义IO、监听网络中断状态、设置options、直播秒开调优等
AVFormatContext结构体的源码位于libavformat/avformat.h
typedef struct AVFormatContext {
// 用于打印日志和设置选项的类
const AVClass *av_class;
// 输入容器格式
ff_const59 struct AVInputFormat *iformat;
// 输出容器格式
ff_const59 struct AVOutputFormat *oformat;
// 私有数据
void *priv_data;
//I/O 上下文
//- demuxing: avformat_open_input()之前设置
//- muxing: avformat_write_header()之前设置
AVIOContext *pb;
// 关于stream属性的Flags
int ctx_flags;
// 码流的数量
unsigned int nb_streams;
// 码流数组
AVStream **streams;
// 输入或输出的URL
char *url;
// 第一帧的开始时间
int64_t start_time;
// 码流的时长
int64_t duration;
// 码流的码率,单位为bit/s
int64_t bit_rate;
unsigned int packet_size;
int max_delay;
int flags;
#define AVFMT_FLAG_GENPTS 0x0001 ///< Generate missing pts
#define AVFMT_FLAG_IGNIDX 0x0002 ///< Ignore index.
#define AVFMT_FLAG_NONBLOCK 0x0004 ///< Do not block when reading packets from input.
#define AVFMT_FLAG_IGNDTS 0x0008 ///< Ignore DTS on frames
#define AVFMT_FLAG_NOFILLIN 0x0010 ///< Do not infer any values from other values
#define AVFMT_FLAG_NOPARSE 0x0020 ///< Do not use AVParsers
#define AVFMT_FLAG_NOBUFFER 0x0040 ///< Do not buffer frames when possible
#define AVFMT_FLAG_CUSTOM_IO 0x0080 ///< The caller has supplied a custom AVIOContext
#define AVFMT_FLAG_DISCARD_CORRUPT 0x0100 ///< Discard frames marked corrupted
#define AVFMT_FLAG_FLUSH_PACKETS 0x0200 ///< Flush the AVIOContext every packet.
#define AVFMT_FLAG_BITEXACT 0x0400
#if FF_API_LAVF_MP4A_LATM
#define AVFMT_FLAG_MP4A_LATM 0x8000 ///< Deprecated, does nothing.
#endif
#define AVFMT_FLAG_SORT_DTS 0x10000 ///< try to interleave outputted packets by dts
#if FF_API_LAVF_PRIV_OPT
#define AVFMT_FLAG_PRIV_OPT 0x20000 ///< Enable use of private options by delaying codec open (deprecated, will do nothing once av_demuxer_open() is removed)
#endif
#if FF_API_LAVF_KEEPSIDE_FLAG
#define AVFMT_FLAG_KEEP_SIDE_DATA 0x40000 ///< Deprecated, does nothing.
#endif
#define AVFMT_FLAG_FAST_SEEK 0x80000 ///< Enable fast, but inaccurate seeks for some formats
#define AVFMT_FLAG_SHORTEST 0x100000 ///< Stop muxing when the shortest stream stops.
#define AVFMT_FLAG_AUTO_BSF 0x200000 ///< Add bitstream filters as requested by the muxer
// 从输入流探测数据包的大小
int64_t probesize;
// 最大的分析时长,单位为AV_TIME_BASE
int64_t max_analyze_duration;
const uint8_t *key;
int keylen;
unsigned int nb_programs;
AVProgram **programs;
enum AVCodecID video_codec_id;
enum AVCodecID audio_codec_id;
enum AVCodecID subtitle_codec_id;
unsigned int max_index_size;
unsigned int max_picture_buffer;
unsigned int nb_chapters;
AVChapter **chapters;
// 文件元数据媒体信息
AVDictionary *metadata;
int64_t start_time_realtime;
int fps_probe_size;
int error_recognition;
// 自定义的I/O层中断回调
AVIOInterruptCB interrupt_callback;
int debug;
int64_t max_interleave_delta;
int strict_std_compliance;
int event_flags;
int max_ts_probe;
// 避免负数的时间戳
int avoid_negative_ts;
#define AVFMT_AVOID_NEG_TS_AUTO -1 ///< Enabled when required by target format
#define AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE 1 ///< Shift timestamps so they are non negative
#define AVFMT_AVOID_NEG_TS_MAKE_ZERO 2 ///< Shift timestamps so that they start at 0
int ts_id;
int audio_preload;
int max_chunk_duration;
int max_chunk_size;
int use_wallclock_as_timestamps;
int avio_flags;
enum AVDurationEstimationMethod duration_estimation_method;
int64_t skip_initial_bytes;
unsigned int correct_ts_overflow;
// 强制seek到任意帧,包括非关键帧位置
int seek2any;
int flush_packets;
// 格式探测分数
int probe_score;
int format_probesize;
// codec的白名单
char *codec_whitelist;
// format的白名单
char *format_whitelist;
AVFormatInternal *internal;
int io_repositioned;
// 视频codec
AVCodec *video_codec;
// 音频codec
AVCodec *audio_codec;
// 字幕codec
AVCodec *subtitle_codec;
// 数据codec
AVCodec *data_codec;
int metadata_header_padding;
// 用户数据
void *opaque;
av_format_control_message control_message_cb;
int64_t output_ts_offset;
uint8_t *dump_separator;
enum AVCodecID data_codec_id;
// 协议白名单
char *protocol_whitelist;
// 打开新IO流的回调
int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,
int flags, AVDictionary **options);
// 关闭IO流的回调
void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);
// 协议黑名单
char *protocol_blacklist;
int max_streams;
int skip_estimate_duration_from_pts;
int max_probe_packets;
} AVFormatContext;
打开视频流
打开输入流的流程比较简单,先分配AVFormatContext,然后再打开输入流
AVFormatContext *format_ctx = avformat_alloc_context();
int ret = avformat_open_input(&format_ctx, url, NULL, NULL);
如果要设置avoptions,使用av_dict_set()来设置参数键值对
AVDictionary *options = NULL;
av_dict_set(&options, "video_size", "640x480", 0);
av_dict_set(&options, "pixel_format", "rgb24", 0);
AVFormatContext *format_ctx = avformat_alloc_context();
int ret = avformat_open_input(&format_ctx, url, NULL, &options);
如果要监听网络中断状态,使用AVIOInterruptCB进行监听
static int custom_interrupt_callback(void *arg) {
......
}
AVFormatContext *format_ctx = avformat_alloc_context();
format_ctx->interrupt_callback.callback = custom_interrupt_callback;
format_ctx->interrupt_callback.opaque= NULL;
int ret = avformat_open_input(&format_ctx, url, NULL, NULL);
// 其中AVIOInterruptCB的结构体定义如下,包含callback函数指针和opaque:
typedef struct AVIOInterruptCB {
int (*callback)(void*);
void *opaque;
} AVIOInterruptCB;
在使用FFmpeg拉流直播时,做直播秒开调优
可以调节probesize和max_analyze_duration。
其中probesize表示探测数据包的大小,max_analyze_duration表示最大分析时长,比如这样:
probesize = 5 * 1024 * 1024;
max_analyze_duration = 500;
自定义IO读取缓冲区数据
使用AVIOContext,分配缓冲区,实现read_packet、write_packet、seek方法:
#define IO_BUFFER_SIZE (4 * 1024 * 1024)
uint8_t *io_buffer = av_malloc(IO_BUFFER_SIZE);
int read_packet(void *opaque, uint8_t *buf, int buf_size) {
......
}
int64_t seek(void *opaque, int64_t offset, int whence) {
......
}
AVIOContext *avio_ctx = avio_alloc_context(io_buffer, IO_BUFFER_SIZE, 0, &user_data, &read_packet, NULL, &seek);
AVFormatContext *format_ctx = avformat_alloc_context();
format_ctx.pb = avio_ctx;
format_ctx->flags |= AVFMT_FLAG_CUSTOM_IO;
使用ffmpeg、x264库强制编出关键帧(I帧)
当h264数据在使用udp传输时,丢包是一件很常见的事情,当接收端丢包发生后,应该通知发送端,让发送端立刻重新发送一个关键帧。
此时就需要编码器无视关键帧间隔立刻编出一个关键帧。
一、x264 编码
_pic->i_type = X264_TYPE_KEYFRAME;
x264_encoder_encode(_encoder, &nal, &i_nal,_pic, &pic_out);
在使用x264编码器的x264_encoder_encode函数时,第四个参数存储要编码的YUV数据。 其中有个成员i_type表示要求编码器编出的帧类型。通常我们默认会将这个i_type成员设为X264_TYPE_AUTO,表示由编码器自动选择类型。
当我们想要强制编码出关键帧时,只要将这个i_type成员设为X264_TYPE_KEYFRAME即可,之后恢复为X264_TYPE_AUTO。
二、ffmpeg
pFrame->pict_type = AV_PICTURE_TYPE_I;
avcodec_send_frame(pCodecCtx,pFrame);
在使用ffmpeg编码器的avcodec_encode_video2函数时,第三个参数存储要编码的YUV数据。其中有个成员pict_type表示要求编码器编出的帧类型。
通常我们默认不会设置这个pict_type,由编码器自动选择类型。
当我们想要强制编码出关键帧时,只要将这个pict_type成员设为AV_PICTURE_TYPE_I即可,之后恢复为AV_PICTURE_TYPE_NONE。
音视频同步
static void sync_frame_timer(const AVPacket *packet, const AVFrame *vframe, const AVFrame *aframe) {
double timestamp;
//判断视频是否有有效的pts
if(packet->pts == AV_NOPTS_VALUE) {
timestamp = 0;
} else {
timestamp = av_frame_get_best_effort_timestamp(vframe)*av_q2d(fmt_ctx->streams[video_stream_index]->time_base);
}
//音频数据
double audioclock = aframe->pkt_pts * av_q2d(fmt_ctx->streams[audio_stream_index]->time_base);
//计算帧率,平均每帧间隔时间
double frameRate = av_q2d(fmt_ctx->streams[video_stream_index]->avg_frame_rate);
frameRate += vframe->repeat_pict * (frameRate * 0.5);
if (timestamp == 0.0) {
//按照默认帧率播放
usleep((unsigned long)(frameRate*1000));
}else {
if (fabs(timestamp - audioclock) > AV_SYNC_THRESHOLD_MIN &&
fabs(timestamp - audioclock) < AV_NOSYNC_THRESHOLD) {
//如果视频比音频快,延迟差值播放,否则直接播放,这里没有做丢帧处理
if (timestamp > audioclock) {
usleep((unsigned long)((timestamp - audioclock)*1000000));
}
}
}
}
快进快退
int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)
AVFormatContext *s:流媒体打开的上下文结构指针
int stream_index:流媒体索引,视频流或者是音频流,根据其中一个来检索,实现seek操作
int64_t timestamp:检索时间戳,若指定流媒体索引,则时间单位是流媒体对应的AVStream.time_base,若不指定流媒体索引,则时间单位是AV_TIME_BASE
int flags:指明seek操作的标志
#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward
#define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes
#define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non-keyframes
#define AVSEEK_FLAG_FRAME 8 ///< seeking based on frame number
当flag中有AVSEEK_FLAG_BYTE时,时间戳要改为byte字节计数。
当flag中有AVSEEK_FLAG_FRAME时,表示seek到离timestamp最近的关键帧,对于视频流来说就是I帧,音频流未知。
seek操作要点
seek操作基准:即seek操作当前时间点,因此在进行播放时需要实时记录音视频流的DTS或者PTS
seek时间单位:进行seek操作时,根据流索引,确定其时间单位time_base
seek时间大小:即向前或者向后seek的时间大小,根据此时间大小和当前时间点,可以计算出向前或者向后进行seek操作时的目标时间
seek操作载体:即是按照音频还是视频进行seek操作,只需要按照其中一个流进行seek即可,不需要分别对音视频流进行seek操作
seek操作刷新:在进行seek操作之后,正常播放之前需要将编解码的内部缓冲区进行刷新,同时也要将自行控制的缓冲区内缓存的音视频清零
视频seek
根据seek操作的要点,其中seek时间大小我们可以自行定义,这里暂定为3S
CurVideoDts:记录视频流当前的DTS
seek的时间单位:VideoCtrl.pStream->time_base就是视频流的时间单位,
前进:
int64_t DstVideoDts = CurVideoDts + (int64_t) ( 3 / av_q2d(VideoCtrl.pStream->time_base));
后退:
int64_t DstVideoDts = CurVideoDts - (int64_t) ( 3 / av_q2d(VideoCtrl.pStream->time_base));
因为pStream->time_base结构表示一个分数数据,而视频流中的DTS与PTS时间都是以time_base为时间单位,因此需要将seek时间大小转换为以time_base为时间单位的大小
seek操作:
ret = av_seek_frame(pFormatCtx, VideoIndex, DstVideoDts, AVSEEK_FLAG_FRAME);
向后seek是需要在flag参数中加上AVSEEK_FLAG_BACKWARD
缓冲区刷新:
avcodec_flush_buffers(VideoCtrl.pCodecCtx);
因为time_base分数结构中,num基本都为1,因此为了减少浮点数乘除法,可以直接乘以time_base的分母,见以下代码实例
音频seek
根据seek操作的要点,其中seek时间大小我们可以自行定义,这里暂定为3S
CurAudioDts:记录视频流当前的DTS
seek的时间单位:AudioCtrl.pStream->time_base就是视频流的时间单位,
前进:
int64_t DstAudioDts = CurAudioDts + (int64_t) ( 3 / av_q2d(AudioCtrl.pStream->time_base));
后退:
int64_t DstAudioDts = CurAudioDts - (int64_t) ( 3 / av_q2d(AudioCtrl.pStream->time_base));
因为pStream->time_base结构表示一个分数数据,而视频流中的DTS与PTS时间都是以time_base为时间单位,因此需要将seek时间大小转换为以time_base为时间单位的大小
seek操作:
ret = av_seek_frame(pFormatCtx, AudioIndex, DstAudioDts, AVSEEK_FLAG_FRAME);
向后seek是需要在flag参数中加上AVSEEK_FLAG_BACKWARD
缓冲区刷新:
avcodec_flush_buffers(AudioCtrl.pCodecCtx);
查找视频旋转角度
之前查看的视频旋转角度是使用ffprobe的这个工具 视频的旋转角度存储在视频流的元数据中, 打开视频文件,找到视频中的视频流,获取视频流的元数据信息并得到旋转角度。
double GetVideoRotationMetadataInfo(const std::string& video_path) {
double rotationResult = 0.0;
AVFormatContext* format_ctx = NULL;
AVDictionaryEntry* tag = NULL;
int ret;
if ((ret = avformat_open_input(&format_ctx, video_path.c_str(), NULL, NULL)))
return ret;
// 查找文件视频流信息
int video_stream_idx = -1;
for (int i = 0; i < format_ctx->nb_streams; ++i) {
if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_idx = i;
}
}
// 在视频流中查找元数据的旋转信息
if (video_stream_idx != -1) {
AVStream* pAVStream = format_ctx->streams[video_stream_idx];
if (pAVStream != nullptr) {
while ((tag = av_dict_get(pAVStream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
std::string key = tag->key;
std::string value = tag->value;
if (key.compare("rotate") == 0) {
rotationResult = std::atof(value.c_str());
break;
}
}
}
}
avformat_close_input(&format_ctx);
return rotationResult;
}
图像缩放
简单的初始化方法
Libswscale使用起来很方便,最主要的函数只有3个:
(1)sws_getContext():使用参数初始化SwsContext结构体。
(2)sws_scale():转换一帧图像。
(3)sws_freeContext():释放SwsContext结构体。
其中sws_getContext()也可以用另一个接口函数sws_getCachedContext()取代。
复杂但是更灵活的初始化方法
初始化SwsContext除了调用sws_getContext()之外还有另一种方法,更加灵活,可以配置更多的参数。该方法调用的函数如下所示。
(1)sws_alloc_context():为SwsContext结构体分配内存。
(2)av_opt_set_XXX():通过av_opt_set_int(),av_opt_set()…等等一系列方法设置SwsContext结构体的值。在这里需要注意,SwsContext结构体的定义看不到,所以不能对其中的成员变量直接进行赋值,必须通过av_opt_set()这类的API才能对其进行赋值。
(3)sws_init_context():初始化SwsContext结构体。
这种复杂的方法可以配置一些sws_getContext()配置不了的参数。比如说设置图像的YUV像素的取值范围是JPEG标准(Y、U、V取值范围都是0-255)还是MPEG标准(Y取值范围是16-235,U、V的取值范围是16-240)。
ffmpeg解析.mbf文件失败
mbf就是在原数据基础上增加一个封装头 Setting default whitelist file Statistics: 1048576 bytes read, 0 seeks
ffmpeg打开视频文件的调用
avio.c ffurl_open:
ffurl_open_whitelist:
ffio_open_whitelist
aviobuf.c
ffio_fdopen
avio_closep
avio_close
容器中不支持的音频格式
codec not currently supported in container
这是来自控制台输出的消息。在MP4容器ffmpeg中不支持pcm_alaw。
通过将音频替换-acodec copy为-c:a aac,将音频编码为受支持的格式(例如AAC),或者使用支持的更灵活的输出容器格式pcm_alaw(例如MOV或MKV)。
所有输入应具有相同的流 当第一个输入有音频但第二个输入没有音频时,您的播放器可能仍然可以播放输出。如果仅第二个输入具有音频,则可能无法正常播放。但是,为了实现最佳兼容性,连接所有输入时,应具有相同数量的相同类型的流。
将音频添加到第二个流中,或从第一流中省略音频。例如,将无声音频添加到第二个输入的一种方法是使用anullsrc过滤器:
ffmpeg -i input -filter_complex "anullsrc=channel_layout=mono:sample_rate=8000[a]"
-map 0 -map "[a]" -c:v copy -c:a pcm_alaw -shortest output.mov
此示例中的视频是流复制的,因此不进行重新编码。
Protocol not found
Protocol not found. Did you mean file:
分析:这个报错的重点是 file,而不是前边的 format 类型,如果我们只看上半句就会被带跑偏。 解决方法就是在编译 ffmpeg 文件的时候,增加–enable-protocols 选项。 如果 ffmpeg 可执行文件不是自己编译的话,可以更换其他全量编译的 ffmpeg,或者直接去官网下载。
Invalid data found
Invalid data found when processing input
分析:这个问题就比较笼统了,很可能的原因是封装格式不支持造成的。 所以需要检查一下文件格式是否在ffmpeg支持的封装格式列表中,使用ffmpeg -formats命令进行查看。
如果 ffmpeg 可执行文件不是自己编译的话,可以更换其他全量编译的 ffmpeg,或者直接去官网下载。
Decoder not found
Decoder (codec pcm_f32le) not found for input stream #0:0
分析:需要增加对音频 pcm 编码格式的支持,尽管报错信息提示是没有 pcm_f32le 解码器,但其实 pcm 的格式非常多。 简单点的话,我们可能只需要增加–enable-decoder=pcm_f32le 编译选项。但实际场景中很可能出现不同编码的 pcm 格式, 需要我们提前预判可能出现的不兼容情况。我们先来看一下 pcm 编码都有哪些格式:
pcm_s16be pcm_s64le pcm_zork
pcm_alaw pcm_s16be_planar pcm_s8
pcm_bluray pcm_s16le pcm_s8_planar
pcm_dvd pcm_s16le_planar pcm_u16be
pcm_f16le pcm_s24be pcm_u16le
pcm_f24le pcm_s24daud pcm_u24be
pcm_f32be pcm_s24le pcm_u24le
pcm_f32le pcm_s24le_planar pcm_u32be
pcm_f64be pcm_s32be pcm_u32le
pcm_f64le pcm_s32le pcm_u8
pcm_lxf pcm_s32le_planar pcm_vidc
pcm_mulaw pcm_s64be
不需要一项一项增加 比如
--enable-decoder=pcm_alaw --enable-decoder=pcm_s16be --enbale-decoder=pcm_s64le ...
直接正则匹配一下:
--enable-decoder=pcm*
如果 ffmpeg 可执行文件不是自己编译的话,可以更换其他全量编译的 ffmpeg,或者直接去官网下载。
Error reinitializing filters
Error: ffmpeg exited with code 1: Press [q] to stop, [?] for help’aresample’ filter not present, cannot convert audio formats.
Error reinitializing filters!Failed to inject frame into filter network: Invalid argumentError while processing the decoded data for stream #0:0Conversion failed!
分析:在进行音频转码的过程中必须要进行音频重采样,即使是 pcm 转 aac 编码,这个过程也不能省略。
编译 ffmpeg 的时候需要增加 –enable-filter=aresample 配置选项,这个选项大概会让 ffmpeg 变大 30KB 左右。
如果 ffmpeg 可执行文件不是自己编译的话,可以更换其他全量编译的 ffmpeg,或者直接去官网下载。
An error occured when convert ts
An error occured when convert ts: ffmpeg exited with code 1: *.mp4: Invalid data found when processing input
分析:报错的原因是不能识别 ts 格式的视频文件,需要增加 –enable-muxer=mpegts 和 –enable-demuxer=mpegts 配置选项,这两个选项大概占 600KB 左右。 注意:ts 的封装格式对应的配置项名字是 mpegts,而不是简单的 ts,但我们还可以理解二者的关系,毕竟有 ts 的影子。 但有一个例子就没有那么好理解了,它就是 webm 格式,如果你不了解,你绝对想不到它对应的配置项是 matroska。
Option did not match anything
WARNING: Option –enable-demuxer=mp4 did not match anything
分析:mp4 解封装格式的配置项 mov,不是 mp4,所以在编译 ffmpeg 的时候需要配置 –enable-demuxer=mov。 mov 的解复用器可以用于 mp4。 其实 mov,mp4,m4a,3gp,3g2,mj2 它们共用一个解复用器 mov,如果想了解源码可以查看文件: libavformat/mov.c 。
Unknown bitstream filter h264_mp4toannexb
An error occured when convert ts: ffmpeg exited with code 1: Unknown bitstream filter h264_mp4toannexb
分析:H264 的封装格式有两种,annexb 和 mp4,前者属于传统模式,很多解码器只支持这种格式,它有 startcode,SPS 和 PPS 存储在 ES 中, 后者没有 startcode,SPS 和 PPS 存储在 container 容器中。
ffmpeg 中定义的 H264 封装格式是 annexb 格式的。对于某些封装格式(比如 MP4/FLV/MKV 等)中的 H.264, 需要用到名称为"h264_mp4toannexb"的 bitstream filter。
解决这个问题就是增加编译配置项 –enable-bsf=h264_mp4toannexb。
Unknown bitstream filter aac_adtstoasc
An error occured when convert ts: ffmpeg exited with code 1: Unknown bitstream filter aac_adtstoasc
分析:对于某些封装格式(比如 MP4/FLV/MKV 等)中的 AAC,需要用到名称为"aac_adtstoasc"的 bitstream filter。
所以解决这个问题就是增加编译配置项: –enable-bsf=aac_adtstoasc
Could not find codec parameters
[mpegts @ 020d6180] Could not find codec parameters for stream 1 (Audio: aac ([15][0][0][0] / 0x000F), 0 channels): unspecified sample rate
Consider increasing the value for the ‘analyzeduration’ and ‘probesize’ options
Input #0, mpegts, from ‘concat:C:\Users\PC\Downloads\20200601193107-2.webm.mp4.ts|C:\Users\PC\Downloads\20200601193107-1.webm.mp4.ts’:
Duration: 00:02:42.67, start: 1.400000, bitrate: 1817 kb/s
Program 1
Metadata:
service_name : Service01
service_provider: FFmpeg
Stream #0:0[0x100]: Video: h264 (Constrained Baseline) ([27][0][0][0] / 0x001B), yuv420p, 1280x720, 14.83 tbr, 90k tbn, 60 tbc
Stream #0:1[0x101](eng): Audio: aac ([15][0][0][0] / 0x000F), 0 channels
[mp4 @ 01f79580] sample rate not set
sCould not write header for output file #0 (incorrect codec parameters ?): Invalid argument
分析:当 ts 封装转 mp4 封装的时候,尽管音频编码 aac 和视频编码 h264 是拷贝复用关系,
但是某些参数还是需要进行解码 aac 或者 h264 才能拿到的,
所以 ffmpeg 必须支持 –enable-decoder=aac 以及 –enable-decoder=h264。
ffmpeg 参考网站