static void compress_moov(struct mp4_context_t* mp4_context, struct moov_t* moov, unsigned char* moov_data, uint64_t* moov_size) { uLong sourceLen = (uLong)(*moov_size - ATOM_PREAMBLE_SIZE); uLong destLen = compressBound(sourceLen); unsigned char* cmov = (unsigned char*)malloc(destLen); int zstatus = compress(cmov, &destLen, moov_data, sourceLen); if(zstatus == Z_OK) { MP4_INFO("cmov size = %lu (%ld%%)\n", destLen, 100 * destLen / sourceLen); } { const int extra_space = 4096; if(destLen + extra_space < sourceLen) { const int bytes_saved = sourceLen - destLen; uLong destLen2; int extra = 0; MP4_INFO("shifting offsets by %d\n", -bytes_saved); moov_shift_offsets_inplace(moov, -bytes_saved); extra += ATOM_PREAMBLE_SIZE + 4; // dcom extra += ATOM_PREAMBLE_SIZE + 4; // cmvd extra += ATOM_PREAMBLE_SIZE; // cmov extra += ATOM_PREAMBLE_SIZE + extra_space; // free MP4_INFO("shifting offsets by %d\n", extra); moov_shift_offsets_inplace(moov, extra); // recompress destLen2 = compressBound(sourceLen); zstatus = compress(cmov, &destLen2, moov_data, sourceLen); if(zstatus == Z_OK) { MP4_INFO("cmov size = %lu (%ld%%)\n", destLen2, 100 * destLen2 / sourceLen); if(destLen2 < destLen + extra_space) { // copy compressed movie atom unsigned char* outbuffer = moov_data; uint32_t dcom_size = ATOM_PREAMBLE_SIZE + 4; uint32_t cmvd_size = ATOM_PREAMBLE_SIZE + 4 + destLen2; uint32_t cmov_size = ATOM_PREAMBLE_SIZE + dcom_size + cmvd_size; uint32_t free_size = ATOM_PREAMBLE_SIZE + extra_space + destLen - destLen2; *moov_size = ATOM_PREAMBLE_SIZE + cmov_size + free_size; outbuffer = write_32(outbuffer, (uint32_t)*moov_size); // skip 'moov' outbuffer += 4; outbuffer = write_32(outbuffer, cmov_size); { outbuffer = write_32(outbuffer, FOURCC('c', 'm', 'o', 'v')); outbuffer = write_32(outbuffer, dcom_size); outbuffer = write_32(outbuffer, FOURCC('d', 'c', 'o', 'm')); outbuffer = write_32(outbuffer, FOURCC('z', 'l', 'i', 'b')); outbuffer = write_32(outbuffer, cmvd_size); { outbuffer = write_32(outbuffer, FOURCC('c', 'm', 'v', 'd')); outbuffer = write_32(outbuffer, sourceLen); memcpy(outbuffer, cmov, destLen2); outbuffer += destLen2; } } // add final padding outbuffer = write_32(outbuffer, free_size); outbuffer = write_32(outbuffer, FOURCC('f', 'r', 'e', 'e')); { const char free_bytes[8] = { 'C', 'o', 'd', 'e','S','h', 'o', 'p' }; uint32_t padding_index; for(padding_index = ATOM_PREAMBLE_SIZE; padding_index != free_size; ++padding_index) { outbuffer[padding_index] = free_bytes[padding_index % 8]; } } } else { MP4_ERROR("%s", "2nd pass compress overflow\n"); } } } } free(cmov); }
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; }
static int moof_create(struct mp4_context_t const* mp4_context, struct moov_t* fmoov, struct woov_t* woov, struct moof_t* moof, struct mfra_t* mfra, uint64_t moof_offset, unsigned int seq, struct bucket_t** buckets, int output_raw) { uint32_t mdat_size = ATOM_PREAMBLE_SIZE; bucket_t* mdat_bucket = 0; unsigned int i = 0; uint64_t start_time = 0, end_time = 0; unsigned int start=0, end=0; if(!output_raw) { unsigned char mdat_buffer[32]; mp4_atom_t mdat_atom; int mdat_header_size; mdat_atom.type_ = FOURCC('m', 'd', 'a', 't'); mdat_atom.short_size_ = 0; mdat_header_size = mp4_atom_write_header(mdat_buffer, &mdat_atom); mdat_bucket = bucket_init_memory(mdat_buffer, mdat_header_size); bucket_insert_tail(buckets, mdat_bucket); } moof->mfhd_ = mfhd_init(); moof->mfhd_->sequence_number_ = seq; for(i = 0;i < fmoov->tracks_; i++) { uint32_t trun_mdat_size=0; struct trak_t * trak = fmoov->traks_[i]; struct stsd_t const* stsd = trak->mdia_->minf_->stbl_->stsd_; struct sample_entry_t const* sample_entry = &stsd->sample_entries_[0]; // int is_avc = sample_entry->fourcc_ == FOURCC('a', 'v', 'c', '1'); struct traf_t* traf = traf_init(); moof->trafs_[moof->tracks_] = traf; ++moof->tracks_; start = trak->smoothes_[moof->mfhd_->sequence_number_-1].start; end = trak->smoothes_[moof->mfhd_->sequence_number_].start; { // struct ctts_t const* ctts = trak->mdia_->minf_->stbl_->ctts_; unsigned int trun_index = 0; unsigned int s; struct bucket_t* bucket_prev = 0; traf->tfhd_ = tfhd_init(); // 0x000020 = default-sample-flags present traf->tfhd_->flags_ = 0x000020; traf->tfhd_->track_id_ = trak->tkhd_->track_id_; // sample_degradation_priority traf->tfhd_->default_sample_flags_ = 0x000000; // sample_is_difference_sample if(trak->mdia_->hdlr_->handler_type_ == FOURCC('v', 'i', 'd', 'e')) { traf->tfhd_->default_sample_flags_ |= (1 << 16); } traf->trun_ = trun_init(); // 0x0001 = data-offset is present // 0x0004 = first_sample_flags is present // 0x0100 = sample-duration is present // 0x0200 = sample-size is present traf->trun_->flags_ = 0x000305; // 0x0800 = sample-composition-time-offset is present // if(ctts) { traf->trun_->flags_ |= 0x000800; } traf->trun_->sample_count_ = end - start; // traf->trun_->data_offset_ = // set below traf->trun_->first_sample_flags_= 0x00000000; traf->trun_->table_ = (trun_table_t*)malloc(traf->trun_->sample_count_ * sizeof(trun_table_t)); // traf->trun_->trak_ = trak; // traf->trun_->start_ = start; // traf->trun_->uuid0_pts_ = trak_time_to_moov_time( // trak->samples_[start].pts_, 10000000, trak->mdia_->mdhd_->timescale_); for(s = start; s != end; ++s) { uint64_t pts1 = trak->samples_[s + 1].pts_; uint64_t pts0 = trak->samples_[s + 0].pts_; unsigned int sample_duration = (unsigned int)(pts1 - pts0); uint64_t sample_pos = trak->samples_[s].pos_; unsigned int sample_size = trak->samples_[s].size_; unsigned int cto = trak->samples_[s].cto_; traf->trun_->table_[trun_index].sample_duration_ = sample_duration; traf->trun_->table_[trun_index].sample_size_ = sample_size; traf->trun_->table_[trun_index].sample_composition_time_offset_ = cto; MP4_INFO( "frame=%u pts=%"PRIi64" cto=%u duration=%u offset=%"PRIu64" size=%u\n", s, trak->samples_[s].pts_, trak->samples_[s].cto_, sample_duration, sample_pos, sample_size); if(trak->mdia_->hdlr_->handler_type_ == FOURCC('v', 'i', 'd', 'e')) { #if 0 if(bucket_prev == NULL) { // TODO: return error when no SPS and PPS are available if(is_avc) { unsigned char* buffer; unsigned char* p; unsigned int sps_pps_size = sample_entry->nal_unit_length_ + sample_entry->sps_length_ + sample_entry->nal_unit_length_ + sample_entry->pps_length_; if(sps_pps_size == 0) { MP4_ERROR("%s", "[Error] No SPS or PPS available\n"); return 0; } buffer = (unsigned char*)malloc(sps_pps_size); p = buffer; // sps p = write_32(p, 0x00000001); memcpy(p, sample_entry->sps_, sample_entry->sps_length_); p += sample_entry->sps_length_; // pps p = write_32(p, 0x00000001); memcpy(p, sample_entry->pps_, sample_entry->pps_length_); p += sample_entry->pps_length_; bucket_insert_tail(buckets, bucket_init_memory(buffer, sps_pps_size)); free(buffer); traf->trun_->table_[trun_index].sample_size_ += sps_pps_size; mdat_size += sps_pps_size; trun_mdat_size += sps_pps_size; } } #endif #if 0 if(is_avc) { static const char nal_marker[4] = { 0, 0, 0, 1 }; uint64_t first = sample_pos; uint64_t last = sample_pos + sample_size; while(first != last) { unsigned char buffer[4]; unsigned int nal_size; bucket_insert_tail(buckets, bucket_init_memory(nal_marker, 4)); if(fseeko(mp4_context->infile, first, SEEK_SET) != 0) { MP4_ERROR("%s", "Reached end of file prematurely\n"); return 0; } if(fread(buffer, sample_entry->nal_unit_length_, 1, mp4_context->infile) != 1) { MP4_ERROR("%s", "Error reading NAL size\n"); return 0; } nal_size = read_n(buffer, sample_entry->nal_unit_length_ * 8); if(nal_size == 0) { MP4_ERROR("%s", "Invalid NAL size (0)\n"); return 0; } bucket_prev = bucket_init_file(first + sample_entry->nal_unit_length_, nal_size); bucket_insert_tail(buckets, bucket_prev); first += sample_entry->nal_unit_length_ + nal_size; } } else #endif { // try to merge buckets if(bucket_prev && sample_pos == bucket_prev->offset_ + bucket_prev->size_) { bucket_prev->size_ += sample_size; } else { bucket_prev = bucket_init_file(sample_pos, sample_size); bucket_insert_tail(buckets, bucket_prev); } } } else if(trak->mdia_->hdlr_->handler_type_ == FOURCC('s', 'o', 'u', 'n')) { // ADTS frame header if(sample_entry->wFormatTag == 0x00ff && output_raw) { unsigned char buffer[7]; sample_entry_get_adts(sample_entry, sample_size, buffer); bucket_insert_tail(buckets, bucket_init_memory(buffer, 7)); traf->trun_->table_[trun_index].sample_size_ += 7; mdat_size += 7; trun_mdat_size += 7; bucket_prev = NULL; } // try to merge buckets if(bucket_prev && sample_pos == bucket_prev->offset_ + bucket_prev->size_) { bucket_prev->size_ += sample_size; } else { bucket_prev = bucket_init_file(sample_pos, sample_size); bucket_insert_tail(buckets, bucket_prev); } } mdat_size += sample_size; trun_mdat_size += sample_size; ++trun_index; { // update woov track samples woov->moov->traks_[moof->tracks_-1]->samples_[woov->moov->traks_[moof->tracks_-1]->samples_size_++]=trak->samples_[s]; woov->moov->traks_[moof->tracks_-1]->samples_[woov->moov->traks_[moof->tracks_-1]->samples_size_]=trak->samples_[s+1]; } } { struct tfra_t* tfra = mfra->tfras_[moof->tracks_-1]; tfra_table_t* table = &tfra->table_[moof->mfhd_->sequence_number_-1]; table->time_ = trak->samples_[start].pts_; table->moof_offset_ = moof_offset; table->traf_number_ = moof->tracks_-1; table->trun_number_ = 0; table->sample_number_ = 0; stco_add_chunk(woov->moov->traks_[moof->tracks_-1]->mdia_->minf_->stbl_->stco_, woov->mdat_size); woov->mdat_size += trun_mdat_size; stsc_add_chunk(woov->moov->traks_[moof->tracks_-1]->mdia_->minf_->stbl_->stsc_, woov->moov->traks_[moof->tracks_-1]->mdia_->minf_->stbl_->stco_->entries_-1, end-start, 1); } // update size of mdat atom if(mdat_bucket) { write_32((unsigned char*)mdat_bucket->buf_, mdat_size); } } } return 1; }