Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4349 | Serge | 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 |
||
24 | #if HAVE_UNISTD_H |
||
25 | #include |
||
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 | for (i = 0; i < size; i++) |
||
158 | snprintf(&os->private_str[2*i], 3, "%02x", ptr[i]); |
||
159 | if (ptr != codec->extradata) |
||
160 | av_free(ptr); |
||
161 | } |
||
162 | |||
163 | static void ism_free(AVFormatContext *s) |
||
164 | { |
||
165 | SmoothStreamingContext *c = s->priv_data; |
||
166 | int i, j; |
||
167 | if (!c->streams) |
||
168 | return; |
||
169 | for (i = 0; i < s->nb_streams; i++) { |
||
170 | OutputStream *os = &c->streams[i]; |
||
171 | ffurl_close(os->out); |
||
172 | ffurl_close(os->out2); |
||
173 | ffurl_close(os->tail_out); |
||
174 | os->out = os->out2 = os->tail_out = NULL; |
||
175 | if (os->ctx && os->ctx_inited) |
||
176 | av_write_trailer(os->ctx); |
||
177 | if (os->ctx && os->ctx->pb) |
||
178 | av_free(os->ctx->pb); |
||
179 | if (os->ctx) |
||
180 | avformat_free_context(os->ctx); |
||
181 | av_free(os->private_str); |
||
182 | for (j = 0; j < os->nb_fragments; j++) |
||
183 | av_free(os->fragments[j]); |
||
184 | av_free(os->fragments); |
||
185 | } |
||
186 | av_freep(&c->streams); |
||
187 | } |
||
188 | |||
189 | static void output_chunk_list(OutputStream *os, AVIOContext *out, int final, int skip, int window_size) |
||
190 | { |
||
191 | int removed = 0, i, start = 0; |
||
192 | if (os->nb_fragments <= 0) |
||
193 | return; |
||
194 | if (os->fragments[0]->n > 0) |
||
195 | removed = 1; |
||
196 | if (final) |
||
197 | skip = 0; |
||
198 | if (window_size) |
||
199 | start = FFMAX(os->nb_fragments - skip - window_size, 0); |
||
200 | for (i = start; i < os->nb_fragments - skip; i++) { |
||
201 | Fragment *frag = os->fragments[i]; |
||
202 | if (!final || removed) |
||
203 | avio_printf(out, " |
||
204 | else |
||
205 | avio_printf(out, " |
||
206 | } |
||
207 | } |
||
208 | |||
209 | static int write_manifest(AVFormatContext *s, int final) |
||
210 | { |
||
211 | SmoothStreamingContext *c = s->priv_data; |
||
212 | AVIOContext *out; |
||
213 | char filename[1024], temp_filename[1024]; |
||
214 | int ret, i, video_chunks = 0, audio_chunks = 0, video_streams = 0, audio_streams = 0; |
||
215 | int64_t duration = 0; |
||
216 | |||
217 | snprintf(filename, sizeof(filename), "%s/Manifest", s->filename); |
||
218 | snprintf(temp_filename, sizeof(temp_filename), "%s/Manifest.tmp", s->filename); |
||
219 | ret = avio_open2(&out, temp_filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL); |
||
220 | if (ret < 0) { |
||
221 | av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename); |
||
222 | return ret; |
||
223 | } |
||
224 | avio_printf(out, "\n"); |
||
225 | for (i = 0; i < s->nb_streams; i++) { |
||
226 | OutputStream *os = &c->streams[i]; |
||
227 | if (os->nb_fragments > 0) { |
||
228 | Fragment *last = os->fragments[os->nb_fragments - 1]; |
||
229 | duration = last->start_time + last->duration; |
||
230 | } |
||
231 | if (s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { |
||
232 | video_chunks = os->nb_fragments; |
||
233 | video_streams++; |
||
234 | } else { |
||
235 | audio_chunks = os->nb_fragments; |
||
236 | audio_streams++; |
||
237 | } |
||
238 | } |
||
239 | if (!final) { |
||
240 | duration = 0; |
||
241 | video_chunks = audio_chunks = 0; |
||
242 | } |
||
243 | if (c->window_size) { |
||
244 | video_chunks = FFMIN(video_chunks, c->window_size); |
||
245 | audio_chunks = FFMIN(audio_chunks, c->window_size); |
||
246 | } |
||
247 | avio_printf(out, " |
||
248 | if (!final) |
||
249 | avio_printf(out, " IsLive=\"true\" LookAheadFragmentCount=\"%d\" DVRWindowLength=\"0\"", c->lookahead_count); |
||
250 | avio_printf(out, ">\n"); |
||
251 | if (c->has_video) { |
||
252 | int last = -1, index = 0; |
||
253 | avio_printf(out, " |
||
254 | for (i = 0; i < s->nb_streams; i++) { |
||
255 | OutputStream *os = &c->streams[i]; |
||
256 | if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_VIDEO) |
||
257 | continue; |
||
258 | last = i; |
||
259 | avio_printf(out, " |
||
260 | index++; |
||
261 | } |
||
262 | output_chunk_list(&c->streams[last], out, final, c->lookahead_count, c->window_size); |
||
263 | avio_printf(out, "\n"); |
||
264 | } |
||
265 | if (c->has_audio) { |
||
266 | int last = -1, index = 0; |
||
267 | avio_printf(out, " |
||
268 | for (i = 0; i < s->nb_streams; i++) { |
||
269 | OutputStream *os = &c->streams[i]; |
||
270 | if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO) |
||
271 | continue; |
||
272 | last = i; |
||
273 | avio_printf(out, " |
||
274 | index++; |
||
275 | } |
||
276 | output_chunk_list(&c->streams[last], out, final, c->lookahead_count, c->window_size); |
||
277 | avio_printf(out, "\n"); |
||
278 | } |
||
279 | avio_printf(out, "\n"); |
||
280 | avio_flush(out); |
||
281 | avio_close(out); |
||
282 | rename(temp_filename, filename); |
||
283 | return 0; |
||
284 | } |
||
285 | |||
286 | static int ism_write_header(AVFormatContext *s) |
||
287 | { |
||
288 | SmoothStreamingContext *c = s->priv_data; |
||
289 | int ret = 0, i; |
||
290 | AVOutputFormat *oformat; |
||
291 | |||
292 | if (mkdir(s->filename, 0777) < 0) { |
||
293 | av_log(s, AV_LOG_ERROR, "mkdir failed\n"); |
||
294 | ret = AVERROR(errno); |
||
295 | goto fail; |
||
296 | } |
||
297 | |||
298 | oformat = av_guess_format("ismv", NULL, NULL); |
||
299 | if (!oformat) { |
||
300 | ret = AVERROR_MUXER_NOT_FOUND; |
||
301 | goto fail; |
||
302 | } |
||
303 | |||
304 | c->streams = av_mallocz(sizeof(*c->streams) * s->nb_streams); |
||
305 | if (!c->streams) { |
||
306 | ret = AVERROR(ENOMEM); |
||
307 | goto fail; |
||
308 | } |
||
309 | |||
310 | for (i = 0; i < s->nb_streams; i++) { |
||
311 | OutputStream *os = &c->streams[i]; |
||
312 | AVFormatContext *ctx; |
||
313 | AVStream *st; |
||
314 | AVDictionary *opts = NULL; |
||
315 | char buf[10]; |
||
316 | |||
317 | if (!s->streams[i]->codec->bit_rate) { |
||
318 | av_log(s, AV_LOG_ERROR, "No bit rate set for stream %d\n", i); |
||
319 | ret = AVERROR(EINVAL); |
||
320 | goto fail; |
||
321 | } |
||
322 | snprintf(os->dirname, sizeof(os->dirname), "%s/QualityLevels(%d)", s->filename, s->streams[i]->codec->bit_rate); |
||
323 | if (mkdir(os->dirname, 0777) < 0) { |
||
324 | ret = AVERROR(errno); |
||
325 | av_log(s, AV_LOG_ERROR, "mkdir failed\n"); |
||
326 | goto fail; |
||
327 | } |
||
328 | |||
329 | ctx = avformat_alloc_context(); |
||
330 | if (!ctx) { |
||
331 | ret = AVERROR(ENOMEM); |
||
332 | goto fail; |
||
333 | } |
||
334 | os->ctx = ctx; |
||
335 | ctx->oformat = oformat; |
||
336 | ctx->interrupt_callback = s->interrupt_callback; |
||
337 | |||
338 | if (!(st = avformat_new_stream(ctx, NULL))) { |
||
339 | ret = AVERROR(ENOMEM); |
||
340 | goto fail; |
||
341 | } |
||
342 | avcodec_copy_context(st->codec, s->streams[i]->codec); |
||
343 | st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; |
||
344 | |||
345 | ctx->pb = avio_alloc_context(os->iobuf, sizeof(os->iobuf), AVIO_FLAG_WRITE, os, NULL, ism_write, ism_seek); |
||
346 | if (!ctx->pb) { |
||
347 | ret = AVERROR(ENOMEM); |
||
348 | goto fail; |
||
349 | } |
||
350 | |||
351 | snprintf(buf, sizeof(buf), "%d", c->lookahead_count); |
||
352 | av_dict_set(&opts, "ism_lookahead", buf, 0); |
||
353 | av_dict_set(&opts, "movflags", "frag_custom", 0); |
||
354 | if ((ret = avformat_write_header(ctx, &opts)) < 0) { |
||
355 | goto fail; |
||
356 | } |
||
357 | os->ctx_inited = 1; |
||
358 | avio_flush(ctx->pb); |
||
359 | av_dict_free(&opts); |
||
360 | s->streams[i]->time_base = st->time_base; |
||
361 | if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { |
||
362 | c->has_video = 1; |
||
363 | os->stream_type_tag = "video"; |
||
364 | if (st->codec->codec_id == AV_CODEC_ID_H264) { |
||
365 | os->fourcc = "H264"; |
||
366 | } else if (st->codec->codec_id == AV_CODEC_ID_VC1) { |
||
367 | os->fourcc = "WVC1"; |
||
368 | } else { |
||
369 | av_log(s, AV_LOG_ERROR, "Unsupported video codec\n"); |
||
370 | ret = AVERROR(EINVAL); |
||
371 | goto fail; |
||
372 | } |
||
373 | } else { |
||
374 | c->has_audio = 1; |
||
375 | os->stream_type_tag = "audio"; |
||
376 | if (st->codec->codec_id == AV_CODEC_ID_AAC) { |
||
377 | os->fourcc = "AACL"; |
||
378 | os->audio_tag = 0xff; |
||
379 | } else if (st->codec->codec_id == AV_CODEC_ID_WMAPRO) { |
||
380 | os->fourcc = "WMAP"; |
||
381 | os->audio_tag = 0x0162; |
||
382 | } else { |
||
383 | av_log(s, AV_LOG_ERROR, "Unsupported audio codec\n"); |
||
384 | ret = AVERROR(EINVAL); |
||
385 | goto fail; |
||
386 | } |
||
387 | os->packet_size = st->codec->block_align ? st->codec->block_align : 4; |
||
388 | } |
||
389 | get_private_data(os); |
||
390 | } |
||
391 | |||
392 | if (!c->has_video && c->min_frag_duration <= 0) { |
||
393 | av_log(s, AV_LOG_WARNING, "no video stream and no min frag duration set\n"); |
||
394 | ret = AVERROR(EINVAL); |
||
395 | } |
||
396 | ret = write_manifest(s, 0); |
||
397 | |||
398 | fail: |
||
399 | if (ret) |
||
400 | ism_free(s); |
||
401 | return ret; |
||
402 | } |
||
403 | |||
404 | static int parse_fragment(AVFormatContext *s, const char *filename, int64_t *start_ts, int64_t *duration, int64_t *moof_size, int64_t size) |
||
405 | { |
||
406 | AVIOContext *in; |
||
407 | int ret; |
||
408 | uint32_t len; |
||
409 | if ((ret = avio_open2(&in, filename, AVIO_FLAG_READ, &s->interrupt_callback, NULL)) < 0) |
||
410 | return ret; |
||
411 | ret = AVERROR(EIO); |
||
412 | *moof_size = avio_rb32(in); |
||
413 | if (*moof_size < 8 || *moof_size > size) |
||
414 | goto fail; |
||
415 | if (avio_rl32(in) != MKTAG('m','o','o','f')) |
||
416 | goto fail; |
||
417 | len = avio_rb32(in); |
||
418 | if (len > *moof_size) |
||
419 | goto fail; |
||
420 | if (avio_rl32(in) != MKTAG('m','f','h','d')) |
||
421 | goto fail; |
||
422 | avio_seek(in, len - 8, SEEK_CUR); |
||
423 | avio_rb32(in); /* traf size */ |
||
424 | if (avio_rl32(in) != MKTAG('t','r','a','f')) |
||
425 | goto fail; |
||
426 | while (avio_tell(in) < *moof_size) { |
||
427 | uint32_t len = avio_rb32(in); |
||
428 | uint32_t tag = avio_rl32(in); |
||
429 | int64_t end = avio_tell(in) + len - 8; |
||
430 | if (len < 8 || len >= *moof_size) |
||
431 | goto fail; |
||
432 | if (tag == MKTAG('u','u','i','d')) { |
||
433 | static const uint8_t tfxd[] = { |
||
434 | 0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6, |
||
435 | 0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2 |
||
436 | }; |
||
437 | uint8_t uuid[16]; |
||
438 | avio_read(in, uuid, 16); |
||
439 | if (!memcmp(uuid, tfxd, 16) && len >= 8 + 16 + 4 + 16) { |
||
440 | avio_seek(in, 4, SEEK_CUR); |
||
441 | *start_ts = avio_rb64(in); |
||
442 | *duration = avio_rb64(in); |
||
443 | ret = 0; |
||
444 | break; |
||
445 | } |
||
446 | } |
||
447 | avio_seek(in, end, SEEK_SET); |
||
448 | } |
||
449 | fail: |
||
450 | avio_close(in); |
||
451 | return ret; |
||
452 | } |
||
453 | |||
454 | 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) |
||
455 | { |
||
456 | int err; |
||
457 | Fragment *frag; |
||
458 | if (os->nb_fragments >= os->fragments_size) { |
||
459 | os->fragments_size = (os->fragments_size + 1) * 2; |
||
460 | if ((err = av_reallocp(&os->fragments, sizeof(*os->fragments) * |
||
461 | os->fragments_size)) < 0) { |
||
462 | os->fragments_size = 0; |
||
463 | os->nb_fragments = 0; |
||
464 | return err; |
||
465 | } |
||
466 | } |
||
467 | frag = av_mallocz(sizeof(*frag)); |
||
468 | if (!frag) |
||
469 | return AVERROR(ENOMEM); |
||
470 | av_strlcpy(frag->file, file, sizeof(frag->file)); |
||
471 | av_strlcpy(frag->infofile, infofile, sizeof(frag->infofile)); |
||
472 | frag->start_time = start_time; |
||
473 | frag->duration = duration; |
||
474 | frag->start_pos = start_pos; |
||
475 | frag->size = size; |
||
476 | frag->n = os->fragment_index; |
||
477 | os->fragments[os->nb_fragments++] = frag; |
||
478 | os->fragment_index++; |
||
479 | return 0; |
||
480 | } |
||
481 | |||
482 | static int copy_moof(AVFormatContext *s, const char* infile, const char *outfile, int64_t size) |
||
483 | { |
||
484 | AVIOContext *in, *out; |
||
485 | int ret = 0; |
||
486 | if ((ret = avio_open2(&in, infile, AVIO_FLAG_READ, &s->interrupt_callback, NULL)) < 0) |
||
487 | return ret; |
||
488 | if ((ret = avio_open2(&out, outfile, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL)) < 0) { |
||
489 | avio_close(in); |
||
490 | return ret; |
||
491 | } |
||
492 | while (size > 0) { |
||
493 | uint8_t buf[8192]; |
||
494 | int n = FFMIN(size, sizeof(buf)); |
||
495 | n = avio_read(in, buf, n); |
||
496 | if (n <= 0) { |
||
497 | ret = AVERROR(EIO); |
||
498 | break; |
||
499 | } |
||
500 | avio_write(out, buf, n); |
||
501 | size -= n; |
||
502 | } |
||
503 | avio_flush(out); |
||
504 | avio_close(out); |
||
505 | avio_close(in); |
||
506 | return ret; |
||
507 | } |
||
508 | |||
509 | static int ism_flush(AVFormatContext *s, int final) |
||
510 | { |
||
511 | SmoothStreamingContext *c = s->priv_data; |
||
512 | int i, ret = 0; |
||
513 | |||
514 | for (i = 0; i < s->nb_streams; i++) { |
||
515 | OutputStream *os = &c->streams[i]; |
||
516 | char filename[1024], target_filename[1024], header_filename[1024]; |
||
517 | int64_t start_pos = os->tail_pos, size; |
||
518 | int64_t start_ts, duration, moof_size; |
||
519 | if (!os->packets_written) |
||
520 | continue; |
||
521 | |||
522 | snprintf(filename, sizeof(filename), "%s/temp", os->dirname); |
||
523 | ret = ffurl_open(&os->out, filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL); |
||
524 | if (ret < 0) |
||
525 | break; |
||
526 | os->cur_start_pos = os->tail_pos; |
||
527 | av_write_frame(os->ctx, NULL); |
||
528 | avio_flush(os->ctx->pb); |
||
529 | os->packets_written = 0; |
||
530 | if (!os->out || os->tail_out) |
||
531 | return AVERROR(EIO); |
||
532 | |||
533 | ffurl_close(os->out); |
||
534 | os->out = NULL; |
||
535 | size = os->tail_pos - start_pos; |
||
536 | if ((ret = parse_fragment(s, filename, &start_ts, &duration, &moof_size, size)) < 0) |
||
537 | break; |
||
538 | snprintf(header_filename, sizeof(header_filename), "%s/FragmentInfo(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts); |
||
539 | snprintf(target_filename, sizeof(target_filename), "%s/Fragments(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts); |
||
540 | copy_moof(s, filename, header_filename, moof_size); |
||
541 | rename(filename, target_filename); |
||
542 | add_fragment(os, target_filename, header_filename, start_ts, duration, start_pos, size); |
||
543 | } |
||
544 | |||
545 | if (c->window_size || (final && c->remove_at_exit)) { |
||
546 | for (i = 0; i < s->nb_streams; i++) { |
||
547 | OutputStream *os = &c->streams[i]; |
||
548 | int j; |
||
549 | int remove = os->nb_fragments - c->window_size - c->extra_window_size - c->lookahead_count; |
||
550 | if (final && c->remove_at_exit) |
||
551 | remove = os->nb_fragments; |
||
552 | if (remove > 0) { |
||
553 | for (j = 0; j < remove; j++) { |
||
554 | unlink(os->fragments[j]->file); |
||
555 | unlink(os->fragments[j]->infofile); |
||
556 | av_free(os->fragments[j]); |
||
557 | } |
||
558 | os->nb_fragments -= remove; |
||
559 | memmove(os->fragments, os->fragments + remove, os->nb_fragments * sizeof(*os->fragments)); |
||
560 | } |
||
561 | if (final && c->remove_at_exit) |
||
562 | rmdir(os->dirname); |
||
563 | } |
||
564 | } |
||
565 | |||
566 | if (ret >= 0) |
||
567 | ret = write_manifest(s, final); |
||
568 | return ret; |
||
569 | } |
||
570 | |||
571 | static int ism_write_packet(AVFormatContext *s, AVPacket *pkt) |
||
572 | { |
||
573 | SmoothStreamingContext *c = s->priv_data; |
||
574 | AVStream *st = s->streams[pkt->stream_index]; |
||
575 | OutputStream *os = &c->streams[pkt->stream_index]; |
||
576 | int64_t end_dts = (c->nb_fragments + 1LL) * c->min_frag_duration; |
||
577 | int ret; |
||
578 | |||
579 | if (st->first_dts == AV_NOPTS_VALUE) |
||
580 | st->first_dts = pkt->dts; |
||
581 | |||
582 | if ((!c->has_video || st->codec->codec_type == AVMEDIA_TYPE_VIDEO) && |
||
583 | av_compare_ts(pkt->dts - st->first_dts, st->time_base, |
||
584 | end_dts, AV_TIME_BASE_Q) >= 0 && |
||
585 | pkt->flags & AV_PKT_FLAG_KEY && os->packets_written) { |
||
586 | |||
587 | if ((ret = ism_flush(s, 0)) < 0) |
||
588 | return ret; |
||
589 | c->nb_fragments++; |
||
590 | } |
||
591 | |||
592 | os->packets_written++; |
||
593 | return ff_write_chained(os->ctx, 0, pkt, s); |
||
594 | } |
||
595 | |||
596 | static int ism_write_trailer(AVFormatContext *s) |
||
597 | { |
||
598 | SmoothStreamingContext *c = s->priv_data; |
||
599 | ism_flush(s, 1); |
||
600 | |||
601 | if (c->remove_at_exit) { |
||
602 | char filename[1024]; |
||
603 | snprintf(filename, sizeof(filename), "%s/Manifest", s->filename); |
||
604 | unlink(filename); |
||
605 | rmdir(s->filename); |
||
606 | } |
||
607 | |||
608 | ism_free(s); |
||
609 | return 0; |
||
610 | } |
||
611 | |||
612 | #define OFFSET(x) offsetof(SmoothStreamingContext, x) |
||
613 | #define E AV_OPT_FLAG_ENCODING_PARAM |
||
614 | static const AVOption options[] = { |
||
615 | { "window_size", "number of fragments kept in the manifest", OFFSET(window_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, E }, |
||
616 | { "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 }, |
||
617 | { "lookahead_count", "number of lookahead fragments", OFFSET(lookahead_count), AV_OPT_TYPE_INT, { .i64 = 2 }, 0, INT_MAX, E }, |
||
618 | { "min_frag_duration", "minimum fragment duration (in microseconds)", OFFSET(min_frag_duration), AV_OPT_TYPE_INT64, { .i64 = 5000000 }, 0, INT_MAX, E }, |
||
619 | { "remove_at_exit", "remove all fragments when finished", OFFSET(remove_at_exit), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E }, |
||
620 | { NULL }, |
||
621 | }; |
||
622 | |||
623 | static const AVClass ism_class = { |
||
624 | .class_name = "smooth streaming muxer", |
||
625 | .item_name = av_default_item_name, |
||
626 | .option = options, |
||
627 | .version = LIBAVUTIL_VERSION_INT, |
||
628 | }; |
||
629 | |||
630 | |||
631 | AVOutputFormat ff_smoothstreaming_muxer = { |
||
632 | .name = "smoothstreaming", |
||
633 | .long_name = NULL_IF_CONFIG_SMALL("Smooth Streaming Muxer"), |
||
634 | .priv_data_size = sizeof(SmoothStreamingContext), |
||
635 | .audio_codec = AV_CODEC_ID_AAC, |
||
636 | .video_codec = AV_CODEC_ID_H264, |
||
637 | .flags = AVFMT_GLOBALHEADER | AVFMT_NOFILE, |
||
638 | .write_header = ism_write_header, |
||
639 | .write_packet = ism_write_packet, |
||
640 | .write_trailer = ism_write_trailer, |
||
641 | .codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 }, |
||
642 | .priv_class = &ism_class, |
||
643 | };>>>>>>=>>>>>>>>=>>>>>>>>?xml>>>=>>>>>> |