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