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,发现如下内容:
以上描述的是一个问题,还有另外一个问题,就是 gstream采用64位的时间戳,且单位为纳秒,而pts只有33位,且单位是按照90kHZ来算的 ,因此简单的让gstreamer的时间戳等于pts是不可行的,以下是在gstffmpegutils.h中定义的函数:
上面的函数在gstffmpegdemux.c中的loop函数里被调用,用于gstreamer的时间戳的计算。
为了避免累积误差,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;
如上面所示表示PTS按取值被分成两个区域,其中1区域占据平分成3个部分的两边,2区域占据当中。
为了避免累积误差,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;
先来描述一下问题,采用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:<demuxer> pkt pts:26:30:43.583444444
ffmpeg gstffmpegdemux.c:1378:gst_ffmpegdemux_loop:<demuxer> pkt pts:0:00:00.025755556
ffmpeg gstffmpegdemux.c:1548:gst_ffmpegdemux_loop:<demuxer> 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的条件就造成转码“结束”。
ffmpeg gstffmpegdemux.c:1378:gst_ffmpegdemux_loop:<demuxer> pkt pts:0:00:00.025755556
ffmpeg gstffmpegdemux.c:1548:gst_ffmpegdemux_loop:<demuxer> dropping buffer out of segment, stream eos
ffmpeg gstffmpegdemux.c:407:gst_ffmpegdemux_is_eos: stream 0 0xb561e6c8 eos:0
以上描述的是一个问题,还有另外一个问题,就是 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;
}
由于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。
为了避免累积误差,gst_last_timestamp的计算采用pts_max*循环次数,然后再转换,而不是gst_last_timestamp += pts_max的转换。这就需要一个变量记载pts循环的次数,pts循环一次指的是pts达到最大值从零开始。
如下是ubuntu10.10 server版本源代码的修改:
60a61,65
>
>
>
>
>
979a985,991
>
>
>
>
>
>
>
1029a1042,1047
>
>
>
>
>
>
1364a1383,1404
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1381,1382c1421,1422
<
<
---
>
>
1388,1389c1428,1429
<
<
---
>
>
如上面所示表示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。
为了避免累积误差,gst_last_timestamp的计算采用pts_max*循环次数,然后再转换,而不是gst_last_timestamp += pts_max的转换。这就需要一个变量记载pts循环的次数,pts循环一次指的是pts达到最大值从零开始。
如下是ubuntu10.10 server版本源代码的修改:
60a61,65
>
>
>
>
>
979a985,991
>
>
>
>
>
>
>
1029a1042,1047
>
>
>
>
>
>
1364a1383,1404
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1381,1382c1421,1422
<
<
---
>
>
1388,1389c1428,1429
<
<
---
>
>