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