1 | /* |
2 | Copyright (c) 2012, Broadcom Europe Ltd |
3 | All rights reserved. |
4 | |
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the following conditions are met: |
7 | * Redistributions of source code must retain the above copyright |
8 | notice, this list of conditions and the following disclaimer. |
9 | * Redistributions in binary form must reproduce the above copyright |
10 | notice, this list of conditions and the following disclaimer in the |
11 | documentation and/or other materials provided with the distribution. |
12 | * Neither the name of the copyright holder nor the |
13 | names of its contributors may be used to endorse or promote products |
14 | derived from this software without specific prior written permission. |
15 | |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY |
20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ |
27 | #include <stdlib.h> |
28 | #include <string.h> |
29 | |
30 | #define CONTAINER_IS_LITTLE_ENDIAN |
31 | //#define ENABLE_CONTAINERS_LOG_FORMAT |
32 | //#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE |
33 | #define CONTAINER_HELPER_LOG_INDENT(a) 0 |
34 | #include "containers/core/containers_private.h" |
35 | #include "containers/core/containers_io_helpers.h" |
36 | #include "containers/core/containers_utils.h" |
37 | #include "containers/core/containers_logging.h" |
38 | #include "containers/core/containers_waveformat.h" |
39 | |
40 | /****************************************************************************** |
41 | Defines. |
42 | ******************************************************************************/ |
43 | #define AVISF_DISABLED 0x00000001 /*< If set stream should not be enabled by default. */ |
44 | #define AVIF_MUSTUSEINDEX 0x00000020 |
45 | #define AVIF_TRUSTCKTYPE 0x00000800 /*< (OpenDML) keyframe information reliable. */ |
46 | |
47 | #define AVIIF_LIST 0x00000001 |
48 | #define AVIIF_KEYFRAME 0x00000010 |
49 | #define AVIIF_NOTIME 0x00000100 |
50 | |
51 | #define AVI_INDEX_OF_INDEXES 0x00 |
52 | #define AVI_INDEX_OF_CHUNKS 0x01 |
53 | #define AVI_INDEX_2FIELD 0x01 |
54 | #define AVI_INDEX_DELTAFRAME 0x80000000 |
55 | |
56 | #define AVI_TRACKS_MAX 16 /*< We won't try to handle streams with more tracks than this */ |
57 | |
58 | #define AVI_TWOCC(a,b) ((a) | (b << 8)) |
59 | |
60 | #define AVI_SYNC_CHUNK(ctx) \ |
61 | while(STREAM_POSITION(ctx) & 1) \ |
62 | { \ |
63 | if (SKIP_BYTES(ctx, 1) != 1) break; \ |
64 | } |
65 | |
66 | #define AVI_SKIP_CHUNK(ctx, size) \ |
67 | do { \ |
68 | SKIP_BYTES(ctx, size); \ |
69 | AVI_SYNC_CHUNK(ctx); \ |
70 | } while(0) |
71 | |
72 | /****************************************************************************** |
73 | Type definitions |
74 | ******************************************************************************/ |
75 | typedef struct AVI_TRACK_STREAM_STATE_T |
76 | { |
77 | unsigned current_track_num; /**< Number of track currently being read */ |
78 | int64_t data_offset; /**< Offset within the stream to find the track data */ |
79 | uint32_t chunk_size; /**< Size of the current chunk being read */ |
80 | uint32_t chunk_data_left; /**< Data left from the current chunk being read */ |
81 | |
82 | unsigned ; /**< Temporary storage for in-band data e.g. 'dd' |
83 | chunks */ |
84 | uint32_t [4]; |
85 | uint32_t ; |
86 | uint32_t ; |
87 | } AVI_TRACK_STREAM_STATE_T; |
88 | |
89 | typedef struct AVI_TRACK_CHUNK_STATE_T |
90 | { |
91 | uint64_t index; |
92 | uint64_t offs; /**< offset into bytestream consisting of all chunks for this track */ |
93 | int64_t time_pos; /**< pts of chunk (if known) */ |
94 | uint32_t flags; /**< flags associated with chunk */ |
95 | AVI_TRACK_STREAM_STATE_T local_state; |
96 | AVI_TRACK_STREAM_STATE_T *state; |
97 | } AVI_TRACK_CHUNK_STATE_T; |
98 | |
99 | typedef struct VC_CONTAINER_TRACK_MODULE_T |
100 | { |
101 | int64_t time_start; /**< i.e. 'dwStart' in 'strh' (converted to microseconds) */ |
102 | int64_t duration; /**< i.e. 'dwLength' in 'strh' (converted to microseconds) */ |
103 | uint32_t time_num; /**< i.e. 'dwScale' in 'strh' */ |
104 | uint32_t time_den; /**< i.e. 'dwRate' in 'strh', time_num / time_den = |
105 | samples (or frames) / second for audio (or video) */ |
106 | uint32_t sample_size; /**< i.e. 'dwSampleSize' in 'strh' */ |
107 | |
108 | uint64_t index_offset; /**< Offset to the start of an OpenDML index i.e. 'indx' |
109 | (if available) */ |
110 | uint32_t index_size; /**< Size of the OpenDML index chunk */ |
111 | AVI_TRACK_CHUNK_STATE_T chunk; |
112 | } VC_CONTAINER_TRACK_MODULE_T; |
113 | |
114 | typedef struct VC_CONTAINER_MODULE_T |
115 | { |
116 | VC_CONTAINER_TRACK_T *tracks[AVI_TRACKS_MAX]; |
117 | uint64_t data_offset; /**< Offset to the start of data packets i.e. |
118 | the data in the 'movi' list */ |
119 | uint64_t data_size; /**< Size of the chunk containing data packets */ |
120 | uint64_t index_offset; /**< Offset to the start of index data e.g. |
121 | the data in a 'idx1' list */ |
122 | uint32_t index_size; /**< Size of the chunk containing index data */ |
123 | AVI_TRACK_STREAM_STATE_T state; |
124 | } VC_CONTAINER_MODULE_T; |
125 | |
126 | /****************************************************************************** |
127 | Function prototypes |
128 | ******************************************************************************/ |
129 | VC_CONTAINER_STATUS_T avi_reader_open( VC_CONTAINER_T * ); |
130 | |
131 | /****************************************************************************** |
132 | Local Functions |
133 | ******************************************************************************/ |
134 | |
135 | static VC_CONTAINER_STATUS_T avi_find_chunk(VC_CONTAINER_T *p_ctx, VC_CONTAINER_FOURCC_T id, uint32_t *size) |
136 | { |
137 | VC_CONTAINER_STATUS_T status; |
138 | VC_CONTAINER_FOURCC_T chunk_id; |
139 | uint32_t chunk_size; |
140 | |
141 | do { |
142 | chunk_id = READ_FOURCC(p_ctx, "Chunk ID" ); |
143 | chunk_size = READ_U32(p_ctx, "Chunk size" ); |
144 | if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; |
145 | |
146 | if(chunk_id == id) |
147 | { |
148 | *size = chunk_size; |
149 | return VC_CONTAINER_SUCCESS; |
150 | } |
151 | /* Not interested in this chunk, skip it. */ |
152 | AVI_SKIP_CHUNK(p_ctx, chunk_size); |
153 | } while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS); |
154 | |
155 | return status; /* chunk not found */ |
156 | } |
157 | |
158 | static VC_CONTAINER_STATUS_T avi_find_list(VC_CONTAINER_T *p_ctx, VC_CONTAINER_FOURCC_T fourcc, uint32_t *size) |
159 | { |
160 | VC_CONTAINER_STATUS_T status; |
161 | VC_CONTAINER_FOURCC_T chunk_id; |
162 | uint32_t chunk_size; |
163 | uint32_t peek_buf[1]; |
164 | |
165 | do { |
166 | chunk_id = READ_FOURCC(p_ctx, "Chunk ID" ); |
167 | chunk_size = READ_U32(p_ctx, "Chunk size" ); |
168 | if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; |
169 | |
170 | if(chunk_id == VC_FOURCC('L','I','S','T')) |
171 | { |
172 | if (PEEK_BYTES(p_ctx, (uint8_t*)peek_buf, 4) != 4) |
173 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
174 | if (peek_buf[0] == fourcc) |
175 | { |
176 | *size = chunk_size; |
177 | return VC_CONTAINER_SUCCESS; |
178 | } |
179 | } |
180 | /* Not interested in this chunk, skip it. */ |
181 | AVI_SKIP_CHUNK(p_ctx, chunk_size); |
182 | } while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS); |
183 | |
184 | return status; /* list not found */ |
185 | } |
186 | |
187 | static int64_t avi_stream_ticks_to_us(VC_CONTAINER_TRACK_MODULE_T *track_module, uint64_t ticks) |
188 | { |
189 | int64_t time; |
190 | vc_container_assert(track_module->time_den != 0); |
191 | time = INT64_C(1000000) * track_module->time_num * ticks / track_module->time_den; |
192 | return time; |
193 | } |
194 | |
195 | static int64_t avi_calculate_chunk_time(VC_CONTAINER_TRACK_MODULE_T *track_module) |
196 | { |
197 | if (track_module->sample_size == 0) |
198 | return track_module->time_start + avi_stream_ticks_to_us(track_module, track_module->chunk.index); |
199 | else |
200 | return track_module->time_start + avi_stream_ticks_to_us(track_module, |
201 | ((track_module->chunk.offs + (track_module->sample_size >> 1)) / track_module->sample_size)); |
202 | } |
203 | |
204 | static VC_CONTAINER_STATUS_T (VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track, |
205 | VC_CONTAINER_TRACK_MODULE_T *track_module) |
206 | { |
207 | VC_CONTAINER_STATUS_T status; |
208 | int64_t list_offset; |
209 | uint32_t list_size, chunk_id, chunk_size; |
210 | |
211 | int = 0, stream_format_chunk_read = 0; |
212 | |
213 | /* Look for a 'strl' LIST (sub)chunk */ |
214 | status = avi_find_list(p_ctx, VC_FOURCC('s','t','r','l'), &chunk_size); |
215 | if (status != VC_CONTAINER_SUCCESS) |
216 | { |
217 | LOG_DEBUG(p_ctx, "'strl' LIST not found for stream" ); |
218 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
219 | } |
220 | |
221 | list_offset = STREAM_POSITION(p_ctx); |
222 | list_size = chunk_size; |
223 | SKIP_FOURCC(p_ctx, "strl" ); |
224 | |
225 | while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS && STREAM_POSITION(p_ctx) < list_offset + list_size) |
226 | { |
227 | int64_t offset = STREAM_POSITION(p_ctx); |
228 | chunk_id = READ_FOURCC(p_ctx, "Chunk ID" ); |
229 | chunk_size = READ_U32(p_ctx, "Chunk size" ); |
230 | LOG_FORMAT(p_ctx, "chunk %4.4s, offset: %" PRIi64", size: %i" , (char *)&chunk_id, offset, chunk_size); |
231 | |
232 | if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status; |
233 | |
234 | if(chunk_id == VC_FOURCC('s','t','r','h')) |
235 | { |
236 | VC_CONTAINER_FOURCC_T fourcc_type, fourcc_handler; |
237 | uint32_t flags, scale, rate, div, start, length, sample_size; |
238 | |
239 | /* We won't accept more than one 'strh' per stream */ |
240 | if (stream_header_chunk_read) |
241 | { |
242 | LOG_DEBUG(p_ctx, "rejecting invalid 'strl', found more than one 'strh'" ); |
243 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
244 | } |
245 | |
246 | fourcc_type = READ_FOURCC(p_ctx, "fccType" ); |
247 | fourcc_handler = READ_FOURCC(p_ctx, "fccHandler" ); |
248 | flags = READ_U32(p_ctx, "dwFlags" ); |
249 | SKIP_U16(p_ctx, "wPriority" ); |
250 | SKIP_U16(p_ctx, "wLanguage" ); |
251 | SKIP_U32(p_ctx, "dwInitialFrames" ); |
252 | scale = READ_U32(p_ctx, "dwScale" ); |
253 | rate = READ_U32(p_ctx, "dwRate" ); |
254 | start = READ_U32(p_ctx, "dwStart" ); |
255 | length = READ_U32(p_ctx, "dwLength" ); |
256 | SKIP_U32(p_ctx, "dwSuggestedBufferSize" ); |
257 | SKIP_U32(p_ctx, "dwQuality" ); |
258 | sample_size = READ_U32(p_ctx, "dwSampleSize" ); |
259 | SKIP_U16(p_ctx, "rcFrame.left" ); |
260 | SKIP_U16(p_ctx, "rcFrame.top" ); |
261 | SKIP_U16(p_ctx, "rcFrame.right" ); |
262 | SKIP_U16(p_ctx, "rcFrame.bottom" ); |
263 | |
264 | /* In AVI, sec/frame = scale/rate and frames/sec = rate/scale */ |
265 | if (rate == 0) |
266 | { |
267 | LOG_DEBUG(p_ctx, "invalid dwRate: 0, using 1 as a guess" ); |
268 | LOG_DEBUG(p_ctx, "timestamps will almost certainly be wrong" ); |
269 | rate = 1; |
270 | } |
271 | |
272 | div = vc_container_maths_gcd((int64_t)scale, (int64_t)rate); |
273 | scale = scale / div; |
274 | rate = rate / div; |
275 | |
276 | track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; |
277 | if(fourcc_type == VC_FOURCC('v','i','d','s')) |
278 | { |
279 | track->format->es_type = VC_CONTAINER_ES_TYPE_VIDEO; |
280 | track->format->type->video.frame_rate_num = rate; |
281 | track->format->type->video.frame_rate_den = scale; |
282 | |
283 | if (sample_size != 0) |
284 | { |
285 | LOG_DEBUG(p_ctx, "ignoring dwSampleSize (%d) for video stream" , sample_size); |
286 | sample_size = 0; |
287 | } |
288 | } |
289 | else if(fourcc_type == VC_FOURCC('a','u','d','s')) |
290 | { |
291 | track->format->es_type = VC_CONTAINER_ES_TYPE_AUDIO; |
292 | /* VBR audio is going to be non-framed */ |
293 | track->format->flags &= ~VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; |
294 | } |
295 | else if(fourcc_type == VC_FOURCC('t','x','t','s')) |
296 | track->format->es_type = VC_CONTAINER_ES_TYPE_SUBPICTURE; |
297 | |
298 | /* Don't overwrite any existing value (i.e. in the unlikely case where we |
299 | see 'strf' before 'strh') */ |
300 | if(!track->format->codec) track->format->codec = vfw_fourcc_to_codec(fourcc_handler); |
301 | |
302 | /* FIXME: enable this once read_media does the right thing */ |
303 | if (!(flags & AVISF_DISABLED) || 1) |
304 | track->is_enabled = 1; |
305 | |
306 | track_module->time_num = scale; |
307 | track_module->time_den = rate; |
308 | track_module->time_start = avi_stream_ticks_to_us(track_module, (uint64_t)start); |
309 | track_module->duration = avi_stream_ticks_to_us(track_module, (uint64_t)length); |
310 | track_module->sample_size = sample_size; |
311 | |
312 | p_ctx->duration = MAX(p_ctx->duration, track_module->duration); |
313 | |
314 | stream_header_chunk_read = 1; |
315 | } |
316 | else if(chunk_id == VC_FOURCC('s','t','r','f')) |
317 | { |
318 | uint8_t *buffer; |
319 | unsigned = 0, = 0; |
320 | |
321 | /* We won't accept more than one 'strf' per stream */ |
322 | if (stream_format_chunk_read) |
323 | { |
324 | LOG_DEBUG(p_ctx, "rejecting invalid 'strl', found more than one 'strf'" ); |
325 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
326 | } |
327 | |
328 | /* Use the extradata buffer for reading in the entire 'strf' (should not be a large chunk) */ |
329 | if ((status = vc_container_track_allocate_extradata(p_ctx, track, chunk_size)) != VC_CONTAINER_SUCCESS) |
330 | { |
331 | LOG_DEBUG(p_ctx, "failed to allocate memory for 'strf' (%d bytes)" , chunk_size); |
332 | return status; |
333 | } |
334 | |
335 | buffer = track->priv->extradata; |
336 | if(READ_BYTES(p_ctx, buffer, chunk_size) != chunk_size) |
337 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
338 | AVI_SYNC_CHUNK(p_ctx); |
339 | |
340 | if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) |
341 | { |
342 | status = vc_container_bitmapinfoheader_to_es_format(buffer, chunk_size, &extra_offset, &extra_size, track->format); |
343 | } |
344 | else if(track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) |
345 | { |
346 | status = vc_container_waveformatex_to_es_format(buffer, chunk_size, &extra_offset, &extra_size, track->format); |
347 | if (track_module->sample_size != 0 && track_module->sample_size != track->format->type->audio.block_align) |
348 | { |
349 | LOG_DEBUG(p_ctx, "invalid dwSampleSize (%d), should match nBlockAlign (%d) for audio streams." , |
350 | track_module->sample_size, track->format->type->audio.block_align); |
351 | |
352 | /* Note that if nBlockAlign really is 0, strf is seriously broken... */ |
353 | if (track->format->type->audio.block_align != 0) |
354 | track_module->sample_size = track->format->type->audio.block_align; |
355 | } |
356 | else |
357 | { |
358 | /* Flawed muxers might only set nBlockAlign (i.e. not set dwSampleSize correctly). */ |
359 | if (track->format->type->audio.block_align == 1) |
360 | track_module->sample_size = 1; |
361 | } |
362 | } |
363 | |
364 | if (status != VC_CONTAINER_SUCCESS) return status; |
365 | |
366 | if (extra_size) |
367 | { |
368 | track->format->extradata = buffer + extra_offset; |
369 | track->format->extradata_size = extra_size; |
370 | } |
371 | |
372 | /* Codec specific fix-up */ |
373 | if (track->format->codec == VC_CONTAINER_CODEC_MP4A && |
374 | track->format->extradata_size) |
375 | { |
376 | /* This is going to be raw AAC so it will be framed */ |
377 | track_module->sample_size = 0; |
378 | track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; |
379 | } |
380 | |
381 | /* WMA specific fix-up */ |
382 | if ((track->format->codec == VC_CONTAINER_CODEC_WMA1 || |
383 | track->format->codec == VC_CONTAINER_CODEC_WMA2 || |
384 | track->format->codec == VC_CONTAINER_CODEC_WMAP || |
385 | track->format->codec == VC_CONTAINER_CODEC_WMAL || |
386 | track->format->codec == VC_CONTAINER_CODEC_WMAV) && |
387 | track->format->extradata_size) |
388 | { |
389 | track_module->sample_size = 0; |
390 | track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED; |
391 | } |
392 | |
393 | stream_format_chunk_read = 1; |
394 | } |
395 | else if(chunk_id == VC_FOURCC('s','t','r','d')) |
396 | { |
397 | /* The data in a 'strd' chunk is either codec configuration data or DRM information, |
398 | we can safely assume it might be either as long as we don't overwrite any config |
399 | data read previously from a 'strf' chunk */ |
400 | if ((status = vc_container_track_allocate_drmdata(p_ctx, track, chunk_size)) != VC_CONTAINER_SUCCESS) |
401 | { |
402 | LOG_DEBUG(p_ctx, "failed to allocate memory for 'strd' (%d bytes)" , chunk_size); |
403 | return status; |
404 | } |
405 | |
406 | if(READ_BYTES(p_ctx, track->priv->drmdata, chunk_size) != chunk_size) |
407 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
408 | AVI_SYNC_CHUNK(p_ctx); |
409 | |
410 | if (!track->format->extradata) |
411 | { |
412 | if (vc_container_track_allocate_extradata(p_ctx, track, chunk_size) == VC_CONTAINER_SUCCESS) |
413 | { |
414 | memcpy(track->format->extradata, track->priv->drmdata, chunk_size); |
415 | |
416 | track->format->extradata = track->priv->extradata; |
417 | track->format->extradata_size = chunk_size; |
418 | } |
419 | else |
420 | { |
421 | LOG_DEBUG(p_ctx, "failed to allocate memory for 'strd' (%d bytes)" , chunk_size); |
422 | LOG_DEBUG(p_ctx, "no codec configuration data set" ); |
423 | } |
424 | } |
425 | } |
426 | else if(chunk_id == VC_FOURCC('i','n','d','x')) |
427 | { |
428 | track_module->index_offset = STREAM_POSITION(p_ctx); |
429 | track_module->index_size = chunk_size; |
430 | } |
431 | else |
432 | { |
433 | /* Not interested in this chunk, skip it. */ |
434 | } |
435 | |
436 | /* Skip any left-over data */ |
437 | AVI_SKIP_CHUNK(p_ctx, offset + chunk_size + 8 - STREAM_POSITION(p_ctx) ); |
438 | } |
439 | |
440 | if (!stream_header_chunk_read || !stream_format_chunk_read) |
441 | { |
442 | LOG_DEBUG(p_ctx, "invalid 'strl', 'strh' and 'strf' are both required" ); |
443 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
444 | } |
445 | |
446 | return status; |
447 | } |
448 | |
449 | static VC_CONTAINER_STATUS_T avi_find_next_data_chunk(VC_CONTAINER_T *p_ctx, uint32_t *id, uint32_t *size) |
450 | { |
451 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
452 | VC_CONTAINER_FOURCC_T chunk_id; |
453 | uint32_t chunk_size = 0; |
454 | uint32_t peek_buf[1]; |
455 | |
456 | do |
457 | { |
458 | chunk_id = READ_FOURCC(p_ctx, "Chunk ID" ); |
459 | chunk_size = READ_U32(p_ctx, "Chunk size" ); |
460 | if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) |
461 | break; |
462 | |
463 | /* Check if this is a 'rec ' or a 'movi' LIST instead of a plain data chunk */ |
464 | if(chunk_id == VC_FOURCC('L','I','S','T')) |
465 | { |
466 | if (PEEK_BYTES(p_ctx, (uint8_t*)peek_buf, 4) != 4) |
467 | return VC_CONTAINER_ERROR_EOS; |
468 | if (peek_buf[0] == VC_FOURCC('r','e','c',' ')) |
469 | SKIP_FOURCC(p_ctx, "rec " ); |
470 | else if (peek_buf[0] == VC_FOURCC('m','o','v','i')) |
471 | SKIP_FOURCC(p_ctx, "movi" ); |
472 | else |
473 | AVI_SKIP_CHUNK(p_ctx, chunk_size); /* Not interested in this LIST chunk, skip it. */ |
474 | continue; |
475 | } |
476 | |
477 | /* Check if this is a 'AVIX' RIFF header instead of a data chunk */ |
478 | if(chunk_id == VC_FOURCC('R','I','F','F')) |
479 | { |
480 | if (PEEK_BYTES(p_ctx, (uint8_t*)peek_buf, 4) != 4) |
481 | return VC_CONTAINER_ERROR_EOS; |
482 | if (peek_buf[0] == VC_FOURCC('A','V','I','X')) |
483 | SKIP_FOURCC(p_ctx, "AVIX" ); |
484 | else |
485 | AVI_SKIP_CHUNK(p_ctx, chunk_size); /* Not interested in this RIFF header, skip it. */ |
486 | continue; |
487 | } |
488 | |
489 | /* We treat only db/dc/dd or wb chunks as data */ |
490 | if((uint32_t)chunk_id >> 16 == AVI_TWOCC('d','c') || |
491 | (uint32_t)chunk_id >> 16 == AVI_TWOCC('d','b') || |
492 | (uint32_t)chunk_id >> 16 == AVI_TWOCC('d','d') || |
493 | (uint32_t)chunk_id >> 16 == AVI_TWOCC('w','b')) |
494 | { |
495 | *id = chunk_id; |
496 | *size = chunk_size; |
497 | break; |
498 | } |
499 | |
500 | /* Need to exit if a zero sized chunk encountered so we don't loop forever. */ |
501 | if(chunk_size == 0 && chunk_id == 0) return VC_CONTAINER_ERROR_EOS; |
502 | |
503 | /* Not interested in this chunk, skip it */ |
504 | AVI_SKIP_CHUNK(p_ctx, chunk_size); |
505 | } while ((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS); |
506 | |
507 | return status; |
508 | } |
509 | |
510 | static void avi_track_from_chunk_id(VC_CONTAINER_FOURCC_T chunk_id, uint16_t *data_type, uint16_t *track_num) |
511 | { |
512 | *track_num = (((uint8_t*)&chunk_id)[0] - 48) * 10 + ((uint8_t*)&chunk_id)[1] - 48; |
513 | *data_type = (uint32_t)chunk_id >> 16; |
514 | } |
515 | |
516 | static VC_CONTAINER_STATUS_T avi_check_track(VC_CONTAINER_T *p_ctx, uint16_t data_type, uint16_t track_num) |
517 | { |
518 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
519 | |
520 | if (track_num < p_ctx->tracks_num) |
521 | { |
522 | if (data_type == AVI_TWOCC('w','b') && p_ctx->tracks[track_num]->format->es_type != VC_CONTAINER_ES_TYPE_AUDIO) |
523 | { |
524 | LOG_DEBUG(p_ctx, "suspicious track type ('wb'), track %d is not an audio track" , track_num); |
525 | status = VC_CONTAINER_ERROR_FAILED; |
526 | } |
527 | if (data_type == AVI_TWOCC('d','b') && p_ctx->tracks[track_num]->format->es_type != VC_CONTAINER_ES_TYPE_VIDEO) |
528 | { |
529 | LOG_DEBUG(p_ctx, "suspicious track type ('db'), track %d is not a video track" , track_num); |
530 | status = VC_CONTAINER_ERROR_FAILED; |
531 | } |
532 | if (data_type == AVI_TWOCC('d','c') && p_ctx->tracks[track_num]->format->es_type != VC_CONTAINER_ES_TYPE_VIDEO) |
533 | { |
534 | LOG_DEBUG(p_ctx, "suspicious track type ('dc'), track %d is not a video track" , track_num); |
535 | status = VC_CONTAINER_ERROR_FAILED; |
536 | } |
537 | if (data_type == AVI_TWOCC('d','d') && p_ctx->tracks[track_num]->format->es_type != VC_CONTAINER_ES_TYPE_VIDEO) |
538 | { |
539 | LOG_DEBUG(p_ctx, "suspicious track type ('dd'), track %d is not a video track" , track_num); |
540 | status = VC_CONTAINER_ERROR_FAILED; |
541 | } |
542 | } |
543 | else |
544 | { |
545 | LOG_DEBUG(p_ctx, "invalid track number %d (no more than %d tracks expected)" , |
546 | track_num, p_ctx->tracks_num); |
547 | status = VC_CONTAINER_ERROR_FAILED; |
548 | } |
549 | |
550 | return status; |
551 | } |
552 | |
553 | static int avi_compare_seek_time(int64_t chunk_time, int64_t seek_time, |
554 | int chunk_is_keyframe, VC_CONTAINER_SEEK_FLAGS_T seek_flags) |
555 | { |
556 | if (chunk_time == seek_time && chunk_is_keyframe && !(seek_flags & VC_CONTAINER_SEEK_FLAG_FORWARD)) |
557 | return 0; |
558 | |
559 | if (chunk_time > seek_time && chunk_is_keyframe && (seek_flags & VC_CONTAINER_SEEK_FLAG_FORWARD)) |
560 | return 0; |
561 | |
562 | if (chunk_time > seek_time && !(seek_flags & VC_CONTAINER_SEEK_FLAG_FORWARD)) |
563 | return 1; /* Chunk time is past seek time, caller should use the previous keyframe */ |
564 | |
565 | return -1; |
566 | } |
567 | |
568 | static VC_CONTAINER_STATUS_T avi_scan_legacy_index_chunk(VC_CONTAINER_T *p_ctx, int seek_track_num, |
569 | int64_t *time, VC_CONTAINER_SEEK_FLAGS_T flags, uint64_t *pos) |
570 | { |
571 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
572 | VC_CONTAINER_STATUS_T status; |
573 | VC_CONTAINER_TRACK_MODULE_T *track_module; |
574 | AVI_TRACK_CHUNK_STATE_T selected_chunk; |
575 | int64_t base_offset = module->data_offset; |
576 | int64_t selected_chunk_offset = base_offset + 4; |
577 | int32_t = 0; |
578 | int first_chunk_offset = 1; |
579 | uint64_t position; |
580 | |
581 | SEEK(p_ctx, module->index_offset); |
582 | memset(&selected_chunk, 0, sizeof(selected_chunk)); |
583 | |
584 | while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS && |
585 | (uint64_t)STREAM_POSITION(p_ctx) < module->index_offset + module->index_size) |
586 | { |
587 | VC_CONTAINER_FOURCC_T chunk_id; |
588 | uint16_t data_type, track_num; |
589 | uint32_t chunk_flags, offset, size; |
590 | |
591 | chunk_id = READ_FOURCC(p_ctx, "Chunk ID" ); |
592 | chunk_flags = READ_U32(p_ctx, "dwFlags" ); |
593 | offset = READ_U32(p_ctx, "dwOffset" ); |
594 | size = READ_U32(p_ctx, "dwSize" ); |
595 | |
596 | if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) break; |
597 | |
598 | /* Although it's rare, the offsets might be given from the start of the file |
599 | instead of the data chunk, we have to handle both cases. */ |
600 | if (first_chunk_offset) |
601 | { |
602 | if (offset > module->data_offset) base_offset = INT64_C(0); |
603 | selected_chunk_offset = base_offset + 4; |
604 | first_chunk_offset = 0; |
605 | } |
606 | |
607 | avi_track_from_chunk_id(chunk_id, &data_type, &track_num); |
608 | LOG_DEBUG(p_ctx, "reading track %" PRIu16, track_num); |
609 | |
610 | if (avi_check_track(p_ctx, data_type, track_num) != VC_CONTAINER_SUCCESS) |
611 | { |
612 | LOG_DEBUG(p_ctx, "skipping index entry for track %d/%d" , track_num, p_ctx->tracks_num); |
613 | continue; |
614 | } |
615 | |
616 | track_module = p_ctx->tracks[track_num]->priv->module; |
617 | |
618 | if (data_type == AVI_TWOCC('d','d')) |
619 | { |
620 | if (track_num == seek_track_num) |
621 | track_module->chunk.flags |= VC_CONTAINER_PACKET_FLAG_ENCRYPTED; |
622 | extra_offset = -(size + 8); |
623 | } |
624 | |
625 | /* If this entry does not affect timing, skip it */ |
626 | if ((chunk_flags & (AVIIF_LIST | AVIIF_NOTIME)) || data_type == AVI_TWOCC('d','d')) |
627 | continue; |
628 | |
629 | position = base_offset + offset + extra_offset; |
630 | extra_offset = INT64_C(0); |
631 | |
632 | /* Check validity of position */ |
633 | if (position <= module->data_offset /* || (*pos > module->data_offset + module->data_size*/) |
634 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
635 | |
636 | if (track_num == seek_track_num) |
637 | { |
638 | bool is_keyframe = true; |
639 | int res; |
640 | |
641 | if (p_ctx->tracks[track_num]->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) |
642 | is_keyframe = chunk_flags & AVIIF_KEYFRAME; |
643 | |
644 | if (is_keyframe) |
645 | track_module->chunk.flags |= VC_CONTAINER_PACKET_FLAG_KEYFRAME; |
646 | else |
647 | track_module->chunk.flags &= ~(VC_CONTAINER_PACKET_FLAG_KEYFRAME); |
648 | |
649 | res = avi_compare_seek_time(track_module->chunk.time_pos, *time, is_keyframe, flags); |
650 | if (res > 0) |
651 | break; /* We've found the keyframe we wanted */ |
652 | |
653 | if (is_keyframe) |
654 | { |
655 | selected_chunk_offset = position; |
656 | selected_chunk = track_module->chunk; |
657 | } |
658 | |
659 | if (res == 0) |
660 | break; /* We've found the keyframe we wanted */ |
661 | |
662 | track_module->chunk.index++; |
663 | track_module->chunk.offs += size; |
664 | track_module->chunk.time_pos = avi_calculate_chunk_time(track_module); |
665 | |
666 | LOG_DEBUG(p_ctx, "index %" PRIu64", offs %" PRIu64", time %" PRIi64"us" , track_module->chunk.index, |
667 | track_module->chunk.offs, track_module->chunk.time_pos); |
668 | } |
669 | } |
670 | |
671 | if (status == VC_CONTAINER_SUCCESS || |
672 | /* When seeking backwards, always return the last good position */ |
673 | !(flags & VC_CONTAINER_SEEK_FLAG_FORWARD)) |
674 | { |
675 | *pos = selected_chunk_offset; |
676 | track_module = p_ctx->tracks[seek_track_num]->priv->module; |
677 | track_module->chunk.index = selected_chunk.index; |
678 | track_module->chunk.offs = selected_chunk.offs; |
679 | track_module->chunk.flags = selected_chunk.flags; |
680 | track_module->chunk.time_pos = selected_chunk.time_pos; |
681 | *time = track_module->chunk.time_pos; |
682 | return VC_CONTAINER_SUCCESS; |
683 | } |
684 | |
685 | return VC_CONTAINER_ERROR_NOT_FOUND; |
686 | } |
687 | |
688 | static VC_CONTAINER_STATUS_T avi_scan_standard_index_chunk(VC_CONTAINER_T *p_ctx, uint64_t index_offset, |
689 | unsigned seek_track_num, int64_t *time, VC_CONTAINER_SEEK_FLAGS_T flags, uint64_t *pos) |
690 | { |
691 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_NOT_FOUND; |
692 | VC_CONTAINER_TRACK_MODULE_T *track_module = NULL; |
693 | VC_CONTAINER_FOURCC_T chunk_id; |
694 | uint32_t chunk_size; |
695 | uint16_t data_type, track_num; |
696 | uint8_t index_type, index_sub_type; |
697 | uint32_t entry, entry_count = 0; |
698 | uint16_t entry_size; |
699 | uint64_t base_offset = UINT64_C(0); |
700 | uint64_t position = UINT64_C(0); |
701 | uint64_t prev_keyframe_offs = INT64_C(0); |
702 | AVI_TRACK_CHUNK_STATE_T prev_keyframe_chunk = { 0 }; |
703 | |
704 | SEEK(p_ctx, index_offset); |
705 | |
706 | chunk_id = READ_FOURCC(p_ctx, "Chunk ID" ); |
707 | chunk_size = READ_U32(p_ctx, "Chunk Size" ); |
708 | |
709 | entry_size = READ_U16(p_ctx, "wLongsPerEntry" ); |
710 | index_sub_type = READ_U8(p_ctx, "bIndexSubType" ); |
711 | index_type = READ_U8(p_ctx, "bIndexType" ); |
712 | entry_count = READ_U32(p_ctx, "nEntriesInUse" ); |
713 | chunk_id = READ_FOURCC(p_ctx, "dwChunkId" ); |
714 | base_offset = READ_U64(p_ctx, "qwBaseOffset" ); |
715 | SKIP_U32(p_ctx, "dwReserved" ); |
716 | |
717 | if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) |
718 | return status; |
719 | |
720 | avi_track_from_chunk_id(chunk_id, &data_type, &track_num); |
721 | status = avi_check_track(p_ctx, data_type, track_num); |
722 | if (status || chunk_size < 24 || track_num != seek_track_num) |
723 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
724 | |
725 | if (entry_size != 2 || index_sub_type != 0 || index_type != AVI_INDEX_OF_CHUNKS) |
726 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
727 | |
728 | entry_count = MIN(entry_count, (chunk_size - 24) / (entry_size * 4)); |
729 | |
730 | track_module = p_ctx->tracks[seek_track_num]->priv->module; |
731 | |
732 | for (entry = 0; entry < entry_count; ++entry) |
733 | { |
734 | uint32_t chunk_offset; |
735 | int key_frame = 0; |
736 | |
737 | chunk_offset = READ_U32(p_ctx, "dwOffset" ); |
738 | chunk_size = READ_U32(p_ctx, "dwSize" ); |
739 | |
740 | if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) |
741 | break; |
742 | |
743 | status = VC_CONTAINER_ERROR_NOT_FOUND; |
744 | |
745 | if (!(chunk_size & AVI_INDEX_DELTAFRAME)) |
746 | key_frame = 1; |
747 | chunk_size &= ~AVI_INDEX_DELTAFRAME; |
748 | |
749 | position = base_offset + chunk_offset - 8; |
750 | |
751 | if (key_frame) |
752 | track_module->chunk.flags = VC_CONTAINER_PACKET_FLAG_KEYFRAME; |
753 | else |
754 | track_module->chunk.flags = 0; |
755 | |
756 | if (time != NULL) |
757 | { |
758 | int res; |
759 | status = VC_CONTAINER_ERROR_NOT_FOUND; |
760 | res = avi_compare_seek_time(track_module->chunk.time_pos, *time, key_frame, flags); |
761 | |
762 | if (res == 0) |
763 | { |
764 | *pos = position; |
765 | *time = track_module->chunk.time_pos; |
766 | status = VC_CONTAINER_SUCCESS; |
767 | break; |
768 | } |
769 | else if (res > 0) |
770 | { |
771 | if (prev_keyframe_offs) |
772 | { |
773 | *pos = prev_keyframe_offs; |
774 | track_module->chunk = prev_keyframe_chunk; |
775 | *time = track_module->chunk.time_pos; |
776 | status = VC_CONTAINER_SUCCESS; |
777 | } |
778 | break; |
779 | } |
780 | |
781 | if (key_frame) |
782 | { |
783 | prev_keyframe_offs = position; |
784 | prev_keyframe_chunk = track_module->chunk; |
785 | } |
786 | } |
787 | else |
788 | { |
789 | /* Not seeking to a time position, but scanning |
790 | track chunk state up to a certain file position |
791 | instead */ |
792 | if (position >= *pos) |
793 | { |
794 | status = VC_CONTAINER_SUCCESS; |
795 | break; |
796 | } |
797 | } |
798 | |
799 | track_module->chunk.index++; |
800 | track_module->chunk.offs += chunk_size; |
801 | track_module->chunk.time_pos = avi_calculate_chunk_time(track_module); |
802 | } |
803 | |
804 | return status; |
805 | } |
806 | |
807 | static VC_CONTAINER_STATUS_T avi_scan_super_index_chunk(VC_CONTAINER_T *p_ctx, unsigned index_track_num, |
808 | int64_t *time, VC_CONTAINER_SEEK_FLAGS_T flags, uint64_t *pos) |
809 | { |
810 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_NOT_FOUND; |
811 | VC_CONTAINER_FOURCC_T chunk_id; |
812 | uint64_t index_offset; |
813 | uint32_t index_size; |
814 | uint16_t data_type, track_num; |
815 | uint32_t entry, entry_count; |
816 | uint16_t entry_size; |
817 | uint8_t index_sub_type, index_type; |
818 | |
819 | index_offset = p_ctx->tracks[index_track_num]->priv->module->index_offset; |
820 | index_size = p_ctx->tracks[index_track_num]->priv->module->index_size; |
821 | |
822 | SEEK(p_ctx, index_offset); |
823 | |
824 | entry_size = READ_U16(p_ctx, "wLongsPerEntry" ); |
825 | index_sub_type = READ_U8(p_ctx, "bIndexSubType" ); |
826 | index_type = READ_U8(p_ctx, "bIndexType" ); |
827 | entry_count = READ_U32(p_ctx, "nEntriesInUse" ); |
828 | chunk_id = READ_FOURCC(p_ctx, "dwChunkId" ); |
829 | SKIP_U32(p_ctx, "dwReserved0" ); |
830 | SKIP_U32(p_ctx, "dwReserved1" ); |
831 | SKIP_U32(p_ctx, "dwReserved2" ); |
832 | |
833 | if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) |
834 | return status; |
835 | |
836 | if (index_type == AVI_INDEX_OF_INDEXES) |
837 | { |
838 | avi_track_from_chunk_id(chunk_id, &data_type, &track_num); |
839 | status = avi_check_track(p_ctx, data_type, track_num); |
840 | if (status || index_size < 24 || track_num != index_track_num) return VC_CONTAINER_ERROR_FORMAT_INVALID; |
841 | |
842 | /* FIXME: We should probably support AVI_INDEX_2FIELD as well */ |
843 | if (entry_size != 4 || index_sub_type != 0) |
844 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
845 | |
846 | entry_count = MIN(entry_count, (index_size - 24) / entry_size); |
847 | |
848 | for (entry = 0; entry < entry_count; ++entry) |
849 | { |
850 | uint64_t entry_offset, standard_index_offset; |
851 | standard_index_offset = READ_U64(p_ctx, "qwOffset" ); |
852 | SKIP_U32(p_ctx, "dwSize" ); |
853 | SKIP_U32(p_ctx, "dwDuration" ); |
854 | |
855 | if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) |
856 | break; |
857 | |
858 | if (standard_index_offset == UINT64_C(0)) |
859 | { |
860 | status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; /* Not plausible */ |
861 | break; |
862 | } |
863 | |
864 | entry_offset = STREAM_POSITION(p_ctx); |
865 | status = avi_scan_standard_index_chunk(p_ctx, standard_index_offset, index_track_num, time, flags, pos); |
866 | if (status != VC_CONTAINER_ERROR_NOT_FOUND) break; |
867 | SEEK(p_ctx, entry_offset); /* Move to next entry ('ix' chunk); */ |
868 | } |
869 | } |
870 | else if (index_type == AVI_INDEX_OF_CHUNKS) |
871 | { |
872 | /* It seems we are dealing with a standard index instead... */ |
873 | status = avi_scan_standard_index_chunk(p_ctx, index_offset, index_track_num, time, flags, pos); |
874 | } |
875 | else |
876 | { |
877 | status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
878 | } |
879 | |
880 | return status; |
881 | } |
882 | |
883 | static VC_CONTAINER_STATUS_T avi_read_dd_chunk( VC_CONTAINER_T *p_ctx, |
884 | AVI_TRACK_STREAM_STATE_T *p_state, uint16_t data_type, uint32_t chunk_size, |
885 | uint16_t track_num ) |
886 | { |
887 | if (data_type == AVI_TWOCC('d','d')) |
888 | { |
889 | if (p_state->extra_chunk_data_len || |
890 | chunk_size > sizeof(p_state->extra_chunk_data)) |
891 | { |
892 | LOG_DEBUG(p_ctx, "cannot handle multiple consecutive 'dd' chunks" ); |
893 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
894 | } |
895 | if(READ_BYTES(p_ctx, p_state->extra_chunk_data, chunk_size) != chunk_size) |
896 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
897 | |
898 | AVI_SYNC_CHUNK(p_ctx); |
899 | p_state->extra_chunk_track_num = track_num; |
900 | p_state->extra_chunk_data_len = chunk_size; |
901 | p_state->extra_chunk_data_offs = 0; |
902 | |
903 | return VC_CONTAINER_ERROR_CONTINUE; |
904 | } |
905 | else if (p_state->extra_chunk_data_len && |
906 | p_state->extra_chunk_track_num != track_num) |
907 | { |
908 | LOG_DEBUG(p_ctx, "dropping data from '%02ddd' chunk, not for this track (%d)" , |
909 | p_state->extra_chunk_track_num, track_num); |
910 | p_state->extra_chunk_data_len = 0; |
911 | } |
912 | |
913 | return VC_CONTAINER_SUCCESS; |
914 | } |
915 | |
916 | /***************************************************************************** |
917 | Functions exported as part of the Container Module API |
918 | *****************************************************************************/ |
919 | |
920 | static VC_CONTAINER_STATUS_T avi_reader_read( VC_CONTAINER_T *p_ctx, |
921 | VC_CONTAINER_PACKET_T *p_packet, uint32_t flags ) |
922 | { |
923 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
924 | VC_CONTAINER_TRACK_MODULE_T *track_module = NULL; |
925 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS; |
926 | AVI_TRACK_STREAM_STATE_T *p_state = &module->state; |
927 | |
928 | if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) |
929 | { |
930 | p_state = p_ctx->tracks[p_packet->track]->priv->module->chunk.state; |
931 | } |
932 | |
933 | LOG_DEBUG(p_ctx, "seeking to %" PRIi64, p_state->data_offset); |
934 | SEEK(p_ctx, p_state->data_offset); |
935 | |
936 | if (p_state->chunk_data_left == 0) |
937 | { |
938 | VC_CONTAINER_FOURCC_T chunk_id; |
939 | uint32_t chunk_size; |
940 | uint16_t data_type, track_num; |
941 | |
942 | if ((status = avi_find_next_data_chunk(p_ctx, &chunk_id, &chunk_size)) != VC_CONTAINER_SUCCESS) |
943 | { |
944 | LOG_DEBUG(p_ctx, "unable to find the next data chunk %d" , status); |
945 | p_state->data_offset = STREAM_POSITION(p_ctx); |
946 | return status; |
947 | } |
948 | |
949 | avi_track_from_chunk_id(chunk_id, &data_type, &track_num); |
950 | |
951 | if (avi_check_track(p_ctx, data_type, track_num) != VC_CONTAINER_SUCCESS) |
952 | { |
953 | AVI_SKIP_CHUNK(p_ctx, chunk_size); |
954 | LOG_DEBUG(p_ctx, "skipping data for track %d/%d" , track_num, p_ctx->tracks_num); |
955 | |
956 | p_state->data_offset = STREAM_POSITION(p_ctx); |
957 | return VC_CONTAINER_ERROR_CONTINUE; |
958 | } |
959 | |
960 | /* If we are reading from the global state (i.e. normal read or forced |
961 | read from the track on the global state), and the track we found is |
962 | not on the global state, connect the two */ |
963 | if (p_state == &module->state && |
964 | p_ctx->tracks[track_num]->priv->module->chunk.state != &module->state) |
965 | { |
966 | int64_t next_chunk; |
967 | |
968 | /* The track's offset is past the current position, skip it as we are |
969 | not interested in track data from before the track's offset. If we |
970 | were to read it we would return the same data multiple times. */ |
971 | next_chunk = (STREAM_POSITION(p_ctx) + chunk_size + 1) & ~1; |
972 | if (p_ctx->tracks[track_num]->priv->module->chunk.state->data_offset > next_chunk) |
973 | { |
974 | AVI_SKIP_CHUNK(p_ctx, chunk_size); |
975 | LOG_DEBUG(p_ctx, "skipping track %d/%d as we have already read it" , track_num, p_ctx->tracks_num); |
976 | p_state->data_offset = STREAM_POSITION(p_ctx); |
977 | return VC_CONTAINER_ERROR_CONTINUE; |
978 | } |
979 | |
980 | /* The track state must be pointing to the current chunk. We need to |
981 | reconnect the track to the global state. */ |
982 | LOG_DEBUG(p_ctx, "reconnect track %u to the global state" , track_num); |
983 | |
984 | p_ctx->tracks[track_num]->priv->module->chunk.state = &module->state; |
985 | |
986 | module->state = p_ctx->tracks[track_num]->priv->module->chunk.local_state; |
987 | |
988 | vc_container_assert(chunk_size >= p_state->chunk_data_left); |
989 | vc_container_assert(!p_state->chunk_data_left || |
990 | ((p_state->data_offset + p_state->chunk_data_left + 1) & ~1) == next_chunk); |
991 | vc_container_assert(p_state->current_track_num == track_num); |
992 | |
993 | return VC_CONTAINER_ERROR_CONTINUE; |
994 | } |
995 | |
996 | /* If we are not forcing, or if we are and found the track we are |
997 | interested in, check for dd data and set the track module for the later code */ |
998 | if (!(flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) || |
999 | (track_num == p_packet->track)) |
1000 | { |
1001 | if ((status = avi_read_dd_chunk(p_ctx, p_state, data_type, chunk_size, track_num)) != VC_CONTAINER_SUCCESS) |
1002 | { |
1003 | p_state->data_offset = STREAM_POSITION(p_ctx); |
1004 | return status; |
1005 | } |
1006 | } |
1007 | |
1008 | p_state->chunk_size = p_state->chunk_data_left = chunk_size; |
1009 | p_state->current_track_num = track_num; |
1010 | } |
1011 | |
1012 | /* If there is data from another track skip past it */ |
1013 | if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK && |
1014 | p_state->current_track_num != p_packet->track) |
1015 | { |
1016 | p_state->data_offset = STREAM_POSITION(p_ctx); |
1017 | |
1018 | AVI_SKIP_CHUNK(p_ctx, p_state->chunk_data_left); |
1019 | LOG_DEBUG(p_ctx, "skipping track %d/%d as we are ignoring it" , |
1020 | p_state->current_track_num, p_ctx->tracks_num); |
1021 | |
1022 | track_module = p_ctx->tracks[p_packet->track]->priv->module; |
1023 | |
1024 | /* Handle disconnection from global state */ |
1025 | if (p_state == &module->state && |
1026 | p_ctx->tracks[p_state->current_track_num]->priv->module->chunk.state == &module->state) |
1027 | { |
1028 | /* Make a copy of the global state */ |
1029 | LOG_DEBUG(p_ctx, "using local state on track %d" , p_packet->track); |
1030 | track_module->chunk.local_state = module->state; |
1031 | track_module->chunk.state = &track_module->chunk.local_state; |
1032 | } |
1033 | |
1034 | track_module->chunk.state->data_offset = STREAM_POSITION(p_ctx); |
1035 | track_module->chunk.state->chunk_data_left = 0; |
1036 | |
1037 | return VC_CONTAINER_ERROR_CONTINUE; |
1038 | } |
1039 | |
1040 | track_module = p_ctx->tracks[p_state->current_track_num]->priv->module; |
1041 | |
1042 | if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) |
1043 | { |
1044 | vc_container_assert(p_state->current_track_num == p_packet->track); |
1045 | } |
1046 | |
1047 | LOG_DEBUG(p_ctx, "reading track %u chunk at time %" PRIi64"us, offset %" PRIu64, |
1048 | p_state->current_track_num, track_module->chunk.time_pos, |
1049 | track_module->chunk.offs); |
1050 | if (p_state->extra_chunk_data_len) |
1051 | track_module->chunk.flags |= VC_CONTAINER_PACKET_FLAG_ENCRYPTED; |
1052 | else |
1053 | track_module->chunk.flags &= ~VC_CONTAINER_PACKET_FLAG_ENCRYPTED; |
1054 | |
1055 | if (p_packet) |
1056 | { |
1057 | p_packet->track = p_state->current_track_num; |
1058 | p_packet->size = p_state->chunk_data_left + |
1059 | p_state->extra_chunk_data_len; |
1060 | p_packet->flags = track_module->chunk.flags; |
1061 | |
1062 | if (p_state->chunk_data_left == p_state->chunk_size) |
1063 | { |
1064 | p_packet->pts = track_module->chunk.time_pos; |
1065 | if (track_module->sample_size == 0) |
1066 | p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME; |
1067 | } |
1068 | else |
1069 | { |
1070 | p_packet->pts = VC_CONTAINER_TIME_UNKNOWN; |
1071 | if (track_module->sample_size == 0) |
1072 | p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END; |
1073 | } |
1074 | |
1075 | p_packet->dts = VC_CONTAINER_TIME_UNKNOWN; |
1076 | } |
1077 | |
1078 | if (flags & VC_CONTAINER_READ_FLAG_SKIP) |
1079 | { |
1080 | SKIP_BYTES(p_ctx, p_state->chunk_data_left); |
1081 | AVI_SYNC_CHUNK(p_ctx); |
1082 | p_state->chunk_data_left = 0; |
1083 | p_state->extra_chunk_data_len = 0; |
1084 | } |
1085 | |
1086 | if (flags & VC_CONTAINER_READ_FLAG_INFO) |
1087 | { |
1088 | p_state->data_offset = STREAM_POSITION(p_ctx); |
1089 | |
1090 | LOG_DEBUG(p_ctx, "data position %" PRIi64, p_state->data_offset); |
1091 | |
1092 | return VC_CONTAINER_SUCCESS; |
1093 | } |
1094 | |
1095 | if (p_packet) |
1096 | { |
1097 | uint8_t *data = p_packet->data; |
1098 | uint32_t buffer_size = p_packet->buffer_size; |
1099 | uint32_t size = 0; |
1100 | uint32_t len; |
1101 | |
1102 | /* See if we need to insert extra data */ |
1103 | if (p_state->extra_chunk_data_len) |
1104 | { |
1105 | len = MIN(buffer_size, p_state->extra_chunk_data_len); |
1106 | memcpy(data, p_state->extra_chunk_data + p_state->extra_chunk_data_offs, len); |
1107 | data += len; |
1108 | buffer_size -= len; |
1109 | size = len; |
1110 | p_state->extra_chunk_data_len -= len; |
1111 | p_state->extra_chunk_data_offs += len; |
1112 | } |
1113 | |
1114 | /* Now try to read data into buffer */ |
1115 | len = MIN(buffer_size, p_state->chunk_data_left); |
1116 | READ_BYTES(p_ctx, data, len); |
1117 | size += len; |
1118 | p_state->chunk_data_left -= len; |
1119 | p_packet->size = size; |
1120 | |
1121 | if (p_state->chunk_data_left) |
1122 | p_packet->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_END; |
1123 | } |
1124 | |
1125 | if (p_state->chunk_data_left == 0) |
1126 | { |
1127 | AVI_SYNC_CHUNK(p_ctx); |
1128 | track_module->chunk.index++; |
1129 | track_module->chunk.offs += p_state->chunk_size; |
1130 | track_module->chunk.flags = 0; |
1131 | track_module->chunk.time_pos = avi_calculate_chunk_time(track_module); |
1132 | } |
1133 | |
1134 | /* Update the track's position */ |
1135 | p_state->data_offset = STREAM_POSITION(p_ctx); |
1136 | |
1137 | LOG_DEBUG(p_ctx, "data position %" PRIi64, p_state->data_offset); |
1138 | |
1139 | return STREAM_STATUS(p_ctx); |
1140 | } |
1141 | |
1142 | /*****************************************************************************/ |
1143 | static VC_CONTAINER_STATUS_T avi_reader_seek( VC_CONTAINER_T *p_ctx, int64_t *p_offset, |
1144 | VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags) |
1145 | { |
1146 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
1147 | VC_CONTAINER_STATUS_T status; |
1148 | uint64_t position, pos; |
1149 | AVI_TRACK_CHUNK_STATE_T chunk_state[AVI_TRACKS_MAX]; |
1150 | AVI_TRACK_STREAM_STATE_T global_state; |
1151 | unsigned seek_track_num, i; |
1152 | |
1153 | if (mode != VC_CONTAINER_SEEK_MODE_TIME || !STREAM_SEEKABLE(p_ctx)) |
1154 | return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; |
1155 | |
1156 | LOG_DEBUG(p_ctx, "AVI seeking to %" PRIi64"us" , *p_offset); |
1157 | |
1158 | /* Save current position and chunk state so we can restore it if we |
1159 | hit an error whilst scanning index data */ |
1160 | position = STREAM_POSITION(p_ctx); |
1161 | for(i = 0; i < p_ctx->tracks_num; i++) |
1162 | chunk_state[i] = p_ctx->tracks[i]->priv->module->chunk; |
1163 | global_state = p_ctx->priv->module->state; |
1164 | |
1165 | /* Clear track state affected by a seek operation of any kind */ |
1166 | for(i = 0; i < p_ctx->tracks_num; i++) |
1167 | { |
1168 | p_ctx->tracks[i]->priv->module->chunk.index = INT64_C(0); |
1169 | p_ctx->tracks[i]->priv->module->chunk.offs = INT64_C(0); |
1170 | p_ctx->tracks[i]->priv->module->chunk.flags = 0; |
1171 | p_ctx->tracks[i]->priv->module->chunk.time_pos = p_ctx->tracks[i]->priv->module->time_start; |
1172 | p_ctx->tracks[i]->priv->module->chunk.state = &p_ctx->tracks[i]->priv->module->chunk.local_state; |
1173 | p_ctx->tracks[i]->priv->module->chunk.local_state.chunk_data_left = UINT64_C(0); |
1174 | p_ctx->tracks[i]->priv->module->chunk.local_state.chunk_size = UINT64_C(0); |
1175 | p_ctx->tracks[i]->priv->module->chunk.local_state.extra_chunk_data_len = 0; |
1176 | p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset = module->data_offset + 4; |
1177 | } |
1178 | |
1179 | /* Clear the global state */ |
1180 | p_ctx->priv->module->state.chunk_data_left = UINT64_C(0); |
1181 | p_ctx->priv->module->state.chunk_size = UINT64_C(0); |
1182 | p_ctx->priv->module->state.extra_chunk_data_len = 0; |
1183 | p_ctx->priv->module->state.data_offset = module->data_offset + 4; |
1184 | |
1185 | /* Choose track to use for seeking, favor video tracks and tracks |
1186 | that are enabled */ |
1187 | for(i = 0; i < p_ctx->tracks_num; i++) |
1188 | if(p_ctx->tracks[i]->is_enabled && |
1189 | p_ctx->tracks[i]->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) break; |
1190 | if(i == p_ctx->tracks_num) |
1191 | for(i = 0; i < p_ctx->tracks_num; i++) |
1192 | if(p_ctx->tracks[i]->is_enabled) break; |
1193 | if(i == p_ctx->tracks_num) i = 0; |
1194 | |
1195 | LOG_DEBUG(p_ctx, "seek on track %d/%d" , i, p_ctx->tracks_num); |
1196 | seek_track_num = i; |
1197 | |
1198 | if (p_ctx->tracks[seek_track_num]->priv->module->index_offset) |
1199 | { |
1200 | LOG_DEBUG(p_ctx, "seeking using the super index" ); |
1201 | status = avi_scan_super_index_chunk(p_ctx, seek_track_num, p_offset, flags, &pos); |
1202 | if (status != VC_CONTAINER_SUCCESS) goto error; |
1203 | |
1204 | /* As AVI chunks don't convey timestamp information, we need to scan all tracks |
1205 | to the seek file position */ |
1206 | for(i = 0; i < p_ctx->tracks_num; i++) |
1207 | { |
1208 | if (p_ctx->tracks[i]->priv->module->index_offset && i != seek_track_num) |
1209 | { |
1210 | uint64_t track_pos; |
1211 | int64_t track_time = *p_offset; |
1212 | |
1213 | status = avi_scan_super_index_chunk(p_ctx, i, &track_time, flags, &track_pos); |
1214 | if (status != VC_CONTAINER_SUCCESS) goto error; |
1215 | p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset = track_pos; |
1216 | } |
1217 | } |
1218 | } |
1219 | else |
1220 | { |
1221 | LOG_DEBUG(p_ctx, "seeking using the legacy index" ); |
1222 | |
1223 | /* The legacy index comes after data so it might not have been available at the |
1224 | time the container was opened; if this is the case, see if we can find an index |
1225 | now, if we can't, then there's no way we can proceed with the seek. */ |
1226 | if(!module->index_offset) |
1227 | { |
1228 | uint32_t chunk_size; |
1229 | |
1230 | LOG_DEBUG(p_ctx, "no index offset, searching for one" ); |
1231 | |
1232 | /* Locate data chunk and skip it */ |
1233 | SEEK(p_ctx, module->data_offset); |
1234 | AVI_SKIP_CHUNK(p_ctx, module->data_size); |
1235 | /* Now search for the index */ |
1236 | status = avi_find_chunk(p_ctx, VC_FOURCC('i','d','x','1'), &chunk_size); |
1237 | if (status == VC_CONTAINER_SUCCESS) |
1238 | { |
1239 | /* Store offset to index data */ |
1240 | module->index_offset = STREAM_POSITION(p_ctx); |
1241 | module->index_size = chunk_size; |
1242 | p_ctx->capabilities |= VC_CONTAINER_CAPS_HAS_INDEX; |
1243 | p_ctx->capabilities |= VC_CONTAINER_CAPS_DATA_HAS_KEYFRAME_FLAG; |
1244 | } |
1245 | } |
1246 | /* Check again, we may or may not have an index */ |
1247 | if (!module->index_offset) |
1248 | { |
1249 | /* If there is no index and we are seeking to 0 we can assume the |
1250 | correct location is the start of the data. Otherwise we are unable |
1251 | to seek to a specified non-zero location without an index */ |
1252 | if (*p_offset != INT64_C(0)) |
1253 | { |
1254 | LOG_DEBUG(p_ctx, "failed to find the legacy index, unable to seek" ); |
1255 | status = VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION; |
1256 | goto error; |
1257 | } |
1258 | pos = module->data_offset; |
1259 | } |
1260 | else |
1261 | { |
1262 | LOG_DEBUG(p_ctx, "scanning the legacy index chunk" ); |
1263 | status = avi_scan_legacy_index_chunk(p_ctx, seek_track_num, p_offset, flags, &pos); |
1264 | if (status != VC_CONTAINER_SUCCESS) goto error; |
1265 | |
1266 | for (i = 0; i < p_ctx->tracks_num; i++) |
1267 | { |
1268 | if (i != seek_track_num) |
1269 | { |
1270 | uint64_t track_pos = pos; |
1271 | int64_t track_time = *p_offset; |
1272 | |
1273 | status = avi_scan_legacy_index_chunk(p_ctx, i, &track_time, flags, &track_pos); |
1274 | if (status != VC_CONTAINER_SUCCESS) goto error; |
1275 | p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset = track_pos; |
1276 | p_ctx->tracks[i]->priv->module->chunk.local_state.current_track_num = i; |
1277 | } |
1278 | } |
1279 | } |
1280 | } |
1281 | |
1282 | position = pos; |
1283 | |
1284 | /* Set the seek track's data offset */ |
1285 | p_ctx->tracks[seek_track_num]->priv->module->chunk.local_state.data_offset = position; |
1286 | p_ctx->tracks[seek_track_num]->priv->module->chunk.local_state.current_track_num = seek_track_num; |
1287 | |
1288 | /* Connect the earlier track(s) to the global state. Needs 2 passes */ |
1289 | module->state.data_offset = INT64_MAX; |
1290 | for(i = 0; i < p_ctx->tracks_num; i++) |
1291 | { |
1292 | if(p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset < |
1293 | module->state.data_offset) |
1294 | module->state = p_ctx->tracks[i]->priv->module->chunk.local_state; |
1295 | } |
1296 | for(i = 0; i < p_ctx->tracks_num; i++) |
1297 | { |
1298 | if(p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset == |
1299 | module->state.data_offset) |
1300 | p_ctx->tracks[i]->priv->module->chunk.state = &module->state; |
1301 | } |
1302 | |
1303 | LOG_DEBUG(p_ctx, "seek to %" PRIi64", position %" PRIu64, *p_offset, pos); |
1304 | |
1305 | return SEEK(p_ctx, position); |
1306 | |
1307 | error: |
1308 | p_ctx->priv->module->state = global_state; |
1309 | for(i = 0; i < p_ctx->tracks_num; i++) |
1310 | p_ctx->tracks[i]->priv->module->chunk = chunk_state[i]; |
1311 | SEEK(p_ctx, position); |
1312 | return status; |
1313 | } |
1314 | |
1315 | /*****************************************************************************/ |
1316 | static VC_CONTAINER_STATUS_T avi_reader_close( VC_CONTAINER_T *p_ctx ) |
1317 | { |
1318 | VC_CONTAINER_MODULE_T *module = p_ctx->priv->module; |
1319 | unsigned int i; |
1320 | |
1321 | for(i = 0; i < p_ctx->tracks_num; i++) |
1322 | vc_container_free_track(p_ctx, p_ctx->tracks[i]); |
1323 | p_ctx->tracks = NULL; |
1324 | p_ctx->tracks_num = 0; |
1325 | free(module); |
1326 | p_ctx->priv->module = 0; |
1327 | return VC_CONTAINER_SUCCESS; |
1328 | } |
1329 | |
1330 | /*****************************************************************************/ |
1331 | VC_CONTAINER_STATUS_T avi_reader_open( VC_CONTAINER_T *p_ctx ) |
1332 | { |
1333 | VC_CONTAINER_MODULE_T *module = 0; |
1334 | VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID; |
1335 | uint32_t chunk_size; |
1336 | uint32_t peek_buf[3]; |
1337 | unsigned int i; |
1338 | uint32_t flags, num_streams; |
1339 | int64_t offset; |
1340 | |
1341 | /* Check the RIFF chunk descriptor */ |
1342 | if (PEEK_BYTES(p_ctx, (uint8_t*)peek_buf, 12) != 12) |
1343 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
1344 | if( peek_buf[0] != VC_FOURCC('R','I','F','F') ) |
1345 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
1346 | if( peek_buf[2] != VC_FOURCC('A','V','I',' ') ) |
1347 | return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; |
1348 | |
1349 | /* |
1350 | * We now know we are dealing with an AVI file |
1351 | */ |
1352 | SKIP_FOURCC(p_ctx, "RIFF ID" ); |
1353 | SKIP_U32(p_ctx, "fileSize" ); |
1354 | SKIP_FOURCC(p_ctx, "fileType" ); |
1355 | |
1356 | /* Look for the 'hdrl' LIST (sub)chunk */ |
1357 | status = avi_find_list(p_ctx, VC_FOURCC('h','d','r','l'), &chunk_size); |
1358 | if (status != VC_CONTAINER_SUCCESS) |
1359 | { |
1360 | LOG_DEBUG(p_ctx, "'hdrl' LIST not found" ); |
1361 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
1362 | } |
1363 | |
1364 | SKIP_FOURCC(p_ctx, "hdrl" ); |
1365 | |
1366 | /* Now look for the 'avih' sub-chunk */ |
1367 | status = avi_find_chunk(p_ctx, VC_FOURCC('a','v','i','h'), &chunk_size); |
1368 | if (status != VC_CONTAINER_SUCCESS) |
1369 | { |
1370 | LOG_DEBUG(p_ctx, "'avih' not found" ); |
1371 | return VC_CONTAINER_ERROR_FORMAT_INVALID; |
1372 | } |
1373 | |
1374 | /* Parse the 'avih' sub-chunk */ |
1375 | SKIP_U32(p_ctx, "dwMicroSecPerFrame" ); |
1376 | SKIP_U32(p_ctx, "dwMaxBytesPerSec" ); |
1377 | SKIP_U32(p_ctx, "dwPaddingGranularity" ); |
1378 | flags = READ_U32(p_ctx, "dwFlags" ); |
1379 | SKIP_U32(p_ctx, "dwTotalFrames" ); |
1380 | SKIP_U32(p_ctx, "dwInitialFrames" ); |
1381 | num_streams = READ_U32(p_ctx, "dwStreams" ); |
1382 | SKIP_U32(p_ctx, "dwSuggestedBufferSize" ); |
1383 | SKIP_U32(p_ctx, "dwWidth" ); |
1384 | SKIP_U32(p_ctx, "dwHeight" ); |
1385 | SKIP_U32(p_ctx, "dwReserved0" ); |
1386 | SKIP_U32(p_ctx, "dwReserved1" ); |
1387 | SKIP_U32(p_ctx, "dwReserved2" ); |
1388 | SKIP_U32(p_ctx, "dwReserved3" ); |
1389 | |
1390 | if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) |
1391 | goto error; |
1392 | |
1393 | /* Allocate our context and tracks */ |
1394 | if ((module = malloc(sizeof(*module))) == NULL) |
1395 | return VC_CONTAINER_ERROR_OUT_OF_MEMORY; |
1396 | memset(module, 0, sizeof(*module)); |
1397 | p_ctx->priv->module = module; |
1398 | p_ctx->tracks = module->tracks; |
1399 | |
1400 | if (num_streams > AVI_TRACKS_MAX) |
1401 | { |
1402 | LOG_DEBUG(p_ctx, "cannot handle %u tracks, restricted to %d" , num_streams, AVI_TRACKS_MAX); |
1403 | num_streams = AVI_TRACKS_MAX; |
1404 | } |
1405 | |
1406 | for (p_ctx->tracks_num = 0; p_ctx->tracks_num != num_streams; p_ctx->tracks_num++) |
1407 | { |
1408 | p_ctx->tracks[p_ctx->tracks_num] = vc_container_allocate_track(p_ctx, sizeof(*p_ctx->tracks[0]->priv->module)); |
1409 | if(!p_ctx->tracks[p_ctx->tracks_num]) break; |
1410 | } |
1411 | if(p_ctx->tracks_num != num_streams) |
1412 | { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; } |
1413 | |
1414 | /* Try to read stream header list chunks ('strl') */ |
1415 | for (i = 0; i != num_streams; ++i) |
1416 | { |
1417 | status = avi_read_stream_header_list(p_ctx, p_ctx->tracks[i], p_ctx->tracks[i]->priv->module); |
1418 | if(status != VC_CONTAINER_SUCCESS) goto error; |
1419 | } |
1420 | |
1421 | /* Look for the 'movi' LIST (sub)chunk */ |
1422 | status = avi_find_list(p_ctx, VC_FOURCC('m','o','v','i'), &chunk_size); |
1423 | if (status != VC_CONTAINER_SUCCESS) |
1424 | { |
1425 | LOG_DEBUG(p_ctx, "'movi' LIST not found" ); |
1426 | status = VC_CONTAINER_ERROR_FORMAT_INVALID; |
1427 | goto error; |
1428 | } |
1429 | |
1430 | /* Store offset to the start and size of data (the 'movi' LIST) */ |
1431 | module->data_offset = STREAM_POSITION(p_ctx); |
1432 | module->data_size = chunk_size; |
1433 | |
1434 | p_ctx->priv->pf_close = avi_reader_close; |
1435 | p_ctx->priv->pf_read = avi_reader_read; |
1436 | p_ctx->priv->pf_seek = avi_reader_seek; |
1437 | |
1438 | if (flags & AVIF_MUSTUSEINDEX) |
1439 | { |
1440 | LOG_DEBUG(p_ctx, "AVIF_MUSTUSEINDEX not supported, playback might not work properly" ); |
1441 | } |
1442 | |
1443 | /* If the stream is seekable, see if we can find an index (for at |
1444 | least one of the tracks); even if we cannot find an index now, |
1445 | one might become available later (e.g. when the stream grows |
1446 | run-time), in that case we might want to report that we can seek |
1447 | and re-search for the index again if or when we're requested to |
1448 | seek. */ |
1449 | if(STREAM_SEEKABLE(p_ctx)) |
1450 | { |
1451 | p_ctx->capabilities |= VC_CONTAINER_CAPS_CAN_SEEK; |
1452 | p_ctx->capabilities |= VC_CONTAINER_CAPS_FORCE_TRACK; |
1453 | |
1454 | for(i = 0; i < p_ctx->tracks_num; i++) |
1455 | if(p_ctx->tracks[i]->priv->module->index_offset) break; |
1456 | |
1457 | if (i < p_ctx->tracks_num) |
1458 | { |
1459 | p_ctx->capabilities |= VC_CONTAINER_CAPS_HAS_INDEX; |
1460 | if (flags & AVIF_TRUSTCKTYPE) |
1461 | p_ctx->capabilities |= VC_CONTAINER_CAPS_DATA_HAS_KEYFRAME_FLAG; |
1462 | } |
1463 | else |
1464 | { |
1465 | /* Skip data first */ |
1466 | AVI_SKIP_CHUNK(p_ctx, module->data_size); |
1467 | /* Now search for the index */ |
1468 | status = avi_find_chunk(p_ctx, VC_FOURCC('i','d','x','1'), &chunk_size); |
1469 | if (status == VC_CONTAINER_SUCCESS) |
1470 | { |
1471 | LOG_DEBUG(p_ctx, "'idx1' found" ); |
1472 | /* Store offset to index data */ |
1473 | module->index_offset = STREAM_POSITION(p_ctx); |
1474 | module->index_size = chunk_size; |
1475 | p_ctx->capabilities |= VC_CONTAINER_CAPS_HAS_INDEX; |
1476 | p_ctx->capabilities |= VC_CONTAINER_CAPS_DATA_HAS_KEYFRAME_FLAG; |
1477 | } |
1478 | |
1479 | /* Seek back to the start of the data */ |
1480 | SEEK(p_ctx, module->data_offset); |
1481 | } |
1482 | } |
1483 | |
1484 | SKIP_FOURCC(p_ctx, "movi" ); |
1485 | |
1486 | for (i = 0; i != num_streams; i++) |
1487 | { |
1488 | p_ctx->tracks[i]->priv->module->chunk.state = &p_ctx->priv->module->state; |
1489 | } |
1490 | p_ctx->priv->module->state.data_offset = STREAM_POSITION(p_ctx); |
1491 | |
1492 | /* Update the tracks to set their data offsets. This help with bad |
1493 | interleaving, for example when there is all the video tracks followed |
1494 | by all the audio tracks. It means we don't have to read through the |
1495 | tracks we are not interested in when forcing a read from a given track, |
1496 | as could be the case in the above example. If this fails we will fall |
1497 | back to skipping track data. */ |
1498 | offset = INT64_C(0); |
1499 | avi_reader_seek(p_ctx, &offset, VC_CONTAINER_SEEK_MODE_TIME, VC_CONTAINER_SEEK_FLAG_PRECISE); |
1500 | |
1501 | if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) goto error; |
1502 | return VC_CONTAINER_SUCCESS; |
1503 | |
1504 | error: |
1505 | LOG_DEBUG(p_ctx, "error opening stream (%i)" , status); |
1506 | for(i = 0; i < p_ctx->tracks_num; i++) |
1507 | vc_container_free_track(p_ctx, p_ctx->tracks[i]); |
1508 | p_ctx->tracks = NULL; |
1509 | p_ctx->tracks_num = 0; |
1510 | if (module) free(module); |
1511 | p_ctx->priv->module = NULL; |
1512 | return status; |
1513 | } |
1514 | |
1515 | /******************************************************************************** |
1516 | Entrypoint function |
1517 | ********************************************************************************/ |
1518 | |
1519 | #if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__) |
1520 | # pragma weak reader_open avi_reader_open |
1521 | #endif |
1522 | |