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  : options_(options),
31  info_(NULL),
32  muxer_listener_(NULL),
33  progress_listener_(NULL),
34  progress_target_(0),
35  accumulated_progress_(0),
36  total_duration_(0),
37  sample_duration_(0),
38  segment_payload_pos_(0),
39  cluster_length_sec_(0),
40  segment_length_sec_(0),
41  track_id_(0) {}
42 
43 Segmenter::~Segmenter() {}
44 
45 Status Segmenter::Initialize(scoped_ptr<MkvWriter> writer,
46  StreamInfo* info,
47  ProgressListener* progress_listener,
48  MuxerListener* muxer_listener,
49  KeySource* encryption_key_source,
50  uint32_t max_sd_pixels,
51  double clear_lead_in_seconds) {
52  muxer_listener_ = muxer_listener;
53  info_ = info;
54  clear_lead_ = clear_lead_in_seconds;
55 
56  // Use media duration as progress target.
57  progress_target_ = info_->duration();
58  progress_listener_ = progress_listener;
59 
60  const std::string version_string =
61  "https://github.com/google/edash-packager version " +
62  options().packager_version_string;
63 
64  segment_info_.Init();
65  segment_info_.set_timecode_scale(kTimecodeScale);
66  segment_info_.set_writing_app(version_string.c_str());
67  if (options().single_segment) {
68  // Set an initial duration so the duration element is written; will be
69  // overwritten at the end.
70  segment_info_.set_duration(1);
71  }
72 
73  Status status;
74  if (encryption_key_source) {
75  status = InitializeEncryptor(encryption_key_source, max_sd_pixels);
76  if (!status.ok())
77  return status;
78  }
79 
80  // Create the track info.
81  switch (info_->stream_type()) {
82  case kStreamVideo:
83  status = CreateVideoTrack(static_cast<VideoStreamInfo*>(info_));
84  break;
85  case kStreamAudio:
86  status = CreateAudioTrack(static_cast<AudioStreamInfo*>(info_));
87  break;
88  default:
89  NOTIMPLEMENTED() << "Not implemented for stream type: "
90  << info_->stream_type();
91  status = Status(error::UNIMPLEMENTED, "Not implemented for stream type");
92  }
93  if (!status.ok())
94  return status;
95 
96  return DoInitialize(writer.Pass());
97 }
98 
100  segment_info_.set_duration(FromBMFFTimescale(total_duration_));
101  return DoFinalize();
102 }
103 
104 Status Segmenter::AddSample(scoped_refptr<MediaSample> sample) {
105  if (sample_duration_ == 0) {
106  sample_duration_ = sample->duration();
107  if (muxer_listener_)
108  muxer_listener_->OnSampleDurationReady(sample_duration_);
109  }
110 
111  UpdateProgress(sample->duration());
112 
113  // Create a new cluster if needed.
114  Status status;
115  if (!cluster_) {
116  status = NewSegment(sample->pts());
117  } else if (segment_length_sec_ >= options_.segment_duration) {
118  if (sample->is_key_frame() || !options_.segment_sap_aligned) {
119  status = NewSegment(sample->pts());
120  segment_length_sec_ = 0;
121  cluster_length_sec_ = 0;
122  }
123  } else if (cluster_length_sec_ >= options_.fragment_duration) {
124  if (sample->is_key_frame() || !options_.fragment_sap_aligned) {
125  status = NewSubsegment(sample->pts());
126  cluster_length_sec_ = 0;
127  }
128  }
129  if (!status.ok())
130  return status;
131 
132  // Encrypt the frame.
133  if (encryptor_) {
134  const bool encrypt_frame =
135  static_cast<double>(total_duration_) / info_->time_scale() >=
136  clear_lead_;
137  status = encryptor_->EncryptFrame(sample, encrypt_frame);
138  if (!status.ok()) {
139  LOG(ERROR) << "Error encrypting frame.";
140  return status;
141  }
142  }
143 
144  const int64_t time_ns =
145  sample->pts() * kSecondsToNs / info_->time_scale();
146  bool addframe_result;
147  if (sample->side_data_size() > 0) {
148  uint64_t block_add_id;
149  // First 8 bytes of side_data is the BlockAddID element's value, which is
150  // done to mimic ffmpeg behavior. See webm_cluster_parser.cc for details.
151  CHECK_GT(sample->side_data_size(), sizeof(block_add_id));
152  memcpy(&block_add_id, sample->side_data(), sizeof(block_add_id));
153  addframe_result = cluster_->AddFrameWithAdditional(
154  sample->data(), sample->data_size(),
155  sample->side_data() + sizeof(block_add_id),
156  sample->side_data_size() - sizeof(block_add_id), block_add_id,
157  track_id_, time_ns, sample->is_key_frame());
158  } else {
159  addframe_result =
160  cluster_->AddFrame(sample->data(), sample->data_size(), track_id_,
161  time_ns, sample->is_key_frame());
162  }
163  if (!addframe_result) {
164  LOG(ERROR) << "Error adding sample to segment.";
165  return Status(error::FILE_FAILURE, "Error adding sample to segment.");
166  }
167 
168  const double duration_sec =
169  static_cast<double>(sample->duration()) / info_->time_scale();
170  cluster_length_sec_ += duration_sec;
171  segment_length_sec_ += duration_sec;
172  total_duration_ += sample->duration();
173 
174  return Status::OK;
175 }
176 
177 float Segmenter::GetDuration() const {
178  return static_cast<float>(segment_info_.duration()) *
179  segment_info_.timecode_scale() / kSecondsToNs;
180 }
181 
182 uint64_t Segmenter::FromBMFFTimescale(uint64_t time_timescale) {
183  // Convert the time from BMFF time_code to WebM timecode scale.
184  const int64_t time_ns =
185  kSecondsToNs * time_timescale / info_->time_scale();
186  return time_ns / segment_info_.timecode_scale();
187 }
188 
189 uint64_t Segmenter::FromWebMTimecode(uint64_t time_webm_timecode) {
190  // Convert the time to BMFF time_code from WebM timecode scale.
191  const int64_t time_ns = time_webm_timecode * segment_info_.timecode_scale();
192  return time_ns * info_->time_scale() / kSecondsToNs;
193 }
194 
195 Status Segmenter::WriteSegmentHeader(uint64_t file_size, MkvWriter* writer) {
196  Status error_status(error::FILE_FAILURE, "Error writing segment header.");
197 
198  if (!WriteEbmlHeader(writer))
199  return error_status;
200 
201  if (WriteID(writer, mkvmuxer::kMkvSegment) != 0)
202  return error_status;
203 
204  const uint64_t segment_size_size = 8;
205  segment_payload_pos_ = writer->Position() + segment_size_size;
206  if (file_size > 0) {
207  // We want the size of the segment element, so subtract the header.
208  if (WriteUIntSize(writer, file_size - segment_payload_pos_,
209  segment_size_size) != 0)
210  return error_status;
211  if (!seek_head_.Write(writer))
212  return error_status;
213  } else {
214  if (SerializeInt(writer, mkvmuxer::kEbmlUnknownValue, segment_size_size) !=
215  0)
216  return error_status;
217  // We don't know the header size, so write a placeholder.
218  if (!seek_head_.WriteVoid(writer))
219  return error_status;
220  }
221 
222  if (!segment_info_.Write(writer))
223  return error_status;
224 
225  if (!tracks_.Write(writer))
226  return error_status;
227 
228  return Status::OK;
229 }
230 
231 Status Segmenter::SetCluster(uint64_t start_webm_timecode,
232  uint64_t position,
233  MkvWriter* writer) {
234  const uint64_t scale = segment_info_.timecode_scale();
235  cluster_.reset(new mkvmuxer::Cluster(start_webm_timecode, position, scale));
236  cluster_->Init(writer);
237  return Status::OK;
238 }
239 
240 void Segmenter::UpdateProgress(uint64_t progress) {
241  accumulated_progress_ += progress;
242  if (!progress_listener_ || progress_target_ == 0)
243  return;
244  // It might happen that accumulated progress exceeds progress_target due to
245  // computation errors, e.g. rounding error. Cap it so it never reports > 100%
246  // progress.
247  if (accumulated_progress_ >= progress_target_) {
248  progress_listener_->OnProgress(1.0);
249  } else {
250  progress_listener_->OnProgress(static_cast<double>(accumulated_progress_) /
251  progress_target_);
252  }
253 }
254 
255 Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) {
256  // The seed is only used to create a UID which we overwrite later.
257  unsigned int seed = 0;
258  mkvmuxer::VideoTrack* track = new mkvmuxer::VideoTrack(&seed);
259  if (!track)
260  return Status(error::INTERNAL_ERROR, "Failed to create video track.");
261 
262  if (info->codec() == kCodecVP8) {
263  track->set_codec_id(mkvmuxer::Tracks::kVp8CodecId);
264  } else if (info->codec() == kCodecVP9) {
265  track->set_codec_id(mkvmuxer::Tracks::kVp9CodecId);
266  } else {
267  LOG(ERROR) << "Only VP8 and VP9 video codec is supported.";
268  return Status(error::UNIMPLEMENTED,
269  "Only VP8 and VP9 video codecs are supported.");
270  }
271 
272  track->set_uid(info->track_id());
273  if (!info->language().empty())
274  track->set_language(info->language().c_str());
275  track->set_type(mkvmuxer::Tracks::kVideo);
276  track->set_width(info->width());
277  track->set_height(info->height());
278  track->set_display_height(info->height());
279  track->set_display_width(info->width() * info->pixel_width() /
280  info->pixel_height());
281 
282  if (encryptor_)
283  encryptor_->AddTrackInfo(track);
284 
285  tracks_.AddTrack(track, info->track_id());
286  track_id_ = track->number();
287  return Status::OK;
288 }
289 
290 Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) {
291  // The seed is only used to create a UID which we overwrite later.
292  unsigned int seed = 0;
293  mkvmuxer::AudioTrack* track = new mkvmuxer::AudioTrack(&seed);
294  if (!track)
295  return Status(error::INTERNAL_ERROR, "Failed to create audio track.");
296 
297  if (info->codec() == kCodecOpus) {
298  track->set_codec_id(mkvmuxer::Tracks::kOpusCodecId);
299  } else if (info->codec() == kCodecVorbis) {
300  track->set_codec_id(mkvmuxer::Tracks::kVorbisCodecId);
301  } else {
302  LOG(ERROR) << "Only Vorbis and Opus audio codec is supported.";
303  return Status(error::UNIMPLEMENTED,
304  "Only Vorbis and Opus audio codecs are supported.");
305  }
306  if (!track->SetCodecPrivate(info->extra_data().data(),
307  info->extra_data().size())) {
308  return Status(error::INTERNAL_ERROR,
309  "Private codec data required for audio streams");
310  }
311 
312  track->set_uid(info->track_id());
313  if (!info->language().empty())
314  track->set_language(info->language().c_str());
315  track->set_type(mkvmuxer::Tracks::kAudio);
316  track->set_sample_rate(info->sampling_frequency());
317  track->set_channels(info->num_channels());
318 
319  if (encryptor_)
320  encryptor_->AddTrackInfo(track);
321 
322  tracks_.AddTrack(track, info->track_id());
323  track_id_ = track->number();
324  return Status::OK;
325 }
326 
327 Status Segmenter::InitializeEncryptor(KeySource* key_source,
328  uint32_t max_sd_pixels) {
329  encryptor_.reset(new Encryptor());
330  switch (info_->stream_type()) {
331  case kStreamVideo: {
332  VideoStreamInfo* video_info = static_cast<VideoStreamInfo*>(info_);
333  uint32_t pixels = video_info->width() * video_info->height();
334  KeySource::TrackType type = (pixels > max_sd_pixels)
335  ? KeySource::TRACK_TYPE_HD
336  : KeySource::TRACK_TYPE_SD;
337  return encryptor_->Initialize(muxer_listener_, type, key_source);
338  }
339  case kStreamAudio:
340  return encryptor_->Initialize(
341  muxer_listener_, KeySource::TrackType::TRACK_TYPE_AUDIO, key_source);
342  default:
343  // Other streams are not encrypted.
344  return Status::OK;
345  }
346 }
347 
348 } // namespace webm
349 } // namespace media
350 } // 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
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:71
This class listens to progress updates events.
Status AddSample(const MediaStream *stream, scoped_refptr< MediaSample > sample)
Definition: segmenter.cc:277
KeySource is responsible for encryption key acquisition.
Definition: key_source.h:29
virtual void OnProgress(double progress)=0
void UpdateProgress(uint64_t progress)
Update segmentation progress using ProgressListener.
Definition: segmenter.cc:344
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)
Definition: segmenter.cc:140
Holds video stream information.
virtual void OnSampleDurationReady(uint32_t sample_duration)=0