记录排查直播流断流错误过程
FSPlayer 播放直播流时,出现以下错误,排查发现是 http_buf_read 函数在读取数据时,遇到了流结束的情况,导致返回了 AVERROR(EIO) 错误。具体错误日志如下:
1
2
3
4
5
6
7
8
[03-26 09:59:28.380] [FSPlayer] Stream ends prematurely at 2622243, should be 18446744073709551615
[03-26 09:59:28.380] [FSPlayer] av_read_frame error: Input/output error
[03-26 09:59:28.427] [FSPlayer] ffp_toggle_buffering: error: -5
[03-26 09:59:28.427] [FSPlayer] IO error: End of file
[03-26 09:59:28.427] [FSPlayer] Stream ends prematurely at 2622243, should be 18446744073709551615
[03-26 09:59:28.428] [FSPlayer] av_read_frame error: Input/output error
[03-26 09:59:28.428] [FSPlayer] ffp_toggle_buffering: eof
[03-26 09:59:28.432] [Live] live play err withCode -5
查到 Stream ends prematurely 的日志来自于 http_buf_read 函数,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
static int http_buf_read(URLContext *h, uint8_t *buf, int size)
{
HTTPContext *s = h->priv_data;
int len;
if (s->chunksize != UINT64_MAX) {
if (s->chunkend) {
return AVERROR_EOF;
}
if (!s->chunksize) {
char line[32];
int err;
do {
if ((err = http_get_line(s, line, sizeof(line))) < 0)
return err;
} while (!*line); /* skip CR LF from last chunk */
s->chunksize = strtoull(line, NULL, 16);
av_log(h, AV_LOG_TRACE,
"Chunked encoding data size: %"PRIu64"\n",
s->chunksize);
if (!s->chunksize && s->multiple_requests) {
http_get_line(s, line, sizeof(line)); // read empty chunk
s->chunkend = 1;
return 0;
}
else if (!s->chunksize) {
av_log(h, AV_LOG_DEBUG, "Last chunk received, closing conn\n");
ffurl_closep(&s->hd);
return 0;
}
else if (s->chunksize == UINT64_MAX) {
av_log(h, AV_LOG_ERROR, "Invalid chunk size %"PRIu64"\n",
s->chunksize);
return AVERROR(EINVAL);
}
}
size = FFMIN(size, s->chunksize);
}
/* read bytes from input buffer first */
len = s->buf_end - s->buf_ptr;
if (len > 0) {
if (len > size)
len = size;
memcpy(buf, s->buf_ptr, len);
s->buf_ptr += len;
} else {
uint64_t target_end = s->end_off ? s->end_off : s->filesize;
if ((!s->willclose || s->chunksize == UINT64_MAX) && s->off >= target_end)
return AVERROR_EOF;
len = size;
if (s->filesize > 0 && s->filesize != UINT64_MAX && s->filesize != INT32_MAX) {
int64_t unread = s->filesize - s->off;
if (len > unread)
len = (int)unread;
}
if (len > 0)
len = ffurl_read(s->hd, buf, len);
if ((!len || len == AVERROR_EOF) &&
(!s->willclose || s->chunksize == UINT64_MAX) && s->off < target_end) {
av_log(h, AV_LOG_ERROR,
"Stream ends prematurely at %"PRIu64", should be %"PRIu64"\n",
s->off, target_end
);
return AVERROR(EIO);
}
}
if (len > 0) {
s->off += len;
if (s->chunksize > 0 && s->chunksize != UINT64_MAX) {
av_assert0(s->chunksize >= len);
s->chunksize -= len;
}
}
return len;
}
编译 debug 版本的 FFmpeg,跟踪调用栈,发现 http_buf_read 打印这个错误,是由于 ffurl_read 返回的 len 出错导致的,具体调用链如下:
ffurl_read -> ffurl_read2 -> retry_transfer_wrapper -> ffurl_read2 -> tls_read
完整代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
static inline int ffurl_read(URLContext *h, uint8_t *buf, int size)
{
return ffurl_read2(h, buf, size);
}
int ffurl_read2(void *urlcontext, uint8_t *buf, int size)
{
URLContext *h = urlcontext;
if (!(h->flags & AVIO_FLAG_READ))
return AVERROR(EIO);
return retry_transfer_wrapper(h, buf, NULL, size, 1, 1);
}
static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,
const uint8_t *cbuf,
int size, int size_min,
int read)
{
int ret, len;
int fast_retries = 5;
int64_t wait_since = 0;
len = 0;
while (len < size_min) {
if (ff_check_interrupt(&h->interrupt_callback))
return AVERROR_EXIT;
ret = read ? h->prot->url_read (h, buf + len, size - len):
h->prot->url_write(h, cbuf + len, size - len);
if (ret == AVERROR(EINTR))
continue;
if (h->flags & AVIO_FLAG_NONBLOCK)
return ret;
if (ret == AVERROR(EAGAIN)) {
ret = 0;
if (fast_retries) {
fast_retries--;
} else {
if (h->rw_timeout) {
if (!wait_since)
wait_since = av_gettime_relative();
else if (av_gettime_relative() > wait_since + h->rw_timeout)
return AVERROR(EIO);
}
av_usleep(1000);
}
} else if (ret == AVERROR_EOF)
return (len > 0) ? len : AVERROR_EOF;
else if (ret < 0)
return ret;
if (ret) {
fast_retries = FFMAX(fast_retries, 2);
wait_since = 0;
}
len += ret;
}
return len;
}
static int tls_read(URLContext *h, uint8_t *buf, int size)
{
TLSContext *c = h->priv_data;
int ret;
// Set or clear the AVIO_FLAG_NONBLOCK on c->tls_shared.tcp
c->tls_shared.tcp->flags &= ~AVIO_FLAG_NONBLOCK;
c->tls_shared.tcp->flags |= h->flags & AVIO_FLAG_NONBLOCK;
ret = SSL_read(c->ssl, buf, size);
if (ret > 0)
return ret;
if (ret == 0)
return AVERROR_EOF;
return print_tls_error(h, ret);
}
也就是说 tls 层读数据遇到问题了。这种情况抛给上层播放器,播放器就会认为流结束了,导致播放失败。
本文由作者按照 CC BY 4.0 进行授权