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