Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * Live smooth streaming fragmenter | |
3 | * Copyright (c) 2012 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 | #include <float.h> | |
24 | #if HAVE_UNISTD_H | |
25 | #include <unistd.h> | |
26 | #endif | |
27 | ||
28 | #include "avformat.h" | |
29 | #include "internal.h" | |
30 | #include "os_support.h" | |
31 | #include "avc.h" | |
32 | #include "url.h" | |
33 | #include "isom.h" | |
34 | ||
35 | #include "libavutil/opt.h" | |
36 | #include "libavutil/avstring.h" | |
f6fa7814 | 37 | #include "libavutil/file.h" |
2ba45a60 DM |
38 | #include "libavutil/mathematics.h" |
39 | #include "libavutil/intreadwrite.h" | |
40 | ||
41 | typedef struct { | |
42 | char file[1024]; | |
43 | char infofile[1024]; | |
44 | int64_t start_time, duration; | |
45 | int n; | |
46 | int64_t start_pos, size; | |
47 | } Fragment; | |
48 | ||
49 | typedef struct { | |
50 | AVFormatContext *ctx; | |
51 | int ctx_inited; | |
52 | char dirname[1024]; | |
53 | uint8_t iobuf[32768]; | |
54 | URLContext *out; // Current output stream where all output is written | |
55 | URLContext *out2; // Auxiliary output stream where all output is also written | |
56 | URLContext *tail_out; // The actual main output stream, if we're currently seeked back to write elsewhere | |
57 | int64_t tail_pos, cur_pos, cur_start_pos; | |
58 | int packets_written; | |
59 | const char *stream_type_tag; | |
60 | int nb_fragments, fragments_size, fragment_index; | |
61 | Fragment **fragments; | |
62 | ||
63 | const char *fourcc; | |
64 | char *private_str; | |
65 | int packet_size; | |
66 | int audio_tag; | |
67 | } OutputStream; | |
68 | ||
69 | typedef struct { | |
70 | const AVClass *class; /* Class for private options. */ | |
71 | int window_size; | |
72 | int extra_window_size; | |
73 | int lookahead_count; | |
74 | int min_frag_duration; | |
75 | int remove_at_exit; | |
76 | OutputStream *streams; | |
77 | int has_video, has_audio; | |
78 | int nb_fragments; | |
79 | } SmoothStreamingContext; | |
80 | ||
81 | static int ism_write(void *opaque, uint8_t *buf, int buf_size) | |
82 | { | |
83 | OutputStream *os = opaque; | |
84 | if (os->out) | |
85 | ffurl_write(os->out, buf, buf_size); | |
86 | if (os->out2) | |
87 | ffurl_write(os->out2, buf, buf_size); | |
88 | os->cur_pos += buf_size; | |
89 | if (os->cur_pos >= os->tail_pos) | |
90 | os->tail_pos = os->cur_pos; | |
91 | return buf_size; | |
92 | } | |
93 | ||
94 | static int64_t ism_seek(void *opaque, int64_t offset, int whence) | |
95 | { | |
96 | OutputStream *os = opaque; | |
97 | int i; | |
98 | if (whence != SEEK_SET) | |
99 | return AVERROR(ENOSYS); | |
100 | if (os->tail_out) { | |
101 | if (os->out) { | |
102 | ffurl_close(os->out); | |
103 | } | |
104 | if (os->out2) { | |
105 | ffurl_close(os->out2); | |
106 | } | |
107 | os->out = os->tail_out; | |
108 | os->out2 = NULL; | |
109 | os->tail_out = NULL; | |
110 | } | |
111 | if (offset >= os->cur_start_pos) { | |
112 | if (os->out) | |
113 | ffurl_seek(os->out, offset - os->cur_start_pos, SEEK_SET); | |
114 | os->cur_pos = offset; | |
115 | return offset; | |
116 | } | |
117 | for (i = os->nb_fragments - 1; i >= 0; i--) { | |
118 | Fragment *frag = os->fragments[i]; | |
119 | if (offset >= frag->start_pos && offset < frag->start_pos + frag->size) { | |
120 | int ret; | |
121 | AVDictionary *opts = NULL; | |
122 | os->tail_out = os->out; | |
123 | av_dict_set(&opts, "truncate", "0", 0); | |
124 | ret = ffurl_open(&os->out, frag->file, AVIO_FLAG_READ_WRITE, &os->ctx->interrupt_callback, &opts); | |
125 | av_dict_free(&opts); | |
126 | if (ret < 0) { | |
127 | os->out = os->tail_out; | |
128 | os->tail_out = NULL; | |
129 | return ret; | |
130 | } | |
131 | av_dict_set(&opts, "truncate", "0", 0); | |
132 | ffurl_open(&os->out2, frag->infofile, AVIO_FLAG_READ_WRITE, &os->ctx->interrupt_callback, &opts); | |
133 | av_dict_free(&opts); | |
134 | ffurl_seek(os->out, offset - frag->start_pos, SEEK_SET); | |
135 | if (os->out2) | |
136 | ffurl_seek(os->out2, offset - frag->start_pos, SEEK_SET); | |
137 | os->cur_pos = offset; | |
138 | return offset; | |
139 | } | |
140 | } | |
141 | return AVERROR(EIO); | |
142 | } | |
143 | ||
144 | static void get_private_data(OutputStream *os) | |
145 | { | |
146 | AVCodecContext *codec = os->ctx->streams[0]->codec; | |
147 | uint8_t *ptr = codec->extradata; | |
148 | int size = codec->extradata_size; | |
149 | int i; | |
150 | if (codec->codec_id == AV_CODEC_ID_H264) { | |
151 | ff_avc_write_annexb_extradata(ptr, &ptr, &size); | |
152 | if (!ptr) | |
153 | ptr = codec->extradata; | |
154 | } | |
155 | if (!ptr) | |
156 | return; | |
157 | os->private_str = av_mallocz(2*size + 1); | |
158 | if (!os->private_str) | |
159 | goto fail; | |
160 | for (i = 0; i < size; i++) | |
161 | snprintf(&os->private_str[2*i], 3, "%02x", ptr[i]); | |
162 | fail: | |
163 | if (ptr != codec->extradata) | |
164 | av_free(ptr); | |
165 | } | |
166 | ||
167 | static void ism_free(AVFormatContext *s) | |
168 | { | |
169 | SmoothStreamingContext *c = s->priv_data; | |
170 | int i, j; | |
171 | if (!c->streams) | |
172 | return; | |
173 | for (i = 0; i < s->nb_streams; i++) { | |
174 | OutputStream *os = &c->streams[i]; | |
175 | ffurl_close(os->out); | |
176 | ffurl_close(os->out2); | |
177 | ffurl_close(os->tail_out); | |
178 | os->out = os->out2 = os->tail_out = NULL; | |
179 | if (os->ctx && os->ctx_inited) | |
180 | av_write_trailer(os->ctx); | |
181 | if (os->ctx && os->ctx->pb) | |
182 | av_free(os->ctx->pb); | |
183 | if (os->ctx) | |
184 | avformat_free_context(os->ctx); | |
185 | av_free(os->private_str); | |
186 | for (j = 0; j < os->nb_fragments; j++) | |
187 | av_free(os->fragments[j]); | |
188 | av_free(os->fragments); | |
189 | } | |
190 | av_freep(&c->streams); | |
191 | } | |
192 | ||
193 | static void output_chunk_list(OutputStream *os, AVIOContext *out, int final, int skip, int window_size) | |
194 | { | |
195 | int removed = 0, i, start = 0; | |
196 | if (os->nb_fragments <= 0) | |
197 | return; | |
198 | if (os->fragments[0]->n > 0) | |
199 | removed = 1; | |
200 | if (final) | |
201 | skip = 0; | |
202 | if (window_size) | |
203 | start = FFMAX(os->nb_fragments - skip - window_size, 0); | |
204 | for (i = start; i < os->nb_fragments - skip; i++) { | |
205 | Fragment *frag = os->fragments[i]; | |
206 | if (!final || removed) | |
207 | avio_printf(out, "<c t=\"%"PRIu64"\" d=\"%"PRIu64"\" />\n", frag->start_time, frag->duration); | |
208 | else | |
209 | avio_printf(out, "<c n=\"%d\" d=\"%"PRIu64"\" />\n", frag->n, frag->duration); | |
210 | } | |
211 | } | |
212 | ||
213 | static int write_manifest(AVFormatContext *s, int final) | |
214 | { | |
215 | SmoothStreamingContext *c = s->priv_data; | |
216 | AVIOContext *out; | |
217 | char filename[1024], temp_filename[1024]; | |
218 | int ret, i, video_chunks = 0, audio_chunks = 0, video_streams = 0, audio_streams = 0; | |
219 | int64_t duration = 0; | |
220 | ||
221 | snprintf(filename, sizeof(filename), "%s/Manifest", s->filename); | |
222 | snprintf(temp_filename, sizeof(temp_filename), "%s/Manifest.tmp", s->filename); | |
223 | ret = avio_open2(&out, temp_filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL); | |
224 | if (ret < 0) { | |
225 | av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename); | |
226 | return ret; | |
227 | } | |
228 | avio_printf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); | |
229 | for (i = 0; i < s->nb_streams; i++) { | |
230 | OutputStream *os = &c->streams[i]; | |
231 | if (os->nb_fragments > 0) { | |
232 | Fragment *last = os->fragments[os->nb_fragments - 1]; | |
233 | duration = last->start_time + last->duration; | |
234 | } | |
235 | if (s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { | |
236 | video_chunks = os->nb_fragments; | |
237 | video_streams++; | |
238 | } else { | |
239 | audio_chunks = os->nb_fragments; | |
240 | audio_streams++; | |
241 | } | |
242 | } | |
243 | if (!final) { | |
244 | duration = 0; | |
245 | video_chunks = audio_chunks = 0; | |
246 | } | |
247 | if (c->window_size) { | |
248 | video_chunks = FFMIN(video_chunks, c->window_size); | |
249 | audio_chunks = FFMIN(audio_chunks, c->window_size); | |
250 | } | |
251 | avio_printf(out, "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" Duration=\"%"PRIu64"\"", duration); | |
252 | if (!final) | |
253 | avio_printf(out, " IsLive=\"true\" LookAheadFragmentCount=\"%d\" DVRWindowLength=\"0\"", c->lookahead_count); | |
254 | avio_printf(out, ">\n"); | |
255 | if (c->has_video) { | |
256 | int last = -1, index = 0; | |
257 | avio_printf(out, "<StreamIndex Type=\"video\" QualityLevels=\"%d\" Chunks=\"%d\" Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n", video_streams, video_chunks); | |
258 | for (i = 0; i < s->nb_streams; i++) { | |
259 | OutputStream *os = &c->streams[i]; | |
260 | if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_VIDEO) | |
261 | continue; | |
262 | last = i; | |
263 | avio_printf(out, "<QualityLevel Index=\"%d\" Bitrate=\"%d\" FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" CodecPrivateData=\"%s\" />\n", index, s->streams[i]->codec->bit_rate, os->fourcc, s->streams[i]->codec->width, s->streams[i]->codec->height, os->private_str); | |
264 | index++; | |
265 | } | |
266 | output_chunk_list(&c->streams[last], out, final, c->lookahead_count, c->window_size); | |
267 | avio_printf(out, "</StreamIndex>\n"); | |
268 | } | |
269 | if (c->has_audio) { | |
270 | int last = -1, index = 0; | |
271 | avio_printf(out, "<StreamIndex Type=\"audio\" QualityLevels=\"%d\" Chunks=\"%d\" Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n", audio_streams, audio_chunks); | |
272 | for (i = 0; i < s->nb_streams; i++) { | |
273 | OutputStream *os = &c->streams[i]; | |
274 | if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO) | |
275 | continue; | |
276 | last = i; | |
277 | avio_printf(out, "<QualityLevel Index=\"%d\" Bitrate=\"%d\" FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" BitsPerSample=\"16\" PacketSize=\"%d\" AudioTag=\"%d\" CodecPrivateData=\"%s\" />\n", index, s->streams[i]->codec->bit_rate, os->fourcc, s->streams[i]->codec->sample_rate, s->streams[i]->codec->channels, os->packet_size, os->audio_tag, os->private_str); | |
278 | index++; | |
279 | } | |
280 | output_chunk_list(&c->streams[last], out, final, c->lookahead_count, c->window_size); | |
281 | avio_printf(out, "</StreamIndex>\n"); | |
282 | } | |
283 | avio_printf(out, "</SmoothStreamingMedia>\n"); | |
284 | avio_flush(out); | |
285 | avio_close(out); | |
f6fa7814 | 286 | return ff_rename(temp_filename, filename, s); |
2ba45a60 DM |
287 | } |
288 | ||
289 | static int ism_write_header(AVFormatContext *s) | |
290 | { | |
291 | SmoothStreamingContext *c = s->priv_data; | |
292 | int ret = 0, i; | |
293 | AVOutputFormat *oformat; | |
294 | ||
295 | if (mkdir(s->filename, 0777) == -1 && errno != EEXIST) { | |
2ba45a60 | 296 | ret = AVERROR(errno); |
f6fa7814 | 297 | av_log(s, AV_LOG_ERROR, "mkdir failed\n"); |
2ba45a60 DM |
298 | goto fail; |
299 | } | |
300 | ||
301 | oformat = av_guess_format("ismv", NULL, NULL); | |
302 | if (!oformat) { | |
303 | ret = AVERROR_MUXER_NOT_FOUND; | |
304 | goto fail; | |
305 | } | |
306 | ||
307 | c->streams = av_mallocz_array(s->nb_streams, sizeof(*c->streams)); | |
308 | if (!c->streams) { | |
309 | ret = AVERROR(ENOMEM); | |
310 | goto fail; | |
311 | } | |
312 | ||
313 | for (i = 0; i < s->nb_streams; i++) { | |
314 | OutputStream *os = &c->streams[i]; | |
315 | AVFormatContext *ctx; | |
316 | AVStream *st; | |
317 | AVDictionary *opts = NULL; | |
318 | ||
319 | if (!s->streams[i]->codec->bit_rate) { | |
320 | av_log(s, AV_LOG_ERROR, "No bit rate set for stream %d\n", i); | |
321 | ret = AVERROR(EINVAL); | |
322 | goto fail; | |
323 | } | |
324 | snprintf(os->dirname, sizeof(os->dirname), "%s/QualityLevels(%d)", s->filename, s->streams[i]->codec->bit_rate); | |
325 | if (mkdir(os->dirname, 0777) == -1 && errno != EEXIST) { | |
326 | ret = AVERROR(errno); | |
327 | av_log(s, AV_LOG_ERROR, "mkdir failed\n"); | |
328 | goto fail; | |
329 | } | |
330 | ||
331 | ctx = avformat_alloc_context(); | |
332 | if (!ctx) { | |
333 | ret = AVERROR(ENOMEM); | |
334 | goto fail; | |
335 | } | |
336 | os->ctx = ctx; | |
337 | ctx->oformat = oformat; | |
338 | ctx->interrupt_callback = s->interrupt_callback; | |
339 | ||
340 | if (!(st = avformat_new_stream(ctx, NULL))) { | |
341 | ret = AVERROR(ENOMEM); | |
342 | goto fail; | |
343 | } | |
344 | avcodec_copy_context(st->codec, s->streams[i]->codec); | |
345 | st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; | |
f6fa7814 | 346 | st->time_base = s->streams[i]->time_base; |
2ba45a60 DM |
347 | |
348 | ctx->pb = avio_alloc_context(os->iobuf, sizeof(os->iobuf), AVIO_FLAG_WRITE, os, NULL, ism_write, ism_seek); | |
349 | if (!ctx->pb) { | |
350 | ret = AVERROR(ENOMEM); | |
351 | goto fail; | |
352 | } | |
353 | ||
354 | av_dict_set_int(&opts, "ism_lookahead", c->lookahead_count, 0); | |
355 | av_dict_set(&opts, "movflags", "frag_custom", 0); | |
356 | if ((ret = avformat_write_header(ctx, &opts)) < 0) { | |
357 | goto fail; | |
358 | } | |
359 | os->ctx_inited = 1; | |
360 | avio_flush(ctx->pb); | |
361 | av_dict_free(&opts); | |
362 | s->streams[i]->time_base = st->time_base; | |
363 | if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { | |
364 | c->has_video = 1; | |
365 | os->stream_type_tag = "video"; | |
366 | if (st->codec->codec_id == AV_CODEC_ID_H264) { | |
367 | os->fourcc = "H264"; | |
368 | } else if (st->codec->codec_id == AV_CODEC_ID_VC1) { | |
369 | os->fourcc = "WVC1"; | |
370 | } else { | |
371 | av_log(s, AV_LOG_ERROR, "Unsupported video codec\n"); | |
372 | ret = AVERROR(EINVAL); | |
373 | goto fail; | |
374 | } | |
375 | } else { | |
376 | c->has_audio = 1; | |
377 | os->stream_type_tag = "audio"; | |
378 | if (st->codec->codec_id == AV_CODEC_ID_AAC) { | |
379 | os->fourcc = "AACL"; | |
380 | os->audio_tag = 0xff; | |
381 | } else if (st->codec->codec_id == AV_CODEC_ID_WMAPRO) { | |
382 | os->fourcc = "WMAP"; | |
383 | os->audio_tag = 0x0162; | |
384 | } else { | |
385 | av_log(s, AV_LOG_ERROR, "Unsupported audio codec\n"); | |
386 | ret = AVERROR(EINVAL); | |
387 | goto fail; | |
388 | } | |
389 | os->packet_size = st->codec->block_align ? st->codec->block_align : 4; | |
390 | } | |
391 | get_private_data(os); | |
392 | } | |
393 | ||
394 | if (!c->has_video && c->min_frag_duration <= 0) { | |
395 | av_log(s, AV_LOG_WARNING, "no video stream and no min frag duration set\n"); | |
396 | ret = AVERROR(EINVAL); | |
397 | } | |
398 | ret = write_manifest(s, 0); | |
399 | ||
400 | fail: | |
401 | if (ret) | |
402 | ism_free(s); | |
403 | return ret; | |
404 | } | |
405 | ||
406 | static int parse_fragment(AVFormatContext *s, const char *filename, int64_t *start_ts, int64_t *duration, int64_t *moof_size, int64_t size) | |
407 | { | |
408 | AVIOContext *in; | |
409 | int ret; | |
410 | uint32_t len; | |
411 | if ((ret = avio_open2(&in, filename, AVIO_FLAG_READ, &s->interrupt_callback, NULL)) < 0) | |
412 | return ret; | |
413 | ret = AVERROR(EIO); | |
414 | *moof_size = avio_rb32(in); | |
415 | if (*moof_size < 8 || *moof_size > size) | |
416 | goto fail; | |
417 | if (avio_rl32(in) != MKTAG('m','o','o','f')) | |
418 | goto fail; | |
419 | len = avio_rb32(in); | |
420 | if (len > *moof_size) | |
421 | goto fail; | |
422 | if (avio_rl32(in) != MKTAG('m','f','h','d')) | |
423 | goto fail; | |
424 | avio_seek(in, len - 8, SEEK_CUR); | |
425 | avio_rb32(in); /* traf size */ | |
426 | if (avio_rl32(in) != MKTAG('t','r','a','f')) | |
427 | goto fail; | |
428 | while (avio_tell(in) < *moof_size) { | |
429 | uint32_t len = avio_rb32(in); | |
430 | uint32_t tag = avio_rl32(in); | |
431 | int64_t end = avio_tell(in) + len - 8; | |
432 | if (len < 8 || len >= *moof_size) | |
433 | goto fail; | |
434 | if (tag == MKTAG('u','u','i','d')) { | |
435 | static const uint8_t tfxd[] = { | |
436 | 0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6, | |
437 | 0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2 | |
438 | }; | |
439 | uint8_t uuid[16]; | |
440 | avio_read(in, uuid, 16); | |
441 | if (!memcmp(uuid, tfxd, 16) && len >= 8 + 16 + 4 + 16) { | |
442 | avio_seek(in, 4, SEEK_CUR); | |
443 | *start_ts = avio_rb64(in); | |
444 | *duration = avio_rb64(in); | |
445 | ret = 0; | |
446 | break; | |
447 | } | |
448 | } | |
449 | avio_seek(in, end, SEEK_SET); | |
450 | } | |
451 | fail: | |
452 | avio_close(in); | |
453 | return ret; | |
454 | } | |
455 | ||
456 | static int add_fragment(OutputStream *os, const char *file, const char *infofile, int64_t start_time, int64_t duration, int64_t start_pos, int64_t size) | |
457 | { | |
458 | int err; | |
459 | Fragment *frag; | |
460 | if (os->nb_fragments >= os->fragments_size) { | |
461 | os->fragments_size = (os->fragments_size + 1) * 2; | |
462 | if ((err = av_reallocp(&os->fragments, sizeof(*os->fragments) * | |
463 | os->fragments_size)) < 0) { | |
464 | os->fragments_size = 0; | |
465 | os->nb_fragments = 0; | |
466 | return err; | |
467 | } | |
468 | } | |
469 | frag = av_mallocz(sizeof(*frag)); | |
470 | if (!frag) | |
471 | return AVERROR(ENOMEM); | |
472 | av_strlcpy(frag->file, file, sizeof(frag->file)); | |
473 | av_strlcpy(frag->infofile, infofile, sizeof(frag->infofile)); | |
474 | frag->start_time = start_time; | |
475 | frag->duration = duration; | |
476 | frag->start_pos = start_pos; | |
477 | frag->size = size; | |
478 | frag->n = os->fragment_index; | |
479 | os->fragments[os->nb_fragments++] = frag; | |
480 | os->fragment_index++; | |
481 | return 0; | |
482 | } | |
483 | ||
484 | static int copy_moof(AVFormatContext *s, const char* infile, const char *outfile, int64_t size) | |
485 | { | |
486 | AVIOContext *in, *out; | |
487 | int ret = 0; | |
488 | if ((ret = avio_open2(&in, infile, AVIO_FLAG_READ, &s->interrupt_callback, NULL)) < 0) | |
489 | return ret; | |
490 | if ((ret = avio_open2(&out, outfile, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL)) < 0) { | |
491 | avio_close(in); | |
492 | return ret; | |
493 | } | |
494 | while (size > 0) { | |
495 | uint8_t buf[8192]; | |
496 | int n = FFMIN(size, sizeof(buf)); | |
497 | n = avio_read(in, buf, n); | |
498 | if (n <= 0) { | |
499 | ret = AVERROR(EIO); | |
500 | break; | |
501 | } | |
502 | avio_write(out, buf, n); | |
503 | size -= n; | |
504 | } | |
505 | avio_flush(out); | |
506 | avio_close(out); | |
507 | avio_close(in); | |
508 | return ret; | |
509 | } | |
510 | ||
511 | static int ism_flush(AVFormatContext *s, int final) | |
512 | { | |
513 | SmoothStreamingContext *c = s->priv_data; | |
514 | int i, ret = 0; | |
515 | ||
516 | for (i = 0; i < s->nb_streams; i++) { | |
517 | OutputStream *os = &c->streams[i]; | |
518 | char filename[1024], target_filename[1024], header_filename[1024]; | |
f6fa7814 | 519 | int64_t size; |
2ba45a60 DM |
520 | int64_t start_ts, duration, moof_size; |
521 | if (!os->packets_written) | |
522 | continue; | |
523 | ||
524 | snprintf(filename, sizeof(filename), "%s/temp", os->dirname); | |
525 | ret = ffurl_open(&os->out, filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL); | |
526 | if (ret < 0) | |
527 | break; | |
528 | os->cur_start_pos = os->tail_pos; | |
529 | av_write_frame(os->ctx, NULL); | |
530 | avio_flush(os->ctx->pb); | |
531 | os->packets_written = 0; | |
532 | if (!os->out || os->tail_out) | |
533 | return AVERROR(EIO); | |
534 | ||
535 | ffurl_close(os->out); | |
536 | os->out = NULL; | |
f6fa7814 | 537 | size = os->tail_pos - os->cur_start_pos; |
2ba45a60 DM |
538 | if ((ret = parse_fragment(s, filename, &start_ts, &duration, &moof_size, size)) < 0) |
539 | break; | |
540 | snprintf(header_filename, sizeof(header_filename), "%s/FragmentInfo(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts); | |
541 | snprintf(target_filename, sizeof(target_filename), "%s/Fragments(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts); | |
542 | copy_moof(s, filename, header_filename, moof_size); | |
f6fa7814 DM |
543 | ret = ff_rename(filename, target_filename, s); |
544 | if (ret < 0) | |
545 | break; | |
546 | add_fragment(os, target_filename, header_filename, start_ts, duration, | |
547 | os->cur_start_pos, size); | |
2ba45a60 DM |
548 | } |
549 | ||
550 | if (c->window_size || (final && c->remove_at_exit)) { | |
551 | for (i = 0; i < s->nb_streams; i++) { | |
552 | OutputStream *os = &c->streams[i]; | |
553 | int j; | |
554 | int remove = os->nb_fragments - c->window_size - c->extra_window_size - c->lookahead_count; | |
555 | if (final && c->remove_at_exit) | |
556 | remove = os->nb_fragments; | |
557 | if (remove > 0) { | |
558 | for (j = 0; j < remove; j++) { | |
559 | unlink(os->fragments[j]->file); | |
560 | unlink(os->fragments[j]->infofile); | |
561 | av_free(os->fragments[j]); | |
562 | } | |
563 | os->nb_fragments -= remove; | |
564 | memmove(os->fragments, os->fragments + remove, os->nb_fragments * sizeof(*os->fragments)); | |
565 | } | |
566 | if (final && c->remove_at_exit) | |
567 | rmdir(os->dirname); | |
568 | } | |
569 | } | |
570 | ||
571 | if (ret >= 0) | |
572 | ret = write_manifest(s, final); | |
573 | return ret; | |
574 | } | |
575 | ||
576 | static int ism_write_packet(AVFormatContext *s, AVPacket *pkt) | |
577 | { | |
578 | SmoothStreamingContext *c = s->priv_data; | |
579 | AVStream *st = s->streams[pkt->stream_index]; | |
580 | OutputStream *os = &c->streams[pkt->stream_index]; | |
f6fa7814 | 581 | int64_t end_dts = (c->nb_fragments + 1) * (int64_t) c->min_frag_duration; |
2ba45a60 DM |
582 | int ret; |
583 | ||
584 | if (st->first_dts == AV_NOPTS_VALUE) | |
585 | st->first_dts = pkt->dts; | |
586 | ||
587 | if ((!c->has_video || st->codec->codec_type == AVMEDIA_TYPE_VIDEO) && | |
588 | av_compare_ts(pkt->dts - st->first_dts, st->time_base, | |
589 | end_dts, AV_TIME_BASE_Q) >= 0 && | |
590 | pkt->flags & AV_PKT_FLAG_KEY && os->packets_written) { | |
591 | ||
592 | if ((ret = ism_flush(s, 0)) < 0) | |
593 | return ret; | |
594 | c->nb_fragments++; | |
595 | } | |
596 | ||
597 | os->packets_written++; | |
598 | return ff_write_chained(os->ctx, 0, pkt, s, 0); | |
599 | } | |
600 | ||
601 | static int ism_write_trailer(AVFormatContext *s) | |
602 | { | |
603 | SmoothStreamingContext *c = s->priv_data; | |
604 | ism_flush(s, 1); | |
605 | ||
606 | if (c->remove_at_exit) { | |
607 | char filename[1024]; | |
608 | snprintf(filename, sizeof(filename), "%s/Manifest", s->filename); | |
609 | unlink(filename); | |
610 | rmdir(s->filename); | |
611 | } | |
612 | ||
613 | ism_free(s); | |
614 | return 0; | |
615 | } | |
616 | ||
617 | #define OFFSET(x) offsetof(SmoothStreamingContext, x) | |
618 | #define E AV_OPT_FLAG_ENCODING_PARAM | |
619 | static const AVOption options[] = { | |
620 | { "window_size", "number of fragments kept in the manifest", OFFSET(window_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, E }, | |
621 | { "extra_window_size", "number of fragments kept outside of the manifest before removing from disk", OFFSET(extra_window_size), AV_OPT_TYPE_INT, { .i64 = 5 }, 0, INT_MAX, E }, | |
622 | { "lookahead_count", "number of lookahead fragments", OFFSET(lookahead_count), AV_OPT_TYPE_INT, { .i64 = 2 }, 0, INT_MAX, E }, | |
623 | { "min_frag_duration", "minimum fragment duration (in microseconds)", OFFSET(min_frag_duration), AV_OPT_TYPE_INT64, { .i64 = 5000000 }, 0, INT_MAX, E }, | |
624 | { "remove_at_exit", "remove all fragments when finished", OFFSET(remove_at_exit), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E }, | |
625 | { NULL }, | |
626 | }; | |
627 | ||
628 | static const AVClass ism_class = { | |
629 | .class_name = "smooth streaming muxer", | |
630 | .item_name = av_default_item_name, | |
631 | .option = options, | |
632 | .version = LIBAVUTIL_VERSION_INT, | |
633 | }; | |
634 | ||
635 | ||
636 | AVOutputFormat ff_smoothstreaming_muxer = { | |
637 | .name = "smoothstreaming", | |
638 | .long_name = NULL_IF_CONFIG_SMALL("Smooth Streaming Muxer"), | |
639 | .priv_data_size = sizeof(SmoothStreamingContext), | |
640 | .audio_codec = AV_CODEC_ID_AAC, | |
641 | .video_codec = AV_CODEC_ID_H264, | |
642 | .flags = AVFMT_GLOBALHEADER | AVFMT_NOFILE, | |
643 | .write_header = ism_write_header, | |
644 | .write_packet = ism_write_packet, | |
645 | .write_trailer = ism_write_trailer, | |
646 | .codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 }, | |
647 | .priv_class = &ism_class, | |
648 | }; |