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