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 
78 Status Segmenter::Initialize(const StreamInfo& info,
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 
152 Status Segmenter::Finalize() {
153  if (prev_sample_ && !prev_sample_->end_of_stream()) {
154  uint64_t duration =
155  prev_sample_->pts() - first_timestamp_ + prev_sample_->duration();
156  segment_info_.set_duration(FromBmffTimestamp(duration));
157  }
158  return DoFinalize();
159 }
160 
161 Status Segmenter::AddSample(const MediaSample& source_sample) {
162  std::shared_ptr<MediaSample> sample(source_sample.Clone());
163 
164  if (sample_duration_ == 0) {
165  first_timestamp_ = sample->pts();
166  sample_duration_ = sample->duration();
167  if (muxer_listener_)
168  muxer_listener_->OnSampleDurationReady(sample_duration_);
169  }
170 
171  UpdateProgress(sample->duration());
172 
173  // This writes frames in a delay. Meaning that the previous frame is written
174  // on this call to AddSample. The current frame is stored until the next
175  // call. This is done to determine which frame is the last in a Cluster.
176  // This first block determines if this is a new Cluster and writes the
177  // previous frame first before creating the new Cluster.
178 
179  Status status;
180  if (new_segment_ || new_subsegment_) {
181  status = NewSegment(sample->pts(), new_subsegment_);
182  } else {
183  status = WriteFrame(false /* write_duration */);
184  }
185  if (!status.ok())
186  return status;
187 
188  if (is_encrypted_)
189  UpdateFrameForEncryption(sample.get());
190 
191  new_subsegment_ = false;
192  new_segment_ = false;
193  prev_sample_ = sample;
194  return Status::OK;
195 }
196 
197 Status Segmenter::FinalizeSegment(uint64_t start_timestamp,
198  uint64_t duration_timestamp,
199  bool is_subsegment) {
200  if (is_subsegment)
201  new_subsegment_ = true;
202  else
203  new_segment_ = true;
204  return WriteFrame(true /* write duration */);
205 }
206 
207 float Segmenter::GetDurationInSeconds() const {
208  return WebMTimecodeToNs(segment_info_.duration(),
209  segment_info_.timecode_scale()) /
210  static_cast<double>(kSecondsToNs);
211 }
212 
213 uint64_t Segmenter::FromBmffTimestamp(uint64_t bmff_timestamp) {
214  return NsToWebMTimecode(
215  BmffTimestampToNs(bmff_timestamp, time_scale_),
216  segment_info_.timecode_scale());
217 }
218 
219 uint64_t Segmenter::FromWebMTimecode(uint64_t webm_timecode) {
220  return NsToBmffTimestamp(
221  WebMTimecodeToNs(webm_timecode, segment_info_.timecode_scale()),
222  time_scale_);
223 }
224 
225 Status Segmenter::WriteSegmentHeader(uint64_t file_size, MkvWriter* writer) {
226  Status error_status(error::FILE_FAILURE, "Error writing segment header.");
227 
228  if (!WriteEbmlHeader(writer))
229  return error_status;
230 
231  if (WriteID(writer, mkvmuxer::kMkvSegment) != 0)
232  return error_status;
233 
234  const uint64_t segment_size_size = 8;
235  segment_payload_pos_ = writer->Position() + segment_size_size;
236  if (file_size > 0) {
237  // We want the size of the segment element, so subtract the header.
238  if (WriteUIntSize(writer, file_size - segment_payload_pos_,
239  segment_size_size) != 0)
240  return error_status;
241  if (!seek_head_.Write(writer))
242  return error_status;
243  } else {
244  if (SerializeInt(writer, mkvmuxer::kEbmlUnknownValue, segment_size_size) !=
245  0)
246  return error_status;
247  // We don't know the header size, so write a placeholder.
248  if (!seek_head_.WriteVoid(writer))
249  return error_status;
250  }
251 
252  seek_head_.set_info_pos(writer->Position() - segment_payload_pos_);
253  if (!segment_info_.Write(writer))
254  return error_status;
255 
256  seek_head_.set_tracks_pos(writer->Position() - segment_payload_pos_);
257  if (!tracks_.Write(writer))
258  return error_status;
259 
260  return Status::OK;
261 }
262 
263 Status Segmenter::SetCluster(uint64_t start_webm_timecode,
264  uint64_t position,
265  MkvWriter* writer) {
266  const uint64_t scale = segment_info_.timecode_scale();
267  cluster_.reset(new mkvmuxer::Cluster(start_webm_timecode, position, scale));
268  cluster_->Init(writer);
269  return Status::OK;
270 }
271 
272 void Segmenter::UpdateProgress(uint64_t progress) {
273  accumulated_progress_ += progress;
274  if (!progress_listener_ || progress_target_ == 0)
275  return;
276  // It might happen that accumulated progress exceeds progress_target due to
277  // computation errors, e.g. rounding error. Cap it so it never reports > 100%
278  // progress.
279  if (accumulated_progress_ >= progress_target_) {
280  progress_listener_->OnProgress(1.0);
281  } else {
282  progress_listener_->OnProgress(static_cast<double>(accumulated_progress_) /
283  progress_target_);
284  }
285 }
286 
287 Status Segmenter::InitializeVideoTrack(const VideoStreamInfo& info,
288  VideoTrack* track) {
289  if (info.codec() == kCodecAV1) {
290  track->set_codec_id("V_AV1");
291  if (!track->SetCodecPrivate(info.codec_config().data(),
292  info.codec_config().size())) {
293  return Status(error::INTERNAL_ERROR,
294  "Private codec data required for AV1 streams");
295  }
296  } else if (info.codec() == kCodecVP8) {
297  track->set_codec_id("V_VP8");
298  } else if (info.codec() == kCodecVP9) {
299  track->set_codec_id("V_VP9");
300 
301  // The |StreamInfo::codec_config| field is stored using the MP4 format; we
302  // need to convert it to the WebM format.
303  VPCodecConfigurationRecord vp_config;
304  if (!vp_config.ParseMP4(info.codec_config())) {
305  return Status(error::INTERNAL_ERROR,
306  "Unable to parse VP9 codec configuration");
307  }
308 
309  mkvmuxer::Colour colour;
310  if (vp_config.matrix_coefficients() != AVCOL_SPC_UNSPECIFIED) {
311  colour.set_matrix_coefficients(vp_config.matrix_coefficients());
312  }
313  if (vp_config.transfer_characteristics() != AVCOL_TRC_UNSPECIFIED) {
314  colour.set_transfer_characteristics(vp_config.transfer_characteristics());
315  }
316  if (vp_config.color_primaries() != AVCOL_PRI_UNSPECIFIED) {
317  colour.set_primaries(vp_config.color_primaries());
318  }
319  if (!track->SetColour(colour)) {
320  return Status(error::INTERNAL_ERROR,
321  "Failed to setup color element for VPx streams");
322  }
323 
324  std::vector<uint8_t> codec_config;
325  vp_config.WriteWebM(&codec_config);
326  if (!track->SetCodecPrivate(codec_config.data(), codec_config.size())) {
327  return Status(error::INTERNAL_ERROR,
328  "Private codec data required for VPx streams");
329  }
330  } else {
331  LOG(ERROR) << "Only VP8, VP9 and AV1 video codecs are supported in WebM.";
332  return Status(error::UNIMPLEMENTED,
333  "Only VP8, VP9 and AV1 video codecs are supported in WebM.");
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::kVideo);
340  track->set_width(info.width());
341  track->set_height(info.height());
342  track->set_display_height(info.height());
343  track->set_display_width(info.width() * info.pixel_width() /
344  info.pixel_height());
345  return Status::OK;
346 }
347 
348 Status Segmenter::InitializeAudioTrack(const AudioStreamInfo& info,
349  AudioTrack* track) {
350  if (info.codec() == kCodecOpus) {
351  track->set_codec_id(mkvmuxer::Tracks::kOpusCodecId);
352  } else if (info.codec() == kCodecVorbis) {
353  track->set_codec_id(mkvmuxer::Tracks::kVorbisCodecId);
354  } else {
355  LOG(ERROR) << "Only Vorbis and Opus audio codec are supported in WebM.";
356  return Status(error::UNIMPLEMENTED,
357  "Only Vorbis and Opus audio codecs are supported in WebM.");
358  }
359  if (!track->SetCodecPrivate(info.codec_config().data(),
360  info.codec_config().size())) {
361  return Status(error::INTERNAL_ERROR,
362  "Private codec data required for audio streams");
363  }
364 
365  track->set_uid(info.track_id());
366  if (!info.language().empty())
367  track->set_language(info.language().c_str());
368  track->set_type(mkvmuxer::Tracks::kAudio);
369  track->set_sample_rate(info.sampling_frequency());
370  track->set_channels(info.num_channels());
371  track->set_seek_pre_roll(info.seek_preroll_ns());
372  track->set_codec_delay(info.codec_delay_ns());
373  return Status::OK;
374 }
375 
376 Status Segmenter::WriteFrame(bool write_duration) {
377  // Create a frame manually so we can create non-SimpleBlock frames. This
378  // is required to allow the frame duration to be added. If the duration
379  // is not set, then a SimpleBlock will still be written.
380  mkvmuxer::Frame frame;
381 
382  if (!frame.Init(prev_sample_->data(), prev_sample_->data_size())) {
383  return Status(error::MUXER_FAILURE,
384  "Error adding sample to segment: Frame::Init failed");
385  }
386 
387  if (write_duration) {
388  frame.set_duration(
389  BmffTimestampToNs(prev_sample_->duration(), time_scale_));
390  }
391  frame.set_is_key(prev_sample_->is_key_frame());
392  frame.set_timestamp(
393  BmffTimestampToNs(prev_sample_->pts(), time_scale_));
394  frame.set_track_number(track_id_);
395 
396  if (prev_sample_->side_data_size() > 0) {
397  uint64_t block_add_id;
398  // First 8 bytes of side_data is the BlockAddID element's value, which is
399  // done to mimic ffmpeg behavior. See webm_cluster_parser.cc for details.
400  CHECK_GT(prev_sample_->side_data_size(), sizeof(block_add_id));
401  memcpy(&block_add_id, prev_sample_->side_data(), sizeof(block_add_id));
402  if (!frame.AddAdditionalData(
403  prev_sample_->side_data() + sizeof(block_add_id),
404  prev_sample_->side_data_size() - sizeof(block_add_id),
405  block_add_id)) {
406  return Status(
407  error::MUXER_FAILURE,
408  "Error adding sample to segment: Frame::AddAditionalData Failed");
409  }
410  }
411 
412  if (!prev_sample_->is_key_frame() && !frame.CanBeSimpleBlock()) {
413  frame.set_reference_block_timestamp(
414  BmffTimestampToNs(reference_frame_timestamp_, time_scale_));
415  }
416 
417  // GetRelativeTimecode will return -1 if the relative timecode is too large
418  // to fit in the frame.
419  if (cluster_->GetRelativeTimecode(NsToWebMTimecode(
420  frame.timestamp(), cluster_->timecode_scale())) < 0) {
421  const double segment_duration =
422  static_cast<double>(frame.timestamp() -
423  WebMTimecodeToNs(cluster_->timecode(),
424  cluster_->timecode_scale())) /
425  kSecondsToNs;
426  LOG(ERROR) << "Error adding sample to segment: segment too large, "
427  << segment_duration
428  << " seconds. Please check your GOP size and segment duration.";
429  return Status(error::MUXER_FAILURE,
430  "Error adding sample to segment: segment too large");
431  }
432 
433  if (!cluster_->AddFrame(&frame)) {
434  return Status(error::MUXER_FAILURE,
435  "Error adding sample to segment: Cluster::AddFrame failed");
436  }
437 
438  // A reference frame is needed for non-keyframes. Having a reference to the
439  // previous block is good enough.
440  // See libwebm Segment::AddGenericFrame
441  reference_frame_timestamp_ = prev_sample_->pts();
442  return Status::OK;
443 }
444 
445 } // namespace webm
446 } // namespace media
447 } // namespace shaka
Holds audio stream information.
Class to hold a media sample.
Definition: media_sample.h:22
std::shared_ptr< MediaSample > Clone() const
Clone the object and return a new MediaSample.
Definition: media_sample.cc:81
An implementation of IMkvWriter using our File type.
Definition: mkv_writer.h:21
mkvmuxer::int64 Position() const override
Definition: mkv_writer.cc:74
This class listens to progress updates events.
Abstract class holds stream information.
Definition: stream_info.h:65
Holds video stream information.
All the methods that are virtual are virtual for mocking.