static uint32_t woov_write( struct mp4_context_t const* mp4_context, struct woov_t* woov, struct bucket_t** buckets) { unsigned int i; uint32_t woov_size=0; bucket_t* woov_bucket = bucket_init(BUCKET_TYPE_MEMORY); bucket_insert_tail(buckets, woov_bucket); for(i = 0; i != woov->moov->tracks_; ++i) { trak_t* trak = woov->moov->traks_[i]; samples_t const* first = (samples_t const*)&trak->samples_[0]; samples_t const* last = (samples_t const*)&trak->samples_[trak->samples_size_]; // info need to update trak->mdia_->minf_->stbl_->stts_ = stts_create(mp4_context, first, last); //time-to-sample trak->mdia_->minf_->stbl_->ctts_ = ctts_create(mp4_context, first, last); //composition time-to-sample table trak->mdia_->minf_->stbl_->stsz_ = stsz_create(mp4_context, first, last); //sample sizes (framing) trak->mdia_->minf_->stbl_->stss_ = stss_create(mp4_context, first, last); //sync sample // update trak duration trak->mdia_->mdhd_->duration_ = last->pts_ - first->pts_; trak->tkhd_->duration_ = trak_time_to_moov_time(trak->mdia_->mdhd_->duration_, woov->moov->mvhd_->timescale_, trak->mdia_->mdhd_->timescale_); // update movie duration if(trak->tkhd_->duration_ > woov->moov->mvhd_->duration_) { woov->moov->mvhd_->duration_ = trak->tkhd_->duration_ ; } } woov_bucket->buf_ = malloc( 16 * 1024 * 1024 ); woov_bucket->size_ = woov_size = moov_write(woov->moov, (unsigned char*)woov_bucket->buf_); //write_32((unsigned char*)woov_bucket->buf_ + 4, FOURCC('m', 'o', 'o', 'v')); write_32((unsigned char*)woov_bucket->buf_ + 4, FOURCC('f', 'r', 'e', 'e')); return woov_size; }
extern int output_mp4(struct mp4_context_t* mp4_context, unsigned int const* trak_sample_start, unsigned int const* trak_sample_end, struct bucket_t** buckets, struct mp4_split_options_t* options) { unsigned int i; uint64_t mdat_start = mp4_context->mdat_atom.start_; uint64_t mdat_size = mp4_context->mdat_atom.size_; int64_t offset; struct moov_t* moov = mp4_context->moov; // unsigned char* moov_data = mp4_context->moov_data; unsigned char* moov_data = (unsigned char*) malloc((size_t)mp4_context->moov_atom.size_ + ATOM_PREAMBLE_SIZE + 1024); uint64_t moov_size; long moov_time_scale = moov->mvhd_->timescale_; uint64_t skip_from_start = UINT64_MAX; uint64_t end_offset = 0; uint64_t moov_duration = 0; #if 1 uint64_t new_mdat_start = 0; { static char const free_data[] = { 0x0, 0x0, 0x0, 42, 'f', 'r', 'e', 'e', 'v', 'i', 'd', 'e', 'o', ' ', 's', 'e', 'r', 'v', 'e', 'd', ' ', 'b', 'y', ' ', 'm', 'o', 'd', '_', 'h', '2', '6', '4', '_', 's', 't', 'r', 'e', 'a', 'm', 'i', 'n', 'g' }; uint32_t size_of_header = (uint32_t)mp4_context->ftyp_atom.size_ + sizeof(free_data); unsigned char* buffer = (unsigned char*)malloc(size_of_header); if(mp4_context->ftyp_atom.size_) { fseeko(mp4_context->infile, mp4_context->ftyp_atom.start_, SEEK_SET); if(fread(buffer, (off_t)mp4_context->ftyp_atom.size_, 1, mp4_context->infile) != 1) { MP4_ERROR("%s", "Error reading ftyp atom\n"); free(buffer); return 0; } } // copy free data memcpy(buffer + mp4_context->ftyp_atom.size_, free_data, sizeof(free_data)); if(options->output_format == OUTPUT_FORMAT_MP4) { bucket_t* bucket = bucket_init_memory(buffer, size_of_header); bucket_insert_tail(buckets, bucket); } free(buffer); new_mdat_start += size_of_header; } // new_mdat_start += mp4_context->moov_atom.size_; #endif offset = new_mdat_start - mp4_context->mdat_atom.start_; // subtract old moov size // offset -= mp4_context->moov_atom.size_; for(i = 0; i != moov->tracks_; ++i) { struct trak_t* trak = moov->traks_[i]; struct stbl_t* stbl = trak->mdia_->minf_->stbl_; unsigned int start_sample = trak_sample_start[i]; unsigned int end_sample = trak_sample_end[i]; if (options->exact) trak_fast_forward_first_partial_GOP(mp4_context, options, trak, start_sample); trak_update_index(mp4_context, trak, start_sample, end_sample); if(trak->samples_size_ == 0) { MP4_WARNING("Trak %u contains no samples. Maybe a fragmented file?", i); return 1; } { uint64_t skip = trak->samples_[start_sample].pos_ - trak->samples_[0].pos_; if(skip < skip_from_start) skip_from_start = skip; MP4_INFO("Trak can skip %"PRIu64" bytes\n", skip); if(end_sample != trak->samples_size_) { uint64_t end_pos = trak->samples_[end_sample].pos_; if(end_pos > end_offset) end_offset = end_pos; MP4_INFO("New endpos=%"PRIu64"\n", end_pos); MP4_INFO("Trak can skip %"PRIu64" bytes at end\n", mdat_start + mdat_size - end_offset); } } { // fixup trak (duration) uint64_t trak_duration = stts_get_duration(stbl->stts_); long trak_time_scale = trak->mdia_->mdhd_->timescale_; { uint64_t duration = trak_time_to_moov_time(trak_duration, moov_time_scale, trak_time_scale); trak->mdia_->mdhd_->duration_= trak_duration; trak->tkhd_->duration_ = duration; MP4_INFO("trak: new_duration=%"PRIu64"\n", duration); if(duration > moov_duration) moov_duration = duration; } } // MP4_INFO("stco.size=%d, ", read_int32(stbl->stco_ + 4)); // MP4_INFO("stts.size=%d samples=%d\n", read_int32(stbl->stts_ + 4), stts_get_samples(stbl->stts_)); // MP4_INFO("stsz.size=%d\n", read_int32(stbl->stsz_ + 8)); // MP4_INFO("stsc.samples=%d\n", stsc_get_samples(stbl->stsc_)); } moov->mvhd_->duration_ = moov_duration; MP4_INFO("moov: new_duration=%.2f seconds\n", moov_duration / (float)moov_time_scale); // subtract bytes we skip at the front of the mdat atom offset -= skip_from_start; MP4_INFO("%s", "moov: writing header\n"); moov_write(moov, moov_data); moov_size = read_32(moov_data); // add new moov size offset += moov_size; MP4_INFO("shifting offsets by %"PRId64"\n", offset); moov_shift_offsets_inplace(moov, offset); // traffic shaping: create offsets for each second create_traffic_shaping(moov, trak_sample_start, trak_sample_end, offset, options); #ifdef COMPRESS_MOOV_ATOM if(!options->client_is_flash) { compress_moov(mp4_context, moov, moov_data, &moov_size); } #endif if(end_offset != 0) { MP4_INFO("mdat_size=%"PRId64" end_offset=%"PRId64"\n", mdat_size, end_offset); mdat_size = end_offset - mdat_start; } mdat_start += skip_from_start; mdat_size -= skip_from_start; MP4_INFO("mdat_bucket(%"PRId64", %"PRId64")\n", mdat_start, mdat_size); bucket_insert_tail(buckets, bucket_init_memory(moov_data, moov_size)); free(moov_data); { struct mp4_atom_t mdat_atom; mdat_atom.type_ = FOURCC('m', 'd', 'a', 't'); mdat_atom.short_size_ = 0; // TODO: use original small/wide mdat box if(options->adaptive) { // empty mdat atom mdat_atom.size_ = ATOM_PREAMBLE_SIZE; } else { mdat_atom.size_ = mdat_size; } { unsigned char buffer[32]; int mdat_header_size = mp4_atom_write_header(buffer, &mdat_atom); bucket_insert_tail(buckets, bucket_init_memory(buffer, mdat_header_size)); if(mdat_atom.size_ - mdat_header_size) { bucket_insert_tail(buckets, bucket_init_file(mdat_start + mdat_header_size, mdat_atom.size_ - mdat_header_size)); } } } return 1; }
extern int mp4_fragment_file(struct mp4_context_t const* mp4_context, struct bucket_t** buckets) { uint64_t filepos = 0; int result = 1; moov_t* moov = mp4_context->moov; moov_t* fmoov; struct woov_t* woov = NULL; mfra_t* mfra; if(!moov_build_index(mp4_context, mp4_context->moov)) { return 0; } // Start with the ftyp { unsigned char ftyp[28]; unsigned char* buffer = ftyp; buffer = write_32(buffer, 28); buffer = write_32(buffer, FOURCC('f', 't', 'y', 'p')); buffer = write_32(buffer, FOURCC('a', 'v', 'c', '1')); // major_brand buffer = write_32(buffer, 0); // minor_version buffer = write_32(buffer, FOURCC('i', 's', 'o', 'm')); // compatible_brands buffer = write_32(buffer, FOURCC('i', 's', 'o', '2')); buffer = write_32(buffer, FOURCC('f', 'r', 'a', 'g')); bucket_insert_tail(buckets, bucket_init_memory(ftyp, sizeof(ftyp))); filepos += sizeof(ftyp); } { uint32_t i; struct trak_weight_t{ int w; void* v; }wtrack[MAX_TRACKS]; for(i = 0; i < moov->tracks_; i++) { if(moov->traks_[i]->mdia_->hdlr_->handler_type_ == FOURCC('s', 'o', 'u', 'n')) {wtrack[i].w=1;wtrack[i].v=moov->traks_[i];} else if(moov->traks_[i]->mdia_->hdlr_->handler_type_ == FOURCC('v', 'i', 'd', 'e')) {wtrack[i].w=2;wtrack[i].v=moov->traks_[i];} else {wtrack[i].w=3;wtrack[i].v=moov->traks_[i];} } for (i = 1; i < moov->tracks_; ++i) { unsigned int j; for (j= moov->tracks_ - 1; j>=i;j--) { if(wtrack[j].w < wtrack[j-1].w) { struct trak_weight_t t = wtrack[j]; wtrack[j] = wtrack[j-1]; wtrack[j-1] = t; } } } for (i = 0; i < moov->tracks_; ++i) { moov->traks_[i] = wtrack[i].v; moov->traks_[i]->tkhd_->track_id_=i+1; } moov->mvhd_->next_track_id_=i+1; } // A fragmented MPEG4 file starts with a MOOV atom with only the mandatory // atoms fmoov = moov_init(); { unsigned int i; mvex_t* mvex = mvex_init(); fmoov->mvhd_ = mvhd_copy(moov->mvhd_); fmoov->mvhd_->duration_ = 0; fmoov->tracks_ = moov->tracks_; fmoov->mvex_ = mvex; for(i = 0; i != moov->tracks_; ++i) { unsigned int s; trak_t* trak = moov->traks_[i]; trak_t* ftrak = trak_init(); mdia_t* mdia = trak->mdia_; mdia_t* fmdia = mdia_init(); minf_t* minf = mdia->minf_; minf_t* fminf = minf_init(); stbl_t* stbl = minf->stbl_; stbl_t* fstbl = stbl_init(); fmoov->traks_[i] = ftrak; ftrak->tkhd_ = tkhd_copy(trak->tkhd_); ftrak->tkhd_->duration_ = 0; ftrak->mdia_ = fmdia; ftrak->samples_size_ = trak->samples_size_; ftrak->samples_ = (samples_t*) malloc((trak->samples_size_ + 1) * sizeof(samples_t)); memcpy(ftrak->samples_, trak->samples_, (trak->samples_size_ + 1) * sizeof(samples_t)); ftrak->smoothes_size_ = trak->smoothes_size_; ftrak->smoothes_ = (struct smooth_t*) malloc((trak->samples_size_ + 1) * sizeof(struct smooth_t)); memcpy(ftrak->smoothes_, trak->smoothes_, (trak->smoothes_size_ + 1) * sizeof(struct smooth_t)); fmdia->mdhd_ = mdhd_copy(mdia->mdhd_); // convert trak's timescale and duration fmdia->mdhd_->version_ = 1; fmdia->mdhd_->timescale_ = 10000000; fmdia->mdhd_->duration_ = 0; // trak_time_to_moov_time(fmdia->mdhd_->duration_, // fmdia->mdhd_->timescale_, mdia->mdhd_->timescale_); fmdia->hdlr_ = hdlr_copy(mdia->hdlr_); fmdia->minf_ = fminf; fminf->smhd_ = minf->smhd_ == NULL ? NULL : smhd_copy(minf->smhd_); fminf->vmhd_ = minf->vmhd_ == NULL ? NULL : vmhd_copy(minf->vmhd_); fminf->dinf_ = dinf_copy(minf->dinf_); fminf->stbl_ = fstbl; fstbl->stts_ = stts_init(); fstbl->ctts_ = ctts_init(); fstbl->stsz_ = stsz_init(); fstbl->stsc_ = stsc_init(); fstbl->stco_ = stco_init(); fstbl->stsd_ = stsd_copy(stbl->stsd_); for(s = 0; s != ftrak->samples_size_ + 1; ++s) { // SmoothStreaming uses a fixed 10000000 timescale ftrak->samples_[s].pts_ = trak_time_to_moov_time( ftrak->samples_[s].pts_, ftrak->mdia_->mdhd_->timescale_, trak->mdia_->mdhd_->timescale_); ftrak->samples_[s].cto_ = (unsigned int)(trak_time_to_moov_time( ftrak->samples_[s].cto_, ftrak->mdia_->mdhd_->timescale_, trak->mdia_->mdhd_->timescale_)); } { // update trak duration samples_t const* first = (samples_t const*)&ftrak->samples_[0]; samples_t const* last = (samples_t const*)&ftrak->samples_[ftrak->samples_size_]; ftrak->mdia_->mdhd_->duration_ = last->pts_ - first->pts_; ftrak->tkhd_->duration_ = trak_time_to_moov_time(ftrak->mdia_->mdhd_->duration_, fmoov->mvhd_->timescale_, ftrak->mdia_->mdhd_->timescale_); // update movie duration if(ftrak->tkhd_->duration_ > fmoov->mvhd_->duration_) { fmoov->mvhd_->duration_ = ftrak->tkhd_->duration_ ; } } { trex_t* trex = trex_init(); trex->track_id_ = trak->tkhd_->track_id_; trex->default_sample_description_index_ = 1; mvex->trexs_[mvex->tracks_] = trex; ++mvex->tracks_; } } { unsigned char* moov_data = mp4_context->moov_data; uint32_t moov_size = moov_write(fmoov, moov_data); bucket_insert_tail(buckets, bucket_init_memory(moov_data, moov_size)); filepos += moov_size; } } woov = woov_init(mp4_context, fmoov); mfra = mfra_init(); mfra->tracks_ = fmoov->tracks_; { unsigned int i; unsigned int tfra_entries = 0; for(i = 0; i != fmoov->tracks_; ++i) { trak_t const* trak = fmoov->traks_[i]; struct tfra_t* tfra = tfra_init(); mfra->tfras_[i] = tfra; tfra->version_ = 1; tfra->flags_ = 0; tfra->track_id_ = trak->tkhd_->track_id_; tfra->length_size_of_traf_num_ = 1; tfra->length_size_of_trun_num_ = 1; tfra->length_size_of_sample_num_ = 1; // count the number of smooth sync samples (nr of moofs) tfra->number_of_entry_ = 0; { unsigned int start; for(start = 0; start != trak->samples_size_; ++start) { { if(trak->samples_[start].is_smooth_ss_) { ++tfra->number_of_entry_; } } } } tfra->table_ = (tfra_table_t*) malloc(tfra->number_of_entry_ * sizeof(tfra_table_t)); tfra_entries += tfra->number_of_entry_; // next track } { unsigned int tfra_index = 0; trak_t const* base_trak = fmoov->traks_[0]; while(tfra_index != base_trak->smoothes_size_) { // insert moof bucket { moof_t* moof = moof_init(); bucket_t* bucket = bucket_init(BUCKET_TYPE_MEMORY); bucket_insert_tail(buckets, bucket); // create moof and write samples moof_create(mp4_context, fmoov, woov, moof, mfra, filepos, tfra_index+1, buckets, 0 /* OUTPUT_FORMAT_MP4 */); // if(options->output_format == OUTPUT_FORMAT_MP4) { unsigned int samples_count = 0; unsigned char* moof_data = NULL; unsigned int moof_size = 0; for(i = 0;i < moof->tracks_; ++i) samples_count += moof->trafs_[i]->trun_->sample_count_; moof_data = (unsigned char*)malloc(8192 + (samples_count) * 12); moof_size = moof_write(moof, moof_data); // now that we know the size of the moof atom, we know where the mdat // will start. We patch the 'data_offset' field to skip the // moof atom and the mdat header. moof->trafs_[0]->trun_->data_offset_ = moof_size + ATOM_PREAMBLE_SIZE; moof_size = moof_write(moof, moof_data); bucket->buf_ = malloc(moof_size); bucket->size_ = moof_size; memcpy(bucket->buf_, moof_data, (size_t)bucket->size_); free(moof_data); } moof_exit(moof); // advance filepos for moof and mdat atom while(*buckets != bucket) { filepos += bucket->size_; bucket = bucket->next_; } } // next fragment ++tfra_index; } } moov_exit(fmoov); { uint32_t woov_size = woov_write(mp4_context, woov, buckets); int offset = 0; offset += 28; // ftyp offset += woov_size; //woov offset += ATOM_PREAMBLE_SIZE; //mdat woov->mdat_size+=8; // header; if(woov->mdat_size > UINT32_MAX) { offset+=8; woov->mdat_size+=8; } for(i = 0; i != woov->moov->tracks_; ++i) { trak_t* trak = woov->moov->traks_[i]; stco_shift_offsets_inplace( (unsigned char*)trak->mdia_->minf_->stbl_->stco_->stco_inplace_, (int)offset); } write_32(woov_data + 8, woov_size); write_64(woov_data + 12, woov->mdat_size); bucket_insert_tail(buckets, bucket_init_memory(woov_data, sizeof(woov_data))); woov_exit(woov); } // Write the Movie Fragment Random Access (MFRA) atom { unsigned char* mfra_data = (unsigned char*)malloc(8192 + tfra_entries * 28); uint32_t mfra_size = mfra_write(mfra, mfra_data); bucket_insert_tail(buckets, bucket_init_memory(mfra_data, mfra_size)); mfra_exit(mfra); free(mfra_data); } } return result; }