Coverage Report

Created: 2026-06-13 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/stream/stream_lavf.c
Line
Count
Source
1
/*
2
 * This file is part of mpv.
3
 *
4
 * mpv is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * mpv is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
#include <libavformat/avformat.h>
19
#include <libavformat/avio.h>
20
#include <libavutil/opt.h>
21
22
#include "options/path.h"
23
#include "common/common.h"
24
#include "common/msg.h"
25
#include "common/tags.h"
26
#include "common/av_common.h"
27
#include "demux/demux.h"
28
#include "misc/charset_conv.h"
29
#include "misc/thread_tools.h"
30
#include "stream.h"
31
#include "network.h"
32
#include "options/m_config.h"
33
#include "options/m_option.h"
34
35
#include "cookies.h"
36
37
#include "misc/bstr.h"
38
#include "mpv_talloc.h"
39
40
#define OPT_BASE_STRUCT struct stream_lavf_opts
41
42
struct stream_lavf_opts {
43
    char **avopts;
44
};
45
46
const struct m_sub_options stream_lavf_conf = {
47
    .opts = (const m_option_t[]) {
48
        {"stream-lavf-o", OPT_KEYVALUELIST(avopts)},
49
        {0}
50
    },
51
    .size = sizeof(struct stream_lavf_opts),
52
};
53
54
static const char *const http_like[] =
55
    {"http", "https", "mmsh", "mmshttp", "httproxy", NULL};
56
57
static int open_f(stream_t *stream);
58
static struct mp_tags *read_icy(stream_t *stream);
59
60
static int fill_buffer(stream_t *s, void *buffer, int max_len)
61
798k
{
62
798k
    AVIOContext *avio = s->priv;
63
798k
    int r = avio_read_partial(avio, buffer, max_len);
64
798k
    return (r <= 0) ? -1 : r;
65
798k
}
66
67
static int write_buffer(stream_t *s, void *buffer, int len)
68
0
{
69
0
    AVIOContext *avio = s->priv;
70
0
    avio_write(avio, buffer, len);
71
0
    avio_flush(avio);
72
0
    if (avio->error)
73
0
        return -1;
74
0
    return len;
75
0
}
76
77
static int seek(stream_t *s, int64_t newpos)
78
845k
{
79
845k
    AVIOContext *avio = s->priv;
80
845k
    if (avio_seek(avio, newpos, SEEK_SET) < 0) {
81
845k
        return 0;
82
845k
    }
83
45
    return 1;
84
845k
}
85
86
static int64_t get_size(stream_t *s)
87
916k
{
88
916k
    AVIOContext *avio = s->priv;
89
916k
    return avio_size(avio);
90
916k
}
91
92
static void close_f(stream_t *stream)
93
23.3k
{
94
23.3k
    AVIOContext *avio = stream->priv;
95
    /* NOTE: As of 2011 write streams must be manually flushed before close.
96
     * Currently write_buffer() always flushes them after writing.
97
     * avio_close() could return an error, but we have no way to return that
98
     * with the current stream API.
99
     */
100
23.3k
    if (avio)
101
23.3k
        avio_close(avio);
