【该文章,是属于ffmpeg的细枝末节,会写的比较啰嗦】
【不同的转码环境,会有代码流程的不同】
首先普及下:
时间戳,DTS(decoding time stamp),PTS(presention time stamp),CTS(current time stamp)。
ffmepg中的时间戳,是以微秒为单位,关乎timebase变量,它是作为dts、pts的时间基准粒度,数值会很大。
其中函数av_rescale_q()是很多的,AV_ROUND_NEAR_INF是就近、中间从零,av_rescale_rnd它是计算a*b/c,传入参数为八字节,为避免溢出,里面做了与INT_MAX的比较,分开计算。
先看前端的packets parsing,也就是av_read_frame函数:
const int genpts = s->flags & AVFMT_FLAG_GENPTS;关于该flags,各标志位的说明在 avformat.h
//ffmpeg.c, opt_input_file() ic->flags |= AVFMT_FLAG_NONBLOCK;进入 read_frame_internal()函数,【该函数:http://www.chinavideo.org/viewthread.php?action=printable&tid=13846】
while (!got_packet && !s->parse_queue) {...} //got_packet有效则返回
在 ff_read_packet() 函数中,
ret= s->iformat->read_packet(s, pkt);demux出一个包,在
if(!pktl && st->request_probe <= 0)返回。AVFrame:need_parsing此时是无效,
compute_pkt_fields(s, st, NULL, pkt);
got_packet = 1;到此,we output pkt as it is。
来看看transcode中在parser和解码前做了什么,
if (pkt.dts != AV_NOPTS_VALUE && ist->next_dts != AV_NOPTS_VALUE && !copy_ts) { int64_t pkt_dts = av_rescale_q(pkt.dts, ist->st->time_base, AV_TIME_BASE_Q); int64_t delta = pkt_dts - ist->next_dts; if (is->iformat->flags & AVFMT_TS_DISCONT) { if(delta < -1LL*dts_delta_threshold*AV_TIME_BASE || (delta > 1LL*dts_delta_threshold*AV_TIME_BASE && ist->st->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) || pkt_dts+1<ist->pts){ input_files[ist->file_index]->ts_offset -= delta; pkt.dts-= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); if (pkt.pts != AV_NOPTS_VALUE) pkt.pts-= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base); } } else { if ( delta < -1LL*dts_error_threshold*AV_TIME_BASE || (delta > 1LL*dts_error_threshold*AV_TIME_BASE && ist->st->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) || pkt_dts+1<ist->pts){ pkt.dts = AV_NOPTS_VALUE; } if (pkt.pts != AV_NOPTS_VALUE){ int64_t pkt_pts = av_rescale_q(pkt.pts, ist->st->time_base, AV_TIME_BASE_Q); delta = pkt_pts - ist->next_dts; if ( delta < -1LL*dts_error_threshold*AV_TIME_BASE || (delta > 1LL*dts_error_threshold*AV_TIME_BASE && ist->st->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) || pkt_pts+1<ist->pts) { pkt.pts = AV_NOPTS_VALUE;//fallenink:如果pts小了,这里置为无效。则在output_packet中置为ist->dts } } } }
再看解码 InputStream:【关注InputStream结构中的dts、next_dts、pts、next_pts】
在output_packet中,用saw_first_ts标记给dts、pts做一次性初始化,等
if (!ist->saw_first_ts) { ist->dts = ist->st->avg_frame_rate.num ? - ist->st->codec->has_b_frames * AV_TIME_BASE / av_q2d(ist->st->avg_frame_rate) : 0; ist->pts = 0; if (pkt != NULL && pkt->pts != AV_NOPTS_VALUE && !ist->decoding_needed) { ist->dts += av_rescale_q(pkt->pts, ist->st->time_base, AV_TIME_BASE_Q); ist->pts = ist->dts; //unused but better to set it to a value thats not totally wrong } ist->saw_first_ts = 1; } if (ist->next_dts == AV_NOPTS_VALUE) ist->next_dts = ist->dts; if (ist->next_pts == AV_NOPTS_VALUE) ist->next_pts = ist->pts;
if (pkt->dts != AV_NOPTS_VALUE) {//这里如果pkt的时戳被置为无效了,则不作ist的更新 ist->next_dts = ist->dts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); if (ist->st->codec->codec_type != AVMEDIA_TYPE_VIDEO || !ist->decoding_needed) //fallenink: "not video" or "copy" ist->next_pts = ist->pts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); }接着,音频直接是copy(In my case)
if (!ist->decoding_needed) {
rate_emu_sleep(ist);
ist->dts = ist->next_dts;
switch (ist->st->codec->codec_type) {
case AVMEDIA_TYPE_AUDIO:
ist->next_dts += ((int64_t)AV_TIME_BASE * ist->st->codec->frame_size) /
ist->st->codec->sample_rate;
break;
case AVMEDIA_TYPE_VIDEO:
//...
}
ist->pts = ist->dts;
ist->next_pts = ist->next_dts;
}
for (i = 0; pkt && i < nb_output_streams; i++) { OutputStream *ost = output_streams[i]; if (!check_output_constraints(ist, ost) || ost->encoding_needed) continue; do_streamcopy(ist, ost, pkt); }在解码前,
ist->pts = ist->next_pts;
ist->dts = ist->next_dts;
在decode_video函数中,
pkt->dts = av_rescale_q(ist->dts, AV_TIME_BASE_Q, ist->st->time_base);//这里将输入流的时戳,更新给pkt
在avcodec_decode_video2中,单线程情况中,
ret = avctx->codec->decode(avctx, picture, got_picture_ptr, &tmp); picture->pkt_dts= avpkt->dts;
如果解码有输出,则
if (*got_picture_ptr){ avctx->frame_number++; picture->best_effort_timestamp = guess_correct_pts(avctx, picture->pkt_pts, picture->pkt_dts); }
调用guess_correct_pts,一般就是返回picture->pkt_dts的值,再回到decode_video中,
best_effort_timestamp = av_frame_get_best_effort_timestamp(decoded_frame); if(best_effort_timestamp != AV_NOPTS_VALUE) ist->next_pts = ist->pts = av_rescale_q(decoded_frame->pts = best_effort_timestamp, ist->st->time_base, AV_TIME_BASE_Q);
函数av_frame_get_best_effort_timestamp()定义在哪里呢?是这样的:
/* ./libavcodec/utils.c */
#define MAKE_ACCESSORS(str, name, type, field) \
type av_##name##_get_##field(const str *s) { return s->field; } \
void av_##name##_set_##field(str *s, type v) { s->field = v; }
继续向后看,在后处理前先做下预处理(某些编解码往往要加边的)
pre_process_video_frame(ist, (AVPicture *)decoded_frame, &buffer_to_free);0.11.1版本中已经在用filter,替代原来的swscale模块了,
if (ist->dr1 && decoded_frame->type==FF_BUFFER_TYPE_USER && !changed) {//fallenink: "type" come from "codec_get_buffer" FrameBuffer *buf = decoded_frame->opaque; AVFilterBufferRef *fb = avfilter_get_video_buffer_ref_from_arrays( decoded_frame->data, decoded_frame->linesize, AV_PERM_READ | AV_PERM_PRESERVE, ist->st->codec->width, ist->st->codec->height, ist->st->codec->pix_fmt); avfilter_copy_frame_props(fb, decoded_frame); fb->buf->priv = buf; fb->buf->free = filter_release_buffer; av_assert0(buf->refcount>0); buf->refcount++; av_buffersrc_add_ref(ist->filters[i]->filter, fb, AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT | AV_BUFFERSRC_FLAG_NO_COPY); } else if(av_buffersrc_add_frame(ist->filters[i]->filter, decoded_frame, 0)<0) {//fallenink: codec buffer copy to filter av_log(NULL, AV_LOG_FATAL, "Failed to inject frame into filter network\n"); exit_program(1); }
这里dr1和decoded_frame->type的type的初始化在init_input_stream中,可以看到,
ist->dr1 = (codec->capabilities & CODEC_CAP_DR1) && !do_deinterlace; if (codec->type == AVMEDIA_TYPE_VIDEO && ist->dr1) { ist->st->codec->get_buffer = codec_get_buffer; ist->st->codec->release_buffer = codec_release_buffer; ist->st->codec->opaque = ist; }capabilities对应于AVCodec结构体定义中给的值,比如 h264.c中,
AVCodec ff_h264_decoder = { .name = "h264", //...省略 .capabilities = /*CODEC_CAP_DRAW_HORIZ_BAND |*/ CODEC_CAP_DR1 | CODEC_CAP_DELAY | CODEC_CAP_SLICE_THREADS | CODEC_CAP_FRAME_THREADS, //...省略 };
avfilter_copy_frame_props(fb, decoded_frame); fb->buf->priv = buf; fb->buf->free = filter_release_buffer; av_assert0(buf->refcount>0); buf->refcount++; av_buffersrc_add_ref(ist->filters[i]->filter, fb, AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT | AV_BUFFERSRC_FLAG_NO_COPY);
if (avpkt.duration) { duration = av_rescale_q(avpkt.duration, ist->st->time_base, AV_TIME_BASE_Q); } else if(ist->st->codec->time_base.num != 0 && ist->st->codec->time_base.den != 0) { int ticks= ist->st->parser ? ist->st->parser->repeat_pict+1 : ist->st->codec->ticks_per_frame; duration = ((int64_t)AV_TIME_BASE * ist->st->codec->time_base.num * ticks) / ist->st->codec->time_base.den; } else duration = 0; if(ist->dts != AV_NOPTS_VALUE && duration) { ist->next_dts += duration; }else ist->next_dts = AV_NOPTS_VALUE; if (got_output) ist->next_pts += duration; //FIXME the duration is not correct in some cases
frame_pts = AV_NOPTS_VALUE; if (picref->pts != AV_NOPTS_VALUE) { filtered_frame->pts = frame_pts = av_rescale_q(picref->pts, ost->filter->filter->inputs[0]->time_base, ost->st->codec->time_base) - av_rescale_q(of->start_time, AV_TIME_BASE_Q, ost->st->codec->time_base); if (of->start_time && filtered_frame->pts < 0) { avfilter_unref_buffer(picref); continue; } } //... avfilter_fill_frame_from_video_buffer_ref(filtered_frame, picref); filtered_frame->pts = frame_pts;进入到do_video_out()函数中,
if(ist && ist->st->start_time != AV_NOPTS_VALUE && ist->st->first_dts != AV_NOPTS_VALUE && ost->frame_rate.num) duration = 1/(av_q2d(ost->frame_rate) * av_q2d(enc->time_base)); sync_ipts = in_picture->pts; delta = sync_ipts - ost->sync_opts + duration; switch (format_video_sync) { /*ost->sync_opts = lrint(sync_ipts);*/} in_picture->pts = ost->sync_opts; if (pkt.pts == AV_NOPTS_VALUE && !(enc->codec->capabilities & CODEC_CAP_DELAY)) pkt.pts = ost->sync_opts; if (pkt.pts != AV_NOPTS_VALUE) pkt.pts = av_rescale_q(pkt.pts, enc->time_base, ost->st->time_base); if (pkt.dts != AV_NOPTS_VALUE) pkt.dts = av_rescale_q(pkt.dts, enc->time_base, ost->st->time_base);
接下来就不多说了,直接就送到mux,然后写到远端去了。
_______________________________________________无聊的分割线_________________________________________________
下面谈谈我遇到的问题吧,上面说了一些流程,没什么技术含量,只是作为一个梳理,不梳理很难发现并处理好细节问题。
现象一:流媒体转码中(前端为多线程解码),如果去掉ff_interleave_packet_per_dts()函数中的输出限制,改成有任意流都送去muxer,发现此时出去的流,视频是晚于(时戳值偏小)音频的。原因:正因为前端是多线程解码,如果不把线程数目控制好,cpu过载,会有packet缓冲在线程中,没有得到及时的处理,导致该现象。
现象二:基于现象一,改为单线程处理,如果多数时候,发出去的流,视频又是提前于音频的,甚至数秒。原因:(说明一下,这里的原因只是在下遇到的情况,不具普遍性。)服务器端一开始发了好一些视频的,时戳为0的包,小于了ist->next_dts则被替换,而ist->next_dts是按序,按duration_frame递增的,所以出现该情况,这里我在解码后将这样的情况,置为got output none。这儿是不能释放frame的,因为出来的几片内存实在codec中分配的,然后拷贝去了滤波器模块中。在我这里,导致了不同步问题,重点在这里,时戳问题是表面的,我们大家关注的还是音视频同步问题。
另外,在现象一的基础上,我这里是往fms推流,视频时戳总是晚于音频(或者早于音频很多),出现了一段时间后,rtmp的写端在write,select总是失败,具体原因还不明白。但是在现象二中,却不会出现这个问题。why???转码过程中,当然不会有严格处理同步的能力,它是以前端过来的packet时戳为根据的,但转码后端要做好音视频包的交错,也就是ff_interleave_packet_per_dts()这个函数所做的,当然如果mux本身提供interleave_packet则优先使用。
另外有个问题,就是当解码端收到,视频帧被拆分的情况,ffmpeg会连续收到几个包为pts相同的情况,下面这段代码关注下:
if(ist->dts != AV_NOPTS_VALUE && duration) { ist->next_dts += duration; }else ist->next_dts = AV_NOPTS_VALUE; if (got_output) ist->next_pts += duration; //FIXME the duration is not correct in some cases
按理说,当解码没有输出的时候,是不应该next_pts累加duration的。在我这里,视频需要编解码,音频直接copy,导致了不同步,以及在音视频交错那边视频帧pts增长过快,在那里囤积,导致客户端视频卡住。