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