102
23.3k
}
103
104
static int control(stream_t *s, int cmd, void *arg)
105
2.14M
{
106
2.14M
    AVIOContext *avio = s->priv;
107
2.14M
    switch(cmd) {
108
0
    case STREAM_CTRL_AVSEEK: {
109
0
        struct stream_avseek *c = arg;
110
0
        int64_t r = avio_seek_time(avio, c->stream_index, c->timestamp, c->flags);
111
0
        if (r >= 0) {
112
0
            stream_drop_buffers(s);
113
0
            return 1;
114
0
        }
115
0
        break;
116
0
    }
117
10.9k
    case STREAM_CTRL_HAS_AVSEEK: {
118
        // Starting at some point, read_seek is always available, and runtime
119
        // behavior decides whether it exists or not. FFmpeg's API doesn't
120
        // return anything helpful to determine seekability upfront, so here's
121
        // a hardcoded whitelist. Not our fault.
122
        // In addition we also have to jump through ridiculous hoops just to
123
        // get the fucking protocol name.
124
10.9k
        const char *proto = NULL;
125
10.9k
        if (avio->av_class && avio->av_class->child_next) {
126
            // This usually yields the URLContext (why does it even exist?),
127
            // which holds the name of the actual protocol implementation.
128
10.9k
            void *child = avio->av_class->child_next(avio, NULL);
129
10.9k
            AVClass *cl = child ? *(AVClass **)child : NULL;
130
10.9k
            if (cl && cl->item_name)
131
10.9k
                proto = cl->item_name(child);
132
10.9k
        }
133
10.9k
        static const char *const has_read_seek[] = {
134
10.9k
            "rtmp", "rtmpt", "rtmpe", "rtmpte", "rtmps", "rtmpts", "mmsh", 0};
135
87.2k
        for (int n = 0; has_read_seek[n]; n++) {
136
76.3k
            if (avio->read_seek && proto && strcmp(proto, has_read_seek[n]) == 0)
137
0
                return 1;
138
76.3k
        }
139
10.9k
        break;
140
10.9k
    }
141
2.13M
    case STREAM_CTRL_GET_METADATA: {
142
2.13M
        *(struct mp_tags **)arg = read_icy(s);
143
2.13M
        if (!*(struct mp_tags **)arg)
144
2.13M
            break;
145
0
        return 1;
146
2.13M
    }
147
2.14M
    }
148
2.14M
    return STREAM_UNSUPPORTED;
149
2.14M
}
150
151
static int interrupt_cb(void *ctx)
152
2.86M
{
153
2.86M
    struct stream *stream = ctx;
154
2.86M
    return mp_cancel_test(stream->cancel);
155
2.86M
}
156
157
static const char * const prefix[] = { "lavf://", "ffmpeg://" };
158
159
void mp_setup_av_network_options(AVDictionary **dict, const char *target_fmt,
160
                                 struct mpv_global *global, struct mp_log *log)
