blob: 6232c70da243d0aca165779a448b28f9fc15551f
1 | /* |
2 | * MPEG-DASH ISO BMFF segmenter |
3 | * Copyright (c) 2014 Martin Storsjo |
4 | * |
5 | * This file is part of FFmpeg. |
6 | * |
7 | * FFmpeg is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2.1 of the License, or (at your option) any later version. |
11 | * |
12 | * FFmpeg is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with FFmpeg; if not, write to the Free Software |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
20 | */ |
21 | |
22 | #include "config.h" |
23 | #if HAVE_UNISTD_H |
24 | #include <unistd.h> |
25 | #endif |
26 | |
27 | #include "libavutil/avassert.h" |
28 | #include "libavutil/avstring.h" |
29 | #include "libavutil/intreadwrite.h" |
30 | #include "libavutil/mathematics.h" |
31 | #include "libavutil/opt.h" |
32 | #include "libavutil/rational.h" |
33 | #include "libavutil/time_internal.h" |
34 | |
35 | #include "avc.h" |
36 | #include "avformat.h" |
37 | #include "avio_internal.h" |
38 | #include "internal.h" |
39 | #include "isom.h" |
40 | #include "os_support.h" |
41 | #include "url.h" |
42 | |
43 | // See ISO/IEC 23009-1:2014 5.3.9.4.4 |
44 | typedef enum { |
45 | DASH_TMPL_ID_UNDEFINED = -1, |
46 | DASH_TMPL_ID_ESCAPE, |
47 | DASH_TMPL_ID_REP_ID, |
48 | DASH_TMPL_ID_NUMBER, |
49 | DASH_TMPL_ID_BANDWIDTH, |
50 | DASH_TMPL_ID_TIME, |
51 | } DASHTmplId; |
52 | |
53 | typedef struct Segment { |
54 | char file[1024]; |
55 | int64_t start_pos; |
56 | int range_length, index_length; |
57 | int64_t time; |
58 | int duration; |
59 | int n; |
60 | } Segment; |
61 | |
62 | typedef struct OutputStream { |
63 | AVFormatContext *ctx; |
64 | int ctx_inited; |
65 | uint8_t iobuf[32768]; |
66 | AVIOContext *out; |
67 | int packets_written; |
68 | char initfile[1024]; |
69 | int64_t init_start_pos; |
70 | int init_range_length; |
71 | int nb_segments, segments_size, segment_index; |
72 | Segment **segments; |
73 | int64_t first_pts, start_pts, max_pts; |
74 | int64_t last_dts; |
75 | int bit_rate; |
76 | char bandwidth_str[64]; |
77 | |
78 | char codec_str[100]; |
79 | } OutputStream; |
80 | |
81 | typedef struct DASHContext { |
82 | const AVClass *class; /* Class for private options. */ |
83 | int window_size; |
84 | int extra_window_size; |
85 | int min_seg_duration; |
86 | int remove_at_exit; |
87 | int use_template; |
88 | int use_timeline; |
89 | int single_file; |
90 | OutputStream *streams; |
91 | int has_video, has_audio; |
92 | int64_t last_duration; |
93 | int64_t total_duration; |
94 | char availability_start_time[100]; |
95 | char dirname[1024]; |
96 | const char *single_file_name; |
97 | const char *init_seg_name; |
98 | const char *media_seg_name; |
99 | AVRational min_frame_rate, max_frame_rate; |
100 | int ambiguous_frame_rate; |
101 | } DASHContext; |
102 | |
103 | static int dash_write(void *opaque, uint8_t *buf, int buf_size) |
104 | { |
105 | OutputStream *os = opaque; |
106 | if (os->out) |
107 | avio_write(os->out, buf, buf_size); |
108 | return buf_size; |
109 | } |
110 | |
111 | // RFC 6381 |
112 | static void set_codec_str(AVFormatContext *s, AVCodecParameters *par, |
113 | char *str, int size) |
114 | { |
115 | const AVCodecTag *tags[2] = { NULL, NULL }; |
116 | uint32_t tag; |
117 | if (par->codec_type == AVMEDIA_TYPE_VIDEO) |
118 | tags[0] = ff_codec_movvideo_tags; |
119 | else if (par->codec_type == AVMEDIA_TYPE_AUDIO) |
120 | tags[0] = ff_codec_movaudio_tags; |
121 | else |
122 | return; |
123 | |
124 | tag = av_codec_get_tag(tags, par->codec_id); |
125 | if (!tag) |
126 | return; |
127 | if (size < 5) |
128 | return; |
129 | |
130 | AV_WL32(str, tag); |
131 | str[4] = '\0'; |
132 | if (!strcmp(str, "mp4a") || !strcmp(str, "mp4v")) { |
133 | uint32_t oti; |
134 | tags[0] = ff_mp4_obj_type; |
135 | oti = av_codec_get_tag(tags, par->codec_id); |
136 | if (oti) |
137 | av_strlcatf(str, size, ".%02"PRIx32, oti); |
138 | else |
139 | return; |
140 | |
141 | if (tag == MKTAG('m', 'p', '4', 'a')) { |
142 | if (par->extradata_size >= 2) { |
143 | int aot = par->extradata[0] >> 3; |
144 | if (aot == 31) |
145 | aot = ((AV_RB16(par->extradata) >> 5) & 0x3f) + 32; |
146 | av_strlcatf(str, size, ".%d", aot); |
147 | } |
148 | } else if (tag == MKTAG('m', 'p', '4', 'v')) { |
149 | // Unimplemented, should output ProfileLevelIndication as a decimal number |
150 | av_log(s, AV_LOG_WARNING, "Incomplete RFC 6381 codec string for mp4v\n"); |
151 | } |
152 | } else if (!strcmp(str, "avc1")) { |
153 | uint8_t *tmpbuf = NULL; |
154 | uint8_t *extradata = par->extradata; |
155 | int extradata_size = par->extradata_size; |
156 | if (!extradata_size) |
157 | return; |
158 | if (extradata[0] != 1) { |
159 | AVIOContext *pb; |
160 | if (avio_open_dyn_buf(&pb) < 0) |
161 | return; |
162 | if (ff_isom_write_avcc(pb, extradata, extradata_size) < 0) { |
163 | ffio_free_dyn_buf(&pb); |
164 | return; |
165 | } |
166 | extradata_size = avio_close_dyn_buf(pb, &extradata); |
167 | tmpbuf = extradata; |
168 | } |
169 | |
170 | if (extradata_size >= 4) |
171 | av_strlcatf(str, size, ".%02x%02x%02x", |
172 | extradata[1], extradata[2], extradata[3]); |
173 | av_free(tmpbuf); |
174 | } |
175 | } |
176 | |
177 | static void dash_free(AVFormatContext *s) |
178 | { |
179 | DASHContext *c = s->priv_data; |
180 | int i, j; |
181 | if (!c->streams) |
182 | return; |
183 | for (i = 0; i < s->nb_streams; i++) { |
184 | OutputStream *os = &c->streams[i]; |
185 | if (os->ctx && os->ctx_inited) |
186 | av_write_trailer(os->ctx); |
187 | if (os->ctx && os->ctx->pb) |
188 | av_free(os->ctx->pb); |
189 | ff_format_io_close(s, &os->out); |
190 | if (os->ctx) |
191 | avformat_free_context(os->ctx); |
192 | for (j = 0; j < os->nb_segments; j++) |
193 | av_free(os->segments[j]); |
194 | av_free(os->segments); |
195 | } |
196 | av_freep(&c->streams); |
197 | } |
198 | |
199 | static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext *c) |
200 | { |
201 | int i, start_index = 0, start_number = 1; |
202 | if (c->window_size) { |
203 | start_index = FFMAX(os->nb_segments - c->window_size, 0); |
204 | start_number = FFMAX(os->segment_index - c->window_size, 1); |
205 | } |
206 | |
207 | if (c->use_template) { |
208 | int timescale = c->use_timeline ? os->ctx->streams[0]->time_base.den : AV_TIME_BASE; |
209 | avio_printf(out, "\t\t\t\t<SegmentTemplate timescale=\"%d\" ", timescale); |
210 | if (!c->use_timeline) |
211 | avio_printf(out, "duration=\"%"PRId64"\" ", c->last_duration); |
212 | avio_printf(out, "initialization=\"%s\" media=\"%s\" startNumber=\"%d\">\n", c->init_seg_name, c->media_seg_name, c->use_timeline ? start_number : 1); |
213 | if (c->use_timeline) { |
214 | int64_t cur_time = 0; |
215 | avio_printf(out, "\t\t\t\t\t<SegmentTimeline>\n"); |
216 | for (i = start_index; i < os->nb_segments; ) { |
217 | Segment *seg = os->segments[i]; |
218 | int repeat = 0; |
219 | avio_printf(out, "\t\t\t\t\t\t<S "); |
220 | if (i == start_index || seg->time != cur_time) { |
221 | cur_time = seg->time; |
222 | avio_printf(out, "t=\"%"PRId64"\" ", seg->time); |
223 | } |
224 | avio_printf(out, "d=\"%d\" ", seg->duration); |
225 | while (i + repeat + 1 < os->nb_segments && |
226 | os->segments[i + repeat + 1]->duration == seg->duration && |
227 | os->segments[i + repeat + 1]->time == os->segments[i + repeat]->time + os->segments[i + repeat]->duration) |
228 | repeat++; |
229 | if (repeat > 0) |
230 | avio_printf(out, "r=\"%d\" ", repeat); |
231 | avio_printf(out, "/>\n"); |
232 | i += 1 + repeat; |
233 | cur_time += (1 + repeat) * seg->duration; |
234 | } |
235 | avio_printf(out, "\t\t\t\t\t</SegmentTimeline>\n"); |
236 | } |
237 | avio_printf(out, "\t\t\t\t</SegmentTemplate>\n"); |
238 | } else if (c->single_file) { |
239 | avio_printf(out, "\t\t\t\t<BaseURL>%s</BaseURL>\n", os->initfile); |
240 | avio_printf(out, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%"PRId64"\" startNumber=\"%d\">\n", AV_TIME_BASE, c->last_duration, start_number); |
241 | avio_printf(out, "\t\t\t\t\t<Initialization range=\"%"PRId64"-%"PRId64"\" />\n", os->init_start_pos, os->init_start_pos + os->init_range_length - 1); |
242 | for (i = start_index; i < os->nb_segments; i++) { |
243 | Segment *seg = os->segments[i]; |
244 | avio_printf(out, "\t\t\t\t\t<SegmentURL mediaRange=\"%"PRId64"-%"PRId64"\" ", seg->start_pos, seg->start_pos + seg->range_length - 1); |
245 | if (seg->index_length) |
246 | avio_printf(out, "indexRange=\"%"PRId64"-%"PRId64"\" ", seg->start_pos, seg->start_pos + seg->index_length - 1); |
247 | avio_printf(out, "/>\n"); |
248 | } |
249 | avio_printf(out, "\t\t\t\t</SegmentList>\n"); |
250 | } else { |
251 | avio_printf(out, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%"PRId64"\" startNumber=\"%d\">\n", AV_TIME_BASE, c->last_duration, start_number); |
252 | avio_printf(out, "\t\t\t\t\t<Initialization sourceURL=\"%s\" />\n", os->initfile); |
253 | for (i = start_index; i < os->nb_segments; i++) { |
254 | Segment *seg = os->segments[i]; |
255 | avio_printf(out, "\t\t\t\t\t<SegmentURL media=\"%s\" />\n", seg->file); |
256 | } |
257 | avio_printf(out, "\t\t\t\t</SegmentList>\n"); |
258 | } |
259 | } |
260 | |
261 | static DASHTmplId dash_read_tmpl_id(const char *identifier, char *format_tag, |
262 | size_t format_tag_size, const char **ptr) { |
263 | const char *next_ptr; |
264 | DASHTmplId id_type = DASH_TMPL_ID_UNDEFINED; |
265 | |
266 | if (av_strstart(identifier, "$$", &next_ptr)) { |
267 | id_type = DASH_TMPL_ID_ESCAPE; |
268 | *ptr = next_ptr; |
269 | } else if (av_strstart(identifier, "$RepresentationID$", &next_ptr)) { |
270 | id_type = DASH_TMPL_ID_REP_ID; |
271 | // default to basic format, as $RepresentationID$ identifiers |
272 | // are not allowed to have custom format-tags. |
273 | av_strlcpy(format_tag, "%d", format_tag_size); |
274 | *ptr = next_ptr; |
275 | } else { // the following identifiers may have an explicit format_tag |
276 | if (av_strstart(identifier, "$Number", &next_ptr)) |
277 | id_type = DASH_TMPL_ID_NUMBER; |
278 | else if (av_strstart(identifier, "$Bandwidth", &next_ptr)) |
279 | id_type = DASH_TMPL_ID_BANDWIDTH; |
280 | else if (av_strstart(identifier, "$Time", &next_ptr)) |
281 | id_type = DASH_TMPL_ID_TIME; |
282 | else |
283 | id_type = DASH_TMPL_ID_UNDEFINED; |
284 | |
285 | // next parse the dash format-tag and generate a c-string format tag |
286 | // (next_ptr now points at the first '%' at the beginning of the format-tag) |
287 | if (id_type != DASH_TMPL_ID_UNDEFINED) { |
288 | const char *number_format = (id_type == DASH_TMPL_ID_TIME) ? PRId64 : "d"; |
289 | if (next_ptr[0] == '$') { // no dash format-tag |
290 | snprintf(format_tag, format_tag_size, "%%%s", number_format); |
291 | *ptr = &next_ptr[1]; |
292 | } else { |
293 | const char *width_ptr; |
294 | // only tolerate single-digit width-field (i.e. up to 9-digit width) |
295 | if (av_strstart(next_ptr, "%0", &width_ptr) && |
296 | av_isdigit(width_ptr[0]) && |
297 | av_strstart(&width_ptr[1], "d$", &next_ptr)) { |
298 | // yes, we're using a format tag to build format_tag. |
299 | snprintf(format_tag, format_tag_size, "%s%c%s", "%0", width_ptr[0], number_format); |
300 | *ptr = next_ptr; |
301 | } else { |
302 | av_log(NULL, AV_LOG_WARNING, "Failed to parse format-tag beginning with %s. Expected either a " |
303 | "closing '$' character or a format-string like '%%0[width]d', " |
304 | "where width must be a single digit\n", next_ptr); |
305 | id_type = DASH_TMPL_ID_UNDEFINED; |
306 | } |
307 | } |
308 | } |
309 | } |
310 | return id_type; |
311 | } |
312 | |
313 | static void dash_fill_tmpl_params(char *dst, size_t buffer_size, |
314 | const char *template, int rep_id, |
315 | int number, int bit_rate, |
316 | int64_t time) { |
317 | int dst_pos = 0; |
318 | const char *t_cur = template; |
319 | while (dst_pos < buffer_size - 1 && *t_cur) { |
320 | char format_tag[7]; // May be "%d", "%0Xd", or "%0Xlld" (for $Time$), where X is in [0-9] |
321 | int n = 0; |
322 | DASHTmplId id_type; |
323 | const char *t_next = strchr(t_cur, '$'); // copy over everything up to the first '$' character |
324 | if (t_next) { |
325 | int num_copy_bytes = FFMIN(t_next - t_cur, buffer_size - dst_pos - 1); |
326 | av_strlcpy(&dst[dst_pos], t_cur, num_copy_bytes + 1); |
327 | // advance |
328 | dst_pos += num_copy_bytes; |
329 | t_cur = t_next; |
330 | } else { // no more DASH identifiers to substitute - just copy the rest over and break |
331 | av_strlcpy(&dst[dst_pos], t_cur, buffer_size - dst_pos); |
332 | break; |
333 | } |
334 | |
335 | if (dst_pos >= buffer_size - 1 || !*t_cur) |
336 | break; |
337 | |
338 | // t_cur is now pointing to a '$' character |
339 | id_type = dash_read_tmpl_id(t_cur, format_tag, sizeof(format_tag), &t_next); |
340 | switch (id_type) { |
341 | case DASH_TMPL_ID_ESCAPE: |
342 | av_strlcpy(&dst[dst_pos], "$", 2); |
343 | n = 1; |
344 | break; |
345 | case DASH_TMPL_ID_REP_ID: |
346 | n = snprintf(&dst[dst_pos], buffer_size - dst_pos, format_tag, rep_id); |
347 | break; |
348 | case DASH_TMPL_ID_NUMBER: |
349 | n = snprintf(&dst[dst_pos], buffer_size - dst_pos, format_tag, number); |
350 | break; |
351 | case DASH_TMPL_ID_BANDWIDTH: |
352 | n = snprintf(&dst[dst_pos], buffer_size - dst_pos, format_tag, bit_rate); |
353 | break; |
354 | case DASH_TMPL_ID_TIME: |
355 | n = snprintf(&dst[dst_pos], buffer_size - dst_pos, format_tag, time); |
356 | break; |
357 | case DASH_TMPL_ID_UNDEFINED: |
358 | // copy over one byte and advance |
359 | av_strlcpy(&dst[dst_pos], t_cur, 2); |
360 | n = 1; |
361 | t_next = &t_cur[1]; |
362 | break; |
363 | } |
364 | // t_next points just past the processed identifier |
365 | // n is the number of bytes that were attempted to be written to dst |
366 | // (may have failed to write all because buffer_size). |
367 | |
368 | // advance |
369 | dst_pos += FFMIN(n, buffer_size - dst_pos - 1); |
370 | t_cur = t_next; |
371 | } |
372 | } |
373 | |
374 | static char *xmlescape(const char *str) { |
375 | int outlen = strlen(str)*3/2 + 6; |
376 | char *out = av_realloc(NULL, outlen + 1); |
377 | int pos = 0; |
378 | if (!out) |
379 | return NULL; |
380 | for (; *str; str++) { |
381 | if (pos + 6 > outlen) { |
382 | char *tmp; |
383 | outlen = 2 * outlen + 6; |
384 | tmp = av_realloc(out, outlen + 1); |
385 | if (!tmp) { |
386 | av_free(out); |
387 | return NULL; |
388 | } |
389 | out = tmp; |
390 | } |
391 | if (*str == '&') { |
392 | memcpy(&out[pos], "&", 5); |
393 | pos += 5; |
394 | } else if (*str == '<') { |
395 | memcpy(&out[pos], "<", 4); |
396 | pos += 4; |
397 | } else if (*str == '>') { |
398 | memcpy(&out[pos], ">", 4); |
399 | pos += 4; |
400 | } else if (*str == '\'') { |
401 | memcpy(&out[pos], "'", 6); |
402 | pos += 6; |
403 | } else if (*str == '\"') { |
404 | memcpy(&out[pos], """, 6); |
405 | pos += 6; |
406 | } else { |
407 | out[pos++] = *str; |
408 | } |
409 | } |
410 | out[pos] = '\0'; |
411 | return out; |
412 | } |
413 | |
414 | static void write_time(AVIOContext *out, int64_t time) |
415 | { |
416 | int seconds = time / AV_TIME_BASE; |
417 | int fractions = time % AV_TIME_BASE; |
418 | int minutes = seconds / 60; |
419 | int hours = minutes / 60; |
420 | seconds %= 60; |
421 | minutes %= 60; |
422 | avio_printf(out, "PT"); |
423 | if (hours) |
424 | avio_printf(out, "%dH", hours); |
425 | if (hours || minutes) |
426 | avio_printf(out, "%dM", minutes); |
427 | avio_printf(out, "%d.%dS", seconds, fractions / (AV_TIME_BASE / 10)); |
428 | } |
429 | |
430 | static void format_date_now(char *buf, int size) |
431 | { |
432 | time_t t = time(NULL); |
433 | struct tm *ptm, tmbuf; |
434 | ptm = gmtime_r(&t, &tmbuf); |
435 | if (ptm) { |
436 | if (!strftime(buf, size, "%Y-%m-%dT%H:%M:%S", ptm)) |
437 | buf[0] = '\0'; |
438 | } |
439 | } |
440 | |
441 | static int write_manifest(AVFormatContext *s, int final) |
442 | { |
443 | DASHContext *c = s->priv_data; |
444 | AVIOContext *out; |
445 | char temp_filename[1024]; |
446 | int ret, i; |
447 | const char *proto = avio_find_protocol_name(s->filename); |
448 | int use_rename = proto && !strcmp(proto, "file"); |
449 | static unsigned int warned_non_file = 0; |
450 | AVDictionaryEntry *title = av_dict_get(s->metadata, "title", NULL, 0); |
451 | |
452 | if (!use_rename && !warned_non_file++) |
453 | av_log(s, AV_LOG_ERROR, "Cannot use rename on non file protocol, this may lead to races and temporary partial files\n"); |
454 | |
455 | snprintf(temp_filename, sizeof(temp_filename), use_rename ? "%s.tmp" : "%s", s->filename); |
456 | ret = s->io_open(s, &out, temp_filename, AVIO_FLAG_WRITE, NULL); |
457 | if (ret < 0) { |
458 | av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename); |
459 | return ret; |
460 | } |
461 | avio_printf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); |
462 | avio_printf(out, "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" |
463 | "\txmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n" |
464 | "\txmlns:xlink=\"http://www.w3.org/1999/xlink\"\n" |
465 | "\txsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\"\n" |
466 | "\tprofiles=\"urn:mpeg:dash:profile:isoff-live:2011\"\n" |
467 | "\ttype=\"%s\"\n", final ? "static" : "dynamic"); |
468 | if (final) { |
469 | avio_printf(out, "\tmediaPresentationDuration=\""); |
470 | write_time(out, c->total_duration); |
471 | avio_printf(out, "\"\n"); |
472 | } else { |
473 | int64_t update_period = c->last_duration / AV_TIME_BASE; |
474 | char now_str[100]; |
475 | if (c->use_template && !c->use_timeline) |
476 | update_period = 500; |
477 | avio_printf(out, "\tminimumUpdatePeriod=\"PT%"PRId64"S\"\n", update_period); |
478 | avio_printf(out, "\tsuggestedPresentationDelay=\"PT%"PRId64"S\"\n", c->last_duration / AV_TIME_BASE); |
479 | if (!c->availability_start_time[0] && s->nb_streams > 0 && c->streams[0].nb_segments > 0) { |
480 | format_date_now(c->availability_start_time, sizeof(c->availability_start_time)); |
481 | } |
482 | if (c->availability_start_time[0]) |
483 | avio_printf(out, "\tavailabilityStartTime=\"%s\"\n", c->availability_start_time); |
484 | format_date_now(now_str, sizeof(now_str)); |
485 | if (now_str[0]) |
486 | avio_printf(out, "\tpublishTime=\"%s\"\n", now_str); |
487 | if (c->window_size && c->use_template) { |
488 | avio_printf(out, "\ttimeShiftBufferDepth=\""); |
489 | write_time(out, c->last_duration * c->window_size); |
490 | avio_printf(out, "\"\n"); |
491 | } |
492 | } |
493 | avio_printf(out, "\tminBufferTime=\""); |
494 | write_time(out, c->last_duration); |
495 | avio_printf(out, "\">\n"); |
496 | avio_printf(out, "\t<ProgramInformation>\n"); |
497 | if (title) { |
498 | char *escaped = xmlescape(title->value); |
499 | avio_printf(out, "\t\t<Title>%s</Title>\n", escaped); |
500 | av_free(escaped); |
501 | } |
502 | avio_printf(out, "\t</ProgramInformation>\n"); |
503 | if (c->window_size && s->nb_streams > 0 && c->streams[0].nb_segments > 0 && !c->use_template) { |
504 | OutputStream *os = &c->streams[0]; |
505 | int start_index = FFMAX(os->nb_segments - c->window_size, 0); |
506 | int64_t start_time = av_rescale_q(os->segments[start_index]->time, s->streams[0]->time_base, AV_TIME_BASE_Q); |
507 | avio_printf(out, "\t<Period start=\""); |
508 | write_time(out, start_time); |
509 | avio_printf(out, "\">\n"); |
510 | } else { |
511 | avio_printf(out, "\t<Period start=\"PT0.0S\">\n"); |
512 | } |
513 | |
514 | if (c->has_video) { |
515 | avio_printf(out, "\t\t<AdaptationSet contentType=\"video\" segmentAlignment=\"true\" bitstreamSwitching=\"true\""); |
516 | if (c->max_frame_rate.num && !c->ambiguous_frame_rate) |
517 | avio_printf(out, " %s=\"%d/%d\"", (av_cmp_q(c->min_frame_rate, c->max_frame_rate) < 0) ? "maxFrameRate" : "frameRate", c->max_frame_rate.num, c->max_frame_rate.den); |
518 | avio_printf(out, ">\n"); |
519 | |
520 | for (i = 0; i < s->nb_streams; i++) { |
521 | AVStream *st = s->streams[i]; |
522 | OutputStream *os = &c->streams[i]; |
523 | |
524 | if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) |
525 | continue; |
526 | |
527 | avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"video/mp4\" codecs=\"%s\"%s width=\"%d\" height=\"%d\"", i, os->codec_str, os->bandwidth_str, st->codecpar->width, st->codecpar->height); |
528 | if (st->avg_frame_rate.num) |
529 | avio_printf(out, " frameRate=\"%d/%d\"", st->avg_frame_rate.num, st->avg_frame_rate.den); |
530 | avio_printf(out, ">\n"); |
531 | |
532 | output_segment_list(&c->streams[i], out, c); |
533 | avio_printf(out, "\t\t\t</Representation>\n"); |
534 | } |
535 | avio_printf(out, "\t\t</AdaptationSet>\n"); |
536 | } |
537 | if (c->has_audio) { |
538 | avio_printf(out, "\t\t<AdaptationSet contentType=\"audio\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n"); |
539 | for (i = 0; i < s->nb_streams; i++) { |
540 | AVStream *st = s->streams[i]; |
541 | OutputStream *os = &c->streams[i]; |
542 | |
543 | if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) |
544 | continue; |
545 | |
546 | avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"audio/mp4\" codecs=\"%s\"%s audioSamplingRate=\"%d\">\n", i, os->codec_str, os->bandwidth_str, st->codecpar->sample_rate); |
547 | avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", st->codecpar->channels); |
548 | output_segment_list(&c->streams[i], out, c); |
549 | avio_printf(out, "\t\t\t</Representation>\n"); |
550 | } |
551 | avio_printf(out, "\t\t</AdaptationSet>\n"); |
552 | } |
553 | avio_printf(out, "\t</Period>\n"); |
554 | avio_printf(out, "</MPD>\n"); |
555 | avio_flush(out); |
556 | ff_format_io_close(s, &out); |
557 | |
558 | if (use_rename) |
559 | return avpriv_io_move(temp_filename, s->filename); |
560 | |
561 | return 0; |
562 | } |
563 | |
564 | static int set_bitrate(AVFormatContext *s) |
565 | { |
566 | DASHContext *c = s->priv_data; |
567 | int i; |
568 | |
569 | for (i = 0; i < s->nb_streams; i++) { |
570 | OutputStream *os = &c->streams[i]; |
571 | |
572 | os->bit_rate = s->streams[i]->codecpar->bit_rate; |
573 | if (os->bit_rate) { |
574 | snprintf(os->bandwidth_str, sizeof(os->bandwidth_str), |
575 | " bandwidth=\"%d\"", os->bit_rate); |
576 | } else { |
577 | int level = s->strict_std_compliance >= FF_COMPLIANCE_STRICT ? |
578 | AV_LOG_ERROR : AV_LOG_WARNING; |
579 | av_log(s, level, "No bit rate set for stream %d\n", i); |
580 | if (s->strict_std_compliance >= FF_COMPLIANCE_STRICT) |
581 | return AVERROR(EINVAL); |
582 | } |
583 | } |
584 | |
585 | return 0; |
586 | } |
587 | |
588 | static int dash_init(AVFormatContext *s) |
589 | { |
590 | DASHContext *c = s->priv_data; |
591 | int ret = 0, i; |
592 | AVOutputFormat *oformat; |
593 | char *ptr; |
594 | char basename[1024]; |
595 | |
596 | if (c->single_file_name) |
597 | c->single_file = 1; |
598 | if (c->single_file) |
599 | c->use_template = 0; |
600 | c->ambiguous_frame_rate = 0; |
601 | |
602 | av_strlcpy(c->dirname, s->filename, sizeof(c->dirname)); |
603 | ptr = strrchr(c->dirname, '/'); |
604 | if (ptr) { |
605 | av_strlcpy(basename, &ptr[1], sizeof(basename)); |
606 | ptr[1] = '\0'; |
607 | } else { |
608 | c->dirname[0] = '\0'; |
609 | av_strlcpy(basename, s->filename, sizeof(basename)); |
610 | } |
611 | |
612 | ptr = strrchr(basename, '.'); |
613 | if (ptr) |
614 | *ptr = '\0'; |
615 | |
616 | oformat = av_guess_format("mp4", NULL, NULL); |
617 | if (!oformat) |
618 | return AVERROR_MUXER_NOT_FOUND; |
619 | |
620 | c->streams = av_mallocz(sizeof(*c->streams) * s->nb_streams); |
621 | if (!c->streams) |
622 | return AVERROR(ENOMEM); |
623 | |
624 | ret = set_bitrate(s); |
625 | if (ret < 0) |
626 | return ret; |
627 | |
628 | for (i = 0; i < s->nb_streams; i++) { |
629 | OutputStream *os = &c->streams[i]; |
630 | AVFormatContext *ctx; |
631 | AVStream *st; |
632 | AVDictionary *opts = NULL; |
633 | char filename[1024]; |
634 | |
635 | ctx = avformat_alloc_context(); |
636 | if (!ctx) |
637 | return AVERROR(ENOMEM); |
638 | os->ctx = ctx; |
639 | ctx->oformat = oformat; |
640 | ctx->interrupt_callback = s->interrupt_callback; |
641 | ctx->opaque = s->opaque; |
642 | ctx->io_close = s->io_close; |
643 | ctx->io_open = s->io_open; |
644 | |
645 | if (!(st = avformat_new_stream(ctx, NULL))) |
646 | return AVERROR(ENOMEM); |
647 | avcodec_parameters_copy(st->codecpar, s->streams[i]->codecpar); |
648 | st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; |
649 | st->time_base = s->streams[i]->time_base; |
650 | ctx->avoid_negative_ts = s->avoid_negative_ts; |
651 | ctx->flags = s->flags; |
652 | |
653 | ctx->pb = avio_alloc_context(os->iobuf, sizeof(os->iobuf), AVIO_FLAG_WRITE, os, NULL, dash_write, NULL); |
654 | if (!ctx->pb) |
655 | return AVERROR(ENOMEM); |
656 | |
657 | if (c->single_file) { |
658 | if (c->single_file_name) |
659 | dash_fill_tmpl_params(os->initfile, sizeof(os->initfile), c->single_file_name, i, 0, os->bit_rate, 0); |
660 | else |
661 | snprintf(os->initfile, sizeof(os->initfile), "%s-stream%d.m4s", basename, i); |
662 | } else { |
663 | dash_fill_tmpl_params(os->initfile, sizeof(os->initfile), c->init_seg_name, i, 0, os->bit_rate, 0); |
664 | } |
665 | snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile); |
666 | ret = s->io_open(s, &os->out, filename, AVIO_FLAG_WRITE, NULL); |
667 | if (ret < 0) |
668 | return ret; |
669 | os->init_start_pos = 0; |
670 | |
671 | av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); |
672 | if ((ret = avformat_init_output(ctx, &opts)) < 0) |
673 | return ret; |
674 | os->ctx_inited = 1; |
675 | avio_flush(ctx->pb); |
676 | av_dict_free(&opts); |
677 | |
678 | av_log(s, AV_LOG_VERBOSE, "Representation %d init segment will be written to: %s\n", i, filename); |
679 | |
680 | s->streams[i]->time_base = st->time_base; |
681 | // If the muxer wants to shift timestamps, request to have them shifted |
682 | // already before being handed to this muxer, so we don't have mismatches |
683 | // between the MPD and the actual segments. |
684 | s->avoid_negative_ts = ctx->avoid_negative_ts; |
685 | if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { |
686 | AVRational avg_frame_rate = s->streams[i]->avg_frame_rate; |
687 | if (avg_frame_rate.num > 0) { |
688 | if (av_cmp_q(avg_frame_rate, c->min_frame_rate) < 0) |
689 | c->min_frame_rate = avg_frame_rate; |
690 | if (av_cmp_q(c->max_frame_rate, avg_frame_rate) < 0) |
691 | c->max_frame_rate = avg_frame_rate; |
692 | } else { |
693 | c->ambiguous_frame_rate = 1; |
694 | } |
695 | c->has_video = 1; |
696 | } else if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { |
697 | c->has_audio = 1; |
698 | } |
699 | |
700 | set_codec_str(s, st->codecpar, os->codec_str, sizeof(os->codec_str)); |
701 | os->first_pts = AV_NOPTS_VALUE; |
702 | os->max_pts = AV_NOPTS_VALUE; |
703 | os->last_dts = AV_NOPTS_VALUE; |
704 | os->segment_index = 1; |
705 | } |
706 | |
707 | if (!c->has_video && c->min_seg_duration <= 0) { |
708 | av_log(s, AV_LOG_WARNING, "no video stream and no min seg duration set\n"); |
709 | return AVERROR(EINVAL); |
710 | } |
711 | return 0; |
712 | } |
713 | |
714 | static int dash_write_header(AVFormatContext *s) |
715 | { |
716 | DASHContext *c = s->priv_data; |
717 | int i, ret; |
718 | for (i = 0; i < s->nb_streams; i++) { |
719 | OutputStream *os = &c->streams[i]; |
720 | if ((ret = avformat_write_header(os->ctx, NULL)) < 0) { |
721 | dash_free(s); |
722 | return ret; |
723 | } |
724 | } |
725 | ret = write_manifest(s, 0); |
726 | if (!ret) |
727 | av_log(s, AV_LOG_VERBOSE, "Manifest written to: %s\n", s->filename); |
728 | return ret; |
729 | } |
730 | |
731 | static int add_segment(OutputStream *os, const char *file, |
732 | int64_t time, int duration, |
733 | int64_t start_pos, int64_t range_length, |
734 | int64_t index_length) |
735 | { |
736 | int err; |
737 | Segment *seg; |
738 | if (os->nb_segments >= os->segments_size) { |
739 | os->segments_size = (os->segments_size + 1) * 2; |
740 | if ((err = av_reallocp(&os->segments, sizeof(*os->segments) * |
741 | os->segments_size)) < 0) { |
742 | os->segments_size = 0; |
743 | os->nb_segments = 0; |
744 | return err; |
745 | } |
746 | } |
747 | seg = av_mallocz(sizeof(*seg)); |
748 | if (!seg) |
749 | return AVERROR(ENOMEM); |
750 | av_strlcpy(seg->file, file, sizeof(seg->file)); |
751 | seg->time = time; |
752 | seg->duration = duration; |
753 | if (seg->time < 0) { // If pts<0, it is expected to be cut away with an edit list |
754 | seg->duration += seg->time; |
755 | seg->time = 0; |
756 | } |
757 | seg->start_pos = start_pos; |
758 | seg->range_length = range_length; |
759 | seg->index_length = index_length; |
760 | os->segments[os->nb_segments++] = seg; |
761 | os->segment_index++; |
762 | return 0; |
763 | } |
764 | |
765 | static void write_styp(AVIOContext *pb) |
766 | { |
767 | avio_wb32(pb, 24); |
768 | ffio_wfourcc(pb, "styp"); |
769 | ffio_wfourcc(pb, "msdh"); |
770 | avio_wb32(pb, 0); /* minor */ |
771 | ffio_wfourcc(pb, "msdh"); |
772 | ffio_wfourcc(pb, "msix"); |
773 | } |
774 | |
775 | static void find_index_range(AVFormatContext *s, const char *full_path, |
776 | int64_t pos, int *index_length) |
777 | { |
778 | uint8_t buf[8]; |
779 | AVIOContext *pb; |
780 | int ret; |
781 | |
782 | ret = s->io_open(s, &pb, full_path, AVIO_FLAG_READ, NULL); |
783 | if (ret < 0) |
784 | return; |
785 | if (avio_seek(pb, pos, SEEK_SET) != pos) { |
786 | ff_format_io_close(s, &pb); |
787 | return; |
788 | } |
789 | ret = avio_read(pb, buf, 8); |
790 | ff_format_io_close(s, &pb); |
791 | if (ret < 8) |
792 | return; |
793 | if (AV_RL32(&buf[4]) != MKTAG('s', 'i', 'd', 'x')) |
794 | return; |
795 | *index_length = AV_RB32(&buf[0]); |
796 | } |
797 | |
798 | static int update_stream_extradata(AVFormatContext *s, OutputStream *os, |
799 | AVCodecParameters *par) |
800 | { |
801 | uint8_t *extradata; |
802 | |
803 | if (os->ctx->streams[0]->codecpar->extradata_size || !par->extradata_size) |
804 | return 0; |
805 | |
806 | extradata = av_malloc(par->extradata_size); |
807 | |
808 | if (!extradata) |
809 | return AVERROR(ENOMEM); |
810 | |
811 | memcpy(extradata, par->extradata, par->extradata_size); |
812 | |
813 | os->ctx->streams[0]->codecpar->extradata = extradata; |
814 | os->ctx->streams[0]->codecpar->extradata_size = par->extradata_size; |
815 | |
816 | set_codec_str(s, par, os->codec_str, sizeof(os->codec_str)); |
817 | |
818 | return 0; |
819 | } |
820 | |
821 | static int dash_flush(AVFormatContext *s, int final, int stream) |
822 | { |
823 | DASHContext *c = s->priv_data; |
824 | int i, ret = 0; |
825 | |
826 | const char *proto = avio_find_protocol_name(s->filename); |
827 | int use_rename = proto && !strcmp(proto, "file"); |
828 | |
829 | int cur_flush_segment_index = 0; |
830 | if (stream >= 0) |
831 | cur_flush_segment_index = c->streams[stream].segment_index; |
832 | |
833 | for (i = 0; i < s->nb_streams; i++) { |
834 | OutputStream *os = &c->streams[i]; |
835 | char filename[1024] = "", full_path[1024], temp_path[1024]; |
836 | int64_t start_pos; |
837 | int range_length, index_length = 0; |
838 | |
839 | if (!os->packets_written) |
840 | continue; |
841 | |
842 | // Flush the single stream that got a keyframe right now. |
843 | // Flush all audio streams as well, in sync with video keyframes, |
844 | // but not the other video streams. |
845 | if (stream >= 0 && i != stream) { |
846 | if (s->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) |
847 | continue; |
848 | // Make sure we don't flush audio streams multiple times, when |
849 | // all video streams are flushed one at a time. |
850 | if (c->has_video && os->segment_index > cur_flush_segment_index) |
851 | continue; |
852 | } |
853 | |
854 | if (!os->init_range_length) { |
855 | av_write_frame(os->ctx, NULL); |
856 | os->init_range_length = avio_tell(os->ctx->pb); |
857 | if (!c->single_file) |
858 | ff_format_io_close(s, &os->out); |
859 | } |
860 | |
861 | start_pos = avio_tell(os->ctx->pb); |
862 | |
863 | if (!c->single_file) { |
864 | dash_fill_tmpl_params(filename, sizeof(filename), c->media_seg_name, i, os->segment_index, os->bit_rate, os->start_pts); |
865 | snprintf(full_path, sizeof(full_path), "%s%s", c->dirname, filename); |
866 | snprintf(temp_path, sizeof(temp_path), use_rename ? "%s.tmp" : "%s", full_path); |
867 | ret = s->io_open(s, &os->out, temp_path, AVIO_FLAG_WRITE, NULL); |
868 | if (ret < 0) |
869 | break; |
870 | write_styp(os->ctx->pb); |
871 | } else { |
872 | snprintf(full_path, sizeof(full_path), "%s%s", c->dirname, os->initfile); |
873 | } |
874 | |
875 | av_write_frame(os->ctx, NULL); |
876 | avio_flush(os->ctx->pb); |
877 | os->packets_written = 0; |
878 | |
879 | range_length = avio_tell(os->ctx->pb) - start_pos; |
880 | if (c->single_file) { |
881 | find_index_range(s, full_path, start_pos, &index_length); |
882 | } else { |
883 | ff_format_io_close(s, &os->out); |
884 | |
885 | if (use_rename) { |
886 | ret = avpriv_io_move(temp_path, full_path); |
887 | if (ret < 0) |
888 | break; |
889 | } |
890 | } |
891 | add_segment(os, filename, os->start_pts, os->max_pts - os->start_pts, start_pos, range_length, index_length); |
892 | av_log(s, AV_LOG_VERBOSE, "Representation %d media segment %d written to: %s\n", i, os->segment_index, full_path); |
893 | } |
894 | |
895 | if (c->window_size || (final && c->remove_at_exit)) { |
896 | for (i = 0; i < s->nb_streams; i++) { |
897 | OutputStream *os = &c->streams[i]; |
898 | int j; |
899 | int remove = os->nb_segments - c->window_size - c->extra_window_size; |
900 | if (final && c->remove_at_exit) |
901 | remove = os->nb_segments; |
902 | if (remove > 0) { |
903 | for (j = 0; j < remove; j++) { |
904 | char filename[1024]; |
905 | snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->segments[j]->file); |
906 | unlink(filename); |
907 | av_free(os->segments[j]); |
908 | } |
909 | os->nb_segments -= remove; |
910 | memmove(os->segments, os->segments + remove, os->nb_segments * sizeof(*os->segments)); |
911 | } |
912 | } |
913 | } |
914 | |
915 | if (ret >= 0) |
916 | ret = write_manifest(s, final); |
917 | return ret; |
918 | } |
919 | |
920 | static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) |
921 | { |
922 | DASHContext *c = s->priv_data; |
923 | AVStream *st = s->streams[pkt->stream_index]; |
924 | OutputStream *os = &c->streams[pkt->stream_index]; |
925 | int64_t seg_end_duration = (os->segment_index) * (int64_t) c->min_seg_duration; |
926 | int ret; |
927 | |
928 | ret = update_stream_extradata(s, os, st->codecpar); |
929 | if (ret < 0) |
930 | return ret; |
931 | |
932 | // Fill in a heuristic guess of the packet duration, if none is available. |
933 | // The mp4 muxer will do something similar (for the last packet in a fragment) |
934 | // if nothing is set (setting it for the other packets doesn't hurt). |
935 | // By setting a nonzero duration here, we can be sure that the mp4 muxer won't |
936 | // invoke its heuristic (this doesn't have to be identical to that algorithm), |
937 | // so that we know the exact timestamps of fragments. |
938 | if (!pkt->duration && os->last_dts != AV_NOPTS_VALUE) |
939 | pkt->duration = pkt->dts - os->last_dts; |
940 | os->last_dts = pkt->dts; |
941 | |
942 | // If forcing the stream to start at 0, the mp4 muxer will set the start |
943 | // timestamps to 0. Do the same here, to avoid mismatches in duration/timestamps. |
944 | if (os->first_pts == AV_NOPTS_VALUE && |
945 | s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO) { |
946 | pkt->pts -= pkt->dts; |
947 | pkt->dts = 0; |
948 | } |
949 | |
950 | if (os->first_pts == AV_NOPTS_VALUE) |
951 | os->first_pts = pkt->pts; |
952 | |
953 | if ((!c->has_video || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) && |
954 | pkt->flags & AV_PKT_FLAG_KEY && os->packets_written && |
955 | av_compare_ts(pkt->pts - os->first_pts, st->time_base, |
956 | seg_end_duration, AV_TIME_BASE_Q) >= 0) { |
957 | int64_t prev_duration = c->last_duration; |
958 | |
959 | c->last_duration = av_rescale_q(pkt->pts - os->start_pts, |
960 | st->time_base, |
961 | AV_TIME_BASE_Q); |
962 | c->total_duration = av_rescale_q(pkt->pts - os->first_pts, |
963 | st->time_base, |
964 | AV_TIME_BASE_Q); |
965 | |
966 | if ((!c->use_timeline || !c->use_template) && prev_duration) { |
967 | if (c->last_duration < prev_duration*9/10 || |
968 | c->last_duration > prev_duration*11/10) { |
969 | av_log(s, AV_LOG_WARNING, |
970 | "Segment durations differ too much, enable use_timeline " |
971 | "and use_template, or keep a stricter keyframe interval\n"); |
972 | } |
973 | } |
974 | |
975 | if ((ret = dash_flush(s, 0, pkt->stream_index)) < 0) |
976 | return ret; |
977 | } |
978 | |
979 | if (!os->packets_written) { |
980 | // If we wrote a previous segment, adjust the start time of the segment |
981 | // to the end of the previous one (which is the same as the mp4 muxer |
982 | // does). This avoids gaps in the timeline. |
983 | if (os->max_pts != AV_NOPTS_VALUE) |
984 | os->start_pts = os->max_pts; |
985 | else |
986 | os->start_pts = pkt->pts; |
987 | } |
988 | if (os->max_pts == AV_NOPTS_VALUE) |
989 | os->max_pts = pkt->pts + pkt->duration; |
990 | else |
991 | os->max_pts = FFMAX(os->max_pts, pkt->pts + pkt->duration); |
992 | os->packets_written++; |
993 | return ff_write_chained(os->ctx, 0, pkt, s, 0); |
994 | } |
995 | |
996 | static int dash_write_trailer(AVFormatContext *s) |
997 | { |
998 | DASHContext *c = s->priv_data; |
999 | |
1000 | set_bitrate(s); |
1001 | |
1002 | if (s->nb_streams > 0) { |
1003 | OutputStream *os = &c->streams[0]; |
1004 | // If no segments have been written so far, try to do a crude |
1005 | // guess of the segment duration |
1006 | if (!c->last_duration) |
1007 | c->last_duration = av_rescale_q(os->max_pts - os->start_pts, |
1008 | s->streams[0]->time_base, |
1009 | AV_TIME_BASE_Q); |
1010 | c->total_duration = av_rescale_q(os->max_pts - os->first_pts, |
1011 | s->streams[0]->time_base, |
1012 | AV_TIME_BASE_Q); |
1013 | } |
1014 | dash_flush(s, 1, -1); |
1015 | |
1016 | if (c->remove_at_exit) { |
1017 | char filename[1024]; |
1018 | int i; |
1019 | for (i = 0; i < s->nb_streams; i++) { |
1020 | OutputStream *os = &c->streams[i]; |
1021 | snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile); |
1022 | unlink(filename); |
1023 | } |
1024 | unlink(s->filename); |
1025 | } |
1026 | |
1027 | return 0; |
1028 | } |
1029 | |
1030 | static int dash_check_bitstream(struct AVFormatContext *s, const AVPacket *avpkt) |
1031 | { |
1032 | DASHContext *c = s->priv_data; |
1033 | OutputStream *os = &c->streams[avpkt->stream_index]; |
1034 | AVFormatContext *oc = os->ctx; |
1035 | if (oc->oformat->check_bitstream) { |
1036 | int ret; |
1037 | AVPacket pkt = *avpkt; |
1038 | pkt.stream_index = 0; |
1039 | ret = oc->oformat->check_bitstream(oc, &pkt); |
1040 | if (ret == 1) { |
1041 | AVStream *st = s->streams[avpkt->stream_index]; |
1042 | AVStream *ost = oc->streams[0]; |
1043 | st->internal->bsfcs = ost->internal->bsfcs; |
1044 | st->internal->nb_bsfcs = ost->internal->nb_bsfcs; |
1045 | ost->internal->bsfcs = NULL; |
1046 | ost->internal->nb_bsfcs = 0; |
1047 | } |
1048 | return ret; |
1049 | } |
1050 | return 1; |
1051 | } |
1052 | |
1053 | #define OFFSET(x) offsetof(DASHContext, x) |
1054 | #define E AV_OPT_FLAG_ENCODING_PARAM |
1055 | static const AVOption options[] = { |
1056 | { "window_size", "number of segments kept in the manifest", OFFSET(window_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, E }, |
1057 | { "extra_window_size", "number of segments kept outside of the manifest before removing from disk", OFFSET(extra_window_size), AV_OPT_TYPE_INT, { .i64 = 5 }, 0, INT_MAX, E }, |
1058 | { "min_seg_duration", "minimum segment duration (in microseconds)", OFFSET(min_seg_duration), AV_OPT_TYPE_INT64, { .i64 = 5000000 }, 0, INT_MAX, E }, |
1059 | { "remove_at_exit", "remove all segments when finished", OFFSET(remove_at_exit), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E }, |
1060 | { "use_template", "Use SegmentTemplate instead of SegmentList", OFFSET(use_template), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, E }, |
1061 | { "use_timeline", "Use SegmentTimeline in SegmentTemplate", OFFSET(use_timeline), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, E }, |
1062 | { "single_file", "Store all segments in one file, accessed using byte ranges", OFFSET(single_file), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E }, |
1063 | { "single_file_name", "DASH-templated name to be used for baseURL. Implies storing all segments in one file, accessed using byte ranges", OFFSET(single_file_name), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E }, |
1064 | { "init_seg_name", "DASH-templated name to used for the initialization segment", OFFSET(init_seg_name), AV_OPT_TYPE_STRING, {.str = "init-stream$RepresentationID$.m4s"}, 0, 0, E }, |
1065 | { "media_seg_name", "DASH-templated name to used for the media segments", OFFSET(media_seg_name), AV_OPT_TYPE_STRING, {.str = "chunk-stream$RepresentationID$-$Number%05d$.m4s"}, 0, 0, E }, |
1066 | { NULL }, |
1067 | }; |
1068 | |
1069 | static const AVClass dash_class = { |
1070 | .class_name = "dash muxer", |
1071 | .item_name = av_default_item_name, |
1072 | .option = options, |
1073 | .version = LIBAVUTIL_VERSION_INT, |
1074 | }; |
1075 | |
1076 | AVOutputFormat ff_dash_muxer = { |
1077 | .name = "dash", |
1078 | .long_name = NULL_IF_CONFIG_SMALL("DASH Muxer"), |
1079 | .priv_data_size = sizeof(DASHContext), |
1080 | .audio_codec = AV_CODEC_ID_AAC, |
1081 | .video_codec = AV_CODEC_ID_H264, |
1082 | .flags = AVFMT_GLOBALHEADER | AVFMT_NOFILE | AVFMT_TS_NEGATIVE, |
1083 | .init = dash_init, |
1084 | .write_header = dash_write_header, |
1085 | .write_packet = dash_write_packet, |
1086 | .write_trailer = dash_write_trailer, |
1087 | .deinit = dash_free, |
1088 | .codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 }, |
1089 | .check_bitstream = dash_check_bitstream, |
1090 | .priv_class = &dash_class, |
1091 | }; |
1092 |