Reviewed-by: Steven Liu <lingjiujianke@gmail.com>
Vishwanath Dixit authored on 2017/11/20 11:04:34... | ... |
@@ -828,6 +828,26 @@ out_1.m3u8, out_2.m3u8 and out_3.m3u8 will be created. |
828 | 828 |
|
829 | 829 |
By default, a single hls variant containing all the encoded streams is created. |
830 | 830 |
|
831 |
+@item master_pl_name |
|
832 |
+Create HLS master playlist with the given name. |
|
833 |
+ |
|
834 |
+@example |
|
835 |
+ffmpeg -re -i in.ts -f hls -master_pl_name master.m3u8 http://example.com/live/out.m3u8 |
|
836 |
+@end example |
|
837 |
+This example creates HLS master playlist with name master.m3u8 and it is |
|
838 |
+published at http://example.com/live/ |
|
839 |
+ |
|
840 |
+@item master_pl_publish_rate |
|
841 |
+Publish master play list repeatedly every after specified number of segment intervals. |
|
842 |
+ |
|
843 |
+@example |
|
844 |
+ffmpeg -re -i in.ts -f hls -master_pl_name master.m3u8 \ |
|
845 |
+-hls_time 2 -master_pl_publish_rate 30 http://example.com/live/out.m3u8 |
|
846 |
+@end example |
|
847 |
+ |
|
848 |
+This example creates HLS master playlist with name master.m3u8 and keep |
|
849 |
+publishing it repeatedly every after 30 segments i.e. every after 60s. |
|
850 |
+ |
|
831 | 851 |
@end table |
832 | 852 |
|
833 | 853 |
@anchor{ico} |
... | ... |
@@ -145,6 +145,7 @@ typedef struct VariantStream { |
145 | 145 |
|
146 | 146 |
AVStream **streams; |
147 | 147 |
unsigned int nb_streams; |
148 |
+ int m3u8_created; /* status of media play-list creation */ |
|
148 | 149 |
char *baseurl; |
149 | 150 |
} VariantStream; |
150 | 151 |
|
... | ... |
@@ -196,7 +197,13 @@ typedef struct HLSContext { |
196 | 196 |
|
197 | 197 |
VariantStream *var_streams; |
198 | 198 |
unsigned int nb_varstreams; |
199 |
+ |
|
200 |
+ int master_m3u8_created; /* status of master play-list creation */ |
|
201 |
+ char *master_m3u8_url; /* URL of the master m3u8 file */ |
|
202 |
+ int version; /* HLS version */ |
|
199 | 203 |
char *var_stream_map; /* user specified variant stream map string */ |
204 |
+ char *master_pl_name; |
|
205 |
+ unsigned int master_publish_rate; |
|
200 | 206 |
} HLSContext; |
201 | 207 |
|
202 | 208 |
static int get_int_from_double(double val) |
... | ... |
@@ -1039,6 +1046,126 @@ static void hls_rename_temp_file(AVFormatContext *s, AVFormatContext *oc) |
1039 | 1039 |
oc->filename[len-4] = '\0'; |
1040 | 1040 |
} |
1041 | 1041 |
|
1042 |
+static int get_relative_url(const char *master_url, const char *media_url, |
|
1043 |
+ char *rel_url, int rel_url_buf_size) |
|
1044 |
+{ |
|
1045 |
+ char *p = NULL; |
|
1046 |
+ int base_len = -1; |
|
1047 |
+ p = strrchr(master_url, '/') ? strrchr(master_url, '/') :\ |
|
1048 |
+ strrchr(master_url, '\\'); |
|
1049 |
+ if (p) { |
|
1050 |
+ base_len = FFABS(p - master_url); |
|
1051 |
+ if (av_strncasecmp(master_url, media_url, base_len)) { |
|
1052 |
+ av_log(NULL, AV_LOG_WARNING, "Unable to find relative url\n"); |
|
1053 |
+ return AVERROR(EINVAL); |
|
1054 |
+ } |
|
1055 |
+ } |
|
1056 |
+ av_strlcpy(rel_url, &(media_url[base_len + 1]), rel_url_buf_size); |
|
1057 |
+ return 0; |
|
1058 |
+} |
|
1059 |
+ |
|
1060 |
+static int create_master_playlist(AVFormatContext *s, |
|
1061 |
+ VariantStream * const input_vs) |
|
1062 |
+{ |
|
1063 |
+ HLSContext *hls = s->priv_data; |
|
1064 |
+ VariantStream *vs; |
|
1065 |
+ AVStream *vid_st, *aud_st; |
|
1066 |
+ AVIOContext *master_pb = 0; |
|
1067 |
+ AVDictionary *options = NULL; |
|
1068 |
+ unsigned int i, j; |
|
1069 |
+ int m3u8_name_size, ret, bandwidth; |
|
1070 |
+ char *m3U8_rel_name; |
|
1071 |
+ |
|
1072 |
+ input_vs->m3u8_created = 1; |
|
1073 |
+ if (!hls->master_m3u8_created) { |
|
1074 |
+ /* For the first time, wait until all the media playlists are created */ |
|
1075 |
+ for (i = 0; i < hls->nb_varstreams; i++) |
|
1076 |
+ if (!hls->var_streams[i].m3u8_created) |
|
1077 |
+ return 0; |
|
1078 |
+ } else { |
|
1079 |
+ /* Keep publishing the master playlist at the configured rate */ |
|
1080 |
+ if (&hls->var_streams[0] != input_vs || !hls->master_publish_rate || |
|
1081 |
+ input_vs->number % hls->master_publish_rate) |
|
1082 |
+ return 0; |
|
1083 |
+ } |
|
1084 |
+ |
|
1085 |
+ if (hls->user_agent) |
|
1086 |
+ av_dict_set(&options, "user-agent", hls->user_agent, 0); |
|
1087 |
+ |
|
1088 |
+ ret = s->io_open(s, &master_pb, hls->master_m3u8_url, AVIO_FLAG_WRITE,\ |
|
1089 |
+ &options); |
|
1090 |
+ av_dict_free(&options); |
|
1091 |
+ if (ret < 0) { |
|
1092 |
+ av_log(NULL, AV_LOG_ERROR, "Failed to open master play list file '%s'\n", |
|
1093 |
+ hls->master_m3u8_url); |
|
1094 |
+ goto fail; |
|
1095 |
+ } |
|
1096 |
+ |
|
1097 |
+ avio_printf(master_pb, "#EXTM3U\n"); |
|
1098 |
+ avio_printf(master_pb, "#EXT-X-VERSION:%d\n", hls->version); |
|
1099 |
+ |
|
1100 |
+ /* For variant streams with video add #EXT-X-STREAM-INF tag with attributes*/ |
|
1101 |
+ for (i = 0; i < hls->nb_varstreams; i++) { |
|
1102 |
+ vs = &(hls->var_streams[i]); |
|
1103 |
+ |
|
1104 |
+ m3u8_name_size = strlen(vs->m3u8_name) + 1; |
|
1105 |
+ m3U8_rel_name = av_malloc(m3u8_name_size); |
|
1106 |
+ if (!m3U8_rel_name) { |
|
1107 |
+ ret = AVERROR(ENOMEM); |
|
1108 |
+ goto fail; |
|
1109 |
+ } |
|
1110 |
+ av_strlcpy(m3U8_rel_name, vs->m3u8_name, m3u8_name_size); |
|
1111 |
+ ret = get_relative_url(hls->master_m3u8_url, vs->m3u8_name, |
|
1112 |
+ m3U8_rel_name, m3u8_name_size); |
|
1113 |
+ if (ret < 0) { |
|
1114 |
+ av_log(NULL, AV_LOG_ERROR, "Unable to find relative URL\n"); |
|
1115 |
+ goto fail; |
|
1116 |
+ } |
|
1117 |
+ |
|
1118 |
+ vid_st = NULL; |
|
1119 |
+ aud_st = NULL; |
|
1120 |
+ for (j = 0; j < vs->nb_streams; j++) { |
|
1121 |
+ if (vs->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) |
|
1122 |
+ vid_st = vs->streams[j]; |
|
1123 |
+ else if (vs->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) |
|
1124 |
+ aud_st = vs->streams[j]; |
|
1125 |
+ } |
|
1126 |
+ |
|
1127 |
+ if (!vid_st && !aud_st) { |
|
1128 |
+ av_log(NULL, AV_LOG_WARNING, "Media stream not found\n"); |
|
1129 |
+ continue; |
|
1130 |
+ } |
|
1131 |
+ |
|
1132 |
+ bandwidth = 0; |
|
1133 |
+ if (vid_st) |
|
1134 |
+ bandwidth += vid_st->codecpar->bit_rate; |
|
1135 |
+ if (aud_st) |
|
1136 |
+ bandwidth += aud_st->codecpar->bit_rate; |
|
1137 |
+ bandwidth += bandwidth / 10; |
|
1138 |
+ |
|
1139 |
+ if (!bandwidth) { |
|
1140 |
+ av_log(NULL, AV_LOG_WARNING, |
|
1141 |
+ "Bandwidth info not available, set audio and video bitrates\n"); |
|
1142 |
+ av_freep(&m3U8_rel_name); |
|
1143 |
+ continue; |
|
1144 |
+ } |
|
1145 |
+ |
|
1146 |
+ avio_printf(master_pb, "#EXT-X-STREAM-INF:BANDWIDTH=%d", bandwidth); |
|
1147 |
+ if (vid_st && vid_st->codecpar->width > 0 && vid_st->codecpar->height > 0) |
|
1148 |
+ avio_printf(master_pb, ",RESOLUTION=%dx%d", vid_st->codecpar->width, |
|
1149 |
+ vid_st->codecpar->height); |
|
1150 |
+ avio_printf(master_pb, "\n%s\n\n", m3U8_rel_name); |
|
1151 |
+ |
|
1152 |
+ av_freep(&m3U8_rel_name); |
|
1153 |
+ } |
|
1154 |
+fail: |
|
1155 |
+ if(ret >=0) |
|
1156 |
+ hls->master_m3u8_created = 1; |
|
1157 |
+ av_freep(&m3U8_rel_name); |
|
1158 |
+ ff_format_io_close(s, &master_pb); |
|
1159 |
+ return ret; |
|
1160 |
+} |
|
1161 |
+ |
|
1042 | 1162 |
static int hls_window(AVFormatContext *s, int last, VariantStream *vs) |
1043 | 1163 |
{ |
1044 | 1164 |
HLSContext *hls = s->priv_data; |
... | ... |
@@ -1049,7 +1176,6 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs) |
1049 | 1049 |
AVIOContext *sub_out = NULL; |
1050 | 1050 |
char temp_filename[1024]; |
1051 | 1051 |
int64_t sequence = FFMAX(hls->start_sequence, vs->sequence - vs->nb_entries); |
1052 |
- int version = 3; |
|
1053 | 1052 |
const char *proto = avio_find_protocol_name(s->filename); |
1054 | 1053 |
int use_rename = proto && !strcmp(proto, "file"); |
1055 | 1054 |
static unsigned warned_non_file; |
... | ... |
@@ -1059,13 +1185,14 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs) |
1059 | 1059 |
double prog_date_time = vs->initial_prog_date_time; |
1060 | 1060 |
int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0); |
1061 | 1061 |
|
1062 |
+ hls->version = 3; |
|
1062 | 1063 |
if (byterange_mode) { |
1063 |
- version = 4; |
|
1064 |
+ hls->version = 4; |
|
1064 | 1065 |
sequence = 0; |
1065 | 1066 |
} |
1066 | 1067 |
|
1067 | 1068 |
if (hls->segment_type == SEGMENT_TYPE_FMP4) { |
1068 |
- version = 7; |
|
1069 |
+ hls->version = 7; |
|
1069 | 1070 |
} |
1070 | 1071 |
|
1071 | 1072 |
if (!use_rename && !warned_non_file++) |
... | ... |
@@ -1082,7 +1209,7 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs) |
1082 | 1082 |
} |
1083 | 1083 |
|
1084 | 1084 |
vs->discontinuity_set = 0; |
1085 |
- write_m3u8_head_block(hls, out, version, target_duration, sequence); |
|
1085 |
+ write_m3u8_head_block(hls, out, hls->version, target_duration, sequence); |
|
1086 | 1086 |
if (hls->pl_type == PLAYLIST_TYPE_EVENT) { |
1087 | 1087 |
avio_printf(out, "#EXT-X-PLAYLIST-TYPE:EVENT\n"); |
1088 | 1088 |
} else if (hls->pl_type == PLAYLIST_TYPE_VOD) { |
... | ... |
@@ -1158,7 +1285,7 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs) |
1158 | 1158 |
if( vs->vtt_m3u8_name ) { |
1159 | 1159 |
if ((ret = s->io_open(s, &sub_out, vs->vtt_m3u8_name, AVIO_FLAG_WRITE, &options)) < 0) |
1160 | 1160 |
goto fail; |
1161 |
- write_m3u8_head_block(hls, sub_out, version, target_duration, sequence); |
|
1161 |
+ write_m3u8_head_block(hls, sub_out, hls->version, target_duration, sequence); |
|
1162 | 1162 |
|
1163 | 1163 |
for (en = vs->segments; en; en = en->next) { |
1164 | 1164 |
avio_printf(sub_out, "#EXTINF:%f,\n", en->duration); |
... | ... |
@@ -1181,6 +1308,11 @@ fail: |
1181 | 1181 |
ff_format_io_close(s, &sub_out); |
1182 | 1182 |
if (ret >= 0 && use_rename) |
1183 | 1183 |
ff_rename(temp_filename, vs->m3u8_name, s); |
1184 |
+ |
|
1185 |
+ if (ret >= 0 && hls->master_pl_name) |
|
1186 |
+ if (create_master_playlist(s, vs) < 0) |
|
1187 |
+ av_log(s, AV_LOG_WARNING, "Master playlist creation failed\n"); |
|
1188 |
+ |
|
1184 | 1189 |
return ret; |
1185 | 1190 |
} |
1186 | 1191 |
|
... | ... |
@@ -1509,6 +1641,32 @@ static int update_variant_stream_info(AVFormatContext *s) { |
1509 | 1509 |
return 0; |
1510 | 1510 |
} |
1511 | 1511 |
|
1512 |
+static int update_master_pl_info(AVFormatContext *s) { |
|
1513 |
+ HLSContext *hls = s->priv_data; |
|
1514 |
+ int m3u8_name_size, ret; |
|
1515 |
+ char *p; |
|
1516 |
+ |
|
1517 |
+ m3u8_name_size = strlen(s->filename) + strlen(hls->master_pl_name) + 1; |
|
1518 |
+ hls->master_m3u8_url = av_malloc(m3u8_name_size); |
|
1519 |
+ if (!hls->master_m3u8_url) { |
|
1520 |
+ ret = AVERROR(ENOMEM); |
|
1521 |
+ return ret; |
|
1522 |
+ } |
|
1523 |
+ |
|
1524 |
+ av_strlcpy(hls->master_m3u8_url, s->filename, m3u8_name_size); |
|
1525 |
+ p = strrchr(hls->master_m3u8_url, '/') ? |
|
1526 |
+ strrchr(hls->master_m3u8_url, '/') : |
|
1527 |
+ strrchr(hls->master_m3u8_url, '\\'); |
|
1528 |
+ if (p) { |
|
1529 |
+ *(p + 1) = '\0'; |
|
1530 |
+ av_strlcat(hls->master_m3u8_url, hls->master_pl_name, m3u8_name_size); |
|
1531 |
+ } else { |
|
1532 |
+ av_strlcpy(hls->master_m3u8_url, hls->master_pl_name, m3u8_name_size); |
|
1533 |
+ } |
|
1534 |
+ |
|
1535 |
+ return 0; |
|
1536 |
+} |
|
1537 |
+ |
|
1512 | 1538 |
static int hls_write_header(AVFormatContext *s) |
1513 | 1539 |
{ |
1514 | 1540 |
HLSContext *hls = s->priv_data; |
... | ... |
@@ -1537,6 +1695,15 @@ static int hls_write_header(AVFormatContext *s) |
1537 | 1537 |
goto fail; |
1538 | 1538 |
} |
1539 | 1539 |
|
1540 |
+ if (hls->master_pl_name) { |
|
1541 |
+ ret = update_master_pl_info(s); |
|
1542 |
+ if (ret < 0) { |
|
1543 |
+ av_log(s, AV_LOG_ERROR, "Master stream info update failed with status %x\n", |
|
1544 |
+ ret); |
|
1545 |
+ goto fail; |
|
1546 |
+ } |
|
1547 |
+ } |
|
1548 |
+ |
|
1540 | 1549 |
if (hls->segment_type == SEGMENT_TYPE_FMP4) { |
1541 | 1550 |
pattern = "%d.m4s"; |
1542 | 1551 |
} |
... | ... |
@@ -1873,9 +2040,9 @@ fail: |
1873 | 1873 |
avformat_free_context(vs->avf); |
1874 | 1874 |
if (vs->vtt_avf) |
1875 | 1875 |
avformat_free_context(vs->vtt_avf); |
1876 |
- |
|
1877 | 1876 |
} |
1878 | 1877 |
av_freep(&hls->var_streams); |
1878 |
+ av_freep(&hls->master_m3u8_url); |
|
1879 | 1879 |
} |
1880 | 1880 |
return ret; |
1881 | 1881 |
} |
... | ... |
@@ -2112,6 +2279,7 @@ static int hls_write_trailer(struct AVFormatContext *s) |
2112 | 2112 |
|
2113 | 2113 |
av_freep(&hls->key_basename); |
2114 | 2114 |
av_freep(&hls->var_streams); |
2115 |
+ av_freep(&hls->master_m3u8_url); |
|
2115 | 2116 |
return 0; |
2116 | 2117 |
} |
2117 | 2118 |
|
... | ... |
@@ -2167,6 +2335,8 @@ static const AVOption options[] = { |
2167 | 2167 |
{"datetime", "current datetime as YYYYMMDDhhmmss", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_START_SEQUENCE_AS_FORMATTED_DATETIME }, INT_MIN, INT_MAX, E, "start_sequence_source_type" }, |
2168 | 2168 |
{"http_user_agent", "override User-Agent field in HTTP header", OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, |
2169 | 2169 |
{"var_stream_map", "Variant stream map string", OFFSET(var_stream_map), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, |
2170 |
+ {"master_pl_name", "Create HLS master playlist with this name", OFFSET(master_pl_name), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, |
|
2171 |
+ {"master_pl_publish_rate", "Publish master play list every after this many segment intervals", OFFSET(master_publish_rate), AV_OPT_TYPE_INT, {.i64 = 0}, 0, UINT_MAX, E}, |
|
2170 | 2172 |
{ NULL }, |
2171 | 2173 |
}; |
2172 | 2174 |
|
... | ... |
@@ -33,7 +33,7 @@ |
33 | 33 |
// Also please add any ticket numbers that you believe might be affected here |
34 | 34 |
#define LIBAVFORMAT_VERSION_MAJOR 58 |
35 | 35 |
#define LIBAVFORMAT_VERSION_MINOR 2 |
36 |
-#define LIBAVFORMAT_VERSION_MICRO 101 |
|
36 |
+#define LIBAVFORMAT_VERSION_MICRO 102 |
|
37 | 37 |
|
38 | 38 |
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ |
39 | 39 |
LIBAVFORMAT_VERSION_MINOR, \ |