From f35410deb12638141a1ede5f5f845851ab47d9fd Mon Sep 17 00:00:00 2001 From: Aaron Vaage Date: Mon, 5 Feb 2018 13:08:20 -0800 Subject: [PATCH] Output Empty MP4 WebVtt Cues When a gap is found in the text stream, the WebVtt to Mp4 converter will now output the special empty vtt cue. Change-Id: I8be88c6b7589aa120a2215e1e4b8e98031fe326d Closes: #324 --- .../formats/webvtt/webvtt_to_mp4_handler.cc | 44 +++++--- .../webvtt/webvtt_to_mp4_handler_unittest.cc | 106 +++++++++++++++--- 2 files changed, 118 insertions(+), 32 deletions(-) diff --git a/packager/media/formats/webvtt/webvtt_to_mp4_handler.cc b/packager/media/formats/webvtt/webvtt_to_mp4_handler.cc index 0125879a4d..3d0297bcad 100644 --- a/packager/media/formats/webvtt/webvtt_to_mp4_handler.cc +++ b/packager/media/formats/webvtt/webvtt_to_mp4_handler.cc @@ -129,21 +129,24 @@ Status WebVttToMp4Handler::ProcessUpToTime(uint64_t cutoff_time) { // Get the time range for which the current active state is valid. const uint64_t previous_change = next_change_; next_change_ = actions_.top()->time(); - // The only time that |previous_change| and |next_change_| should ever break - // the rule |next_change_ > previous_change| is at the start where - // |previous_change| and |next_change_| are both zero. - DCHECK((previous_change == 0 && next_change_ == 0) || - next_change_ > previous_change); - // Send out the active group. If there is nothing in the active group, then - // an empty cue is sent. - Status status = - active_.size() - ? MergeAndSendSamples(active_, previous_change, next_change_) - : SendEmptySample(previous_change, next_change_); + if (next_change_ > previous_change) { + // Send out the active group. If there is nothing in the active group, + // then an empty cue is sent. + Status status = + active_.size() + ? MergeAndSendSamples(active_, previous_change, next_change_) + : SendEmptySample(previous_change, next_change_); - if (!status.ok()) { - return status; + if (!status.ok()) { + return status; + } + } else { + // The only time that |previous_change| and |next_change_| should ever + // break the rule |next_change_ > previous_change| is at the start where + // |previous_change| and |next_change_| are both zero. + DCHECK_EQ(previous_change, 0u); + DCHECK_EQ(next_change_, 0u); } // STAGE 2: Move to the next state. @@ -180,8 +183,19 @@ Status WebVttToMp4Handler::MergeAndSendSamples( Status WebVttToMp4Handler::SendEmptySample(uint64_t start_time, uint64_t end_time) { - // TODO(vaage): Dispatch an empty vtt sample. - return Status::OK; + DCHECK_GT(end_time, start_time); + + box_writer_.Clear(); + + mp4::VTTEmptyCueBox box; + box.Write(&box_writer_); + + std::shared_ptr sample = + MediaSample::CopyFrom(box_writer_.Buffer(), box_writer_.Size(), true); + sample->set_pts(start_time); + sample->set_dts(start_time); + sample->set_duration(end_time - start_time); + return DispatchMediaSample(kTrackId, std::move(sample)); } uint64_t WebVttToMp4Handler::NextActionId() { diff --git a/packager/media/formats/webvtt/webvtt_to_mp4_handler_unittest.cc b/packager/media/formats/webvtt/webvtt_to_mp4_handler_unittest.cc index 9b89fde285..3ad8b2b8c8 100644 --- a/packager/media/formats/webvtt/webvtt_to_mp4_handler_unittest.cc +++ b/packager/media/formats/webvtt/webvtt_to_mp4_handler_unittest.cc @@ -63,32 +63,104 @@ class WebVttToMp4HandlerTest : public MediaHandlerTestBase { std::shared_ptr mp4_handler_; }; -// Verify the cues are grouped correctly when the cues do not overlap at all. -// -// [----A---] [---B---] -TEST_F(WebVttToMp4HandlerTest, NoOverlap) { - const int64_t kStart[] = {0, 1100}; - const int64_t kEnd[] = {1000, 2100}; +// Verify that when the stream starts at a non-zero value, the gap at the +// start will be filled. +// | [----A----] +TEST_F(WebVttToMp4HandlerTest, NonZeroStartTime) { + const int64_t kGapStart = 0; + const int64_t kGapEnd = 100; + const int64_t kGapDuration = kGapEnd - kGapStart; + + const char* kSampleId = kId[0]; + const char* kSamplePayload = kPayload[0]; + const int64_t kSampleStart = kGapEnd; + const int64_t kSampleDuration = 500; + const int64_t kSampleEnd = kSampleStart + kSampleDuration; { testing::InSequence s; - for (size_t i = kA; i <= kB; i++) { - EXPECT_CALL(*mp4_handler_, OnWriteCue(kId[i], kNoSettings, kPayload[i])); - EXPECT_CALL(*Output(kOutputIndex), - OnProcess(IsMediaSample(kStreamIndex, kStart[i], - kEnd[i] - kStart[i], !kEncrypted))); - } + // Empty Cue to fill gap + EXPECT_CALL(*Output(kOutputIndex), + OnProcess(IsMediaSample(kStreamIndex, kGapStart, kGapDuration, + !kEncrypted))); + + // Sample + EXPECT_CALL(*mp4_handler_, + OnWriteCue(kSampleId, kNoSettings, kSamplePayload)); + EXPECT_CALL(*Output(kOutputIndex), + OnProcess(IsMediaSample(kStreamIndex, kSampleStart, + kSampleDuration, !kEncrypted))); EXPECT_CALL(*Output(kOutputIndex), OnFlush(kStreamIndex)); } - for (size_t i = kA; i <= kB; i++) { - ASSERT_OK(Input(kInputIndex) - ->Dispatch(StreamData::FromTextSample( - kStreamIndex, - GetTextSample(kId[i], kStart[i], kEnd[i], kPayload[i])))); + ASSERT_OK(Input(kInputIndex) + ->Dispatch(StreamData::FromTextSample( + kStreamIndex, GetTextSample(kSampleId, kSampleStart, + kSampleEnd, kSamplePayload)))); + + ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams()); +} + +// Verify the cues are grouped correctly when the cues do not overlap at all. +// An empty cue should be inserted between the two as there is a gap. +// +// [----A---] [---B---] +TEST_F(WebVttToMp4HandlerTest, NoOverlap) { + const int64_t kDuration = 1000; + + const char* kSample1Id = kId[0]; + const char* kSample1Payload = kPayload[0]; + const int64_t kSample1Start = 0; + const int64_t kSample1End = kSample1Start + kDuration; + + // Make sample 2 be just a little after sample 1. + const char* kSample2Id = kId[1]; + const char* kSample2Payload = kPayload[1]; + const int64_t kSample2Start = kSample1End + 100; + const int64_t kSample2End = kSample2Start + kDuration; + + const int64_t kGapStart = kSample1End; + const int64_t kGapDuration = kSample2Start - kSample1End; + + { + testing::InSequence s; + + // Sample 1 + EXPECT_CALL(*mp4_handler_, + OnWriteCue(kSample1Id, kNoSettings, kSample1Payload)); + EXPECT_CALL(*Output(kOutputIndex), + OnProcess(IsMediaSample(kStreamIndex, kSample1Start, kDuration, + !kEncrypted))); + + // Empty Cue to fill gap + EXPECT_CALL(*Output(kOutputIndex), + OnProcess(IsMediaSample(kStreamIndex, kGapStart, kGapDuration, + !kEncrypted))); + + // Sample 2 + EXPECT_CALL(*mp4_handler_, + OnWriteCue(kSample2Id, kNoSettings, kSample2Payload)); + EXPECT_CALL(*Output(kOutputIndex), + OnProcess(IsMediaSample(kStreamIndex, kSample2Start, kDuration, + !kEncrypted))); + + EXPECT_CALL(*Output(kOutputIndex), OnFlush(kStreamIndex)); } + + ASSERT_OK( + Input(kInputIndex) + ->Dispatch(StreamData::FromTextSample( + kStreamIndex, GetTextSample(kSample1Id, kSample1Start, + kSample1End, kSample1Payload)))); + + ASSERT_OK( + Input(kInputIndex) + ->Dispatch(StreamData::FromTextSample( + kStreamIndex, GetTextSample(kSample2Id, kSample2Start, + kSample2End, kSample2Payload)))); + ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams()); }