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