diff --git a/packager/app/test/testdata/bear-320x240-vorbis-golden.webm b/packager/app/test/testdata/bear-320x240-vorbis-golden.webm index 37237b9638..66a902379a 100644 Binary files a/packager/app/test/testdata/bear-320x240-vorbis-golden.webm and b/packager/app/test/testdata/bear-320x240-vorbis-golden.webm differ diff --git a/packager/app/test/testdata/bear-320x240-vorbis-webm-golden.mpd b/packager/app/test/testdata/bear-320x240-vorbis-webm-golden.mpd index a4c68fbba5..0cbd5ff024 100644 --- a/packager/app/test/testdata/bear-320x240-vorbis-webm-golden.mpd +++ b/packager/app/test/testdata/bear-320x240-vorbis-webm-golden.mpd @@ -3,10 +3,10 @@ - + output_audio.webm - + diff --git a/packager/app/test/testdata/bear-320x240-vp9-golden.webm b/packager/app/test/testdata/bear-320x240-vp9-golden.webm index d387a02576..0ec03cd26d 100644 Binary files a/packager/app/test/testdata/bear-320x240-vp9-golden.webm and b/packager/app/test/testdata/bear-320x240-vp9-golden.webm differ diff --git a/packager/app/test/testdata/bear-320x240-vp9-webm-golden.mpd b/packager/app/test/testdata/bear-320x240-vp9-webm-golden.mpd index 8ee2342618..59314d26fc 100644 --- a/packager/app/test/testdata/bear-320x240-vp9-webm-golden.mpd +++ b/packager/app/test/testdata/bear-320x240-vp9-webm-golden.mpd @@ -3,9 +3,9 @@ - + output_video.webm - + diff --git a/packager/app/test/testdata/bear-640x360-vp8-cenc-golden.webm b/packager/app/test/testdata/bear-640x360-vp8-cenc-golden.webm index 0111edcc04..76ce0e1c93 100644 Binary files a/packager/app/test/testdata/bear-640x360-vp8-cenc-golden.webm and b/packager/app/test/testdata/bear-640x360-vp8-cenc-golden.webm differ diff --git a/packager/app/test/testdata/bear-640x360-vp8-cenc-webm-golden.mpd b/packager/app/test/testdata/bear-640x360-vp8-cenc-webm-golden.mpd index 914e633901..0d4fd16daf 100644 --- a/packager/app/test/testdata/bear-640x360-vp8-cenc-webm-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-vp8-cenc-webm-golden.mpd @@ -3,12 +3,12 @@ - + AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2 output_video.webm - + diff --git a/packager/app/test/testdata/bear-640x360-vp8-golden.webm b/packager/app/test/testdata/bear-640x360-vp8-golden.webm index 0de835fd4a..59fdcdac00 100644 Binary files a/packager/app/test/testdata/bear-640x360-vp8-golden.webm and b/packager/app/test/testdata/bear-640x360-vp8-golden.webm differ diff --git a/packager/app/test/testdata/bear-640x360-vp8-webm-golden.mpd b/packager/app/test/testdata/bear-640x360-vp8-webm-golden.mpd index 38e0b57c58..a4951cb6ea 100644 --- a/packager/app/test/testdata/bear-640x360-vp8-webm-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-vp8-webm-golden.mpd @@ -3,9 +3,9 @@ - + output_video.webm - + diff --git a/packager/media/formats/webm/encrypted_segmenter_unittest.cc b/packager/media/formats/webm/encrypted_segmenter_unittest.cc index 2a4b0aacdc..558cee07e3 100644 --- a/packager/media/formats/webm/encrypted_segmenter_unittest.cc +++ b/packager/media/formats/webm/encrypted_segmenter_unittest.cc @@ -37,8 +37,8 @@ const uint8_t kBasicSupportData[] = { 0x42, 0x87, 0x81, 0x02, // DocTypeReadVersion: 2 0x42, 0x85, 0x81, 0x02, - // ID: Segment, Payload Size: 400 - 0x18, 0x53, 0x80, 0x67, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x90, + // ID: Segment, Payload Size: 406 + 0x18, 0x53, 0x80, 0x67, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x96, // ID: SeekHead, Payload Size: 30 0x11, 0x4d, 0x9b, 0x74, 0x9e, // ID: Seek, Payload Size: 12 @@ -52,7 +52,7 @@ const uint8_t kBasicSupportData[] = { // SeekID: binary(4) (Cues) 0x53, 0xab, 0x84, 0x1c, 0x53, 0xbb, 0x6b, // SeekPosition: 429 - 0x53, 0xac, 0x82, 0x01, 0xad, + 0x53, 0xac, 0x82, 0x01, 0xb3, // ID: Void, Payload Size: 52 0xec, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -121,8 +121,8 @@ const uint8_t kBasicSupportData[] = { 0x54, 0xb0, 0x81, 0x64, // DisplayHeight: 100 0x54, 0xba, 0x81, 0x64, - // ID: Cluster, Payload Size: 95 - 0x1f, 0x43, 0xb6, 0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, + // ID: Cluster, Payload Size: 101 + 0x1f, 0x43, 0xb6, 0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, // Timecode: 0 0xe7, 0x81, 0x00, // ID: SimpleBlock, Payload Size: 10 @@ -155,14 +155,18 @@ const uint8_t kBasicSupportData[] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x47, // Frame Data: 0x0d, 0x8e, 0xae, 0xbe, 0xd0, - // ID: SimpleBlock, Payload Size: 18 - 0xa3, 0x92, 0x81, 0x0f, 0xa0, 0x80, - // Signal Byte: Encrypted - 0x01, - // IV: - 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x48, - // Frame Data: - 0xa5, 0x97, 0xf8, 0x9e, 0x87, + // ID: BlockGroup, Payload Size: 24 + 0xa0, 0x98, + // ID: Block, Payload Size: 18 + 0xa1, 0x92, 0x81, 0x0f, 0xa0, 0x00, + // Signal Byte: Encrypted + 0x01, + // IV: + 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x48, + // Frame Data: + 0xa5, 0x97, 0xf8, 0x9e, 0x87, + // BlockDuration: 1000 + 0x9b, 0x82, 0x03, 0xe8, // ID: Cues, Payload Size: 14 0x1c, 0x53, 0xbb, 0x6b, 0x8e, // ID: CuePoint, Payload Size: 12 diff --git a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc index 708104f097..6d303c64c1 100644 --- a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc @@ -85,8 +85,8 @@ const uint8_t kBasicSupportDataInit[] = { 0x54, 0xba, 0x81, 0x64 }; const uint8_t kBasicSupportDataSegment[] = { - // ID: Cluster, Payload Size: 58 - 0x1f, 0x43, 0xb6, 0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, + // ID: Cluster, Payload Size: 64 + 0x1f, 0x43, 0xb6, 0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, // Timecode: 0 0xe7, 0x81, 0x00, // ID: SimpleBlock, Payload Size: 9 @@ -97,8 +97,12 @@ const uint8_t kBasicSupportDataSegment[] = { 0xa3, 0x89, 0x81, 0x07, 0xd0, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00, // ID: SimpleBlock, Payload Size: 9 0xa3, 0x89, 0x81, 0x0b, 0xb8, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00, - // ID: SimpleBlock, Payload Size: 9 - 0xa3, 0x89, 0x81, 0x0f, 0xa0, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00 + // ID: BlockGroup, Payload Size: 15 + 0xa0, 0x8f, + // ID: Block, Payload Size: 9 + 0xa1, 0x89, 0x81, 0x0f, 0xa0, 0x00, 0xde, 0xad, 0xbe, 0xef, 0x00, + // BlockDuration: 1000 + 0x9b, 0x82, 0x03, 0xe8 }; } // namespace diff --git a/packager/media/formats/webm/segmenter.cc b/packager/media/formats/webm/segmenter.cc index d0233bd985..34e0cafbde 100644 --- a/packager/media/formats/webm/segmenter.cc +++ b/packager/media/formats/webm/segmenter.cc @@ -27,13 +27,14 @@ int64_t kSecondsToNs = 1000000000L; } // namespace Segmenter::Segmenter(const MuxerOptions& options) - : options_(options), + : reference_frame_timestamp_(0), + options_(options), info_(NULL), muxer_listener_(NULL), progress_listener_(NULL), progress_target_(0), accumulated_progress_(0), - total_duration_(0), + first_timestamp_(0), sample_duration_(0), segment_payload_pos_(0), cluster_length_sec_(0), @@ -66,7 +67,8 @@ Status Segmenter::Initialize(scoped_ptr writer, segment_info_.set_writing_app(version_string.c_str()); if (options().single_segment) { // Set an initial duration so the duration element is written; will be - // overwritten at the end. + // overwritten at the end. This works because this is a float and floats + // are always the same size. segment_info_.set_duration(1); } @@ -97,12 +99,19 @@ Status Segmenter::Initialize(scoped_ptr writer, } Status Segmenter::Finalize() { - segment_info_.set_duration(FromBMFFTimescale(total_duration_)); + Status status = WriteFrame(true /* write_duration */); + if (!status.ok()) + return status; + + uint64_t duration = + prev_sample_->pts() - first_timestamp_ + prev_sample_->duration(); + segment_info_.set_duration(FromBMFFTimescale(duration)); return DoFinalize(); } Status Segmenter::AddSample(scoped_refptr sample) { if (sample_duration_ == 0) { + first_timestamp_ = sample->pts(); sample_duration_ = sample->duration(); if (muxer_listener_) muxer_listener_->OnSampleDurationReady(sample_duration_); @@ -110,29 +119,45 @@ Status Segmenter::AddSample(scoped_refptr sample) { UpdateProgress(sample->duration()); - // Create a new cluster if needed. + // This writes frames in a delay. Meaning that the previous frame is written + // on this call to AddSample. The current frame is stored until the next + // call. This is done to determine which frame is the last in a Cluster. + // This first block determines if this is a new Cluster and writes the + // previous frame first before creating the new Cluster. + Status status; + bool wrote_frame = false; if (!cluster_) { status = NewSegment(sample->pts()); + // First frame, so no previous frame to write. + wrote_frame = true; } else if (segment_length_sec_ >= options_.segment_duration) { if (sample->is_key_frame() || !options_.segment_sap_aligned) { - status = NewSegment(sample->pts()); + status = WriteFrame(true /* write_duration */); + status.Update(NewSegment(sample->pts())); segment_length_sec_ = 0; cluster_length_sec_ = 0; + wrote_frame = true; } } else if (cluster_length_sec_ >= options_.fragment_duration) { if (sample->is_key_frame() || !options_.fragment_sap_aligned) { - status = NewSubsegment(sample->pts()); + status = WriteFrame(true /* write_duration */); + status.Update(NewSubsegment(sample->pts())); cluster_length_sec_ = 0; + wrote_frame = true; } } + if (!wrote_frame) { + status = WriteFrame(false /* write_duration */); + } if (!status.ok()) return status; // Encrypt the frame. if (encryptor_) { const bool encrypt_frame = - static_cast(total_duration_) / info_->time_scale() >= + static_cast(sample->pts() - first_timestamp_) / + info_->time_scale() >= clear_lead_; status = encryptor_->EncryptFrame(sample, encrypt_frame); if (!status.ok()) { @@ -141,36 +166,16 @@ Status Segmenter::AddSample(scoped_refptr sample) { } } - const int64_t time_ns = - sample->pts() * kSecondsToNs / info_->time_scale(); - bool addframe_result; - if (sample->side_data_size() > 0) { - uint64_t block_add_id; - // First 8 bytes of side_data is the BlockAddID element's value, which is - // done to mimic ffmpeg behavior. See webm_cluster_parser.cc for details. - CHECK_GT(sample->side_data_size(), sizeof(block_add_id)); - memcpy(&block_add_id, sample->side_data(), sizeof(block_add_id)); - addframe_result = cluster_->AddFrameWithAdditional( - sample->data(), sample->data_size(), - sample->side_data() + sizeof(block_add_id), - sample->side_data_size() - sizeof(block_add_id), block_add_id, - track_id_, time_ns, sample->is_key_frame()); - } else { - addframe_result = - cluster_->AddFrame(sample->data(), sample->data_size(), track_id_, - time_ns, sample->is_key_frame()); - } - if (!addframe_result) { - LOG(ERROR) << "Error adding sample to segment."; - return Status(error::FILE_FAILURE, "Error adding sample to segment."); - } + // Add the sample to the durations even though we have not written the frame + // yet. This is needed to make sure we split Clusters at the correct point. + // These are only used in this method. const double duration_sec = static_cast(sample->duration()) / info_->time_scale(); cluster_length_sec_ += duration_sec; segment_length_sec_ += duration_sec; - total_duration_ += sample->duration(); + prev_sample_ = sample; return Status::OK; } @@ -345,6 +350,60 @@ Status Segmenter::InitializeEncryptor(KeySource* key_source, } } +Status Segmenter::WriteFrame(bool write_duration) { + // Create a frame manually so we can create non-SimpleBlock frames. This + // is required to allow the frame duration to be added. If the duration + // is not set, then a SimpleBlock will still be written. + mkvmuxer::Frame frame; + + if (!frame.Init(prev_sample_->data(), prev_sample_->data_size())) { + return Status(error::MUXER_FAILURE, + "Error adding sample to segment: Frame::Init failed"); + } + + if (write_duration) { + const uint64_t duration_ns = + prev_sample_->duration() * kSecondsToNs / info_->time_scale(); + frame.set_duration(duration_ns); + } + frame.set_is_key(prev_sample_->is_key_frame()); + frame.set_timestamp(prev_sample_->pts() * kSecondsToNs / info_->time_scale()); + frame.set_track_number(track_id_); + + if (prev_sample_->side_data_size() > 0) { + uint64_t block_add_id; + // First 8 bytes of side_data is the BlockAddID element's value, which is + // done to mimic ffmpeg behavior. See webm_cluster_parser.cc for details. + CHECK_GT(prev_sample_->side_data_size(), sizeof(block_add_id)); + memcpy(&block_add_id, prev_sample_->side_data(), sizeof(block_add_id)); + if (!frame.AddAdditionalData( + prev_sample_->side_data() + sizeof(block_add_id), + prev_sample_->side_data_size() - sizeof(block_add_id), + block_add_id)) { + return Status( + error::MUXER_FAILURE, + "Error adding sample to segment: Frame::AddAditionalData Failed"); + } + } + + if (!prev_sample_->is_key_frame() && !frame.CanBeSimpleBlock()) { + const int64_t timestamp_ns = + reference_frame_timestamp_ * kSecondsToNs / info_->time_scale(); + frame.set_reference_block_timestamp(timestamp_ns); + } + + if (!cluster_->AddFrame(&frame)) { + return Status(error::MUXER_FAILURE, + "Error adding sample to segment: Cluster::AddFrame failed"); + } + + // A reference frame is needed for non-keyframes. Having a reference to the + // previous block is good enough. + // See libwebm Segment::AddGenericFrame + reference_frame_timestamp_ = prev_sample_->pts(); + return Status::OK; +} + } // namespace webm } // namespace media } // namespace edash_packager diff --git a/packager/media/formats/webm/segmenter.h b/packager/media/formats/webm/segmenter.h index f6e0bfd56d..987e4b900b 100644 --- a/packager/media/formats/webm/segmenter.h +++ b/packager/media/formats/webm/segmenter.h @@ -114,6 +114,9 @@ class Segmenter { Status CreateAudioTrack(AudioStreamInfo* info); Status InitializeEncryptor(KeySource* key_source, uint32_t max_sd_pixels); + // Writes the previous frame to the file. + Status WriteFrame(bool write_duration); + // This is called when there needs to be a new subsegment. This does nothing // in single-segment mode. In multi-segment mode this creates a new Cluster // element. @@ -122,9 +125,12 @@ class Segmenter { // mode, this creates a new Cluster element. In multi-segment mode this // creates a new output file. virtual Status NewSegment(uint64_t start_timescale) = 0; - // This is called when a segment ends. This is called right before a call - // to NewSegment and at the start of Finalize. - Status FinalizeSegment(uint64_t end_timescale); + + // Store the previous sample so we know which one is the last frame. + scoped_refptr prev_sample_; + // The reference frame timestamp; used to populate the ReferenceBlock element + // when writing non-keyframe BlockGroups. + uint64_t reference_frame_timestamp_; const MuxerOptions& options_; scoped_ptr encryptor_; @@ -141,8 +147,8 @@ class Segmenter { ProgressListener* progress_listener_; uint64_t progress_target_; uint64_t accumulated_progress_; - uint64_t total_duration_; - uint64_t sample_duration_; + uint64_t first_timestamp_; + int64_t sample_duration_; // The position (in bytes) of the start of the Segment payload in the init // file. This is also the size of the header before the SeekHead. uint64_t segment_payload_pos_; diff --git a/packager/media/formats/webm/single_segment_segmenter_unittest.cc b/packager/media/formats/webm/single_segment_segmenter_unittest.cc index 104f3695d3..3bcda22cde 100644 --- a/packager/media/formats/webm/single_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/single_segment_segmenter_unittest.cc @@ -33,8 +33,8 @@ const uint8_t kBasicSupportData[] = { 0x42, 0x87, 0x81, 0x02, // DocTypeReadVersion: 2 0x42, 0x85, 0x81, 0x02, - // ID: Segment, Payload Size: 337 - 0x18, 0x53, 0x80, 0x67, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x51, + // ID: Segment, Payload Size: 343 + 0x18, 0x53, 0x80, 0x67, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x57, // ID: SeekHead, Payload Size: 30 0x11, 0x4d, 0x9b, 0x74, 0x9e, // ID: Seek, Payload Size: 12 @@ -48,7 +48,7 @@ const uint8_t kBasicSupportData[] = { // SeekID: binary(4) (Cues) 0x53, 0xab, 0x84, 0x1c, 0x53, 0xbb, 0x6b, // SeekPosition: 367 - 0x53, 0xac, 0x82, 0x01, 0x6f, + 0x53, 0xac, 0x82, 0x01, 0x75, // ID: Void, Payload Size: 52 0xec, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -95,8 +95,8 @@ const uint8_t kBasicSupportData[] = { 0x54, 0xb0, 0x81, 0x64, // DisplayHeight: 100 0x54, 0xba, 0x81, 0x64, - // ID: Cluster, Payload Size: 79 - 0x1f, 0x43, 0xb6, 0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, + // ID: Cluster, Payload Size: 85 + 0x1f, 0x43, 0xb6, 0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, // Timecode: 0 0xe7, 0x81, 0x00, // ID: SimpleBlock, Payload Size: 9 @@ -117,8 +117,12 @@ const uint8_t kBasicSupportData[] = { 0xee, 0x85, 0x9a, 0x78, 0x56, 0x34, 0x12, // ID: BlockAdditional, Payload Size: 5 0xa5, 0x85, 0x73, 0x69, 0x64, 0x65, 0x00, - // ID: SimpleBlock, Payload Size: 9 - 0xa3, 0x89, 0x81, 0x0f, 0xa0, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00, + // ID: BlockGroup, Payload Size: 15 + 0xa0, 0x8f, + // ID: Block, Payload Size: 9 + 0xa1, 0x89, 0x81, 0x0f, 0xa0, 0x00, 0xde, 0xad, 0xbe, 0xef, 0x00, + // BlockDuration: 1000 + 0x9b, 0x82, 0x03, 0xe8, // ID: Cues, Payload Size: 13 0x1c, 0x53, 0xbb, 0x6b, 0x8d, // ID: CuePoint, Payload Size: 11