7 #include "packager/media/formats/webm/segmenter.h"
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"
26 using mkvmuxer::AudioTrack;
27 using mkvmuxer::VideoTrack;
33 const int64_t kTimecodeScale = 1000000;
34 const int64_t kSecondsToNs = 1000000000L;
37 uint64_t Round(
double value) {
38 return static_cast<uint64_t
>(value + 0.5);
52 uint64_t BmffTimestampToNs(uint64_t timestamp, uint64_t time_scale) {
55 return Round(
static_cast<double>(timestamp) / time_scale * kSecondsToNs);
58 uint64_t NsToBmffTimestamp(uint64_t ns, uint64_t time_scale) {
61 return Round(
static_cast<double>(ns) / kSecondsToNs * time_scale);
64 uint64_t NsToWebMTimecode(uint64_t ns, uint64_t timecode_scale) {
65 return ns / timecode_scale;
68 uint64_t WebMTimecodeToNs(uint64_t timecode, uint64_t timecode_scale) {
69 return timecode * timecode_scale;
74 Segmenter::Segmenter(
const MuxerOptions& options) : options_(options) {}
76 Segmenter::~Segmenter() {}
81 is_encrypted_ = info.is_encrypted();
82 duration_ = info.duration();
83 time_scale_ = info.time_scale();
85 muxer_listener_ = muxer_listener;
88 progress_target_ = info.duration();
89 progress_listener_ = progress_listener;
92 segment_info_.set_timecode_scale(kTimecodeScale);
94 const std::string version = GetPackagerVersion();
95 if (!version.empty()) {
96 segment_info_.set_writing_app(
97 (GetPackagerProjectUrl() +
" version " + version).c_str());
100 if (options().segment_template.empty()) {
104 segment_info_.set_duration(1);
109 unsigned int seed = 0;
110 std::unique_ptr<mkvmuxer::Track> track;
112 switch (info.stream_type()) {
114 std::unique_ptr<VideoTrack> video_track(
new VideoTrack(&seed));
115 status = InitializeVideoTrack(
static_cast<const VideoStreamInfo&
>(info),
117 track = std::move(video_track);
121 std::unique_ptr<AudioTrack> audio_track(
new AudioTrack(&seed));
122 status = InitializeAudioTrack(
static_cast<const AudioStreamInfo&
>(info),
124 track = std::move(audio_track);
128 NOTIMPLEMENTED() <<
"Not implemented for stream type: "
129 << info.stream_type();
130 status =
Status(error::UNIMPLEMENTED,
"Not implemented for stream type");
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,
144 tracks_.AddTrack(track.get(), info.track_id());
146 track_id_ = track->number();
149 return DoInitialize();
153 if (prev_sample_ && !prev_sample_->end_of_stream()) {
155 prev_sample_->pts() - first_timestamp_ + prev_sample_->duration();
156 segment_info_.set_duration(FromBmffTimestamp(duration));
162 std::shared_ptr<MediaSample> sample(source_sample.
Clone());
164 if (sample_duration_ == 0) {
165 first_timestamp_ = sample->pts();
166 sample_duration_ = sample->duration();
180 if (new_segment_ || new_subsegment_) {
181 status = NewSegment(sample->pts(), new_subsegment_);
183 status = WriteFrame(
false );
189 UpdateFrameForEncryption(sample.get());
191 new_subsegment_ =
false;
192 new_segment_ =
false;
193 prev_sample_ = sample;
198 uint64_t duration_timestamp,
199 bool is_subsegment) {
201 new_subsegment_ =
true;
204 return WriteFrame(
true );
207 float Segmenter::GetDurationInSeconds()
const {
208 return WebMTimecodeToNs(segment_info_.duration(),
209 segment_info_.timecode_scale()) /
210 static_cast<double>(kSecondsToNs);
213 uint64_t Segmenter::FromBmffTimestamp(uint64_t bmff_timestamp) {
214 return NsToWebMTimecode(
215 BmffTimestampToNs(bmff_timestamp, time_scale_),
216 segment_info_.timecode_scale());
219 uint64_t Segmenter::FromWebMTimecode(uint64_t webm_timecode) {
220 return NsToBmffTimestamp(
221 WebMTimecodeToNs(webm_timecode, segment_info_.timecode_scale()),
226 Status error_status(error::FILE_FAILURE,
"Error writing segment header.");
228 if (!WriteEbmlHeader(writer))
231 if (WriteID(writer, mkvmuxer::kMkvSegment) != 0)
234 const uint64_t segment_size_size = 8;
235 segment_payload_pos_ = writer->
Position() + segment_size_size;
238 if (WriteUIntSize(writer, file_size - segment_payload_pos_,
239 segment_size_size) != 0)
241 if (!seek_head_.Write(writer))
244 if (SerializeInt(writer, mkvmuxer::kEbmlUnknownValue, segment_size_size) !=
248 if (!seek_head_.WriteVoid(writer))
252 seek_head_.set_info_pos(writer->
Position() - segment_payload_pos_);
253 if (!segment_info_.Write(writer))
256 seek_head_.set_tracks_pos(writer->
Position() - segment_payload_pos_);
257 if (!tracks_.Write(writer))
263 Status Segmenter::SetCluster(uint64_t start_webm_timecode,
266 const uint64_t scale = segment_info_.timecode_scale();
267 cluster_.reset(
new mkvmuxer::Cluster(start_webm_timecode, position, scale));
268 cluster_->Init(writer);
273 accumulated_progress_ += progress;
274 if (!progress_listener_ || progress_target_ == 0)
279 if (accumulated_progress_ >= progress_target_) {
282 progress_listener_->
OnProgress(
static_cast<double>(accumulated_progress_) /
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");
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");
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");
309 mkvmuxer::Colour colour;
310 if (vp_config.matrix_coefficients() != AVCOL_SPC_UNSPECIFIED) {
311 colour.set_matrix_coefficients(vp_config.matrix_coefficients());
313 if (vp_config.transfer_characteristics() != AVCOL_TRC_UNSPECIFIED) {
314 colour.set_transfer_characteristics(vp_config.transfer_characteristics());
316 if (vp_config.color_primaries() != AVCOL_PRI_UNSPECIFIED) {
317 colour.set_primaries(vp_config.color_primaries());
319 if (!track->SetColour(colour)) {
320 return Status(error::INTERNAL_ERROR,
321 "Failed to setup color element for VPx streams");
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");
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.");
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());
348 Status Segmenter::InitializeAudioTrack(
const AudioStreamInfo& info,
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);
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.");
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");
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());
376 Status Segmenter::WriteFrame(
bool write_duration) {
380 mkvmuxer::Frame frame;
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");
387 if (write_duration) {
389 BmffTimestampToNs(prev_sample_->duration(), time_scale_));
391 frame.set_is_key(prev_sample_->is_key_frame());
393 BmffTimestampToNs(prev_sample_->pts(), time_scale_));
394 frame.set_track_number(track_id_);
396 if (prev_sample_->side_data_size() > 0) {
397 uint64_t block_add_id;
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),
407 error::MUXER_FAILURE,
408 "Error adding sample to segment: Frame::AddAditionalData Failed");
412 if (!prev_sample_->is_key_frame() && !frame.CanBeSimpleBlock()) {
413 frame.set_reference_block_timestamp(
414 BmffTimestampToNs(reference_frame_timestamp_, time_scale_));
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())) /
426 LOG(ERROR) <<
"Error adding sample to segment: segment too large, "
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");
433 if (!cluster_->AddFrame(&frame)) {
434 return Status(error::MUXER_FAILURE,
435 "Error adding sample to segment: Cluster::AddFrame failed");
441 reference_frame_timestamp_ = prev_sample_->pts();