7#include <packager/media/formats/mp2t/ts_muxer.h>
11#include <absl/log/check.h>
13#include <packager/macros/status.h>
14#include <packager/media/base/aes_encryptor.h>
15#include <packager/media/base/fourccs.h>
16#include <packager/media/base/muxer_util.h>
23const int32_t kTsTimescale = 90000;
26TsMuxer::TsMuxer(
const MuxerOptions& muxer_options) : Muxer(muxer_options) {}
29Status TsMuxer::InitializeMuxer() {
30 if (streams().size() > 1u)
31 return Status(error::MUXER_FAILURE,
"Cannot handle more than one streams.");
33 if (options().segment_template.empty()) {
34 const std::string& file_name = options().output_file_name;
35 DCHECK(!file_name.empty());
36 output_file_.reset(File::Open(file_name.c_str(),
"w"));
38 return Status(error::FILE_FAILURE,
39 "Cannot open file for write " + file_name);
43 segmenter_.reset(
new TsSegmenter(options(), muxer_listener()));
44 Status status = segmenter_->Initialize(*streams()[0]);
47 const auto& stream = *streams()[0];
48 if (stream.is_encrypted() &&
49 stream.encryption_config().protection_scheme == kAes128ProtectionScheme) {
50 aes128_encryption_config_ = stream.encryption_config();
53 FireOnMediaStartEvent();
57Status TsMuxer::Finalize() {
58 FireOnMediaEndEvent();
59 return segmenter_->Finalize();
62Status TsMuxer::AddMediaSample(
size_t stream_id,
const MediaSample& sample) {
63 DCHECK_EQ(stream_id, 0u);
67 if (num_samples_ < 2) {
68 sample_durations_[num_samples_] =
69 sample.duration() * kTsTimescale / streams().front()->time_scale();
70 if (num_samples_ == 1 && muxer_listener())
71 muxer_listener()->OnSampleDurationReady(sample_durations_[num_samples_]);
74 return segmenter_->AddSample(sample);
77Status TsMuxer::FinalizeSegment(
size_t stream_id,
78 const SegmentInfo& segment_info) {
79 DCHECK_EQ(stream_id, 0u);
81 if (segment_info.is_subsegment)
84 Status s = segmenter_->FinalizeSegment(segment_info.start_timestamp,
85 segment_info.duration);
88 if (!segmenter_->segment_started())
91 int64_t segment_start_timestamp = segmenter_->segment_start_timestamp();
93 std::string segment_path =
94 options().segment_template.empty()
95 ? options().output_file_name
96 : GetSegmentName(options().segment_template, segment_start_timestamp,
97 segment_info.segment_number, options().bandwidth);
99 const int64_t file_size = segmenter_->segment_buffer()->Size();
101 EncryptSegmentIfNeeded(segmenter_->segment_buffer());
103 RETURN_IF_ERROR(WriteSegment(segment_path, segmenter_->segment_buffer()));
105 total_duration_ += segment_info.duration;
107 if (muxer_listener()) {
108 muxer_listener()->OnNewSegment(
110 segment_info.start_timestamp * segmenter_->timescale() +
111 segmenter_->transport_stream_timestamp_offset(),
112 segment_info.duration * segmenter_->timescale(), file_size,
113 segment_info.segment_number);
116 segmenter_->set_segment_started(
false);
121void TsMuxer::EncryptSegmentIfNeeded(BufferWriter* segment_buffer) {
122 if (aes128_encryption_config_.protection_scheme != kAes128ProtectionScheme)
124 if (segment_buffer->Size() == 0)
131 AesCbcEncryptor encryptor(kPkcs5Padding, AesCryptor::kUseConstantIv);
132 if (!encryptor.InitializeWithIv(aes128_encryption_config_.key,
133 aes128_encryption_config_.constant_iv)) {
134 LOG(ERROR) <<
"AES-128: failed to initialize encryptor for segment.";
138 std::vector<uint8_t> plaintext(
139 segment_buffer->Buffer(),
140 segment_buffer->Buffer() + segment_buffer->Size());
141 std::vector<uint8_t> ciphertext;
142 if (!encryptor.Crypt(plaintext, &ciphertext)) {
143 LOG(ERROR) <<
"AES-128: encryption failed for segment.";
146 segment_buffer->Clear();
147 segment_buffer->AppendVector(ciphertext);
150Status TsMuxer::WriteSegment(
const std::string& segment_path,
151 BufferWriter* segment_buffer) {
152 std::unique_ptr<File, FileCloser> file;
157 range.start = media_ranges_.subsegment_ranges.empty()
159 : (media_ranges_.subsegment_ranges.back().end + 1);
160 range.end = range.start + segment_buffer->Size() - 1;
161 media_ranges_.subsegment_ranges.push_back(range);
163 file.reset(File::Open(segment_path.c_str(),
"w"));
165 return Status(error::FILE_FAILURE,
166 "Cannot open file for write " + segment_path);
170 RETURN_IF_ERROR(segment_buffer->WriteToFile(output_file_ ? output_file_.get()
174 RETURN_IF_ERROR(CloseFile(std::move(file)));
178Status TsMuxer::CloseFile(std::unique_ptr<File, FileCloser> file) {
179 std::string file_name = file->file_name();
180 if (!file.release()->Close()) {
183 "Cannot close file " + file_name +
184 ", possibly file permission issue or running out of disk space.");
189void TsMuxer::FireOnMediaStartEvent() {
190 if (!muxer_listener())
192 muxer_listener()->OnMediaStart(options(), *streams().front(), kTsTimescale,
193 MuxerListener::kContainerMpeg2ts);
196void TsMuxer::FireOnMediaEndEvent() {
197 if (!muxer_listener())
200 muxer_listener()->OnMediaEnd(media_ranges_, total_duration_);
All the methods that are virtual are virtual for mocking.