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/media/formats/webm/encryptor.h"
21 #include "packager/media/formats/webm/webm_constants.h"
22 #include "packager/third_party/libwebm/src/mkvmuxerutil.hpp"
23 #include "packager/third_party/libwebm/src/webmids.hpp"
24 #include "packager/version/version.h"
25 
26 using mkvmuxer::AudioTrack;
27 using mkvmuxer::VideoTrack;
28 
29 namespace shaka {
30 namespace media {
31 namespace webm {
32 namespace {
33 const int64_t kTimecodeScale = 1000000;
34 const int64_t kSecondsToNs = 1000000000L;
35 
36 // Round to closest integer.
37 uint64_t Round(double value) {
38  return static_cast<uint64_t>(value + 0.5);
39 }
40 
41 // There are three different kinds of timestamp here:
42 // (1) ISO-BMFF timestamp (seconds scaled by ISO-BMFF timescale)
43 // This is used in our MediaSample and StreamInfo structures.
44 // (2) WebM timecode (seconds scaled by kSecondsToNs / WebM timecode scale)
45 // This is used in most WebM structures.
46 // (3) Nanoseconds (seconds scaled by kSecondsToNs)
47 // This is used in some WebM structures, e.g. Frame.
48 // We use Nanoseconds as intermediate format here for conversion, in
49 // uint64_t/int64_t, which is sufficient to represent a time as large as 292
50 // years.
51 
52 uint64_t BmffTimestampToNs(uint64_t timestamp, uint64_t time_scale) {
53  // Casting to double is needed otherwise kSecondsToNs * timestamp may overflow
54  // uint64_t/int64_t.
55  return Round(static_cast<double>(timestamp) / time_scale * kSecondsToNs);
56 }
57 
58 uint64_t NsToBmffTimestamp(uint64_t ns, uint64_t time_scale) {
59  // Casting to double is needed otherwise ns * time_scale may overflow
60  // uint64_t/int64_t.
61  return Round(static_cast<double>(ns) / kSecondsToNs * time_scale);
62 }
63 
64 uint64_t NsToWebMTimecode(uint64_t ns, uint64_t timecode_scale) {
65  return ns / timecode_scale;
66 }
67 
68 uint64_t WebMTimecodeToNs(uint64_t timecode, uint64_t timecode_scale) {
69  return timecode * timecode_scale;
70 }
71 
72 } // namespace
73 
74 Segmenter::Segmenter(const MuxerOptions& options) : options_(options) {}
75 
76 Segmenter::~Segmenter() {}
77 
79  ProgressListener* progress_listener,
80  MuxerListener* muxer_listener) {
81  muxer_listener_ = muxer_listener;
82  info_ = info;
83 
84  // Use media duration as progress target.
85  progress_target_ = info_->duration();
86  progress_listener_ = progress_listener;
87 
88  segment_info_.Init();
89  segment_info_.set_timecode_scale(kTimecodeScale);
90 
91  const std::string version = GetPackagerVersion();
92  if (!version.empty()) {
93  segment_info_.set_writing_app(
94  (GetPackagerProjectUrl() + " version " + version).c_str());
95  }
96 
97  if (options().segment_template.empty()) {
98  // Set an initial duration so the duration element is written; will be
99  // overwritten at the end. This works because this is a float and floats
100  // are always the same size.
101  segment_info_.set_duration(1);
102  }
103 
104  // Create the track info.
105  // The seed is only used to create a UID which we overwrite later.
106  unsigned int seed = 0;
107  std::unique_ptr<mkvmuxer::Track> track;
108  Status status;
109  switch (info_->stream_type()) {
110  case kStreamVideo: {
111  std::unique_ptr<VideoTrack> video_track(new VideoTrack(&seed));
112  status = InitializeVideoTrack(static_cast<VideoStreamInfo*>(info_),
113  video_track.get());
114  track = std::move(video_track);
115  break;
116  }
117  case kStreamAudio: {
118  std::unique_ptr<AudioTrack> audio_track(new AudioTrack(&seed));
119  status = InitializeAudioTrack(static_cast<AudioStreamInfo*>(info_),
120  audio_track.get());
121  track = std::move(audio_track);
122  break;
123  }
124  default:
125  NOTIMPLEMENTED() << "Not implemented for stream type: "
126  << info_->stream_type();
127  status = Status(error::UNIMPLEMENTED, "Not implemented for stream type");
128  }
129  if (!status.ok())
130  return status;
131 
132  if (info_->is_encrypted()) {
133  if (info->encryption_config().per_sample_iv_size != kWebMIvSize)
134  return Status(error::MUXER_FAILURE, "Incorrect size WebM encryption IV.");
135  status = UpdateTrackForEncryption(info_->encryption_config().key_id,
136  track.get());
137  if (!status.ok())
138  return status;
139  }
140 
141  tracks_.AddTrack(track.get(), info_->track_id());
142  // number() is only available after the above instruction.
143  track_id_ = track->number();
144  // |tracks_| owns |track|.
145  track.release();
146  return DoInitialize();
147 }
148 
150  uint64_t duration =
151  prev_sample_->pts() - first_timestamp_ + prev_sample_->duration();
152  segment_info_.set_duration(FromBmffTimestamp(duration));
153  return DoFinalize();
154 }
155 
156 Status Segmenter::AddSample(std::shared_ptr<MediaSample> sample) {
157  if (sample_duration_ == 0) {
158  first_timestamp_ = sample->pts();
159  sample_duration_ = sample->duration();
160  if (muxer_listener_)
161  muxer_listener_->OnSampleDurationReady(sample_duration_);
162  }
163 
164  UpdateProgress(sample->duration());
165 
166  // This writes frames in a delay. Meaning that the previous frame is written
167  // on this call to AddSample. The current frame is stored until the next
168  // call. This is done to determine which frame is the last in a Cluster.
169  // This first block determines if this is a new Cluster and writes the
170  // previous frame first before creating the new Cluster.
171 
172  Status status;
173  if (new_segment_ || new_subsegment_) {
174  status = NewSegment(sample->pts(), new_subsegment_);
175  } else {
176  status = WriteFrame(false /* write_duration */);
177  }
178  if (!status.ok())
179  return status;
180 
181  if (info_->is_encrypted())
182  UpdateFrameForEncryption(sample.get());
183 
184  new_subsegment_ = false;
185  new_segment_ = false;
186  prev_sample_ = sample;
187  return Status::OK;
188 }
189 
190 Status Segmenter::FinalizeSegment(uint64_t start_timestamp,
191  uint64_t duration_timestamp,
192  bool is_subsegment) {
193  if (is_subsegment)
194  new_subsegment_ = true;
195  else
196  new_segment_ = true;
197  return WriteFrame(true /* write duration */);
198 }
199 
200 float Segmenter::GetDurationInSeconds() const {
201  return WebMTimecodeToNs(segment_info_.duration(),
202  segment_info_.timecode_scale()) /
203  static_cast<double>(kSecondsToNs);
204 }
205 
206 uint64_t Segmenter::FromBmffTimestamp(uint64_t bmff_timestamp) {
207  return NsToWebMTimecode(
208  BmffTimestampToNs(bmff_timestamp, info_->time_scale()),
209  segment_info_.timecode_scale());
210 }
211 
212 uint64_t Segmenter::FromWebMTimecode(uint64_t webm_timecode) {
213  return NsToBmffTimestamp(
214  WebMTimecodeToNs(webm_timecode, segment_info_.timecode_scale()),
215  info_->time_scale());
216 }
217 
218 Status Segmenter::WriteSegmentHeader(uint64_t file_size, MkvWriter* writer) {
219  Status error_status(error::FILE_FAILURE, "Error writing segment header.");
220 
221  if (!WriteEbmlHeader(writer))
222  return error_status;
223 
224  if (WriteID(writer, mkvmuxer::kMkvSegment) != 0)
225  return error_status;
226 
227  const uint64_t segment_size_size = 8;
228  segment_payload_pos_ = writer->Position() + segment_size_size;
229  if (file_size > 0) {
230  // We want the size of the segment element, so subtract the header.
231  if (WriteUIntSize(writer, file_size - segment_payload_pos_,
232  segment_size_size) != 0)
233  return error_status;
234  if (!seek_head_.Write(writer))
235  return error_status;
236  } else {
237  if (SerializeInt(writer, mkvmuxer::kEbmlUnknownValue, segment_size_size) !=
238  0)
239  return error_status;
240  // We don't know the header size, so write a placeholder.
241  if (!seek_head_.WriteVoid(writer))
242  return error_status;
243  }
244 
245  seek_head_.set_info_pos(writer->Position() - segment_payload_pos_);
246  if (!segment_info_.Write(writer))
247  return error_status;
248 
249  seek_head_.set_tracks_pos(writer->Position() - segment_payload_pos_);
250  if (!tracks_.Write(writer))
251  return error_status;
252 
253  return Status::OK;
254 }
255 
256 Status Segmenter::SetCluster(uint64_t start_webm_timecode,
257  uint64_t position,
258  MkvWriter* writer) {
259  const uint64_t scale = segment_info_.timecode_scale();
260  cluster_.reset(new mkvmuxer::Cluster(start_webm_timecode, position, scale));
261  cluster_->Init(writer);
262  return Status::OK;
263 }
264 
265 void Segmenter::UpdateProgress(uint64_t progress) {
266  accumulated_progress_ += progress;
267  if (!progress_listener_ || progress_target_ == 0)
268  return;
269  // It might happen that accumulated progress exceeds progress_target due to
270  // computation errors, e.g. rounding error. Cap it so it never reports > 100%
271  // progress.
272  if (accumulated_progress_ >= progress_target_) {
273  progress_listener_->OnProgress(1.0);
274  } else {
275  progress_listener_->OnProgress(static_cast<double>(accumulated_progress_) /
276  progress_target_);
277  }
278 }
279 
280 Status Segmenter::InitializeVideoTrack(const VideoStreamInfo* info,
281  VideoTrack* track) {
282  if (info->codec() == kCodecVP8) {
283  track->set_codec_id(mkvmuxer::Tracks::kVp8CodecId);
284  } else if (info->codec() == kCodecVP9) {
285  track->set_codec_id(mkvmuxer::Tracks::kVp9CodecId);
286 
287  // The |StreamInfo::codec_config| field is stored using the MP4 format; we
288  // need to convert it to the WebM format.
289  VPCodecConfigurationRecord vp_config;
290  if (!vp_config.ParseMP4(info->codec_config())) {
291  return Status(error::INTERNAL_ERROR,
292  "Unable to parse VP9 codec configuration");
293  }
294 
295  mkvmuxer::Colour colour;
296  if (vp_config.matrix_coefficients() != AVCOL_SPC_UNSPECIFIED) {
297  colour.set_matrix_coefficients(vp_config.matrix_coefficients());
298  }
299  if (vp_config.transfer_characteristics() != AVCOL_TRC_UNSPECIFIED) {
300  colour.set_transfer_characteristics(vp_config.transfer_characteristics());
301  }
302  if (vp_config.color_primaries() != AVCOL_PRI_UNSPECIFIED) {
303  colour.set_primaries(vp_config.color_primaries());
304  }
305  if (!track->SetColour(colour)) {
306  return Status(error::INTERNAL_ERROR,
307  "Failed to setup color element for VPx streams");
308  }
309 
310  std::vector<uint8_t> codec_config;
311  vp_config.WriteWebM(&codec_config);
312  if (!track->SetCodecPrivate(codec_config.data(), codec_config.size())) {
313  return Status(error::INTERNAL_ERROR,
314  "Private codec data required for VPx streams");
315  }
316  } else {
317  LOG(ERROR) << "Only VP8 and VP9 video codecs are supported in WebM.";
318  return Status(error::UNIMPLEMENTED,
319  "Only VP8 and VP9 video codecs are supported in WebM.");
320  }
321 
322  track->set_uid(info->track_id());
323  if (!info->language().empty())
324  track->set_language(info->language().c_str());
325  track->set_type(mkvmuxer::Tracks::kVideo);
326  track->set_width(info->width());
327  track->set_height(info->height());
328  track->set_display_height(info->height());
329  track->set_display_width(info->width() * info->pixel_width() /
330  info->pixel_height());
331  return Status::OK;
332 }
333 
334 Status Segmenter::InitializeAudioTrack(const AudioStreamInfo* info,
335  AudioTrack* track) {
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 are supported in WebM.";
342  return Status(error::UNIMPLEMENTED,
343  "Only Vorbis and Opus audio codecs are supported in WebM.");
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  return Status::OK;
360 }
361 
362 Status Segmenter::WriteFrame(bool write_duration) {
363  // Create a frame manually so we can create non-SimpleBlock frames. This
364  // is required to allow the frame duration to be added. If the duration
365  // is not set, then a SimpleBlock will still be written.
366  mkvmuxer::Frame frame;
367 
368  if (!frame.Init(prev_sample_->data(), prev_sample_->data_size())) {
369  return Status(error::MUXER_FAILURE,
370  "Error adding sample to segment: Frame::Init failed");
371  }
372 
373  if (write_duration) {
374  frame.set_duration(
375  BmffTimestampToNs(prev_sample_->duration(), info_->time_scale()));
376  }
377  frame.set_is_key(prev_sample_->is_key_frame());
378  frame.set_timestamp(
379  BmffTimestampToNs(prev_sample_->pts(), info_->time_scale()));
380  frame.set_track_number(track_id_);
381 
382  if (prev_sample_->side_data_size() > 0) {
383  uint64_t block_add_id;
384  // First 8 bytes of side_data is the BlockAddID element's value, which is
385  // done to mimic ffmpeg behavior. See webm_cluster_parser.cc for details.
386  CHECK_GT(prev_sample_->side_data_size(), sizeof(block_add_id));
387  memcpy(&block_add_id, prev_sample_->side_data(), sizeof(block_add_id));
388  if (!frame.AddAdditionalData(
389  prev_sample_->side_data() + sizeof(block_add_id),
390  prev_sample_->side_data_size() - sizeof(block_add_id),
391  block_add_id)) {
392  return Status(
393  error::MUXER_FAILURE,
394  "Error adding sample to segment: Frame::AddAditionalData Failed");
395  }
396  }
397 
398  if (!prev_sample_->is_key_frame() && !frame.CanBeSimpleBlock()) {
399  frame.set_reference_block_timestamp(
400  BmffTimestampToNs(reference_frame_timestamp_, info_->time_scale()));
401  }
402 
403  // GetRelativeTimecode will return -1 if the relative timecode is too large
404  // to fit in the frame.
405  if (cluster_->GetRelativeTimecode(NsToWebMTimecode(
406  frame.timestamp(), cluster_->timecode_scale())) < 0) {
407  const double segment_duration =
408  static_cast<double>(frame.timestamp()) / kSecondsToNs;
409  LOG(ERROR) << "Error adding sample to segment: segment too large, "
410  << segment_duration << " seconds.";
411  return Status(error::MUXER_FAILURE,
412  "Error adding sample to segment: segment too large");
413  }
414 
415  if (!cluster_->AddFrame(&frame)) {
416  return Status(error::MUXER_FAILURE,
417  "Error adding sample to segment: Cluster::AddFrame failed");
418  }
419 
420  // A reference frame is needed for non-keyframes. Having a reference to the
421  // previous block is good enough.
422  // See libwebm Segment::AddGenericFrame
423  reference_frame_timestamp_ = prev_sample_->pts();
424  return Status::OK;
425 }
426 
427 } // namespace webm
428 } // namespace media
429 } // namespace shaka
Abstract class holds stream information.
Definition: stream_info.h:57
void UpdateProgress(uint64_t progress)
Update segmentation progress using ProgressListener.
Definition: segmenter.cc:225
virtual void OnSampleDurationReady(uint32_t sample_duration)=0
Class for parsing or writing VP codec configuration record.
Status Initialize(const std::vector< std::shared_ptr< StreamInfo >> &streams, MuxerListener *muxer_listener, ProgressListener *progress_listener)
Definition: segmenter.cc:49
Status AddSample(size_t stream_id, std::shared_ptr< MediaSample > sample)
Definition: segmenter.cc:115
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
Status FinalizeSegment(size_t stream_id, std::shared_ptr< SegmentInfo > segment_info)
Definition: segmenter.cc:140
Holds video stream information.