161
46.9k
{
162
46.9k
    void *temp = talloc_new(NULL);
163
46.9k
    struct mp_network_opts *opts =
164
46.9k
        mp_get_config_group(temp, global, &mp_network_conf);
165
166
    // HTTP specific options (other protocols ignore them)
167
46.9k
    if (opts->useragent)
168
46.9k
        av_dict_set(dict, "user_agent", opts->useragent, 0);
169
46.9k
    if (opts->cookies_enabled) {
170
3
        char *file = opts->cookies_file;
171
3
        if (file && file[0])
172
0
            file = mp_get_user_path(temp, global, file);
173
3
        char *cookies = cookies_lavf(temp, global, log, file);
174
3
        if (cookies && cookies[0])
175
0
            av_dict_set(dict, "cookies", cookies, 0);
176
3
    }
177
46.9k
    av_dict_set(dict, "tls_verify", opts->tls_verify ? "1" : "0", 0);
178
46.9k
    if (opts->tls_ca_file) {
179
0
        char *file = mp_get_user_path(temp, global, opts->tls_ca_file);
180
0
        av_dict_set(dict, "ca_file", file, 0);
181
0
    }
182
46.9k
    if (opts->tls_cert_file) {
183
0
        char *file = mp_get_user_path(temp, global, opts->tls_cert_file);
184
0
        av_dict_set(dict, "cert_file", file, 0);
185
0
    }
186
46.9k
    if (opts->tls_key_file) {
187
0
        char *file = mp_get_user_path(temp, global, opts->tls_key_file);
188
0
        av_dict_set(dict, "key_file", file, 0);
189
0
    }
190
46.9k
    char *cust_headers = talloc_strdup(temp, "");
191
46.9k
    if (opts->referrer) {
192
1
        cust_headers = talloc_asprintf_append(cust_headers, "Referer: %s\r\n",
193
1
                                              opts->referrer);
194
1
    }
195
46.9k
    if (opts->http_header_fields) {
196
0
        for (int n = 0; opts->http_header_fields[n]; n++) {
197
0
            cust_headers = talloc_asprintf_append(cust_headers, "%s\r\n",
198
0
                                                  opts->http_header_fields[n]);
199
0
        }
200
0
    }
201
46.9k
    if (strlen(cust_headers))
202
1
        av_dict_set(dict, "headers", cust_headers, 0);
203
46.9k
    av_dict_set(dict, "icy", "1", 0);
204
    // So far, every known protocol uses microseconds for this
205
    // Except rtsp.
206
46.9k
    if (opts->timeout > 0) {
207
46.9k
        if (target_fmt && strcmp(target_fmt, "rtsp") == 0) {
208
0
            mp_verbose(log, "Broken FFmpeg RTSP API => not setting timeout.\n");
209
46.9k
        } else {
210
46.9k
            char buf[80];
211
46.9k
            snprintf(buf, sizeof(buf), "%lld", (long long)(opts->timeout * 1e6));
212
46.9k
            av_dict_set(dict, "timeout", buf, 0);
213
46.9k
        }
214
46.9k
    }
215
46.9k
    if (opts->http_proxy && opts->http_proxy[0])
216
0
        av_dict_set(dict, "http_proxy", opts->http_proxy, 0);
217
218
46.9k
    struct stream_lavf_opts *lavf_opts =
219
46.9k
        mp_get_config_group(temp, global, &stream_lavf_conf);
220
46.9k
    mp_set_avdict(dict, lavf_opts->avopts);
221
222
46.9k
    talloc_free(temp);
223
46.9k
}
224
225
#define PROTO(...) (const char *[]){__VA_ARGS__, NULL}
226
227
// List of safe protocols and their aliases
228
static const char **safe_protos[] = {
229
    PROTO("data"),
230
    PROTO("gopher"),
231
    PROTO("gophers"),
232
    PROTO("http", "dav", "webdav"),
233
    PROTO("httpproxy"),
234
    PROTO("https", "davs", "webdavs"),
235
    PROTO("ipfs"),
236
    PROTO("ipns"),
237
    PROTO("mmsh", "mms", "mmshttp"),
238
    PROTO("mmst"),
239
    PROTO("rist"),
240
    PROTO("rtmp"),
241
    PROTO("rtmpe"),
242
    PROTO("rtmps"),
243
    PROTO("rtmpt"),
244
    PROTO("rtmpte"),
245
    PROTO("rtmpts"),
246
    PROTO("rtp"),
247
    PROTO("srt"),
248
    PROTO("srtp"),
249
    NULL,
250
};
251
252
static char **get_safe_protocols(void)
253
63.3k
{
254
63.3k
    int num = 0;
255
63.3k
    char **protocols = NULL;
256
63.3k
    char **ffmpeg_demuxers = mp_get_lavf_demuxers();
257
63.3k
    char **ffmpeg_protos = mp_get_lavf_protocols();
258
259
759k
    for (int i = 0; ffmpeg_protos[i]; i++) {
260
13.3M
        for (int j = 0; safe_protos[j]; j++) {
261
12.7M
            if (strcmp(ffmpeg_protos[i], safe_protos[j][0]) != 0)
262
12.6M
                continue;
263
126k
            for (int k = 0; safe_protos[j][k]; k++)
264
63.3k
                MP_TARRAY_APPEND(NULL, protocols, num, talloc_strdup(protocols, safe_protos[j][k]));
265
63.3k
            break;
266
12.7M
        }
267
696k
    }
268
269
    // rtsp is a demuxer not protocol in ffmpeg so it is handled separately
270
22.7M
    for (int i = 0; ffmpeg_demuxers[i]; i++) {
271
22.7M
        if (strcmp("rtsp", ffmpeg_demuxers[i]) == 0) {
272
0
            MP_TARRAY_APPEND(NULL, protocols, num, talloc_strdup(protocols, "rtsp"));
273
0
            MP_TARRAY_APPEND(NULL, protocols, num, talloc_strdup(protocols, "rtsps"));
274
0
            break;
275
0
        }
276
22.7M
    }
277
278
63.3k
    MP_TARRAY_APPEND(NULL, protocols, num, NULL);
279
280
63.3k
    talloc_free(ffmpeg_demuxers);
281
63.3k
    talloc_free(ffmpeg_protos);
282
283
63.3k
    return protocols;
284
63.3k
}
285
286
static char **get_unsafe_protocols(void)
287
24.9k
{
288
24.9k
    int num = 0;
289
24.9k
    char **protocols = NULL;
290
24.9k
    char **safe_protocols = get_safe_protocols();
291
24.9k
    char **ffmpeg_protos = mp_get_lavf_protocols();
292
293
299k
    for (int i = 0; ffmpeg_protos[i]; i++) {
294
274k
        bool safe_protocol = false;
295
523k
        for (int j = 0; safe_protocols[j]; j++) {
296
274k
            if (strcmp(ffmpeg_protos[i], safe_protocols[j]) == 0) {
297
24.9k
                safe_protocol = true;
298
24.9k
                break;
299
24.9k
            }
300
274k
        }
301
        // Skip to avoid name conflict with builtin mpv protocol.
302
274k
        if (strcmp(ffmpeg_protos[i], "bluray") == 0 || strcmp(ffmpeg_protos[i], "dvd") == 0)
303
0
            continue;
304
305
274k
        if (!safe_protocol)
306
249k
            MP_TARRAY_APPEND(NULL, protocols, num, talloc_strdup(protocols, ffmpeg_protos[i]));
307
274k
    }
308
309
24.9k
    MP_TARRAY_APPEND(NULL, protocols, num, talloc_strdup(protocols, "ffmpeg"));
310
24.9k
    MP_TARRAY_APPEND(NULL, protocols, num, talloc_strdup(protocols, "lavf"));
311
312
24.9k
    MP_TARRAY_APPEND(NULL, protocols, num, NULL);
313
314
24.9k
    talloc_free(ffmpeg_protos);
315
24.9k
    talloc_free(safe_protocols);
316
24.9k
    return protocols;
317
24.9k
}
318
319
// Escape http URLs with unescaped, invalid characters in them.
320
// libavformat's http protocol does not do this, and a patch to add this
321
// in a 100% safe case (spaces only) was rejected.
322
static char *normalize_url(void *ta_parent, const char *filename)
323
24.6k
{
324
24.6k
    bstr proto = mp_split_proto(bstr0(filename), NULL);
325
147k
    for (int n = 0; http_like[n]; n++) {
326
123k
        if (bstr_equals0(proto, http_like[n]))
327
            // Escape everything but reserved characters.
328
            // Also don't double-scape, so include '%'.
329
104
            return mp_url_escape(ta_parent, filename, ":/?#[]@!$&'()*+,;=%");
330
123k
    }
331
24.5k
    return (char *)filename;
332
24.6k
}
333
334
static int open_f(stream_t *stream)
335
24.6k
{
336
24.6k
    AVIOContext *avio = NULL;
337
24.6k
    int res = STREAM_ERROR;
338
24.6k
    AVDictionary *dict = NULL;
339
24.6k
    void *temp = talloc_new(NULL);
340
341
24.6k
    stream->seek = NULL;
342
24.6k
    stream->seekable = false;
343
344
24.6k
    int flags = stream->mode == STREAM_WRITE ? AVIO_FLAG_WRITE : AVIO_FLAG_READ;
345
346
24.6k
    const char *filename = stream->url;
347
24.6k
    if (!filename) {
348
0
        MP_ERR(stream, "No URL\n");
349
0
        goto out;
350
0
    }
351
74.0k
    for (int i = 0; i < MP_ARRAY_SIZE(prefix); i++)
352
49.3k
        if (!strncmp(filename, prefix[i], strlen(prefix[i])))
353
9.97k
            filename += strlen(prefix[i]);
354
24.6k
    if (!strncmp(filename, "rtsp:", 5) || !strncmp(filename, "rtsps:", 6)) {
355
        /* This is handled as a special demuxer, without a separate
356
         * stream layer. demux_lavf will do all the real work. Note
357
         * that libavformat doesn't even provide a protocol entry for
358
         * this (the rtsp demuxer's probe function checks for a "rtsp:"
359
         * filename prefix), so it has to be handled specially here.
360
         */
361
8
        stream->demuxer = "lavf";
362
8
        stream->lavf_type = "rtsp";
363
8
        talloc_free(temp);
364
8
        return STREAM_OK;
365
8
    }
366
367
    // Replace "mms://" with "mmsh://", so that most mms:// URLs just work.
368
    // Replace "dav://" or "webdav://" with "http://" and "davs://" or "webdavs://" with "https://"
369
24.6k
    bstr b_filename = bstr0(filename);
370
24.6k
    if (bstr_eatstart0(&b_filename, "mms://") ||
371
24.6k
        bstr_eatstart0(&b_filename, "mmshttp://"))
372
36
    {
373
36
        filename = talloc_asprintf(temp, "mmsh://%.*s", BSTR_P(b_filename));
374
24.6k
    } else if (bstr_eatstart0(&b_filename, "dav://") || bstr_eatstart0(&b_filename, "webdav://"))
375
62
    {
376
62
        filename = talloc_asprintf(temp, "http://%.*s", BSTR_P(b_filename));
377
24.5k
    } else if (bstr_eatstart0(&b_filename, "davs://") || bstr_eatstart0(&b_filename, "webdavs://"))
378
6
    {
379
6
        filename = talloc_asprintf(temp, "https://%.*s", BSTR_P(b_filename));
380
6
    }
381
382
24.6k
    av_dict_set(&dict, "reconnect", "1", 0);
383
24.6k
    av_dict_set(&dict, "reconnect_delay_max", "7", 0);
384
385
24.6k
    mp_setup_av_network_options(&dict, NULL, stream->global, stream->log);
386
387
24.6k
    AVIOInterruptCB cb = {
388
24.6k
        .callback = interrupt_cb,
389
24.6k
        .opaque = stream,
390
24.6k
    };
391
392
24.6k
    filename = normalize_url(stream, filename);
393
394
24.6k
    if (strncmp(filename, "rtmp", 4) == 0) {
395
2
        stream->demuxer = "lavf";
396
2
        stream->lavf_type = "flv";
397
        // Setting timeout enables listen mode - force it to disabled.
398
2
        av_dict_set(&dict, "timeout", "0", 0);
399
2
    }
400
401
24.6k
    int err = avio_open2(&avio, filename, flags, &cb, &dict);
402
24.6k
    if (err < 0) {
403
1.37k
        if (err == AVERROR_PROTOCOL_NOT_FOUND)
404
1.37k
            MP_ERR(stream, "Protocol not found. Make sure"
405
1.37k
                   " FFmpeg is compiled with networking support.\n");
406
1.37k
        goto out;
407
1.37k
    }
408
409
23.3k
    mp_avdict_print_unset(stream->log, MSGL_V, dict);
410
411
23.3k
    if (avio->av_class) {
412
23.3k
        uint8_t *mt = NULL;
413
23.3k
        if (av_opt_get(avio, "mime_type", AV_OPT_SEARCH_CHILDREN, &mt) >= 0) {
414
0
            stream->mime_type = talloc_strdup(stream, mt);
415
0
            av_free(mt);
416
0
        }
417
23.3k
    }
418
419
23.3k
    stream->priv = avio;
420
23.3k
    stream->seekable = avio->seekable & AVIO_SEEKABLE_NORMAL;
421
23.3k
    stream->seek = stream->seekable ? seek : NULL;
422
23.3k
    stream->fill_buffer = fill_buffer;
423
23.3k
    stream->write_buffer = write_buffer;
424
23.3k
    stream->get_size = get_size;
425
23.3k
    stream->control = control;
426
23.3k
    stream->close = close_f;
427
    // enable cache (should be avoided for files, but no way to detect this)
428
23.3k
    stream->streaming = true;
429
23.3k
    if (stream->info->stream_origin == STREAM_ORIGIN_NET)
430
13.4k
        stream->is_network = true;
431
23.3k
    res = STREAM_OK;
432
433
24.6k
out:
434
24.6k
    av_dict_free(&dict);
435
24.6k
    talloc_free(temp);
436
24.6k
    return res;
437
23.3k
}
438
439
static struct mp_tags *read_icy(stream_t *s)
440
2.13M
{
441
2.13M
    AVIOContext *avio = s->priv;
442
443
2.13M
    if (!avio->av_class)
444
0
        return NULL;
445
446
2.13M
    uint8_t *icy_header = NULL;
447
2.13M
    if (av_opt_get(avio, "icy_metadata_headers", AV_OPT_SEARCH_CHILDREN,
448
2.13M
                   &icy_header) < 0)
449
2.13M
        icy_header = NULL;
450
451
2.13M
    uint8_t *icy_packet;
452
2.13M
    if (av_opt_get(avio, "icy_metadata_packet", AV_OPT_SEARCH_CHILDREN,
453
2.13M
                   &icy_packet) < 0)
454
2.13M
        icy_packet = NULL;
455
456
    // Send a metadata update only 1. on start, and 2. on a new metadata packet.
457
    // To detect new packages, set the icy_metadata_packet to "-" once we've
458
    // read it (a bit hacky, but works).
459
460
2.13M
    struct mp_tags *res = NULL;
461
2.13M
    if ((!icy_header || !icy_header[0]) && (!icy_packet || !icy_packet[0]))
462
2.13M
        goto done;
463
464
0
    bstr packet = bstr0(icy_packet);
465
0
    if (bstr_equals0(packet, "-"))
466
0
        goto done;
467
468
0
    res = talloc_zero(NULL, struct mp_tags);
469
470
0
    bstr header = bstr0(icy_header);
471
0
    while (header.len) {
472
0
        bstr line = bstr_strip_linebreaks(bstr_getline(header, &header));
473
0
        bstr name, val;
474
0
        if (bstr_split_tok(line, ": ", &name, &val))
475
0
            mp_tags_set_bstr(res, name, val);
476
0
    }
477
478
0
    bstr head = bstr0("StreamTitle='");
479
0
    int i = bstr_find(packet, head);
480
0
    if (i >= 0) {
481
0
        packet = bstr_cut(packet, i + head.len);
482
0
        int end = bstr_find(packet, bstr0("\';"));
483
0
        packet = bstr_splice(packet, 0, end);
484
485
0
        bool allocated = false;
486
0
        struct demux_opts *opts = mp_get_config_group(NULL, s->global, &demux_conf);
487
0
        const char *charset = mp_charset_guess(s, s->log, packet, opts->meta_cp, 0);
488
0
        if (charset && !mp_charset_is_utf8(charset)) {
489
0
            bstr conv = mp_iconv_to_utf8(s->log, packet, charset, 0);
490
0
            if (conv.start && conv.start != packet.start) {
491
0
                allocated = true;
492
0
                packet = conv;
493
0
            }
494
0
        }
495
0
        mp_tags_set_bstr(res, bstr0("icy-title"), packet);
496
0
        talloc_free(opts);
497
0
        if (allocated)
498
0
            talloc_free(packet.start);
499
0
    }
500
501
0
    av_opt_set(avio, "icy_metadata_packet", "-", AV_OPT_SEARCH_CHILDREN);
502
503
2.13M
done:
504
2.13M
    av_free(icy_header);
505
2.13M
    av_free(icy_packet);
506
2.13M
    return res;
507
0
}
508
509
const stream_info_t stream_info_ffmpeg = {
510
    .name = "ffmpeg",
511
    .open = open_f,
512
    .get_protocols = get_safe_protocols,
513
    .can_write = true,
514
    .stream_origin = STREAM_ORIGIN_NET,
515
};
516
517
// Unlike above, this is not marked as safe, and can contain protocols which
518
// may do insecure things. (Such as "ffmpeg", which can access the "lavfi"
519
// pseudo-demuxer, which in turn gives access to filters that can access the
520
// local filesystem.)
521
const stream_info_t stream_info_ffmpeg_unsafe = {
522
    .name = "ffmpeg",
523
    .open = open_f,
524
    .get_protocols = get_unsafe_protocols,
525
    .stream_origin = STREAM_ORIGIN_UNSAFE,
526
    .can_write = true,
527
};