extern int mp4_split(struct mp4_context_t* mp4_context, unsigned int* trak_sample_start, unsigned int* trak_sample_end, mp4_split_options_t const* options) { int result; float start_time = options->start; float end_time = options->end; moov_build_index(mp4_context, mp4_context->moov); { struct moov_t const* moov = mp4_context->moov; long moov_time_scale = moov->mvhd_->timescale_; unsigned int start = (unsigned int)(start_time * moov_time_scale + 0.5f); unsigned int end = (unsigned int)(end_time * moov_time_scale + 0.5f); // for every trak, convert seconds to sample (time-to-sample). // adjust sample to keyframe result = get_aligned_start_and_end(mp4_context, start, end, trak_sample_start, trak_sample_end); if (options->exact){ // now we need to find the audio track and RESET *its* trak_sample_start // time to the exact start time we want, regardless of keyframes unsigned int i=0; for(i=0; i != moov->tracks_; ++i){ struct trak_t* trak = moov->traks_[i]; if (trak->mdia_->hdlr_->handler_type_ == FOURCC('s','o','u','n')){ // the FOURCC is soun(d) AKA audio track long trak_time_scale = trak->mdia_->mdhd_->timescale_; struct stts_t* stts = trak->mdia_->minf_->stbl_->stts_; unsigned int start_exact_time_sample = stts_get_sample(stts, moov_time_to_trak_time((options->start * moov_time_scale), moov_time_scale, trak_time_scale)); MP4_WARNING("FFGOP: AUDIO REWRITING trak_sample_start[%i]: %u => %u\n", i, trak_sample_start[i], start_exact_time_sample); trak_sample_start[i] = start_exact_time_sample; } } } } return result; }
static void trak_fast_forward_first_partial_GOP(struct mp4_context_t const* mp4_context, struct mp4_split_options_t* options, struct trak_t *trak, unsigned int start_sample) { if (!trak->mdia_->minf_->stbl_->stts_){ MP4_WARNING("FFGOP: NO STTS FOR THIS TRACK -- CANNOT ADJUST THIS TRACK\n",""); return; } // NOTE: STTS atom = "time to sample" atom, which is what we use // (and STSS atom = "sync samples" atom, which is list of keyframes) struct stts_t* stts = trak->mdia_->minf_->stbl_->stts_; // find the sample frame location of the exact desired time we wanted to // start at (regardless of keyframes!) struct moov_t* moov = mp4_context->moov; float moov_time_scale = moov->mvhd_->timescale_; float trak_time_scale = trak->mdia_->mdhd_->timescale_; unsigned int start_exact_time_sample = stts_get_sample(stts, moov_time_to_trak_time((options->start * moov_time_scale), moov_time_scale, trak_time_scale)); if (start_exact_time_sample == start_sample) return; // starting at wanted time already, nothing to do! MP4_INFO("FFGOP: start: %fs; sample start exact time:%u; sample keyframe just before:%u\n", options->start, start_exact_time_sample, start_sample); MP4_INFO("FFGOP: moov_time_scale = %f, trak_time_scale = %f\n", moov_time_scale, trak_time_scale); // In practice, IA videos seem to always have stts->entries_ == 1 8-) // That's the starting number / table setup. // The STTS atom will be rewritten by caller, expanding to more entries since we dropping durations! unsigned int s=0, i=0, j=0, nRewritten=0; for (j=0; j < stts->entries_; j++){ for (i=0; i < stts->table_[j].sample_count_; i++){ // NOTE: begin time-shifting at "start_sample" bec. mod_h264_streaming // finds the keyframe (sample time) before the exact start time, and *then* // decrements by one. so those samples "go out the door" -- and thus we // need to rewrite them, too if (s >= start_sample && s < start_exact_time_sample){ /* see mp4_io.h for samples_t (pts_/size_/pos_/cto_/is_ss_/is_smooth_ss_) */ samples_t sample = trak->samples_[s]; // let's change current PTS to something fractionally *just* less than // the PTS of the first frame we want to see fully. each frame we dont want // to see is 1 fraction earlier PTS than the next frame PTS. uint64_t pts = sample.pts_; uint64_t pts2 = trak->samples_[start_exact_time_sample].pts_ - (start_exact_time_sample-s); //uint64_t pts2 = trak->samples_[start_exact_time_sample].pts_ + (s <= (start_sample+1) ? -2 : -1); trak->samples_[s].pts_ = pts2; MP4_INFO("FFGOP: stts[%d] samples_[%d].pts_ = %lu (%0.3fsec) REWRITING TO %lu (%0.3fsec)\n", j, s, pts, ((float)pts / trak_time_scale), pts2, ((float)pts2 / trak_time_scale)); nRewritten++; } s++; } } if (nRewritten){ MP4_WARNING("FFGOP: ==============> %u FRAMES GOT FAST-FORWARDED (APPROXIMATELY %2.1f SECONDS ASSUMING 29.97 fps, YMMV)\n\n", nRewritten, nRewritten/29.97); } }
// reported by everwanna: // av out of sync because: // audio track 0 without stss, seek to the exact time. // video track 1 with stss, seek to the nearest key frame time. // // fixed: // first pass we get the new aligned times for traks with an stss present // second pass is for traks without an stss static int get_aligned_start_and_end(struct mp4_context_t const* mp4_context, unsigned int start, unsigned int end, unsigned int* trak_sample_start, unsigned int* trak_sample_end) { unsigned int pass; struct moov_t* moov = mp4_context->moov; long moov_time_scale = moov->mvhd_->timescale_; for(pass = 0; pass != 2; ++pass) { unsigned int i; for(i = 0; i != moov->tracks_; ++i) { struct trak_t* trak = moov->traks_[i]; struct stbl_t* stbl = trak->mdia_->minf_->stbl_; long trak_time_scale = trak->mdia_->mdhd_->timescale_; // 1st pass: stss present, 2nd pass: no stss present if(pass == 0 && !stbl->stss_) continue; if(pass == 1 && stbl->stss_) continue; // get start if(start == 0) { trak_sample_start[i] = start; } else { start = stts_get_sample(stbl->stts_, moov_time_to_trak_time(start, moov_time_scale, trak_time_scale)); MP4_INFO("start=%u (trac time)\n", start); MP4_INFO("start=%.2f (seconds)\n", stts_get_time(stbl->stts_, start) / (float)trak_time_scale); start = stbl_get_nearest_keyframe(stbl, start + 1) - 1; MP4_INFO("start=%u (zero based keyframe)\n", start); trak_sample_start[i] = start; start = (unsigned int)(trak_time_to_moov_time( stts_get_time(stbl->stts_, start), moov_time_scale, trak_time_scale)); MP4_INFO("start=%u (moov time)\n", start); MP4_INFO("start=%.2f (seconds)\n", start / (float)moov_time_scale); } // get end if(end == 0) { // The default is till-the-end of the track trak_sample_end[i] = trak->samples_size_; } else { end = stts_get_sample(stbl->stts_, moov_time_to_trak_time(end, moov_time_scale, trak_time_scale)); MP4_INFO("end=%u (trac time)\n", end); MP4_INFO("end=%.2f (seconds)\n", stts_get_time(stbl->stts_, end) / (float)trak_time_scale); if(end >= trak->samples_size_) { end = trak->samples_size_; } else { end = stbl_get_nearest_keyframe(stbl, end + 1) - 1; } MP4_INFO("end=%u (zero based keyframe)\n", end); trak_sample_end[i] = end; // MP4_INFO("endframe=%u, samples_size_=%u\n", end, trak->samples_size_); end = (unsigned int)trak_time_to_moov_time( stts_get_time(stbl->stts_, end), moov_time_scale, trak_time_scale); MP4_INFO("end=%u (moov time)\n", end); MP4_INFO("end=%.2f (seconds)\n", end / (float)moov_time_scale); } } } MP4_INFO("start=%u\n", start); MP4_INFO("end=%u\n", end); if(end && start >= end) { return 0; } return 1; }
// reported by everwanna: // av out of sync because: // audio track 0 without stss, seek to the exact time. // video track 1 with stss, seek to the nearest key frame time. // // fixed: // first pass we get the new aligned times for traks with an stss present // second pass is for traks without an stss static int get_aligned_start_and_end(mp4_context_t const* mp4_context, int64_t start, int64_t end, unsigned int* trak_sample_start, unsigned int* trak_sample_end) { unsigned int pass; moov_t const* moov = mp4_context->moov; long moov_time_scale = moov->mvhd_->timescale_; for(pass = 0; pass != 2; ++pass) { unsigned int i; for(i = 0; i != moov->tracks_; ++i) { trak_t const* trak = moov->traks_[i]; long trak_time_scale = trak->mdia_->mdhd_->timescale_; if(trak->samples_size_ == 0) { trak_sample_start[i] = 0; trak_sample_end[i] = 0; continue; } // get start { unsigned int sample_index = time_to_sample(trak, moov_time_to_trak_time(start, moov_time_scale, trak_time_scale)); // backtrack to nearest keyframe if(!trak->samples_[sample_index].is_ss_) { while(sample_index && !trak->samples_[sample_index].is_ss_) { --sample_index; } start = trak_time_to_moov_time(trak->samples_[sample_index].pts_, moov_time_scale, trak_time_scale); } trak_sample_start[i] = sample_index; // MP4_INFO("ts=%"PRId64" (moov time)\n", start); MP4_INFO("ts=%.2f (seconds)\n", trak->samples_[sample_index].pts_ / (float)trak_time_scale); } // get end { unsigned int sample_index; if(end == 0) { // The default is till-the-end of the track sample_index = trak->samples_size_; } else { sample_index = time_to_sample(trak, moov_time_to_trak_time(end, moov_time_scale, trak_time_scale)); } // backtrack to nearest keyframe if(!trak->samples_[sample_index].is_ss_) { while(sample_index && !trak->samples_[sample_index].is_ss_) { --sample_index; } end = trak_time_to_moov_time(trak->samples_[sample_index].pts_, moov_time_scale, trak_time_scale); } trak_sample_end[i] = sample_index; // MP4_INFO("te=%"PRId64" (moov time)\n", end); MP4_INFO("te=%.2f (seconds)\n", trak->samples_[sample_index].pts_ / (float)trak_time_scale); } } } MP4_INFO("final start=%"PRId64"\n", start); MP4_INFO("final end=%"PRId64"\n", end); if(end && start >= end) { return 0; } return 1; }