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
This commit is contained in:
parent
8257eea804
commit
f35410deb1
|
@ -129,14 +129,10 @@ Status WebVttToMp4Handler::ProcessUpToTime(uint64_t cutoff_time) {
|
||||||
// Get the time range for which the current active state is valid.
|
// Get the time range for which the current active state is valid.
|
||||||
const uint64_t previous_change = next_change_;
|
const uint64_t previous_change = next_change_;
|
||||||
next_change_ = actions_.top()->time();
|
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
|
if (next_change_ > previous_change) {
|
||||||
// an empty cue is sent.
|
// Send out the active group. If there is nothing in the active group,
|
||||||
|
// then an empty cue is sent.
|
||||||
Status status =
|
Status status =
|
||||||
active_.size()
|
active_.size()
|
||||||
? MergeAndSendSamples(active_, previous_change, next_change_)
|
? MergeAndSendSamples(active_, previous_change, next_change_)
|
||||||
|
@ -145,6 +141,13 @@ Status WebVttToMp4Handler::ProcessUpToTime(uint64_t cutoff_time) {
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return status;
|
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.
|
// STAGE 2: Move to the next state.
|
||||||
while (actions_.size() && actions_.top()->time() == next_change_) {
|
while (actions_.size() && actions_.top()->time() == next_change_) {
|
||||||
|
@ -180,8 +183,19 @@ Status WebVttToMp4Handler::MergeAndSendSamples(
|
||||||
|
|
||||||
Status WebVttToMp4Handler::SendEmptySample(uint64_t start_time,
|
Status WebVttToMp4Handler::SendEmptySample(uint64_t start_time,
|
||||||
uint64_t end_time) {
|
uint64_t end_time) {
|
||||||
// TODO(vaage): Dispatch an empty vtt sample.
|
DCHECK_GT(end_time, start_time);
|
||||||
return Status::OK;
|
|
||||||
|
box_writer_.Clear();
|
||||||
|
|
||||||
|
mp4::VTTEmptyCueBox box;
|
||||||
|
box.Write(&box_writer_);
|
||||||
|
|
||||||
|
std::shared_ptr<MediaSample> 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() {
|
uint64_t WebVttToMp4Handler::NextActionId() {
|
||||||
|
|
|
@ -63,32 +63,104 @@ class WebVttToMp4HandlerTest : public MediaHandlerTestBase {
|
||||||
std::shared_ptr<TestableWebVttToMp4Handler> mp4_handler_;
|
std::shared_ptr<TestableWebVttToMp4Handler> mp4_handler_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Verify the cues are grouped correctly when the cues do not overlap at all.
|
// Verify that when the stream starts at a non-zero value, the gap at the
|
||||||
//
|
// start will be filled.
|
||||||
// [----A---] [---B---]
|
// | [----A----]
|
||||||
TEST_F(WebVttToMp4HandlerTest, NoOverlap) {
|
TEST_F(WebVttToMp4HandlerTest, NonZeroStartTime) {
|
||||||
const int64_t kStart[] = {0, 1100};
|
const int64_t kGapStart = 0;
|
||||||
const int64_t kEnd[] = {1000, 2100};
|
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;
|
testing::InSequence s;
|
||||||
|
|
||||||
for (size_t i = kA; i <= kB; i++) {
|
// Empty Cue to fill gap
|
||||||
EXPECT_CALL(*mp4_handler_, OnWriteCue(kId[i], kNoSettings, kPayload[i]));
|
|
||||||
EXPECT_CALL(*Output(kOutputIndex),
|
EXPECT_CALL(*Output(kOutputIndex),
|
||||||
OnProcess(IsMediaSample(kStreamIndex, kStart[i],
|
OnProcess(IsMediaSample(kStreamIndex, kGapStart, kGapDuration,
|
||||||
kEnd[i] - kStart[i], !kEncrypted)));
|
!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));
|
EXPECT_CALL(*Output(kOutputIndex), OnFlush(kStreamIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = kA; i <= kB; i++) {
|
|
||||||
ASSERT_OK(Input(kInputIndex)
|
ASSERT_OK(Input(kInputIndex)
|
||||||
->Dispatch(StreamData::FromTextSample(
|
->Dispatch(StreamData::FromTextSample(
|
||||||
kStreamIndex,
|
kStreamIndex, GetTextSample(kSampleId, kSampleStart,
|
||||||
GetTextSample(kId[i], kStart[i], kEnd[i], kPayload[i]))));
|
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());
|
ASSERT_OK(Input(kInputIndex)->FlushAllDownstreams());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue