Ignore extra Ad cues at the end of the stream

They will result in empty DASH representations, which is not spec
compliant.

For text, if the cue is before the max end time, it will still be
dispatched as the text samples intercepted by the cue can be split into
two at the cue point.

Bug: 111359775.

Change-Id: I55c2025c4e9d64c88e6a685c0cf3024a0cc4a6d8
This commit is contained in:
KongQun Yang 2018-07-20 10:31:26 -07:00
parent 86a86ce954
commit 9a26ec1e83
3 changed files with 48 additions and 11 deletions

View File

@ -6,6 +6,8 @@
#include "packager/media/chunking/cue_alignment_handler.h" #include "packager/media/chunking/cue_alignment_handler.h"
#include <algorithm>
#include "packager/status_macros.h" #include "packager/status_macros.h"
namespace shaka { namespace shaka {
@ -49,6 +51,15 @@ double TimeInSeconds(const StreamInfo& info, const StreamData& data) {
return static_cast<double>(scaled_time) / time_scale; return static_cast<double>(scaled_time) / time_scale;
} }
double TextEndTimeInSeconds(const StreamInfo& info, const StreamData& data) {
DCHECK(data.text_sample);
const int64_t scaled_time = data.text_sample->EndTime();
const uint32_t time_scale = info.time_scale();
return static_cast<double>(scaled_time) / time_scale;
}
Status GetNextCue(double hint, Status GetNextCue(double hint,
SyncPointQueue* sync_points, SyncPointQueue* sync_points,
std::shared_ptr<const CueEvent>* out_cue) { std::shared_ptr<const CueEvent>* out_cue) {
@ -132,13 +143,24 @@ Status CueAlignmentHandler::OnFlushRequest(size_t stream_index) {
RETURN_IF_ERROR(RunThroughSamples(&stream)); RETURN_IF_ERROR(RunThroughSamples(&stream));
DCHECK_EQ(stream.samples.size(), 0u); DCHECK_EQ(stream.samples.size(), 0u);
// It is possible for there to be cues that come after all the samples. Make // Ignore extra cues at the end, except for text, as they will result in
// sure to send them out too. // empty DASH Representations, which is not spec compliant.
while (stream.cues.size()) { // For text, if the cue is before the max end time, it will still be
RETURN_IF_ERROR(Dispatch(std::move(stream.cues.front()))); // dispatched as the text samples intercepted by the cue can be split into
stream.cues.pop_front(); // two at the cue point.
for (auto& cue : stream.cues) {
// |max_text_sample_end_time_seconds| is always 0 for non-text samples.
if (cue->cue_event->time_in_seconds <
stream.max_text_sample_end_time_seconds) {
RETURN_IF_ERROR(Dispatch(std::move(cue)));
} else {
VLOG(1) << "Ignore extra cue in stream " << cue->stream_index
<< " with time " << cue->cue_event->time_in_seconds
<< "s in the end.";
} }
} }
stream.cues.clear();
}
return FlushAllDownstreams(); return FlushAllDownstreams();
} }
@ -218,9 +240,16 @@ Status CueAlignmentHandler::OnSample(std::unique_ptr<StreamData> sample) {
// us until there is a sync point. // us until there is a sync point.
const size_t stream_index = sample->stream_index; const size_t stream_index = sample->stream_index;
if (sample->text_sample) {
StreamState& stream = stream_states_[stream_index];
stream.max_text_sample_end_time_seconds =
std::max(stream.max_text_sample_end_time_seconds,
TextEndTimeInSeconds(*stream.info, *sample));
}
const StreamType stream_type = const StreamType stream_type =
stream_states_[stream_index].info->stream_type(); stream_states_[stream_index].info->stream_type();
const bool is_video = stream_type == kStreamVideo; const bool is_video = stream_type == kStreamVideo;
return is_video ? OnVideoSample(std::move(sample)) return is_video ? OnVideoSample(std::move(sample))

View File

@ -39,6 +39,8 @@ class CueAlignmentHandler : public MediaHandler {
std::list<std::unique_ptr<StreamData>> samples; std::list<std::unique_ptr<StreamData>> samples;
// If set, the stream is pending to be flushed. // If set, the stream is pending to be flushed.
bool to_be_flushed = false; bool to_be_flushed = false;
// Only set for text stream.
double max_text_sample_end_time_seconds = 0;
// A list of cues that the stream should inject between media samples. When // A list of cues that the stream should inject between media samples. When
// there are no cues, the stream should run up to the hint. // there are no cues, the stream should run up to the hint.

View File

@ -474,11 +474,15 @@ TEST_F(CueAlignmentHandlerTest, TextInputWithCueAfterLastStart) {
const int64_t kSample2Start = kSample1End; const int64_t kSample2Start = kSample1End;
const int64_t kSample2End = kSample2Start + kSampleDuration; const int64_t kSample2End = kSample2Start + kSampleDuration;
// Put the cue between the start and end of the last sample. // Put the cue 1 between the start and end of the last sample.
const double kCueTimeInSeconds = const double kCue1TimeInSeconds =
static_cast<double>(kSample2Start + kSample2End) / kMsTimeScale; static_cast<double>(kSample2Start + kSample2End) / 2 / kMsTimeScale;
auto sync_points = CreateSyncPoints({kCueTimeInSeconds}); // Put the cue 2 after the end of the last sample.
const double kCue2TimeInSeconds =
static_cast<double>(kSample2End) / kMsTimeScale;
auto sync_points = CreateSyncPoints({kCue1TimeInSeconds, kCue2TimeInSeconds});
auto handler = std::make_shared<CueAlignmentHandler>(sync_points.get()); auto handler = std::make_shared<CueAlignmentHandler>(sync_points.get());
ASSERT_OK(SetUpAndInitializeGraph(handler, kOneInput, kOneOutput)); ASSERT_OK(SetUpAndInitializeGraph(handler, kOneInput, kOneOutput));
@ -496,8 +500,10 @@ TEST_F(CueAlignmentHandlerTest, TextInputWithCueAfterLastStart) {
EXPECT_CALL( EXPECT_CALL(
*Output(kTextStream), *Output(kTextStream),
OnProcess(IsTextSample(_, _, kSample2Start, kSample2End, _, _))); OnProcess(IsTextSample(_, _, kSample2Start, kSample2End, _, _)));
// Cue before the sample end is processed.
EXPECT_CALL(*Output(kTextStream), EXPECT_CALL(*Output(kTextStream),
OnProcess(IsCueEvent(_, kCueTimeInSeconds))); OnProcess(IsCueEvent(_, kCue1TimeInSeconds)));
// Cue after the samples is ignored.
EXPECT_CALL(*Output(kTextStream), OnFlush(_)); EXPECT_CALL(*Output(kTextStream), OnFlush(_));
} }