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