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