mirror of
https://github.com/libretro/RetroArch
synced 2025-02-01 00:32:46 +00:00
1880 lines
68 KiB
C
1880 lines
68 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>
|
|
|
|
/* Work-around for MSVC debugger issue */
|
|
#define VC_CONTAINER_MODULE_T VC_CONTAINER_MODULE_MP4_READER_T
|
|
#define VC_CONTAINER_TRACK_MODULE_T VC_CONTAINER_TRACK_MODULE_MP4_READER_T
|
|
|
|
#define CONTAINER_IS_BIG_ENDIAN
|
|
#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/mp4/mp4_common.h"
|
|
#undef CONTAINER_HELPER_LOG_INDENT
|
|
#define CONTAINER_HELPER_LOG_INDENT(a) (a)->priv->module->box_level
|
|
|
|
VC_CONTAINER_STATUS_T mp4_reader_open( VC_CONTAINER_T *p_ctx );
|
|
|
|
/******************************************************************************
|
|
TODO:
|
|
- aspect ratio
|
|
- itunes gapless
|
|
- edit list
|
|
- subpicture track
|
|
******************************************************************************/
|
|
|
|
/******************************************************************************
|
|
Defines.
|
|
******************************************************************************/
|
|
#define MP4_TRACKS_MAX 16
|
|
|
|
#define MP4_BOX_MIN_HEADER_SIZE 8
|
|
#define MP4_MAX_BOX_SIZE (1<<29) /* Does not apply to the mdat box */
|
|
#define MP4_MAX_BOX_LEVEL 10
|
|
|
|
#define MP4_MAX_SAMPLES_BATCH_SIZE (16*1024)
|
|
|
|
#define MP4_SKIP_U8(ctx,n) (size -= 1, SKIP_U8(ctx,n))
|
|
#define MP4_SKIP_U16(ctx,n) (size -= 2, SKIP_U16(ctx,n))
|
|
#define MP4_SKIP_U24(ctx,n) (size -= 3, SKIP_U24(ctx,n))
|
|
#define MP4_SKIP_U32(ctx,n) (size -= 4, SKIP_U32(ctx,n))
|
|
#define MP4_SKIP_U64(ctx,n) (size -= 8, SKIP_U64(ctx,n))
|
|
#define MP4_READ_U8(ctx,n) (size -= 1, READ_U8(ctx,n))
|
|
#define MP4_READ_U16(ctx,n) (size -= 2, READ_U16(ctx,n))
|
|
#define MP4_READ_U24(ctx,n) (size -= 3, READ_U24(ctx,n))
|
|
#define MP4_READ_U32(ctx,n) (size -= 4, READ_U32(ctx,n))
|
|
#define MP4_READ_U64(ctx,n) (size -= 8, READ_U64(ctx,n))
|
|
#define MP4_READ_FOURCC(ctx,n) (size -= 4, READ_FOURCC(ctx,n))
|
|
#define MP4_SKIP_FOURCC(ctx,n) (size -= 4, SKIP_FOURCC(ctx,n))
|
|
#define MP4_READ_BYTES(ctx,buffer,sz) (size -= sz, READ_BYTES(ctx,buffer,sz))
|
|
#define MP4_SKIP_BYTES(ctx,sz) (size -= sz, SKIP_BYTES(ctx,sz))
|
|
#define MP4_SKIP_STRING(ctx,sz,n) (size -= sz, SKIP_STRING(ctx,sz,n))
|
|
|
|
/******************************************************************************
|
|
Type definitions.
|
|
******************************************************************************/
|
|
typedef struct
|
|
{
|
|
VC_CONTAINER_STATUS_T status;
|
|
|
|
int64_t duration;
|
|
int64_t pts;
|
|
int64_t dts;
|
|
|
|
uint32_t sample;
|
|
int64_t offset;
|
|
unsigned int sample_offset;
|
|
unsigned int sample_size;
|
|
|
|
uint32_t sample_duration;
|
|
uint32_t sample_duration_count;
|
|
int32_t sample_composition_offset;
|
|
uint32_t sample_composition_count;
|
|
|
|
uint32_t next_sync_sample;
|
|
bool keyframe;
|
|
|
|
uint32_t samples_per_chunk;
|
|
uint32_t chunks;
|
|
uint32_t samples_in_chunk;
|
|
|
|
struct {
|
|
uint32_t entry;
|
|
} sample_table[MP4_SAMPLE_TABLE_NUM];
|
|
|
|
} MP4_READER_STATE_T;
|
|
|
|
typedef struct VC_CONTAINER_TRACK_MODULE_T
|
|
{
|
|
MP4_READER_STATE_T state;
|
|
|
|
int64_t timescale;
|
|
uint8_t object_type_indication;
|
|
|
|
uint32_t sample_size;
|
|
struct {
|
|
int64_t offset;
|
|
uint32_t entries;
|
|
uint32_t entry_size;
|
|
} sample_table[MP4_SAMPLE_TABLE_NUM];
|
|
|
|
uint32_t samples_batch_size;
|
|
|
|
} VC_CONTAINER_TRACK_MODULE_T;
|
|
|
|
typedef struct VC_CONTAINER_MODULE_T
|
|
{
|
|
int64_t box_offset;
|
|
int box_level;
|
|
|
|
MP4_BRAND_T brand;
|
|
|
|
int64_t timescale;
|
|
|
|
VC_CONTAINER_TRACK_T *tracks[MP4_TRACKS_MAX];
|
|
unsigned int current_track;
|
|
|
|
bool found_moov;
|
|
int64_t data_offset;
|
|
int64_t data_size;
|
|
|
|
} VC_CONTAINER_MODULE_T;
|
|
|
|
/******************************************************************************
|
|
Static functions within this file.
|
|
******************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box( VC_CONTAINER_T *p_ctx, int64_t size, MP4_BOX_TYPE_T parent_type );
|
|
static VC_CONTAINER_STATUS_T mp4_read_boxes( VC_CONTAINER_T *p_ctx, int64_t size, MP4_BOX_TYPE_T type );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_ftyp( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_moov( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_mvhd( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_trak( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_tkhd( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_mdia( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_mdhd( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_hdlr( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_minf( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_vmhd( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_smhd( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_dinf( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_dref( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stbl( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stsd( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stts( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_ctts( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stsc( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stsz( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stco( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_co64( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stss( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_vide( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_soun( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_text( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_esds( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_vide_avcC( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_vide_d263( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_soun_damr( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_soun_dawp( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_soun_devc( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_soun_wave( VC_CONTAINER_T *p_ctx, int64_t size );
|
|
|
|
static struct {
|
|
const MP4_BOX_TYPE_T type;
|
|
VC_CONTAINER_STATUS_T (*pf_func)( VC_CONTAINER_T *, int64_t );
|
|
const MP4_BOX_TYPE_T parent_type;
|
|
} mp4_box_list[] =
|
|
{
|
|
{MP4_BOX_TYPE_FTYP, mp4_read_box_ftyp, MP4_BOX_TYPE_ROOT},
|
|
{MP4_BOX_TYPE_MDAT, 0, MP4_BOX_TYPE_ROOT},
|
|
{MP4_BOX_TYPE_MOOV, mp4_read_box_moov, MP4_BOX_TYPE_ROOT},
|
|
{MP4_BOX_TYPE_MVHD, mp4_read_box_mvhd, MP4_BOX_TYPE_MOOV},
|
|
{MP4_BOX_TYPE_TRAK, mp4_read_box_trak, MP4_BOX_TYPE_MOOV},
|
|
{MP4_BOX_TYPE_TKHD, mp4_read_box_tkhd, MP4_BOX_TYPE_TRAK},
|
|
{MP4_BOX_TYPE_MDIA, mp4_read_box_mdia, MP4_BOX_TYPE_TRAK},
|
|
{MP4_BOX_TYPE_MDHD, mp4_read_box_mdhd, MP4_BOX_TYPE_MDIA},
|
|
{MP4_BOX_TYPE_HDLR, mp4_read_box_hdlr, MP4_BOX_TYPE_MDIA},
|
|
{MP4_BOX_TYPE_MINF, mp4_read_box_minf, MP4_BOX_TYPE_MDIA},
|
|
{MP4_BOX_TYPE_VMHD, mp4_read_box_vmhd, MP4_BOX_TYPE_MINF},
|
|
{MP4_BOX_TYPE_SMHD, mp4_read_box_smhd, MP4_BOX_TYPE_MINF},
|
|
{MP4_BOX_TYPE_DINF, mp4_read_box_dinf, MP4_BOX_TYPE_MINF},
|
|
{MP4_BOX_TYPE_DREF, mp4_read_box_dref, MP4_BOX_TYPE_DINF},
|
|
{MP4_BOX_TYPE_STBL, mp4_read_box_stbl, MP4_BOX_TYPE_MINF},
|
|
{MP4_BOX_TYPE_STSD, mp4_read_box_stsd, MP4_BOX_TYPE_STBL},
|
|
{MP4_BOX_TYPE_STTS, mp4_read_box_stts, MP4_BOX_TYPE_STBL},
|
|
{MP4_BOX_TYPE_CTTS, mp4_read_box_ctts, MP4_BOX_TYPE_STBL},
|
|
{MP4_BOX_TYPE_STSC, mp4_read_box_stsc, MP4_BOX_TYPE_STBL},
|
|
{MP4_BOX_TYPE_STSZ, mp4_read_box_stsz, MP4_BOX_TYPE_STBL},
|
|
{MP4_BOX_TYPE_STCO, mp4_read_box_stco, MP4_BOX_TYPE_STBL},
|
|
{MP4_BOX_TYPE_CO64, mp4_read_box_co64, MP4_BOX_TYPE_STBL},
|
|
{MP4_BOX_TYPE_STSS, mp4_read_box_stss, MP4_BOX_TYPE_STBL},
|
|
{MP4_BOX_TYPE_VIDE, mp4_read_box_vide, MP4_BOX_TYPE_STSD},
|
|
{MP4_BOX_TYPE_SOUN, mp4_read_box_soun, MP4_BOX_TYPE_STSD},
|
|
{MP4_BOX_TYPE_TEXT, mp4_read_box_text, MP4_BOX_TYPE_STSD},
|
|
|
|
/* Codec specific boxes */
|
|
{MP4_BOX_TYPE_AVCC, mp4_read_box_vide_avcC, MP4_BOX_TYPE_VIDE},
|
|
{MP4_BOX_TYPE_D263, mp4_read_box_vide_d263, MP4_BOX_TYPE_VIDE},
|
|
{MP4_BOX_TYPE_ESDS, mp4_read_box_esds, MP4_BOX_TYPE_VIDE},
|
|
{MP4_BOX_TYPE_DAMR, mp4_read_box_soun_damr, MP4_BOX_TYPE_SOUN},
|
|
{MP4_BOX_TYPE_DAWP, mp4_read_box_soun_dawp, MP4_BOX_TYPE_SOUN},
|
|
{MP4_BOX_TYPE_DEVC, mp4_read_box_soun_devc, MP4_BOX_TYPE_SOUN},
|
|
{MP4_BOX_TYPE_WAVE, mp4_read_box_soun_wave, MP4_BOX_TYPE_SOUN},
|
|
{MP4_BOX_TYPE_ESDS, mp4_read_box_esds, MP4_BOX_TYPE_SOUN},
|
|
|
|
{MP4_BOX_TYPE_UNKNOWN, 0, MP4_BOX_TYPE_UNKNOWN}
|
|
};
|
|
|
|
static struct {
|
|
const VC_CONTAINER_FOURCC_T type;
|
|
const VC_CONTAINER_FOURCC_T codec;
|
|
bool batch;
|
|
} mp4_codec_mapping[] =
|
|
{
|
|
{VC_FOURCC('a','v','c','1'), VC_CONTAINER_CODEC_H264, 0},
|
|
{VC_FOURCC('m','p','4','v'), VC_CONTAINER_CODEC_MP4V, 0},
|
|
{VC_FOURCC('s','2','6','3'), VC_CONTAINER_CODEC_H263, 0},
|
|
{VC_FOURCC('m','p','e','g'), VC_CONTAINER_CODEC_MP2V, 0},
|
|
{VC_FOURCC('m','j','p','a'), VC_CONTAINER_CODEC_MJPEGA, 0},
|
|
{VC_FOURCC('m','j','p','b'), VC_CONTAINER_CODEC_MJPEGB, 0},
|
|
|
|
{VC_FOURCC('j','p','e','g'), VC_CONTAINER_CODEC_JPEG, 0},
|
|
|
|
{VC_FOURCC('m','p','4','a'), VC_CONTAINER_CODEC_MP4A, 0},
|
|
{VC_FOURCC('s','a','m','r'), VC_CONTAINER_CODEC_AMRNB, 0},
|
|
{VC_FOURCC('s','a','w','b'), VC_CONTAINER_CODEC_AMRWB, 0},
|
|
{VC_FOURCC('s','a','w','p'), VC_CONTAINER_CODEC_AMRWBP, 0},
|
|
{VC_FOURCC('a','c','-','3'), VC_CONTAINER_CODEC_AC3, 0},
|
|
{VC_FOURCC('e','c','-','3'), VC_CONTAINER_CODEC_EAC3, 0},
|
|
{VC_FOURCC('s','e','v','c'), VC_CONTAINER_CODEC_EVRC, 0},
|
|
{VC_FOURCC('e','v','r','c'), VC_CONTAINER_CODEC_EVRC, 0},
|
|
{VC_FOURCC('s','q','c','p'), VC_CONTAINER_CODEC_QCELP, 0},
|
|
{VC_FOURCC('a','l','a','w'), VC_CONTAINER_CODEC_ALAW, 1},
|
|
{VC_FOURCC('u','l','a','w'), VC_CONTAINER_CODEC_MULAW, 1},
|
|
{VC_FOURCC('t','w','o','s'), VC_CONTAINER_CODEC_PCM_SIGNED_BE, 1},
|
|
{VC_FOURCC('s','o','w','t'), VC_CONTAINER_CODEC_PCM_SIGNED_LE, 1},
|
|
|
|
{0, 0},
|
|
};
|
|
static VC_CONTAINER_FOURCC_T mp4_box_type_to_codec(VC_CONTAINER_FOURCC_T type)
|
|
{
|
|
int i;
|
|
for(i = 0; mp4_codec_mapping[i].type; i++ )
|
|
if(mp4_codec_mapping[i].type == type) break;
|
|
return mp4_codec_mapping[i].codec;
|
|
}
|
|
|
|
static bool codec_needs_batch_mode(VC_CONTAINER_FOURCC_T codec)
|
|
{
|
|
int i;
|
|
for(i = 0; mp4_codec_mapping[i].codec; i++ )
|
|
if(mp4_codec_mapping[i].codec == codec) break;
|
|
return mp4_codec_mapping[i].batch;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_header( VC_CONTAINER_T *p_ctx, int64_t size,
|
|
MP4_BOX_TYPE_T *box_type, int64_t *box_size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
int64_t offset = STREAM_POSITION(p_ctx);
|
|
|
|
module->box_offset = offset;
|
|
|
|
*box_size = _READ_U32(p_ctx);
|
|
*box_type = _READ_FOURCC(p_ctx);
|
|
if(!*box_type) return VC_CONTAINER_ERROR_CORRUPTED;
|
|
|
|
if(*box_size == 1) *box_size = _READ_U64(p_ctx);
|
|
LOG_FORMAT(p_ctx, "- Box %4.4s, Size: %"PRIi64", Offset: %"PRIi64,
|
|
(const char *)box_type, *box_size, offset);
|
|
|
|
/* Sanity check the box size */
|
|
if(*box_size < 0 /* Shouldn't ever get that big */ ||
|
|
/* Only the mdat box can really be massive */
|
|
(*box_type != MP4_BOX_TYPE_MDAT && *box_size > MP4_MAX_BOX_SIZE))
|
|
{
|
|
LOG_DEBUG(p_ctx, "box %4.4s has an invalid size (%"PRIi64")",
|
|
(const char *)box_type, *box_size);
|
|
return VC_CONTAINER_ERROR_CORRUPTED;
|
|
}
|
|
|
|
#if 0
|
|
/* It is valid for a box to have a zero size (i.e unknown) if it is the last one */
|
|
if(*box_size == 0 && size >= 0) *box_size = size;
|
|
else if(*box_size == 0) *box_size = INT64_C(-1);
|
|
#else
|
|
if(*box_size <= 0)
|
|
{
|
|
LOG_DEBUG(p_ctx, "box %4.4s has an invalid size (%"PRIi64")",
|
|
(const char *)box_type, *box_size);
|
|
return VC_CONTAINER_ERROR_CORRUPTED;
|
|
}
|
|
#endif
|
|
|
|
/* Sanity check box size against parent */
|
|
if(size >= 0 && *box_size > size)
|
|
{
|
|
LOG_DEBUG(p_ctx, "box %4.4s is bigger than it should (%"PRIi64" > %"PRIi64")",
|
|
(const char *)box_type, *box_size, size);
|
|
return VC_CONTAINER_ERROR_CORRUPTED;
|
|
}
|
|
|
|
*box_size -= (STREAM_POSITION(p_ctx) - offset);
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_data( VC_CONTAINER_T *p_ctx,
|
|
MP4_BOX_TYPE_T box_type, int64_t box_size, MP4_BOX_TYPE_T parent_type )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
int64_t offset = STREAM_POSITION(p_ctx);
|
|
VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
|
|
unsigned int i;
|
|
|
|
/* Check if the box is a recognised one */
|
|
for(i = 0; mp4_box_list[i].type; i++)
|
|
if(mp4_box_list[i].type == box_type &&
|
|
mp4_box_list[i].parent_type == parent_type) break;
|
|
if(mp4_box_list[i].type == MP4_BOX_TYPE_UNKNOWN)
|
|
for(i = 0; mp4_box_list[i].type; i++)
|
|
if(mp4_box_list[i].type == box_type) break;
|
|
|
|
/* Sanity check that the box has the right parent */
|
|
if(mp4_box_list[i].type != MP4_BOX_TYPE_UNKNOWN &&
|
|
mp4_box_list[i].parent_type != MP4_BOX_TYPE_UNKNOWN &&
|
|
parent_type != MP4_BOX_TYPE_UNKNOWN && parent_type != mp4_box_list[i].parent_type)
|
|
{
|
|
LOG_FORMAT(p_ctx, "Ignoring mis-placed box %4.4s", (const char *)&box_type);
|
|
goto skip;
|
|
}
|
|
|
|
/* Sanity check that the element isn't too deeply nested */
|
|
if(module->box_level >= 2 * MP4_MAX_BOX_LEVEL)
|
|
{
|
|
LOG_DEBUG(p_ctx, "box %4.4s is too deep. skipping", (const char *)&box_type);
|
|
goto skip;
|
|
}
|
|
|
|
module->box_level++;
|
|
|
|
/* Call the box specific parsing function */
|
|
if(mp4_box_list[i].pf_func)
|
|
status = mp4_box_list[i].pf_func(p_ctx, box_size);
|
|
|
|
module->box_level--;
|
|
|
|
if(status != VC_CONTAINER_SUCCESS)
|
|
LOG_DEBUG(p_ctx, "box %4.4s appears to be corrupted (%i)", (const char *)&box_type, status);
|
|
|
|
skip:
|
|
/* Skip the rest of the box */
|
|
box_size -= (STREAM_POSITION(p_ctx) - offset);
|
|
if(box_size < 0) /* Check for overruns */
|
|
{
|
|
/* Things have gone really bad here and we ended up reading past the end of the
|
|
* box. We could maybe try to be clever and recover by seeking back to the end
|
|
* of the box. However if we get there, the file is clearly corrupted so there's
|
|
* no guarantee it would work anyway. */
|
|
LOG_DEBUG(p_ctx, "%"PRIi64" bytes overrun past the end of box %4.4s",
|
|
-box_size, (const char *)&box_type);
|
|
return VC_CONTAINER_ERROR_CORRUPTED;
|
|
}
|
|
|
|
if(box_size)
|
|
LOG_FORMAT(p_ctx, "%"PRIi64" bytes left unread in box %4.4s",
|
|
box_size, (const char *)&box_type );
|
|
|
|
if(box_size < MP4_MAX_BOX_SIZE) box_size = SKIP_BYTES(p_ctx, box_size);
|
|
else SEEK(p_ctx, STREAM_POSITION(p_ctx) + box_size);
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box( VC_CONTAINER_T *p_ctx, int64_t size,
|
|
MP4_BOX_TYPE_T parent_type )
|
|
{
|
|
VC_CONTAINER_STATUS_T status;
|
|
MP4_BOX_TYPE_T box_type;
|
|
int64_t box_size;
|
|
|
|
status = mp4_read_box_header( p_ctx, size, &box_type, &box_size );
|
|
if(status != VC_CONTAINER_SUCCESS) return status;
|
|
return mp4_read_box_data( p_ctx, box_type, box_size, parent_type );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_boxes( VC_CONTAINER_T *p_ctx, int64_t size,
|
|
MP4_BOX_TYPE_T type)
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
|
|
int64_t offset = STREAM_POSITION(p_ctx);
|
|
bool unknown_size = size < 0;
|
|
|
|
/* Read contained boxes */
|
|
module->box_level++;
|
|
while(status == VC_CONTAINER_SUCCESS &&
|
|
(unknown_size || size >= MP4_BOX_MIN_HEADER_SIZE))
|
|
{
|
|
offset = STREAM_POSITION(p_ctx);
|
|
status = mp4_read_box(p_ctx, size, type);
|
|
if(!unknown_size) size -= (STREAM_POSITION(p_ctx) - offset);
|
|
}
|
|
module->box_level--;
|
|
return status;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_ftyp( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
|
|
module->brand = MP4_READ_FOURCC(p_ctx, "major_brand");
|
|
MP4_SKIP_U32(p_ctx, "minor_version");
|
|
while(size >= 4) MP4_SKIP_FOURCC(p_ctx, "compatible_brands");
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_moov( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_MOOV);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_mvhd( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
uint32_t version, i;
|
|
int64_t duration;
|
|
|
|
version = MP4_READ_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
if(version)
|
|
{
|
|
MP4_SKIP_U64(p_ctx, "creation_time");
|
|
MP4_SKIP_U64(p_ctx, "modification_time");
|
|
module->timescale = MP4_READ_U32(p_ctx, "timescale");
|
|
duration = MP4_READ_U64(p_ctx, "duration");
|
|
}
|
|
else
|
|
{
|
|
MP4_SKIP_U32(p_ctx, "creation_time");
|
|
MP4_SKIP_U32(p_ctx, "modification_time");
|
|
module->timescale = MP4_READ_U32(p_ctx, "timescale");
|
|
duration = MP4_READ_U32(p_ctx, "duration");
|
|
}
|
|
|
|
if(module->timescale)
|
|
p_ctx->duration = duration * 1000000 / module->timescale;
|
|
|
|
MP4_SKIP_U32(p_ctx, "rate");
|
|
MP4_SKIP_U16(p_ctx, "volume");
|
|
MP4_SKIP_U16(p_ctx, "reserved");
|
|
for(i = 0; i < 2; i++) MP4_SKIP_U32(p_ctx, "reserved");
|
|
for(i = 0; i < 9; i++) MP4_SKIP_U32(p_ctx, "matrix");
|
|
for(i = 0; i < 6; i++) MP4_SKIP_U32(p_ctx, "pre_defined");
|
|
MP4_SKIP_U32(p_ctx, "next_track_ID");
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_trak( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_T *track;
|
|
|
|
/* We have a new track. Allocate and initialise our track context */
|
|
if(p_ctx->tracks_num >= MP4_TRACKS_MAX) return VC_CONTAINER_ERROR_OUT_OF_RESOURCES;
|
|
p_ctx->tracks[p_ctx->tracks_num] = track =
|
|
vc_container_allocate_track(p_ctx, sizeof(*p_ctx->tracks[0]->priv->module));
|
|
if(!track) return VC_CONTAINER_ERROR_OUT_OF_MEMORY;
|
|
|
|
track->priv->module->sample_table[MP4_SAMPLE_TABLE_STTS].entry_size = 8;
|
|
track->priv->module->sample_table[MP4_SAMPLE_TABLE_STSZ].entry_size = 4;
|
|
track->priv->module->sample_table[MP4_SAMPLE_TABLE_STSC].entry_size = 12;
|
|
track->priv->module->sample_table[MP4_SAMPLE_TABLE_STCO].entry_size = 4;
|
|
track->priv->module->sample_table[MP4_SAMPLE_TABLE_STSS].entry_size = 4;
|
|
track->priv->module->sample_table[MP4_SAMPLE_TABLE_CO64].entry_size = 8;
|
|
track->priv->module->sample_table[MP4_SAMPLE_TABLE_CTTS].entry_size = 8;
|
|
|
|
status = mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_TRAK);
|
|
|
|
/* TODO: Sanity check track */
|
|
|
|
track->is_enabled = true;
|
|
track->format->flags |= VC_CONTAINER_ES_FORMAT_FLAG_FRAMED;
|
|
module->current_track++;
|
|
p_ctx->tracks_num++;
|
|
|
|
return status;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_tkhd( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
uint32_t i, version;
|
|
int64_t duration;
|
|
|
|
version = MP4_READ_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
if(version)
|
|
{
|
|
MP4_SKIP_U64(p_ctx, "creation_time");
|
|
MP4_SKIP_U64(p_ctx, "modification_time");
|
|
MP4_SKIP_U32(p_ctx, "track_ID");
|
|
MP4_SKIP_U32(p_ctx, "reserved");
|
|
duration = MP4_READ_U64(p_ctx, "duration");
|
|
}
|
|
else
|
|
{
|
|
MP4_SKIP_U32(p_ctx, "creation_time");
|
|
MP4_SKIP_U32(p_ctx, "modification_time");
|
|
MP4_SKIP_U32(p_ctx, "track_ID");
|
|
MP4_SKIP_U32(p_ctx, "reserved");
|
|
duration = MP4_READ_U32(p_ctx, "duration");
|
|
}
|
|
|
|
if(module->timescale)
|
|
duration = duration * 1000000 / module->timescale;
|
|
|
|
for(i = 0; i < 2; i++) MP4_SKIP_U32(p_ctx, "reserved");
|
|
MP4_SKIP_U16(p_ctx, "layer");
|
|
MP4_SKIP_U16(p_ctx, "alternate_group");
|
|
MP4_SKIP_U16(p_ctx, "volume");
|
|
MP4_SKIP_U16(p_ctx, "reserved");
|
|
for(i = 0; i < 9; i++) MP4_SKIP_U32(p_ctx, "matrix");
|
|
|
|
MP4_SKIP_U32(p_ctx, "width");
|
|
MP4_SKIP_U32(p_ctx, "height");
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_mdia( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_MDIA);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_mdhd( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track]->priv->module;
|
|
uint32_t version, timescale;
|
|
int64_t duration;
|
|
|
|
version = MP4_READ_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
if(version)
|
|
{
|
|
MP4_SKIP_U64(p_ctx, "creation_time");
|
|
MP4_SKIP_U64(p_ctx, "modification_time");
|
|
timescale = MP4_READ_U32(p_ctx, "timescale");
|
|
duration = MP4_READ_U64(p_ctx, "duration");
|
|
}
|
|
else
|
|
{
|
|
MP4_SKIP_U32(p_ctx, "creation_time");
|
|
MP4_SKIP_U32(p_ctx, "modification_time");
|
|
timescale = MP4_READ_U32(p_ctx, "timescale");
|
|
duration = MP4_READ_U32(p_ctx, "duration");
|
|
}
|
|
|
|
if(timescale) duration = duration * 1000000 / timescale;
|
|
track_module->timescale = timescale;
|
|
|
|
MP4_SKIP_U16(p_ctx, "language"); /* ISO-639-2/T language code */
|
|
MP4_SKIP_U16(p_ctx, "pre_defined");
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_hdlr( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track];
|
|
uint32_t i, fourcc, string_size;
|
|
VC_CONTAINER_ES_TYPE_T es_type = VC_CONTAINER_ES_TYPE_UNKNOWN;
|
|
|
|
if(size <= 24) return VC_CONTAINER_ERROR_CORRUPTED;
|
|
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
MP4_SKIP_U32(p_ctx, "pre-defined");
|
|
|
|
fourcc = MP4_READ_FOURCC(p_ctx, "handler_type");
|
|
if(fourcc == MP4_BOX_TYPE_VIDE) es_type = VC_CONTAINER_ES_TYPE_VIDEO;
|
|
if(fourcc == MP4_BOX_TYPE_SOUN) es_type = VC_CONTAINER_ES_TYPE_AUDIO;
|
|
if(fourcc == MP4_BOX_TYPE_TEXT) es_type = VC_CONTAINER_ES_TYPE_SUBPICTURE;
|
|
track->format->es_type = es_type;
|
|
|
|
for(i = 0; i < 3; i++) MP4_SKIP_U32(p_ctx, "reserved");
|
|
|
|
string_size = size;
|
|
if(module->brand == MP4_BRAND_QT)
|
|
string_size = MP4_READ_U8(p_ctx, "string_size");
|
|
|
|
if(size < 0) return VC_CONTAINER_ERROR_CORRUPTED;
|
|
if(string_size > size) string_size = size;
|
|
|
|
MP4_SKIP_STRING(p_ctx, string_size, "name");
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_minf( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_MINF);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_vmhd( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
MP4_SKIP_U16(p_ctx, "graphicsmode");
|
|
MP4_SKIP_U16(p_ctx, "opcolor");
|
|
MP4_SKIP_U16(p_ctx, "opcolor");
|
|
MP4_SKIP_U16(p_ctx, "opcolor");
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_smhd( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
MP4_SKIP_U16(p_ctx, "balance");
|
|
MP4_SKIP_U16(p_ctx, "reserved");
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_dinf( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_DINF);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_dref( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
MP4_SKIP_U32(p_ctx, "entry_count");
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stbl( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_STBL);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stsd( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track];
|
|
VC_CONTAINER_STATUS_T status;
|
|
MP4_BOX_TYPE_T box_type;
|
|
int64_t box_size;
|
|
uint32_t count;
|
|
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
count = MP4_READ_U32(p_ctx, "entry_count");
|
|
if(!count) return VC_CONTAINER_ERROR_CORRUPTED;
|
|
|
|
status = mp4_read_box_header( p_ctx, size, &box_type, &box_size );
|
|
if(status != VC_CONTAINER_SUCCESS) return status;
|
|
|
|
track->format->codec = mp4_box_type_to_codec(box_type);
|
|
if(!track->format->codec) track->format->codec = box_type;
|
|
|
|
if(track->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) box_type = MP4_BOX_TYPE_VIDE;
|
|
if(track->format->es_type == VC_CONTAINER_ES_TYPE_AUDIO) box_type = MP4_BOX_TYPE_SOUN;
|
|
if(track->format->es_type == VC_CONTAINER_ES_TYPE_SUBPICTURE) box_type = MP4_BOX_TYPE_TEXT;
|
|
status = mp4_read_box_data( p_ctx, box_type, box_size, MP4_BOX_TYPE_STSD );
|
|
if(status != VC_CONTAINER_SUCCESS) return status;
|
|
|
|
/* Special treatment for MPEG4 */
|
|
if(track->format->codec == VC_CONTAINER_CODEC_MP4A)
|
|
{
|
|
switch (track->priv->module->object_type_indication)
|
|
{
|
|
case MP4_MPEG4_AAC_LC_OBJECT_TYPE:
|
|
case MP4_MPEG2_AAC_LC_OBJECT_TYPE:
|
|
track->format->codec = VC_CONTAINER_CODEC_MP4A; break;
|
|
case MP4_MP3_OBJECT_TYPE:
|
|
case MP4_MPEG1_AUDIO_OBJECT_TYPE:
|
|
case MP4_KTF_MP3_OBJECT_TYPE:
|
|
track->format->codec = VC_CONTAINER_CODEC_MPGA; break;
|
|
case MP4_SKT_EVRC_2V1_OBJECT_TYPE:
|
|
case MP4_SKT_EVRC_OBJECT_TYPE:
|
|
track->format->codec = VC_CONTAINER_CODEC_EVRC; break;
|
|
case MP4_3GPP2_QCELP_OBJECT_TYPE:
|
|
track->format->codec = VC_CONTAINER_CODEC_QCELP; break;
|
|
default:
|
|
track->format->codec = VC_CONTAINER_CODEC_UNKNOWN; break;
|
|
}
|
|
}
|
|
else if(track->format->codec == VC_CONTAINER_CODEC_MP4V)
|
|
{
|
|
switch (track->priv->module->object_type_indication)
|
|
{
|
|
case MP4_MPEG4_VISUAL_OBJECT_TYPE:
|
|
track->format->codec = VC_CONTAINER_CODEC_MP4V; break;
|
|
case MP4_JPEG_OBJECT_TYPE:
|
|
track->format->codec = VC_CONTAINER_CODEC_JPEG; break;
|
|
case MP4_MPEG2_SP_OBJECT_TYPE:
|
|
case MP4_MPEG2_SNR_OBJECT_TYPE:
|
|
case MP4_MPEG2_AAC_LC_OBJECT_TYPE:
|
|
case MP4_MPEG2_MP_OBJECT_TYPE:
|
|
track->format->codec = VC_CONTAINER_CODEC_MP2V; break;
|
|
case MP4_MPEG1_VISUAL_OBJECT_TYPE:
|
|
track->format->codec = VC_CONTAINER_CODEC_MP1V; break;
|
|
default:
|
|
track->format->codec = VC_CONTAINER_CODEC_UNKNOWN; break;
|
|
}
|
|
}
|
|
|
|
/* For some codecs we process the samples in batches to be more efficient */
|
|
if(codec_needs_batch_mode(track->format->codec))
|
|
track->priv->module->samples_batch_size = MP4_MAX_SAMPLES_BATCH_SIZE;
|
|
|
|
/* Fix-up some of the data */
|
|
switch(track->format->codec)
|
|
{
|
|
case VC_CONTAINER_CODEC_ALAW:
|
|
case VC_CONTAINER_CODEC_MULAW:
|
|
track->format->type->audio.bits_per_sample = 8;
|
|
track->priv->module->sample_size = track->format->type->audio.channels;
|
|
break;
|
|
case VC_CONTAINER_CODEC_PCM_SIGNED_LE:
|
|
case VC_CONTAINER_CODEC_PCM_SIGNED_BE:
|
|
track->priv->module->sample_size = (track->format->type->audio.bits_per_sample + 7) /
|
|
8 * track->format->type->audio.channels;
|
|
break;
|
|
case VC_CONTAINER_CODEC_MP4A:
|
|
/* samplerate / channels is sometimes invalid so sanity check it using the codec config data */
|
|
if(track->format->extradata_size >= 2)
|
|
{
|
|
static unsigned int rate[] =
|
|
{ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
|
|
16000, 12000, 11025, 8000, 7350 };
|
|
unsigned int samplerate = 0, channels = 0;
|
|
uint8_t *p = track->format->extradata;
|
|
uint32_t index = (p[0] & 7) << 1 | (p[1] >> 7);
|
|
if(index == 15 && track->format->extradata_size >= 5)
|
|
{
|
|
samplerate = (p[1] & 0x7f) << 17 | (p[2] << 9) | (p[3] << 1) | (p[4] >> 7);
|
|
channels = (p[4] >> 3) & 15;
|
|
}
|
|
else if(index < 13)
|
|
{
|
|
samplerate = rate[index];
|
|
channels = (p[1] >> 3) & 15;;
|
|
}
|
|
|
|
if(samplerate && samplerate != track->format->type->audio.sample_rate &&
|
|
2 * samplerate != track->format->type->audio.sample_rate)
|
|
track->format->type->audio.sample_rate = samplerate;
|
|
if(channels && channels != track->format->type->audio.channels &&
|
|
2 * channels != track->format->type->audio.channels)
|
|
track->format->type->audio.channels = channels;
|
|
}
|
|
break;
|
|
default: break;
|
|
}
|
|
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_cache_table( VC_CONTAINER_T *p_ctx, MP4_SAMPLE_TABLE_T table,
|
|
uint32_t entries, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track]->priv->module;
|
|
uint32_t available_entries, entries_size;
|
|
|
|
if(size < 0) return VC_CONTAINER_ERROR_CORRUPTED;
|
|
|
|
track_module->sample_table[table].offset = STREAM_POSITION(p_ctx);
|
|
track_module->sample_table[table].entries = entries;
|
|
|
|
available_entries = size / track_module->sample_table[table].entry_size;
|
|
if(available_entries < entries)
|
|
{
|
|
LOG_DEBUG(p_ctx, "table has less entries than advertised (%i/%i)", available_entries, entries);
|
|
entries = available_entries;
|
|
}
|
|
|
|
entries_size = entries * track_module->sample_table[table].entry_size;
|
|
size = vc_container_io_cache(p_ctx->priv->io, entries_size );
|
|
if(size != entries_size)
|
|
{
|
|
available_entries = size / track_module->sample_table[table].entry_size;
|
|
LOG_DEBUG(p_ctx, "cached less table entries than advertised (%i/%i)", available_entries, entries);
|
|
track_module->sample_table[table].entries = available_entries;
|
|
}
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stts( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
uint32_t entries;
|
|
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
entries = MP4_READ_U32(p_ctx, "entry_count");
|
|
return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_STTS, entries, size );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_ctts( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
uint32_t entries;
|
|
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
entries = MP4_READ_U32(p_ctx, "entry_count");
|
|
return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_CTTS, entries, size );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stsc( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
uint32_t entries;
|
|
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
entries = MP4_READ_U32(p_ctx, "entry_count");
|
|
return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_STSC, entries, size );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stsz( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[module->current_track]->priv->module;
|
|
uint32_t entries;
|
|
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
track_module->sample_size = READ_U32(p_ctx, "sample_size");
|
|
if(track_module->sample_size) return STREAM_STATUS(p_ctx);
|
|
|
|
entries = MP4_READ_U32(p_ctx, "sample_count");
|
|
return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_STSZ, entries, size );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stco( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
uint32_t entries;
|
|
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
entries = MP4_READ_U32(p_ctx, "entry_count");
|
|
return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_STCO, entries, size );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_co64( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
uint32_t entries;
|
|
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
entries = MP4_READ_U32(p_ctx, "entry_count");
|
|
return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_CO64, entries, size );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_stss( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
uint32_t entries;
|
|
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
entries = MP4_READ_U32(p_ctx, "entry_count");
|
|
return mp4_cache_table( p_ctx, MP4_SAMPLE_TABLE_STSS, entries, size );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_esds_descriptor_header(VC_CONTAINER_T *p_ctx, int64_t *size,
|
|
uint32_t *descriptor_length, uint8_t *descriptor_type)
|
|
{
|
|
uint32_t byte, length = 0;
|
|
|
|
if(*size <= 0) return VC_CONTAINER_ERROR_CORRUPTED;
|
|
|
|
*descriptor_type = _READ_U8(p_ctx);
|
|
(*size)--;
|
|
|
|
/* Read descriptor size */
|
|
while(*size)
|
|
{
|
|
byte = _READ_U8(p_ctx);
|
|
(*size)--;
|
|
length = (length << 7) | (byte&0x7F);
|
|
if(!(byte & 0x80)) break;
|
|
}
|
|
|
|
if(*size <= 0 || length > *size)
|
|
{
|
|
LOG_FORMAT(p_ctx, "esds descriptor is corrupted");
|
|
return VC_CONTAINER_ERROR_CORRUPTED;
|
|
}
|
|
|
|
*descriptor_length = length;
|
|
LOG_FORMAT(p_ctx, "esds descriptor %x, size %i", *descriptor_type, *descriptor_length);
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_esds( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track];
|
|
VC_CONTAINER_STATUS_T status;
|
|
uint32_t descriptor_length;
|
|
uint8_t descriptor_type;
|
|
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U24(p_ctx, "flags");
|
|
|
|
status = mp4_read_esds_descriptor_header(p_ctx, &size, &descriptor_length, &descriptor_type);
|
|
if(status != VC_CONTAINER_SUCCESS) return status;
|
|
|
|
if(descriptor_type == 0x3) /* ES descriptor */
|
|
{
|
|
uint8_t flags;
|
|
|
|
MP4_SKIP_U16(p_ctx, "es_id");
|
|
flags = MP4_READ_U8(p_ctx, "flags");
|
|
|
|
if(flags & 0x80) /* Stream dependence */
|
|
MP4_SKIP_U16(p_ctx, "depend_on_es_id");
|
|
|
|
if(flags & 0x40) /* URL */
|
|
{
|
|
uint32_t url_size = MP4_READ_U8(p_ctx, "url_size");
|
|
MP4_SKIP_STRING(p_ctx, url_size, "url");
|
|
}
|
|
|
|
if(flags & 0x20) /* OCR_stream*/
|
|
MP4_SKIP_U16(p_ctx, "OCR_es_id");
|
|
|
|
status = mp4_read_esds_descriptor_header(p_ctx, &size, &descriptor_length, &descriptor_type);
|
|
if(status != VC_CONTAINER_SUCCESS) return status;
|
|
}
|
|
|
|
if(descriptor_type == 0x4) /* Decoder Config descriptor */
|
|
{
|
|
track->priv->module->object_type_indication = MP4_READ_U8(p_ctx, "object_type_indication");
|
|
MP4_SKIP_U8(p_ctx, "stream_type");
|
|
MP4_SKIP_U24(p_ctx, "buffer_size_db");
|
|
MP4_SKIP_U32(p_ctx, "max_bitrate");
|
|
track->format->bitrate = MP4_READ_U32(p_ctx, "avg_bitrate");
|
|
|
|
if(size <= 0 || descriptor_length <= 13) return STREAM_STATUS(p_ctx);
|
|
|
|
status = mp4_read_esds_descriptor_header(p_ctx, &size, &descriptor_length, &descriptor_type);
|
|
if(status != VC_CONTAINER_SUCCESS) return status;
|
|
if(descriptor_type == 0x05 && descriptor_length)
|
|
{
|
|
status = vc_container_track_allocate_extradata(p_ctx, track, descriptor_length);
|
|
if(status != VC_CONTAINER_SUCCESS) return status;
|
|
track->format->extradata_size = MP4_READ_BYTES(p_ctx, track->format->extradata, descriptor_length);
|
|
}
|
|
}
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_vide( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track];
|
|
unsigned int i;
|
|
|
|
for(i = 0; i < 6; i++) MP4_SKIP_U8(p_ctx, "reserved");
|
|
MP4_SKIP_U16(p_ctx, "data_reference_index");
|
|
|
|
MP4_SKIP_U16(p_ctx, "pre_defined");
|
|
MP4_SKIP_U16(p_ctx, "reserved");
|
|
for(i = 0; i < 3; i++) MP4_SKIP_U32(p_ctx, "pre_defined");
|
|
track->format->type->video.width = MP4_READ_U16(p_ctx, "width");
|
|
track->format->type->video.height = MP4_READ_U16(p_ctx, "height");
|
|
MP4_SKIP_U32(p_ctx, "horizresolution"); /* dpi */
|
|
MP4_SKIP_U32(p_ctx, "vertresolution"); /* dpi */
|
|
MP4_SKIP_U32(p_ctx, "reserved");
|
|
MP4_SKIP_U16(p_ctx, "frame_count");
|
|
MP4_SKIP_BYTES(p_ctx, 32);
|
|
MP4_SKIP_U16(p_ctx, "depth");
|
|
MP4_SKIP_U16(p_ctx, "pre_defined");
|
|
|
|
if(size > 0)
|
|
return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_VIDE );
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_vide_avcC( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track];
|
|
VC_CONTAINER_STATUS_T status;
|
|
|
|
if(track->format->codec != VC_CONTAINER_CODEC_H264 || size <= 0)
|
|
return VC_CONTAINER_ERROR_CORRUPTED;
|
|
|
|
track->format->codec_variant = VC_FOURCC('a','v','c','C');
|
|
|
|
status = vc_container_track_allocate_extradata(p_ctx, track, (unsigned int)size);
|
|
if(status != VC_CONTAINER_SUCCESS) return status;
|
|
track->format->extradata_size = READ_BYTES(p_ctx, track->format->extradata, size);
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_vide_d263( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
MP4_SKIP_FOURCC(p_ctx, "vendor");
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U8(p_ctx, "level");
|
|
MP4_SKIP_U8(p_ctx, "profile");
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_soun( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track];
|
|
unsigned int i, version = 0;
|
|
|
|
for(i = 0; i < 6; i++) MP4_SKIP_U8(p_ctx, "reserved");
|
|
MP4_SKIP_U16(p_ctx, "data_reference_index");
|
|
|
|
version = MP4_READ_U16(p_ctx, "version");
|
|
MP4_SKIP_U16(p_ctx, "revision_level");
|
|
MP4_SKIP_U32(p_ctx, "vendor");
|
|
|
|
track->format->type->audio.channels = MP4_READ_U16(p_ctx, "channelcount");
|
|
track->format->type->audio.bits_per_sample = MP4_READ_U16(p_ctx, "samplesize");
|
|
MP4_SKIP_U16(p_ctx, "pre_defined");
|
|
MP4_SKIP_U16(p_ctx, "reserved");
|
|
track->format->type->audio.sample_rate = MP4_READ_U16(p_ctx, "samplerate");
|
|
MP4_SKIP_U16(p_ctx, "samplerate_fp_low");
|
|
|
|
if(version == 1)
|
|
{
|
|
MP4_SKIP_U32(p_ctx, "samples_per_packet");
|
|
MP4_SKIP_U32(p_ctx, "bytes_per_packet");
|
|
MP4_SKIP_U32(p_ctx, "bytes_per_frame");
|
|
MP4_SKIP_U32(p_ctx, "bytes_per_sample");
|
|
}
|
|
|
|
if(size > 0)
|
|
return mp4_read_box( p_ctx, size, MP4_BOX_TYPE_SOUN );
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_soun_damr( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track];
|
|
|
|
MP4_SKIP_FOURCC(p_ctx, "vendor");
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U8(p_ctx, "mode_set");
|
|
MP4_SKIP_U8(p_ctx, "mode_change_period");
|
|
MP4_SKIP_U8(p_ctx, "frame_per_second");
|
|
|
|
track->format->type->audio.channels = 1;
|
|
if(track->format->codec == VC_CONTAINER_CODEC_AMRNB)
|
|
track->format->type->audio.sample_rate = 8000;
|
|
else if(track->format->codec == VC_CONTAINER_CODEC_AMRWB)
|
|
track->format->type->audio.sample_rate = 16000;
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_soun_dawp( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track];
|
|
|
|
MP4_SKIP_FOURCC(p_ctx, "vendor");
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
|
|
track->format->type->audio.channels = 2;
|
|
track->format->type->audio.sample_rate = 16000;
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_soun_devc( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_T *track = p_ctx->tracks[module->current_track];
|
|
|
|
MP4_SKIP_FOURCC(p_ctx, "vendor");
|
|
MP4_SKIP_U8(p_ctx, "version");
|
|
MP4_SKIP_U8(p_ctx, "samples_per_frame");
|
|
|
|
track->format->type->audio.channels = 1;
|
|
track->format->type->audio.sample_rate = 8000;
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_soun_wave( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
return mp4_read_boxes( p_ctx, size, MP4_BOX_TYPE_SOUN);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_box_text( VC_CONTAINER_T *p_ctx, int64_t size )
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_PARAM_UNUSED(module);
|
|
|
|
/* TODO */if(1) return VC_CONTAINER_ERROR_FAILED;
|
|
|
|
if(size > 0)
|
|
return mp4_read_box( p_ctx, size, MP4_BOX_TYPE_TEXT );
|
|
|
|
return STREAM_STATUS(p_ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_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]);
|
|
free(module);
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
#ifdef ENABLE_MP4_READER_LOG_STATE
|
|
static void mp4_log_state( VC_CONTAINER_T *p_ctx, MP4_READER_STATE_T *state )
|
|
{
|
|
VC_CONTAINER_PARAM_UNUSED(p_ctx);
|
|
|
|
LOG_DEBUG(p_ctx, "state:");
|
|
LOG_DEBUG(p_ctx, "duration: %i, pts %i, dts %i", (int)state->duration,
|
|
(int)state->pts, (int)state->dts);
|
|
LOG_DEBUG(p_ctx, "sample: %i, offset %i, sample_offset %i, sample_size %i",
|
|
state->sample, (int)state->offset, state->sample_offset,
|
|
state->sample_size);
|
|
LOG_DEBUG(p_ctx, "sample_duration: %i, count %i",
|
|
state->sample_duration, state->sample_duration_count);
|
|
LOG_DEBUG(p_ctx, "sample_composition_offset: %i, count %i",
|
|
state->sample_composition_offset, state->sample_composition_count);
|
|
LOG_DEBUG(p_ctx, "next_sync_sample: %i, keyframe %i",
|
|
state->next_sync_sample, state->keyframe);
|
|
LOG_DEBUG(p_ctx, "samples_per_chunk: %i, chunks %i, samples_in_chunk %i",
|
|
state->samples_per_chunk, state->chunks, state->samples_in_chunk);
|
|
LOG_DEBUG(p_ctx, "MP4_SAMPLE_TABLE_STTS %i", state->sample_table[MP4_SAMPLE_TABLE_STTS].entry);
|
|
LOG_DEBUG(p_ctx, "MP4_SAMPLE_TABLE_STSZ %i", state->sample_table[MP4_SAMPLE_TABLE_STSZ].entry);
|
|
LOG_DEBUG(p_ctx, "MP4_SAMPLE_TABLE_STSC %i", state->sample_table[MP4_SAMPLE_TABLE_STSC].entry);
|
|
LOG_DEBUG(p_ctx, "MP4_SAMPLE_TABLE_STCO %i", state->sample_table[MP4_SAMPLE_TABLE_STCO].entry);
|
|
LOG_DEBUG(p_ctx, "MP4_SAMPLE_TABLE_CO64 %i", state->sample_table[MP4_SAMPLE_TABLE_CO64].entry);
|
|
LOG_DEBUG(p_ctx, "MP4_SAMPLE_TABLE_CTTS %i", state->sample_table[MP4_SAMPLE_TABLE_CTTS].entry);
|
|
}
|
|
#endif /* ENABLE_MP4_READER_LOG_STATE */
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_seek_sample_table( VC_CONTAINER_T *p_ctx,
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module, MP4_READER_STATE_T *state,
|
|
MP4_SAMPLE_TABLE_T table)
|
|
{
|
|
int64_t seek_offset;
|
|
|
|
/* Seek to the next entry in the table */
|
|
if(state->sample_table[table].entry >= track_module->sample_table[table].entries)
|
|
return VC_CONTAINER_ERROR_EOS;
|
|
|
|
seek_offset = track_module->sample_table[table].offset +
|
|
track_module->sample_table[table].entry_size * state->sample_table[table].entry;
|
|
|
|
return SEEK(p_ctx, seek_offset);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_sample_table( VC_CONTAINER_T *p_ctx,
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module, MP4_READER_STATE_T *state,
|
|
MP4_SAMPLE_TABLE_T table, unsigned int seek)
|
|
{
|
|
uint32_t value;
|
|
|
|
if(table == MP4_SAMPLE_TABLE_STSZ && track_module->sample_size)
|
|
{
|
|
state->sample_size = track_module->sample_size;
|
|
return state->status;
|
|
}
|
|
|
|
/* CO64 support */
|
|
if(table == MP4_SAMPLE_TABLE_STCO &&
|
|
track_module->sample_table[MP4_SAMPLE_TABLE_CO64].entries)
|
|
table = MP4_SAMPLE_TABLE_CO64;
|
|
|
|
/* Seek to the next entry in the table */
|
|
if(seek)
|
|
{
|
|
state->status = mp4_seek_sample_table( p_ctx, track_module, state, table );
|
|
if(state->status != VC_CONTAINER_SUCCESS) return state->status;
|
|
}
|
|
|
|
switch(table)
|
|
{
|
|
case MP4_SAMPLE_TABLE_STSZ:
|
|
state->sample_size = _READ_U32(p_ctx);
|
|
state->status = STREAM_STATUS(p_ctx);
|
|
break;
|
|
|
|
case MP4_SAMPLE_TABLE_STTS:
|
|
state->sample_duration_count = _READ_U32(p_ctx);
|
|
state->sample_duration = _READ_U32(p_ctx);
|
|
state->status = STREAM_STATUS(p_ctx);
|
|
if(state->status != VC_CONTAINER_SUCCESS) break;
|
|
if(!state->sample_duration_count) state->status = VC_CONTAINER_ERROR_CORRUPTED;
|
|
break;
|
|
|
|
case MP4_SAMPLE_TABLE_CTTS:
|
|
state->sample_composition_count = _READ_U32(p_ctx);
|
|
state->sample_composition_offset = _READ_U32(p_ctx); /* Converted to signed */
|
|
state->status = STREAM_STATUS(p_ctx);
|
|
if(state->status != VC_CONTAINER_SUCCESS) break;
|
|
if(!state->sample_composition_count) state->status = VC_CONTAINER_ERROR_CORRUPTED;
|
|
break;
|
|
|
|
case MP4_SAMPLE_TABLE_STSC:
|
|
state->chunks = _READ_U32(p_ctx);
|
|
state->samples_per_chunk = _READ_U32(p_ctx);
|
|
_SKIP_U32(p_ctx);
|
|
state->status = STREAM_STATUS(p_ctx);
|
|
if(state->status != VC_CONTAINER_SUCCESS) break;
|
|
|
|
if(state->sample_table[table].entry + 1 <
|
|
track_module->sample_table[MP4_SAMPLE_TABLE_STSC].entries) value = _READ_U32(p_ctx);
|
|
else value = -1;
|
|
|
|
if(!state->chunks || !state->samples_per_chunk || state->chunks >= value )
|
|
{state->status = VC_CONTAINER_ERROR_CORRUPTED; break;}
|
|
state->chunks = value - state->chunks;
|
|
state->samples_in_chunk = state->samples_per_chunk;
|
|
break;
|
|
|
|
case MP4_SAMPLE_TABLE_STCO:
|
|
case MP4_SAMPLE_TABLE_CO64:
|
|
state->offset = table == MP4_SAMPLE_TABLE_STCO ? _READ_U32(p_ctx) : _READ_U64(p_ctx);
|
|
state->status = STREAM_STATUS(p_ctx);
|
|
if(state->status != VC_CONTAINER_SUCCESS) break;
|
|
if(!state->offset) state->status = VC_CONTAINER_ERROR_CORRUPTED;
|
|
state->samples_in_chunk = state->samples_per_chunk;
|
|
break;
|
|
|
|
case MP4_SAMPLE_TABLE_STSS:
|
|
state->next_sync_sample = _READ_U32(p_ctx);
|
|
state->status = STREAM_STATUS(p_ctx);
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
state->sample_table[table].entry++;
|
|
return state->status;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_sample_header( VC_CONTAINER_T *p_ctx, uint32_t track,
|
|
MP4_READER_STATE_T *state )
|
|
{
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[track]->priv->module;
|
|
|
|
if(state->status != VC_CONTAINER_SUCCESS) return state->status;
|
|
|
|
if(state->sample_offset < state->sample_size)
|
|
return state->status; /* We still have data left from the current sample */
|
|
|
|
/* Switch to the next sample */
|
|
state->offset += state->sample_size;
|
|
state->sample_offset = 0;
|
|
state->sample_size = 0;
|
|
state->sample++;
|
|
|
|
if(!state->samples_in_chunk)
|
|
{
|
|
/* We're switching to the next chunk */
|
|
if(!state->chunks)
|
|
{
|
|
/* Seek to the next entry in the STSC */
|
|
state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSC, 1 );
|
|
if(state->status != VC_CONTAINER_SUCCESS) goto error;
|
|
}
|
|
|
|
/* Get the offset of the new chunk */
|
|
state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STCO, 1 );
|
|
if(state->status != VC_CONTAINER_SUCCESS) goto error;
|
|
|
|
state->chunks--;
|
|
}
|
|
state->samples_in_chunk--;
|
|
|
|
/* Get the new sample size */
|
|
state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSZ, 1 );
|
|
if(state->status != VC_CONTAINER_SUCCESS) goto error;
|
|
|
|
/* Get the timestamp */
|
|
if(track_module->timescale)
|
|
state->pts = state->dts = state->duration * 1000000 / track_module->timescale;
|
|
if(!state->sample_duration_count)
|
|
{
|
|
state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STTS, 1 );
|
|
if(state->status != VC_CONTAINER_SUCCESS) goto error;
|
|
}
|
|
state->sample_duration_count--;
|
|
|
|
/* Get the composition time */
|
|
if(track_module->sample_table[MP4_SAMPLE_TABLE_CTTS].entries)
|
|
{
|
|
if(!state->sample_composition_count)
|
|
{
|
|
state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_CTTS, 1 );
|
|
if(state->status != VC_CONTAINER_SUCCESS) goto error;
|
|
}
|
|
if(track_module->timescale)
|
|
state->pts = (state->duration + state->sample_composition_offset) * 1000000 / track_module->timescale;
|
|
state->sample_composition_count--;
|
|
}
|
|
state->duration += state->sample_duration;
|
|
|
|
/* Get the keyframe flag */
|
|
if(state->sample_table[MP4_SAMPLE_TABLE_STSS].entry <
|
|
track_module->sample_table[MP4_SAMPLE_TABLE_STSS].entries &&
|
|
!state->next_sync_sample)
|
|
{
|
|
mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSS, 1 );
|
|
state->status = VC_CONTAINER_SUCCESS; /* This isn't a critical error */
|
|
}
|
|
|
|
state->keyframe =
|
|
track_module->sample_table[MP4_SAMPLE_TABLE_STSS].entries &&
|
|
state->sample == state->next_sync_sample;
|
|
if(state->keyframe)
|
|
state->next_sync_sample = 0;
|
|
|
|
/* Try to batch several samples together if requested. We'll always stop at the chunk boundary */
|
|
if(track_module->samples_batch_size)
|
|
{
|
|
uint32_t size = state->sample_size;
|
|
while(state->samples_in_chunk && size < track_module->samples_batch_size)
|
|
{
|
|
if(mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSZ, 1 )) break;
|
|
|
|
if(!state->sample_duration_count)
|
|
if(mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STTS, 1 )) break;
|
|
|
|
state->sample_duration_count--;
|
|
state->duration += state->sample_duration;
|
|
|
|
size += state->sample_size;
|
|
state->samples_in_chunk--;
|
|
state->sample++;
|
|
}
|
|
state->sample_size = size;
|
|
}
|
|
|
|
#ifdef ENABLE_MP4_READER_LOG_STATE
|
|
mp4_log_state(p_ctx, state);
|
|
#endif
|
|
|
|
error:
|
|
return state->status;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_read_sample_data( VC_CONTAINER_T *p_ctx, uint32_t track,
|
|
MP4_READER_STATE_T *state, uint8_t *data, unsigned int *data_size )
|
|
{
|
|
VC_CONTAINER_STATUS_T status;
|
|
unsigned int size = state->sample_size - state->sample_offset;
|
|
|
|
if(state->status != VC_CONTAINER_SUCCESS) return state->status;
|
|
|
|
if(data_size && *data_size < size) size = *data_size;
|
|
|
|
if(data)
|
|
{
|
|
state->status = SEEK(p_ctx, state->offset + state->sample_offset);
|
|
if(state->status != VC_CONTAINER_SUCCESS) return state->status;
|
|
|
|
size = READ_BYTES(p_ctx, data, size);
|
|
}
|
|
state->sample_offset += size;
|
|
|
|
if(data_size) *data_size = size;
|
|
state->status = STREAM_STATUS(p_ctx);
|
|
if(state->status != VC_CONTAINER_SUCCESS) return state->status;
|
|
|
|
status = state->status;
|
|
|
|
/* Switch to the start of the next sample */
|
|
if(state->sample_offset >= state->sample_size)
|
|
mp4_read_sample_header(p_ctx, track, state);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_reader_read( VC_CONTAINER_T *p_ctx,
|
|
VC_CONTAINER_PACKET_T *packet, uint32_t flags )
|
|
{
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module;
|
|
VC_CONTAINER_STATUS_T status;
|
|
MP4_READER_STATE_T *state;
|
|
uint32_t i, track;
|
|
unsigned int data_size;
|
|
uint8_t *data = 0;
|
|
int64_t offset;
|
|
|
|
/* Select the track to read from. If no specific track is requested by the caller, this
|
|
* will be the track to which the next bit of data in the mdat belongs to */
|
|
if(!(flags & VC_CONTAINER_READ_FLAG_FORCE_TRACK))
|
|
{
|
|
for(i = 0, track = 0, offset = -1; i < p_ctx->tracks_num; i++)
|
|
{
|
|
track_module = p_ctx->tracks[i]->priv->module;
|
|
|
|
/* Ignore tracks which have no more readable data */
|
|
if(track_module->state.status != VC_CONTAINER_SUCCESS) continue;
|
|
|
|
if(offset >= 0 && track_module->state.offset >= offset) continue;
|
|
offset = track_module->state.offset;
|
|
track = i;
|
|
}
|
|
}
|
|
else track = packet->track;
|
|
|
|
if(track >= p_ctx->tracks_num) return VC_CONTAINER_ERROR_INVALID_ARGUMENT;
|
|
|
|
track_module = p_ctx->tracks[track]->priv->module;
|
|
state = &track_module->state;
|
|
|
|
status = mp4_read_sample_header(p_ctx, track, state);
|
|
if(status != VC_CONTAINER_SUCCESS) return status;
|
|
|
|
if(!packet) /* Skip packet */
|
|
return mp4_read_sample_data(p_ctx, track, state, 0, 0);
|
|
|
|
packet->dts = state->dts;
|
|
packet->pts = state->pts;
|
|
packet->flags = VC_CONTAINER_PACKET_FLAG_FRAME_END;
|
|
if(state->keyframe) packet->flags |= VC_CONTAINER_PACKET_FLAG_KEYFRAME;
|
|
if(!state->sample_offset) packet->flags |= VC_CONTAINER_PACKET_FLAG_FRAME_START;
|
|
packet->track = track;
|
|
packet->frame_size = state->sample_size;
|
|
packet->size = state->sample_size - state->sample_offset;
|
|
|
|
if(flags & VC_CONTAINER_READ_FLAG_SKIP)
|
|
return mp4_read_sample_data(p_ctx, track, state, 0, 0);
|
|
else if((flags & VC_CONTAINER_READ_FLAG_INFO) || !packet->data)
|
|
return VC_CONTAINER_SUCCESS;
|
|
|
|
data = packet->data;
|
|
data_size = packet->buffer_size;
|
|
|
|
status = mp4_read_sample_data(p_ctx, track, state, data, &data_size);
|
|
if(status != VC_CONTAINER_SUCCESS)
|
|
{
|
|
/* FIXME */
|
|
return status;
|
|
}
|
|
|
|
packet->size = data_size;
|
|
if(state->sample_offset) //?
|
|
packet->flags &= ~VC_CONTAINER_PACKET_FLAG_FRAME_END;
|
|
|
|
return status;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static uint32_t mp4_find_sample( VC_CONTAINER_T *p_ctx, uint32_t track,
|
|
MP4_READER_STATE_T *state, int64_t seek_time, VC_CONTAINER_STATUS_T *p_status )
|
|
{
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[track]->priv->module;
|
|
VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
|
|
uint32_t sample = 0, sample_duration_count;
|
|
int64_t sample_duration, seek_time_up = seek_time + 1;
|
|
unsigned int i;
|
|
VC_CONTAINER_PARAM_UNUSED(state);
|
|
|
|
seek_time = seek_time * track_module->timescale / 1000000;
|
|
/* We also need to check against the time rounded up to account for
|
|
* rounding errors in the timestamp (because of the timescale conversion) */
|
|
seek_time_up = seek_time_up * track_module->timescale / 1000000;
|
|
|
|
status = SEEK(p_ctx, track_module->sample_table[MP4_SAMPLE_TABLE_STTS].offset);
|
|
if(status != VC_CONTAINER_SUCCESS) goto end;
|
|
|
|
/* Find the sample which corresponds to the requested time */
|
|
for(i = 0; i < track_module->sample_table[MP4_SAMPLE_TABLE_STTS].entries; i++)
|
|
{
|
|
sample_duration_count = _READ_U32(p_ctx);
|
|
sample_duration = _READ_U32(p_ctx);
|
|
status = STREAM_STATUS(p_ctx);
|
|
if(status != VC_CONTAINER_SUCCESS) break;
|
|
|
|
if(sample_duration_count * sample_duration <= seek_time)
|
|
{
|
|
seek_time -= sample_duration_count * sample_duration;
|
|
seek_time_up -= sample_duration_count * sample_duration;
|
|
sample += sample_duration_count;
|
|
continue;
|
|
}
|
|
if(!sample_duration) break;
|
|
|
|
seek_time /= sample_duration;
|
|
seek_time_up /= sample_duration;
|
|
sample += MAX(seek_time, seek_time_up);
|
|
break;
|
|
}
|
|
|
|
end:
|
|
if(p_status) *p_status = status;
|
|
return sample;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_seek_track( VC_CONTAINER_T *p_ctx, uint32_t track,
|
|
MP4_READER_STATE_T *state, uint32_t sample )
|
|
{
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module = p_ctx->tracks[track]->priv->module;
|
|
uint32_t chunk = 0, samples;
|
|
unsigned int i;
|
|
|
|
memset(state, 0, sizeof(*state));
|
|
|
|
/* Find the right chunk */
|
|
for(i = 0, samples = sample; i < track_module->sample_table[MP4_SAMPLE_TABLE_STSC].entries; i++)
|
|
{
|
|
state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSC, 1 );
|
|
if(state->status != VC_CONTAINER_SUCCESS) goto error;
|
|
|
|
if(state->chunks * state->samples_per_chunk <= samples)
|
|
{
|
|
samples -= state->chunks * state->samples_per_chunk;
|
|
chunk += state->chunks;
|
|
continue;
|
|
}
|
|
|
|
while(samples >= state->samples_per_chunk)
|
|
{
|
|
samples -= state->samples_per_chunk;
|
|
state->chunks--;
|
|
chunk++;
|
|
}
|
|
|
|
state->chunks--;
|
|
break;
|
|
}
|
|
|
|
/* Get the offset of the selected chunk */
|
|
state->sample_table[MP4_SAMPLE_TABLE_STCO].entry = chunk;
|
|
state->sample_table[MP4_SAMPLE_TABLE_CO64].entry = chunk;
|
|
state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STCO, 1 );
|
|
if(state->status != VC_CONTAINER_SUCCESS) goto error;
|
|
|
|
/* Find the sample offset within the chunk */
|
|
state->sample_table[MP4_SAMPLE_TABLE_STSZ].entry = sample - samples;
|
|
for(i = 0; i < samples; i++)
|
|
{
|
|
state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSZ, !i );
|
|
if(state->status != VC_CONTAINER_SUCCESS) goto error;
|
|
state->offset += state->sample_size;
|
|
state->samples_in_chunk--;
|
|
}
|
|
|
|
/* Get the timestamp */
|
|
for(i = 0, samples = sample; i < track_module->sample_table[MP4_SAMPLE_TABLE_STTS].entries; i++)
|
|
{
|
|
state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STTS, !i );
|
|
if(state->status != VC_CONTAINER_SUCCESS) goto error;
|
|
|
|
if(state->sample_duration_count <= samples)
|
|
{
|
|
samples -= state->sample_duration_count;
|
|
state->duration += state->sample_duration * state->sample_duration_count;
|
|
continue;
|
|
}
|
|
|
|
state->sample_duration_count -= samples;
|
|
state->duration += samples * state->sample_duration;
|
|
break;
|
|
}
|
|
|
|
/* Find the right place in the sample composition table */
|
|
for(i = 0, samples = sample; i < track_module->sample_table[MP4_SAMPLE_TABLE_CTTS].entries; i++)
|
|
{
|
|
state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_CTTS, !i );
|
|
if(state->status != VC_CONTAINER_SUCCESS) goto error;
|
|
|
|
if(state->sample_composition_count <= samples)
|
|
{
|
|
samples -= state->sample_composition_count;
|
|
continue;
|
|
}
|
|
|
|
state->sample_composition_count -= samples;
|
|
break;
|
|
}
|
|
|
|
/* Find the right place in the synchronisation table */
|
|
for(i = 0; i < track_module->sample_table[MP4_SAMPLE_TABLE_STSS].entries; i++)
|
|
{
|
|
state->status = mp4_read_sample_table( p_ctx, track_module, state, MP4_SAMPLE_TABLE_STSS, !i );
|
|
if(state->status != VC_CONTAINER_SUCCESS) goto error;
|
|
|
|
if(state->next_sync_sample >= sample + 1) break;
|
|
}
|
|
|
|
state->sample = sample;
|
|
state->sample_size = 0;
|
|
mp4_read_sample_header(p_ctx, track, state);
|
|
|
|
error:
|
|
return state->status;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static VC_CONTAINER_STATUS_T mp4_reader_seek(VC_CONTAINER_T *p_ctx,
|
|
int64_t *offset, VC_CONTAINER_SEEK_MODE_T mode, VC_CONTAINER_SEEK_FLAGS_T flags)
|
|
{
|
|
VC_CONTAINER_MODULE_T *module = p_ctx->priv->module;
|
|
VC_CONTAINER_TRACK_MODULE_T *track_module;
|
|
VC_CONTAINER_STATUS_T status;
|
|
uint32_t i, track, sample, prev_sample, next_sample;
|
|
int64_t seek_time = *offset;
|
|
VC_CONTAINER_PARAM_UNUSED(module);
|
|
VC_CONTAINER_PARAM_UNUSED(mode);
|
|
|
|
/* Reset the states */
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
memset(&p_ctx->tracks[i]->priv->module->state, 0, sizeof(p_ctx->tracks[i]->priv->module->state));
|
|
|
|
/* Deal with the easy case first */
|
|
if(!*offset)
|
|
{
|
|
/* Initialise tracks */
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
{
|
|
/* FIXME: we should check we've got at least one success */
|
|
mp4_read_sample_header(p_ctx, i, &p_ctx->tracks[i]->priv->module->state);
|
|
}
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
|
|
/* Find the first enabled video track */
|
|
for(track = 0; track < p_ctx->tracks_num; track++)
|
|
if(p_ctx->tracks[track]->is_enabled &&
|
|
p_ctx->tracks[track]->format->es_type == VC_CONTAINER_ES_TYPE_VIDEO) break;
|
|
if(track == p_ctx->tracks_num) goto seek_time_found; /* No video track found */
|
|
track_module = p_ctx->tracks[track]->priv->module;
|
|
|
|
/* Find the sample number for the requested time */
|
|
sample = mp4_find_sample( p_ctx, track, &track_module->state, seek_time, &status );
|
|
if(status != VC_CONTAINER_SUCCESS) goto seek_time_found;
|
|
|
|
/* Find the closest sync sample */
|
|
status = mp4_seek_sample_table( p_ctx, track_module, &track_module->state, MP4_SAMPLE_TABLE_STSS );
|
|
if(status != VC_CONTAINER_SUCCESS) goto seek_time_found;
|
|
for(i = 0, prev_sample = 0, next_sample = 0;
|
|
i < track_module->sample_table[MP4_SAMPLE_TABLE_STSS].entries; i++)
|
|
{
|
|
next_sample = _READ_U32(p_ctx) - 1;
|
|
if(next_sample > sample)
|
|
{
|
|
sample = (flags & VC_CONTAINER_SEEK_FLAG_FORWARD) ? next_sample : prev_sample;
|
|
break;
|
|
}
|
|
prev_sample = next_sample;
|
|
}
|
|
|
|
/* Do the seek on this track and use its timestamp as the new seek point */
|
|
status = mp4_seek_track(p_ctx, track, &track_module->state, sample);
|
|
if(status != VC_CONTAINER_SUCCESS) goto seek_time_found;
|
|
seek_time = track_module->state.pts;
|
|
|
|
seek_time_found:
|
|
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
{
|
|
uint32_t sample;
|
|
track_module = p_ctx->tracks[i]->priv->module;
|
|
if(track_module->state.offset) continue;
|
|
sample = mp4_find_sample( p_ctx, i, &track_module->state, seek_time, &status );
|
|
if(status != VC_CONTAINER_SUCCESS) return status; //FIXME
|
|
|
|
status = mp4_seek_track(p_ctx, i, &track_module->state, sample);
|
|
}
|
|
|
|
*offset = seek_time;
|
|
return VC_CONTAINER_SUCCESS;
|
|
}
|
|
|
|
/******************************************************************************
|
|
Global function definitions.
|
|
******************************************************************************/
|
|
|
|
VC_CONTAINER_STATUS_T mp4_reader_open( VC_CONTAINER_T *p_ctx )
|
|
{
|
|
VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
VC_CONTAINER_MODULE_T *module = 0;
|
|
unsigned int i;
|
|
uint8_t h[8];
|
|
|
|
/* Check for a known box type to see if we're dealing with mp4 */
|
|
if( PEEK_BYTES(p_ctx, h, 8) != 8 )
|
|
return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
switch(VC_FOURCC(h[4],h[5],h[6],h[7]))
|
|
{
|
|
case MP4_BOX_TYPE_FTYP:
|
|
case MP4_BOX_TYPE_MDAT:
|
|
case MP4_BOX_TYPE_MOOV:
|
|
case MP4_BOX_TYPE_FREE:
|
|
case MP4_BOX_TYPE_SKIP:
|
|
case MP4_BOX_TYPE_WIDE:
|
|
case MP4_BOX_TYPE_PNOT:
|
|
case MP4_BOX_TYPE_PICT:
|
|
case MP4_BOX_TYPE_UDTA:
|
|
case MP4_BOX_TYPE_UUID:
|
|
break;
|
|
default:
|
|
/* Couldn't recognize the box type. This doesn't look like an mp4. */
|
|
return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
|
|
}
|
|
|
|
/*
|
|
* We are dealing with an MP4 file
|
|
*/
|
|
|
|
LOG_DEBUG(p_ctx, "using mp4 reader");
|
|
|
|
/* Allocate our context */
|
|
module = malloc(sizeof(*module));
|
|
if(!module) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; }
|
|
memset(module, 0, sizeof(*module));
|
|
p_ctx->priv->module = module;
|
|
p_ctx->tracks = module->tracks;
|
|
|
|
while(STREAM_STATUS(p_ctx) == VC_CONTAINER_SUCCESS)
|
|
{
|
|
MP4_BOX_TYPE_T box_type;
|
|
int64_t box_size;
|
|
|
|
status = mp4_read_box_header( p_ctx, INT64_C(-1), &box_type, &box_size );
|
|
if(status != VC_CONTAINER_SUCCESS) goto error;
|
|
|
|
if(box_type == MP4_BOX_TYPE_MDAT)
|
|
{
|
|
module->data_offset = STREAM_POSITION(p_ctx);
|
|
module->data_size = box_size;
|
|
if(module->found_moov) break; /* We've got everything we want */
|
|
}
|
|
else if(box_type == MP4_BOX_TYPE_MOOV)
|
|
module->found_moov = true;
|
|
|
|
status = mp4_read_box_data( p_ctx, box_type, box_size, MP4_BOX_TYPE_ROOT );
|
|
if(status != VC_CONTAINER_SUCCESS) goto error;
|
|
|
|
if(module->found_moov && module->data_offset) break; /* We've got everything we want */
|
|
}
|
|
|
|
/* Initialise tracks */
|
|
for(i = 0; i < p_ctx->tracks_num; i++)
|
|
{
|
|
/* FIXME: we should check we've got at least one success */
|
|
status = mp4_read_sample_header(p_ctx, i, &p_ctx->tracks[i]->priv->module->state);
|
|
}
|
|
|
|
status = SEEK(p_ctx, module->data_offset);
|
|
if(status != VC_CONTAINER_SUCCESS) goto error;
|
|
|
|
p_ctx->priv->pf_close = mp4_reader_close;
|
|
p_ctx->priv->pf_read = mp4_reader_read;
|
|
p_ctx->priv->pf_seek = mp4_reader_seek;
|
|
|
|
if(STREAM_SEEKABLE(p_ctx))
|
|
p_ctx->capabilities |= VC_CONTAINER_CAPS_CAN_SEEK;
|
|
|
|
return VC_CONTAINER_SUCCESS;
|
|
|
|
error:
|
|
LOG_DEBUG(p_ctx, "mp4: error opening stream");
|
|
if(module) mp4_reader_close(p_ctx);
|
|
return status;
|
|
}
|
|
|
|
/********************************************************************************
|
|
Entrypoint function
|
|
********************************************************************************/
|
|
|
|
#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__)
|
|
# pragma weak reader_open mp4_reader_open
|
|
#endif
|