Line |
Branch |
Exec |
Source |
1 |
|
|
/* |
2 |
|
|
* Live HDS fragmenter |
3 |
|
|
* Copyright (c) 2013 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 "avformat.h" |
28 |
|
|
#include "internal.h" |
29 |
|
|
#include "mux.h" |
30 |
|
|
#include "os_support.h" |
31 |
|
|
|
32 |
|
|
#include "libavutil/avstring.h" |
33 |
|
|
#include "libavutil/base64.h" |
34 |
|
|
#include "libavutil/intreadwrite.h" |
35 |
|
|
#include "libavutil/mathematics.h" |
36 |
|
|
#include "libavutil/mem.h" |
37 |
|
|
#include "libavutil/opt.h" |
38 |
|
|
|
39 |
|
|
typedef struct Fragment { |
40 |
|
|
char file[1024]; |
41 |
|
|
int64_t start_time, duration; |
42 |
|
|
int n; |
43 |
|
|
} Fragment; |
44 |
|
|
|
45 |
|
|
typedef struct OutputStream { |
46 |
|
|
int bitrate; |
47 |
|
|
int first_stream; |
48 |
|
|
AVFormatContext *ctx; |
49 |
|
|
int ctx_inited; |
50 |
|
|
uint8_t iobuf[32768]; |
51 |
|
|
char temp_filename[1024]; |
52 |
|
|
int64_t frag_start_ts, last_ts; |
53 |
|
|
AVIOContext *out; |
54 |
|
|
int packets_written; |
55 |
|
|
int nb_fragments, fragments_size, fragment_index; |
56 |
|
|
Fragment **fragments; |
57 |
|
|
|
58 |
|
|
int has_audio, has_video; |
59 |
|
|
|
60 |
|
|
uint8_t *metadata; |
61 |
|
|
int metadata_size; |
62 |
|
|
|
63 |
|
|
uint8_t *extra_packets[2]; |
64 |
|
|
int extra_packet_sizes[2]; |
65 |
|
|
int nb_extra_packets; |
66 |
|
|
} OutputStream; |
67 |
|
|
|
68 |
|
|
typedef struct HDSContext { |
69 |
|
|
const AVClass *class; /* Class for private options. */ |
70 |
|
|
int window_size; |
71 |
|
|
int extra_window_size; |
72 |
|
|
int min_frag_duration; |
73 |
|
|
int remove_at_exit; |
74 |
|
|
|
75 |
|
|
OutputStream *streams; |
76 |
|
|
int nb_streams; |
77 |
|
|
} HDSContext; |
78 |
|
|
|
79 |
|
✗ |
static int parse_header(OutputStream *os, const uint8_t *buf, int buf_size) |
80 |
|
|
{ |
81 |
|
✗ |
if (buf_size < 13) |
82 |
|
✗ |
return AVERROR_INVALIDDATA; |
83 |
|
✗ |
if (memcmp(buf, "FLV", 3)) |
84 |
|
✗ |
return AVERROR_INVALIDDATA; |
85 |
|
✗ |
buf += 13; |
86 |
|
✗ |
buf_size -= 13; |
87 |
|
✗ |
while (buf_size >= 11 + 4) { |
88 |
|
✗ |
int type = buf[0]; |
89 |
|
✗ |
int size = AV_RB24(&buf[1]) + 11 + 4; |
90 |
|
✗ |
if (size > buf_size) |
91 |
|
✗ |
return AVERROR_INVALIDDATA; |
92 |
|
✗ |
if (type == 8 || type == 9) { |
93 |
|
✗ |
if (os->nb_extra_packets >= FF_ARRAY_ELEMS(os->extra_packets)) |
94 |
|
✗ |
return AVERROR_INVALIDDATA; |
95 |
|
✗ |
os->extra_packet_sizes[os->nb_extra_packets] = size; |
96 |
|
✗ |
os->extra_packets[os->nb_extra_packets] = av_memdup(buf, size); |
97 |
|
✗ |
if (!os->extra_packets[os->nb_extra_packets]) |
98 |
|
✗ |
return AVERROR(ENOMEM); |
99 |
|
✗ |
os->nb_extra_packets++; |
100 |
|
✗ |
} else if (type == 0x12) { |
101 |
|
✗ |
if (os->metadata) |
102 |
|
✗ |
return AVERROR_INVALIDDATA; |
103 |
|
✗ |
os->metadata_size = size - 11 - 4; |
104 |
|
✗ |
os->metadata = av_memdup(buf + 11, os->metadata_size); |
105 |
|
✗ |
if (!os->metadata) |
106 |
|
✗ |
return AVERROR(ENOMEM); |
107 |
|
|
} |
108 |
|
✗ |
buf += size; |
109 |
|
✗ |
buf_size -= size; |
110 |
|
|
} |
111 |
|
✗ |
if (!os->metadata) |
112 |
|
✗ |
return AVERROR_INVALIDDATA; |
113 |
|
✗ |
return 0; |
114 |
|
|
} |
115 |
|
|
|
116 |
|
✗ |
static int hds_write(void *opaque, const uint8_t *buf, int buf_size) |
117 |
|
|
{ |
118 |
|
✗ |
OutputStream *os = opaque; |
119 |
|
✗ |
if (os->out) { |
120 |
|
✗ |
avio_write(os->out, buf, buf_size); |
121 |
|
|
} else { |
122 |
|
✗ |
if (!os->metadata_size) { |
123 |
|
|
int ret; |
124 |
|
|
// Assuming the IO buffer is large enough to fit the |
125 |
|
|
// FLV header and all metadata and extradata packets |
126 |
|
✗ |
if ((ret = parse_header(os, buf, buf_size)) < 0) |
127 |
|
✗ |
return ret; |
128 |
|
|
} |
129 |
|
|
} |
130 |
|
✗ |
return buf_size; |
131 |
|
|
} |
132 |
|
|
|
133 |
|
✗ |
static void hds_free(AVFormatContext *s) |
134 |
|
|
{ |
135 |
|
✗ |
HDSContext *c = s->priv_data; |
136 |
|
|
int i, j; |
137 |
|
✗ |
if (!c->streams) |
138 |
|
✗ |
return; |
139 |
|
✗ |
for (i = 0; i < s->nb_streams; i++) { |
140 |
|
✗ |
OutputStream *os = &c->streams[i]; |
141 |
|
✗ |
if (os->out) |
142 |
|
✗ |
ff_format_io_close(s, &os->out); |
143 |
|
✗ |
if (os->ctx && os->ctx_inited) |
144 |
|
✗ |
av_write_trailer(os->ctx); |
145 |
|
✗ |
if (os->ctx) |
146 |
|
✗ |
avio_context_free(&os->ctx->pb); |
147 |
|
✗ |
avformat_free_context(os->ctx); |
148 |
|
✗ |
av_freep(&os->metadata); |
149 |
|
✗ |
for (j = 0; j < os->nb_extra_packets; j++) |
150 |
|
✗ |
av_freep(&os->extra_packets[j]); |
151 |
|
✗ |
for (j = 0; j < os->nb_fragments; j++) |
152 |
|
✗ |
av_freep(&os->fragments[j]); |
153 |
|
✗ |
av_freep(&os->fragments); |
154 |
|
|
} |
155 |
|
✗ |
av_freep(&c->streams); |
156 |
|
|
} |
157 |
|
|
|
158 |
|
✗ |
static int write_manifest(AVFormatContext *s, int final) |
159 |
|
|
{ |
160 |
|
✗ |
HDSContext *c = s->priv_data; |
161 |
|
|
AVIOContext *out; |
162 |
|
|
char filename[1024], temp_filename[1024]; |
163 |
|
|
int ret, i; |
164 |
|
✗ |
double duration = 0; |
165 |
|
|
|
166 |
|
✗ |
if (c->nb_streams > 0) |
167 |
|
✗ |
duration = c->streams[0].last_ts * av_q2d(s->streams[0]->time_base); |
168 |
|
|
|
169 |
|
✗ |
snprintf(filename, sizeof(filename), "%s/index.f4m", s->url); |
170 |
|
✗ |
snprintf(temp_filename, sizeof(temp_filename), "%s/index.f4m.tmp", s->url); |
171 |
|
✗ |
ret = s->io_open(s, &out, temp_filename, AVIO_FLAG_WRITE, NULL); |
172 |
|
✗ |
if (ret < 0) { |
173 |
|
✗ |
av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename); |
174 |
|
✗ |
return ret; |
175 |
|
|
} |
176 |
|
✗ |
avio_printf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); |
177 |
|
✗ |
avio_printf(out, "<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n"); |
178 |
|
✗ |
avio_printf(out, "\t<id>%s</id>\n", av_basename(s->url)); |
179 |
|
✗ |
avio_printf(out, "\t<streamType>%s</streamType>\n", |
180 |
|
|
final ? "recorded" : "live"); |
181 |
|
✗ |
avio_printf(out, "\t<deliveryType>streaming</deliveryType>\n"); |
182 |
|
✗ |
if (final) |
183 |
|
✗ |
avio_printf(out, "\t<duration>%f</duration>\n", duration); |
184 |
|
✗ |
for (i = 0; i < c->nb_streams; i++) { |
185 |
|
✗ |
OutputStream *os = &c->streams[i]; |
186 |
|
✗ |
int b64_size = AV_BASE64_SIZE(os->metadata_size); |
187 |
|
✗ |
char *base64 = av_malloc(b64_size); |
188 |
|
✗ |
if (!base64) { |
189 |
|
✗ |
ff_format_io_close(s, &out); |
190 |
|
✗ |
return AVERROR(ENOMEM); |
191 |
|
|
} |
192 |
|
✗ |
av_base64_encode(base64, b64_size, os->metadata, os->metadata_size); |
193 |
|
|
|
194 |
|
✗ |
avio_printf(out, "\t<bootstrapInfo profile=\"named\" url=\"stream%d.abst\" id=\"bootstrap%d\" />\n", i, i); |
195 |
|
✗ |
avio_printf(out, "\t<media bitrate=\"%d\" url=\"stream%d\" bootstrapInfoId=\"bootstrap%d\">\n", os->bitrate/1000, i, i); |
196 |
|
✗ |
avio_printf(out, "\t\t<metadata>%s</metadata>\n", base64); |
197 |
|
✗ |
avio_printf(out, "\t</media>\n"); |
198 |
|
✗ |
av_free(base64); |
199 |
|
|
} |
200 |
|
✗ |
avio_printf(out, "</manifest>\n"); |
201 |
|
✗ |
avio_flush(out); |
202 |
|
✗ |
ff_format_io_close(s, &out); |
203 |
|
✗ |
return ff_rename(temp_filename, filename, s); |
204 |
|
|
} |
205 |
|
|
|
206 |
|
✗ |
static void update_size(AVIOContext *out, int64_t pos) |
207 |
|
|
{ |
208 |
|
✗ |
int64_t end = avio_tell(out); |
209 |
|
✗ |
avio_seek(out, pos, SEEK_SET); |
210 |
|
✗ |
avio_wb32(out, end - pos); |
211 |
|
✗ |
avio_seek(out, end, SEEK_SET); |
212 |
|
✗ |
} |
213 |
|
|
|
214 |
|
|
/* Note, the .abst files need to be served with the "binary/octet" |
215 |
|
|
* mime type, otherwise at least the OSMF player can easily fail |
216 |
|
|
* with "stream not found" when polling for the next fragment. */ |
217 |
|
✗ |
static int write_abst(AVFormatContext *s, OutputStream *os, int final) |
218 |
|
|
{ |
219 |
|
✗ |
HDSContext *c = s->priv_data; |
220 |
|
|
AVIOContext *out; |
221 |
|
|
char filename[1024], temp_filename[1024]; |
222 |
|
|
int i, ret; |
223 |
|
|
int64_t asrt_pos, afrt_pos; |
224 |
|
✗ |
int start = 0, fragments; |
225 |
|
✗ |
int index = s->streams[os->first_stream]->id; |
226 |
|
✗ |
int64_t cur_media_time = 0; |
227 |
|
✗ |
if (c->window_size) |
228 |
|
✗ |
start = FFMAX(os->nb_fragments - c->window_size, 0); |
229 |
|
✗ |
fragments = os->nb_fragments - start; |
230 |
|
✗ |
if (final) |
231 |
|
✗ |
cur_media_time = os->last_ts; |
232 |
|
✗ |
else if (os->nb_fragments) |
233 |
|
✗ |
cur_media_time = os->fragments[os->nb_fragments - 1]->start_time; |
234 |
|
|
|
235 |
|
✗ |
snprintf(filename, sizeof(filename), |
236 |
|
|
"%s/stream%d.abst", s->url, index); |
237 |
|
✗ |
snprintf(temp_filename, sizeof(temp_filename), |
238 |
|
|
"%s/stream%d.abst.tmp", s->url, index); |
239 |
|
✗ |
ret = s->io_open(s, &out, temp_filename, AVIO_FLAG_WRITE, NULL); |
240 |
|
✗ |
if (ret < 0) { |
241 |
|
✗ |
av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename); |
242 |
|
✗ |
return ret; |
243 |
|
|
} |
244 |
|
✗ |
avio_wb32(out, 0); // abst size |
245 |
|
✗ |
avio_wl32(out, MKTAG('a','b','s','t')); |
246 |
|
✗ |
avio_wb32(out, 0); // version + flags |
247 |
|
✗ |
avio_wb32(out, os->fragment_index - 1); // BootstrapinfoVersion |
248 |
|
✗ |
avio_w8(out, final ? 0 : 0x20); // profile, live, update |
249 |
|
✗ |
avio_wb32(out, 1000); // timescale |
250 |
|
✗ |
avio_wb64(out, cur_media_time); |
251 |
|
✗ |
avio_wb64(out, 0); // SmpteTimeCodeOffset |
252 |
|
✗ |
avio_w8(out, 0); // MovieIdentifer (null string) |
253 |
|
✗ |
avio_w8(out, 0); // ServerEntryCount |
254 |
|
✗ |
avio_w8(out, 0); // QualityEntryCount |
255 |
|
✗ |
avio_w8(out, 0); // DrmData (null string) |
256 |
|
✗ |
avio_w8(out, 0); // MetaData (null string) |
257 |
|
✗ |
avio_w8(out, 1); // SegmentRunTableCount |
258 |
|
✗ |
asrt_pos = avio_tell(out); |
259 |
|
✗ |
avio_wb32(out, 0); // asrt size |
260 |
|
✗ |
avio_wl32(out, MKTAG('a','s','r','t')); |
261 |
|
✗ |
avio_wb32(out, 0); // version + flags |
262 |
|
✗ |
avio_w8(out, 0); // QualityEntryCount |
263 |
|
✗ |
avio_wb32(out, 1); // SegmentRunEntryCount |
264 |
|
✗ |
avio_wb32(out, 1); // FirstSegment |
265 |
|
✗ |
avio_wb32(out, final ? (os->fragment_index - 1) : 0xffffffff); // FragmentsPerSegment |
266 |
|
✗ |
update_size(out, asrt_pos); |
267 |
|
✗ |
avio_w8(out, 1); // FragmentRunTableCount |
268 |
|
✗ |
afrt_pos = avio_tell(out); |
269 |
|
✗ |
avio_wb32(out, 0); // afrt size |
270 |
|
✗ |
avio_wl32(out, MKTAG('a','f','r','t')); |
271 |
|
✗ |
avio_wb32(out, 0); // version + flags |
272 |
|
✗ |
avio_wb32(out, 1000); // timescale |
273 |
|
✗ |
avio_w8(out, 0); // QualityEntryCount |
274 |
|
✗ |
avio_wb32(out, fragments); // FragmentRunEntryCount |
275 |
|
✗ |
for (i = start; i < os->nb_fragments; i++) { |
276 |
|
✗ |
avio_wb32(out, os->fragments[i]->n); |
277 |
|
✗ |
avio_wb64(out, os->fragments[i]->start_time); |
278 |
|
✗ |
avio_wb32(out, os->fragments[i]->duration); |
279 |
|
|
} |
280 |
|
✗ |
update_size(out, afrt_pos); |
281 |
|
✗ |
update_size(out, 0); |
282 |
|
✗ |
ff_format_io_close(s, &out); |
283 |
|
✗ |
return ff_rename(temp_filename, filename, s); |
284 |
|
|
} |
285 |
|
|
|
286 |
|
✗ |
static int init_file(AVFormatContext *s, OutputStream *os, int64_t start_ts) |
287 |
|
|
{ |
288 |
|
|
int ret, i; |
289 |
|
✗ |
ret = s->io_open(s, &os->out, os->temp_filename, AVIO_FLAG_WRITE, NULL); |
290 |
|
✗ |
if (ret < 0) |
291 |
|
✗ |
return ret; |
292 |
|
✗ |
avio_wb32(os->out, 0); |
293 |
|
✗ |
avio_wl32(os->out, MKTAG('m','d','a','t')); |
294 |
|
✗ |
for (i = 0; i < os->nb_extra_packets; i++) { |
295 |
|
✗ |
AV_WB24(os->extra_packets[i] + 4, start_ts); |
296 |
|
✗ |
os->extra_packets[i][7] = (start_ts >> 24) & 0x7f; |
297 |
|
✗ |
avio_write(os->out, os->extra_packets[i], os->extra_packet_sizes[i]); |
298 |
|
|
} |
299 |
|
✗ |
return 0; |
300 |
|
|
} |
301 |
|
|
|
302 |
|
✗ |
static void close_file(AVFormatContext *s, OutputStream *os) |
303 |
|
|
{ |
304 |
|
✗ |
int64_t pos = avio_tell(os->out); |
305 |
|
✗ |
avio_seek(os->out, 0, SEEK_SET); |
306 |
|
✗ |
avio_wb32(os->out, pos); |
307 |
|
✗ |
avio_flush(os->out); |
308 |
|
✗ |
ff_format_io_close(s, &os->out); |
309 |
|
✗ |
} |
310 |
|
|
|
311 |
|
✗ |
static int hds_write_header(AVFormatContext *s) |
312 |
|
|
{ |
313 |
|
✗ |
HDSContext *c = s->priv_data; |
314 |
|
|
const AVOutputFormat *oformat; |
315 |
|
✗ |
int ret = 0, i; |
316 |
|
|
|
317 |
|
✗ |
if (mkdir(s->url, 0777) == -1 && errno != EEXIST) { |
318 |
|
✗ |
av_log(s, AV_LOG_ERROR , "Failed to create directory %s\n", s->url); |
319 |
|
✗ |
return AVERROR(errno); |
320 |
|
|
} |
321 |
|
|
|
322 |
|
✗ |
oformat = av_guess_format("flv", NULL, NULL); |
323 |
|
✗ |
if (!oformat) { |
324 |
|
✗ |
return AVERROR_MUXER_NOT_FOUND; |
325 |
|
|
} |
326 |
|
|
|
327 |
|
✗ |
c->streams = av_calloc(s->nb_streams, sizeof(*c->streams)); |
328 |
|
✗ |
if (!c->streams) { |
329 |
|
✗ |
return AVERROR(ENOMEM); |
330 |
|
|
} |
331 |
|
|
|
332 |
|
✗ |
for (i = 0; i < s->nb_streams; i++) { |
333 |
|
✗ |
OutputStream *os = &c->streams[c->nb_streams]; |
334 |
|
|
AVFormatContext *ctx; |
335 |
|
✗ |
AVStream *st = s->streams[i]; |
336 |
|
|
|
337 |
|
✗ |
if (!st->codecpar->bit_rate) { |
338 |
|
✗ |
av_log(s, AV_LOG_ERROR, "No bit rate set for stream %d\n", i); |
339 |
|
✗ |
return AVERROR(EINVAL); |
340 |
|
|
} |
341 |
|
✗ |
if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { |
342 |
|
✗ |
if (os->has_video) { |
343 |
|
✗ |
c->nb_streams++; |
344 |
|
✗ |
os++; |
345 |
|
|
} |
346 |
|
✗ |
os->has_video = 1; |
347 |
|
✗ |
} else if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { |
348 |
|
✗ |
if (os->has_audio) { |
349 |
|
✗ |
c->nb_streams++; |
350 |
|
✗ |
os++; |
351 |
|
|
} |
352 |
|
✗ |
os->has_audio = 1; |
353 |
|
|
} else { |
354 |
|
✗ |
av_log(s, AV_LOG_ERROR, "Unsupported stream type in stream %d\n", i); |
355 |
|
✗ |
return AVERROR(EINVAL); |
356 |
|
|
} |
357 |
|
✗ |
os->bitrate += s->streams[i]->codecpar->bit_rate; |
358 |
|
|
|
359 |
|
✗ |
if (!os->ctx) { |
360 |
|
✗ |
os->first_stream = i; |
361 |
|
✗ |
ctx = avformat_alloc_context(); |
362 |
|
✗ |
if (!ctx) { |
363 |
|
✗ |
return AVERROR(ENOMEM); |
364 |
|
|
} |
365 |
|
✗ |
os->ctx = ctx; |
366 |
|
✗ |
ctx->oformat = oformat; |
367 |
|
✗ |
ctx->interrupt_callback = s->interrupt_callback; |
368 |
|
✗ |
ctx->flags = s->flags; |
369 |
|
|
|
370 |
|
✗ |
ctx->pb = avio_alloc_context(os->iobuf, sizeof(os->iobuf), |
371 |
|
|
1, os, |
372 |
|
|
NULL, hds_write, NULL); |
373 |
|
✗ |
if (!ctx->pb) { |
374 |
|
✗ |
return AVERROR(ENOMEM); |
375 |
|
|
} |
376 |
|
|
} else { |
377 |
|
✗ |
ctx = os->ctx; |
378 |
|
|
} |
379 |
|
✗ |
s->streams[i]->id = c->nb_streams; |
380 |
|
|
|
381 |
|
✗ |
if (!(st = avformat_new_stream(ctx, NULL))) { |
382 |
|
✗ |
return AVERROR(ENOMEM); |
383 |
|
|
} |
384 |
|
✗ |
avcodec_parameters_copy(st->codecpar, s->streams[i]->codecpar); |
385 |
|
✗ |
st->codecpar->codec_tag = 0; |
386 |
|
✗ |
st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; |
387 |
|
✗ |
st->time_base = s->streams[i]->time_base; |
388 |
|
|
} |
389 |
|
✗ |
if (c->streams[c->nb_streams].ctx) |
390 |
|
✗ |
c->nb_streams++; |
391 |
|
|
|
392 |
|
✗ |
for (i = 0; i < c->nb_streams; i++) { |
393 |
|
✗ |
OutputStream *os = &c->streams[i]; |
394 |
|
|
int j; |
395 |
|
✗ |
if ((ret = avformat_write_header(os->ctx, NULL)) < 0) { |
396 |
|
✗ |
return ret; |
397 |
|
|
} |
398 |
|
✗ |
os->ctx_inited = 1; |
399 |
|
✗ |
avio_flush(os->ctx->pb); |
400 |
|
✗ |
for (j = 0; j < os->ctx->nb_streams; j++) |
401 |
|
✗ |
s->streams[os->first_stream + j]->time_base = os->ctx->streams[j]->time_base; |
402 |
|
|
|
403 |
|
✗ |
snprintf(os->temp_filename, sizeof(os->temp_filename), |
404 |
|
|
"%s/stream%d_temp", s->url, i); |
405 |
|
✗ |
ret = init_file(s, os, 0); |
406 |
|
✗ |
if (ret < 0) |
407 |
|
✗ |
return ret; |
408 |
|
|
|
409 |
|
✗ |
if (!os->has_video && c->min_frag_duration <= 0) { |
410 |
|
✗ |
av_log(s, AV_LOG_WARNING, |
411 |
|
|
"No video stream in output stream %d and no min frag duration set\n", i); |
412 |
|
|
} |
413 |
|
✗ |
os->fragment_index = 1; |
414 |
|
✗ |
write_abst(s, os, 0); |
415 |
|
|
} |
416 |
|
✗ |
ret = write_manifest(s, 0); |
417 |
|
|
|
418 |
|
✗ |
return ret; |
419 |
|
|
} |
420 |
|
|
|
421 |
|
✗ |
static int add_fragment(OutputStream *os, const char *file, |
422 |
|
|
int64_t start_time, int64_t duration) |
423 |
|
|
{ |
424 |
|
|
Fragment *frag; |
425 |
|
✗ |
if (duration == 0) |
426 |
|
✗ |
duration = 1; |
427 |
|
✗ |
if (os->nb_fragments >= os->fragments_size) { |
428 |
|
|
int ret; |
429 |
|
✗ |
os->fragments_size = (os->fragments_size + 1) * 2; |
430 |
|
✗ |
if ((ret = av_reallocp_array(&os->fragments, os->fragments_size, |
431 |
|
|
sizeof(*os->fragments))) < 0) { |
432 |
|
✗ |
os->fragments_size = 0; |
433 |
|
✗ |
os->nb_fragments = 0; |
434 |
|
✗ |
return ret; |
435 |
|
|
} |
436 |
|
|
} |
437 |
|
✗ |
frag = av_mallocz(sizeof(*frag)); |
438 |
|
✗ |
if (!frag) |
439 |
|
✗ |
return AVERROR(ENOMEM); |
440 |
|
✗ |
av_strlcpy(frag->file, file, sizeof(frag->file)); |
441 |
|
✗ |
frag->start_time = start_time; |
442 |
|
✗ |
frag->duration = duration; |
443 |
|
✗ |
frag->n = os->fragment_index; |
444 |
|
✗ |
os->fragments[os->nb_fragments++] = frag; |
445 |
|
✗ |
os->fragment_index++; |
446 |
|
✗ |
return 0; |
447 |
|
|
} |
448 |
|
|
|
449 |
|
✗ |
static int hds_flush(AVFormatContext *s, OutputStream *os, int final, |
450 |
|
|
int64_t end_ts) |
451 |
|
|
{ |
452 |
|
✗ |
HDSContext *c = s->priv_data; |
453 |
|
✗ |
int i, ret = 0; |
454 |
|
|
char target_filename[1024]; |
455 |
|
✗ |
int index = s->streams[os->first_stream]->id; |
456 |
|
|
|
457 |
|
✗ |
if (!os->packets_written) |
458 |
|
✗ |
return 0; |
459 |
|
|
|
460 |
|
✗ |
avio_flush(os->ctx->pb); |
461 |
|
✗ |
os->packets_written = 0; |
462 |
|
✗ |
close_file(s, os); |
463 |
|
|
|
464 |
|
✗ |
snprintf(target_filename, sizeof(target_filename), |
465 |
|
|
"%s/stream%dSeg1-Frag%d", s->url, index, os->fragment_index); |
466 |
|
✗ |
ret = ff_rename(os->temp_filename, target_filename, s); |
467 |
|
✗ |
if (ret < 0) |
468 |
|
✗ |
return ret; |
469 |
|
✗ |
add_fragment(os, target_filename, os->frag_start_ts, end_ts - os->frag_start_ts); |
470 |
|
|
|
471 |
|
✗ |
if (!final) { |
472 |
|
✗ |
ret = init_file(s, os, end_ts); |
473 |
|
✗ |
if (ret < 0) |
474 |
|
✗ |
return ret; |
475 |
|
|
} |
476 |
|
|
|
477 |
|
✗ |
if (c->window_size || (final && c->remove_at_exit)) { |
478 |
|
✗ |
int remove = os->nb_fragments - c->window_size - c->extra_window_size; |
479 |
|
✗ |
if (final && c->remove_at_exit) |
480 |
|
✗ |
remove = os->nb_fragments; |
481 |
|
✗ |
if (remove > 0) { |
482 |
|
✗ |
for (i = 0; i < remove; i++) { |
483 |
|
✗ |
unlink(os->fragments[i]->file); |
484 |
|
✗ |
av_freep(&os->fragments[i]); |
485 |
|
|
} |
486 |
|
✗ |
os->nb_fragments -= remove; |
487 |
|
✗ |
memmove(os->fragments, os->fragments + remove, |
488 |
|
✗ |
os->nb_fragments * sizeof(*os->fragments)); |
489 |
|
|
} |
490 |
|
|
} |
491 |
|
|
|
492 |
|
✗ |
if (ret >= 0) |
493 |
|
✗ |
ret = write_abst(s, os, final); |
494 |
|
✗ |
return ret; |
495 |
|
|
} |
496 |
|
|
|
497 |
|
✗ |
static int hds_write_packet(AVFormatContext *s, AVPacket *pkt) |
498 |
|
|
{ |
499 |
|
✗ |
HDSContext *c = s->priv_data; |
500 |
|
✗ |
AVStream *st = s->streams[pkt->stream_index]; |
501 |
|
✗ |
FFStream *const sti = ffstream(st); |
502 |
|
✗ |
OutputStream *os = &c->streams[s->streams[pkt->stream_index]->id]; |
503 |
|
✗ |
int64_t end_dts = os->fragment_index * (int64_t)c->min_frag_duration; |
504 |
|
|
int ret; |
505 |
|
|
|
506 |
|
✗ |
if (sti->first_dts == AV_NOPTS_VALUE) |
507 |
|
✗ |
sti->first_dts = pkt->dts; |
508 |
|
|
|
509 |
|
✗ |
if ((!os->has_video || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) && |
510 |
|
✗ |
av_compare_ts(pkt->dts - sti->first_dts, st->time_base, |
511 |
|
✗ |
end_dts, AV_TIME_BASE_Q) >= 0 && |
512 |
|
✗ |
pkt->flags & AV_PKT_FLAG_KEY && os->packets_written) { |
513 |
|
|
|
514 |
|
✗ |
if ((ret = hds_flush(s, os, 0, pkt->dts)) < 0) |
515 |
|
✗ |
return ret; |
516 |
|
|
} |
517 |
|
|
|
518 |
|
|
// Note, these fragment start timestamps, that represent a whole |
519 |
|
|
// OutputStream, assume all streams in it have the same time base. |
520 |
|
✗ |
if (!os->packets_written) |
521 |
|
✗ |
os->frag_start_ts = pkt->dts; |
522 |
|
✗ |
os->last_ts = pkt->dts; |
523 |
|
|
|
524 |
|
✗ |
os->packets_written++; |
525 |
|
✗ |
return ff_write_chained(os->ctx, pkt->stream_index - os->first_stream, pkt, s, 0); |
526 |
|
|
} |
527 |
|
|
|
528 |
|
✗ |
static int hds_write_trailer(AVFormatContext *s) |
529 |
|
|
{ |
530 |
|
✗ |
HDSContext *c = s->priv_data; |
531 |
|
|
int i; |
532 |
|
|
|
533 |
|
✗ |
for (i = 0; i < c->nb_streams; i++) |
534 |
|
✗ |
hds_flush(s, &c->streams[i], 1, c->streams[i].last_ts); |
535 |
|
✗ |
write_manifest(s, 1); |
536 |
|
|
|
537 |
|
✗ |
if (c->remove_at_exit) { |
538 |
|
|
char filename[1024]; |
539 |
|
✗ |
snprintf(filename, sizeof(filename), "%s/index.f4m", s->url); |
540 |
|
✗ |
unlink(filename); |
541 |
|
✗ |
for (i = 0; i < c->nb_streams; i++) { |
542 |
|
✗ |
snprintf(filename, sizeof(filename), "%s/stream%d.abst", s->url, i); |
543 |
|
✗ |
unlink(filename); |
544 |
|
|
} |
545 |
|
✗ |
rmdir(s->url); |
546 |
|
|
} |
547 |
|
|
|
548 |
|
✗ |
return 0; |
549 |
|
|
} |
550 |
|
|
|
551 |
|
|
#define OFFSET(x) offsetof(HDSContext, x) |
552 |
|
|
#define E AV_OPT_FLAG_ENCODING_PARAM |
553 |
|
|
static const AVOption options[] = { |
554 |
|
|
{ "window_size", "number of fragments kept in the manifest", OFFSET(window_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, E }, |
555 |
|
|
{ "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 }, |
556 |
|
|
{ "min_frag_duration", "minimum fragment duration (in microseconds)", OFFSET(min_frag_duration), AV_OPT_TYPE_INT64, { .i64 = 10000000 }, 0, INT_MAX, E }, |
557 |
|
|
{ "remove_at_exit", "remove all fragments when finished", OFFSET(remove_at_exit), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E }, |
558 |
|
|
{ NULL }, |
559 |
|
|
}; |
560 |
|
|
|
561 |
|
|
static const AVClass hds_class = { |
562 |
|
|
.class_name = "HDS muxer", |
563 |
|
|
.item_name = av_default_item_name, |
564 |
|
|
.option = options, |
565 |
|
|
.version = LIBAVUTIL_VERSION_INT, |
566 |
|
|
}; |
567 |
|
|
|
568 |
|
|
const FFOutputFormat ff_hds_muxer = { |
569 |
|
|
.p.name = "hds", |
570 |
|
|
.p.long_name = NULL_IF_CONFIG_SMALL("HDS Muxer"), |
571 |
|
|
.p.audio_codec = AV_CODEC_ID_AAC, |
572 |
|
|
.p.video_codec = AV_CODEC_ID_H264, |
573 |
|
|
.p.flags = AVFMT_GLOBALHEADER | AVFMT_NOFILE, |
574 |
|
|
.p.priv_class = &hds_class, |
575 |
|
|
.priv_data_size = sizeof(HDSContext), |
576 |
|
|
.write_header = hds_write_header, |
577 |
|
|
.write_packet = hds_write_packet, |
578 |
|
|
.write_trailer = hds_write_trailer, |
579 |
|
|
.deinit = hds_free, |
580 |
|
|
}; |
581 |
|
|
|