#include "fmp4-writer.h" #include "mov-internal.h" #include #include #include #include #include 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�\base�\is�\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�\base�\is�\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); }