Shaka Packager SDK
Loading...
Searching...
No Matches
multi_segment_segmenter.cc
1// Copyright 2014 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/mp4/multi_segment_segmenter.h>
8
9#include <algorithm>
10#include <cstddef>
11#include <cstdint>
12#include <memory>
13#include <string>
14#include <utility>
15#include <vector>
16
17#include <absl/log/check.h>
18#include <absl/log/log.h>
19
20#include <packager/file.h>
21#include <packager/file/file_closer.h>
22#include <packager/macros/status.h>
23#include <packager/media/base/aes_cryptor.h>
24#include <packager/media/base/aes_encryptor.h>
25#include <packager/media/base/buffer_writer.h>
26#include <packager/media/base/fourccs.h>
27#include <packager/media/base/muxer_options.h>
28#include <packager/media/base/muxer_util.h>
29#include <packager/media/base/range.h>
30#include <packager/media/event/muxer_listener.h>
31#include <packager/media/formats/mp4/box_definitions.h>
32#include <packager/media/formats/mp4/key_frame_info.h>
33#include <packager/media/formats/mp4/segmenter.h>
34#include <packager/status.h>
35
36namespace shaka {
37namespace media {
38namespace mp4 {
39
40MultiSegmentSegmenter::MultiSegmentSegmenter(const MuxerOptions& options,
41 std::unique_ptr<FileType> ftyp,
42 std::unique_ptr<Movie> moov)
43 : Segmenter(options, std::move(ftyp), std::move(moov)),
44 styp_(new SegmentType) {
45 // Use the same brands for styp as ftyp.
46 styp_->major_brand = Segmenter::ftyp()->major_brand;
47 styp_->compatible_brands = Segmenter::ftyp()->compatible_brands;
48 // Replace 'cmfc' with 'cmfs' for CMAF segments compatibility.
49 std::replace(styp_->compatible_brands.begin(), styp_->compatible_brands.end(),
50 FOURCC_cmfc, FOURCC_cmfs);
51}
52
53MultiSegmentSegmenter::~MultiSegmentSegmenter() {}
54
55bool MultiSegmentSegmenter::GetInitRange(size_t* offset, size_t* size) {
56 VLOG(1) << "MultiSegmentSegmenter outputs init segment: "
57 << options().output_file_name;
58 return false;
59}
60
61bool MultiSegmentSegmenter::GetIndexRange(size_t* offset, size_t* size) {
62 VLOG(1) << "MultiSegmentSegmenter does not have index range.";
63 return false;
64}
65
66std::vector<Range> MultiSegmentSegmenter::GetSegmentRanges() {
67 VLOG(1) << "MultiSegmentSegmenter does not have media segment ranges.";
68 return std::vector<Range>();
69}
70
71Status MultiSegmentSegmenter::DoInitialize() {
72 return WriteInitSegment();
73}
74
75Status MultiSegmentSegmenter::DoFinalize() {
76 // Update init segment with media duration set.
77 RETURN_IF_ERROR(WriteInitSegment());
78 SetComplete();
79 return Status::OK;
80}
81
82Status MultiSegmentSegmenter::DoFinalizeSegment(int64_t segment_number) {
83 return WriteSegment(segment_number);
84}
85
86Status MultiSegmentSegmenter::WriteInitSegment() {
87 DCHECK(ftyp());
88 DCHECK(moov());
89 // Generate the output file with init segment.
90 std::unique_ptr<File, FileCloser> file(
91 File::Open(options().output_file_name.c_str(), "w"));
92 if (!file) {
93 return Status(error::FILE_FAILURE,
94 "Cannot open file for write " + options().output_file_name);
95 }
96 std::unique_ptr<BufferWriter> buffer(new BufferWriter);
97 ftyp()->Write(buffer.get());
98 moov()->Write(buffer.get());
99 return buffer->WriteToFile(file.get());
100}
101
102Status MultiSegmentSegmenter::WriteSegment(int64_t segment_number) {
103 DCHECK(sidx());
104 DCHECK(fragment_buffer());
105 DCHECK(styp_);
106
107 DCHECK(!sidx()->references.empty());
108 // earliest_presentation_time is the earliest presentation time of any access
109 // unit in the reference stream in the first subsegment.
110 sidx()->earliest_presentation_time =
111 sidx()->references[0].earliest_presentation_time;
112
113 std::unique_ptr<BufferWriter> buffer(new BufferWriter());
114 std::unique_ptr<File, FileCloser> file;
115 std::string file_name;
116 if (options().segment_template.empty()) {
117 // Append the segment to output file if segment template is not specified.
118 file_name = options().output_file_name.c_str();
119 file.reset(File::Open(file_name.c_str(), "a"));
120 if (!file) {
121 return Status(error::FILE_FAILURE, "Cannot open file for append " +
122 options().output_file_name);
123 }
124 } else {
125 file_name = GetSegmentName(options().segment_template,
126 sidx()->earliest_presentation_time,
127 segment_number, options().bandwidth);
128 file.reset(File::Open(file_name.c_str(), "w"));
129 if (!file) {
130 return Status(error::FILE_FAILURE,
131 "Cannot open file for write " + file_name);
132 }
133 styp_->Write(buffer.get());
134 }
135
136 if (options().mp4_params.generate_sidx_in_media_segments)
137 sidx()->Write(buffer.get());
138
139 const size_t segment_header_size = buffer->Size();
140 const size_t segment_size = segment_header_size + fragment_buffer()->Size();
141 DCHECK_NE(segment_size, 0u);
142
143 if (aes128_encryption_config().protection_scheme == kAes128ProtectionScheme) {
144 // Encrypt the whole segment (header + fragment) as one CBC stream.
145 // Per RFC 8216 §5.2, PKCS7 padding is required.
146 buffer->AppendBuffer(*fragment_buffer());
147 // AppendBuffer() above copies the accumulated fragment bytes into
148 // |buffer|; it does not drain |fragment_buffer()|. The non-AES-128
149 // branch below drains it as a side effect of WriteToFile() (see
150 // BufferWriter::WriteToFile, which clears its buffer after writing),
151 // so without this explicit Clear() the same bytes are copied again on
152 // every subsequent segment, causing unbounded cumulative growth across
153 // the whole asset. See
154 // https://github.com/shaka-project/shaka-packager/issues/1588.
155 fragment_buffer()->Clear();
156 AesCbcEncryptor encryptor(kPkcs5Padding, AesCryptor::kUseConstantIv);
157 if (!encryptor.InitializeWithIv(aes128_encryption_config().key,
158 aes128_encryption_config().constant_iv)) {
159 return Status(error::ENCRYPTION_FAILURE,
160 "AES-128: failed to initialize encryptor for MP4 segment.");
161 }
162 std::vector<uint8_t> plaintext(buffer->Buffer(),
163 buffer->Buffer() + buffer->Size());
164 std::vector<uint8_t> ciphertext;
165 if (!encryptor.Crypt(plaintext, &ciphertext)) {
166 return Status(error::ENCRYPTION_FAILURE,
167 "AES-128: segment encryption failed.");
168 }
169 buffer->Clear();
170 buffer->AppendVector(ciphertext);
171 RETURN_IF_ERROR(buffer->WriteToFile(file.get()));
172 } else {
173 RETURN_IF_ERROR(buffer->WriteToFile(file.get()));
174 if (muxer_listener()) {
175 for (const KeyFrameInfo& key_frame_info : key_frame_infos()) {
176 muxer_listener()->OnKeyFrame(
177 key_frame_info.timestamp,
178 segment_header_size + key_frame_info.start_byte_offset,
179 key_frame_info.size);
180 }
181 }
182 RETURN_IF_ERROR(fragment_buffer()->WriteToFile(file.get()));
183 }
184
185 // Close the file, which also does flushing, to make sure the file is written
186 // before manifest is updated.
187 if (!file.release()->Close()) {
188 return Status(
189 error::FILE_FAILURE,
190 "Cannot close file " + file_name +
191 ", possibly file permission issue or running out of disk space.");
192 }
193
194 int64_t segment_duration = 0;
195 // ISO/IEC 23009-1:2012: the value shall be identical to sum of the the
196 // values of all Subsegment_duration fields in the first ‘sidx’ box.
197 for (size_t i = 0; i < sidx()->references.size(); ++i)
198 segment_duration += sidx()->references[i].subsegment_duration;
199
200 UpdateProgress(segment_duration);
201 if (muxer_listener()) {
202 muxer_listener()->OnSampleDurationReady(sample_duration());
203 muxer_listener()->OnNewSegment(
204 file_name, sidx()->earliest_presentation_time, segment_duration,
205 segment_size, segment_number);
206 }
207
208 return Status::OK;
209}
210
211} // namespace mp4
212} // namespace media
213} // namespace shaka
All the methods that are virtual are virtual for mocking.