2023-12-01 17:32:19 +00:00
|
|
|
// Copyright 2016 Google LLC. All rights reserved.
|
2016-03-21 23:33:00 +00:00
|
|
|
//
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file or at
|
|
|
|
// https://developers.google.com/open-source/licenses/bsd
|
|
|
|
|
2023-12-01 17:32:19 +00:00
|
|
|
#include <packager/media/formats/mp2t/ts_muxer.h>
|
|
|
|
|
|
|
|
#include <absl/log/check.h>
|
2016-03-21 23:33:00 +00:00
|
|
|
|
2024-02-23 23:28:11 +00:00
|
|
|
#include <packager/macros/status.h>
|
|
|
|
#include <packager/media/base/muxer_util.h>
|
|
|
|
|
2016-05-20 21:19:33 +00:00
|
|
|
namespace shaka {
|
2016-03-21 23:33:00 +00:00
|
|
|
namespace media {
|
|
|
|
namespace mp2t {
|
|
|
|
|
2016-04-16 22:58:47 +00:00
|
|
|
namespace {
|
2021-08-04 18:56:44 +00:00
|
|
|
const int32_t kTsTimescale = 90000;
|
2016-04-16 22:58:47 +00:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
TsMuxer::TsMuxer(const MuxerOptions& muxer_options) : Muxer(muxer_options) {}
|
2016-03-21 23:33:00 +00:00
|
|
|
TsMuxer::~TsMuxer() {}
|
|
|
|
|
2017-02-21 18:36:50 +00:00
|
|
|
Status TsMuxer::InitializeMuxer() {
|
2016-03-21 23:33:00 +00:00
|
|
|
if (streams().size() > 1u)
|
|
|
|
return Status(error::MUXER_FAILURE, "Cannot handle more than one streams.");
|
2016-04-16 22:58:47 +00:00
|
|
|
|
2024-02-23 23:28:11 +00:00
|
|
|
if (options().segment_template.empty()) {
|
|
|
|
const std::string& file_name = options().output_file_name;
|
|
|
|
DCHECK(!file_name.empty());
|
|
|
|
output_file_.reset(File::Open(file_name.c_str(), "w"));
|
|
|
|
if (!output_file_) {
|
|
|
|
return Status(error::FILE_FAILURE,
|
|
|
|
"Cannot open file for write " + file_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-16 22:58:47 +00:00
|
|
|
segmenter_.reset(new TsSegmenter(options(), muxer_listener()));
|
2017-03-11 02:49:55 +00:00
|
|
|
Status status = segmenter_->Initialize(*streams()[0]);
|
2016-04-16 22:58:47 +00:00
|
|
|
FireOnMediaStartEvent();
|
|
|
|
return status;
|
2016-03-21 23:33:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Status TsMuxer::Finalize() {
|
2016-04-16 22:58:47 +00:00
|
|
|
FireOnMediaEndEvent();
|
|
|
|
return segmenter_->Finalize();
|
2016-03-21 23:33:00 +00:00
|
|
|
}
|
|
|
|
|
2020-07-30 21:26:45 +00:00
|
|
|
Status TsMuxer::AddMediaSample(size_t stream_id, const MediaSample& sample) {
|
2017-03-03 00:10:30 +00:00
|
|
|
DCHECK_EQ(stream_id, 0u);
|
2024-02-28 23:53:06 +00:00
|
|
|
|
|
|
|
// The duration of the first sample may have been adjusted, so use
|
|
|
|
// the duration of the second sample instead.
|
2020-09-02 21:27:02 +00:00
|
|
|
if (num_samples_ < 2) {
|
2020-07-30 21:26:45 +00:00
|
|
|
sample_durations_[num_samples_] =
|
|
|
|
sample.duration() * kTsTimescale / streams().front()->time_scale();
|
|
|
|
if (num_samples_ == 1 && muxer_listener())
|
2020-09-02 21:27:02 +00:00
|
|
|
muxer_listener()->OnSampleDurationReady(sample_durations_[num_samples_]);
|
|
|
|
num_samples_++;
|
2020-07-30 21:26:45 +00:00
|
|
|
}
|
2016-04-16 22:58:47 +00:00
|
|
|
return segmenter_->AddSample(sample);
|
|
|
|
}
|
|
|
|
|
2017-03-03 00:10:30 +00:00
|
|
|
Status TsMuxer::FinalizeSegment(size_t stream_id,
|
2017-09-12 17:24:24 +00:00
|
|
|
const SegmentInfo& segment_info) {
|
2017-03-03 00:10:30 +00:00
|
|
|
DCHECK_EQ(stream_id, 0u);
|
2024-02-23 23:28:11 +00:00
|
|
|
|
|
|
|
if (segment_info.is_subsegment)
|
|
|
|
return Status::OK;
|
|
|
|
|
|
|
|
Status s = segmenter_->FinalizeSegment(segment_info.start_timestamp,
|
|
|
|
segment_info.duration);
|
|
|
|
if (!s.ok())
|
|
|
|
return s;
|
|
|
|
if (!segmenter_->segment_started())
|
|
|
|
return Status::OK;
|
|
|
|
|
|
|
|
int64_t segment_start_timestamp = segmenter_->segment_start_timestamp();
|
|
|
|
|
|
|
|
std::string segment_path =
|
|
|
|
options().segment_template.empty()
|
|
|
|
? options().output_file_name
|
|
|
|
: GetSegmentName(options().segment_template, segment_start_timestamp,
|
|
|
|
segment_number_++, options().bandwidth);
|
|
|
|
|
|
|
|
const int64_t file_size = segmenter_->segment_buffer()->Size();
|
|
|
|
|
|
|
|
RETURN_IF_ERROR(WriteSegment(segment_path, segmenter_->segment_buffer()));
|
|
|
|
|
|
|
|
total_duration_ += segment_info.duration;
|
|
|
|
|
|
|
|
if (muxer_listener()) {
|
|
|
|
muxer_listener()->OnNewSegment(
|
|
|
|
segment_path,
|
|
|
|
segment_info.start_timestamp * segmenter_->timescale() +
|
|
|
|
segmenter_->transport_stream_timestamp_offset(),
|
|
|
|
segment_info.duration * segmenter_->timescale(), file_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
segmenter_->set_segment_started(false);
|
|
|
|
|
|
|
|
return Status::OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
Status TsMuxer::WriteSegment(const std::string& segment_path,
|
|
|
|
BufferWriter* segment_buffer) {
|
|
|
|
std::unique_ptr<File, FileCloser> file;
|
|
|
|
|
|
|
|
if (output_file_) {
|
|
|
|
// This is in single segment mode.
|
|
|
|
Range range;
|
|
|
|
range.start = media_ranges_.subsegment_ranges.empty()
|
|
|
|
? 0
|
|
|
|
: (media_ranges_.subsegment_ranges.back().end + 1);
|
|
|
|
range.end = range.start + segment_buffer->Size() - 1;
|
|
|
|
media_ranges_.subsegment_ranges.push_back(range);
|
|
|
|
} else {
|
|
|
|
file.reset(File::Open(segment_path.c_str(), "w"));
|
|
|
|
if (!file) {
|
|
|
|
return Status(error::FILE_FAILURE,
|
|
|
|
"Cannot open file for write " + segment_path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
RETURN_IF_ERROR(segment_buffer->WriteToFile(output_file_ ? output_file_.get()
|
|
|
|
: file.get()));
|
|
|
|
|
|
|
|
if (file)
|
|
|
|
RETURN_IF_ERROR(CloseFile(std::move(file)));
|
|
|
|
return Status::OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
Status TsMuxer::CloseFile(std::unique_ptr<File, FileCloser> file) {
|
|
|
|
std::string file_name = file->file_name();
|
|
|
|
if (!file.release()->Close()) {
|
|
|
|
return Status(
|
|
|
|
error::FILE_FAILURE,
|
|
|
|
"Cannot close file " + file_name +
|
|
|
|
", possibly file permission issue or running out of disk space.");
|
|
|
|
}
|
|
|
|
return Status::OK;
|
2017-02-24 01:17:47 +00:00
|
|
|
}
|
|
|
|
|
2016-04-16 22:58:47 +00:00
|
|
|
void TsMuxer::FireOnMediaStartEvent() {
|
|
|
|
if (!muxer_listener())
|
|
|
|
return;
|
2017-02-21 18:36:50 +00:00
|
|
|
muxer_listener()->OnMediaStart(options(), *streams().front(), kTsTimescale,
|
2018-05-17 01:21:18 +00:00
|
|
|
MuxerListener::kContainerMpeg2ts);
|
2016-04-16 22:58:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void TsMuxer::FireOnMediaEndEvent() {
|
|
|
|
if (!muxer_listener())
|
|
|
|
return;
|
|
|
|
|
2024-02-23 23:28:11 +00:00
|
|
|
muxer_listener()->OnMediaEnd(media_ranges_, total_duration_);
|
2016-03-21 23:33:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace mp2t
|
|
|
|
} // namespace media
|
2016-05-20 21:19:33 +00:00
|
|
|
} // namespace shaka
|