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