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  muxer_listener_ = muxer_listener;
51  info_ = info;
52 
53  // Use media duration as progress target.
54  progress_target_ = info_->duration();
55  progress_listener_ = progress_listener;
56 
57  const std::string version_string =
58  "https://github.com/google/edash-packager version " +
59  options().packager_version_string;
60 
61  segment_info_.Init();
62  segment_info_.set_timecode_scale(kTimecodeScale);
63  segment_info_.set_writing_app(version_string.c_str());
64  if (options().single_segment) {
65  // Set an initial duration so the duration element is written; will be
66  // overwritten at the end.
67  segment_info_.set_duration(1);
68  }
69 
70  // Create the track info.
71  Status status;
72  switch (info_->stream_type()) {
73  case kStreamVideo:
74  status = CreateVideoTrack(static_cast<VideoStreamInfo*>(info_));
75  break;
76  case kStreamAudio:
77  status = CreateAudioTrack(static_cast<AudioStreamInfo*>(info_));
78  break;
79  default:
80  NOTIMPLEMENTED() << "Not implemented for stream type: "
81  << info_->stream_type();
82  status = Status(error::UNIMPLEMENTED, "Not implemented for stream type");
83  }
84  if (!status.ok())
85  return status;
86 
87  return DoInitialize(writer.Pass());
88 }
89 
91  segment_info_.set_duration(FromBMFFTimescale(total_duration_));
92  return DoFinalize();
93 }
94 
95 Status Segmenter::AddSample(scoped_refptr<MediaSample> sample) {
96  if (sample_duration_ == 0) {
97  sample_duration_ = sample->duration();
98  if (muxer_listener_)
99  muxer_listener_->OnSampleDurationReady(sample_duration_);
100  }
101 
102  UpdateProgress(sample->duration());
103 
104  // Create a new cluster if needed.
105  Status status;
106  if (!cluster_) {
107  status = NewSegment(sample->pts());
108  } else if (segment_length_sec_ >= options_.segment_duration) {
109  if (sample->is_key_frame() || !options_.segment_sap_aligned) {
110  status = NewSegment(sample->pts());
111  segment_length_sec_ = 0;
112  cluster_length_sec_ = 0;
113  }
114  } else if (cluster_length_sec_ >= options_.fragment_duration) {
115  if (sample->is_key_frame() || !options_.fragment_sap_aligned) {
116  status = NewSubsegment(sample->pts());
117  cluster_length_sec_ = 0;
118  }
119  }
120  if (!status.ok())
121  return status;
122 
123  const int64_t time_ns =
124  sample->pts() * kSecondsToNs / info_->time_scale();
125  if (!cluster_->AddFrame(sample->data(), sample->data_size(), track_id_,
126  time_ns, sample->is_key_frame())) {
127  LOG(ERROR) << "Error adding sample to segment.";
128  return Status(error::FILE_FAILURE, "Error adding sample to segment.");
129  }
130  const double duration_sec =
131  static_cast<double>(sample->duration()) / info_->time_scale();
132  cluster_length_sec_ += duration_sec;
133  segment_length_sec_ += duration_sec;
134  total_duration_ += sample->duration();
135 
136  return Status::OK;
137 }
138 
139 float Segmenter::GetDuration() const {
140  return static_cast<float>(segment_info_.duration()) *
141  segment_info_.timecode_scale() / kSecondsToNs;
142 }
143 
144 uint64_t Segmenter::FromBMFFTimescale(uint64_t time_timescale) {
145  // Convert the time from BMFF time_code to WebM timecode scale.
146  const int64_t time_ns =
147  kSecondsToNs * time_timescale / info_->time_scale();
148  return time_ns / segment_info_.timecode_scale();
149 }
150 
151 uint64_t Segmenter::FromWebMTimecode(uint64_t time_webm_timecode) {
152  // Convert the time to BMFF time_code from WebM timecode scale.
153  const int64_t time_ns = time_webm_timecode * segment_info_.timecode_scale();
154  return time_ns * info_->time_scale() / kSecondsToNs;
155 }
156 
157 Status Segmenter::WriteSegmentHeader(uint64_t file_size, MkvWriter* writer) {
158  Status error_status(error::FILE_FAILURE, "Error writing segment header.");
159 
160  if (!WriteEbmlHeader(writer))
161  return error_status;
162 
163  if (WriteID(writer, mkvmuxer::kMkvSegment) != 0)
164  return error_status;
165 
166  const uint64_t segment_size_size = 8;
167  segment_payload_pos_ = writer->Position() + segment_size_size;
168  if (file_size > 0) {
169  // We want the size of the segment element, so subtract the header.
170  if (WriteUIntSize(writer, file_size - segment_payload_pos_,
171  segment_size_size) != 0)
172  return error_status;
173  if (!seek_head_.Write(writer))
174  return error_status;
175  } else {
176  if (SerializeInt(writer, mkvmuxer::kEbmlUnknownValue, segment_size_size) !=
177  0)
178  return error_status;
179  // We don't know the header size, so write a placeholder.
180  if (!seek_head_.WriteVoid(writer))
181  return error_status;
182  }
183 
184  if (!segment_info_.Write(writer))
185  return error_status;
186 
187  if (!tracks_.Write(writer))
188  return error_status;
189 
190  return Status::OK;
191 }
192 
193 Status Segmenter::SetCluster(uint64_t start_webm_timecode,
194  uint64_t position,
195  MkvWriter* writer) {
196  const uint64_t scale = segment_info_.timecode_scale();
197  cluster_.reset(new mkvmuxer::Cluster(start_webm_timecode, position, scale));
198  cluster_->Init(writer);
199  return Status::OK;
200 }
201 
202 void Segmenter::UpdateProgress(uint64_t progress) {
203  accumulated_progress_ += progress;
204  if (!progress_listener_ || progress_target_ == 0)
205  return;
206  // It might happen that accumulated progress exceeds progress_target due to
207  // computation errors, e.g. rounding error. Cap it so it never reports > 100%
208  // progress.
209  if (accumulated_progress_ >= progress_target_) {
210  progress_listener_->OnProgress(1.0);
211  } else {
212  progress_listener_->OnProgress(static_cast<double>(accumulated_progress_) /
213  progress_target_);
214  }
215 }
216 
217 Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) {
218  // The seed is only used to create a UID which we overwrite later.
219  unsigned int seed = 0;
220  mkvmuxer::VideoTrack* track = new mkvmuxer::VideoTrack(&seed);
221  if (!track)
222  return Status(error::INTERNAL_ERROR, "Failed to create video track.");
223 
224  if (info->codec() == kCodecVP8) {
225  track->set_codec_id(mkvmuxer::Tracks::kVp8CodecId);
226  } else if (info->codec() == kCodecVP9) {
227  track->set_codec_id(mkvmuxer::Tracks::kVp9CodecId);
228  } else {
229  LOG(ERROR) << "Only VP8 and VP9 video codec is supported.";
230  return Status(error::UNIMPLEMENTED,
231  "Only VP8 and VP9 video codecs are supported.");
232  }
233 
234  track->set_uid(info->track_id());
235  track->set_type(mkvmuxer::Tracks::kVideo);
236  track->set_width(info->width());
237  track->set_height(info->height());
238  track->set_display_height(info->height());
239  track->set_display_width(info->width() * info->pixel_width() /
240  info->pixel_height());
241  track->set_language(info->language().c_str());
242 
243  tracks_.AddTrack(track, info->track_id());
244  track_id_ = track->number();
245  return Status::OK;
246 }
247 
248 Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) {
249  // The seed is only used to create a UID which we overwrite later.
250  unsigned int seed = 0;
251  mkvmuxer::AudioTrack* track = new mkvmuxer::AudioTrack(&seed);
252  if (!track)
253  return Status(error::INTERNAL_ERROR, "Failed to create audio track.");
254 
255  if (info->codec() == kCodecOpus) {
256  track->set_codec_id(mkvmuxer::Tracks::kOpusCodecId);
257  } else if (info->codec() == kCodecVorbis) {
258  track->set_codec_id(mkvmuxer::Tracks::kVorbisCodecId);
259  } else {
260  LOG(ERROR) << "Only Vorbis and Opus audio codec is supported.";
261  return Status(error::UNIMPLEMENTED,
262  "Only Vorbis and Opus audio codecs are supported.");
263  }
264 
265  track->set_uid(info->track_id());
266  track->set_language(info->language().c_str());
267  track->set_type(mkvmuxer::Tracks::kAudio);
268  track->set_sample_rate(info->sampling_frequency());
269  track->set_channels(info->num_channels());
270 
271  tracks_.AddTrack(track, info->track_id());
272  track_id_ = track->number();
273  return Status::OK;
274 }
275 
276 } // namespace webm
277 } // namespace media
278 } // 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