386 lines
12 KiB
C
386 lines
12 KiB
C
|
#include "mov-internal.h"
|
||
|
#include <errno.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <assert.h>
|
||
|
|
||
|
#define FREE(p) do { if(p) free(p); } while(0)
|
||
|
|
||
|
struct mov_track_t* mov_add_track(struct mov_t* mov)
|
||
|
{
|
||
|
void* ptr = NULL;
|
||
|
struct mov_track_t* track;
|
||
|
|
||
|
ptr = realloc(mov->tracks, sizeof(struct mov_track_t) * (mov->track_count + 1));
|
||
|
if (NULL == ptr) return NULL;
|
||
|
|
||
|
mov->tracks = ptr;
|
||
|
track = &mov->tracks[mov->track_count];
|
||
|
memset(track, 0, sizeof(struct mov_track_t));
|
||
|
track->start_dts = INT64_MIN;
|
||
|
track->last_dts = INT64_MIN;
|
||
|
|
||
|
track->stsd.entries = calloc(1, sizeof(struct mov_sample_entry_t));
|
||
|
if (NULL == track->stsd.entries)
|
||
|
return NULL;
|
||
|
track->stsd.current = track->stsd.entries;
|
||
|
|
||
|
return track;
|
||
|
}
|
||
|
|
||
|
void mov_free_track(struct mov_track_t* track)
|
||
|
{
|
||
|
size_t i;
|
||
|
for (i = 0; i < track->sample_count; i++)
|
||
|
{
|
||
|
if (track->samples[i].data)
|
||
|
free(track->samples[i].data);
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < track->stsd.entry_count; i++)
|
||
|
{
|
||
|
if (track->stsd.entries[i].extra_data)
|
||
|
free(track->stsd.entries[i].extra_data);
|
||
|
}
|
||
|
|
||
|
FREE(track->elst);
|
||
|
FREE(track->frags);
|
||
|
FREE(track->samples);
|
||
|
// FREE(track->extra_data);
|
||
|
FREE(track->stsd.entries);
|
||
|
FREE(track->stbl.stco);
|
||
|
FREE(track->stbl.stsc);
|
||
|
FREE(track->stbl.stss);
|
||
|
FREE(track->stbl.stts);
|
||
|
FREE(track->stbl.ctts);
|
||
|
}
|
||
|
|
||
|
struct mov_track_t* mov_find_track(const struct mov_t* mov, uint32_t track)
|
||
|
{
|
||
|
int i;
|
||
|
for (i = 0; i < mov->track_count; i++)
|
||
|
{
|
||
|
if (mov->tracks[i].tkhd.track_ID == track)
|
||
|
return mov->tracks + i;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
struct mov_track_t* mov_fetch_track(struct mov_t* mov, uint32_t track)
|
||
|
{
|
||
|
struct mov_track_t* t;
|
||
|
t = mov_find_track(mov, track);
|
||
|
if (NULL == t)
|
||
|
{
|
||
|
t = mov_add_track(mov);
|
||
|
if (NULL != t)
|
||
|
{
|
||
|
++mov->track_count;
|
||
|
t->tkhd.track_ID = track;
|
||
|
}
|
||
|
}
|
||
|
return t;
|
||
|
}
|
||
|
|
||
|
int mov_add_audio(struct mov_track_t* track, const struct mov_mvhd_t* mvhd, uint32_t timescale, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size)
|
||
|
{
|
||
|
struct mov_sample_entry_t* audio;
|
||
|
|
||
|
if (MOV_OBJECT_MP3 == object && sample_rate > 24000)
|
||
|
object = MOV_OBJECT_MP1A; // use mpeg1 sample rate table, see more @libflv/source/mp3-header.c
|
||
|
|
||
|
audio = &track->stsd.entries[0];
|
||
|
audio->data_reference_index = 1;
|
||
|
audio->object_type_indication = object;
|
||
|
audio->stream_type = MP4_STREAM_AUDIO;
|
||
|
audio->u.audio.channelcount = (uint16_t)channel_count;
|
||
|
audio->u.audio.samplesize = (uint16_t)bits_per_sample;
|
||
|
audio->u.audio.samplerate = ((uint32_t)(sample_rate > 56635 ? 0 : sample_rate)) << 16;
|
||
|
|
||
|
assert(0 != mov_object_to_tag(object));
|
||
|
track->tag = mov_object_to_tag(object);
|
||
|
track->handler_type = MOV_AUDIO;
|
||
|
track->handler_descr = "SoundHandler";
|
||
|
track->stsd.entry_count = 1;
|
||
|
track->offset = 0;
|
||
|
|
||
|
track->tkhd.flags = MOV_TKHD_FLAG_TRACK_ENABLE | MOV_TKHD_FLAG_TRACK_IN_MOVIE;
|
||
|
track->tkhd.track_ID = mvhd->next_track_ID;
|
||
|
track->tkhd.creation_time = mvhd->creation_time;
|
||
|
track->tkhd.modification_time = mvhd->modification_time;
|
||
|
track->tkhd.width = 0;
|
||
|
track->tkhd.height = 0;
|
||
|
track->tkhd.volume = 0x0100;
|
||
|
track->tkhd.duration = 0; // placeholder
|
||
|
|
||
|
track->mdhd.creation_time = track->tkhd.creation_time;
|
||
|
track->mdhd.modification_time = track->tkhd.modification_time;
|
||
|
track->mdhd.timescale = timescale; //sample_rate
|
||
|
track->mdhd.language = 0x55c4;
|
||
|
track->mdhd.duration = 0; // placeholder
|
||
|
|
||
|
audio->extra_data = malloc(extra_data_size + 1);
|
||
|
if (NULL == audio->extra_data)
|
||
|
return -ENOMEM;
|
||
|
memcpy(audio->extra_data, extra_data, extra_data_size);
|
||
|
audio->extra_data_size = (int)extra_data_size;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int mov_add_video(struct mov_track_t* track, const struct mov_mvhd_t* mvhd, uint32_t timescale, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size)
|
||
|
{
|
||
|
struct mov_sample_entry_t* video;
|
||
|
|
||
|
video = &track->stsd.entries[0];
|
||
|
video->data_reference_index = 1;
|
||
|
video->object_type_indication = object;
|
||
|
video->stream_type = MP4_STREAM_VISUAL;
|
||
|
video->u.visual.width = (uint16_t)width;
|
||
|
video->u.visual.height = (uint16_t)height;
|
||
|
video->u.visual.depth = 0x0018;
|
||
|
video->u.visual.frame_count = 1;
|
||
|
video->u.visual.horizresolution = 0x00480000;
|
||
|
video->u.visual.vertresolution = 0x00480000;
|
||
|
|
||
|
assert(0 != mov_object_to_tag(object));
|
||
|
track->tag = mov_object_to_tag(object);
|
||
|
track->handler_type = MOV_VIDEO;
|
||
|
track->handler_descr = "VideoHandler";
|
||
|
track->stsd.entry_count = 1;
|
||
|
track->offset = 0;
|
||
|
|
||
|
track->tkhd.flags = MOV_TKHD_FLAG_TRACK_ENABLE | MOV_TKHD_FLAG_TRACK_IN_MOVIE;
|
||
|
track->tkhd.track_ID = mvhd->next_track_ID;
|
||
|
track->tkhd.creation_time = mvhd->creation_time;
|
||
|
track->tkhd.modification_time = mvhd->modification_time;
|
||
|
track->tkhd.width = width << 16;
|
||
|
track->tkhd.height = height << 16;
|
||
|
track->tkhd.volume = 0;
|
||
|
track->tkhd.duration = 0; // placeholder
|
||
|
|
||
|
track->mdhd.creation_time = track->tkhd.creation_time;
|
||
|
track->mdhd.modification_time = track->tkhd.modification_time;
|
||
|
track->mdhd.timescale = timescale; //mov->mvhd.timescale
|
||
|
track->mdhd.language = 0x55c4;
|
||
|
track->mdhd.duration = 0; // placeholder
|
||
|
|
||
|
video->extra_data = malloc(extra_data_size + 1);
|
||
|
if (NULL == video->extra_data)
|
||
|
return -ENOMEM;
|
||
|
memcpy(video->extra_data, extra_data, extra_data_size);
|
||
|
video->extra_data_size = (int)extra_data_size;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int mov_add_subtitle(struct mov_track_t* track, const struct mov_mvhd_t* mvhd, uint32_t timescale, uint8_t object, const void* extra_data, size_t extra_data_size)
|
||
|
{
|
||
|
static const uint8_t chapter_extra_data[] = {
|
||
|
// TextSampleEntry
|
||
|
0x00, 0x00, 0x00, 0x01, // displayFlags
|
||
|
0x00, 0x00, // horizontal + vertical justification
|
||
|
0x00, 0x00, 0x00, 0x00, // bgColourRed/Green/Blue/Alpha
|
||
|
// BoxRecord
|
||
|
0x00, 0x00, 0x00, 0x00, // defTextBoxTop/Left
|
||
|
0x00, 0x00, 0x00, 0x00, // defTextBoxBottom/Right
|
||
|
// StyleRecord
|
||
|
0x00, 0x00, 0x00, 0x00, // startChar + endChar
|
||
|
0x00, 0x01, // fontID
|
||
|
0x00, 0x00, // fontStyleFlags + fontSize
|
||
|
0x00, 0x00, 0x00, 0x00, // fgColourRed/Green/Blue/Alpha
|
||
|
// FontTableBox
|
||
|
0x00, 0x00, 0x00, 0x0D, // box size
|
||
|
'f', 't', 'a', 'b', // box atom name
|
||
|
0x00, 0x01, // entry count
|
||
|
// FontRecord
|
||
|
0x00, 0x01, // font ID
|
||
|
0x00, // font name length
|
||
|
};
|
||
|
|
||
|
struct mov_sample_entry_t* subtitle;
|
||
|
|
||
|
subtitle = &track->stsd.entries[0];
|
||
|
subtitle->data_reference_index = 1;
|
||
|
subtitle->object_type_indication = object;
|
||
|
subtitle->stream_type = MP4_STREAM_VISUAL; // Visually composed tracks including video and text are layered using the 'layer' value.
|
||
|
|
||
|
assert(0 != mov_object_to_tag(object));
|
||
|
track->tag = mov_object_to_tag(object);
|
||
|
track->handler_type = track->tag == MOV_TAG('t', 'e', 'x', 't') ? MOV_TEXT : MOV_SBTL;
|
||
|
track->handler_descr = "SubtitleHandler";
|
||
|
track->stsd.entry_count = 1;
|
||
|
track->offset = 0;
|
||
|
|
||
|
track->tkhd.flags = (track->tag == MOV_TAG('t', 'e', 'x', 't') ? 0 : MOV_TKHD_FLAG_TRACK_ENABLE) | MOV_TKHD_FLAG_TRACK_IN_MOVIE;
|
||
|
track->tkhd.track_ID = mvhd->next_track_ID;
|
||
|
track->tkhd.creation_time = mvhd->creation_time;
|
||
|
track->tkhd.modification_time = mvhd->modification_time;
|
||
|
track->tkhd.width = 0;
|
||
|
track->tkhd.height = 0;
|
||
|
track->tkhd.volume = 0;
|
||
|
track->tkhd.duration = 0; // placeholder
|
||
|
|
||
|
track->mdhd.creation_time = track->tkhd.creation_time;
|
||
|
track->mdhd.modification_time = track->tkhd.modification_time;
|
||
|
track->mdhd.timescale = timescale;
|
||
|
track->mdhd.language = 0x55c4;
|
||
|
track->mdhd.duration = 0; // placeholder
|
||
|
|
||
|
if (object == MOV_OBJECT_CHAPTER && 0 == extra_data_size)
|
||
|
{
|
||
|
extra_data = chapter_extra_data;
|
||
|
extra_data_size = sizeof(chapter_extra_data);
|
||
|
}
|
||
|
|
||
|
subtitle->extra_data = malloc(extra_data_size + 1);
|
||
|
if (NULL == subtitle->extra_data)
|
||
|
return -ENOMEM;
|
||
|
memcpy(subtitle->extra_data, extra_data, extra_data_size);
|
||
|
subtitle->extra_data_size = (int)extra_data_size;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// ISO/IEC 14496-12:2012(E) 6.2.3 Box Order (p23)
|
||
|
// It is recommended that the boxes within the Sample Table Box be in the following order:
|
||
|
// Sample Description, Time to Sample, Sample to Chunk, Sample Size, Chunk Offset.
|
||
|
size_t mov_write_stbl(const struct mov_t* mov)
|
||
|
{
|
||
|
size_t size;
|
||
|
uint32_t count;
|
||
|
uint64_t offset;
|
||
|
struct mov_track_t* track;
|
||
|
track = (struct mov_track_t*)mov->track;
|
||
|
|
||
|
size = 8 /* Box */;
|
||
|
offset = mov_buffer_tell(&mov->io);
|
||
|
mov_buffer_w32(&mov->io, 0); /* size */
|
||
|
mov_buffer_write(&mov->io, "stbl", 4);
|
||
|
|
||
|
size += mov_write_stsd(mov);
|
||
|
|
||
|
count = mov_build_stts(track);
|
||
|
size += mov_write_stts(mov, count);
|
||
|
if (track->tkhd.width > 0 && track->tkhd.height > 0)
|
||
|
size += mov_write_stss(mov); // video only
|
||
|
count = mov_build_ctts(track);
|
||
|
if (track->sample_count > 0 && (count > 1 || track->samples[0].samples_per_chunk != 0))
|
||
|
size += mov_write_ctts(mov, count);
|
||
|
|
||
|
count = mov_build_stco(track);
|
||
|
size += mov_write_stsc(mov);
|
||
|
size += mov_write_stsz(mov);
|
||
|
size += mov_write_stco(mov, count);
|
||
|
|
||
|
mov_write_size(mov, offset, size); /* update size */
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
size_t mov_write_minf(const struct mov_t* mov)
|
||
|
{
|
||
|
size_t size;
|
||
|
uint64_t offset;
|
||
|
const struct mov_track_t* track = mov->track;
|
||
|
|
||
|
size = 8 /* Box */;
|
||
|
offset = mov_buffer_tell(&mov->io);
|
||
|
mov_buffer_w32(&mov->io, 0); /* size */
|
||
|
mov_buffer_write(&mov->io, "minf", 4);
|
||
|
|
||
|
if (MOV_VIDEO == track->handler_type)
|
||
|
{
|
||
|
size += mov_write_vmhd(mov);
|
||
|
}
|
||
|
else if (MOV_AUDIO == track->handler_type)
|
||
|
{
|
||
|
size += mov_write_smhd(mov);
|
||
|
}
|
||
|
else if (MOV_TEXT == track->handler_type)
|
||
|
{
|
||
|
size += mov_write_gmhd(mov);
|
||
|
}
|
||
|
else if (MOV_SUBT == track->handler_type || MOV_SBTL == track->handler_type)
|
||
|
{
|
||
|
size += mov_write_nmhd(mov);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
assert(0);
|
||
|
}
|
||
|
|
||
|
size += mov_write_dinf(mov);
|
||
|
size += mov_write_stbl(mov);
|
||
|
mov_write_size(mov, offset, size); /* update size */
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
size_t mov_write_mdia(const struct mov_t* mov)
|
||
|
{
|
||
|
size_t size;
|
||
|
uint64_t offset;
|
||
|
|
||
|
size = 8 /* Box */;
|
||
|
offset = mov_buffer_tell(&mov->io);
|
||
|
mov_buffer_w32(&mov->io, 0); /* size */
|
||
|
mov_buffer_write(&mov->io, "mdia", 4);
|
||
|
|
||
|
size += mov_write_mdhd(mov);
|
||
|
size += mov_write_hdlr(mov);
|
||
|
size += mov_write_minf(mov);
|
||
|
|
||
|
mov_write_size(mov, offset, size); /* update size */
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
size_t mov_write_trak(const struct mov_t* mov)
|
||
|
{
|
||
|
size_t size;
|
||
|
uint64_t offset;
|
||
|
|
||
|
size = 8 /* Box */;
|
||
|
offset = mov_buffer_tell(&mov->io);
|
||
|
mov_buffer_w32(&mov->io, 0); /* size */
|
||
|
mov_buffer_write(&mov->io, "trak", 4);
|
||
|
|
||
|
size += mov_write_tkhd(mov);
|
||
|
size += mov_write_edts(mov);
|
||
|
if(mov->track->chpl_track != 0)
|
||
|
size += mov_write_tref(mov);
|
||
|
size += mov_write_mdia(mov);
|
||
|
|
||
|
mov_write_size(mov, offset, size); /* update size */
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
size_t mov_write_edts(const struct mov_t* mov)
|
||
|
{
|
||
|
size_t size;
|
||
|
uint64_t offset;
|
||
|
|
||
|
if (mov->track->sample_count < 1)
|
||
|
return 0;
|
||
|
|
||
|
size = 8 /* Box */;
|
||
|
offset = mov_buffer_tell(&mov->io);
|
||
|
mov_buffer_w32(&mov->io, 0); /* size */
|
||
|
mov_buffer_write(&mov->io, "edts", 4);
|
||
|
|
||
|
size += mov_write_elst(mov);
|
||
|
|
||
|
mov_write_size(mov, offset, size); /* update size */
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
size_t mov_write_tref(const struct mov_t* mov)
|
||
|
{
|
||
|
mov_buffer_w32(&mov->io, 20); /* size */
|
||
|
mov_buffer_write(&mov->io, "tref", 4);
|
||
|
|
||
|
mov_buffer_w32(&mov->io, 12); /* size */
|
||
|
mov_buffer_write(&mov->io, "chap", 4);
|
||
|
mov_buffer_w32(&mov->io, mov->track->chpl_track);
|
||
|
|
||
|
return 20;
|
||
|
}
|