Shaka Packager SDK
Loading...
Searching...
No Matches
ts_muxer.cc
1// Copyright 2016 Google LLC. 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/mp2t/ts_muxer.h>
8
9#include <vector>
10
11#include <absl/log/check.h>
12
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>
17
18namespace shaka {
19namespace media {
20namespace mp2t {
21
22namespace {
23const int32_t kTsTimescale = 90000;
24} // namespace
25
26TsMuxer::TsMuxer(const MuxerOptions& muxer_options) : Muxer(muxer_options) {}
27TsMuxer::~TsMuxer() {}
28
29Status TsMuxer::InitializeMuxer() {
30 if (streams().size() > 1u)
31 return Status(error::MUXER_FAILURE, "Cannot handle more than one streams.");
32
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"));
37 if (!output_file_) {
38 return Status(error::FILE_FAILURE,
39 "Cannot open file for write " + file_name);
40 }
41 }
42
43 segmenter_.reset(new TsSegmenter(options(), muxer_listener()));
44 Status status = segmenter_->Initialize(*streams()[0]);
45
46 // Capture AES-128 encryption config once so FinalizeSegment can encrypt.
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();
51 }
52
53 FireOnMediaStartEvent();
54 return status;
55}
56
57Status TsMuxer::Finalize() {
58 FireOnMediaEndEvent();
59 return segmenter_->Finalize();
60}
61
62Status TsMuxer::AddMediaSample(size_t stream_id, const MediaSample& sample) {
63 DCHECK_EQ(stream_id, 0u);
64
65 // The duration of the first sample may have been adjusted, so use
66 // the duration of the second sample instead.
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_]);
72 num_samples_++;
73 }
74 return segmenter_->AddSample(sample);
75}
76
77Status TsMuxer::FinalizeSegment(size_t stream_id,
78 const SegmentInfo& segment_info) {
79 DCHECK_EQ(stream_id, 0u);
80
81 if (segment_info.is_subsegment)
82 return Status::OK;
83
84 Status s = segmenter_->FinalizeSegment(segment_info.start_timestamp,
85 segment_info.duration);
86 if (!s.ok())
87 return s;
88 if (!segmenter_->segment_started())
89 return Status::OK;
90
91 int64_t segment_start_timestamp = segmenter_->segment_start_timestamp();
92
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);
98
99 const int64_t file_size = segmenter_->segment_buffer()->Size();
100
101 EncryptSegmentIfNeeded(segmenter_->segment_buffer());
102
103 RETURN_IF_ERROR(WriteSegment(segment_path, segmenter_->segment_buffer()));
104
105 total_duration_ += segment_info.duration;
106
107 if (muxer_listener()) {
108 muxer_listener()->OnNewSegment(
109 segment_path,
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);
114 }
115
116 segmenter_->set_segment_started(false);
117
118 return Status::OK;
119}
120
121void TsMuxer::EncryptSegmentIfNeeded(BufferWriter* segment_buffer) {
122 if (aes128_encryption_config_.protection_scheme != kAes128ProtectionScheme)
123 return;
124 if (segment_buffer->Size() == 0)
125 return;
126
127 // AES-128 HLS: encrypt the entire TS segment as one CBC stream.
128 // Per RFC 8216 ยง5.2, PKCS7 padding is required; TS packet size (188 bytes)
129 // is not a multiple of 16, so segments are not guaranteed to be
130 // block-aligned.
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.";
135 return;
136 }
137
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.";
144 return;
145 }
146 segment_buffer->Clear();
147 segment_buffer->AppendVector(ciphertext);
148}
149
150Status TsMuxer::WriteSegment(const std::string& segment_path,
151 BufferWriter* segment_buffer) {
152 std::unique_ptr<File, FileCloser> file;
153
154 if (output_file_) {
155 // This is in single segment mode.
156 Range range;
157 range.start = media_ranges_.subsegment_ranges.empty()
158 ? 0
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);
162 } else {
163 file.reset(File::Open(segment_path.c_str(), "w"));
164 if (!file) {
165 return Status(error::FILE_FAILURE,
166 "Cannot open file for write " + segment_path);
167 }
168 }
169
170 RETURN_IF_ERROR(segment_buffer->WriteToFile(output_file_ ? output_file_.get()
171 : file.get()));
172
173 if (file)
174 RETURN_IF_ERROR(CloseFile(std::move(file)));
175 return Status::OK;
176}
177
178Status TsMuxer::CloseFile(std::unique_ptr<File, FileCloser> file) {
179 std::string file_name = file->file_name();
180 if (!file.release()->Close()) {
181 return Status(
182 error::FILE_FAILURE,
183 "Cannot close file " + file_name +
184 ", possibly file permission issue or running out of disk space.");
185 }
186 return Status::OK;
187}
188
189void TsMuxer::FireOnMediaStartEvent() {
190 if (!muxer_listener())
191 return;
192 muxer_listener()->OnMediaStart(options(), *streams().front(), kTsTimescale,
193 MuxerListener::kContainerMpeg2ts);
194}
195
196void TsMuxer::FireOnMediaEndEvent() {
197 if (!muxer_listener())
198 return;
199
200 muxer_listener()->OnMediaEnd(media_ranges_, total_duration_);
201}
202
203} // namespace mp2t
204} // namespace media
205} // namespace shaka
All the methods that are virtual are virtual for mocking.