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  if (!cluster_) {
139  status = NewSegment(sample->pts());
140  // First frame, so no previous frame to write.
141  wrote_frame = true;
142  } else if (segment_length_sec_ >= options_.segment_duration) {
143  if (sample->is_key_frame() || !options_.segment_sap_aligned) {
144  status = WriteFrame(true /* write_duration */);
145  status.Update(NewSegment(sample->pts()));
146  segment_length_sec_ = 0;
147  cluster_length_sec_ = 0;
148  wrote_frame = true;
149 
150  if (encryptor_ && !enable_encryption_) {
151  if (sample->pts() - first_timestamp_ >=
152  clear_lead_ * info_->time_scale()) {
153  enable_encryption_ = true;
154  if (muxer_listener_)
155  muxer_listener_->OnEncryptionStart();
156  }
157  }
158  }
159  } else if (cluster_length_sec_ >= options_.fragment_duration) {
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_sec_ = 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  status = encryptor_->EncryptFrame(sample, enable_encryption_);
176  if (!status.ok()) {
177  LOG(ERROR) << "Error encrypting frame.";
178  return status;
179  }
180  }
181 
182  // Add the sample to the durations even though we have not written the frame
183  // yet. This is needed to make sure we split Clusters at the correct point.
184  // These are only used in this method.
185  const double duration_sec =
186  static_cast<double>(sample->duration()) / info_->time_scale();
187  cluster_length_sec_ += duration_sec;
188  segment_length_sec_ += duration_sec;
189 
190  prev_sample_ = sample;
191  return Status::OK;
192 }
193 
194 float Segmenter::GetDuration() const {
195  return static_cast<float>(segment_info_.duration()) *
196  segment_info_.timecode_scale() / kSecondsToNs;
197 }
198 
199 uint64_t Segmenter::FromBMFFTimescale(uint64_t time_timescale) {
200  // Convert the time from BMFF time_code to WebM timecode scale.
201  const int64_t time_ns =
202  kSecondsToNs * time_timescale / info_->time_scale();
203  return time_ns / segment_info_.timecode_scale();
204 }
205 
206 uint64_t Segmenter::FromWebMTimecode(uint64_t time_webm_timecode) {
207  // Convert the time to BMFF time_code from WebM timecode scale.
208  const int64_t time_ns = time_webm_timecode * segment_info_.timecode_scale();
209  return time_ns * info_->time_scale() / kSecondsToNs;
210 }
211 
212 Status Segmenter::WriteSegmentHeader(uint64_t file_size, MkvWriter* writer) {
213  Status error_status(error::FILE_FAILURE, "Error writing segment header.");
214 
215  if (!WriteEbmlHeader(writer))
216  return error_status;
217 
218  if (WriteID(writer, mkvmuxer::kMkvSegment) != 0)
219  return error_status;
220 
221  const uint64_t segment_size_size = 8;
222  segment_payload_pos_ = writer->Position() + segment_size_size;
223  if (file_size > 0) {
224  // We want the size of the segment element, so subtract the header.
225  if (WriteUIntSize(writer, file_size - segment_payload_pos_,
226  segment_size_size) != 0)
227  return error_status;
228  if (!seek_head_.Write(writer))
229  return error_status;
230  } else {
231  if (SerializeInt(writer, mkvmuxer::kEbmlUnknownValue, segment_size_size) !=
232  0)
233  return error_status;
234  // We don't know the header size, so write a placeholder.
235  if (!seek_head_.WriteVoid(writer))
236  return error_status;
237  }
238 
239  seek_head_.set_info_pos(writer->Position() - segment_payload_pos_);
240  if (!segment_info_.Write(writer))
241  return error_status;
242 
243  seek_head_.set_tracks_pos(writer->Position() - segment_payload_pos_);
244  if (!tracks_.Write(writer))
245  return error_status;
246 
247  return Status::OK;
248 }
249 
250 Status Segmenter::SetCluster(uint64_t start_webm_timecode,
251  uint64_t position,
252  MkvWriter* writer) {
253  const uint64_t scale = segment_info_.timecode_scale();
254  cluster_.reset(new mkvmuxer::Cluster(start_webm_timecode, position, scale));
255  cluster_->Init(writer);
256  return Status::OK;
257 }
258 
259 void Segmenter::UpdateProgress(uint64_t progress) {
260  accumulated_progress_ += progress;
261  if (!progress_listener_ || progress_target_ == 0)
262  return;
263  // It might happen that accumulated progress exceeds progress_target due to
264  // computation errors, e.g. rounding error. Cap it so it never reports > 100%
265  // progress.
266  if (accumulated_progress_ >= progress_target_) {
267  progress_listener_->OnProgress(1.0);
268  } else {
269  progress_listener_->OnProgress(static_cast<double>(accumulated_progress_) /
270  progress_target_);
271  }
272 }
273 
274 Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) {
275  // The seed is only used to create a UID which we overwrite later.
276  unsigned int seed = 0;
277  mkvmuxer::VideoTrack* track = new mkvmuxer::VideoTrack(&seed);
278  if (!track)
279  return Status(error::INTERNAL_ERROR, "Failed to create video track.");
280 
281  if (info->codec() == kCodecVP8) {
282  track->set_codec_id(mkvmuxer::Tracks::kVp8CodecId);
283  } else if (info->codec() == kCodecVP9) {
284  track->set_codec_id(mkvmuxer::Tracks::kVp9CodecId);
285 
286  // The |StreamInfo::codec_config| field is stored using the MP4 format; we
287  // need to convert it to the WebM format.
288  VPCodecConfigurationRecord vp_config;
289  if (!vp_config.ParseMP4(info->codec_config())) {
290  return Status(error::INTERNAL_ERROR,
291  "Unable to parse VP9 codec configuration");
292  }
293 
294  std::vector<uint8_t> codec_config;
295  vp_config.WriteWebM(&codec_config);
296  if (!track->SetCodecPrivate(codec_config.data(), codec_config.size())) {
297  return Status(error::INTERNAL_ERROR,
298  "Private codec data required for VP9 streams");
299  }
300  } else {
301  LOG(ERROR) << "Only VP8 and VP9 video codecs are supported.";
302  return Status(error::UNIMPLEMENTED,
303  "Only VP8 and VP9 video codecs are supported.");
304  }
305 
306  track->set_uid(info->track_id());
307  if (!info->language().empty())
308  track->set_language(info->language().c_str());
309  track->set_type(mkvmuxer::Tracks::kVideo);
310  track->set_width(info->width());
311  track->set_height(info->height());
312  track->set_display_height(info->height());
313  track->set_display_width(info->width() * info->pixel_width() /
314  info->pixel_height());
315 
316  if (encryptor_)
317  encryptor_->AddTrackInfo(track);
318 
319  tracks_.AddTrack(track, info->track_id());
320  track_id_ = track->number();
321  return Status::OK;
322 }
323 
324 Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) {
325  // The seed is only used to create a UID which we overwrite later.
326  unsigned int seed = 0;
327  mkvmuxer::AudioTrack* track = new mkvmuxer::AudioTrack(&seed);
328  if (!track)
329  return Status(error::INTERNAL_ERROR, "Failed to create audio track.");
330 
331  if (info->codec() == kCodecOpus) {
332  track->set_codec_id(mkvmuxer::Tracks::kOpusCodecId);
333  } else if (info->codec() == kCodecVorbis) {
334  track->set_codec_id(mkvmuxer::Tracks::kVorbisCodecId);
335  } else {
336  LOG(ERROR) << "Only Vorbis and Opus audio codec is supported.";
337  return Status(error::UNIMPLEMENTED,
338  "Only Vorbis and Opus audio codecs are supported.");
339  }
340  if (!track->SetCodecPrivate(info->codec_config().data(),
341  info->codec_config().size())) {
342  return Status(error::INTERNAL_ERROR,
343  "Private codec data required for audio streams");
344  }
345 
346  track->set_uid(info->track_id());
347  if (!info->language().empty())
348  track->set_language(info->language().c_str());
349  track->set_type(mkvmuxer::Tracks::kAudio);
350  track->set_sample_rate(info->sampling_frequency());
351  track->set_channels(info->num_channels());
352  track->set_seek_pre_roll(info->seek_preroll_ns());
353  track->set_codec_delay(info->codec_delay_ns());
354 
355  if (encryptor_)
356  encryptor_->AddTrackInfo(track);
357 
358  tracks_.AddTrack(track, info->track_id());
359  track_id_ = track->number();
360  return Status::OK;
361 }
362 
363 Status Segmenter::InitializeEncryptor(KeySource* key_source,
364  uint32_t max_sd_pixels) {
365  encryptor_.reset(new Encryptor());
366  const KeySource::TrackType track_type =
367  GetTrackTypeForEncryption(*info_, max_sd_pixels);
368  if (track_type == KeySource::TrackType::TRACK_TYPE_UNKNOWN)
369  return Status::OK;
370  return encryptor_->Initialize(muxer_listener_, track_type, info_->codec(),
371  key_source, options_.webm_subsample_encryption);
372 }
373 
374 Status Segmenter::WriteFrame(bool write_duration) {
375  // Create a frame manually so we can create non-SimpleBlock frames. This
376  // is required to allow the frame duration to be added. If the duration
377  // is not set, then a SimpleBlock will still be written.
378  mkvmuxer::Frame frame;
379 
380  if (!frame.Init(prev_sample_->data(), prev_sample_->data_size())) {
381  return Status(error::MUXER_FAILURE,
382  "Error adding sample to segment: Frame::Init failed");
383  }
384 
385  if (write_duration) {
386  const uint64_t duration_ns =
387  prev_sample_->duration() * kSecondsToNs / info_->time_scale();
388  frame.set_duration(duration_ns);
389  }
390  frame.set_is_key(prev_sample_->is_key_frame());
391  frame.set_timestamp(prev_sample_->pts() * kSecondsToNs / info_->time_scale());
392  frame.set_track_number(track_id_);
393 
394  if (prev_sample_->side_data_size() > 0) {
395  uint64_t block_add_id;
396  // First 8 bytes of side_data is the BlockAddID element's value, which is
397  // done to mimic ffmpeg behavior. See webm_cluster_parser.cc for details.
398  CHECK_GT(prev_sample_->side_data_size(), sizeof(block_add_id));
399  memcpy(&block_add_id, prev_sample_->side_data(), sizeof(block_add_id));
400  if (!frame.AddAdditionalData(
401  prev_sample_->side_data() + sizeof(block_add_id),
402  prev_sample_->side_data_size() - sizeof(block_add_id),
403  block_add_id)) {
404  return Status(
405  error::MUXER_FAILURE,
406  "Error adding sample to segment: Frame::AddAditionalData Failed");
407  }
408  }
409 
410  if (!prev_sample_->is_key_frame() && !frame.CanBeSimpleBlock()) {
411  const int64_t timestamp_ns =
412  reference_frame_timestamp_ * kSecondsToNs / info_->time_scale();
413  frame.set_reference_block_timestamp(timestamp_ns);
414  }
415 
416  // GetRelativeTimecode will return -1 if the relative timecode is too large
417  // to fit in the frame.
418  if (cluster_->GetRelativeTimecode(frame.timestamp() /
419  cluster_->timecode_scale()) < 0) {
420  const double segment_duration =
421  static_cast<double>(frame.timestamp()) / kSecondsToNs;
422  LOG(ERROR) << "Error adding sample to segment: segment too large, "
423  << segment_duration << " seconds.";
424  return Status(error::MUXER_FAILURE,
425  "Error adding sample to segment: segment too large");
426  }
427 
428  if (!cluster_->AddFrame(&frame)) {
429  return Status(error::MUXER_FAILURE,
430  "Error adding sample to segment: Cluster::AddFrame failed");
431  }
432 
433  // A reference frame is needed for non-keyframes. Having a reference to the
434  // previous block is good enough.
435  // See libwebm Segment::AddGenericFrame
436  reference_frame_timestamp_ = prev_sample_->pts();
437  return Status::OK;
438 }
439 
440 } // namespace webm
441 } // namespace media
442 } // 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.