mirror of
https://github.com/libretro/RetroArch
synced 2025-02-08 15:40:14 +00:00
1522 lines
57 KiB
C
1522 lines
57 KiB
C
/*
|
|
Copyright (c) 2012, Broadcom Europe Ltd
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
* Neither the name of the copyright holder nor the
|
|
names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define CONTAINER_IS_LITTLE_ENDIAN
|
|
//#define ENABLE_CONTAINERS_LOG_FORMAT
|
|
//#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE
|
|
#define CONTAINER_HELPER_LOG_INDENT(a) 0
|
|
#include "containers/core/containers_private.h"
|
|
#include "containers/core/containers_io_helpers.h"
|
|
#include "containers/core/containers_utils.h"
|
|
#include "containers/core/containers_logging.h"
|
|
#include "containers/core/containers_waveformat.h"
|
|
|
|
/******************************************************************************
|
|
Defines.
|
|
******************************************************************************/
|
|
#define AVISF_DISABLED 0x00000001 /*< If set stream should not be enabled by default. */
|
|
#define AVIF_MUSTUSEINDEX 0x00000020
|
|
#define AVIF_TRUSTCKTYPE 0x00000800 /*< (OpenDML) keyframe information reliable. */
|
|
|
|
#define AVIIF_LIST 0x00000001
|
|
#define AVIIF_KEYFRAME 0x00000010
|
|
#define AVIIF_NOTIME 0x00000100
|
|
|
|
#define AVI_INDEX_OF_INDEXES 0x00
|
|
#define AVI_INDEX_OF_CHUNKS 0x01
|
|
#define AVI_INDEX_2FIELD 0x01
|
|
#define AVI_INDEX_DELTAFRAME 0x80000000
|
|
|
|
#define AVI_TRACKS_MAX 16 /*< We won't try to handle streams with more tracks than this */
|
|
|
|
#define AVI_TWOCC(a,b) ((a) | (b << 8))
|
|
|
|
#define AVI_SYNC_CHUNK(ctx) \
|
|
while(STREAM_POSITION(ctx) & 1) \
|
|
{ \
|
|
if (SKIP_BYTES(ctx, 1) != 1) break; \
|
|
}
|
|
|
|
#define AVI_SKIP_CHUNK(ctx, size) \
|
|
do { \
|
|
SKIP_BYTES(ctx, size); \
|
|
AVI_SYNC_CHUNK(ctx); \
|
|
} while(0)
|
|
|
|
/******************************************************************************
|
|
Type definitions
|
|
******************************************************************************/
|
|
typedef struct AVI_TRACK_STREAM_STATE_T
|
|
{
|
|
unsigned current_track_num; /**< Number of track currently being read */
|
|
int64_t data_offset; /**< Offset within the stream to find the track data */
|
|
uint32_t chunk_size; /**< Size of the current chunk being read */
|
|
uint32_t chunk_data_left; /**< Data left from the current chunk being read */
|
|
|
|
unsigned extra_chunk_track_num; /**< Temporary storage for in-band data e.g. 'dd'
|
|
chunks */
|
|
uint32_t extra_chunk_data[4];
|
|
uint32_t extra_chunk_data_offs;
|
|
uint32_t extra_chunk_data_len;
|
|
} AVI_TRACK_STREAM_STATE_T;
|
|
|
|
typedef struct AVI_TRACK_CHUNK_STATE_T
|
|
{
|
|
uint64_t index;
|
|
uint64_t offs; /**< offset into bytestream consisting of all chunks for this track */
|
|
int64_t time_pos; /**< pts of chunk (if known) */
|
|
uint32_t flags; /**< flags associated with chunk */
|
|
AVI_TRACK_STREAM_STATE_T local_state;
|
|
AVI_TRACK_STREAM_STATE_T *state;
|
|
} AVI_TRACK_CHUNK_STATE_T;
|
|
|
|
typedef struct VC_CONTAINER_TRACK_MODULE_T
|
|
{
|
|
int64_t time_start; /**< i.e. 'dwStart' in 'strh' (converted to microseconds) */
|
|
int64_t duration; /**< i.e. 'dwLength' in 'strh' (converted to microseconds) */
|
|
uint32_t time_num; /**< i.e. 'dwScale' in 'strh' */
|
|
uint32_t time_den; /**< i.e. 'dwRate' in 'strh', time_num / time_den =
|
|
samples (or frames) / second for audio (or video) */
|
|
uint32_t sample_size; /**< i.e. 'dwSampleSize' in 'strh' */
|
|
|
|
uint64_t index_offset; /**< Offset to the start of an OpenDML index i.e. 'indx'
|
|
(if available) */
|
|
uint32_t index_size; /**< Size of the OpenDML index chunk */
|
|
AVI_TRACK_CHUNK_STATE_T chunk;
|
|
} VC_CONTAINER_TRACK_MODULE_T;
|
|
|
|
typedef struct VC_CONTAINER_MODULE_T
|
|
{
|
|
VC_CONTAINER_TRACK_T *tracks[AVI_TRACKS_MAX];
|
|
uint64_t data_offset; /**< Offset to the start of data packets i.e.
|
|
the data in the 'movi' list */
|
|
uint64_t data_size; /**< Size of the chunk containing data packets */
|
|
uint64_t index_offset; /**< Offset to the start of index data e.g.
|
|
the data in a 'idx1' list */
|
|
uint32_t index_size; /**< Size of the chunk containing index data */
|
|
AVI_TRACK_STREAM_STATE_T state;
|
|
} VC_CONTAINER_MODULE_T;
|
|
|
|
/******************************************************************************
|
|
Function prototypes
|
|
******************************************************************************/
|
|
VC_CONTAINER_STATUS_T avi_reader_open( VC_CONTAINER_T * );
|
|
|
|
/******************************************************************************
|
|
Local Functions
|
|
******************************************************************************/
|
|
|
|
static VC_CONTAINER_STATUS_T avi_find_chunk(VC_CONTAINER_T *p_ctx, VC_CONTAINER_FOURCC_T id, uint32_t *size)
|
|
{
|
|
VC_CONTAINER_STATUS_T status;
|
|
VC_CONTAINER_FOURCC_T chunk_id;
|
|
uint32_t chunk_size;
|
|
|
|
do {
|
|
chunk_id = READ_FOURCC(p_ctx, "Chunk ID");
|
|
chunk_size = READ_U32(p_ctx, "Chunk size");
|
|
if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status;
|
|
|
|
if(chunk_id == id)
|
|
{
|
|
*size = chunk_size;
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
/* Not interested in this chunk, skip it. */
|
|
AVI_SKIP_CHUNK(p_ctx, chunk_size);
|
|
} while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS);
|
|
|
|
return status; /* chunk not found */
|
|
}
|
|
|
|
static VC_CONTAINER_STATUS_T avi_find_list(VC_CONTAINER_T *p_ctx, VC_CONTAINER_FOURCC_T fourcc, uint32_t *size)
|
|
{
|
|
VC_CONTAINER_STATUS_T status;
|
|
VC_CONTAINER_FOURCC_T chunk_id;
|
|
uint32_t chunk_size;
|
|
uint32_t peek_buf[1];
|
|
|
|
do {
|
|
chunk_id = READ_FOURCC(p_ctx, "Chunk ID");
|
|
chunk_size = READ_U32(p_ctx, "Chunk size");
|
|
if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status;
|
|
|
|
if(chunk_id == VC_FOURCC('L','I','S','T'))
|
|
{
|
|
if (PEEK_BYTES(p_ctx, (uint8_t*)peek_buf, 4) != 4)
|
|
return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
if (peek_buf[0] == fourcc)
|
|
{
|
|
*size = chunk_size;
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
}
|
|
/* Not interested in this chunk, skip it. */
|
|
AVI_SKIP_CHUNK(p_ctx, chunk_size);
|
|
} while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS);
|
|
|
|
return status; /* list not found */
|
|
}
|
|
|
|
static int64_t avi_stream_ticks_to_us(VC_CONTAINER_TRACK_MODULE_T *track_module, uint64_t ticks)
|
|
{
|
|
int64_t time;
|
|
vc_container_assert(track_module->time_den != 0);
|
|
time = INT64_C(1000000) * track_module->time_num * ticks / track_module->time_den;
|
|
return time;
|
|
}
|
|
|
|
static int64_t avi_calculate_chunk_time(VC_CONTAINER_TRACK_MODULE_T *track_module)
|
|
{
|
|
if (track_module->sample_size == 0)
|
|
return track_module->time_start + avi_stream_ticks_to_us(track_module, track_module->chunk.index);
|
|
else
|
|
return track_module->time_start + avi_stream_ticks_to_us(track_module,
|
|
((track_module->chunk.offs + (track_module->sample_size >> 1)) / track_module->sample_size));
|
|
}
|
|
|
|
static VC_CONTAINER_STATUS_T avi_read_stream_header_list(VC_CONTAINER_T *p_ctx, VC_CONTAINER_TRACK_T *track,
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module)
|
|
{
|
|
VC_CONTAINER_STATUS_T status;
|
|
int64_t list_offset;
|
|
uint32_t list_size, chunk_id, chunk_size;
|
|
|
|
int stream_header_chunk_read = 0, stream_format_chunk_read = 0;
|
|
|
|
/* Look for a 'strl' LIST (sub)chunk */
|
|
status = avi_find_list(p_ctx, VC_FOURCC('s','t','r','l'), &chunk_size);
|
|
if (status != VC_CONTAINER_SUCCESS)
|
|
{
|
|
LOG_DEBUG(p_ctx, "'strl' LIST not found for stream");
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
}
|
|
|
|
list_offset = STREAM_POSITION(p_ctx);
|
|
list_size = chunk_size;
|
|
SKIP_FOURCC(p_ctx, "strl");
|
|
|
|
while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS && STREAM_POSITION(p_ctx) < list_offset + list_size)
|
|
{
|
|
int64_t offset = STREAM_POSITION(p_ctx);
|
|
chunk_id = READ_FOURCC(p_ctx, "Chunk ID");
|
|
chunk_size = READ_U32(p_ctx, "Chunk size");
|
|
LOG_FORMAT(p_ctx, "chunk %4.4s, offset: %"PRIi64", size: %i", (char *)&chunk_id, offset, chunk_size);
|
|
|
|
if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) return status;
|
|
|
|
if(chunk_id == VC_FOURCC('s','t','r','h'))
|
|
{
|
|
VC_CONTAINER_FOURCC_T fourcc_type, fourcc_handler;
|
|
uint32_t flags, scale, rate, div, start, length, sample_size;
|
|
|
|
/* We won't accept more than one 'strh' per stream */
|
|
if (stream_header_chunk_read)
|
|
{
|
|
LOG_DEBUG(p_ctx, "rejecting invalid 'strl', found more than one 'strh'");
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
}
|
|
|
|
fourcc_type = READ_FOURCC(p_ctx, "fccType");
|
|
fourcc_handler = READ_FOURCC(p_ctx, "fccHandler");
|
|
flags = READ_U32(p_ctx, "dwFlags");
|
|
SKIP_U16(p_ctx, "wPriority");
|
|
SKIP_U16(p_ctx, "wLanguage");
|
|
SKIP_U32(p_ctx, "dwInitialFrames");
|
|
scale = READ_U32(p_ctx, "dwScale");
|
|
rate = READ_U32(p_ctx, "dwRate");
|
|
start = READ_U32(p_ctx, "dwStart");
|
|
length = READ_U32(p_ctx, "dwLength");
|
|
SKIP_U32(p_ctx, "dwSuggestedBufferSize");
|
|
SKIP_U32(p_ctx, "dwQuality");
|
|
sample_size = READ_U32(p_ctx, "dwSampleSize");
|
|
SKIP_U16(p_ctx, "rcFrame.left");
|
|
SKIP_U16(p_ctx, "rcFrame.top");
|
|
SKIP_U16(p_ctx, "rcFrame.right");
|
|
SKIP_U16(p_ctx, "rcFrame.bottom");
|
|
|
|
/* In AVI, sec/frame = scale/rate and frames/sec = rate/scale */
|
|
if (rate == 0)
|
|
{
|
|
LOG_DEBUG(p_ctx, "invalid dwRate: 0, using 1 as a guess");
|
|
LOG_DEBUG(p_ctx, "timestamps will almost certainly be wrong");
|
|
rate = 1;
|
|
}
|
|
|
|
div = vc_container_maths_gcd((int64_t)scale, (int64_t)rate);
|
|
scale = scale / div;
|
|
rate = rate / div;
|
|
|
|
track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED;
|
|
if(fourcc_type == VC_FOURCC('v','i','d','s'))
|
|
{
|
|
track->format->es_type = VC_CONTAINER_ES_TYPE_VIDEO;
|
|
track->format->type->video.frame_rate_num = rate;
|
|
track->format->type->video.frame_rate_den = scale;
|
|
|
|
if (sample_size != 0)
|
|
{
|
|
LOG_DEBUG(p_ctx, "ignoring dwSampleSize (%d) for video stream", sample_size);
|
|
sample_size = 0;
|
|
}
|
|
}
|
|
else if(fourcc_type == VC_FOURCC('a','u','d','s'))
|
|
{
|
|
track->format->es_type = VC_CONTAINER_ES_TYPE_AUDIO;
|
|
/* VBR audio is going to be non-framed */
|
|
track->format->flags &= ~VC_CONTAINER_ES_FORMAT_FLAG_FRAMED;
|
|
}
|
|
else if(fourcc_type == VC_FOURCC('t','x','t','s'))
|
|
track->format->es_type = VC_CONTAINER_ES_TYPE_SUBPICTURE;
|
|
|
|
/* Don't overwrite any existing value (i.e. in the unlikely case where we
|
|
see 'strf' before 'strh') */
|
|
if(!track->format->codec) track->format->codec = vfw_fourcc_to_codec(fourcc_handler);
|
|
|
|
/* FIXME: enable this once read_media does the right thing */
|
|
if (!(flags & AVISF_DISABLED) || 1)
|
|
track->is_enabled = 1;
|
|
|
|
track_module->time_num = scale;
|
|
track_module->time_den = rate;
|
|
track_module->time_start = avi_stream_ticks_to_us(track_module, (uint64_t)start);
|
|
track_module->duration = avi_stream_ticks_to_us(track_module, (uint64_t)length);
|
|
track_module->sample_size = sample_size;
|
|
|
|
p_ctx->duration = MAX(p_ctx->duration, track_module->duration);
|
|
|
|
stream_header_chunk_read = 1;
|
|
}
|
|
else if(chunk_id == VC_FOURCC('s','t','r','f'))
|
|
{
|
|
uint8_t *buffer;
|
|
unsigned extra_offset = 0, extra_size = 0;
|
|
|
|
/* We won't accept more than one 'strf' per stream */
|
|
if (stream_format_chunk_read)
|
|
{
|
|
LOG_DEBUG(p_ctx, "rejecting invalid 'strl', found more than one 'strf'");
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
}
|
|
|
|
/* Use the extradata buffer for reading in the entire 'strf' (should not be a large chunk) */
|
|
if ((status = vc_container_track_allocate_extradata(p_ctx, track, chunk_size)) != VC_CONTAINER_SUCCESS)
|
|
{
|
|
LOG_DEBUG(p_ctx, "failed to allocate memory for 'strf' (%d bytes)", chunk_size);
|
|
return status;
|
|
}
|
|
|
|
buffer = track->priv->extradata;
|
|
if(READ_BYTES(p_ctx, buffer, chunk_size) != chunk_size)
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
AVI_SYNC_CHUNK(p_ctx);
|
|
|
|
if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO)
|
|
{
|
|
status = vc_container_bitmapinfoheader_to_es_format(buffer, chunk_size, &extra_offset, &extra_size, track->format);
|
|
}
|
|
else if(track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO)
|
|
{
|
|
status = vc_container_waveformatex_to_es_format(buffer, chunk_size, &extra_offset, &extra_size, track->format);
|
|
if (track_module->sample_size != 0 && track_module->sample_size != track->format->type->audio.block_align)
|
|
{
|
|
LOG_DEBUG(p_ctx, "invalid dwSampleSize (%d), should match nBlockAlign (%d) for audio streams.",
|
|
track_module->sample_size, track->format->type->audio.block_align);
|
|
|
|
/* Note that if nBlockAlign really is 0, strf is seriously broken... */
|
|
if (track->format->type->audio.block_align != 0)
|
|
track_module->sample_size = track->format->type->audio.block_align;
|
|
}
|
|
else
|
|
{
|
|
/* Flawed muxers might only set nBlockAlign (i.e. not set dwSampleSize correctly). */
|
|
if (track->format->type->audio.block_align == 1)
|
|
track_module->sample_size = 1;
|
|
}
|
|
}
|
|
|
|
if (status != VC_CONTAINER_SUCCESS) return status;
|
|
|
|
if (extra_size)
|
|
{
|
|
track->format->extradata = buffer + extra_offset;
|
|
track->format->extradata_size = extra_size;
|
|
}
|
|
|
|
/* Codec specific fix-up */
|
|
if (track->format->codec == VC_CONTAINER_CODEC_MP4A &&
|
|
track->format->extradata_size)
|
|
{
|
|
/* This is going to be raw AAC so it will be framed */
|
|
track_module->sample_size = 0;
|
|
track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED;
|
|
}
|
|
|
|
/* WMA specific fix-up */
|
|
if ((track->format->codec == VC_CONTAINER_CODEC_WMA1 ||
|
|
track->format->codec == VC_CONTAINER_CODEC_WMA2 ||
|
|
track->format->codec == VC_CONTAINER_CODEC_WMAP ||
|
|
track->format->codec == VC_CONTAINER_CODEC_WMAL ||
|
|
track->format->codec == VC_CONTAINER_CODEC_WMAV) &&
|
|
track->format->extradata_size)
|
|
{
|
|
track_module->sample_size = 0;
|
|
track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED;
|
|
}
|
|
|
|
stream_format_chunk_read = 1;
|
|
}
|
|
else if(chunk_id == VC_FOURCC('s','t','r','d'))
|
|
{
|
|
/* The data in a 'strd' chunk is either codec configuration data or DRM information,
|
|
we can safely assume it might be either as long as we don't overwrite any config
|
|
data read previously from a 'strf' chunk */
|
|
if ((status = vc_container_track_allocate_drmdata(p_ctx, track, chunk_size)) != VC_CONTAINER_SUCCESS)
|
|
{
|
|
LOG_DEBUG(p_ctx, "failed to allocate memory for 'strd' (%d bytes)", chunk_size);
|
|
return status;
|
|
}
|
|
|
|
if(READ_BYTES(p_ctx, track->priv->drmdata, chunk_size) != chunk_size)
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
AVI_SYNC_CHUNK(p_ctx);
|
|
|
|
if (!track->format->extradata)
|
|
{
|
|
if (vc_container_track_allocate_extradata(p_ctx, track, chunk_size) == VC_CONTAINER_SUCCESS)
|
|
{
|
|
memcpy(track->format->extradata, track->priv->drmdata, chunk_size);
|
|
|
|
track->format->extradata = track->priv->extradata;
|
|
track->format->extradata_size = chunk_size;
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG(p_ctx, "failed to allocate memory for 'strd' (%d bytes)", chunk_size);
|
|
LOG_DEBUG(p_ctx, "no codec configuration data set");
|
|
}
|
|
}
|
|
}
|
|
else if(chunk_id == VC_FOURCC('i','n','d','x'))
|
|
{
|
|
track_module->index_offset = STREAM_POSITION(p_ctx);
|
|
track_module->index_size = chunk_size;
|
|
}
|
|
else
|
|
{
|
|
/* Not interested in this chunk, skip it. */
|
|
}
|
|
|
|
/* Skip any left-over data */
|
|
AVI_SKIP_CHUNK(p_ctx, offset + chunk_size + 8 - STREAM_POSITION(p_ctx) );
|
|
}
|
|
|
|
if (!stream_header_chunk_read || !stream_format_chunk_read)
|
|
{
|
|
LOG_DEBUG(p_ctx, "invalid 'strl', 'strh' and 'strf' are both required");
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static VC_CONTAINER_STATUS_T avi_find_next_data_chunk(VC_CONTAINER_T *p_ctx, uint32_t *id, uint32_t *size)
|
|
{
|
|
VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
|
|
VC_CONTAINER_FOURCC_T chunk_id;
|
|
uint32_t chunk_size = 0;
|
|
uint32_t peek_buf[1];
|
|
|
|
do
|
|
{
|
|
chunk_id = READ_FOURCC(p_ctx, "Chunk ID");
|
|
chunk_size = READ_U32(p_ctx, "Chunk size");
|
|
if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS)
|
|
break;
|
|
|
|
/* Check if this is a 'rec ' or a 'movi' LIST instead of a plain data chunk */
|
|
if(chunk_id == VC_FOURCC('L','I','S','T'))
|
|
{
|
|
if (PEEK_BYTES(p_ctx, (uint8_t*)peek_buf, 4) != 4)
|
|
return VC_CONTAINER_ERROR_EOS;
|
|
if (peek_buf[0] == VC_FOURCC('r','e','c',' '))
|
|
SKIP_FOURCC(p_ctx, "rec ");
|
|
else if (peek_buf[0] == VC_FOURCC('m','o','v','i'))
|
|
SKIP_FOURCC(p_ctx, "movi");
|
|
else
|
|
AVI_SKIP_CHUNK(p_ctx, chunk_size); /* Not interested in this LIST chunk, skip it. */
|
|
continue;
|
|
}
|
|
|
|
/* Check if this is a 'AVIX' RIFF header instead of a data chunk */
|
|
if(chunk_id == VC_FOURCC('R','I','F','F'))
|
|
{
|
|
if (PEEK_BYTES(p_ctx, (uint8_t*)peek_buf, 4) != 4)
|
|
return VC_CONTAINER_ERROR_EOS;
|
|
if (peek_buf[0] == VC_FOURCC('A','V','I','X'))
|
|
SKIP_FOURCC(p_ctx, "AVIX");
|
|
else
|
|
AVI_SKIP_CHUNK(p_ctx, chunk_size); /* Not interested in this RIFF header, skip it. */
|
|
continue;
|
|
}
|
|
|
|
/* We treat only db/dc/dd or wb chunks as data */
|
|
if((uint32_t)chunk_id >> 16 == AVI_TWOCC('d','c') ||
|
|
(uint32_t)chunk_id >> 16 == AVI_TWOCC('d','b') ||
|
|
(uint32_t)chunk_id >> 16 == AVI_TWOCC('d','d') ||
|
|
(uint32_t)chunk_id >> 16 == AVI_TWOCC('w','b'))
|
|
{
|
|
*id = chunk_id;
|
|
*size = chunk_size;
|
|
break;
|
|
}
|
|
|
|
/* Need to exit if a zero sized chunk encountered so we don't loop forever. */
|
|
if(chunk_size == 0 && chunk_id == 0) return VC_CONTAINER_ERROR_EOS;
|
|
|
|
/* Not interested in this chunk, skip it */
|
|
AVI_SKIP_CHUNK(p_ctx, chunk_size);
|
|
} while ((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS);
|
|
|
|
return status;
|
|
}
|
|
|
|
static void avi_track_from_chunk_id(VC_CONTAINER_FOURCC_T chunk_id, uint16_t *data_type, uint16_t *track_num)
|
|
{
|
|
*track_num = (((uint8_t*)&chunk_id)[0] - 48) * 10 + ((uint8_t*)&chunk_id)[1] - 48;
|
|
*data_type = (uint32_t)chunk_id >> 16;
|
|
}
|
|
|
|
static VC_CONTAINER_STATUS_T avi_check_track(VC_CONTAINER_T *p_ctx, uint16_t data_type, uint16_t track_num)
|
|
{
|
|
VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
|
|
|
|
if (track_num < p_ctx->tracks_num)
|
|
{
|
|
if (data_type == AVI_TWOCC('w','b') && p_ctx->tracks[track_num]->format->es_type != VC_CONTAINER_ES_TYPE_AUDIO)
|
|
{
|
|
LOG_DEBUG(p_ctx, "suspicious track type ('wb'), track %d is not an audio track", track_num);
|
|
status = VC_CONTAINER_ERROR_FAILED;
|
|
}
|
|
if (data_type == AVI_TWOCC('d','b') && p_ctx->tracks[track_num]->format->es_type != VC_CONTAINER_ES_TYPE_VIDEO)
|
|
{
|
|
LOG_DEBUG(p_ctx, "suspicious track type ('db'), track %d is not a video track", track_num);
|
|
status = VC_CONTAINER_ERROR_FAILED;
|
|
}
|
|
if (data_type == AVI_TWOCC('d','c') && p_ctx->tracks[track_num]->format->es_type != VC_CONTAINER_ES_TYPE_VIDEO)
|
|
{
|
|
LOG_DEBUG(p_ctx, "suspicious track type ('dc'), track %d is not a video track", track_num);
|
|
status = VC_CONTAINER_ERROR_FAILED;
|
|
}
|
|
if (data_type == AVI_TWOCC('d','d') && p_ctx->tracks[track_num]->format->es_type != VC_CONTAINER_ES_TYPE_VIDEO)
|
|
{
|
|
LOG_DEBUG(p_ctx, "suspicious track type ('dd'), track %d is not a video track", track_num);
|
|
status = VC_CONTAINER_ERROR_FAILED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG(p_ctx, "invalid track number %d (no more than %d tracks expected)",
|
|
track_num, p_ctx->tracks_num);
|
|
status = VC_CONTAINER_ERROR_FAILED;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static int avi_compare_seek_time(int64_t chunk_time, int64_t seek_time,
|
|
int chunk_is_keyframe, VC_CONTAINER_SEEK_FLAGS_T seek_flags)
|
|
{
|
|
if (chunk_time == seek_time && chunk_is_keyframe && !(seek_flags & VC_CONTAINER_SEEK_FLAG_FORWARD))
|
|
return 0;
|
|
|
|
if (chunk_time > seek_time && chunk_is_keyframe && (seek_flags & VC_CONTAINER_SEEK_FLAG_FORWARD))
|
|
return 0;
|
|
|
|
if (chunk_time > seek_time && !(seek_flags & VC_CONTAINER_SEEK_FLAG_FORWARD))
|
|
return 1; /* Chunk time is past seek time, caller should use the previous keyframe */
|
|
|
|
return -1;
|
|
}
|
|
|
|
static VC_CONTAINER_STATUS_T avi_scan_legacy_index_chunk(VC_CONTAINER_T *p_ctx, int seek_track_num,
|
|
int64_t *time, VC_CONTAINER_SEEK_FLAGS_T flags, uint64_t *pos)
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_STATUS_T status;
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module;
|
|
AVI_TRACK_CHUNK_STATE_T selected_chunk;
|
|
int64_t base_offset = module->data_offset;
|
|
int64_t selected_chunk_offset = base_offset + 4;
|
|
int32_t extra_offset = 0;
|
|
int first_chunk_offset = 1;
|
|
uint64_t position;
|
|
|
|
SEEK(p_ctx, module->index_offset);
|
|
memset(&selected_chunk, 0, sizeof(selected_chunk));
|
|
|
|
while((status = STREAM_STATUS(p_ctx)) == VC_CONTAINER_SUCCESS &&
|
|
(uint64_t)STREAM_POSITION(p_ctx) < module->index_offset + module->index_size)
|
|
{
|
|
VC_CONTAINER_FOURCC_T chunk_id;
|
|
uint16_t data_type, track_num;
|
|
uint32_t chunk_flags, offset, size;
|
|
|
|
chunk_id = READ_FOURCC(p_ctx, "Chunk ID");
|
|
chunk_flags = READ_U32(p_ctx, "dwFlags");
|
|
offset = READ_U32(p_ctx, "dwOffset");
|
|
size = READ_U32(p_ctx, "dwSize");
|
|
|
|
if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) break;
|
|
|
|
/* Although it's rare, the offsets might be given from the start of the file
|
|
instead of the data chunk, we have to handle both cases. */
|
|
if (first_chunk_offset)
|
|
{
|
|
if (offset > module->data_offset) base_offset = INT64_C(0);
|
|
selected_chunk_offset = base_offset + 4;
|
|
first_chunk_offset = 0;
|
|
}
|
|
|
|
avi_track_from_chunk_id(chunk_id, &data_type, &track_num);
|
|
LOG_DEBUG(p_ctx, "reading track %"PRIu16, track_num);
|
|
|
|
if (avi_check_track(p_ctx, data_type, track_num) != VC_CONTAINER_SUCCESS)
|
|
{
|
|
LOG_DEBUG(p_ctx, "skipping index entry for track %d/%d", track_num, p_ctx->tracks_num);
|
|
continue;
|
|
}
|
|
|
|
track_module = p_ctx->tracks[track_num]->priv->module;
|
|
|
|
if (data_type == AVI_TWOCC('d','d'))
|
|
{
|
|
if (track_num == seek_track_num)
|
|
track_module->chunk.flags |= VC_CONTAINER_PACKET_FLAG_ENCRYPTED;
|
|
extra_offset = -(size + 8);
|
|
}
|
|
|
|
/* If this entry does not affect timing, skip it */
|
|
if ((chunk_flags & (AVIIF_LIST | AVIIF_NOTIME)) || data_type == AVI_TWOCC('d','d'))
|
|
continue;
|
|
|
|
position = base_offset + offset + extra_offset;
|
|
extra_offset = INT64_C(0);
|
|
|
|
/* Check validity of position */
|
|
if (position <= module->data_offset /* || (*pos > module->data_offset + module->data_size*/)
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
|
|
if (track_num == seek_track_num)
|
|
{
|
|
bool is_keyframe = true;
|
|
int res;
|
|
|
|
if (p_ctx->tracks[track_num]->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO)
|
|
is_keyframe = chunk_flags & AVIIF_KEYFRAME;
|
|
|
|
if (is_keyframe)
|
|
track_module->chunk.flags |= VC_CONTAINER_PACKET_FLAG_KEYFRAME;
|
|
else
|
|
track_module->chunk.flags &= ~(VC_CONTAINER_PACKET_FLAG_KEYFRAME);
|
|
|
|
res = avi_compare_seek_time(track_module->chunk.time_pos, *time, is_keyframe, flags);
|
|
if (res > 0)
|
|
break; /* We've found the keyframe we wanted */
|
|
|
|
if (is_keyframe)
|
|
{
|
|
selected_chunk_offset = position;
|
|
selected_chunk = track_module->chunk;
|
|
}
|
|
|
|
if (res == 0)
|
|
break; /* We've found the keyframe we wanted */
|
|
|
|
track_module->chunk.index++;
|
|
track_module->chunk.offs += size;
|
|
track_module->chunk.time_pos = avi_calculate_chunk_time(track_module);
|
|
|
|
LOG_DEBUG(p_ctx, "index %"PRIu64", offs %"PRIu64", time %"PRIi64"us", track_module->chunk.index,
|
|
track_module->chunk.offs, track_module->chunk.time_pos);
|
|
}
|
|
}
|
|
|
|
if (status == VC_CONTAINER_SUCCESS ||
|
|
/* When seeking backwards, always return the last good position */
|
|
!(flags & VC_CONTAINER_SEEK_FLAG_FORWARD))
|
|
{
|
|
*pos = selected_chunk_offset;
|
|
track_module = p_ctx->tracks[seek_track_num]->priv->module;
|
|
track_module->chunk.index = selected_chunk.index;
|
|
track_module->chunk.offs = selected_chunk.offs;
|
|
track_module->chunk.flags = selected_chunk.flags;
|
|
track_module->chunk.time_pos = selected_chunk.time_pos;
|
|
*time = track_module->chunk.time_pos;
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
|
|
return VC_CONTAINER_ERROR_NOT_FOUND;
|
|
}
|
|
|
|
static VC_CONTAINER_STATUS_T avi_scan_standard_index_chunk(VC_CONTAINER_T *p_ctx, uint64_t index_offset,
|
|
unsigned seek_track_num, int64_t *time, VC_CONTAINER_SEEK_FLAGS_T flags, uint64_t *pos)
|
|
{
|
|
VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_NOT_FOUND;
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module = NULL;
|
|
VC_CONTAINER_FOURCC_T chunk_id;
|
|
uint32_t chunk_size;
|
|
uint16_t data_type, track_num;
|
|
uint8_t index_type, index_sub_type;
|
|
uint32_t entry, entry_count = 0;
|
|
uint16_t entry_size;
|
|
uint64_t base_offset = UINT64_C(0);
|
|
uint64_t position = UINT64_C(0);
|
|
uint64_t prev_keyframe_offs = INT64_C(0);
|
|
AVI_TRACK_CHUNK_STATE_T prev_keyframe_chunk = { 0 };
|
|
|
|
SEEK(p_ctx, index_offset);
|
|
|
|
chunk_id = READ_FOURCC(p_ctx, "Chunk ID");
|
|
chunk_size = READ_U32(p_ctx, "Chunk Size");
|
|
|
|
entry_size = READ_U16(p_ctx, "wLongsPerEntry");
|
|
index_sub_type = READ_U8(p_ctx, "bIndexSubType");
|
|
index_type = READ_U8(p_ctx, "bIndexType");
|
|
entry_count = READ_U32(p_ctx, "nEntriesInUse");
|
|
chunk_id = READ_FOURCC(p_ctx, "dwChunkId");
|
|
base_offset = READ_U64(p_ctx, "qwBaseOffset");
|
|
SKIP_U32(p_ctx, "dwReserved");
|
|
|
|
if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS)
|
|
return status;
|
|
|
|
avi_track_from_chunk_id(chunk_id, &data_type, &track_num);
|
|
status = avi_check_track(p_ctx, data_type, track_num);
|
|
if (status || chunk_size < 24 || track_num != seek_track_num)
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
|
|
if (entry_size != 2 || index_sub_type != 0 || index_type != AVI_INDEX_OF_CHUNKS)
|
|
return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
|
|
entry_count = MIN(entry_count, (chunk_size - 24) / (entry_size * 4));
|
|
|
|
track_module = p_ctx->tracks[seek_track_num]->priv->module;
|
|
|
|
for (entry = 0; entry < entry_count; ++entry)
|
|
{
|
|
uint32_t chunk_offset;
|
|
int key_frame = 0;
|
|
|
|
chunk_offset = READ_U32(p_ctx, "dwOffset");
|
|
chunk_size = READ_U32(p_ctx, "dwSize");
|
|
|
|
if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS)
|
|
break;
|
|
|
|
status = VC_CONTAINER_ERROR_NOT_FOUND;
|
|
|
|
if (!(chunk_size & AVI_INDEX_DELTAFRAME))
|
|
key_frame = 1;
|
|
chunk_size &= ~AVI_INDEX_DELTAFRAME;
|
|
|
|
position = base_offset + chunk_offset - 8;
|
|
|
|
if (key_frame)
|
|
track_module->chunk.flags = VC_CONTAINER_PACKET_FLAG_KEYFRAME;
|
|
else
|
|
track_module->chunk.flags = 0;
|
|
|
|
if (time != NULL)
|
|
{
|
|
int res;
|
|
status = VC_CONTAINER_ERROR_NOT_FOUND;
|
|
res = avi_compare_seek_time(track_module->chunk.time_pos, *time, key_frame, flags);
|
|
|
|
if (res == 0)
|
|
{
|
|
*pos = position;
|
|
*time = track_module->chunk.time_pos;
|
|
status = VC_CONTAINER_SUCCESS;
|
|
break;
|
|
}
|
|
else if (res > 0)
|
|
{
|
|
if (prev_keyframe_offs)
|
|
{
|
|
*pos = prev_keyframe_offs;
|
|
track_module->chunk = prev_keyframe_chunk;
|
|
*time = track_module->chunk.time_pos;
|
|
status = VC_CONTAINER_SUCCESS;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (key_frame)
|
|
{
|
|
prev_keyframe_offs = position;
|
|
prev_keyframe_chunk = track_module->chunk;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Not seeking to a time position, but scanning
|
|
track chunk state up to a certain file position
|
|
instead */
|
|
if (position >= *pos)
|
|
{
|
|
status = VC_CONTAINER_SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
track_module->chunk.index++;
|
|
track_module->chunk.offs += chunk_size;
|
|
track_module->chunk.time_pos = avi_calculate_chunk_time(track_module);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static VC_CONTAINER_STATUS_T avi_scan_super_index_chunk(VC_CONTAINER_T *p_ctx, unsigned index_track_num,
|
|
int64_t *time, VC_CONTAINER_SEEK_FLAGS_T flags, uint64_t *pos)
|
|
{
|
|
VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_NOT_FOUND;
|
|
VC_CONTAINER_FOURCC_T chunk_id;
|
|
uint64_t index_offset;
|
|
uint32_t index_size;
|
|
uint16_t data_type, track_num;
|
|
uint32_t entry, entry_count;
|
|
uint16_t entry_size;
|
|
uint8_t index_sub_type, index_type;
|
|
|
|
index_offset = p_ctx->tracks[index_track_num]->priv->module->index_offset;
|
|
index_size = p_ctx->tracks[index_track_num]->priv->module->index_size;
|
|
|
|
SEEK(p_ctx, index_offset);
|
|
|
|
entry_size = READ_U16(p_ctx, "wLongsPerEntry");
|
|
index_sub_type = READ_U8(p_ctx, "bIndexSubType");
|
|
index_type = READ_U8(p_ctx, "bIndexType");
|
|
entry_count = READ_U32(p_ctx, "nEntriesInUse");
|
|
chunk_id = READ_FOURCC(p_ctx, "dwChunkId");
|
|
SKIP_U32(p_ctx, "dwReserved0");
|
|
SKIP_U32(p_ctx, "dwReserved1");
|
|
SKIP_U32(p_ctx, "dwReserved2");
|
|
|
|
if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS)
|
|
return status;
|
|
|
|
if (index_type == AVI_INDEX_OF_INDEXES)
|
|
{
|
|
avi_track_from_chunk_id(chunk_id, &data_type, &track_num);
|
|
status = avi_check_track(p_ctx, data_type, track_num);
|
|
if (status || index_size < 24 || track_num != index_track_num) return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
|
|
/* FIXME: We should probably support AVI_INDEX_2FIELD as well */
|
|
if (entry_size != 4 || index_sub_type != 0)
|
|
return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
|
|
entry_count = MIN(entry_count, (index_size - 24) / entry_size);
|
|
|
|
for (entry = 0; entry < entry_count; ++entry)
|
|
{
|
|
uint64_t entry_offset, standard_index_offset;
|
|
standard_index_offset = READ_U64(p_ctx, "qwOffset");
|
|
SKIP_U32(p_ctx, "dwSize");
|
|
SKIP_U32(p_ctx, "dwDuration");
|
|
|
|
if ((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS)
|
|
break;
|
|
|
|
if (standard_index_offset == UINT64_C(0))
|
|
{
|
|
status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED; /* Not plausible */
|
|
break;
|
|
}
|
|
|
|
entry_offset = STREAM_POSITION(p_ctx);
|
|
status = avi_scan_standard_index_chunk(p_ctx, standard_index_offset, index_track_num, time, flags, pos);
|
|
if (status != VC_CONTAINER_ERROR_NOT_FOUND) break;
|
|
SEEK(p_ctx, entry_offset); /* Move to next entry ('ix' chunk); */
|
|
}
|
|
}
|
|
else if (index_type == AVI_INDEX_OF_CHUNKS)
|
|
{
|
|
/* It seems we are dealing with a standard index instead... */
|
|
status = avi_scan_standard_index_chunk(p_ctx, index_offset, index_track_num, time, flags, pos);
|
|
}
|
|
else
|
|
{
|
|
status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static VC_CONTAINER_STATUS_T avi_read_dd_chunk( VC_CONTAINER_T *p_ctx,
|
|
AVI_TRACK_STREAM_STATE_T *p_state, uint16_t data_type, uint32_t chunk_size,
|
|
uint16_t track_num )
|
|
{
|
|
if (data_type == AVI_TWOCC('d','d'))
|
|
{
|
|
if (p_state->extra_chunk_data_len ||
|
|
chunk_size > sizeof(p_state->extra_chunk_data))
|
|
{
|
|
LOG_DEBUG(p_ctx, "cannot handle multiple consecutive 'dd' chunks");
|
|
return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
}
|
|
if(READ_BYTES(p_ctx, p_state->extra_chunk_data, chunk_size) != chunk_size)
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
|
|
AVI_SYNC_CHUNK(p_ctx);
|
|
p_state->extra_chunk_track_num = track_num;
|
|
p_state->extra_chunk_data_len = chunk_size;
|
|
p_state->extra_chunk_data_offs = 0;
|
|
|
|
return VC_CONTAINER_ERROR_CONTINUE;
|
|
}
|
|
else if (p_state->extra_chunk_data_len &&
|
|
p_state->extra_chunk_track_num != track_num)
|
|
{
|
|
LOG_DEBUG(p_ctx, "dropping data from '%02ddd' chunk, not for this track (%d)",
|
|
p_state->extra_chunk_track_num, track_num);
|
|
p_state->extra_chunk_data_len = 0;
|
|
}
|
|
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Functions exported as part of the Container Module API
|
|
*****************************************************************************/
|
|
|
|
static VC_CONTAINER_STATUS_T avi_reader_read( VC_CONTAINER_T *p_ctx,
|
|
VC_CONTAINER_PACKET_T *p_packet, uint32_t flags )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module = NULL;
|
|
VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
|
|
AVI_TRACK_STREAM_STATE_T *p_state = &module->state;
|
|
|
|
if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK)
|
|
{
|
|
p_state = p_ctx->tracks[p_packet->track]->priv->module->chunk.state;
|
|
}
|
|
|
|
LOG_DEBUG(p_ctx, "seeking to %"PRIi64, p_state->data_offset);
|
|
SEEK(p_ctx, p_state->data_offset);
|
|
|
|
if (p_state->chunk_data_left == 0)
|
|
{
|
|
VC_CONTAINER_FOURCC_T chunk_id;
|
|
uint32_t chunk_size;
|
|
uint16_t data_type, track_num;
|
|
|
|
if ((status = avi_find_next_data_chunk(p_ctx, &chunk_id, &chunk_size)) != VC_CONTAINER_SUCCESS)
|
|
{
|
|
LOG_DEBUG(p_ctx, "unable to find the next data chunk %d", status);
|
|
p_state->data_offset = STREAM_POSITION(p_ctx);
|
|
return status;
|
|
}
|
|
|
|
avi_track_from_chunk_id(chunk_id, &data_type, &track_num);
|
|
|
|
if (avi_check_track(p_ctx, data_type, track_num) != VC_CONTAINER_SUCCESS)
|
|
{
|
|
AVI_SKIP_CHUNK(p_ctx, chunk_size);
|
|
LOG_DEBUG(p_ctx, "skipping data for track %d/%d", track_num, p_ctx->tracks_num);
|
|
|
|
p_state->data_offset = STREAM_POSITION(p_ctx);
|
|
return VC_CONTAINER_ERROR_CONTINUE;
|
|
}
|
|
|
|
/* If we are reading from the global state (i.e. normal read or forced
|
|
read from the track on the global state), and the track we found is
|
|
not on the global state, connect the two */
|
|
if (p_state == &module->state &&
|
|
p_ctx->tracks[track_num]->priv->module->chunk.state != &module->state)
|
|
{
|
|
int64_t next_chunk;
|
|
|
|
/* The track's offset is past the current position, skip it as we are
|
|
not interested in track data from before the track's offset. If we
|
|
were to read it we would return the same data multiple times. */
|
|
next_chunk = (STREAM_POSITION(p_ctx) + chunk_size + 1) & ~1;
|
|
if (p_ctx->tracks[track_num]->priv->module->chunk.state->data_offset > next_chunk)
|
|
{
|
|
AVI_SKIP_CHUNK(p_ctx, chunk_size);
|
|
LOG_DEBUG(p_ctx, "skipping track %d/%d as we have already read it", track_num, p_ctx->tracks_num);
|
|
p_state->data_offset = STREAM_POSITION(p_ctx);
|
|
return VC_CONTAINER_ERROR_CONTINUE;
|
|
}
|
|
|
|
/* The track state must be pointing to the current chunk. We need to
|
|
reconnect the track to the global state. */
|
|
LOG_DEBUG(p_ctx, "reconnect track %u to the global state", track_num);
|
|
|
|
p_ctx->tracks[track_num]->priv->module->chunk.state = &module->state;
|
|
|
|
module->state = p_ctx->tracks[track_num]->priv->module->chunk.local_state;
|
|
|
|
vc_container_assert(chunk_size >= p_state->chunk_data_left);
|
|
vc_container_assert(!p_state->chunk_data_left ||
|
|
((p_state->data_offset + p_state->chunk_data_left + 1) & ~1) == next_chunk);
|
|
vc_container_assert(p_state->current_track_num == track_num);
|
|
|
|
return VC_CONTAINER_ERROR_CONTINUE;
|
|
}
|
|
|
|
/* If we are not forcing, or if we are and found the track we are
|
|
interested in, check for dd data and set the track module for the later code */
|
|
if (!(flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK) ||
|
|
(track_num == p_packet->track))
|
|
{
|
|
if ((status = avi_read_dd_chunk(p_ctx, p_state, data_type, chunk_size, track_num)) != VC_CONTAINER_SUCCESS)
|
|
{
|
|
p_state->data_offset = STREAM_POSITION(p_ctx);
|
|
return status;
|
|
}
|
|
}
|
|
|
|
p_state->chunk_size = p_state->chunk_data_left = chunk_size;
|
|
p_state->current_track_num = track_num;
|
|
}
|
|
|
|
/* If there is data from another track skip past it */
|
|
if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK &&
|
|
p_state->current_track_num != p_packet->track)
|
|
{
|
|
p_state->data_offset = STREAM_POSITION(p_ctx);
|
|
|
|
AVI_SKIP_CHUNK(p_ctx, p_state->chunk_data_left);
|
|
LOG_DEBUG(p_ctx, "skipping track %d/%d as we are ignoring it",
|
|
p_state->current_track_num, p_ctx->tracks_num);
|
|
|
|
track_module = p_ctx->tracks[p_packet->track]->priv->module;
|
|
|
|
/* Handle disconnection from global state */
|
|
if (p_state == &module->state &&
|
|
p_ctx->tracks[p_state->current_track_num]->priv->module->chunk.state == &module->state)
|
|
{
|
|
/* Make a copy of the global state */
|
|
LOG_DEBUG(p_ctx, "using local state on track %d", p_packet->track);
|
|
track_module->chunk.local_state = module->state;
|
|
track_module->chunk.state = &track_module->chunk.local_state;
|
|
}
|
|
|
|
track_module->chunk.state->data_offset = STREAM_POSITION(p_ctx);
|
|
track_module->chunk.state->chunk_data_left = 0;
|
|
|
|
return VC_CONTAINER_ERROR_CONTINUE;
|
|
}
|
|
|
|
track_module = p_ctx->tracks[p_state->current_track_num]->priv->module;
|
|
|
|
if (flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK)
|
|
{
|
|
vc_container_assert(p_state->current_track_num == p_packet->track);
|
|
}
|
|
|
|
LOG_DEBUG(p_ctx, "reading track %u chunk at time %"PRIi64"us, offset %"PRIu64,
|
|
p_state->current_track_num, track_module->chunk.time_pos,
|
|
track_module->chunk.offs);
|
|
if (p_state->extra_chunk_data_len)
|
|
track_module->chunk.flags |= VC_CONTAINER_PACKET_FLAG_ENCRYPTED;
|
|
else
|
|
track_module->chunk.flags &= ~VC_CONTAINER_PACKET_FLAG_ENCRYPTED;
|
|
|
|
if (p_packet)
|
|
{
|
|
p_packet->track = p_state->current_track_num;
|
|
p_packet->size = p_state->chunk_data_left +
|
|
p_state->extra_chunk_data_len;
|
|
p_packet->flags = track_module->chunk.flags;
|
|
|
|
if (p_state->chunk_data_left == p_state->chunk_size)
|
|
{
|
|
p_packet->pts = track_module->chunk.time_pos;
|
|
if (track_module->sample_size == 0)
|
|
p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME;
|
|
}
|
|
else
|
|
{
|
|
p_packet->pts = VC_CONTAINER_TIME_UNKNOWN;
|
|
if (track_module->sample_size == 0)
|
|
p_packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_END;
|
|
}
|
|
|
|
p_packet->dts = VC_CONTAINER_TIME_UNKNOWN;
|
|
}
|
|
|
|
if (flags & VC_CONTAINER_READ_FLAG_SKIP)
|
|
{
|
|
SKIP_BYTES(p_ctx, p_state->chunk_data_left);
|
|
AVI_SYNC_CHUNK(p_ctx);
|
|
p_state->chunk_data_left = 0;
|
|
p_state->extra_chunk_data_len = 0;
|
|
}
|
|
|
|
if (flags & VC_CONTAINER_READ_FLAG_INFO)
|
|
{
|
|
p_state->data_offset = STREAM_POSITION(p_ctx);
|
|
|
|
LOG_DEBUG(p_ctx, "data position %"PRIi64, p_state->data_offset);
|
|
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
|
|
if (p_packet)
|
|
{
|
|
uint8_t *data = p_packet->data;
|
|
uint32_t buffer_size = p_packet->buffer_size;
|
|
uint32_t size = 0;
|
|
uint32_t len;
|
|
|
|
/* See if we need to insert extra data */
|
|
if (p_state->extra_chunk_data_len)
|
|
{
|
|
len = MIN(buffer_size, p_state->extra_chunk_data_len);
|
|
memcpy(data, p_state->extra_chunk_data + p_state->extra_chunk_data_offs, len);
|
|
data += len;
|
|
buffer_size -= len;
|
|
size = len;
|
|
p_state->extra_chunk_data_len -= len;
|
|
p_state->extra_chunk_data_offs += len;
|
|
}
|
|
|
|
/* Now try to read data into buffer */
|
|
len = MIN(buffer_size, p_state->chunk_data_left);
|
|
READ_BYTES(p_ctx, data, len);
|
|
size += len;
|
|
p_state->chunk_data_left -= len;
|
|
p_packet->size = size;
|
|
|
|
if (p_state->chunk_data_left)
|
|
p_packet->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_END;
|
|
}
|
|
|
|
if (p_state->chunk_data_left == 0)
|
|
{
|
|
AVI_SYNC_CHUNK(p_ctx);
|
|
track_module->chunk.index++;
|
|
track_module->chunk.offs += p_state->chunk_size;
|
|
track_module->chunk.flags = 0;
|
|
track_module->chunk.time_pos = avi_calculate_chunk_time(track_module);
|
|
}
|
|
|
|
/* Update the track's position */
|
|
p_state->data_offset = STREAM_POSITION(p_ctx);
|
|
|
|
LOG_DEBUG(p_ctx, "data position %"PRIi64, p_state->data_offset);
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T avi_reader_seek( VC_CONTAINER_T *p_ctx, int64_t *p_offset,
|
|
VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags)
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_STATUS_T status;
|
|
uint64_t position, pos;
|
|
AVI_TRACK_CHUNK_STATE_T chunk_state[AVI_TRACKS_MAX];
|
|
AVI_TRACK_STREAM_STATE_T global_state;
|
|
unsigned seek_track_num, i;
|
|
|
|
if (mode != VC_CONTAINER_SEEK_MODE_TIME || !STREAM_SEEKABLE(p_ctx))
|
|
return VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION;
|
|
|
|
LOG_DEBUG(p_ctx, "AVI seeking to %"PRIi64"us", *p_offset);
|
|
|
|
/* Save current position and chunk state so we can restore it if we
|
|
hit an error whilst scanning index data */
|
|
position = STREAM_POSITION(p_ctx);
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
chunk_state[i] = p_ctx->tracks[i]->priv->module->chunk;
|
|
global_state = p_ctx->priv->module->state;
|
|
|
|
/* Clear track state affected by a seek operation of any kind */
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
{
|
|
p_ctx->tracks[i]->priv->module->chunk.index = INT64_C(0);
|
|
p_ctx->tracks[i]->priv->module->chunk.offs = INT64_C(0);
|
|
p_ctx->tracks[i]->priv->module->chunk.flags = 0;
|
|
p_ctx->tracks[i]->priv->module->chunk.time_pos = p_ctx->tracks[i]->priv->module->time_start;
|
|
p_ctx->tracks[i]->priv->module->chunk.state = &p_ctx->tracks[i]->priv->module->chunk.local_state;
|
|
p_ctx->tracks[i]->priv->module->chunk.local_state.chunk_data_left = UINT64_C(0);
|
|
p_ctx->tracks[i]->priv->module->chunk.local_state.chunk_size = UINT64_C(0);
|
|
p_ctx->tracks[i]->priv->module->chunk.local_state.extra_chunk_data_len = 0;
|
|
p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset = module->data_offset + 4;
|
|
}
|
|
|
|
/* Clear the global state */
|
|
p_ctx->priv->module->state.chunk_data_left = UINT64_C(0);
|
|
p_ctx->priv->module->state.chunk_size = UINT64_C(0);
|
|
p_ctx->priv->module->state.extra_chunk_data_len = 0;
|
|
p_ctx->priv->module->state.data_offset = module->data_offset + 4;
|
|
|
|
/* Choose track to use for seeking, favor video tracks and tracks
|
|
that are enabled */
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
if(p_ctx->tracks[i]->is_enabled &&
|
|
p_ctx->tracks[i]->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) break;
|
|
if(i == p_ctx->tracks_num)
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
if(p_ctx->tracks[i]->is_enabled) break;
|
|
if(i == p_ctx->tracks_num) i = 0;
|
|
|
|
LOG_DEBUG(p_ctx, "seek on track %d/%d", i, p_ctx->tracks_num);
|
|
seek_track_num = i;
|
|
|
|
if (p_ctx->tracks[seek_track_num]->priv->module->index_offset)
|
|
{
|
|
LOG_DEBUG(p_ctx, "seeking using the super index");
|
|
status = avi_scan_super_index_chunk(p_ctx, seek_track_num, p_offset, flags, &pos);
|
|
if (status != VC_CONTAINER_SUCCESS) goto error;
|
|
|
|
/* As AVI chunks don't convey timestamp information, we need to scan all tracks
|
|
to the seek file position */
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
{
|
|
if (p_ctx->tracks[i]->priv->module->index_offset && i != seek_track_num)
|
|
{
|
|
uint64_t track_pos;
|
|
int64_t track_time = *p_offset;
|
|
|
|
status = avi_scan_super_index_chunk(p_ctx, i, &track_time, flags, &track_pos);
|
|
if (status != VC_CONTAINER_SUCCESS) goto error;
|
|
p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset = track_pos;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG(p_ctx, "seeking using the legacy index");
|
|
|
|
/* The legacy index comes after data so it might not have been available at the
|
|
time the container was opened; if this is the case, see if we can find an index
|
|
now, if we can't, then there's no way we can proceed with the seek. */
|
|
if(!module->index_offset)
|
|
{
|
|
uint32_t chunk_size;
|
|
|
|
LOG_DEBUG(p_ctx, "no index offset, searching for one");
|
|
|
|
/* Locate data chunk and skip it */
|
|
SEEK(p_ctx, module->data_offset);
|
|
AVI_SKIP_CHUNK(p_ctx, module->data_size);
|
|
/* Now search for the index */
|
|
status = avi_find_chunk(p_ctx, VC_FOURCC('i','d','x','1'), &chunk_size);
|
|
if (status == VC_CONTAINER_SUCCESS)
|
|
{
|
|
/* Store offset to index data */
|
|
module->index_offset = STREAM_POSITION(p_ctx);
|
|
module->index_size = chunk_size;
|
|
p_ctx->capabilities |= VC_CONTAINER_CAPS_HAS_INDEX;
|
|
p_ctx->capabilities |= VC_CONTAINER_CAPS_DATA_HAS_KEYFRAME_FLAG;
|
|
}
|
|
}
|
|
/* Check again, we may or may not have an index */
|
|
if (!module->index_offset)
|
|
{
|
|
/* If there is no index and we are seeking to 0 we can assume the
|
|
correct location is the start of the data. Otherwise we are unable
|
|
to seek to a specified non-zero location without an index */
|
|
if (*p_offset != INT64_C(0))
|
|
{
|
|
LOG_DEBUG(p_ctx, "failed to find the legacy index, unable to seek");
|
|
status = VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION;
|
|
goto error;
|
|
}
|
|
pos = module->data_offset;
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG(p_ctx, "scanning the legacy index chunk");
|
|
status = avi_scan_legacy_index_chunk(p_ctx, seek_track_num, p_offset, flags, &pos);
|
|
if (status != VC_CONTAINER_SUCCESS) goto error;
|
|
|
|
for (i = 0; i < p_ctx->tracks_num; i++)
|
|
{
|
|
if (i != seek_track_num)
|
|
{
|
|
uint64_t track_pos = pos;
|
|
int64_t track_time = *p_offset;
|
|
|
|
status = avi_scan_legacy_index_chunk(p_ctx, i, &track_time, flags, &track_pos);
|
|
if (status != VC_CONTAINER_SUCCESS) goto error;
|
|
p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset = track_pos;
|
|
p_ctx->tracks[i]->priv->module->chunk.local_state.current_track_num = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
position = pos;
|
|
|
|
/* Set the seek track's data offset */
|
|
p_ctx->tracks[seek_track_num]->priv->module->chunk.local_state.data_offset = position;
|
|
p_ctx->tracks[seek_track_num]->priv->module->chunk.local_state.current_track_num = seek_track_num;
|
|
|
|
/* Connect the earlier track(s) to the global state. Needs 2 passes */
|
|
module->state.data_offset = INT64_MAX;
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
{
|
|
if(p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset <
|
|
module->state.data_offset)
|
|
module->state = p_ctx->tracks[i]->priv->module->chunk.local_state;
|
|
}
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
{
|
|
if(p_ctx->tracks[i]->priv->module->chunk.local_state.data_offset ==
|
|
module->state.data_offset)
|
|
p_ctx->tracks[i]->priv->module->chunk.state = &module->state;
|
|
}
|
|
|
|
LOG_DEBUG(p_ctx, "seek to %"PRIi64", position %"PRIu64, *p_offset, pos);
|
|
|
|
return SEEK(p_ctx, position);
|
|
|
|
error:
|
|
p_ctx->priv->module->state = global_state;
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
p_ctx->tracks[i]->priv->module->chunk = chunk_state[i];
|
|
SEEK(p_ctx, position);
|
|
return status;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T avi_reader_close( VC_CONTAINER_T *p_ctx )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
unsigned int i;
|
|
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
vc_container_free_track(p_ctx, p_ctx->tracks[i]);
|
|
p_ctx->tracks = NULL;
|
|
p_ctx->tracks_num = 0;
|
|
free(module);
|
|
p_ctx->priv->module = 0;
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
VC_CONTAINER_STATUS_T avi_reader_open( VC_CONTAINER_T *p_ctx )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = 0;
|
|
VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
uint32_t chunk_size;
|
|
uint32_t peek_buf[3];
|
|
unsigned int i;
|
|
uint32_t flags, num_streams;
|
|
int64_t offset;
|
|
|
|
/* Check the RIFF chunk descriptor */
|
|
if (PEEK_BYTES(p_ctx, (uint8_t*)peek_buf, 12) != 12)
|
|
return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
if( peek_buf[0] != VC_FOURCC('R','I','F','F') )
|
|
return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
if( peek_buf[2] != VC_FOURCC('A','V','I',' ') )
|
|
return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
|
|
/*
|
|
* We now know we are dealing with an AVI file
|
|
*/
|
|
SKIP_FOURCC(p_ctx, "RIFF ID");
|
|
SKIP_U32(p_ctx, "fileSize");
|
|
SKIP_FOURCC(p_ctx, "fileType");
|
|
|
|
/* Look for the 'hdrl' LIST (sub)chunk */
|
|
status = avi_find_list(p_ctx, VC_FOURCC('h','d','r','l'), &chunk_size);
|
|
if (status != VC_CONTAINER_SUCCESS)
|
|
{
|
|
LOG_DEBUG(p_ctx, "'hdrl' LIST not found");
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
}
|
|
|
|
SKIP_FOURCC(p_ctx, "hdrl");
|
|
|
|
/* Now look for the 'avih' sub-chunk */
|
|
status = avi_find_chunk(p_ctx, VC_FOURCC('a','v','i','h'), &chunk_size);
|
|
if (status != VC_CONTAINER_SUCCESS)
|
|
{
|
|
LOG_DEBUG(p_ctx, "'avih' not found");
|
|
return VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
}
|
|
|
|
/* Parse the 'avih' sub-chunk */
|
|
SKIP_U32(p_ctx, "dwMicroSecPerFrame");
|
|
SKIP_U32(p_ctx, "dwMaxBytesPerSec");
|
|
SKIP_U32(p_ctx, "dwPaddingGranularity");
|
|
flags = READ_U32(p_ctx, "dwFlags");
|
|
SKIP_U32(p_ctx, "dwTotalFrames");
|
|
SKIP_U32(p_ctx, "dwInitialFrames");
|
|
num_streams = READ_U32(p_ctx, "dwStreams");
|
|
SKIP_U32(p_ctx, "dwSuggestedBufferSize");
|
|
SKIP_U32(p_ctx, "dwWidth");
|
|
SKIP_U32(p_ctx, "dwHeight");
|
|
SKIP_U32(p_ctx, "dwReserved0");
|
|
SKIP_U32(p_ctx, "dwReserved1");
|
|
SKIP_U32(p_ctx, "dwReserved2");
|
|
SKIP_U32(p_ctx, "dwReserved3");
|
|
|
|
if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS)
|
|
goto error;
|
|
|
|
/* Allocate our context and tracks */
|
|
if ((module = malloc(sizeof(*module))) == NULL)
|
|
return VC_CONTAINER_ERROR_OUT_OF_MEMORY;
|
|
memset(module, 0, sizeof(*module));
|
|
p_ctx->priv->module = module;
|
|
p_ctx->tracks = module->tracks;
|
|
|
|
if (num_streams > AVI_TRACKS_MAX)
|
|
{
|
|
LOG_DEBUG(p_ctx, "cannot handle %u tracks, restricted to %d", num_streams, AVI_TRACKS_MAX);
|
|
num_streams = AVI_TRACKS_MAX;
|
|
}
|
|
|
|
for (p_ctx->tracks_num = 0; p_ctx->tracks_num != num_streams; p_ctx->tracks_num++)
|
|
{
|
|
p_ctx->tracks[p_ctx->tracks_num] = vc_container_allocate_track(p_ctx, sizeof(*p_ctx->tracks[0]->priv->module));
|
|
if(!p_ctx->tracks[p_ctx->tracks_num]) break;
|
|
}
|
|
if(p_ctx->tracks_num != num_streams)
|
|
{ status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; }
|
|
|
|
/* Try to read stream header list chunks ('strl') */
|
|
for (i = 0; i != num_streams; ++i)
|
|
{
|
|
status = avi_read_stream_header_list(p_ctx, p_ctx->tracks[i], p_ctx->tracks[i]->priv->module);
|
|
if(status != VC_CONTAINER_SUCCESS) goto error;
|
|
}
|
|
|
|
/* Look for the 'movi' LIST (sub)chunk */
|
|
status = avi_find_list(p_ctx, VC_FOURCC('m','o','v','i'), &chunk_size);
|
|
if (status != VC_CONTAINER_SUCCESS)
|
|
{
|
|
LOG_DEBUG(p_ctx, "'movi' LIST not found");
|
|
status = VC_CONTAINER_ERROR_FORMAT_INVALID;
|
|
goto error;
|
|
}
|
|
|
|
/* Store offset to the start and size of data (the 'movi' LIST) */
|
|
module->data_offset = STREAM_POSITION(p_ctx);
|
|
module->data_size = chunk_size;
|
|
|
|
p_ctx->priv->pf_close = avi_reader_close;
|
|
p_ctx->priv->pf_read = avi_reader_read;
|
|
p_ctx->priv->pf_seek = avi_reader_seek;
|
|
|
|
if (flags & AVIF_MUSTUSEINDEX)
|
|
{
|
|
LOG_DEBUG(p_ctx, "AVIF_MUSTUSEINDEX not supported, playback might not work properly");
|
|
}
|
|
|
|
/* If the stream is seekable, see if we can find an index (for at
|
|
least one of the tracks); even if we cannot find an index now,
|
|
one might become available later (e.g. when the stream grows
|
|
run-time), in that case we might want to report that we can seek
|
|
and re-search for the index again if or when we're requested to
|
|
seek. */
|
|
if(STREAM_SEEKABLE(p_ctx))
|
|
{
|
|
p_ctx->capabilities |= VC_CONTAINER_CAPS_CAN_SEEK;
|
|
p_ctx->capabilities |= VC_CONTAINER_CAPS_FORCE_TRACK;
|
|
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
if(p_ctx->tracks[i]->priv->module->index_offset) break;
|
|
|
|
if (i < p_ctx->tracks_num)
|
|
{
|
|
p_ctx->capabilities |= VC_CONTAINER_CAPS_HAS_INDEX;
|
|
if (flags & AVIF_TRUSTCKTYPE)
|
|
p_ctx->capabilities |= VC_CONTAINER_CAPS_DATA_HAS_KEYFRAME_FLAG;
|
|
}
|
|
else
|
|
{
|
|
/* Skip data first */
|
|
AVI_SKIP_CHUNK(p_ctx, module->data_size);
|
|
/* Now search for the index */
|
|
status = avi_find_chunk(p_ctx, VC_FOURCC('i','d','x','1'), &chunk_size);
|
|
if (status == VC_CONTAINER_SUCCESS)
|
|
{
|
|
LOG_DEBUG(p_ctx, "'idx1' found");
|
|
/* Store offset to index data */
|
|
module->index_offset = STREAM_POSITION(p_ctx);
|
|
module->index_size = chunk_size;
|
|
p_ctx->capabilities |= VC_CONTAINER_CAPS_HAS_INDEX;
|
|
p_ctx->capabilities |= VC_CONTAINER_CAPS_DATA_HAS_KEYFRAME_FLAG;
|
|
}
|
|
|
|
/* Seek back to the start of the data */
|
|
SEEK(p_ctx, module->data_offset);
|
|
}
|
|
}
|
|
|
|
SKIP_FOURCC(p_ctx, "movi");
|
|
|
|
for (i = 0; i != num_streams; i++)
|
|
{
|
|
p_ctx->tracks[i]->priv->module->chunk.state = &p_ctx->priv->module->state;
|
|
}
|
|
p_ctx->priv->module->state.data_offset = STREAM_POSITION(p_ctx);
|
|
|
|
/* Update the tracks to set their data offsets. This help with bad
|
|
interleaving, for example when there is all the video tracks followed
|
|
by all the audio tracks. It means we don't have to read through the
|
|
tracks we are not interested in when forcing a read from a given track,
|
|
as could be the case in the above example. If this fails we will fall
|
|
back to skipping track data. */
|
|
offset = INT64_C(0);
|
|
avi_reader_seek(p_ctx, &offset, VC_CONTAINER_SEEK_MODE_TIME, VC_CONTAINER_SEEK_FLAG_PRECISE);
|
|
|
|
if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) goto error;
|
|
return VC_CONTAINER_SUCCESS;
|
|
|
|
error:
|
|
LOG_DEBUG(p_ctx, "error opening stream (%i)", status);
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
vc_container_free_track(p_ctx, p_ctx->tracks[i]);
|
|
p_ctx->tracks = NULL;
|
|
p_ctx->tracks_num = 0;
|
|
if (module) free(module);
|
|
p_ctx->priv->module = NULL;
|
|
return status;
|
|
}
|
|
|
|
/********************************************************************************
|
|
Entrypoint function
|
|
********************************************************************************/
|
|
|
|
#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__)
|
|
# pragma weak reader_open avi_reader_open
|
|
#endif
|