Shaka Packager SDK
Loading...
Searching...
No Matches
single_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/single_segment_segmenter.h>
8
9#include <algorithm>
10
11#include <absl/log/check.h>
12
13#include <packager/file/file_util.h>
14#include <packager/media/base/buffer_writer.h>
15#include <packager/media/base/muxer_options.h>
16#include <packager/media/event/progress_listener.h>
17#include <packager/media/formats/mp4/key_frame_info.h>
18
19namespace shaka {
20namespace media {
21namespace mp4 {
22
23SingleSegmentSegmenter::SingleSegmentSegmenter(const MuxerOptions& options,
24 std::unique_ptr<FileType> ftyp,
25 std::unique_ptr<Movie> moov)
26 : Segmenter(options, std::move(ftyp), std::move(moov)) {}
27
28SingleSegmentSegmenter::~SingleSegmentSegmenter() {
29 if (temp_file_)
30 temp_file_.release()->Close();
31 if (!temp_file_name_.empty()) {
32 if (!File::Delete(temp_file_name_.c_str()))
33 LOG(ERROR) << "Unable to delete temporary file " << temp_file_name_;
34 }
35}
36
37bool SingleSegmentSegmenter::GetInitRange(size_t* offset, size_t* size) {
38 // In Finalize, ftyp and moov gets written first so offset must be 0.
39 *offset = 0;
40 *size = ftyp()->ComputeSize() + moov()->ComputeSize();
41 return true;
42}
43
44bool SingleSegmentSegmenter::GetIndexRange(size_t* offset, size_t* size) {
45 // Index range is right after init range so the offset must be the size of
46 // ftyp and moov.
47 *offset = ftyp()->ComputeSize() + moov()->ComputeSize();
48 *size = options().mp4_params.generate_sidx_in_media_segments
49 ? vod_sidx_->ComputeSize()
50 : 0;
51 return true;
52}
53
54std::vector<Range> SingleSegmentSegmenter::GetSegmentRanges() {
55 std::vector<Range> ranges;
56 uint64_t next_offset = ftyp()->ComputeSize() + moov()->ComputeSize() +
57 (options().mp4_params.generate_sidx_in_media_segments
58 ? vod_sidx_->ComputeSize()
59 : 0) +
60 vod_sidx_->first_offset;
61 for (const SegmentReference& segment_reference : vod_sidx_->references) {
62 Range r;
63 r.start = next_offset;
64 // Ranges are inclusive, so -1 to the size.
65 r.end = r.start + segment_reference.referenced_size - 1;
66 next_offset = r.end + 1;
67 ranges.push_back(r);
68 }
69 return ranges;
70}
71
72Status SingleSegmentSegmenter::DoInitialize() {
73 // Single segment segmentation involves two stages:
74 // Stage 1: Create media subsegments from media samples
75 // Stage 2: Update media header (moov) which involves copying of media
76 // subsegments
77 // Assumes stage 2 takes similar amount of time as stage 1. The previous
78 // progress_target was set for stage 1. Times two to account for stage 2.
79 set_progress_target(progress_target() * 2);
80
81 if (!TempFilePath(options().temp_dir, &temp_file_name_))
82 return Status(error::FILE_FAILURE, "Unable to create temporary file.");
83 temp_file_.reset(File::Open(temp_file_name_.c_str(), "w"));
84 return temp_file_ ? Status::OK
85 : Status(error::FILE_FAILURE,
86 "Cannot open file to write " + temp_file_name_);
87}
88
89Status SingleSegmentSegmenter::DoFinalize() {
90 DCHECK(temp_file_);
91 DCHECK(ftyp());
92 DCHECK(moov());
93 DCHECK(vod_sidx_);
94
95 // Close the temp file to prepare for reading later.
96 if (!temp_file_.release()->Close()) {
97 return Status(
98 error::FILE_FAILURE,
99 "Cannot close the temp file " + temp_file_name_ +
100 ", possibly file permission issue or running out of disk space.");
101 }
102
103 std::unique_ptr<File, FileCloser> file(
104 File::Open(options().output_file_name.c_str(), "w"));
105 if (file == NULL) {
106 return Status(error::FILE_FAILURE,
107 "Cannot open file to write " + options().output_file_name);
108 }
109
110 LOG(INFO) << "Update media header (moov) and rewrite the file to '"
111 << options().output_file_name << "'.";
112
113 // Write ftyp, moov and sidx to output file.
114 std::unique_ptr<BufferWriter> buffer(new BufferWriter());
115 ftyp()->Write(buffer.get());
116 moov()->Write(buffer.get());
117
118 if (options().mp4_params.generate_sidx_in_media_segments)
119 vod_sidx_->Write(buffer.get());
120
121 Status status = buffer->WriteToFile(file.get());
122 if (!status.ok())
123 return status;
124
125 // Load the temp file and write to output file.
126 std::unique_ptr<File, FileCloser> temp_file(
127 File::Open(temp_file_name_.c_str(), "r"));
128 if (temp_file == NULL) {
129 return Status(error::FILE_FAILURE,
130 "Cannot open file to read " + temp_file_name_);
131 }
132
133 // The target of 2nd stage of single segment segmentation.
134 const uint64_t re_segment_progress_target = progress_target() * 0.5;
135
136 const int kBufSize = 0x200000; // 2MB.
137 std::unique_ptr<uint8_t[]> buf(new uint8_t[kBufSize]);
138 while (true) {
139 int64_t size = temp_file->Read(buf.get(), kBufSize);
140 if (size == 0) {
141 break;
142 } else if (size < 0) {
143 return Status(error::FILE_FAILURE,
144 "Failed to read file " + temp_file_name_);
145 }
146 int64_t size_written = file->Write(buf.get(), size);
147 if (size_written != size) {
148 return Status(error::FILE_FAILURE,
149 "Failed to write file " + options().output_file_name);
150 }
151 UpdateProgress(static_cast<double>(size) / temp_file->Size() *
152 re_segment_progress_target);
153 }
154 if (!temp_file.release()->Close()) {
155 return Status(error::FILE_FAILURE, "Cannot close the temp file " +
156 temp_file_name_ + " after reading.");
157 }
158 if (!file.release()->Close()) {
159 return Status(
160 error::FILE_FAILURE,
161 "Cannot close file " + options().output_file_name +
162 ", possibly file permission issue or running out of disk space.");
163 }
164 SetComplete();
165 return Status::OK;
166}
167
168Status SingleSegmentSegmenter::DoFinalizeSegment(int64_t segment_number) {
169 DCHECK(sidx());
170 DCHECK(fragment_buffer());
171 // sidx() contains pre-generated segment references with one reference per
172 // fragment. In VOD, this segment is converted into a subsegment, i.e. one
173 // reference, which contains all the fragments in sidx().
174 std::vector<SegmentReference>& refs = sidx()->references;
175 SegmentReference& vod_ref = refs[0];
176 int64_t first_sap_time =
177 refs[0].sap_delta_time + refs[0].earliest_presentation_time;
178 for (uint32_t i = 1; i < refs.size(); ++i) {
179 vod_ref.referenced_size += refs[i].referenced_size;
180 // NOTE: We calculate subsegment duration based on the total duration of
181 // this subsegment instead of subtracting earliest_presentation_time as
182 // indicated in the spec.
183 vod_ref.subsegment_duration += refs[i].subsegment_duration;
184 vod_ref.earliest_presentation_time = std::min(
185 vod_ref.earliest_presentation_time, refs[i].earliest_presentation_time);
186
187 if (vod_ref.sap_type == SegmentReference::TypeUnknown &&
188 refs[i].sap_type != SegmentReference::TypeUnknown) {
189 vod_ref.sap_type = refs[i].sap_type;
190 first_sap_time =
191 refs[i].sap_delta_time + refs[i].earliest_presentation_time;
192 }
193 }
194 // Calculate sap delta time w.r.t. earliest_presentation_time.
195 if (vod_ref.sap_type != SegmentReference::TypeUnknown) {
196 vod_ref.sap_delta_time =
197 first_sap_time - vod_ref.earliest_presentation_time;
198 }
199
200 // Create segment if it does not exist yet.
201 if (vod_sidx_ == NULL) {
202 vod_sidx_.reset(new SegmentIndex());
203 vod_sidx_->reference_id = sidx()->reference_id;
204 vod_sidx_->timescale = sidx()->timescale;
205 vod_sidx_->earliest_presentation_time = vod_ref.earliest_presentation_time;
206 }
207 vod_sidx_->references.push_back(vod_ref);
208
209 if (muxer_listener()) {
210 for (const KeyFrameInfo& key_frame_info : key_frame_infos()) {
211 // Unlike multisegment-segmenter, there is no (sub)segment header (styp,
212 // sidx), so this is already the offset within the (sub)segment.
213 muxer_listener()->OnKeyFrame(key_frame_info.timestamp,
214 key_frame_info.start_byte_offset,
215 key_frame_info.size);
216 }
217 }
218 // Append fragment buffer to temp file.
219 size_t segment_size = fragment_buffer()->Size();
220 Status status = fragment_buffer()->WriteToFile(temp_file_.get());
221 if (!status.ok())
222 return status;
223
224 UpdateProgress(vod_ref.subsegment_duration);
225 if (muxer_listener()) {
226 muxer_listener()->OnSampleDurationReady(sample_duration());
227 muxer_listener()->OnNewSegment(
228 options().output_file_name, vod_ref.earliest_presentation_time,
229 vod_ref.subsegment_duration, segment_size, segment_number);
230 }
231 return Status::OK;
232}
233
234} // namespace mp4
235} // namespace media
236} // namespace shaka
All the methods that are virtual are virtual for mocking.
bool TempFilePath(const std::string &temp_dir, std::string *temp_file_path)
Definition file_util.cc:43