system: Linux mars.sprixweb.com 3.10.0-1160.119.1.el7.x86_64 #1 SMP Tue Jun 4 14:43:51 UTC 2024 x86_64
/*******************************************************************************
mp4_reader.c - A library for reading MPEG4.
Copyright (C) 2007-2009 CodeShop B.V.
http://www.code-shop.com
For licensing see the LICENSE file
******************************************************************************/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef __cplusplus
#define __STDC_FORMAT_MACROS // C++ should define this for PRIu64
#define __STDC_LIMIT_MACROS // C++ should define this for UINT64_MAX
#endif
#include "mp4_reader.h"
#include "mp4_io.h"
#include <stdlib.h>
#include <string.h>
struct atom_t
{
uint32_t type_;
uint32_t short_size_;
uint64_t size_;
unsigned char* start_;
unsigned char* end_;
};
typedef struct atom_t atom_t;
static void atom_print(mp4_context_t const* mp4_context, atom_t const* atom)
{
MP4_INFO("Atom(%c%c%c%c,%"PRIu64")\n",
atom->type_ >> 24,
atom->type_ >> 16,
atom->type_ >> 8,
atom->type_,
atom->size_);
}
static unsigned char*
atom_read_header(mp4_context_t const* mp4_context, unsigned char* buffer,
atom_t* atom)
{
atom->start_ = buffer;
atom->short_size_ = read_32(buffer);
atom->type_ = read_32(buffer + 4);
if(atom->short_size_ == 1)
atom->size_ = read_64(buffer + 8);
else
atom->size_ = atom->short_size_;
atom->end_ = atom->start_ + atom->size_;
atom_print(mp4_context, atom);
if(atom->size_ < ATOM_PREAMBLE_SIZE)
{
MP4_ERROR("%s", "Error: invalid atom size\n");
return 0;
}
return buffer + ATOM_PREAMBLE_SIZE + (atom->short_size_ == 1 ? 8 : 0);
}
static struct unknown_atom_t* unknown_atom_add_atom(struct unknown_atom_t* parent, void* atom)
{
size_t size = read_32((const unsigned char*)atom);
unknown_atom_t* unknown = unknown_atom_init();
unknown->atom_ = malloc(size);
memcpy(unknown->atom_, atom, size);
{
unknown_atom_t** adder = &parent;
while(*adder != NULL)
{
adder = &(*adder)->next_;
}
*adder = unknown;
}
return parent;
}
extern int atom_reader(struct mp4_context_t const* mp4_context,
struct atom_read_list_t* atom_read_list,
unsigned int atom_read_list_size,
void* parent,
unsigned char* buffer, uint64_t size)
{
atom_t leaf_atom;
unsigned char* buffer_start = buffer;
while(buffer < buffer_start + size)
{
unsigned int i;
buffer = atom_read_header(mp4_context, buffer, &leaf_atom);
if(buffer == NULL)
{
return 0;
}
for(i = 0; i != atom_read_list_size; ++i)
{
if(leaf_atom.type_ == atom_read_list[i].type_)
{
break;
}
}
if(i == atom_read_list_size)
{
// add to unkown chunks
(*(unknown_atom_t**)parent) =
unknown_atom_add_atom(*(unknown_atom_t**)(parent), buffer - ATOM_PREAMBLE_SIZE);
}
else
{
void* child =
atom_read_list[i].reader_(mp4_context, parent, buffer,
leaf_atom.size_ - ATOM_PREAMBLE_SIZE);
if(!child)
break;
if(!atom_read_list[i].destination_(mp4_context, parent, child))
break;
}
buffer = leaf_atom.end_;
}
if(buffer < buffer_start + size)
{
return 0;
}
return 1;
}
static void* ctts_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
ctts_t* atom;
if(size < 8)
return 0;
atom = ctts_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->entries_ = read_32(buffer + 4);
if(size < 8 + atom->entries_ * sizeof(ctts_table_t))
return 0;
buffer += 8;
atom->table_ = (ctts_table_t*)(malloc(atom->entries_ * sizeof(ctts_table_t)));
for(i = 0; i != atom->entries_; ++i)
{
atom->table_[i].sample_count_ = read_32(buffer + 0);
atom->table_[i].sample_offset_ = read_32(buffer + 4);
buffer += 8;
}
return atom;
}
static void* stco_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
stco_t* atom;
if(size < 8)
return 0;
atom = stco_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->entries_ = read_32(buffer + 4);
buffer += 8;
if(size < 8 + atom->entries_ * sizeof(uint32_t))
return 0;
atom->chunk_offsets_ = (uint64_t*)malloc(atom->entries_ * sizeof(uint64_t));
for(i = 0; i != atom->entries_; ++i)
{
atom->chunk_offsets_[i] = read_32(buffer);
buffer += 4;
}
return atom;
}
static void* co64_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
stco_t* atom;
if(size < 8)
return 0;
atom = stco_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->entries_ = read_32(buffer + 4);
buffer += 8;
if(size < 8 + atom->entries_ * sizeof(uint64_t))
return 0;
atom->chunk_offsets_ = (uint64_t*)malloc(atom->entries_ * sizeof(uint64_t));
for(i = 0; i != atom->entries_; ++i)
{
atom->chunk_offsets_[i] = read_64(buffer);
buffer += 8;
}
return atom;
}
static void* stsz_read(mp4_context_t const* mp4_context,
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
stsz_t* atom;
if(size < 12)
{
MP4_ERROR("%s", "Error: not enough bytes for stsz atom\n");
return 0;
}
atom = stsz_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->sample_size_ = read_32(buffer + 4);
atom->entries_ = read_32(buffer + 8);
buffer += 12;
// fix for clayton.mp4, it mistakenly says there is 1 entry
// if(atom->sample_size_ && atom->entries_)
// atom->entries_ = 0;
if(!atom->sample_size_)
{
if(size < 12 + atom->entries_ * sizeof(uint32_t))
{
MP4_ERROR("%s", "Error: stsz.entries don't match with size\n");
stsz_exit(atom);
return 0;
}
atom->sample_sizes_ = (uint32_t*)malloc(atom->entries_ * sizeof(uint32_t));
for(i = 0; i != atom->entries_; ++i)
{
atom->sample_sizes_[i] = read_32(buffer);
buffer += 4;
}
}
return atom;
}
static void* stsc_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
stsc_t* atom;
if(size < 8)
return 0;
atom = stsc_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->entries_ = read_32(buffer + 4);
if(size < 8 + atom->entries_ * sizeof(stsc_table_t))
return 0;
buffer += 8;
// reserve space for one extra entry as when splitting the video we may have to
// split the first entry
atom->table_ = (stsc_table_t*)(malloc((atom->entries_ + 1) * sizeof(stsc_table_t)));
for(i = 0; i != atom->entries_; ++i)
{
atom->table_[i].chunk_ = read_32(buffer + 0) - 1; // Note: we use zero based
atom->table_[i].samples_ = read_32(buffer + 4);
atom->table_[i].id_ = read_32(buffer + 8);
buffer += 12;
}
return atom;
}
static void* stss_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
stss_t* atom;
if(size < 8)
return 0;
atom = stss_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->entries_ = read_32(buffer + 4);
if(size < 8 + atom->entries_ * sizeof(uint32_t))
return 0;
buffer += 8;
atom->sample_numbers_ = (uint32_t*)malloc(atom->entries_ * sizeof(uint32_t));
for(i = 0; i != atom->entries_; ++i)
{
atom->sample_numbers_[i] = read_32(buffer);
buffer += 4;
}
return atom;
}
static int mp4_read_desc_len(unsigned char** buffer)
{
uint32_t len = 0;
unsigned int bytes = 0;
for(;;)
{
unsigned int c = read_8(*buffer + bytes);
len <<= 7;
len |= (c & 0x7f);
if(++bytes == 4)
break;
if(!(c & 0x80))
break;
}
*buffer += bytes;
return len;
}
static int esds_read(mp4_context_t const* mp4_context,
sample_entry_t* sample_entry,
unsigned char* buffer, uint64_t size)
{
unsigned int tag;
unsigned int len;
uint16_t stream_id;
unsigned int stream_priority;
unsigned int object_type_id;
unsigned int stream_type;
unsigned int buffer_size_db;
if(size < 9)
return 0;
/* unsigned char version = */ read_8(buffer);
/* unsigned int flags = */ read_24(buffer + 1);
buffer += 4;
// ES_DescrTag
tag = read_8(buffer);
buffer += 1;
if(tag == MP4_ELEMENTARY_STREAM_DESCRIPTOR_TAG)
{
len = mp4_read_desc_len(&buffer);
MP4_INFO("Elementary Stream Descriptor: len=%u\n", len);
stream_id = read_16(buffer + 0);
stream_priority = read_8(buffer + 2);
buffer += 3;
}
else
{
MP4_INFO("Elementary Stream Descriptor: len=%u\n", 2);
stream_id = read_16(buffer + 0);
buffer += 2;
}
tag = read_8(buffer);
buffer += 1;
len = mp4_read_desc_len(&buffer);
MP4_INFO("MPEG: tag=%u len=%u\n", tag, len);
// Decoder config descr Tag
if(tag != MP4_DECODER_CONFIG_DESCRIPTOR_TAG)
{
MP4_INFO("Decoder Config Descriptor: len=%u\n", len);
return 0;
}
object_type_id = read_8(buffer);
buffer += 1; // object_type_id
stream_type = read_8(buffer);
buffer += 1; // stream_type
buffer_size_db = read_24(buffer);
buffer += 3; // buffer_size_db
sample_entry->max_bitrate_ = read_32(buffer);
buffer += 4;
sample_entry->avg_bitrate_ = read_32(buffer);
buffer += 4; // avg_bitrate
MP4_INFO("%s", "Decoder Configuration Descriptor:\n");
MP4_INFO(" object_type_id=$%02x\n", object_type_id);
MP4_INFO(" stream_type=%u\n", stream_type);
MP4_INFO(" buffer_size_db=%u\n", buffer_size_db);
MP4_INFO(" max_bitrate=%u\n", sample_entry->max_bitrate_);
MP4_INFO(" avg_bitrate=%u\n", sample_entry->avg_bitrate_);
switch(object_type_id)
{
case MP4_MPEG2AudioMain:
case MP4_MPEG2AudioLowComplexity:
case MP4_MPEG2AudioScaleableSamplingRate:
case MP4_MPEG4Audio:
sample_entry->wFormatTag = 0x00ff; // WAVE_FORMAT_RAW_AAC1
break;
case MP4_MPEG1Audio:
case MP4_MPEG2AudioPart3:
sample_entry->wFormatTag = 0x0055; // WAVE_FORMAT_MP3
break;
default:
break;
}
if(!sample_entry->nAvgBytesPerSec)
{
unsigned int bitrate = sample_entry->avg_bitrate_;
if(!bitrate)
bitrate = sample_entry->max_bitrate_;
sample_entry->nAvgBytesPerSec = bitrate / 8;
}
tag = read_8(buffer);
buffer += 1;
len = mp4_read_desc_len(&buffer);
MP4_INFO("MPEG: tag=%u len=%u\n", tag, len);
// Decoder specific descr Tag
if(tag == MP4_DECODER_SPECIFIC_DESCRIPTOR_TAG)
{
MP4_INFO("Decoder Specific Info Descriptor: len=%u\n", len);
sample_entry->codec_private_data_length_ = len;
sample_entry->codec_private_data_ = buffer;
}
return 1;
}
static int
stsd_parse_vide(mp4_context_t const* mp4_context,
trak_t* UNUSED(trak),
sample_entry_t* sample_entry)
{
unsigned char* buffer = sample_entry->buf_;
unsigned char* buffer_start = buffer;
uint64_t size = sample_entry->len_;
unsigned int i;
if(size < 78)
{
MP4_ERROR("%s", "invalid stsd video size\n");
return 0;
}
// 'ovc1' is immediately followed by additional data and ends with the codec
// private data, *not* by additional atoms.
if(sample_entry->fourcc_ == FOURCC('o', 'v', 'c', '1'))
{
// TODO: get start of ovc1 codec private data (25 00 00 01 0F CB 6C 1A ..)
sample_entry->codec_private_data_ = buffer + 190;
sample_entry->codec_private_data_length_ = (unsigned int)(size - 190);
return 1;
}
if(size >= 78 + ATOM_PREAMBLE_SIZE)
{
atom_t atom;
buffer += 78;
while(buffer < buffer_start + size - ATOM_PREAMBLE_SIZE)
{
buffer = atom_read_header(mp4_context, buffer, &atom);
if(buffer == NULL)
{
return 0;
}
switch(atom.type_)
{
case FOURCC('a', 'v', 'c', 'C'):
{
unsigned int sequence_parameter_sets;
unsigned int picture_parameter_sets;
unsigned int configuration_version;
unsigned int profile_indication;
unsigned int profile_compatibility;
unsigned int level_indication;
sample_entry->codec_private_data_ = buffer;
configuration_version = read_8(buffer + 0);
profile_indication = read_8(buffer + 1);
profile_compatibility = read_8(buffer + 2);
level_indication = read_8(buffer + 3);
sample_entry->nal_unit_length_ = (read_8(buffer + 4) & 3) + 1;
sequence_parameter_sets = read_8(buffer + 5) & 0x1f;
buffer += 6;
for(i = 0; i != sequence_parameter_sets; ++i)
{
unsigned int sps_length = read_16(buffer);
buffer += 2;
sample_entry->sps_ = buffer;
sample_entry->sps_length_ = sps_length;
buffer += sps_length;
}
picture_parameter_sets = read_8(buffer);
buffer += 1;
for(i = 0; i != picture_parameter_sets; ++i)
{
unsigned int pps_length = read_16(buffer);
buffer += 2;
sample_entry->pps_ = buffer;
sample_entry->pps_length_ = pps_length;
buffer += pps_length;
}
sample_entry->codec_private_data_length_ =
(unsigned int)(buffer - sample_entry->codec_private_data_);
}
break;
case FOURCC('e', 's', 'd', 's'):
if(!esds_read(mp4_context, sample_entry, buffer, atom.size_ - ATOM_PREAMBLE_SIZE))
{
return 0;
}
break;
default:
break;
}
buffer = atom.end_;
}
}
return 1;
}
static int wave_read(mp4_context_t const* mp4_context,
sample_entry_t* sample_entry,
unsigned char* buffer, uint64_t size)
{
unsigned char* end = buffer + size;
while(buffer < end)
{
atom_t atom;
buffer = atom_read_header(mp4_context, buffer, &atom);
if(buffer == NULL)
{
return 0;
}
switch(atom.type_)
{
case FOURCC('e', 's', 'd', 's'):
if(!esds_read(mp4_context, sample_entry, buffer, atom.size_ - ATOM_PREAMBLE_SIZE))
{
return 0;
}
break;
}
buffer = atom.end_;
}
return 1;
}
static int
stsd_parse_soun(mp4_context_t const* mp4_context,
trak_t* UNUSED(trak),
sample_entry_t* sample_entry)
{
unsigned char* buffer = sample_entry->buf_;
unsigned char* buffer_start = buffer;
uint64_t size = sample_entry->len_;
unsigned int data_reference_index;
unsigned int version;
unsigned int revision;
unsigned int vendor;
unsigned int compression_id;
unsigned int packet_size;
if(sample_entry->len_ < 28)
return 0;
data_reference_index = read_16(buffer + 6);
version = read_16(buffer + 8);
revision = read_16(buffer + 10);
vendor = read_32(buffer + 12);
sample_entry->nChannels = read_16(buffer + 16);
if(sample_entry->nChannels == 3)
sample_entry->nChannels = 6;
sample_entry->wBitsPerSample = read_16(buffer + 18);
compression_id = read_16(buffer + 20);
packet_size = read_16(buffer + 22);
// samplerate = {timescale of media} << 16
sample_entry->samplerate_hi_ = read_16(buffer + 24);
sample_entry->samplerate_lo_ = read_16(buffer + 26);
sample_entry->nSamplesPerSec = sample_entry->samplerate_hi_;
MP4_INFO("%s", "Sample Entry:\n");
MP4_INFO(" data_reference_index=%u\n", data_reference_index);
MP4_INFO(" version=%u\n", version);
MP4_INFO(" revision=%u\n", revision);
MP4_INFO(" vendor=%08x\n", vendor);
MP4_INFO(" channel_count=%u\n", sample_entry->nChannels);
MP4_INFO(" sample_size=%u\n", sample_entry->wBitsPerSample);
MP4_INFO(" compression_id=%u\n", compression_id);
MP4_INFO(" packet_size=%u\n", packet_size);
MP4_INFO(" samplerate_hi=%u\n", sample_entry->samplerate_hi_);
MP4_INFO(" samplerate_lo=%u\n", sample_entry->samplerate_lo_);
buffer += 28;
// 'owma' is immediately followed by the codec private data, *not* by
// additional atoms.
if(sample_entry->fourcc_ == FOURCC('o', 'w', 'm', 'a'))
{
sample_entry->codec_private_data_ = buffer;
sample_entry->codec_private_data_length_ = (unsigned int)(size - 28);
// sample_entry->wFormatTag = read_16(buffer + 0);
// ...
return 1;
}
if(version >= 1)
{
unsigned int samples_per_packet;
unsigned int bytes_per_packet;
unsigned int bytes_per_frame;
unsigned int bytes_per_sample;
if(version == 1 && size < 28 + 16)
{
return 0;
}
if(version == 2 && size < 28 + 36)
{
return 0;
}
if(version > 2)
{
return 0;
}
samples_per_packet = read_32(buffer + 0);
bytes_per_packet = read_32(buffer + 4);
bytes_per_frame = read_32(buffer + 8);
bytes_per_sample = read_32(buffer + 12);
MP4_INFO(" samples_per_packet=%u\n", samples_per_packet);
MP4_INFO(" bytes_per_packet=%u\n", bytes_per_packet);
MP4_INFO(" bytes_per_frame=%u\n", bytes_per_frame);
MP4_INFO(" bytes_per_sample=%u\n", bytes_per_sample);
if(samples_per_packet > 0)
{
sample_entry->nAvgBytesPerSec =
(sample_entry->nChannels * sample_entry->nSamplesPerSec *
bytes_per_packet + samples_per_packet / 2) / samples_per_packet;
sample_entry->nBlockAlign = (uint16_t)bytes_per_frame;
}
else
{
sample_entry->nAvgBytesPerSec =
sample_entry->nChannels * sample_entry->nSamplesPerSec *
sample_entry->wBitsPerSample / 8;
}
buffer += version == 1 ? 16 : 36;
}
if(buffer - buffer_start >= ATOM_PREAMBLE_SIZE)
{
atom_t atom;
while(buffer < buffer_start + size - ATOM_PREAMBLE_SIZE)
{
buffer = atom_read_header(mp4_context, buffer, &atom);
if(buffer == NULL)
{
return 0;
}
switch(atom.type_)
{
case FOURCC('w', 'a', 'v', 'e'):
if(!wave_read(mp4_context, sample_entry, buffer, atom.size_ - ATOM_PREAMBLE_SIZE))
{
return 0;
}
break;
case FOURCC('f', 'r', 'm', 'a'):
break;
case FOURCC('e', 's', 'd', 's'):
if(!esds_read(mp4_context, sample_entry, buffer, atom.size_ - ATOM_PREAMBLE_SIZE))
{
return 0;
}
break;
default:
break;
}
buffer = atom.end_;
}
}
return 1;
}
static int stsd_parse(mp4_context_t const* mp4_context,
trak_t* trak, stsd_t* stsd)
{
unsigned int i;
for(i = 0; i != stsd->entries_; ++i)
{
sample_entry_t* sample_entry = &stsd->sample_entries_[i];
switch(trak->mdia_->hdlr_->handler_type_)
{
case FOURCC('v', 'i', 'd', 'e'):
if(!stsd_parse_vide(mp4_context, trak, sample_entry))
{
return 0;
}
break;
case FOURCC('s', 'o', 'u', 'n'):
if(!stsd_parse_soun(mp4_context, trak, sample_entry))
{
return 0;
}
break;
case FOURCC('h', 'i', 'n', 't'):
default:
return 1;
}
}
return 1;
}
static void* stts_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
stts_t* atom;
if(size < 8)
return 0;
atom = stts_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->entries_ = read_32(buffer + 4);
if(size < 8 + atom->entries_ * sizeof(stts_table_t))
return 0;
buffer += 8;
atom->table_ = (stts_table_t*)(malloc(atom->entries_ * sizeof(stts_table_t)));
for(i = 0; i != atom->entries_; ++i)
{
atom->table_[i].sample_count_ = read_32(buffer + 0);
atom->table_[i].sample_duration_ = read_32(buffer + 4);
buffer += 8;
}
return atom;
}
static void* stsd_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
stsd_t* atom;
if(size < 8)
return 0;
atom = stsd_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->entries_ = read_32(buffer + 4);
buffer += 8;
atom->sample_entries_ = (sample_entry_t*)(malloc(atom->entries_ * sizeof(sample_entry_t)));
for(i = 0; i != atom->entries_; ++i)
{
unsigned int j;
sample_entry_t* sample_entry = &atom->sample_entries_[i];
sample_entry_init(sample_entry);
sample_entry->len_ = read_32(buffer) - 8;
sample_entry->fourcc_ = read_32(buffer + 4);
sample_entry->buf_ = (unsigned char*)malloc(sample_entry->len_);
buffer += 8;
for(j = 0; j != sample_entry->len_; ++j)
{
sample_entry->buf_[j] = (unsigned char)read_8(buffer + j);
}
buffer += j;
}
return atom;
}
static int stbl_add_stsd(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
stbl_t* stbl = (stbl_t*)parent;
stbl->stsd_ = (stsd_t*)child;
return 1;
}
static int stbl_add_stts(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
stbl_t* stbl = (stbl_t*)parent;
stbl->stts_ = (stts_t*)child;
return 1;
}
static int stbl_add_stss(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
stbl_t* stbl = (stbl_t*)parent;
stbl->stss_ = (stss_t*)child;
return 1;
}
static int stbl_add_stsc(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
stbl_t* stbl = (stbl_t*)parent;
stbl->stsc_ = (stsc_t*)child;
return 1;
}
static int stbl_add_stsz(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
stbl_t* stbl = (stbl_t*)parent;
stbl->stsz_ = (stsz_t*)child;
return 1;
}
static int stbl_add_stco(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
stbl_t* stbl = (stbl_t*)parent;
stbl->stco_ = (stco_t*)child;
return 1;
}
static int stbl_add_ctts(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
stbl_t* stbl = (stbl_t*)parent;
stbl->ctts_ = (ctts_t*)child;
return 1;
}
static void* stbl_read(mp4_context_t const* mp4_context,
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
stbl_t* atom = stbl_init();
atom_read_list_t atom_read_list[] = {
{ FOURCC('s', 't', 's', 'd'), &stbl_add_stsd, &stsd_read },
{ FOURCC('s', 't', 't', 's'), &stbl_add_stts, &stts_read },
{ FOURCC('s', 't', 's', 's'), &stbl_add_stss, &stss_read },
{ FOURCC('s', 't', 's', 'c'), &stbl_add_stsc, &stsc_read },
{ FOURCC('s', 't', 's', 'z'), &stbl_add_stsz, &stsz_read },
{ FOURCC('s', 't', 'c', 'o'), &stbl_add_stco, &stco_read },
{ FOURCC('c', 'o', '6', '4'), &stbl_add_stco, &co64_read },
{ FOURCC('c', 't', 't', 's'), &stbl_add_ctts, &ctts_read },
};
int result = atom_reader(mp4_context,
atom_read_list,
sizeof(atom_read_list) / sizeof(atom_read_list[0]),
atom,
buffer, size);
// check for mandatory atoms
if(!atom->stsd_)
{
MP4_ERROR("%s", "stbl: missing mandatory stsd\n");
result = 0;
}
if(!atom->stts_)
{
MP4_ERROR("%s", "stbl: missing mandatory stts\n");
result = 0;
}
// Expression Encoder doesn't write mandatory stsz atom
if(!atom->stsc_)
{
MP4_ERROR("%s", "stbl: missing mandatory stsc\n");
// result = 0;
}
// Expression Encoder doesn't write mandatory stsz atom
if(!atom->stsz_)
{
MP4_ERROR("%s", "stbl: missing mandatory stsz\n");
// result = 0;
}
// Expression Encoder doesn't write mandatory stco atom
if(!atom->stco_)
{
MP4_ERROR("%s", "stbl: missing mandatory stco\n");
// result = 0;
}
if(!result)
{
stbl_exit(atom);
return 0;
}
return atom;
}
static void* hdlr_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
hdlr_t* atom;
if(size < 8)
return 0;
atom = hdlr_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->predefined_ = read_32(buffer + 4);
atom->handler_type_ = read_32(buffer + 8);
atom->reserved1_ = read_32(buffer + 12);
atom->reserved2_ = read_32(buffer + 16);
atom->reserved3_ = read_32(buffer + 20);
buffer += 24;
size -= 24;
if(size > 0)
{
size_t length = (size_t)size;
atom->name_ = (char*)malloc(length + 1);
if(atom->predefined_ == FOURCC('m', 'h', 'l', 'r'))
{
length = read_8(buffer);
buffer += 1;
if(size < length)
length = (size_t)size;
}
memcpy(atom->name_, buffer, length);
atom->name_[length] = '\0';
}
return atom;
}
static void* vmhd_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
vmhd_t* atom;
if(size < 12)
return 0;
atom = vmhd_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->graphics_mode_ = read_16(buffer + 4);
buffer += 6;
for(i = 0; i != 3; ++i)
{
atom->opcolor_[i] = read_16(buffer);
buffer += 2;
}
return atom;
}
static void* smhd_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
smhd_t* atom;
if(size < 8)
return 0;
atom = smhd_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->balance_ = read_16(buffer + 4);
atom->reserved_ = read_16(buffer + 6);
return atom;
}
static int dinf_add_dref(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
dinf_t* dinf = (dinf_t*)parent;
dinf->dref_ = (dref_t*)child;
return 1;
}
static void* dref_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
dref_t* atom;
if(size < 20)
return 0;
atom = dref_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->entry_count_ = read_32(buffer + 4);
atom->table_ = atom->entry_count_ == 0 ? NULL : (dref_table_t*)malloc(atom->entry_count_ * sizeof(dref_table_t));
buffer += 8;
for(i = 0; i != atom->entry_count_; ++i)
{
dref_table_t* entry = &atom->table_[i];
uint32_t entry_size = read_32(buffer + 0);
uint32_t type = read_32(buffer + 4);
uint32_t flags = read_32(buffer + 8);
dref_table_init(entry);
entry->flags_ = flags;
if(flags != 0x000001)
{
switch(type)
{
case FOURCC('u', 'r', 'n', ' '):
// read name and location (optional) as UTF8 zero-terminated string
break;
case FOURCC('u', 'r', 'l', ' '):
// read location as UTF8 zero-terminated string
break;
}
}
buffer += entry_size;
}
return atom;
}
static void* dinf_read(mp4_context_t const* mp4_context,
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
dinf_t* atom = dinf_init();
atom_read_list_t atom_read_list[] = {
{ FOURCC('d', 'r', 'e', 'f'), &dinf_add_dref, &dref_read },
};
int result = atom_reader(mp4_context,
atom_read_list,
sizeof(atom_read_list) / sizeof(atom_read_list[0]),
atom,
buffer, size);
// check for mandatory atoms
if(!atom->dref_)
{
MP4_ERROR("%s", "dinf: missing dref\n");
result = 0;
}
if(!result)
{
dinf_exit(atom);
return 0;
}
return atom;
}
static int minf_add_vmhd(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
minf_t* minf = (minf_t*)parent;
minf->vmhd_ = (vmhd_t*)child;
return 1;
}
static int minf_add_smhd(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
minf_t* minf = (minf_t*)parent;
minf->smhd_ = (smhd_t*)child;
return 1;
}
static int minf_add_dinf(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
minf_t* minf = (minf_t*)parent;
minf->dinf_ = (dinf_t*)child;
return 1;
}
static int minf_add_stbl(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
minf_t* minf = (minf_t*)parent;
minf->stbl_ = (stbl_t*)child;
return 1;
}
static void* minf_read(mp4_context_t const* mp4_context,
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
minf_t* atom = minf_init();
atom_read_list_t atom_read_list[] = {
{ FOURCC('v', 'm', 'h', 'd'), &minf_add_vmhd, &vmhd_read },
{ FOURCC('s', 'm', 'h', 'd'), &minf_add_smhd, &smhd_read },
{ FOURCC('d', 'i', 'n', 'f'), &minf_add_dinf, &dinf_read },
{ FOURCC('s', 't', 'b', 'l'), &minf_add_stbl, &stbl_read }
};
int result = atom_reader(mp4_context,
atom_read_list,
sizeof(atom_read_list) / sizeof(atom_read_list[0]),
atom,
buffer, size);
// check for mandatory atoms
if(!atom->stbl_)
{
MP4_ERROR("%s", "minf: missing stbl\n");
result = 0;
}
if(!result)
{
minf_exit(atom);
return 0;
}
return atom;
}
static void* mdhd_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t UNUSED(size))
{
uint16_t language;
unsigned int i;
mdhd_t* mdhd = mdhd_init();
mdhd->version_ = read_8(buffer + 0);
mdhd->flags_ = read_24(buffer + 1);
if(mdhd->version_ == 0)
{
mdhd->creation_time_ = read_32(buffer + 4);
mdhd->modification_time_ = read_32(buffer + 8);
mdhd->timescale_ = read_32(buffer + 12);
mdhd->duration_ = read_32(buffer + 16);
buffer += 20;
}
else
{
mdhd->creation_time_ = read_64(buffer + 4);
mdhd->modification_time_ = read_64(buffer + 12);
mdhd->timescale_ = read_32(buffer + 20);
mdhd->duration_ = read_64(buffer + 24);
buffer += 32;
}
language = read_16(buffer + 0);
for(i = 0; i != 3; ++i)
{
mdhd->language_[i] = ((language >> ((2 - i) * 5)) & 0x1f) + 0x60;
}
mdhd->predefined_ = read_16(buffer + 2);
return mdhd;
}
static void* tkhd_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
tkhd_t* tkhd = tkhd_init();
tkhd->version_ = read_8(buffer + 0);
tkhd->flags_ = read_24(buffer + 1);
if(tkhd->version_ == 0)
{
if(size < 92-8)
return 0;
tkhd->creation_time_ = read_32(buffer + 4);
tkhd->modification_time_ = read_32(buffer + 8);
tkhd->track_id_ = read_32(buffer + 12);
tkhd->reserved_ = read_32(buffer + 16);
tkhd->duration_ = read_32(buffer + 20);
buffer += 24;
}
else
{
if(size < 104-8)
return 0;
tkhd->creation_time_ = read_64(buffer + 4);
tkhd->modification_time_ = read_64(buffer + 12);
tkhd->track_id_ = read_32(buffer + 20);
tkhd->reserved_ = read_32(buffer + 24);
tkhd->duration_ = read_64(buffer + 28);
buffer += 36;
}
tkhd->reserved2_[0] = read_32(buffer + 0);
tkhd->reserved2_[1] = read_32(buffer + 4);
tkhd->layer_ = read_16(buffer + 8);
tkhd->predefined_ = read_16(buffer + 10);
tkhd->volume_ = read_16(buffer + 12);
tkhd->reserved3_ = read_16(buffer + 14);
buffer += 16;
for(i = 0; i != 9; ++i)
{
tkhd->matrix_[i] = read_32(buffer);
buffer += 4;
}
tkhd->width_ = read_32(buffer + 0);
tkhd->height_ = read_32(buffer + 4);
return tkhd;
}
static int mdia_add_mdhd(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
mdia_t* mdia = (mdia_t*)parent;
mdia->mdhd_ = (mdhd_t*)child;
return 1;
}
static int mdia_add_hdlr(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
mdia_t* mdia = (mdia_t*)parent;
mdia->hdlr_ = (hdlr_t*)child;
return 1;
}
static int mdia_add_minf(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
mdia_t* mdia = (mdia_t*)parent;
mdia->minf_ = (minf_t*)child;
return 1;
}
static void* mdia_read(mp4_context_t const* mp4_context,
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
mdia_t* atom = mdia_init();
atom_read_list_t atom_read_list[] = {
{ FOURCC('m', 'd', 'h', 'd'), &mdia_add_mdhd, &mdhd_read },
{ FOURCC('h', 'd', 'l', 'r'), &mdia_add_hdlr, &hdlr_read },
{ FOURCC('m', 'i', 'n', 'f'), &mdia_add_minf, &minf_read }
};
int result = atom_reader(mp4_context,
atom_read_list,
sizeof(atom_read_list) / sizeof(atom_read_list[0]),
atom,
buffer, size);
// check for mandatory atoms
if(!atom->mdhd_)
{
MP4_ERROR("%s", "mdia: missing mdhd\n");
result = 0;
}
if(!atom->hdlr_)
{
MP4_ERROR("%s", "mdia: missing hdlr\n");
result = 0;
}
if(!atom->minf_)
{
MP4_ERROR("%s", "mdia: missing minf\n");
result = 0;
}
if(!result)
{
mdia_exit(atom);
return 0;
}
return atom;
}
static void* elst_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
elst_t* atom;
if(size < 8)
return 0;
atom = elst_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->entry_count_ = read_32(buffer + 4);
buffer += 8;
atom->table_ = (elst_table_t*)(malloc(atom->entry_count_ * sizeof(elst_table_t)));
for(i = 0; i != atom->entry_count_; ++i)
{
elst_table_t* elst_table = &atom->table_[i];
if(atom->version_ == 0)
{
elst_table->segment_duration_ = read_32(buffer);
elst_table->media_time_ = (int32_t)read_32(buffer + 4);
buffer += 8;
}
else
{
elst_table->segment_duration_ = read_64(buffer);
elst_table->media_time_ = read_64(buffer + 8);
buffer += 16;
}
elst_table->media_rate_integer_ = read_16(buffer);
elst_table->media_rate_fraction_ = read_16(buffer + 2);
buffer += 4;
}
return atom;
}
static int edts_add_elst(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* elst)
{
edts_t* edts = (edts_t*)parent;
edts->elst_ = (elst_t*)elst;
return 1;
}
static void* edts_read(mp4_context_t const* mp4_context,
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
edts_t* atom = edts_init();
atom_read_list_t atom_read_list[] = {
{ FOURCC('e', 'l', 's', 't'), &edts_add_elst, &elst_read }
};
int result = atom_reader(mp4_context,
atom_read_list,
sizeof(atom_read_list) / sizeof(atom_read_list[0]),
atom,
buffer, size);
if(!result)
{
edts_exit(atom);
return 0;
}
return atom;
}
static int trak_add_tkhd(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* tkhd)
{
trak_t* trak = (trak_t*)parent;
trak->tkhd_ = (tkhd_t*)tkhd;
return 1;
}
static int trak_add_mdia(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* mdia)
{
trak_t* trak = (trak_t*)parent;
trak->mdia_ = (mdia_t*)mdia;
return 1;
}
static int trak_add_edts(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* edts)
{
trak_t* trak = (trak_t*)parent;
trak->edts_ = (edts_t*)edts;
return 1;
}
static void* trak_read(mp4_context_t const* mp4_context,
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
trak_t* atom = trak_init();
atom_read_list_t atom_read_list[] = {
{ FOURCC('t', 'k', 'h', 'd'), &trak_add_tkhd, &tkhd_read },
{ FOURCC('m', 'd', 'i', 'a'), &trak_add_mdia, &mdia_read },
{ FOURCC('e', 'd', 't', 's'), &trak_add_edts, &edts_read }
};
int result = atom_reader(mp4_context,
atom_read_list,
sizeof(atom_read_list) / sizeof(atom_read_list[0]),
atom,
buffer, size);
// check for mandatory atoms
if(!atom->tkhd_)
{
MP4_ERROR("%s", "trak: missing tkhd\n");
result = 0;
}
if(!atom->mdia_)
{
MP4_ERROR("%s", "trak: missing mdia\n");
result = 0;
}
if(result && !stsd_parse(mp4_context, atom, atom->mdia_->minf_->stbl_->stsd_))
{
MP4_ERROR("%s", "trak: error parsing stsd\n");
result = 0;
}
if(!result)
{
trak_exit(atom);
return 0;
}
return atom;
}
static void* mvhd_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
mvhd_t* atom = mvhd_init();
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
if(atom->version_ == 0)
{
if(size < 108-8)
return 0;
atom->creation_time_ = read_32(buffer + 4);
atom->modification_time_ = read_32(buffer + 8);
atom->timescale_ = read_32(buffer + 12);
atom->duration_ = read_32(buffer + 16);
buffer += 20;
}
else
{
if(size < 120-8)
return 0;
atom->creation_time_ = read_64(buffer + 4);
atom->modification_time_ = read_64(buffer + 12);
atom->timescale_ = read_32(buffer + 20);
atom->duration_ = read_64(buffer + 24);
buffer += 32;
}
atom->rate_ = read_32(buffer + 0);
atom->volume_ = read_16(buffer + 4);
atom->reserved1_ = read_16(buffer + 6);
atom->reserved2_[0] = read_32(buffer + 8);
atom->reserved2_[1] = read_32(buffer + 12);
buffer += 16;
for(i = 0; i != 9; ++i)
{
atom->matrix_[i] = read_32(buffer);
buffer += 4;
}
for(i = 0; i != 6; ++i)
{
atom->predefined_[i] = read_32(buffer);
buffer += 4;
}
atom->next_track_id_ = read_32(buffer + 0);
return atom;
}
static int moov_add_mvhd(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* mvhd)
{
moov_t* moov = (moov_t*)parent;
moov->mvhd_ = (mvhd_t*)mvhd;
return 1;
}
static int moov_add_trak(mp4_context_t const* mp4_context,
void* parent, void* child)
{
moov_t* moov = (moov_t*)parent;
trak_t* trak = (trak_t*)child;
if(moov->tracks_ == MAX_TRACKS)
{
trak_exit(trak);
return 0;
}
if(trak->mdia_->hdlr_->handler_type_ != FOURCC('v', 'i', 'd', 'e') &&
trak->mdia_->hdlr_->handler_type_ != FOURCC('s', 'o', 'u', 'n'))
{
MP4_INFO("Trak ignored (handler_type=%c%c%c%c, name=%s)\n",
trak->mdia_->hdlr_->handler_type_ >> 24,
trak->mdia_->hdlr_->handler_type_ >> 16,
trak->mdia_->hdlr_->handler_type_ >> 8,
trak->mdia_->hdlr_->handler_type_,
trak->mdia_->hdlr_->name_);
trak_exit(trak);
return 1; // continue
}
// check for tracks that have a duration, but no samples. This happens with
// Expression Encoder fragmented movie files.
if(trak->mdia_->minf_->stbl_->stco_ == 0 ||
(trak->mdia_->minf_->stbl_->stco_->entries_ == 0 &&
trak->mdia_->mdhd_->duration_))
{
trak->mdia_->mdhd_->duration_ = 0;
}
#if 0 // we can't ignore empty tracks, as the fragments may come later
// ignore empty track (unless LIVE)
if(trak->mdia_->mdhd_->duration_ == 0 && !moov->mvex_)
{
trak_exit(trak);
return 1; // continue
}
#endif
moov->traks_[moov->tracks_] = trak;
++moov->tracks_;
return 1;
}
static void* trex_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
trex_t* atom = trex_init();
if(size < 24)
return 0;
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->track_id_ = read_32(buffer + 4);
atom->default_sample_description_index_ = read_32(buffer + 8);
atom->default_sample_duration_ = read_32(buffer + 12);
atom->default_sample_size_ = read_32(buffer + 16);
atom->default_sample_flags_ = read_32(buffer + 20);
return atom;
}
static int moov_add_mvex(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* mvex)
{
moov_t* moov = (moov_t*)parent;
moov->mvex_ = (mvex_t*)mvex;
return 1;
}
static int mvex_add_trex(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
mvex_t* mvex = (mvex_t*)parent;
trex_t* trex = (trex_t*)child;
if(mvex->tracks_ == MAX_TRACKS)
{
trex_exit(trex);
return 0;
}
mvex->trexs_[mvex->tracks_] = trex;
++mvex->tracks_;
return 1;
}
static void* mvex_read(mp4_context_t const* mp4_context,
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
mvex_t* atom = mvex_init();
atom_read_list_t atom_read_list[] = {
{ FOURCC('t', 'r', 'e', 'x'), &mvex_add_trex, &trex_read }
};
int result = atom_reader(mp4_context,
atom_read_list,
sizeof(atom_read_list) / sizeof(atom_read_list[0]),
atom,
buffer, size);
// check for mandatory atoms
if(!atom->tracks_)
{
MP4_ERROR("%s", "mvex: missing trex\n");
result = 0;
}
if(!result)
{
mvex_exit(atom);
return 0;
}
return atom;
}
extern void* moov_read(mp4_context_t const* mp4_context,
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
moov_t* atom = moov_init();
atom_read_list_t atom_read_list[] = {
{ FOURCC('m', 'v', 'h', 'd'), &moov_add_mvhd, &mvhd_read },
{ FOURCC('t', 'r', 'a', 'k'), &moov_add_trak, &trak_read },
{ FOURCC('m', 'v', 'e', 'x'), &moov_add_mvex, &mvex_read }
};
int result = atom_reader(mp4_context,
atom_read_list,
sizeof(atom_read_list) / sizeof(atom_read_list[0]),
atom,
buffer, size);
// check for mandatory atoms
if(!atom->mvhd_)
{
MP4_ERROR("%s", "moov: missing mvhd\n");
result = 0;
}
if(!atom->tracks_)
{
MP4_ERROR("%s", "moov: missing trak\n");
result = 0;
}
if(!result)
{
moov_exit(atom);
return 0;
}
return atom;
}
static int add_fragmented_samples(mp4_context_t const* mp4_context,
traf_t const* traf)
{
unsigned int track;
moov_t* moov = mp4_context->moov;
trak_t* trak = NULL;
trun_t* trun;
for(track = 0; track != moov->tracks_; ++track)
{
trak = moov->traks_[track];
if(trak->tkhd_->track_id_ == traf->tfhd_->track_id_)
break;
}
if(track == moov->tracks_)
{
MP4_ERROR("%s", "add_fragmented_samples: trak not found\n");
return 0;
}
trun = traf->trun_;
for(; trun != NULL; trun = trun->next_)
{
tfhd_t const* tfhd = traf->tfhd_;
// trun_t const* trun = traf->trun_;
unsigned int i;
unsigned int s = trak->samples_size_;
uint64_t data_offset = tfhd->base_data_offset_ + trun->data_offset_;
// int64_t pts = trak->fragment_pts_; // trak->mdia_->mdhd_->duration_;
int64_t pts = trak->mdia_->mdhd_->duration_;
unsigned int cto = 0;
//
if(pts == 0 && trak->edts_)
{
elst_t const* elst = trak->edts_->elst_;
if(elst && elst->entry_count_ > 0)
{
elst_table_t* elst_table = &elst->table_[0];
if(elst_table->media_time_ >= -1)
{
pts = elst_table->media_time_ != -1 ? elst_table->media_time_
: (int64_t)elst_table->segment_duration_;
trak->mdia_->mdhd_->duration_ = pts;
// trak->fragment_pts_ = pts;
}
}
}
trak->samples_size_ += trun->sample_count_;
// reserve one extra for the end information (like pts and cto).
trak->samples_ = (samples_t*)realloc(trak->samples_,
(trak->samples_size_ + 1) * sizeof(samples_t));
for(i = 0; i != trun->sample_count_; ++i)
{
samples_t* sample = &trak->samples_[s + i];
trun_table_t const* trun_table = &trun->table_[i];
unsigned int is_difference_sample = (trun_table->sample_flags_ >> 16) & 1;
cto = trun_table->sample_composition_time_offset_;
sample->pts_ = pts;
sample->size_ = trun_table->sample_size_;
sample->pos_ = data_offset;
sample->cto_ = trun_table->sample_composition_time_offset_;
// sample->is_ss_ = i == 0 ? 1 : 0;
sample->is_smooth_ss_ = i == 0 ? 1 : 0;
sample->is_ss_ = is_difference_sample ? 0 : 1;
#if 0
{
unsigned int depends_on = (trun_table->sample_flags_ >> 24) & 3;
unsigned int is_depended_on = (trun_table->sample_flags_ >> 22) & 3;
unsigned int has_redundancy = (trun_table->sample_flags_ >> 20) & 3;
unsigned int padding_value = (trun_table->sample_flags_ >> 17) & 7;
unsigned int is_difference_sample = (trun_table->sample_flags_ >> 16) & 1;
unsigned int degradation_priority = (trun_table->sample_flags_ >> 0) & 0xffff;
MP4_INFO("sample_flags: depends_on=%u is_depended_on=%u has_redundancy=%u padding=%u is_difference_sample=%u degradation_priority=%u\n",
depends_on, is_depended_on, has_redundancy, padding_value, is_difference_sample, degradation_priority);
}
#endif
pts += trun_table->sample_duration_;
data_offset += sample->size_;
// add fragment duration to track duration
trak->mdia_->mdhd_->duration_ += trun_table->sample_duration_;
// trak->fragment_pts_ += trun_table->sample_duration_;
}
// write end pts
trak->samples_[s + i].pts_ = pts;
trak->samples_[s + i].size_ = 0;
trak->samples_[s + i].pos_ = data_offset;
trak->samples_[s + i].cto_ = cto;
trak->samples_[s + i].is_ss_ = 1;
trak->samples_[s + i].is_smooth_ss_ = 1;
}
return 1;
}
static void* trun_read(mp4_context_t const* UNUSED(mp4_context),
void* parent,
unsigned char* buffer, uint64_t size)
{
unsigned int i;
trun_t* atom = trun_init();
traf_t* traf = (traf_t*)parent;
tfhd_t* tfhd = traf->tfhd_;
if(size < 8)
return 0;
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->sample_count_ = read_32(buffer + 4);
buffer += 8;
if(atom->flags_ & 0x0001)
{
atom->data_offset_ = read_32(buffer);
buffer += 4;
}
if(atom->flags_ & 0x0004)
{
atom->first_sample_flags_ = read_32(buffer);
buffer += 4;
}
atom->table_ = (trun_table_t*)malloc(atom->sample_count_ * sizeof(trun_table_t));
for(i = 0; i != atom->sample_count_; ++i)
{
uint32_t sample_duration = tfhd->default_sample_duration_;
uint32_t sample_size = tfhd->default_sample_size_;
uint32_t sample_flags = tfhd->default_sample_flags_;
uint32_t sample_composition_time_offset = 0;
if(atom->flags_ & 0x0100)
{
sample_duration = read_32(buffer);
buffer += 4;
}
if(atom->flags_ & 0x0200)
{
sample_size = read_32(buffer);
buffer += 4;
}
if(atom->flags_ & 0x0400)
{
sample_flags = read_32(buffer);
buffer += 4;
}
else
if(i == 0 && (atom->flags_ & 0x0004))
{
sample_flags = atom->first_sample_flags_;
}
if(atom->flags_ & 0x0800)
{
sample_composition_time_offset = read_32(buffer);
buffer += 4;
}
atom->table_[i].sample_duration_ = sample_duration;
atom->table_[i].sample_size_ = sample_size;
atom->table_[i].sample_flags_ = sample_flags;
atom->table_[i].sample_composition_time_offset_ =
sample_composition_time_offset;
}
return atom;
}
static void* tfhd_read(mp4_context_t const* mp4_context,
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
unsigned int i;
tfhd_t* atom = tfhd_init();
moov_t const* moov = mp4_context->moov;
mvex_t const* mvex = moov->mvex_;
trex_t* trex = NULL;
if(size < 8)
return 0;
if(mvex == NULL)
{
MP4_ERROR("%s", "tfhd: mvex not found\n");
return 0;
}
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->track_id_ = read_32(buffer + 4);
buffer += 8;
// trex_t trex_default;
// trex_default.default_sample_description_index_ = 1;
// trex_default.default_sample_duration_ = 0;
// trex_default.default_sample_size_ = 0;
// trex_default.default_sample_flags_ = 0;
// if(mvex == NULL)
// {
// trex = &trex_default;
//}
// else
// {
for(i = 0; i != mvex->tracks_; ++i)
{
if(mvex->trexs_[i]->track_id_ == atom->track_id_)
{
trex = mvex->trexs_[i];
break;
}
}
// }
if(trex == NULL)
{
MP4_ERROR("tfhd: trex not found (track_id=%u)\n", atom->track_id_);
return 0;
}
if(atom->flags_ & 0x000001)
{
atom->base_data_offset_ = read_64(buffer);
buffer += 8;
}
else
{
atom->base_data_offset_ = mp4_context->moof_offset_;
}
if(atom->flags_ & 0x000002)
{
atom->sample_description_index_ = read_32(buffer);
buffer += 4;
}
else
{
atom->sample_description_index_ = trex->default_sample_description_index_;
}
if(atom->flags_ & 0x000008)
{
atom->default_sample_duration_ = read_32(buffer);
buffer += 4;
}
else
{
atom->default_sample_duration_ = trex->default_sample_duration_;
}
if(atom->flags_ & 0x000010)
{
atom->default_sample_size_ = read_32(buffer);
buffer += 4;
}
else
{
atom->default_sample_size_ = trex->default_sample_size_;
}
if(atom->flags_ & 0x000020)
{
atom->default_sample_flags_ = read_32(buffer);
buffer += 4;
}
else
{
atom->default_sample_flags_ = trex->default_sample_flags_;
}
return atom;
}
static int traf_add_tfhd(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* tfhd)
{
traf_t* traf = (traf_t*)parent;
traf->tfhd_ = (tfhd_t*)tfhd;
return 1;
}
static int traf_add_trun(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
traf_t* traf = (traf_t*)parent;
trun_t* trun = (trun_t*)child;
trun_t** adder = &traf->trun_;
while(*adder != NULL)
{
adder = &(*adder)->next_;
}
*adder = trun;
return 1;
}
static void* traf_read(mp4_context_t const* mp4_context,
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
traf_t* atom = traf_init();
atom_read_list_t atom_read_list[] = {
{ FOURCC('t', 'f', 'h', 'd'), &traf_add_tfhd, &tfhd_read },
{ FOURCC('t', 'r', 'u', 'n'), &traf_add_trun, &trun_read }
};
int result = atom_reader(mp4_context,
atom_read_list,
sizeof(atom_read_list) / sizeof(atom_read_list[0]),
atom,
buffer, size);
// check for mandatory atoms
if(!atom->tfhd_)
{
MP4_ERROR("%s", "traf: missing tfhd\n");
traf_exit(atom);
return 0;
}
if(!result)
{
traf_exit(atom);
return 0;
}
if(!add_fragmented_samples(mp4_context, atom))
{
traf_exit(atom);
return 0;
}
return atom;
}
static void* mfhd_read(mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
mfhd_t* atom = mfhd_init();
if(size < 8)
return 0;
atom->version_ = read_8(buffer + 0);
atom->flags_ = read_24(buffer + 1);
atom->sequence_number_ = read_32(buffer + 4);
return atom;
}
static int moof_add_mfhd(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* mfhd)
{
moof_t* moof = (moof_t*)parent;
moof->mfhd_ = (mfhd_t*)mfhd;
return 1;
}
static int moof_add_traf(mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
moof_t* moof = (moof_t*)parent;
traf_t* traf = (traf_t*)child;
if(moof->tracks_ == MAX_TRACKS)
{
traf_exit(traf);
return 0;
}
moof->trafs_[moof->tracks_] = traf;
++moof->tracks_;
return 1;
}
extern void* moof_read(struct mp4_context_t const* mp4_context,
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
moof_t* atom = moof_init();
atom_read_list_t atom_read_list[] = {
{ FOURCC('m', 'f', 'h', 'd'), &moof_add_mfhd, &mfhd_read },
{ FOURCC('t', 'r', 'a', 'f'), &moof_add_traf, &traf_read },
};
int result = atom_reader(mp4_context,
atom_read_list,
sizeof(atom_read_list) / sizeof(atom_read_list[0]),
atom,
buffer, size);
// check for mandatory atoms
if(!atom->mfhd_)
{
MP4_ERROR("%s", "moof: missing mfhd\n");
result = 0;
}
if(!atom->tracks_)
{
MP4_ERROR("%s", "moof: missing traf\n");
result = 0;
}
if(!result)
{
moof_exit(atom);
return 0;
}
return atom;
}
static int trak_build_index(mp4_context_t const* mp4_context, trak_t* trak)
{
stco_t const* stco = trak->mdia_->minf_->stbl_->stco_;
int have_samples = 1;
unsigned int stco_samples = 0;
if(stco == NULL || stco->entries_ == 0)
{
have_samples = 0;
}
if(have_samples)
{
trak->chunks_size_ = stco->entries_;
trak->chunks_ = (chunks_t*)malloc(trak->chunks_size_ * sizeof(chunks_t));
{
unsigned int i;
for(i = 0; i != trak->chunks_size_; ++i)
{
trak->chunks_[i].pos_ = stco->chunk_offsets_[i];
}
}
}
// process chunkmap:
if(have_samples)
{
stsc_t const* stsc = trak->mdia_->minf_->stbl_->stsc_;
unsigned int last = trak->chunks_size_;
unsigned int i = stsc->entries_;
while(i > 0)
{
unsigned int j;
--i;
for(j = stsc->table_[i].chunk_; j < last; j++)
{
trak->chunks_[j].id_ = stsc->table_[i].id_;
trak->chunks_[j].size_ = stsc->table_[i].samples_;
}
last = stsc->table_[i].chunk_;
}
}
// calc pts of chunks:
if(have_samples)
{
stsz_t const* stsz = trak->mdia_->minf_->stbl_->stsz_;
unsigned int sample_size = stsz->sample_size_;
unsigned int s = 0;
{
unsigned int j;
for(j = 0; j < trak->chunks_size_; j++)
{
trak->chunks_[j].sample_ = s;
s += trak->chunks_[j].size_;
}
}
stco_samples = s;
if(sample_size == 0)
{
trak->samples_size_ = stsz->entries_;
}
else
{
trak->samples_size_ = s;
}
// reserve one extra for the end information (like pts and cto).
trak->samples_ = (samples_t*)calloc(trak->samples_size_ + 1, sizeof(samples_t));
if(sample_size == 0)
{
unsigned int i;
for(i = 0; i != trak->samples_size_ ; ++i)
trak->samples_[i].size_ = stsz->sample_sizes_[i];
}
else
{
unsigned int i;
for(i = 0; i != trak->samples_size_ ; ++i)
trak->samples_[i].size_ = sample_size;
}
}
// calc pts:
if(have_samples)
{
stts_t const* stts = trak->mdia_->minf_->stbl_->stts_;
unsigned int s = 0;
uint64_t pts = 0;
unsigned int entries = stts->entries_;
unsigned int j;
for(j = 0; j < entries; j++)
{
unsigned int i;
unsigned int sample_count = stts->table_[j].sample_count_;
unsigned int sample_duration = stts->table_[j].sample_duration_;
for(i = 0; i < sample_count; i++)
{
trak->samples_[s].pts_ = pts;
++s;
pts += sample_duration;
}
}
// write end pts
trak->samples_[s].pts_ = pts;
}
// calc composition times:
if(have_samples)
{
ctts_t const* ctts = trak->mdia_->minf_->stbl_->ctts_;
if(ctts)
{
unsigned int s = 0;
unsigned int entries = ctts->entries_;
unsigned int j;
unsigned int sample_offset = 0;
for(j = 0; j != entries; j++)
{
unsigned int i;
unsigned int sample_count = ctts->table_[j].sample_count_;
sample_offset = ctts->table_[j].sample_offset_;
for(i = 0; i < sample_count; i++)
{
if(s == trak->samples_size_)
{
MP4_WARNING("Warning: ctts_get_samples=%u, should be %u\n",
ctts_get_samples(ctts), trak->samples_size_);
break;
}
trak->samples_[s].cto_ = sample_offset;
++s;
}
}
// write end cto
trak->samples_[s].cto_ = sample_offset;
}
}
// calc sample offsets
if(have_samples)
{
unsigned int s = 0;
unsigned int j;
for(j = 0; j != trak->chunks_size_; j++)
{
uint64_t pos = trak->chunks_[j].pos_;
unsigned int i;
for(i = 0; i != trak->chunks_[j].size_; i++)
{
if(s == trak->samples_size_)
{
MP4_WARNING("Warning: stco_get_samples=%u, should be %u\n",
stco_samples, trak->samples_size_);
break;
}
trak->samples_[s].pos_ = pos;
pos += trak->samples_[s].size_;
++s;
}
}
}
if(have_samples)
{
stss_t const* stss = trak->mdia_->minf_->stbl_->stss_;
unsigned int i;
if(stss)
{
// TODO: The chunks for smooth streaming are now aligned to the keyframes.
// We may want to consider skipping some keyframes and use a
// minimal_increment_between_keyframes (say 2 seconds) as some chunks
// can be very small.
for(i = 0; i != stss->entries_; ++i)
{
uint32_t s = stss->sample_numbers_[i] - 1;
trak->samples_[s].is_ss_ = 1;
trak->samples_[s].is_smooth_ss_ = 1;
}
}
else
{
for(i = 0; i != trak->samples_size_; ++i)
{
trak->samples_[i].is_ss_ = 1;
}
}
// write end ss
trak->samples_[trak->samples_size_].is_ss_ = 1;
trak->samples_[trak->samples_size_].is_smooth_ss_ = 1;
}
return 1;
}
static void copy_sync_samples_to_audio_track(trak_t* video, trak_t* audio)
{
if(video)
{
samples_t* first = video->samples_;
samples_t* last = video->samples_ + video->samples_size_;
samples_t* audio_first = audio->samples_;
samples_t* audio_last = audio->samples_ + audio->samples_size_;
while(first != last)
{
if(first->is_smooth_ss_)
{
uint64_t pts = trak_time_to_moov_time(first->pts_,
audio->mdia_->mdhd_->timescale_, video->mdia_->mdhd_->timescale_);
while(audio_first != audio_last)
{
if(audio_first->pts_ >= pts)
{
audio_first->is_smooth_ss_ = 1;
break;
}
++audio_first;
}
}
++first;
}
}
else
{
// if there is no video track and we don't have sync samples, then insert
// smooth sync samples every 2 seconds
samples_t* f = audio->samples_;
samples_t* l = audio->samples_ + audio->samples_size_;
uint64_t pts = 0;
uint64_t increment = 2 * audio->mdia_->mdhd_->timescale_;
while(f != l)
{
if(f->pts_ >= pts)
{
f->is_smooth_ss_ = 1;
pts += increment;
}
++f;
}
}
}
extern int moov_build_index(struct mp4_context_t const* mp4_context,
struct moov_t* moov)
{
// Build the track index
trak_t* audio_trak = NULL;
trak_t* video_trak = NULL;
unsigned int track;
// already indexed?
if(moov->is_indexed_)
{
return 1;
}
moov->is_indexed_ = 1;
for(track = 0; track != moov->tracks_; ++track)
{
trak_t* trak = moov->traks_[track];
switch(trak->mdia_->hdlr_->handler_type_)
{
case FOURCC('s', 'o', 'u', 'n'):
audio_trak = trak;
break;
case FOURCC('v', 'i', 'd', 'e'):
video_trak = trak;
break;
}
if(!trak_build_index(mp4_context, trak))
{
return 0;
}
}
// Copy the sync sample markers for smooth streaming from the video trak
// to the audio trak in case the audio track doesn't have an 'stss'.
if(!moov->mvex_ && audio_trak && !audio_trak->mdia_->minf_->stbl_->stss_)
{
copy_sync_samples_to_audio_track(video_trak, audio_trak);
}
return 1;
}
static void* tfra_read(struct mp4_context_t const* UNUSED(mp4_context),
void* UNUSED(parent),
unsigned char* buffer, uint64_t UNUSED(size))
{
unsigned int i;
unsigned int length_fields;
tfra_t* tfra = tfra_init();
tfra->version_ = read_8(buffer + 0);
tfra->flags_ = read_24(buffer + 1);
tfra->track_id_ = read_32(buffer + 4);
length_fields = read_32(buffer + 8);
tfra->length_size_of_traf_num_ = (((length_fields >> 4) & 3) + 1);
tfra->length_size_of_trun_num_ = (((length_fields >> 2) & 3) + 1);
tfra->length_size_of_sample_num_ = (((length_fields >> 0) & 3) + 1);
tfra->number_of_entry_ = read_32(buffer + 12);
tfra->table_ = (tfra_table_t*)malloc(tfra->number_of_entry_ * sizeof(tfra_table_t));
buffer += 16;
for(i = 0; i != tfra->number_of_entry_; ++i)
{
if(tfra->version_ == 0)
{
tfra->table_[i].time_ = read_32(buffer + 0);
tfra->table_[i].moof_offset_ = read_32(buffer + 4);
buffer += 8;
}
else
{
tfra->table_[i].time_ = read_64(buffer + 0);
tfra->table_[i].moof_offset_ = read_64(buffer + 8);
buffer += 16;
}
tfra->table_[i].traf_number_ =
read_n(buffer, tfra->length_size_of_traf_num_ * 8) - 1;
buffer += tfra->length_size_of_traf_num_;
tfra->table_[i].trun_number_ =
read_n(buffer, tfra->length_size_of_trun_num_ * 8) - 1;
buffer += tfra->length_size_of_trun_num_;
tfra->table_[i].sample_number_ =
read_n(buffer, tfra->length_size_of_sample_num_ * 8) - 1;
buffer += tfra->length_size_of_sample_num_ ;
}
return tfra;
}
static int mfra_add_tfra(struct mp4_context_t const* UNUSED(mp4_context),
void* parent, void* child)
{
mfra_t* mfra = (mfra_t*)parent;
tfra_t* tfra = (tfra_t*)child;
if(mfra->tracks_ == MAX_TRACKS)
{
mfra_exit(mfra);
return 0;
}
mfra->tfras_[mfra->tracks_] = tfra;
++mfra->tracks_;
return 1;
}
extern void* mfra_read(struct mp4_context_t const* mp4_context,
void* UNUSED(parent),
unsigned char* buffer, uint64_t size)
{
mfra_t* atom = mfra_init();
struct atom_read_list_t atom_read_list[] = {
{ FOURCC('t', 'f', 'r', 'a'), &mfra_add_tfra, &tfra_read },
};
int result = atom_reader(mp4_context,
atom_read_list,
sizeof(atom_read_list) / sizeof(atom_read_list[0]),
atom,
buffer, size);
if(!result)
{
mfra_exit(atom);
return 0;
}
return atom;
}
// End Of File