DASH Media Packaging SDK
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator
segmenter.cc
1 // Copyright 2015 Google Inc. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file or at
5 // https://developers.google.com/open-source/licenses/bsd
6 
7 #include "packager/media/formats/webm/segmenter.h"
8 
9 #include "packager/base/time/time.h"
10 #include "packager/media/base/audio_stream_info.h"
11 #include "packager/media/base/media_sample.h"
12 #include "packager/media/base/media_stream.h"
13 #include "packager/media/base/muxer_options.h"
14 #include "packager/media/base/muxer_util.h"
15 #include "packager/media/base/stream_info.h"
16 #include "packager/media/base/video_stream_info.h"
17 #include "packager/media/event/muxer_listener.h"
18 #include "packager/media/event/progress_listener.h"
19 #include "packager/third_party/libwebm/src/mkvmuxerutil.hpp"
20 #include "packager/third_party/libwebm/src/webmids.hpp"
21 
22 namespace shaka {
23 namespace media {
24 namespace webm {
25 namespace {
26 int64_t kTimecodeScale = 1000000;
27 int64_t kSecondsToNs = 1000000000L;
28 } // namespace
29 
30 Segmenter::Segmenter(const MuxerOptions& options)
31  : reference_frame_timestamp_(0),
32  options_(options),
33  info_(NULL),
34  muxer_listener_(NULL),
35  progress_listener_(NULL),
36  progress_target_(0),
37  accumulated_progress_(0),
38  first_timestamp_(0),
39  sample_duration_(0),
40  segment_payload_pos_(0),
41  cluster_length_sec_(0),
42  segment_length_sec_(0),
43  track_id_(0) {}
44 
45 Segmenter::~Segmenter() {}
46 
47 Status Segmenter::Initialize(scoped_ptr<MkvWriter> writer,
48  StreamInfo* info,
49  ProgressListener* progress_listener,
50  MuxerListener* muxer_listener,
51  KeySource* encryption_key_source,
52  uint32_t max_sd_pixels,
53  double clear_lead_in_seconds) {
54  muxer_listener_ = muxer_listener;
55  info_ = info;
56  clear_lead_ = clear_lead_in_seconds;
57 
58  // Use media duration as progress target.
59  progress_target_ = info_->duration();
60  progress_listener_ = progress_listener;
61 
62  const std::string version_string =
63  "https://github.com/google/shaka-packager version " +
64  options().packager_version_string;
65 
66  segment_info_.Init();
67  segment_info_.set_timecode_scale(kTimecodeScale);
68  segment_info_.set_writing_app(version_string.c_str());
69  if (options().single_segment) {
70  // Set an initial duration so the duration element is written; will be
71  // overwritten at the end. This works because this is a float and floats
72  // are always the same size.
73  segment_info_.set_duration(1);
74  }
75 
76  Status status;
77  if (encryption_key_source) {
78  status = InitializeEncryptor(encryption_key_source, max_sd_pixels);
79  if (!status.ok())
80  return status;
81  }
82 
83  // Create the track info.
84  switch (info_->stream_type()) {
85  case kStreamVideo:
86  status = CreateVideoTrack(static_cast<VideoStreamInfo*>(info_));
87  break;
88  case kStreamAudio:
89  status = CreateAudioTrack(static_cast<AudioStreamInfo*>(info_));
90  break;
91  default:
92  NOTIMPLEMENTED() << "Not implemented for stream type: "
93  << info_->stream_type();
94  status = Status(error::UNIMPLEMENTED, "Not implemented for stream type");
95  }
96  if (!status.ok())
97  return status;
98 
99  return DoInitialize(writer.Pass());
100 }
101 
103  Status status = WriteFrame(true /* write_duration */);
104  if (!status.ok())
105  return status;
106 
107  uint64_t duration =
108  prev_sample_->pts() - first_timestamp_ + prev_sample_->duration();
109  segment_info_.set_duration(FromBMFFTimescale(duration));
110  return DoFinalize();
111 }
112 
113 Status Segmenter::AddSample(scoped_refptr<MediaSample> sample) {
114  if (sample_duration_ == 0) {
115  first_timestamp_ = sample->pts();
116  sample_duration_ = sample->duration();
117  if (muxer_listener_)
118  muxer_listener_->OnSampleDurationReady(sample_duration_);
119  }
120 
121  UpdateProgress(sample->duration());
122 
123  // This writes frames in a delay. Meaning that the previous frame is written
124  // on this call to AddSample. The current frame is stored until the next
125  // call. This is done to determine which frame is the last in a Cluster.
126  // This first block determines if this is a new Cluster and writes the
127  // previous frame first before creating the new Cluster.
128 
129  Status status;
130  bool wrote_frame = false;
131  if (!cluster_) {
132  status = NewSegment(sample->pts());
133  // First frame, so no previous frame to write.
134  wrote_frame = true;
135  } else if (segment_length_sec_ >= options_.segment_duration) {
136  if (sample->is_key_frame() || !options_.segment_sap_aligned) {
137  status = WriteFrame(true /* write_duration */);
138  status.Update(NewSegment(sample->pts()));
139  segment_length_sec_ = 0;
140  cluster_length_sec_ = 0;
141  wrote_frame = true;
142  }
143  } else if (cluster_length_sec_ >= options_.fragment_duration) {
144  if (sample->is_key_frame() || !options_.fragment_sap_aligned) {
145  status = WriteFrame(true /* write_duration */);
146  status.Update(NewSubsegment(sample->pts()));
147  cluster_length_sec_ = 0;
148  wrote_frame = true;
149  }
150  }
151  if (!wrote_frame) {
152  status = WriteFrame(false /* write_duration */);
153  }
154  if (!status.ok())
155  return status;
156 
157  // Encrypt the frame.
158  if (encryptor_) {
159  const bool encrypt_frame =
160  static_cast<double>(sample->pts() - first_timestamp_) /
161  info_->time_scale() >=
162  clear_lead_;
163  status = encryptor_->EncryptFrame(sample, encrypt_frame);
164  if (!status.ok()) {
165  LOG(ERROR) << "Error encrypting frame.";
166  return status;
167  }
168  }
169 
170 
171  // Add the sample to the durations even though we have not written the frame
172  // yet. This is needed to make sure we split Clusters at the correct point.
173  // These are only used in this method.
174  const double duration_sec =
175  static_cast<double>(sample->duration()) / info_->time_scale();
176  cluster_length_sec_ += duration_sec;
177  segment_length_sec_ += duration_sec;
178 
179  prev_sample_ = sample;
180  return Status::OK;
181 }
182 
183 float Segmenter::GetDuration() const {
184  return static_cast<float>(segment_info_.duration()) *
185  segment_info_.timecode_scale() / kSecondsToNs;
186 }
187 
188 uint64_t Segmenter::FromBMFFTimescale(uint64_t time_timescale) {
189  // Convert the time from BMFF time_code to WebM timecode scale.
190  const int64_t time_ns =
191  kSecondsToNs * time_timescale / info_->time_scale();
192  return time_ns / segment_info_.timecode_scale();
193 }
194 
195 uint64_t Segmenter::FromWebMTimecode(uint64_t time_webm_timecode) {
196  // Convert the time to BMFF time_code from WebM timecode scale.
197  const int64_t time_ns = time_webm_timecode * segment_info_.timecode_scale();
198  return time_ns * info_->time_scale() / kSecondsToNs;
199 }
200 
201 Status Segmenter::WriteSegmentHeader(uint64_t file_size, MkvWriter* writer) {
202  Status error_status(error::FILE_FAILURE, "Error writing segment header.");
203 
204  if (!WriteEbmlHeader(writer))
205  return error_status;
206 
207  if (WriteID(writer, mkvmuxer::kMkvSegment) != 0)
208  return error_status;
209 
210  const uint64_t segment_size_size = 8;
211  segment_payload_pos_ = writer->Position() + segment_size_size;
212  if (file_size > 0) {
213  // We want the size of the segment element, so subtract the header.
214  if (WriteUIntSize(writer, file_size - segment_payload_pos_,
215  segment_size_size) != 0)
216  return error_status;
217  if (!seek_head_.Write(writer))
218  return error_status;
219  } else {
220  if (SerializeInt(writer, mkvmuxer::kEbmlUnknownValue, segment_size_size) !=
221  0)
222  return error_status;
223  // We don't know the header size, so write a placeholder.
224  if (!seek_head_.WriteVoid(writer))
225  return error_status;
226  }
227 
228  seek_head_.set_info_pos(writer->Position() - segment_payload_pos_);
229  if (!segment_info_.Write(writer))
230  return error_status;
231 
232  seek_head_.set_tracks_pos(writer->Position() - segment_payload_pos_);
233  if (!tracks_.Write(writer))
234  return error_status;
235 
236  return Status::OK;
237 }
238 
239 Status Segmenter::SetCluster(uint64_t start_webm_timecode,
240  uint64_t position,
241  MkvWriter* writer) {
242  const uint64_t scale = segment_info_.timecode_scale();
243  cluster_.reset(new mkvmuxer::Cluster(start_webm_timecode, position, scale));
244  cluster_->Init(writer);
245  return Status::OK;
246 }
247 
248 void Segmenter::UpdateProgress(uint64_t progress) {
249  accumulated_progress_ += progress;
250  if (!progress_listener_ || progress_target_ == 0)
251  return;
252  // It might happen that accumulated progress exceeds progress_target due to
253  // computation errors, e.g. rounding error. Cap it so it never reports > 100%
254  // progress.
255  if (accumulated_progress_ >= progress_target_) {
256  progress_listener_->OnProgress(1.0);
257  } else {
258  progress_listener_->OnProgress(static_cast<double>(accumulated_progress_) /
259  progress_target_);
260  }
261 }
262 
263 Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) {
264  // The seed is only used to create a UID which we overwrite later.
265  unsigned int seed = 0;
266  mkvmuxer::VideoTrack* track = new mkvmuxer::VideoTrack(&seed);
267  if (!track)
268  return Status(error::INTERNAL_ERROR, "Failed to create video track.");
269 
270  if (info->codec() == kCodecVP8) {
271  track->set_codec_id(mkvmuxer::Tracks::kVp8CodecId);
272  } else if (info->codec() == kCodecVP9) {
273  track->set_codec_id(mkvmuxer::Tracks::kVp9CodecId);
274  } else {
275  LOG(ERROR) << "Only VP8 and VP9 video codec is supported.";
276  return Status(error::UNIMPLEMENTED,
277  "Only VP8 and VP9 video codecs are supported.");
278  }
279 
280  track->set_uid(info->track_id());
281  if (!info->language().empty())
282  track->set_language(info->language().c_str());
283  track->set_type(mkvmuxer::Tracks::kVideo);
284  track->set_width(info->width());
285  track->set_height(info->height());
286  track->set_display_height(info->height());
287  track->set_display_width(info->width() * info->pixel_width() /
288  info->pixel_height());
289 
290  if (encryptor_)
291  encryptor_->AddTrackInfo(track);
292 
293  tracks_.AddTrack(track, info->track_id());
294  track_id_ = track->number();
295  return Status::OK;
296 }
297 
298 Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) {
299  // The seed is only used to create a UID which we overwrite later.
300  unsigned int seed = 0;
301  mkvmuxer::AudioTrack* track = new mkvmuxer::AudioTrack(&seed);
302  if (!track)
303  return Status(error::INTERNAL_ERROR, "Failed to create audio track.");
304 
305  if (info->codec() == kCodecOpus) {
306  track->set_codec_id(mkvmuxer::Tracks::kOpusCodecId);
307  } else if (info->codec() == kCodecVorbis) {
308  track->set_codec_id(mkvmuxer::Tracks::kVorbisCodecId);
309  } else {
310  LOG(ERROR) << "Only Vorbis and Opus audio codec is supported.";
311  return Status(error::UNIMPLEMENTED,
312  "Only Vorbis and Opus audio codecs are supported.");
313  }
314  if (!track->SetCodecPrivate(info->extra_data().data(),
315  info->extra_data().size())) {
316  return Status(error::INTERNAL_ERROR,
317  "Private codec data required for audio streams");
318  }
319 
320  track->set_uid(info->track_id());
321  if (!info->language().empty())
322  track->set_language(info->language().c_str());
323  track->set_type(mkvmuxer::Tracks::kAudio);
324  track->set_sample_rate(info->sampling_frequency());
325  track->set_channels(info->num_channels());
326  track->set_seek_pre_roll(info->seek_preroll_ns());
327  track->set_codec_delay(info->codec_delay_ns());
328 
329  if (encryptor_)
330  encryptor_->AddTrackInfo(track);
331 
332  tracks_.AddTrack(track, info->track_id());
333  track_id_ = track->number();
334  return Status::OK;
335 }
336 
337 Status Segmenter::InitializeEncryptor(KeySource* key_source,
338  uint32_t max_sd_pixels) {
339  encryptor_.reset(new Encryptor());
340  const KeySource::TrackType track_type =
341  GetTrackTypeForEncryption(*info_, max_sd_pixels);
342  if (track_type == KeySource::TrackType::TRACK_TYPE_UNKNOWN)
343  return Status::OK;
344  return encryptor_->Initialize(muxer_listener_, track_type, key_source);
345 }
346 
347 Status Segmenter::WriteFrame(bool write_duration) {
348  // Create a frame manually so we can create non-SimpleBlock frames. This
349  // is required to allow the frame duration to be added. If the duration
350  // is not set, then a SimpleBlock will still be written.
351  mkvmuxer::Frame frame;
352 
353  if (!frame.Init(prev_sample_->data(), prev_sample_->data_size())) {
354  return Status(error::MUXER_FAILURE,
355  "Error adding sample to segment: Frame::Init failed");
356  }
357 
358  if (write_duration) {
359  const uint64_t duration_ns =
360  prev_sample_->duration() * kSecondsToNs / info_->time_scale();
361  frame.set_duration(duration_ns);
362  }
363  frame.set_is_key(prev_sample_->is_key_frame());
364  frame.set_timestamp(prev_sample_->pts() * kSecondsToNs / info_->time_scale());
365  frame.set_track_number(track_id_);
366 
367  if (prev_sample_->side_data_size() > 0) {
368  uint64_t block_add_id;
369  // First 8 bytes of side_data is the BlockAddID element's value, which is
370  // done to mimic ffmpeg behavior. See webm_cluster_parser.cc for details.
371  CHECK_GT(prev_sample_->side_data_size(), sizeof(block_add_id));
372  memcpy(&block_add_id, prev_sample_->side_data(), sizeof(block_add_id));
373  if (!frame.AddAdditionalData(
374  prev_sample_->side_data() + sizeof(block_add_id),
375  prev_sample_->side_data_size() - sizeof(block_add_id),
376  block_add_id)) {
377  return Status(
378  error::MUXER_FAILURE,
379  "Error adding sample to segment: Frame::AddAditionalData Failed");
380  }
381  }
382 
383  if (!prev_sample_->is_key_frame() && !frame.CanBeSimpleBlock()) {
384  const int64_t timestamp_ns =
385  reference_frame_timestamp_ * kSecondsToNs / info_->time_scale();
386  frame.set_reference_block_timestamp(timestamp_ns);
387  }
388 
389  // GetRelativeTimecode will return -1 if the relative timecode is too large
390  // to fit in the frame.
391  if (cluster_->GetRelativeTimecode(frame.timestamp() /
392  cluster_->timecode_scale()) < 0) {
393  const double segment_duration =
394  static_cast<double>(frame.timestamp()) / kSecondsToNs;
395  LOG(ERROR) << "Error adding sample to segment: segment too large, "
396  << segment_duration << " seconds.";
397  return Status(error::MUXER_FAILURE,
398  "Error adding sample to segment: segment too large");
399  }
400 
401  if (!cluster_->AddFrame(&frame)) {
402  return Status(error::MUXER_FAILURE,
403  "Error adding sample to segment: Cluster::AddFrame failed");
404  }
405 
406  // A reference frame is needed for non-keyframes. Having a reference to the
407  // previous block is good enough.
408  // See libwebm Segment::AddGenericFrame
409  reference_frame_timestamp_ = prev_sample_->pts();
410  return Status::OK;
411 }
412 
413 } // namespace webm
414 } // namespace media
415 } // namespace shaka
Abstract class holds stream information.
Definition: stream_info.h:26
std::string packager_version_string
Specify the version string to be embedded in the output files.
Definition: muxer_options.h:82
Status Initialize(const std::vector< MediaStream * > &streams, MuxerListener *muxer_listener, ProgressListener *progress_listener, KeySource *encryption_key_source, uint32_t max_sd_pixels, double clear_lead_in_seconds, double crypto_period_duration_in_seconds, FourCC protection_scheme)
Definition: segmenter.cc:164
void UpdateProgress(uint64_t progress)
Update segmentation progress using ProgressListener.
Definition: segmenter.cc:379
virtual void OnSampleDurationReady(uint32_t sample_duration)=0
Status AddSample(const MediaStream *stream, scoped_refptr< MediaSample > sample)
Definition: segmenter.cc:312
This class listens to progress updates events.
mkvmuxer::int64 Position() const override
Definition: mkv_writer.cc:71
An implementation of IMkvWriter using our File type.
Definition: mkv_writer.h:21
virtual void OnProgress(double progress)=0
KeySource is responsible for encryption key acquisition.
Definition: key_source.h:31
double GetDuration() const
Definition: segmenter.cc:370
Holds video stream information.