7#include <packager/media/formats/webm/segmenter.h>
9#include <absl/log/check.h>
10#include <mkvmuxer/mkvmuxerutil.h>
12#include <packager/macros/logging.h>
13#include <packager/media/base/audio_stream_info.h>
14#include <packager/media/base/media_handler.h>
15#include <packager/media/base/muxer_options.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/version/version.h>
24using mkvmuxer::AudioTrack;
25using mkvmuxer::VideoTrack;
31const int64_t kTimecodeScale = 1000000;
32const int64_t kSecondsToNs = 1000000000L;
35uint64_t Round(
double value) {
36 return static_cast<uint64_t
>(value + 0.5);
50int64_t BmffTimestampToNs(int64_t timestamp, int64_t time_scale) {
53 return Round(
static_cast<double>(timestamp) / time_scale * kSecondsToNs);
56int64_t NsToBmffTimestamp(int64_t ns, int64_t time_scale) {
59 return Round(
static_cast<double>(ns) / kSecondsToNs * time_scale);
62int64_t NsToWebMTimecode(int64_t ns, int64_t timecode_scale) {
63 return ns / timecode_scale;
66int64_t WebMTimecodeToNs(int64_t timecode, int64_t timecode_scale) {
67 return timecode * timecode_scale;
72Segmenter::Segmenter(
const MuxerOptions& options) : options_(options) {}
74Segmenter::~Segmenter() {}
79 is_encrypted_ = info.is_encrypted();
80 duration_ = info.duration();
81 time_scale_ = info.time_scale();
83 muxer_listener_ = muxer_listener;
86 progress_target_ = info.duration();
87 progress_listener_ = progress_listener;
90 segment_info_.set_timecode_scale(kTimecodeScale);
92 const std::string version = GetPackagerVersion();
93 if (!version.empty()) {
94 segment_info_.set_writing_app(
95 (GetPackagerProjectUrl() +
" version " + version).c_str());
98 if (options().segment_template.empty()) {
102 segment_info_.set_duration(1);
107 unsigned int seed = 0;
108 std::unique_ptr<mkvmuxer::Track> track;
110 switch (info.stream_type()) {
112 std::unique_ptr<VideoTrack> video_track(
new VideoTrack(&seed));
113 status = InitializeVideoTrack(
static_cast<const VideoStreamInfo&
>(info),
115 track = std::move(video_track);
119 std::unique_ptr<AudioTrack> audio_track(
new AudioTrack(&seed));
120 status = InitializeAudioTrack(
static_cast<const AudioStreamInfo&
>(info),
122 track = std::move(audio_track);
126 NOTIMPLEMENTED() <<
"Not implemented for stream type: "
127 << info.stream_type();
128 status = Status(error::UNIMPLEMENTED,
"Not implemented for stream type");
133 if (info.is_encrypted()) {
134 if (info.encryption_config().per_sample_iv_size != kWebMIvSize)
135 return Status(error::MUXER_FAILURE,
"Incorrect size WebM encryption IV.");
137 UpdateTrackForEncryption(info.encryption_config().key_id, track.get());
142 tracks_.AddTrack(track.get(), info.track_id());
144 track_id_ = track->number();
147 return DoInitialize();
150Status Segmenter::Finalize() {
151 if (prev_sample_ && !prev_sample_->end_of_stream()) {
153 prev_sample_->pts() - first_timestamp_ + prev_sample_->duration();
154 segment_info_.set_duration(FromBmffTimestamp(duration));
160 std::shared_ptr<MediaSample> sample(source_sample.
Clone());
164 if (num_samples_ < 2) {
165 sample_durations_[num_samples_] = sample->duration();
166 if (num_samples_ == 0)
167 first_timestamp_ = sample->pts();
168 else if (muxer_listener_)
169 muxer_listener_->OnSampleDurationReady(sample_durations_[num_samples_]);
173 UpdateProgress(sample->duration());
182 if (new_segment_ || new_subsegment_) {
183 status = NewSegment(sample->pts(), new_subsegment_);
185 status = WriteFrame(
false );
191 UpdateFrameForEncryption(sample.get());
193 new_subsegment_ =
false;
194 new_segment_ =
false;
195 prev_sample_ = sample;
199Status Segmenter::FinalizeSegment(int64_t ,
202 int64_t segment_number) {
204 new_subsegment_ =
true;
207 return WriteFrame(
true );
210float Segmenter::GetDurationInSeconds()
const {
211 return WebMTimecodeToNs(segment_info_.duration(),
212 segment_info_.timecode_scale()) /
213 static_cast<double>(kSecondsToNs);
216int64_t Segmenter::FromBmffTimestamp(int64_t bmff_timestamp) {
217 return NsToWebMTimecode(BmffTimestampToNs(bmff_timestamp, time_scale_),
218 segment_info_.timecode_scale());
221int64_t Segmenter::FromWebMTimecode(int64_t webm_timecode) {
222 return NsToBmffTimestamp(
223 WebMTimecodeToNs(webm_timecode, segment_info_.timecode_scale()),
227Status Segmenter::WriteSegmentHeader(uint64_t file_size,
MkvWriter* writer) {
228 Status error_status(error::FILE_FAILURE,
"Error writing segment header.");
230 if (!WriteEbmlHeader(writer))
233 if (WriteID(writer, libwebm::kMkvSegment) != 0)
236 const uint64_t segment_size_size = 8;
237 segment_payload_pos_ = writer->
Position() + segment_size_size;
240 if (WriteUIntSize(writer, file_size - segment_payload_pos_,
241 segment_size_size) != 0)
243 if (!seek_head_.Write(writer))
246 if (SerializeInt(writer, mkvmuxer::kEbmlUnknownValue, segment_size_size) !=
250 if (!seek_head_.WriteVoid(writer))
254 seek_head_.set_info_pos(writer->
Position() - segment_payload_pos_);
255 if (!segment_info_.Write(writer))
258 seek_head_.set_tracks_pos(writer->
Position() - segment_payload_pos_);
259 if (!tracks_.Write(writer))
265Status Segmenter::SetCluster(int64_t start_webm_timecode,
268 const int64_t scale = segment_info_.timecode_scale();
269 cluster_.reset(
new mkvmuxer::Cluster(start_webm_timecode, position, scale));
270 cluster_->Init(writer);
274void Segmenter::UpdateProgress(uint64_t progress) {
275 accumulated_progress_ += progress;
276 if (!progress_listener_ || progress_target_ == 0)
281 if (accumulated_progress_ >= progress_target_) {
282 progress_listener_->OnProgress(1.0);
284 progress_listener_->OnProgress(
static_cast<double>(accumulated_progress_) /
291 if (info.codec() == kCodecAV1) {
292 track->set_codec_id(
"V_AV1");
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 AV1 streams");
298 }
else if (info.codec() == kCodecVP8) {
299 track->set_codec_id(
"V_VP8");
300 }
else if (info.codec() == kCodecVP9) {
301 track->set_codec_id(
"V_VP9");
305 VPCodecConfigurationRecord vp_config;
306 if (!vp_config.ParseMP4(info.codec_config())) {
307 return Status(error::INTERNAL_ERROR,
308 "Unable to parse VP9 codec configuration");
311 mkvmuxer::Colour colour;
312 if (vp_config.matrix_coefficients() != AVCOL_SPC_UNSPECIFIED) {
313 colour.set_matrix_coefficients(vp_config.matrix_coefficients());
315 if (vp_config.transfer_characteristics() != AVCOL_TRC_UNSPECIFIED) {
316 colour.set_transfer_characteristics(vp_config.transfer_characteristics());
318 if (vp_config.color_primaries() != AVCOL_PRI_UNSPECIFIED) {
319 colour.set_primaries(vp_config.color_primaries());
321 if (!track->SetColour(colour)) {
322 return Status(error::INTERNAL_ERROR,
323 "Failed to setup color element for VPx streams");
326 std::vector<uint8_t> codec_config;
327 vp_config.WriteWebM(&codec_config);
328 if (!track->SetCodecPrivate(codec_config.data(), codec_config.size())) {
329 return Status(error::INTERNAL_ERROR,
330 "Private codec data required for VPx streams");
333 LOG(ERROR) <<
"Only VP8, VP9 and AV1 video codecs are supported in WebM.";
334 return Status(error::UNIMPLEMENTED,
335 "Only VP8, VP9 and AV1 video codecs are supported in WebM.");
338 track->set_uid(info.track_id());
339 if (!info.language().empty())
340 track->set_language(info.language().c_str());
341 track->set_type(mkvmuxer::Tracks::kVideo);
342 track->set_width(info.width());
343 track->set_height(info.height());
344 track->set_display_height(info.height());
345 track->set_display_width(info.width() * info.pixel_width() /
346 info.pixel_height());
350Status Segmenter::InitializeAudioTrack(
const AudioStreamInfo& info,
352 if (info.codec() == kCodecOpus) {
353 track->set_codec_id(mkvmuxer::Tracks::kOpusCodecId);
354 }
else if (info.codec() == kCodecVorbis) {
355 track->set_codec_id(mkvmuxer::Tracks::kVorbisCodecId);
357 LOG(ERROR) <<
"Only Vorbis and Opus audio codec are supported in WebM.";
358 return Status(error::UNIMPLEMENTED,
359 "Only Vorbis and Opus audio codecs are supported in WebM.");
361 if (!track->SetCodecPrivate(info.codec_config().data(),
362 info.codec_config().size())) {
363 return Status(error::INTERNAL_ERROR,
364 "Private codec data required for audio streams");
367 track->set_uid(info.track_id());
368 if (!info.language().empty())
369 track->set_language(info.language().c_str());
370 track->set_type(mkvmuxer::Tracks::kAudio);
371 track->set_sample_rate(info.sampling_frequency());
372 track->set_channels(info.num_channels());
373 track->set_seek_pre_roll(info.seek_preroll_ns());
374 track->set_codec_delay(info.codec_delay_ns());
378Status Segmenter::WriteFrame(
bool write_duration) {
382 mkvmuxer::Frame frame;
384 if (!frame.Init(prev_sample_->data(), prev_sample_->data_size())) {
385 return Status(error::MUXER_FAILURE,
386 "Error adding sample to segment: Frame::Init failed");
389 if (write_duration) {
391 BmffTimestampToNs(prev_sample_->duration(), time_scale_));
393 frame.set_is_key(prev_sample_->is_key_frame());
394 frame.set_timestamp(BmffTimestampToNs(prev_sample_->pts(), time_scale_));
395 frame.set_track_number(track_id_);
397 if (prev_sample_->side_data_size() > 0) {
398 uint64_t block_add_id;
401 CHECK_GT(prev_sample_->side_data_size(),
sizeof(block_add_id));
402 memcpy(&block_add_id, prev_sample_->side_data(),
sizeof(block_add_id));
403 if (!frame.AddAdditionalData(
404 prev_sample_->side_data() +
sizeof(block_add_id),
405 prev_sample_->side_data_size() -
sizeof(block_add_id),
408 error::MUXER_FAILURE,
409 "Error adding sample to segment: Frame::AddAditionalData Failed");
413 if (!prev_sample_->is_key_frame() && !frame.CanBeSimpleBlock()) {
414 frame.set_reference_block_timestamp(
415 BmffTimestampToNs(reference_frame_timestamp_, time_scale_));
420 if (cluster_->GetRelativeTimecode(NsToWebMTimecode(
421 frame.timestamp(), cluster_->timecode_scale())) < 0) {
422 const double segment_duration =
423 static_cast<double>(frame.timestamp() -
424 WebMTimecodeToNs(cluster_->timecode(),
425 cluster_->timecode_scale())) /
427 LOG(ERROR) <<
"Error adding sample to segment: segment too large, "
429 <<
" seconds. Please check your GOP size and segment duration.";
430 return Status(error::MUXER_FAILURE,
431 "Error adding sample to segment: segment too large");
434 if (!cluster_->AddFrame(&frame)) {
435 return Status(error::MUXER_FAILURE,
436 "Error adding sample to segment: Cluster::AddFrame failed");
442 reference_frame_timestamp_ = prev_sample_->pts();
All the methods that are virtual are virtual for mocking.