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