Skip to content

Commit cb31033

Browse files
committed
ngx-live: fix audio ends before video issue
the segmenter may try to cut a segment at the audio end timestamp, there is no video key frame at this timestamp - the video segments come out empty, and some of the audio frames are discarded. then it tries to create another segment, but the segments of all tracks come out empty, and the channel is failed. the fix is to avoid averaging the min/max split pts when the span is too large. in this case, the target pts will be the audio end pts, the video segments will come out empty, and the trailing audio will be discarded.
1 parent 0da2ca6 commit cb31033

File tree

5 files changed

+212
-2
lines changed

5 files changed

+212
-2
lines changed

nginx-live-module/src/ngx_live_segmenter.c

+22-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ typedef struct {
5959

6060
ngx_msec_t candidate_margin;
6161
ngx_msec_t keyframe_alignment_margin;
62+
ngx_msec_t max_span_average;
6263

6364
ngx_uint_t ready_threshold;
6465
ngx_uint_t initial_ready_threshold;
@@ -208,6 +209,7 @@ typedef struct {
208209

209210
uint32_t candidate_margin;
210211
uint32_t keyframe_alignment_margin;
212+
uint32_t max_span_average;
211213

212214
uint32_t ready_duration;
213215
uint32_t initial_ready_duration;
@@ -309,6 +311,13 @@ static ngx_command_t ngx_live_segmenter_commands[] = {
309311
offsetof(ngx_live_segmenter_preset_conf_t, keyframe_alignment_margin),
310312
NULL },
311313

314+
{ ngx_string("segmenter_max_span_average"),
315+
NGX_LIVE_MAIN_CONF|NGX_LIVE_PRESET_CONF|NGX_CONF_TAKE1,
316+
ngx_conf_set_msec_slot,
317+
NGX_LIVE_PRESET_CONF_OFFSET,
318+
offsetof(ngx_live_segmenter_preset_conf_t, max_span_average),
319+
NULL },
320+
312321
{ ngx_string("segmenter_ready_threshold"),
313322
NGX_LIVE_MAIN_CONF|NGX_LIVE_PRESET_CONF|NGX_CONF_TAKE1,
314323
ngx_conf_set_num_slot,
@@ -2391,7 +2400,13 @@ ngx_live_segmenter_get_segment_times(ngx_live_channel_t *channel,
23912400
continue;
23922401
}
23932402

2394-
cur_pts = (max[i] + min[i]) / 2;
2403+
if (max[i] - min[i] < cctx->max_span_average) {
2404+
cur_pts = (max[i] + min[i]) / 2;
2405+
2406+
} else {
2407+
cur_pts = candidates.elts[i].pts;
2408+
}
2409+
23952410
if (target_pts == NGX_LIVE_INVALID_PTS ||
23962411
ngx_abs_diff(cur_pts, boundary_pts) <
23972412
ngx_abs_diff(target_pts, boundary_pts))
@@ -3629,6 +3644,8 @@ ngx_live_segmenter_channel_init(ngx_live_channel_t *channel, void *ectx)
36293644
spcf->candidate_margin, 1000, channel->timescale);
36303645
cctx->keyframe_alignment_margin = ngx_live_rescale_time(
36313646
spcf->keyframe_alignment_margin, 1000, channel->timescale);
3647+
cctx->max_span_average = ngx_live_rescale_time(
3648+
spcf->max_span_average, 1000, channel->timescale);
36323649

36333650
cctx->create.data = channel;
36343651
cctx->create.handler = ngx_live_segmenter_create_handler;
@@ -3890,6 +3907,7 @@ ngx_live_segmenter_create_preset_conf(ngx_conf_t *cf)
38903907

38913908
conf->candidate_margin = NGX_CONF_UNSET_MSEC;
38923909
conf->keyframe_alignment_margin = NGX_CONF_UNSET_MSEC;
3910+
conf->max_span_average = NGX_CONF_UNSET_MSEC;
38933911

38943912
conf->ready_threshold = NGX_CONF_UNSET_UINT;
38953913
conf->initial_ready_threshold = NGX_CONF_UNSET_UINT;
@@ -3945,6 +3963,9 @@ ngx_live_segmenter_merge_preset_conf(ngx_conf_t *cf, void *parent, void *child)
39453963
ngx_conf_merge_msec_value(conf->keyframe_alignment_margin,
39463964
prev->keyframe_alignment_margin, 500);
39473965

3966+
ngx_conf_merge_msec_value(conf->max_span_average,
3967+
prev->max_span_average, 500);
3968+
39483969
ngx_conf_merge_uint_value(conf->ready_threshold,
39493970
prev->ready_threshold, 150);
39503971

nginx-live-module/test/nginx.conf

+1-1
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ http {
167167
location /ksmp_proxy/ {
168168
internal;
169169
proxy_pass http://127.0.0.1:8001/ksmp/;
170-
subrequest_output_buffer_size 5m;
170+
subrequest_output_buffer_size 20m;
171171
}
172172

173173
# curl localhost:8001/sgts/<channel_id>/<bucket_id>/seg-<segment_index>-s<track_int_id>.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from test_base import *
2+
3+
# EXPECTED:
4+
# 16 sec first video + audio
5+
# 2 sec first video, no audio
6+
# 10 sec second video + audio
7+
8+
def test(channelId=CHANNEL_ID):
9+
st = KmpSendTimestamps()
10+
11+
nl = setupChannelTimeline(channelId)
12+
13+
rv = KmpMediaFileReader(TEST_VIDEO_HIGH, 0)
14+
ra = KmpMediaFileReader(TEST_VIDEO_HIGH, 1)
15+
16+
sv, sa = createVariant(nl, VARIANT_ID, [('v1', 'video'), ('a1', 'audio')])
17+
18+
kmpSendStreams([
19+
(rv, sv),
20+
(ra, sa),
21+
], st, 17.5, realtime=5)
22+
23+
kmpSendStreams([
24+
(rv, sv),
25+
(ra, KmpNullSender()),
26+
], st, 2, realtime=5)
27+
28+
kmpSendEndOfStream([sv, sa])
29+
30+
time.sleep(2)
31+
32+
initialFrameId = 1000000
33+
34+
rv = KmpMediaFileReader(TEST_VIDEO1, 0)
35+
ra = KmpMediaFileReader(TEST_VIDEO1, 1)
36+
37+
sv, sa = createVariant(nl, VARIANT_ID, [('v1', 'video'), ('a1', 'audio')], initialFrameId=initialFrameId)
38+
39+
kmpSendStreams([
40+
(rv, sv),
41+
(ra, sa),
42+
], st, 10, realtime=5)
43+
44+
kmpSendEndOfStream([sv, sa])
45+
46+
# deactivate the timeline
47+
nl.timeline.update(NginxLiveTimeline(id=TIMELINE_ID, end_list='on'))
48+
49+
testDefaultStreams(channelId, __file__)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
URL: /master.m3u8
2+
HEADERS: 200 application/vnd.apple.mpegurl
3+
BODY: #EXTM3U
4+
#EXT-X-INDEPENDENT-SEGMENTS
5+
6+
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=126573,AVERAGE-BANDWIDTH=114074,RESOLUTION=160x120,FRAME-RATE=15.000,CODECS="avc1.64000b,mp4a.40.2"
7+
index-svar1.m3u8
8+
9+
URL: /index-svar1.m3u8
10+
HEADERS: 200 application/vnd.apple.mpegurl
11+
BODY: #EXTM3U
12+
#EXT-X-TARGETDURATION:8
13+
#EXT-X-VERSION:6
14+
#EXT-X-MEDIA-SEQUENCE:0
15+
#EXT-X-DISCONTINUITY-SEQUENCE:0
16+
#EXT-X-INDEPENDENT-SEGMENTS
17+
#EXT-X-ALLOW-CACHE:YES
18+
#EXT-X-MAP:URI="init-1-svar1.mp4"
19+
#EXT-X-PROGRAM-DATE-TIME:2020-01-01T00:00:00.000+00:00
20+
#EXTINF:8.342,
21+
#EXT-X-BITRATE:4971
22+
seg-1-svar1.m4s
23+
#EXTINF:7.966,
24+
#EXT-X-BITRATE:16215
25+
seg-2-svar1.m4s
26+
#EXT-X-DISCONTINUITY
27+
#EXT-X-PROGRAM-DATE-TIME:2020-01-01T00:00:16.342+00:00
28+
#EXTINF:3.200,
29+
#EXT-X-BITRATE:9141
30+
seg-3-svar1.m4s
31+
#EXT-X-DISCONTINUITY
32+
#EXT-X-MAP:URI="init-5-svar1.mp4"
33+
#EXT-X-PROGRAM-DATE-TIME:2020-01-01T00:00:19.630+00:00
34+
#EXTINF:3.956,
35+
#EXT-X-BITRATE:92
36+
seg-5-svar1.m4s
37+
#EXTINF:3.934,
38+
#EXT-X-BITRATE:123
39+
seg-6-svar1.m4s
40+
#EXTINF:1.989,
41+
#EXT-X-BITRATE:124
42+
seg-7-svar1.m4s
43+
#EXT-X-ENDLIST
44+
45+
URL: /init-1-svar1.mp4
46+
HEADERS: 200 video/mp4
47+
BODY: SIZE: 1106, MD5: 44623fdc2bb6eb1183e22c022be3e765
48+
49+
URL: /seg-1-svar1.m4s
50+
HEADERS: 200 video/mp4
51+
BODY: SIZE: 5180236, MD5: 0d015341c4fbb773cfcc90ecbce81d0d
52+
53+
URL: /seg-2-svar1.m4s
54+
HEADERS: 200 video/mp4
55+
BODY: SIZE: 16014394, MD5: 309376ada6e59c0222bf0bab74028e1e
56+
57+
URL: /seg-3-svar1.m4s
58+
HEADERS: 200 video/mp4
59+
BODY: SIZE: 3732604, MD5: 12a71bf0970f3bafec0a7b800eb51b78
60+
61+
URL: /init-5-svar1.mp4
62+
HEADERS: 200 video/mp4
63+
BODY: SIZE: 1106, MD5: ca1c220b1d37d697270c21bce220cdef
64+
65+
URL: /seg-5-svar1.m4s
66+
HEADERS: 200 video/mp4
67+
BODY: SIZE: 46063, MD5: 3cd142c193f879afc375070a3e30d431
68+
69+
URL: /seg-6-svar1.m4s
70+
HEADERS: 200 video/mp4
71+
BODY: SIZE: 61926, MD5: 8ab6413490c066bb6cbed25d13cdc626
72+
73+
URL: /seg-7-svar1.m4s
74+
HEADERS: 200 video/mp4
75+
BODY: SIZE: 32681, MD5: c03e8d0a6bd6bbb23a5844be89e1b6ea
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
URL: /master.m3u8
2+
HEADERS: 200 application/vnd.apple.mpegurl
3+
BODY: #EXTM3U
4+
#EXT-X-INDEPENDENT-SEGMENTS
5+
6+
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=144154,AVERAGE-BANDWIDTH=131383,RESOLUTION=160x120,FRAME-RATE=15.000,CODECS="avc1.64000b,mp4a.40.2"
7+
index-svar1.m3u8
8+
9+
URL: /index-svar1.m3u8
10+
HEADERS: 200 application/vnd.apple.mpegurl
11+
BODY: #EXTM3U
12+
#EXT-X-TARGETDURATION:8
13+
#EXT-X-VERSION:3
14+
#EXT-X-MEDIA-SEQUENCE:0
15+
#EXT-X-DISCONTINUITY-SEQUENCE:0
16+
#EXT-X-INDEPENDENT-SEGMENTS
17+
#EXT-X-ALLOW-CACHE:YES
18+
#EXT-X-PROGRAM-DATE-TIME:2020-01-01T00:00:00.000+00:00
19+
#EXTINF:8.342,
20+
#EXT-X-BITRATE:5124
21+
seg-1-svar1.ts
22+
#EXTINF:7.966,
23+
#EXT-X-BITRATE:16612
24+
seg-2-svar1.ts
25+
#EXT-X-DISCONTINUITY
26+
#EXT-X-PROGRAM-DATE-TIME:2020-01-01T00:00:16.342+00:00
27+
#EXTINF:3.200,
28+
#EXT-X-BITRATE:9369
29+
seg-3-svar1.ts
30+
#EXT-X-DISCONTINUITY
31+
#EXT-X-PROGRAM-DATE-TIME:2020-01-01T00:00:19.630+00:00
32+
#EXTINF:3.956,
33+
#EXT-X-BITRATE:112
34+
seg-5-svar1.ts
35+
#EXTINF:3.934,
36+
#EXT-X-BITRATE:145
37+
seg-6-svar1.ts
38+
#EXTINF:1.989,
39+
#EXT-X-BITRATE:152
40+
seg-7-svar1.ts
41+
#EXT-X-ENDLIST
42+
43+
URL: /seg-1-svar1.ts
44+
HEADERS: 200 video/mp2t
45+
BODY: SIZE: 5348788, MD5: f120a8a081f82c373c2699c75a46c728
46+
47+
URL: /seg-2-svar1.ts
48+
HEADERS: 200 video/mp2t
49+
BODY: SIZE: 16418228, MD5: 7bb081750052c1cb319753d8110c96f5
50+
51+
URL: /seg-3-svar1.ts
52+
HEADERS: 200 video/mp2t
53+
BODY: SIZE: 3826740, MD5: 61905a4e992990544087081a6855f90a
54+
55+
URL: /seg-5-svar1.ts
56+
HEADERS: 200 video/mp2t
57+
BODY: SIZE: 57716, MD5: 9aba89a2ef46c155e15b0f587bad8a61
58+
59+
URL: /seg-6-svar1.ts
60+
HEADERS: 200 video/mp2t
61+
BODY: SIZE: 72756, MD5: 57df738394e340264cde12be20eaedfa
62+
63+
URL: /seg-7-svar1.ts
64+
HEADERS: 200 video/mp2t
65+
BODY: SIZE: 39668, MD5: 4abc468def0cb3c8a592da711861fd1c

0 commit comments

Comments
 (0)