Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * Silicon Graphics Movie demuxer | |
3 | * Copyright (c) 2012 Peter Ross | |
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 | /** | |
23 | * @file | |
24 | * Silicon Graphics Movie demuxer | |
25 | */ | |
26 | ||
27 | #include "libavutil/channel_layout.h" | |
28 | #include "libavutil/eval.h" | |
29 | #include "libavutil/intreadwrite.h" | |
30 | #include "libavutil/rational.h" | |
31 | ||
32 | #include "avformat.h" | |
33 | #include "internal.h" | |
34 | ||
35 | typedef struct MvContext { | |
36 | int nb_video_tracks; | |
37 | int nb_audio_tracks; | |
38 | ||
39 | int eof_count; ///< number of streams that have finished | |
40 | int stream_index; ///< current stream index | |
41 | int frame[2]; ///< frame nb for current stream | |
42 | ||
43 | int acompression; ///< compression level for audio stream | |
44 | int aformat; ///< audio format | |
45 | } MvContext; | |
46 | ||
47 | #define AUDIO_FORMAT_SIGNED 401 | |
48 | ||
49 | static int mv_probe(AVProbeData *p) | |
50 | { | |
51 | if (AV_RB32(p->buf) == MKBETAG('M', 'O', 'V', 'I') && | |
52 | AV_RB16(p->buf + 4) < 3) | |
53 | return AVPROBE_SCORE_MAX; | |
54 | return 0; | |
55 | } | |
56 | ||
57 | static char *var_read_string(AVIOContext *pb, int size) | |
58 | { | |
59 | int n; | |
60 | char *str = av_malloc(size + 1); | |
61 | if (!str) | |
62 | return NULL; | |
63 | n = avio_get_str(pb, size, str, size + 1); | |
64 | if (n < size) | |
65 | avio_skip(pb, size - n); | |
66 | return str; | |
67 | } | |
68 | ||
69 | static int var_read_int(AVIOContext *pb, int size) | |
70 | { | |
71 | int v; | |
72 | char *s = var_read_string(pb, size); | |
73 | if (!s) | |
74 | return 0; | |
75 | v = strtol(s, NULL, 10); | |
76 | av_free(s); | |
77 | return v; | |
78 | } | |
79 | ||
80 | static AVRational var_read_float(AVIOContext *pb, int size) | |
81 | { | |
82 | AVRational v; | |
83 | char *s = var_read_string(pb, size); | |
84 | if (!s) | |
85 | return (AVRational) { 0, 0 }; | |
86 | v = av_d2q(av_strtod(s, NULL), INT_MAX); | |
87 | av_free(s); | |
88 | return v; | |
89 | } | |
90 | ||
91 | static void var_read_metadata(AVFormatContext *avctx, const char *tag, int size) | |
92 | { | |
93 | char *value = var_read_string(avctx->pb, size); | |
94 | if (value) | |
95 | av_dict_set(&avctx->metadata, tag, value, AV_DICT_DONT_STRDUP_VAL); | |
96 | } | |
97 | ||
98 | static int set_channels(AVFormatContext *avctx, AVStream *st, int channels) | |
99 | { | |
100 | if (channels <= 0) { | |
101 | av_log(avctx, AV_LOG_ERROR, "Channel count %d invalid.\n", channels); | |
102 | return AVERROR_INVALIDDATA; | |
103 | } | |
104 | st->codec->channels = channels; | |
105 | st->codec->channel_layout = (st->codec->channels == 1) ? AV_CH_LAYOUT_MONO | |
106 | : AV_CH_LAYOUT_STEREO; | |
107 | return 0; | |
108 | } | |
109 | ||
110 | /** | |
111 | * Parse global variable | |
112 | * @return < 0 if unknown | |
113 | */ | |
114 | static int parse_global_var(AVFormatContext *avctx, AVStream *st, | |
115 | const char *name, int size) | |
116 | { | |
117 | MvContext *mv = avctx->priv_data; | |
118 | AVIOContext *pb = avctx->pb; | |
119 | if (!strcmp(name, "__NUM_I_TRACKS")) { | |
120 | mv->nb_video_tracks = var_read_int(pb, size); | |
121 | } else if (!strcmp(name, "__NUM_A_TRACKS")) { | |
122 | mv->nb_audio_tracks = var_read_int(pb, size); | |
123 | } else if (!strcmp(name, "COMMENT") || !strcmp(name, "TITLE")) { | |
124 | var_read_metadata(avctx, name, size); | |
125 | } else if (!strcmp(name, "LOOP_MODE") || !strcmp(name, "NUM_LOOPS") || | |
126 | !strcmp(name, "OPTIMIZED")) { | |
127 | avio_skip(pb, size); // ignore | |
128 | } else | |
129 | return AVERROR_INVALIDDATA; | |
130 | ||
131 | return 0; | |
132 | } | |
133 | ||
134 | /** | |
135 | * Parse audio variable | |
136 | * @return < 0 if unknown | |
137 | */ | |
138 | static int parse_audio_var(AVFormatContext *avctx, AVStream *st, | |
139 | const char *name, int size) | |
140 | { | |
141 | MvContext *mv = avctx->priv_data; | |
142 | AVIOContext *pb = avctx->pb; | |
143 | if (!strcmp(name, "__DIR_COUNT")) { | |
144 | st->nb_frames = var_read_int(pb, size); | |
145 | } else if (!strcmp(name, "AUDIO_FORMAT")) { | |
146 | mv->aformat = var_read_int(pb, size); | |
147 | } else if (!strcmp(name, "COMPRESSION")) { | |
148 | mv->acompression = var_read_int(pb, size); | |
149 | } else if (!strcmp(name, "DEFAULT_VOL")) { | |
150 | var_read_metadata(avctx, name, size); | |
151 | } else if (!strcmp(name, "NUM_CHANNELS")) { | |
152 | return set_channels(avctx, st, var_read_int(pb, size)); | |
153 | } else if (!strcmp(name, "SAMPLE_RATE")) { | |
154 | st->codec->sample_rate = var_read_int(pb, size); | |
155 | avpriv_set_pts_info(st, 33, 1, st->codec->sample_rate); | |
156 | } else if (!strcmp(name, "SAMPLE_WIDTH")) { | |
157 | st->codec->bits_per_coded_sample = var_read_int(pb, size) * 8; | |
158 | } else | |
159 | return AVERROR_INVALIDDATA; | |
160 | ||
161 | return 0; | |
162 | } | |
163 | ||
164 | /** | |
165 | * Parse video variable | |
166 | * @return < 0 if unknown | |
167 | */ | |
168 | static int parse_video_var(AVFormatContext *avctx, AVStream *st, | |
169 | const char *name, int size) | |
170 | { | |
171 | AVIOContext *pb = avctx->pb; | |
172 | if (!strcmp(name, "__DIR_COUNT")) { | |
173 | st->nb_frames = st->duration = var_read_int(pb, size); | |
174 | } else if (!strcmp(name, "COMPRESSION")) { | |
175 | char *str = var_read_string(pb, size); | |
176 | if (!str) | |
177 | return AVERROR_INVALIDDATA; | |
178 | if (!strcmp(str, "1")) { | |
179 | st->codec->codec_id = AV_CODEC_ID_MVC1; | |
180 | } else if (!strcmp(str, "2")) { | |
181 | st->codec->pix_fmt = AV_PIX_FMT_ABGR; | |
182 | st->codec->codec_id = AV_CODEC_ID_RAWVIDEO; | |
183 | } else if (!strcmp(str, "3")) { | |
184 | st->codec->codec_id = AV_CODEC_ID_SGIRLE; | |
185 | } else if (!strcmp(str, "10")) { | |
186 | st->codec->codec_id = AV_CODEC_ID_MJPEG; | |
187 | } else if (!strcmp(str, "MVC2")) { | |
188 | st->codec->codec_id = AV_CODEC_ID_MVC2; | |
189 | } else { | |
190 | avpriv_request_sample(avctx, "Video compression %s", str); | |
191 | } | |
192 | av_free(str); | |
193 | } else if (!strcmp(name, "FPS")) { | |
194 | AVRational fps = var_read_float(pb, size); | |
195 | avpriv_set_pts_info(st, 64, fps.den, fps.num); | |
196 | st->avg_frame_rate = fps; | |
197 | } else if (!strcmp(name, "HEIGHT")) { | |
198 | st->codec->height = var_read_int(pb, size); | |
199 | } else if (!strcmp(name, "PIXEL_ASPECT")) { | |
200 | st->sample_aspect_ratio = var_read_float(pb, size); | |
201 | av_reduce(&st->sample_aspect_ratio.num, &st->sample_aspect_ratio.den, | |
202 | st->sample_aspect_ratio.num, st->sample_aspect_ratio.den, | |
203 | INT_MAX); | |
204 | } else if (!strcmp(name, "WIDTH")) { | |
205 | st->codec->width = var_read_int(pb, size); | |
206 | } else if (!strcmp(name, "ORIENTATION")) { | |
207 | if (var_read_int(pb, size) == 1101) { | |
208 | st->codec->extradata = av_strdup("BottomUp"); | |
209 | st->codec->extradata_size = 9; | |
210 | } | |
211 | } else if (!strcmp(name, "Q_SPATIAL") || !strcmp(name, "Q_TEMPORAL")) { | |
212 | var_read_metadata(avctx, name, size); | |
213 | } else if (!strcmp(name, "INTERLACING") || !strcmp(name, "PACKING")) { | |
214 | avio_skip(pb, size); // ignore | |
215 | } else | |
216 | return AVERROR_INVALIDDATA; | |
217 | ||
218 | return 0; | |
219 | } | |
220 | ||
221 | static void read_table(AVFormatContext *avctx, AVStream *st, | |
222 | int (*parse)(AVFormatContext *avctx, AVStream *st, | |
223 | const char *name, int size)) | |
224 | { | |
225 | int count, i; | |
226 | AVIOContext *pb = avctx->pb; | |
227 | avio_skip(pb, 4); | |
228 | count = avio_rb32(pb); | |
229 | avio_skip(pb, 4); | |
230 | for (i = 0; i < count; i++) { | |
231 | char name[17]; | |
232 | int size; | |
233 | avio_read(pb, name, 16); | |
234 | name[sizeof(name) - 1] = 0; | |
235 | size = avio_rb32(pb); | |
236 | if (parse(avctx, st, name, size) < 0) { | |
237 | avpriv_request_sample(avctx, "Variable %s", name); | |
238 | avio_skip(pb, size); | |
239 | } | |
240 | } | |
241 | } | |
242 | ||
243 | static void read_index(AVIOContext *pb, AVStream *st) | |
244 | { | |
245 | uint64_t timestamp = 0; | |
246 | int i; | |
247 | for (i = 0; i < st->nb_frames; i++) { | |
248 | uint32_t pos = avio_rb32(pb); | |
249 | uint32_t size = avio_rb32(pb); | |
250 | avio_skip(pb, 8); | |
251 | av_add_index_entry(st, pos, timestamp, size, 0, AVINDEX_KEYFRAME); | |
252 | if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { | |
253 | timestamp += size / (st->codec->channels * 2); | |
254 | } else { | |
255 | timestamp++; | |
256 | } | |
257 | } | |
258 | } | |
259 | ||
260 | static int mv_read_header(AVFormatContext *avctx) | |
261 | { | |
262 | MvContext *mv = avctx->priv_data; | |
263 | AVIOContext *pb = avctx->pb; | |
264 | AVStream *ast = NULL, *vst = NULL; //initialization to suppress warning | |
265 | int version, i; | |
266 | ||
267 | avio_skip(pb, 4); | |
268 | ||
269 | version = avio_rb16(pb); | |
270 | if (version == 2) { | |
271 | uint64_t timestamp; | |
272 | int v; | |
273 | avio_skip(pb, 22); | |
274 | ||
275 | /* allocate audio track first to prevent unnecessary seeking | |
276 | * (audio packet always precede video packet for a given frame) */ | |
277 | ast = avformat_new_stream(avctx, NULL); | |
278 | if (!ast) | |
279 | return AVERROR(ENOMEM); | |
280 | ||
281 | vst = avformat_new_stream(avctx, NULL); | |
282 | if (!vst) | |
283 | return AVERROR(ENOMEM); | |
284 | avpriv_set_pts_info(vst, 64, 1, 15); | |
285 | vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; | |
286 | vst->avg_frame_rate = av_inv_q(vst->time_base); | |
287 | vst->nb_frames = avio_rb32(pb); | |
288 | v = avio_rb32(pb); | |
289 | switch (v) { | |
290 | case 1: | |
291 | vst->codec->codec_id = AV_CODEC_ID_MVC1; | |
292 | break; | |
293 | case 2: | |
294 | vst->codec->pix_fmt = AV_PIX_FMT_ARGB; | |
295 | vst->codec->codec_id = AV_CODEC_ID_RAWVIDEO; | |
296 | break; | |
297 | default: | |
298 | avpriv_request_sample(avctx, "Video compression %i", v); | |
299 | break; | |
300 | } | |
301 | vst->codec->codec_tag = 0; | |
302 | vst->codec->width = avio_rb32(pb); | |
303 | vst->codec->height = avio_rb32(pb); | |
304 | avio_skip(pb, 12); | |
305 | ||
306 | ast->codec->codec_type = AVMEDIA_TYPE_AUDIO; | |
307 | ast->nb_frames = vst->nb_frames; | |
308 | ast->codec->sample_rate = avio_rb32(pb); | |
309 | avpriv_set_pts_info(ast, 33, 1, ast->codec->sample_rate); | |
310 | if (set_channels(avctx, ast, avio_rb32(pb)) < 0) | |
311 | return AVERROR_INVALIDDATA; | |
312 | ||
313 | v = avio_rb32(pb); | |
314 | if (v == AUDIO_FORMAT_SIGNED) { | |
315 | ast->codec->codec_id = AV_CODEC_ID_PCM_S16BE; | |
316 | } else { | |
317 | avpriv_request_sample(avctx, "Audio compression (format %i)", v); | |
318 | } | |
319 | ||
320 | avio_skip(pb, 12); | |
321 | var_read_metadata(avctx, "title", 0x80); | |
322 | var_read_metadata(avctx, "comment", 0x100); | |
323 | avio_skip(pb, 0x80); | |
324 | ||
325 | timestamp = 0; | |
326 | for (i = 0; i < vst->nb_frames; i++) { | |
327 | uint32_t pos = avio_rb32(pb); | |
328 | uint32_t asize = avio_rb32(pb); | |
329 | uint32_t vsize = avio_rb32(pb); | |
330 | avio_skip(pb, 8); | |
331 | av_add_index_entry(ast, pos, timestamp, asize, 0, AVINDEX_KEYFRAME); | |
332 | av_add_index_entry(vst, pos + asize, i, vsize, 0, AVINDEX_KEYFRAME); | |
333 | timestamp += asize / (ast->codec->channels * 2); | |
334 | } | |
335 | } else if (!version && avio_rb16(pb) == 3) { | |
336 | avio_skip(pb, 4); | |
337 | ||
338 | read_table(avctx, NULL, parse_global_var); | |
339 | ||
340 | if (mv->nb_audio_tracks > 1) { | |
341 | avpriv_request_sample(avctx, "Multiple audio streams support"); | |
342 | return AVERROR_PATCHWELCOME; | |
343 | } else if (mv->nb_audio_tracks) { | |
344 | ast = avformat_new_stream(avctx, NULL); | |
345 | if (!ast) | |
346 | return AVERROR(ENOMEM); | |
347 | ast->codec->codec_type = AVMEDIA_TYPE_AUDIO; | |
348 | read_table(avctx, ast, parse_audio_var); | |
349 | if (mv->acompression == 100 && | |
350 | mv->aformat == AUDIO_FORMAT_SIGNED && | |
351 | ast->codec->bits_per_coded_sample == 16) { | |
352 | ast->codec->codec_id = AV_CODEC_ID_PCM_S16BE; | |
353 | } else { | |
354 | avpriv_request_sample(avctx, | |
355 | "Audio compression %i (format %i, sr %i)", | |
356 | mv->acompression, mv->aformat, | |
357 | ast->codec->bits_per_coded_sample); | |
358 | ast->codec->codec_id = AV_CODEC_ID_NONE; | |
359 | } | |
360 | if (ast->codec->channels <= 0) { | |
361 | av_log(avctx, AV_LOG_ERROR, "No valid channel count found.\n"); | |
362 | return AVERROR_INVALIDDATA; | |
363 | } | |
364 | } | |
365 | ||
366 | if (mv->nb_video_tracks > 1) { | |
367 | avpriv_request_sample(avctx, "Multiple video streams support"); | |
368 | return AVERROR_PATCHWELCOME; | |
369 | } else if (mv->nb_video_tracks) { | |
370 | vst = avformat_new_stream(avctx, NULL); | |
371 | if (!vst) | |
372 | return AVERROR(ENOMEM); | |
373 | vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; | |
374 | read_table(avctx, vst, parse_video_var); | |
375 | } | |
376 | ||
377 | if (mv->nb_audio_tracks) | |
378 | read_index(pb, ast); | |
379 | ||
380 | if (mv->nb_video_tracks) | |
381 | read_index(pb, vst); | |
382 | } else { | |
383 | avpriv_request_sample(avctx, "Version %i", version); | |
384 | return AVERROR_PATCHWELCOME; | |
385 | } | |
386 | ||
387 | return 0; | |
388 | } | |
389 | ||
390 | static int mv_read_packet(AVFormatContext *avctx, AVPacket *pkt) | |
391 | { | |
392 | MvContext *mv = avctx->priv_data; | |
393 | AVIOContext *pb = avctx->pb; | |
394 | AVStream *st = avctx->streams[mv->stream_index]; | |
395 | const AVIndexEntry *index; | |
396 | int frame = mv->frame[mv->stream_index]; | |
397 | int ret; | |
398 | uint64_t pos; | |
399 | ||
400 | if (frame < st->nb_index_entries) { | |
401 | index = &st->index_entries[frame]; | |
402 | pos = avio_tell(pb); | |
403 | if (index->pos > pos) | |
404 | avio_skip(pb, index->pos - pos); | |
405 | else if (index->pos < pos) { | |
406 | if (!pb->seekable) | |
407 | return AVERROR(EIO); | |
408 | ret = avio_seek(pb, index->pos, SEEK_SET); | |
409 | if (ret < 0) | |
410 | return ret; | |
411 | } | |
412 | ret = av_get_packet(pb, pkt, index->size); | |
413 | if (ret < 0) | |
414 | return ret; | |
415 | ||
416 | pkt->stream_index = mv->stream_index; | |
417 | pkt->pts = index->timestamp; | |
418 | pkt->flags |= AV_PKT_FLAG_KEY; | |
419 | ||
420 | mv->frame[mv->stream_index]++; | |
421 | mv->eof_count = 0; | |
422 | } else { | |
423 | mv->eof_count++; | |
424 | if (mv->eof_count >= avctx->nb_streams) | |
425 | return AVERROR_EOF; | |
426 | ||
427 | // avoid returning 0 without a packet | |
428 | return AVERROR(EAGAIN); | |
429 | } | |
430 | ||
431 | mv->stream_index++; | |
432 | if (mv->stream_index >= avctx->nb_streams) | |
433 | mv->stream_index = 0; | |
434 | ||
435 | return 0; | |
436 | } | |
437 | ||
438 | static int mv_read_seek(AVFormatContext *avctx, int stream_index, | |
439 | int64_t timestamp, int flags) | |
440 | { | |
441 | MvContext *mv = avctx->priv_data; | |
442 | AVStream *st = avctx->streams[stream_index]; | |
443 | int frame, i; | |
444 | ||
445 | if ((flags & AVSEEK_FLAG_FRAME) || (flags & AVSEEK_FLAG_BYTE)) | |
446 | return AVERROR(ENOSYS); | |
447 | ||
448 | if (!avctx->pb->seekable) | |
449 | return AVERROR(EIO); | |
450 | ||
451 | frame = av_index_search_timestamp(st, timestamp, flags); | |
452 | if (frame < 0) | |
453 | return AVERROR_INVALIDDATA; | |
454 | ||
455 | for (i = 0; i < avctx->nb_streams; i++) | |
456 | mv->frame[i] = frame; | |
457 | return 0; | |
458 | } | |
459 | ||
460 | AVInputFormat ff_mv_demuxer = { | |
461 | .name = "mv", | |
462 | .long_name = NULL_IF_CONFIG_SMALL("Silicon Graphics Movie"), | |
463 | .priv_data_size = sizeof(MvContext), | |
464 | .read_probe = mv_probe, | |
465 | .read_header = mv_read_header, | |
466 | .read_packet = mv_read_packet, | |
467 | .read_seek = mv_read_seek, | |
468 | }; |