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