diff --git a/app/muxer_flags.h b/app/muxer_flags.h index 3cc726380e..b002bc2578 100644 --- a/app/muxer_flags.h +++ b/app/muxer_flags.h @@ -37,6 +37,10 @@ DEFINE_bool(fragment_sap_aligned, true, "Force fragments to begin with stream access points. This flag " "implies segment_sap_aligned."); +DEFINE_bool(normalize_presentation_timestamp, + true, + "Set to true to normalize the presentation timestamps to start" + "from zero."); DEFINE_int32(num_subsegments_per_sidx, 1, "For ISO BMFF only. Set the number of subsegments in each " diff --git a/app/packager_main.cc b/app/packager_main.cc index 2795047da6..8083d19059 100644 --- a/app/packager_main.cc +++ b/app/packager_main.cc @@ -88,6 +88,8 @@ bool GetMuxerOptions(MuxerOptions* muxer_options) { muxer_options->fragment_duration = FLAGS_fragment_duration; muxer_options->segment_sap_aligned = FLAGS_segment_sap_aligned; muxer_options->fragment_sap_aligned = FLAGS_fragment_sap_aligned; + muxer_options->normalize_presentation_timestamp = + FLAGS_normalize_presentation_timestamp; muxer_options->num_subsegments_per_sidx = FLAGS_num_subsegments_per_sidx; muxer_options->output_file_name = FLAGS_output; muxer_options->segment_template = FLAGS_segment_template; diff --git a/media/base/muxer_options.cc b/media/base/muxer_options.cc index 3882805d10..44e6b77cf5 100644 --- a/media/base/muxer_options.cc +++ b/media/base/muxer_options.cc @@ -12,6 +12,7 @@ MuxerOptions::MuxerOptions() fragment_duration(0), segment_sap_aligned(false), fragment_sap_aligned(false), + normalize_presentation_timestamp(false), num_subsegments_per_sidx(0) {} MuxerOptions::~MuxerOptions() {} diff --git a/media/base/muxer_options.h b/media/base/muxer_options.h index 8a2e58a6fa..c2bfb08bc8 100644 --- a/media/base/muxer_options.h +++ b/media/base/muxer_options.h @@ -35,6 +35,9 @@ struct MuxerOptions { // that segment_sap_aligned is true as well. bool fragment_sap_aligned; + // Set to true to normalize the presentation timestamps to start from zero. + bool normalize_presentation_timestamp; + // For ISO BMFF only. // Set the number of subsegments in each SIDX box. If 0, a single SIDX box // is used per segment. If -1, no SIDX box is used. Otherwise, the Muxer diff --git a/media/mp4/mp4_fragmenter.cc b/media/mp4/mp4_fragmenter.cc index 9cd555b338..468704607e 100644 --- a/media/mp4/mp4_fragmenter.cc +++ b/media/mp4/mp4_fragmenter.cc @@ -42,14 +42,17 @@ namespace mp4 { MP4Fragmenter::MP4Fragmenter(TrackFragment* traf, scoped_ptr encryptor, int64 clear_time, - uint8 nalu_length_size) + uint8 nalu_length_size, + bool normalize_presentation_timestamp) : encryptor_(encryptor.Pass()), nalu_length_size_(nalu_length_size), traf_(traf), fragment_finalized_(false), fragment_duration_(0), - earliest_presentation_time_(0), - first_sap_time_(0), + normalize_presentation_timestamp_(normalize_presentation_timestamp), + presentation_start_time_(kInvalidTime), + earliest_presentation_time_(kInvalidTime), + first_sap_time_(kInvalidTime), clear_time_(clear_time) {} MP4Fragmenter::~MP4Fragmenter() {} @@ -78,12 +81,30 @@ Status MP4Fragmenter::AddSample(scoped_refptr sample) { data_->AppendArray(sample->data(), sample->data_size()); fragment_duration_ += sample->duration(); - if (earliest_presentation_time_ > sample->pts()) - earliest_presentation_time_ = sample->pts(); + int64 pts = sample->pts(); + if (normalize_presentation_timestamp_) { + // Normalize PTS to start from 0. Some players do not like non-zero + // presentation starting time. + // TODO(kqyang): Do we need to add an EditList? + if (presentation_start_time_ == kInvalidTime) { + presentation_start_time_ = pts; + pts = 0; + } else { + // Can we safely assume the first sample in the media has the earliest + // presentation timestamp? + DCHECK_GT(pts, presentation_start_time_); + pts -= presentation_start_time_; + } + } + + // Set |earliest_presentation_time_| to |pts| if |pts| is smaller or if it is + // not yet initialized (kInvalidTime > pts is always true). + if (earliest_presentation_time_ > pts) + earliest_presentation_time_ = pts; if (sample->is_key_frame()) { - if (kInvalidTime == first_sap_time_) - first_sap_time_ = sample->pts(); + if (first_sap_time_ == kInvalidTime) + first_sap_time_ = pts; } return Status::OK; } diff --git a/media/mp4/mp4_fragmenter.h b/media/mp4/mp4_fragmenter.h index b0039375fb..fc78ad6236 100644 --- a/media/mp4/mp4_fragmenter.h +++ b/media/mp4/mp4_fragmenter.h @@ -31,11 +31,13 @@ class MP4Fragmenter { // Caller retains the ownership of |traf| and transfers ownership of // |encryptor|. |clear_time| specifies clear time in the current track // timescale. |nalu_length_size| specifies NAL unit length size, for - // subsample encryption. + // subsample encryption. |normalize_presentation_timestamp| defines whether + // PTS should be normalized to start from zero. MP4Fragmenter(TrackFragment* traf, scoped_ptr encryptor, int64 clear_time, - uint8 nalu_length_size); + uint8 nalu_length_size, + bool normalize_presentation_timestamp); ~MP4Fragmenter(); virtual Status AddSample(scoped_refptr sample); @@ -81,6 +83,8 @@ class MP4Fragmenter { TrackFragment* traf_; bool fragment_finalized_; uint64 fragment_duration_; + bool normalize_presentation_timestamp_; + int64 presentation_start_time_; uint64 earliest_presentation_time_; uint64 first_sap_time_; int64 clear_time_; diff --git a/media/mp4/mp4_segmenter.cc b/media/mp4/mp4_segmenter.cc index d3c8fcf1aa..d4a573bf9a 100644 --- a/media/mp4/mp4_segmenter.cc +++ b/media/mp4/mp4_segmenter.cc @@ -70,7 +70,8 @@ Status MP4Segmenter::Initialize(EncryptorSource* encryptor_source, &moof_->tracks[i], encryptor.Pass(), clear_lead_in_seconds * streams[i]->info()->time_scale(), - nalu_length_size); + nalu_length_size, + options_.normalize_presentation_timestamp); } // Choose the first stream if there is no VIDEO.