Browse code

avformat/hlsenc: creation of hls master playlist file

Reviewed-by: Steven Liu <lingjiujianke@gmail.com>

Vishwanath Dixit authored on 2017/11/20 11:04:34
Showing 3 changed files
... ...
@@ -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, \