1cabdff1aSopenharmony_ci/*
2cabdff1aSopenharmony_ci * Icecast protocol for FFmpeg
3cabdff1aSopenharmony_ci * Copyright (c) 2014 Marvin Scholz
4cabdff1aSopenharmony_ci *
5cabdff1aSopenharmony_ci * This file is part of FFmpeg.
6cabdff1aSopenharmony_ci *
7cabdff1aSopenharmony_ci * FFmpeg is free software; you can redistribute it and/or
8cabdff1aSopenharmony_ci * modify it under the terms of the GNU Lesser General Public
9cabdff1aSopenharmony_ci * License as published by the Free Software Foundation; either
10cabdff1aSopenharmony_ci * version 2.1 of the License, or (at your option) any later version.
11cabdff1aSopenharmony_ci *
12cabdff1aSopenharmony_ci * FFmpeg is distributed in the hope that it will be useful,
13cabdff1aSopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of
14cabdff1aSopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15cabdff1aSopenharmony_ci * Lesser General Public License for more details.
16cabdff1aSopenharmony_ci *
17cabdff1aSopenharmony_ci * You should have received a copy of the GNU Lesser General Public
18cabdff1aSopenharmony_ci * License along with FFmpeg; if not, write to the Free Software
19cabdff1aSopenharmony_ci * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20cabdff1aSopenharmony_ci */
21cabdff1aSopenharmony_ci
22cabdff1aSopenharmony_ci
23cabdff1aSopenharmony_ci#include "libavutil/avstring.h"
24cabdff1aSopenharmony_ci#include "libavutil/bprint.h"
25cabdff1aSopenharmony_ci#include "libavutil/opt.h"
26cabdff1aSopenharmony_ci
27cabdff1aSopenharmony_ci#include "avformat.h"
28cabdff1aSopenharmony_ci#include "network.h"
29cabdff1aSopenharmony_ci
30cabdff1aSopenharmony_ci
31cabdff1aSopenharmony_citypedef struct IcecastContext {
32cabdff1aSopenharmony_ci    const AVClass *class;
33cabdff1aSopenharmony_ci    URLContext *hd;
34cabdff1aSopenharmony_ci    int send_started;
35cabdff1aSopenharmony_ci    char *user;
36cabdff1aSopenharmony_ci    // Options
37cabdff1aSopenharmony_ci    char *content_type;
38cabdff1aSopenharmony_ci    char *description;
39cabdff1aSopenharmony_ci    char *genre;
40cabdff1aSopenharmony_ci    int legacy_icecast;
41cabdff1aSopenharmony_ci    char *name;
42cabdff1aSopenharmony_ci    char *pass;
43cabdff1aSopenharmony_ci    int public;
44cabdff1aSopenharmony_ci    char *url;
45cabdff1aSopenharmony_ci    char *user_agent;
46cabdff1aSopenharmony_ci    int tls;
47cabdff1aSopenharmony_ci} IcecastContext;
48cabdff1aSopenharmony_ci
49cabdff1aSopenharmony_ci#define DEFAULT_ICE_USER "source"
50cabdff1aSopenharmony_ci
51cabdff1aSopenharmony_ci#define NOT_EMPTY(s) (s && s[0])
52cabdff1aSopenharmony_ci
53cabdff1aSopenharmony_ci#define OFFSET(x) offsetof(IcecastContext, x)
54cabdff1aSopenharmony_ci#define E AV_OPT_FLAG_ENCODING_PARAM
55cabdff1aSopenharmony_ci
56cabdff1aSopenharmony_cistatic const AVOption options[] = {
57cabdff1aSopenharmony_ci    { "ice_genre", "set stream genre", OFFSET(genre), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E },
58cabdff1aSopenharmony_ci    { "ice_name", "set stream description", OFFSET(name), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E },
59cabdff1aSopenharmony_ci    { "ice_description", "set stream description", OFFSET(description), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E },
60cabdff1aSopenharmony_ci    { "ice_url", "set stream website", OFFSET(url), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E },
61cabdff1aSopenharmony_ci    { "ice_public", "set if stream is public", OFFSET(public), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
62cabdff1aSopenharmony_ci    { "user_agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E },
63cabdff1aSopenharmony_ci    { "password", "set password", OFFSET(pass), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E },
64cabdff1aSopenharmony_ci    { "content_type", "set content-type, MUST be set if not audio/mpeg", OFFSET(content_type), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E },
65cabdff1aSopenharmony_ci    { "legacy_icecast", "use legacy SOURCE method, for Icecast < v2.4", OFFSET(legacy_icecast), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
66cabdff1aSopenharmony_ci    { "tls", "use a TLS connection", OFFSET(tls), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
67cabdff1aSopenharmony_ci    { NULL }
68cabdff1aSopenharmony_ci};
69cabdff1aSopenharmony_ci
70cabdff1aSopenharmony_ci
71cabdff1aSopenharmony_cistatic void cat_header(AVBPrint *bp, const char key[], const char value[])
72cabdff1aSopenharmony_ci{
73cabdff1aSopenharmony_ci    if (NOT_EMPTY(value))
74cabdff1aSopenharmony_ci        av_bprintf(bp, "%s: %s\r\n", key, value);
75cabdff1aSopenharmony_ci}
76cabdff1aSopenharmony_ci
77cabdff1aSopenharmony_cistatic int icecast_close(URLContext *h)
78cabdff1aSopenharmony_ci{
79cabdff1aSopenharmony_ci    IcecastContext *s = h->priv_data;
80cabdff1aSopenharmony_ci    ffurl_closep(&s->hd);
81cabdff1aSopenharmony_ci    return 0;
82cabdff1aSopenharmony_ci}
83cabdff1aSopenharmony_ci
84cabdff1aSopenharmony_cistatic int icecast_open(URLContext *h, const char *uri, int flags)
85cabdff1aSopenharmony_ci{
86cabdff1aSopenharmony_ci    IcecastContext *s = h->priv_data;
87cabdff1aSopenharmony_ci
88cabdff1aSopenharmony_ci    // Dict to set options that we pass to the HTTP protocol
89cabdff1aSopenharmony_ci    AVDictionary *opt_dict = NULL;
90cabdff1aSopenharmony_ci
91cabdff1aSopenharmony_ci    // URI part variables
92cabdff1aSopenharmony_ci    char h_url[1024], host[1024], auth[1024], path[1024];
93cabdff1aSopenharmony_ci    char *headers, *user = NULL;
94cabdff1aSopenharmony_ci    int port, ret;
95cabdff1aSopenharmony_ci    AVBPrint bp;
96cabdff1aSopenharmony_ci
97cabdff1aSopenharmony_ci    if (flags & AVIO_FLAG_READ)
98cabdff1aSopenharmony_ci        return AVERROR(ENOSYS);
99cabdff1aSopenharmony_ci
100cabdff1aSopenharmony_ci    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
101cabdff1aSopenharmony_ci
102cabdff1aSopenharmony_ci    // Build header strings
103cabdff1aSopenharmony_ci    cat_header(&bp, "Ice-Name", s->name);
104cabdff1aSopenharmony_ci    cat_header(&bp, "Ice-Description", s->description);
105cabdff1aSopenharmony_ci    cat_header(&bp, "Ice-URL", s->url);
106cabdff1aSopenharmony_ci    cat_header(&bp, "Ice-Genre", s->genre);
107cabdff1aSopenharmony_ci    cat_header(&bp, "Ice-Public", s->public ? "1" : "0");
108cabdff1aSopenharmony_ci    if (!av_bprint_is_complete(&bp)) {
109cabdff1aSopenharmony_ci        av_bprint_finalize(&bp, NULL);
110cabdff1aSopenharmony_ci        return AVERROR(ENOMEM);
111cabdff1aSopenharmony_ci    }
112cabdff1aSopenharmony_ci    if ((ret = av_bprint_finalize(&bp, &headers)) < 0)
113cabdff1aSopenharmony_ci        return ret;
114cabdff1aSopenharmony_ci
115cabdff1aSopenharmony_ci    // Set options
116cabdff1aSopenharmony_ci    av_dict_set(&opt_dict, "method", s->legacy_icecast ? "SOURCE" : "PUT", 0);
117cabdff1aSopenharmony_ci    av_dict_set(&opt_dict, "auth_type", "basic", 0);
118cabdff1aSopenharmony_ci    av_dict_set(&opt_dict, "headers", headers, AV_DICT_DONT_STRDUP_VAL);
119cabdff1aSopenharmony_ci    av_dict_set(&opt_dict, "chunked_post", "0", 0);
120cabdff1aSopenharmony_ci    av_dict_set(&opt_dict, "send_expect_100", s->legacy_icecast ? "-1" : "1", 0);
121cabdff1aSopenharmony_ci    if (NOT_EMPTY(s->content_type))
122cabdff1aSopenharmony_ci        av_dict_set(&opt_dict, "content_type", s->content_type, 0);
123cabdff1aSopenharmony_ci    else
124cabdff1aSopenharmony_ci        av_dict_set(&opt_dict, "content_type", "audio/mpeg", 0);
125cabdff1aSopenharmony_ci    if (NOT_EMPTY(s->user_agent))
126cabdff1aSopenharmony_ci        av_dict_set(&opt_dict, "user_agent", s->user_agent, 0);
127cabdff1aSopenharmony_ci
128cabdff1aSopenharmony_ci    // Parse URI
129cabdff1aSopenharmony_ci    av_url_split(NULL, 0, auth, sizeof(auth), host, sizeof(host),
130cabdff1aSopenharmony_ci                 &port, path, sizeof(path), uri);
131cabdff1aSopenharmony_ci
132cabdff1aSopenharmony_ci    // Check for auth data in URI
133cabdff1aSopenharmony_ci    if (auth[0]) {
134cabdff1aSopenharmony_ci        char *sep = strchr(auth, ':');
135cabdff1aSopenharmony_ci        if (sep) {
136cabdff1aSopenharmony_ci            *sep = 0;
137cabdff1aSopenharmony_ci            sep++;
138cabdff1aSopenharmony_ci            if (s->pass) {
139cabdff1aSopenharmony_ci                av_free(s->pass);
140cabdff1aSopenharmony_ci                av_log(h, AV_LOG_WARNING, "Overwriting -password <pass> with URI password!\n");
141cabdff1aSopenharmony_ci            }
142cabdff1aSopenharmony_ci            if (!(s->pass = av_strdup(sep))) {
143cabdff1aSopenharmony_ci                ret = AVERROR(ENOMEM);
144cabdff1aSopenharmony_ci                goto cleanup;
145cabdff1aSopenharmony_ci            }
146cabdff1aSopenharmony_ci        }
147cabdff1aSopenharmony_ci        if (!(user = av_strdup(auth))) {
148cabdff1aSopenharmony_ci            ret = AVERROR(ENOMEM);
149cabdff1aSopenharmony_ci            goto cleanup;
150cabdff1aSopenharmony_ci        }
151cabdff1aSopenharmony_ci    }
152cabdff1aSopenharmony_ci
153cabdff1aSopenharmony_ci    // Build new authstring
154cabdff1aSopenharmony_ci    snprintf(auth, sizeof(auth),
155cabdff1aSopenharmony_ci             "%s:%s",
156cabdff1aSopenharmony_ci             user ? user : DEFAULT_ICE_USER,
157cabdff1aSopenharmony_ci             s->pass ? s->pass : "");
158cabdff1aSopenharmony_ci
159cabdff1aSopenharmony_ci    // Check for mountpoint (path)
160cabdff1aSopenharmony_ci    if (!path[0] || strcmp(path, "/") == 0) {
161cabdff1aSopenharmony_ci        av_log(h, AV_LOG_ERROR, "No mountpoint (path) specified!\n");
162cabdff1aSopenharmony_ci        ret = AVERROR(EIO);
163cabdff1aSopenharmony_ci        goto cleanup;
164cabdff1aSopenharmony_ci    }
165cabdff1aSopenharmony_ci
166cabdff1aSopenharmony_ci    // Build new URI for passing to http protocol
167cabdff1aSopenharmony_ci    ff_url_join(h_url, sizeof(h_url),
168cabdff1aSopenharmony_ci                s->tls ? "https" : "http",
169cabdff1aSopenharmony_ci                auth, host, port, "%s", path);
170cabdff1aSopenharmony_ci    // Finally open http proto handler
171cabdff1aSopenharmony_ci    ret = ffurl_open_whitelist(&s->hd, h_url, AVIO_FLAG_READ_WRITE, NULL,
172cabdff1aSopenharmony_ci                               &opt_dict, h->protocol_whitelist, h->protocol_blacklist, h);
173cabdff1aSopenharmony_ci
174cabdff1aSopenharmony_cicleanup:
175cabdff1aSopenharmony_ci    av_freep(&user);
176cabdff1aSopenharmony_ci    av_dict_free(&opt_dict);
177cabdff1aSopenharmony_ci
178cabdff1aSopenharmony_ci    return ret;
179cabdff1aSopenharmony_ci}
180cabdff1aSopenharmony_ci
181cabdff1aSopenharmony_cistatic int icecast_write(URLContext *h, const uint8_t *buf, int size)
182cabdff1aSopenharmony_ci{
183cabdff1aSopenharmony_ci    IcecastContext *s = h->priv_data;
184cabdff1aSopenharmony_ci    if (!s->send_started) {
185cabdff1aSopenharmony_ci        s->send_started = 1;
186cabdff1aSopenharmony_ci        if (!s->content_type && size >= 8) {
187cabdff1aSopenharmony_ci            static const uint8_t oggs[4] = { 0x4F, 0x67, 0x67, 0x53 };
188cabdff1aSopenharmony_ci            static const uint8_t webm[4] = { 0x1A, 0x45, 0xDF, 0xA3 };
189cabdff1aSopenharmony_ci            static const uint8_t opus[8] = { 0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64 };
190cabdff1aSopenharmony_ci            if (memcmp(buf, oggs, sizeof(oggs)) == 0) {
191cabdff1aSopenharmony_ci                av_log(h, AV_LOG_WARNING, "Streaming Ogg but appropriate content type NOT set!\n");
192cabdff1aSopenharmony_ci                av_log(h, AV_LOG_WARNING, "Set it with -content_type application/ogg\n");
193cabdff1aSopenharmony_ci            } else if (memcmp(buf, opus, sizeof(opus)) == 0) {
194cabdff1aSopenharmony_ci                av_log(h, AV_LOG_WARNING, "Streaming Opus but appropriate content type NOT set!\n");
195cabdff1aSopenharmony_ci                av_log(h, AV_LOG_WARNING, "Set it with -content_type audio/ogg\n");
196cabdff1aSopenharmony_ci            } else if (memcmp(buf, webm, sizeof(webm)) == 0) {
197cabdff1aSopenharmony_ci                av_log(h, AV_LOG_WARNING, "Streaming WebM but appropriate content type NOT set!\n");
198cabdff1aSopenharmony_ci                av_log(h, AV_LOG_WARNING, "Set it with -content_type video/webm\n");
199cabdff1aSopenharmony_ci            } else {
200cabdff1aSopenharmony_ci                av_log(h, AV_LOG_WARNING, "It seems you are streaming an unsupported format.\n");
201cabdff1aSopenharmony_ci                av_log(h, AV_LOG_WARNING, "It might work, but is not officially supported in Icecast!\n");
202cabdff1aSopenharmony_ci            }
203cabdff1aSopenharmony_ci        }
204cabdff1aSopenharmony_ci    }
205cabdff1aSopenharmony_ci    return ffurl_write(s->hd, buf, size);
206cabdff1aSopenharmony_ci}
207cabdff1aSopenharmony_ci
208cabdff1aSopenharmony_cistatic const AVClass icecast_context_class = {
209cabdff1aSopenharmony_ci    .class_name     = "icecast",
210cabdff1aSopenharmony_ci    .item_name      = av_default_item_name,
211cabdff1aSopenharmony_ci    .option         = options,
212cabdff1aSopenharmony_ci    .version        = LIBAVUTIL_VERSION_INT,
213cabdff1aSopenharmony_ci};
214cabdff1aSopenharmony_ci
215cabdff1aSopenharmony_ciconst URLProtocol ff_icecast_protocol = {
216cabdff1aSopenharmony_ci    .name            = "icecast",
217cabdff1aSopenharmony_ci    .url_open        = icecast_open,
218cabdff1aSopenharmony_ci    .url_write       = icecast_write,
219cabdff1aSopenharmony_ci    .url_close       = icecast_close,
220cabdff1aSopenharmony_ci    .priv_data_size  = sizeof(IcecastContext),
221cabdff1aSopenharmony_ci    .priv_data_class = &icecast_context_class,
222cabdff1aSopenharmony_ci    .flags           = URL_PROTOCOL_FLAG_NETWORK,
223cabdff1aSopenharmony_ci};
224