7 #include "packager/media/chunking/cue_alignment_handler.h"
11 #include "packager/status_macros.h"
19 const size_t kMaxBufferSize = 1000;
21 int64_t GetScaledTime(
const StreamInfo& info,
const StreamData& data) {
22 DCHECK(data.text_sample || data.media_sample);
24 if (data.text_sample) {
25 return data.text_sample->start_time();
28 if (info.stream_type() == kStreamText) {
33 <<
"A text streams should use text samples, not media samples.";
36 if (info.stream_type() == kStreamAudio) {
40 return data.media_sample->pts() + data.media_sample->duration() / 2;
43 DCHECK_EQ(info.stream_type(), kStreamVideo);
44 return data.media_sample->pts();
47 double TimeInSeconds(
const StreamInfo& info,
const StreamData& data) {
48 const int64_t scaled_time = GetScaledTime(info, data);
49 const uint32_t time_scale = info.time_scale();
51 return static_cast<double>(scaled_time) / time_scale;
54 double TextEndTimeInSeconds(
const StreamInfo& info,
const StreamData& data) {
55 DCHECK(data.text_sample);
57 const int64_t scaled_time = data.text_sample->EndTime();
58 const uint32_t time_scale = info.time_scale();
60 return static_cast<double>(scaled_time) / time_scale;
63 Status GetNextCue(
double hint,
64 SyncPointQueue* sync_points,
65 std::shared_ptr<const CueEvent>* out_cue) {
69 *out_cue = sync_points->GetNext(hint);
72 return *out_cue ? Status::OK
73 : Status(error::CANCELLED,
"SyncPointQueue is cancelled.");
77 CueAlignmentHandler::CueAlignmentHandler(SyncPointQueue* sync_points)
78 : sync_points_(sync_points) {}
80 Status CueAlignmentHandler::InitializeInternal() {
81 sync_points_->AddThread();
82 stream_states_.resize(num_input_streams());
86 hint_ = sync_points_->GetHint(-1);
91 Status CueAlignmentHandler::Process(std::unique_ptr<StreamData> data) {
92 switch (data->stream_data_type) {
93 case StreamDataType::kStreamInfo:
94 return OnStreamInfo(std::move(data));
95 case StreamDataType::kTextSample:
96 case StreamDataType::kMediaSample:
97 return OnSample(std::move(data));
99 VLOG(3) <<
"Dropping unsupported data type "
100 <<
static_cast<int>(data->stream_data_type);
105 Status CueAlignmentHandler::OnFlushRequest(
size_t stream_index) {
106 stream_states_[stream_index].to_be_flushed =
true;
111 for (
const StreamState& stream_state : stream_states_) {
112 if (!stream_state.to_be_flushed) {
121 for (StreamState& stream : stream_states_) {
122 DCHECK(stream.to_be_flushed);
124 if (stream.info->stream_type() == kStreamVideo) {
125 DCHECK_EQ(stream.samples.size(), 0u)
126 <<
"Video streams should not store samples";
127 DCHECK_EQ(stream.cues.size(), 0u)
128 <<
"Video streams should not store cues";
134 while (sync_points_->HasMore(hint_)) {
135 std::shared_ptr<const CueEvent> next_cue;
136 RETURN_IF_ERROR(GetNextCue(hint_, sync_points_, &next_cue));
137 RETURN_IF_ERROR(UseNewSyncPoint(std::move(next_cue)));
142 for (StreamState& stream : stream_states_) {
143 RETURN_IF_ERROR(RunThroughSamples(&stream));
144 DCHECK_EQ(stream.samples.size(), 0u);
151 for (
auto& cue : stream.cues) {
153 if (cue->cue_event->time_in_seconds <
154 stream.max_text_sample_end_time_seconds) {
155 RETURN_IF_ERROR(Dispatch(std::move(cue)));
157 VLOG(1) <<
"Ignore extra cue in stream " << cue->stream_index
158 <<
" with time " << cue->cue_event->time_in_seconds
165 return FlushAllDownstreams();
168 Status CueAlignmentHandler::OnStreamInfo(std::unique_ptr<StreamData> data) {
169 StreamState& stream_state = stream_states_[data->stream_index];
172 stream_state.info = data->stream_info;
174 return Dispatch(std::move(data));
177 Status CueAlignmentHandler::OnVideoSample(std::unique_ptr<StreamData> sample) {
179 DCHECK(sample->media_sample);
181 const size_t stream_index = sample->stream_index;
182 StreamState& stream = stream_states_[stream_index];
184 const double sample_time = TimeInSeconds(*stream.info, *sample);
185 const bool is_key_frame = sample->media_sample->is_key_frame();
187 if (is_key_frame && sample_time >= hint_) {
188 auto next_sync = sync_points_->PromoteAt(sample_time);
191 LOG(ERROR) <<
"Failed to promote sync point at " << sample_time
192 <<
". This happens only if video streams are not GOP-aligned.";
193 return Status(error::INVALID_ARGUMENT,
194 "Streams are not properly GOP-aligned.");
197 RETURN_IF_ERROR(UseNewSyncPoint(std::move(next_sync)));
198 DCHECK_EQ(stream.cues.size(), 1u);
199 RETURN_IF_ERROR(Dispatch(std::move(stream.cues.front())));
200 stream.cues.pop_front();
203 return Dispatch(std::move(sample));
206 Status CueAlignmentHandler::OnNonVideoSample(
207 std::unique_ptr<StreamData> sample) {
209 DCHECK(sample->media_sample || sample->text_sample);
211 const size_t stream_index = sample->stream_index;
212 StreamState& stream_state = stream_states_[stream_index];
216 RETURN_IF_ERROR(AcceptSample(std::move(sample), &stream_state));
222 if (EveryoneWaitingAtHint()) {
223 std::shared_ptr<const CueEvent> next_sync;
224 RETURN_IF_ERROR(GetNextCue(hint_, sync_points_, &next_sync));
225 RETURN_IF_ERROR(UseNewSyncPoint(next_sync));
231 Status CueAlignmentHandler::OnSample(std::unique_ptr<StreamData> sample) {
242 const size_t stream_index = sample->stream_index;
244 if (sample->text_sample) {
245 StreamState& stream = stream_states_[stream_index];
246 stream.max_text_sample_end_time_seconds =
247 std::max(stream.max_text_sample_end_time_seconds,
248 TextEndTimeInSeconds(*stream.info, *sample));
251 const StreamType stream_type =
252 stream_states_[stream_index].info->stream_type();
253 const bool is_video = stream_type == kStreamVideo;
255 return is_video ? OnVideoSample(std::move(sample))
256 : OnNonVideoSample(std::move(sample));
259 Status CueAlignmentHandler::UseNewSyncPoint(
260 std::shared_ptr<const CueEvent> new_sync) {
261 hint_ = sync_points_->GetHint(new_sync->time_in_seconds);
262 DCHECK_GT(hint_, new_sync->time_in_seconds);
264 for (
size_t stream_index = 0; stream_index < stream_states_.size();
266 StreamState& stream = stream_states_[stream_index];
267 stream.cues.push_back(StreamData::FromCueEvent(stream_index, new_sync));
269 RETURN_IF_ERROR(RunThroughSamples(&stream));
275 bool CueAlignmentHandler::EveryoneWaitingAtHint()
const {
276 for (
const StreamState& stream_state : stream_states_) {
277 if (stream_state.samples.empty()) {
284 Status CueAlignmentHandler::AcceptSample(std::unique_ptr<StreamData> sample,
285 StreamState* stream) {
287 DCHECK(sample->media_sample || sample->text_sample);
292 const size_t stream_index = sample->stream_index;
294 stream->samples.push_back(std::move(sample));
296 if (stream->samples.size() > kMaxBufferSize) {
297 LOG(ERROR) <<
"Stream " << stream_index <<
" has buffered "
298 << stream->samples.size() <<
" when the max is "
300 return Status(error::INVALID_ARGUMENT,
301 "Streams are not properly multiplexed.");
304 return RunThroughSamples(stream);
307 Status CueAlignmentHandler::RunThroughSamples(StreamState* stream) {
310 while (stream->cues.size() && stream->samples.size()) {
311 const double cue_time = stream->cues.front()->cue_event->time_in_seconds;
312 const double sample_time =
313 TimeInSeconds(*stream->info, *stream->samples.front());
315 if (sample_time < cue_time) {
316 RETURN_IF_ERROR(Dispatch(std::move(stream->samples.front())));
317 stream->samples.pop_front();
319 RETURN_IF_ERROR(Dispatch(std::move(stream->cues.front())));
320 stream->cues.pop_front();
327 while (stream->samples.size() &&
328 TimeInSeconds(*stream->info, *stream->samples.front()) < hint_) {
329 RETURN_IF_ERROR(Dispatch(std::move(stream->samples.front())));
330 stream->samples.pop_front();
All the methods that are virtual are virtual for mocking.