ffdemux_mpegts是gstreamer的demux plugin,基于ffmpeg,在使用的时候发现处理实时流存在问题。
先来描述一下问题,采用gst-launch命令启动转码,命令如下:
gst-launch-0.10 udpsrc multicast-group=239.1.80.80 port=49500 ! queue ! ffdemux_mpegts name=demuxer ! queue ! ffdec_mp3 ! queue ! ffenc_mp2 ! queue ! ffmux_mpegts name=muxer preload=10000 maxdelay=500000 muxrate=3600000 ! udpsink host=239.100.100.100 port=12345 demuxer. ! queue ! queue ! mpeg2dec ! queue ! mpeg2enc format=3 bitrate=2900 closed-gop=true sequence-header-every-gop=true ! muxer. --gst-debug-level=2 > udp188-log.txt 2>&1 &
采用了ffdemux_mpegts来解复用mpegts流。采用udpsrc作为ts流的源,来自实时的数字电视。运行过程中会结束转码,而不是持续的运行。把debug的level设置为5,发现如下内容:
ffmpeg gstffmpegdemux.c:1378:gst_ffmpegdemux_loop: pkt pts:26:30:43.583444444
ffmpeg gstffmpegdemux.c:1378:gst_ffmpegdemux_loop: pkt pts:0:00:00.025755556
ffmpeg gstffmpegdemux.c:1548:gst_ffmpegdemux_loop: dropping buffer out of segment, stream eos
ffmpeg gstffmpegdemux.c:407:gst_ffmpegdemux_is_eos: stream 0 0xb561e6c8 eos:0
其中有pts:26:30:43.583444444,也就是pts已经达到了其最大值,因为是33位的,且按照90kHZ计时,刚好是26小时30分钟多一点。接下来就从0开始计数,这让ffdemux_mpegts以为流已经结束,因此发出了eos的events。由于音频和视频分别有自己的pts,另外由于pts并不是单调递增的,所以当音频和视频都满足了eos的条件就造成转码“结束”。
以上描述的是一个问题,还有另外一个问题,就是gstream采用64位的时间戳,且单位为纳秒,而pts只有33位,且单位是按照90kHZ来算的,因此简单的让gstreamer的时间戳等于pts是不可行的,以下是在gstffmpegutils.h中定义的函数:
static inline guint64
gst_ffmpeg_time_ff_to_gst (gint64 pts, AVRational base)
{
guint64 out;
if (pts == AV_NOPTS_VALUE){
out = GST_CLOCK_TIME_NONE;
} else {
AVRational bq = { 1, GST_SECOND };
out = av_rescale_q (pts, base, bq);
}
return out;
}
上面的函数在gstffmpegdemux.c中的loop函数里被调用,用于gstreamer的时间戳的计算。
由于gstreamer的时间戳是64为的,而pts是33位的,且pts并不是严格单调递增的,因此转换就更复杂一点。
如上面所示表示PTS按取值被分成两个区域,其中1区域占据平分成3个部分的两边,2区域占据当中。
- region1_right = 5秒,region1_left=95438(95443-5)秒。
- region2_left = 10秒,region2_right=15433(95443-10)秒。
- 当状态为1区域,如果pts_timestamp的值落在region2_left和region2_right之间,则变为2区域状态,并且gst_last_timestamp = max_pts*循环次数。
- 当状态为2区域,如果遇上pts_timestamp的值落在region1_left和region1_right之间,则变为1区域状态。
- 如果是1区域状态:
-
- 如果pts_timestamp值在1.1区域则gst_timestamp = gst_last_timestamp + max_pts + pts_timestamp。
- 如果pts_timestamp值在1.2区域则gst_timestamp = gst_last_timestamp + pts_timestamp。
- 如果是2区域状态,则gst_timestamp = gst_last_timestamp + pts_timestamp。
64位无符号数,单位是纳秒,能表示约5,6百年(我自己计算的,如果不准请提示),因此起始时间设置为0,可以放心使用。
为了避免累积误差,gst_last_timestamp的计算采用pts_max*循环次数,然后再转换,而不是gst_last_timestamp += pts_max的转换。这就需要一个变量记载pts循环的次数,pts循环一次指的是pts达到最大值从零开始。
如下是ubuntu10.10 server版本源代码的修改:
60a61,65
>
> gint region;
> guint64 pts_region1_left, pts_region1_right;
> guint64 pts_region2_left, pts_region2_right;
> guint64 gst_last_timestamp, pts_accumulate;
979a985,991
> stream->pts_accumulate = 0llu;
> //stream->gst_last_timestamp = (8589934591llu * 100000llu) / 9llu;//0llu;
> stream->gst_last_timestamp = 0llu;
> stream->pts_region1_right = 5000000000llu; //5s
> stream->pts_region1_left = 95438000000000llu; //95438s, 95443-5
> stream->pts_region2_left = 10000000000llu; //10s
> stream->pts_region2_right = 95433000000000llu; //max pts is about 95443s
1029a1042,1047
> if (tmp < stream->pts_region1_right || tmp > stream->pts_region1_left) {
> stream->region = 1;
> } else {
> stream->region = 2;
> }
>
1364a1383,1404
> if (stream->region == 1) {
> if (timestamp > stream->pts_region2_left && timestamp < stream->pts_region2_right) {
> GST_DEBUG_OBJECT (demux, "enter region 2");
> stream->pts_accumulate += 1;
> stream->gst_last_timestamp = (stream->pts_accumulate * 8589934591llu * 100000llu) / 9llu;
> stream->region = 2;
> }
> } else if (stream->region == 2) {
> if (timestamp < stream->pts_region1_right || timestamp > stream->pts_region1_left) {
> GST_DEBUG_OBJECT (demux, "enter region 1");
> stream->region = 1;
> }
> }
> if (stream->region == 1) {
> if (timestamp < stream->pts_region2_left) {
> timestamp += (stream->gst_last_timestamp + (8589934591llu * 100000llu) / 9llu);
> } else if (timestamp > stream->pts_region2_right) {
> timestamp += stream->gst_last_timestamp;
> }
> } else if (stream->region == 2) {
> timestamp += stream->gst_last_timestamp;
> }
1381,1382c1421,1422
< if (demux->start_time != -1 && demux->start_time > timestamp)
< goto drop;
---
> //if (demux->start_time != -1 && demux->start_time > timestamp)
> // goto drop;
1388,1389c1428,1429
< if (demux->segment.stop != -1 && timestamp > demux->segment.stop)
< goto drop;
---
> //if (demux->segment.stop != -1 && timestamp > demux->segment.stop)
> // goto drop;