546 lines
15 KiB
C
546 lines
15 KiB
C
#include "fmp4-writer.h"
|
||
#include "mov-internal.h"
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <assert.h>
|
||
#include <errno.h>
|
||
#include <time.h>
|
||
|
||
struct fmp4_writer_t
|
||
{
|
||
struct mov_t mov;
|
||
size_t mdat_size;
|
||
int has_moov;
|
||
|
||
uint32_t frag_interleave;
|
||
uint32_t fragment_id; // start from 1
|
||
uint32_t sn; // sample sn
|
||
};
|
||
|
||
static int fmp4_write_app(struct mov_t* mov)
|
||
{
|
||
mov_buffer_w32(&mov->io, 8 + strlen(MOV_APP)); /* size */
|
||
mov_buffer_write(&mov->io, "free", 4);
|
||
mov_buffer_write(&mov->io, MOV_APP, strlen(MOV_APP));
|
||
return 0;
|
||
}
|
||
|
||
static size_t fmp4_write_mvex(struct mov_t* mov)
|
||
{
|
||
int i;
|
||
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, "mvex", 4);
|
||
|
||
size += mov_write_mehd(mov);
|
||
for (i = 0; i < mov->track_count; i++)
|
||
{
|
||
mov->track = mov->tracks + i;
|
||
size += mov_write_trex(mov);
|
||
}
|
||
//size += mov_write_leva(mov);
|
||
|
||
mov_write_size(mov, offset, size); /* update size */
|
||
return size;
|
||
}
|
||
|
||
static size_t fmp4_write_traf(struct mov_t* mov, uint32_t moof)
|
||
{
|
||
uint32_t i, start;
|
||
size_t size;
|
||
uint64_t offset;
|
||
struct mov_track_t* track;
|
||
|
||
size = 8 /* Box */;
|
||
offset = mov_buffer_tell(&mov->io);
|
||
mov_buffer_w32(&mov->io, 0); /* size */
|
||
mov_buffer_write(&mov->io, "traf", 4);
|
||
|
||
track = mov->track;
|
||
track->tfhd.flags = MOV_TFHD_FLAG_DEFAULT_FLAGS /*| MOV_TFHD_FLAG_BASE_DATA_OFFSET*/;
|
||
track->tfhd.flags |= MOV_TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX;
|
||
// ISO/IEC 23009-1:2014(E) 6.3.4.2 General format type (p93)
|
||
// The 'moof' boxes shall use movie-fragment relative addressing for media data that
|
||
// does not use external data references, the flag 'default-base-is-moof' shall be set,
|
||
// and data-offset shall be used, i.e. base-data-offset-present shall not be used.
|
||
//if (mov->flags & MOV_FLAG_SEGMENT)
|
||
{
|
||
//track->tfhd.flags &= ~MOV_TFHD_FLAG_BASE_DATA_OFFSET;
|
||
track->tfhd.flags |= MOV_TFHD_FLAG_DEFAULT_BASE_IS_MOOF;
|
||
}
|
||
track->tfhd.base_data_offset = mov->moof_offset;
|
||
track->tfhd.sample_description_index = 1;
|
||
track->tfhd.default_sample_flags = MOV_AUDIO == track->handler_type ? MOV_TREX_FLAG_SAMPLE_DEPENDS_ON_I_PICTURE : (MOV_TREX_FLAG_SAMPLE_IS_NO_SYNC_SAMPLE| MOV_TREX_FLAG_SAMPLE_DEPENDS_ON_NOT_I_PICTURE);
|
||
if (track->sample_count > 0)
|
||
{
|
||
track->tfhd.flags |= MOV_TFHD_FLAG_DEFAULT_DURATION | MOV_TFHD_FLAG_DEFAULT_SIZE;
|
||
track->tfhd.default_sample_duration = track->sample_count > 1 ? (uint32_t)(track->samples[1].dts - track->samples[0].dts) : (uint32_t)track->turn_last_duration;
|
||
track->tfhd.default_sample_size = track->samples[0].bytes;
|
||
}
|
||
else
|
||
{
|
||
track->tfhd.flags |= MOV_TFHD_FLAG_DURATION_IS_EMPTY;
|
||
track->tfhd.default_sample_duration = 0; // not set
|
||
track->tfhd.default_sample_size = 0; // not set
|
||
}
|
||
|
||
size += mov_write_tfhd(mov);
|
||
// ISO/IEC 23009-1:2014(E) 6.3.4.2 General format type (p93)
|
||
// Each 'traf' box shall contain a 'tfdt' box.
|
||
size += mov_write_tfdt(mov);
|
||
|
||
for (start = 0, i = 1; i < track->sample_count; i++)
|
||
{
|
||
if (track->samples[i - 1].offset + track->samples[i - 1].bytes != track->samples[i].offset)
|
||
{
|
||
size += mov_write_trun(mov, start, i-start, moof);
|
||
start = i;
|
||
}
|
||
}
|
||
size += mov_write_trun(mov, start, i-start, moof);
|
||
|
||
mov_write_size(mov, offset, size); /* update size */
|
||
return size;
|
||
}
|
||
|
||
static size_t fmp4_write_moof(struct mov_t* mov, uint32_t fragment, uint32_t moof)
|
||
{
|
||
int i;
|
||
size_t size, j;
|
||
uint64_t offset;
|
||
uint64_t n;
|
||
|
||
size = 8 /* Box */;
|
||
offset = mov_buffer_tell(&mov->io);
|
||
mov_buffer_w32(&mov->io, 0); /* size */
|
||
mov_buffer_write(&mov->io, "moof", 4);
|
||
|
||
size += mov_write_mfhd(mov, fragment);
|
||
|
||
n = 0;
|
||
for (i = 0; i < mov->track_count; i++)
|
||
{
|
||
mov->track = mov->tracks + i;
|
||
|
||
// rewrite offset, write only one trun
|
||
// 2017/10/17 Dale Curtis SHA-1: a5fd8aa45b11c10613e6e576033a6b5a16b9cbb9 (libavformat/mov.c)
|
||
for (j = 0; j < mov->track->sample_count; j++)
|
||
{
|
||
mov->track->samples[j].offset = n;
|
||
n += mov->track->samples[j].bytes;
|
||
}
|
||
|
||
if (mov->track->sample_count > 0)
|
||
size += fmp4_write_traf(mov, moof);
|
||
}
|
||
|
||
mov_write_size(mov, offset, size); /* update size */
|
||
return size;
|
||
}
|
||
|
||
static size_t fmp4_write_moov(struct mov_t* mov)
|
||
{
|
||
int i;
|
||
size_t size;
|
||
uint32_t count;
|
||
uint64_t offset;
|
||
|
||
size = 8 /* Box */;
|
||
offset = mov_buffer_tell(&mov->io);
|
||
mov_buffer_w32(&mov->io, 0); /* size */
|
||
mov_buffer_write(&mov->io, "moov", 4);
|
||
|
||
size += mov_write_mvhd(mov);
|
||
// size += fmp4_write_iods(mov);
|
||
for (i = 0; i < mov->track_count; i++)
|
||
{
|
||
mov->track = mov->tracks + i;
|
||
count = mov->track->sample_count;
|
||
mov->track->sample_count = 0;
|
||
size += mov_write_trak(mov);
|
||
mov->track->sample_count = count; // restore sample count
|
||
}
|
||
|
||
size += fmp4_write_mvex(mov);
|
||
size += mov_write_udta(mov);
|
||
mov_write_size(mov, offset, size); /* update size */
|
||
return size;
|
||
}
|
||
|
||
static size_t fmp4_write_sidx(struct mov_t* mov)
|
||
{
|
||
int i;
|
||
for (i = 0; i < mov->track_count; i++)
|
||
{
|
||
mov->track = mov->tracks + i;
|
||
mov_write_sidx(mov, 52 * (uint64_t)(mov->track_count - i - 1)); /* first_offset */
|
||
}
|
||
|
||
return 52 * mov->track_count;
|
||
}
|
||
|
||
static int fmp4_write_mfra(struct mov_t* mov)
|
||
{
|
||
int i;
|
||
uint64_t mfra_offset;
|
||
uint64_t mfro_offset;
|
||
|
||
// mfra
|
||
mfra_offset = mov_buffer_tell(&mov->io);
|
||
mov_buffer_w32(&mov->io, 0); /* size */
|
||
mov_buffer_write(&mov->io, "mfra", 4);
|
||
|
||
// tfra
|
||
for (i = 0; i < mov->track_count; i++)
|
||
{
|
||
mov->track = mov->tracks + i;
|
||
mov_write_tfra(mov);
|
||
}
|
||
|
||
// mfro
|
||
mfro_offset = mov_buffer_tell(&mov->io);
|
||
mov_buffer_w32(&mov->io, 16); /* size */
|
||
mov_buffer_write(&mov->io, "mfro", 4);
|
||
mov_buffer_w32(&mov->io, 0); /* version & flags */
|
||
mov_buffer_w32(&mov->io, (uint32_t)(mfro_offset - mfra_offset + 16));
|
||
|
||
mov_write_size(mov, mfra_offset, (size_t)(mfro_offset - mfra_offset + 16));
|
||
return (int)(mfro_offset - mfra_offset + 16);
|
||
}
|
||
|
||
static int fmp4_add_fragment_entry(struct mov_track_t* track, uint64_t time, uint64_t offset)
|
||
{
|
||
if (track->frag_count >= track->frag_capacity)
|
||
{
|
||
void* p = realloc(track->frags, sizeof(struct mov_fragment_t) * (track->frag_capacity + 64));
|
||
if (!p) return -ENOMEM;
|
||
track->frags = p;
|
||
track->frag_capacity += 64;
|
||
}
|
||
|
||
track->frags[track->frag_count].time = time;
|
||
track->frags[track->frag_count].offset = offset;
|
||
++track->frag_count;
|
||
return 0;
|
||
}
|
||
|
||
static int fmp4_write_fragment(struct fmp4_writer_t* writer)
|
||
{
|
||
int i;
|
||
size_t n;
|
||
size_t refsize;
|
||
struct mov_t* mov;
|
||
mov = &writer->mov;
|
||
|
||
if (writer->mdat_size < 1)
|
||
return 0; // empty
|
||
|
||
// write moov
|
||
if (mov->flags & MOV_FLAG_SEGMENT)
|
||
{
|
||
// write stype
|
||
mov_write_styp(mov);
|
||
|
||
// ISO/IEC 23009-1:2014(E) 6.3.4.2 General format type (p93)
|
||
// Each Media Segment may contain one or more 'sidx' boxes.
|
||
// If present, the first 'sidx' box shall be placed before any 'moof' box
|
||
// and the first Segment Index box shall document the entire Segment.
|
||
fmp4_write_sidx(mov);
|
||
}
|
||
else if (!writer->has_moov)
|
||
{
|
||
mov_write_ftyp(mov);
|
||
fmp4_write_app(mov);
|
||
fmp4_write_moov(mov);
|
||
writer->has_moov = 1;
|
||
}
|
||
|
||
// moof
|
||
mov->moof_offset = mov_buffer_tell(&mov->io);
|
||
refsize = fmp4_write_moof(mov, ++writer->fragment_id, 0); // start from 1
|
||
// rewrite moof with trun data offset
|
||
mov_buffer_seek(&mov->io, mov->moof_offset);
|
||
fmp4_write_moof(mov, writer->fragment_id, (uint32_t)refsize+8);
|
||
refsize += writer->mdat_size + 8/*mdat box*/;
|
||
|
||
// add mfra entry
|
||
for (i = 0; i < mov->track_count; i++)
|
||
{
|
||
mov->track = mov->tracks + i;
|
||
if (mov->track->sample_count > 0 && 0 == (mov->flags & MOV_FLAG_SEGMENT))
|
||
fmp4_add_fragment_entry(mov->track, mov->track->samples[0].dts, mov->moof_offset);
|
||
|
||
// hack: write sidx referenced_size
|
||
if (mov->flags & MOV_FLAG_SEGMENT)
|
||
mov_write_size(mov, mov->moof_offset - 52 * (uint64_t)(mov->track_count - i) + 40, (0 << 31) | (refsize & 0x7fffffff));
|
||
|
||
mov->track->offset = 0; // reset
|
||
}
|
||
|
||
// mdat
|
||
if (writer->mdat_size + 8 <= UINT32_MAX)
|
||
{
|
||
mov_buffer_w32(&mov->io, (uint32_t)writer->mdat_size + 8); /* size */
|
||
mov_buffer_write(&mov->io, "mdat", 4);
|
||
}
|
||
else
|
||
{
|
||
mov_buffer_w32(&mov->io, 1);
|
||
mov_buffer_write(&mov->io, "mdat", 4);
|
||
mov_buffer_w64(&mov->io, writer->mdat_size + 16);
|
||
}
|
||
|
||
// interleave write samples
|
||
n = 0;
|
||
while(n < writer->mdat_size)
|
||
{
|
||
for (i = 0; i < mov->track_count; i++)
|
||
{
|
||
mov->track = mov->tracks + i;
|
||
while (mov->track->offset < mov->track->sample_count && n == mov->track->samples[mov->track->offset].offset)
|
||
{
|
||
mov_buffer_write(&mov->io, mov->track->samples[mov->track->offset].data, mov->track->samples[mov->track->offset].bytes);
|
||
free(mov->track->samples[mov->track->offset].data); // free av packet memory
|
||
n += mov->track->samples[mov->track->offset].bytes;
|
||
++mov->track->offset;
|
||
}
|
||
}
|
||
}
|
||
|
||
// clear track samples(don't free samples memory)
|
||
for (i = 0; i < mov->track_count; i++)
|
||
{
|
||
mov->tracks[i].sample_count = 0;
|
||
mov->tracks[i].offset = 0;
|
||
}
|
||
writer->mdat_size = 0;
|
||
|
||
return mov_buffer_error(&mov->io);
|
||
}
|
||
|
||
static int fmp4_writer_init(struct mov_t* mov)
|
||
{
|
||
if (mov->flags & MOV_FLAG_SEGMENT)
|
||
{
|
||
mov->ftyp.major_brand = MOV_BRAND_MSDH;
|
||
mov->ftyp.minor_version = 0;
|
||
mov->ftyp.brands_count = 6;
|
||
mov->ftyp.compatible_brands[0] = MOV_BRAND_ISOM;
|
||
mov->ftyp.compatible_brands[1] = MOV_BRAND_MP42;
|
||
mov->ftyp.compatible_brands[2] = MOV_BRAND_MSDH;
|
||
mov->ftyp.compatible_brands[3] = MOV_BRAND_MSIX;
|
||
mov->ftyp.compatible_brands[4] = MOV_BRAND_ISO5; // default<6C>\base<73>\is<69>\moof flag
|
||
mov->ftyp.compatible_brands[5] = MOV_BRAND_ISO6; // styp
|
||
mov->header = 0;
|
||
}
|
||
else
|
||
{
|
||
mov->ftyp.major_brand = MOV_BRAND_ISOM;
|
||
mov->ftyp.minor_version = 1;
|
||
mov->ftyp.brands_count = 5;
|
||
mov->ftyp.compatible_brands[0] = MOV_BRAND_ISOM;
|
||
mov->ftyp.compatible_brands[1] = MOV_BRAND_MP42;
|
||
mov->ftyp.compatible_brands[2] = MOV_BRAND_AVC1;
|
||
mov->ftyp.compatible_brands[3] = MOV_BRAND_DASH;
|
||
mov->ftyp.compatible_brands[4] = MOV_BRAND_ISO5; // default<6C>\base<73>\is<69>\moof flag
|
||
mov->header = 0;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
struct fmp4_writer_t* fmp4_writer_create(const struct mov_buffer_t *buffer, void* param, int flags)
|
||
{
|
||
struct mov_t* mov;
|
||
struct fmp4_writer_t* writer;
|
||
writer = (struct fmp4_writer_t*)calloc(1, sizeof(struct fmp4_writer_t));
|
||
if (NULL == writer)
|
||
return NULL;
|
||
|
||
writer->frag_interleave = 5;
|
||
|
||
mov = &writer->mov;
|
||
mov->flags = flags;
|
||
mov->mvhd.next_track_ID = 1;
|
||
mov->mvhd.creation_time = time(NULL) + 0x7C25B080; // 1970 based -> 1904 based;
|
||
mov->mvhd.modification_time = mov->mvhd.creation_time;
|
||
mov->mvhd.timescale = 1000;
|
||
mov->mvhd.duration = 0; // placeholder
|
||
fmp4_writer_init(mov);
|
||
|
||
mov->io.param = param;
|
||
memcpy(&mov->io.io, buffer, sizeof(mov->io.io));
|
||
return writer;
|
||
}
|
||
|
||
void fmp4_writer_destroy(struct fmp4_writer_t* writer)
|
||
{
|
||
int i;
|
||
struct mov_t* mov;
|
||
mov = &writer->mov;
|
||
|
||
fmp4_writer_save_segment(writer);
|
||
|
||
// write mfra
|
||
if (0 == (mov->flags & MOV_FLAG_SEGMENT))
|
||
{
|
||
fmp4_write_mfra(mov);
|
||
for (i = 0; i < mov->track_count; i++)
|
||
mov->tracks[i].frag_count = 0; // don't free frags memory
|
||
}
|
||
|
||
// mov_buffer_error(&mov->io);
|
||
|
||
for (i = 0; i < mov->track_count; i++)
|
||
mov_free_track(mov->tracks + i);
|
||
if (mov->tracks)
|
||
free(mov->tracks);
|
||
free(writer);
|
||
}
|
||
|
||
int fmp4_writer_write(struct fmp4_writer_t* writer, int idx, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags)
|
||
{
|
||
int64_t duration;
|
||
struct mov_track_t* track;
|
||
struct mov_sample_t* sample;
|
||
|
||
if (idx < 0 || idx >= (int)writer->mov.track_count)
|
||
return -ENOENT;
|
||
|
||
track = &writer->mov.tracks[idx];
|
||
|
||
duration = dts > track->last_dts && INT64_MIN != track->last_dts ? dts - track->last_dts : 0;
|
||
#if 1
|
||
track->turn_last_duration = duration;
|
||
#else
|
||
track->turn_last_duration = track->turn_last_duration > 0 ? track->turn_last_duration * 7 / 8 + duration / 8 : duration;
|
||
#endif
|
||
|
||
// 1. force segment or
|
||
// 2. video key frame
|
||
if (0 == (flags & MOV_AV_FLAG_SEGMENT_DISABLE) && (0 != (flags & MOV_AV_FLAG_SEGMENT_FORCE) || (MOV_VIDEO == track->handler_type && (flags & MOV_AV_FLAG_KEYFREAME))) )
|
||
fmp4_write_fragment(writer); // fragment per video keyframe
|
||
|
||
if (track->sample_count + 1 >= track->sample_offset)
|
||
{
|
||
void* ptr = realloc(track->samples, sizeof(struct mov_sample_t) * (track->sample_offset + 1024));
|
||
if (NULL == ptr) return -ENOMEM;
|
||
track->samples = (struct mov_sample_t*)ptr;
|
||
track->sample_offset += 1024;
|
||
}
|
||
|
||
pts = pts * track->mdhd.timescale / 1000;
|
||
dts = dts * track->mdhd.timescale / 1000;
|
||
|
||
sample = &track->samples[track->sample_count];
|
||
sample->sample_description_index = 1;
|
||
sample->bytes = (uint32_t)bytes;
|
||
sample->flags = flags;
|
||
sample->pts = pts;
|
||
sample->dts = dts;
|
||
sample->offset = writer->mdat_size;
|
||
|
||
sample->data = malloc(bytes);
|
||
if (NULL == sample->data)
|
||
return -ENOMEM;
|
||
memcpy(sample->data, data, bytes);
|
||
|
||
if (INT64_MIN == track->start_dts)
|
||
track->start_dts = sample->dts;
|
||
writer->mdat_size += bytes; // update media data size
|
||
track->sample_count += 1;
|
||
track->last_dts = sample->dts;
|
||
return mov_buffer_error(&writer->mov.io);
|
||
}
|
||
|
||
int fmp4_writer_add_audio(struct fmp4_writer_t* writer, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size)
|
||
{
|
||
struct mov_t* mov;
|
||
struct mov_track_t* track;
|
||
|
||
mov = &writer->mov;
|
||
track = mov_add_track(mov);
|
||
if (NULL == track)
|
||
return -ENOMEM;
|
||
|
||
if (0 != mov_add_audio(track, &mov->mvhd, 1000, object, channel_count, bits_per_sample, sample_rate, extra_data, extra_data_size))
|
||
return -ENOMEM;
|
||
|
||
mov->mvhd.next_track_ID++;
|
||
return mov->track_count++;
|
||
}
|
||
|
||
int fmp4_writer_add_video(struct fmp4_writer_t* writer, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size)
|
||
{
|
||
struct mov_t* mov;
|
||
struct mov_track_t* track;
|
||
|
||
mov = &writer->mov;
|
||
track = mov_add_track(mov);
|
||
if (NULL == track)
|
||
return -ENOMEM;
|
||
|
||
if (0 != mov_add_video(track, &mov->mvhd, 1000, object, width, height, extra_data, extra_data_size))
|
||
return -ENOMEM;
|
||
|
||
mov->mvhd.next_track_ID++;
|
||
return mov->track_count++;
|
||
}
|
||
|
||
int fmp4_writer_add_subtitle(struct fmp4_writer_t* writer, uint8_t object, const void* extra_data, size_t extra_data_size)
|
||
{
|
||
struct mov_t* mov;
|
||
struct mov_track_t* track;
|
||
|
||
mov = &writer->mov;
|
||
track = mov_add_track(mov);
|
||
if (NULL == track)
|
||
return -ENOMEM;
|
||
|
||
if (0 != mov_add_subtitle(track, &mov->mvhd, 1000, object, extra_data, extra_data_size))
|
||
return -ENOMEM;
|
||
|
||
mov->mvhd.next_track_ID++;
|
||
return mov->track_count++;
|
||
}
|
||
|
||
int fmp4_writer_add_udta(fmp4_writer_t* writer, const void* data, size_t size)
|
||
{
|
||
writer->mov.udta = data;
|
||
writer->mov.udta_size = size;
|
||
return 0;
|
||
}
|
||
|
||
int fmp4_writer_save_segment(fmp4_writer_t* writer)
|
||
{
|
||
//int i;
|
||
//struct mov_t* mov;
|
||
//mov = &writer->mov;
|
||
|
||
// flush fragment
|
||
return fmp4_write_fragment(writer);
|
||
|
||
//// write mfra
|
||
//if (0 == (mov->flags & MOV_FLAG_SEGMENT))
|
||
//{
|
||
// fmp4_write_mfra(mov);
|
||
// for (i = 0; i < mov->track_count; i++)
|
||
// mov->tracks[i].frag_count = 0; // don't free frags memory
|
||
//}
|
||
|
||
//return mov_buffer_error(&mov->io);
|
||
}
|
||
|
||
int fmp4_writer_init_segment(fmp4_writer_t* writer)
|
||
{
|
||
struct mov_t* mov;
|
||
mov = &writer->mov;
|
||
mov_write_ftyp(mov);
|
||
fmp4_write_moov(mov);
|
||
writer->has_moov = 1;
|
||
return mov_buffer_error(&mov->io);
|
||
}
|