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_
85 ? Status::OK
86 : Status(error::FILE_FAILURE,
87 "Cannot open file to write " + temp_file_name_);
88}
89
90Status SingleSegmentSegmenter::DoFinalize() {
91 DCHECK(temp_file_);
92 DCHECK(ftyp());
93 DCHECK(moov());
94 DCHECK(vod_sidx_);
95
96 // Close the temp file to prepare for reading later.
97 if (!temp_file_.release()->Close()) {
98 return Status(
99 error::FILE_FAILURE,
100 "Cannot close the temp file " + temp_file_name_ +
101 ", possibly file permission issue or running out of disk space.");
102 }
103
104 std::unique_ptr<File, FileCloser> file(
105 File::Open(options().output_file_name.c_str(), "w"));
106 if (file == NULL) {
107 return Status(error::FILE_FAILURE,
108 "Cannot open file to write " + options().output_file_name);
109 }
110
111 LOG(INFO) << "Update media header (moov) and rewrite the file to '"
112 << options().output_file_name << "'.";
113
114 // Write ftyp, moov and sidx to output file.
115 std::unique_ptr<BufferWriter> buffer(new BufferWriter());
116 ftyp()->Write(buffer.get());
117 moov()->Write(buffer.get());
118
119 if (options().mp4_params.generate_sidx_in_media_segments)
120 vod_sidx_->Write(buffer.get());
121
122 Status status = buffer->WriteToFile(file.get());
123 if (!status.ok())
124 return status;
125
126 // Load the temp file and write to output file.
127 std::unique_ptr<File, FileCloser> temp_file(
128 File::Open(temp_file_name_.c_str(), "r"));
129 if (temp_file == NULL) {
130 return Status(error::FILE_FAILURE,
131 "Cannot open file to read " + temp_file_name_);
132 }
133
134 // The target of 2nd stage of single segment segmentation.
135 const uint64_t re_segment_progress_target = progress_target() * 0.5;
136
137 const int kBufSize = 0x200000; // 2MB.
138 std::unique_ptr<uint8_t[]> buf(new uint8_t[kBufSize]);
139 while (true) {
140 int64_t size = temp_file->Read(buf.get(), kBufSize);
141 if (size == 0) {
142 break;
143 } else if (size < 0) {
144 return Status(error::FILE_FAILURE,
145 "Failed to read file " + temp_file_name_);
146 }
147 int64_t size_written = file->Write(buf.get(), size);
148 if (size_written != size) {
149 return Status(error::FILE_FAILURE,
150 "Failed to write file " + options().output_file_name);
151 }
152 UpdateProgress(static_cast<double>(size) / temp_file->Size() *
153 re_segment_progress_target);
154 }
155 if (!temp_file.release()->Close()) {
156 return Status(error::FILE_FAILURE, "Cannot close the temp file " +
157 temp_file_name_ + " after reading.");
158 }
159 if (!file.release()->Close()) {
160 return Status(
161 error::FILE_FAILURE,
162 "Cannot close file " + options().output_file_name +
163 ", possibly file permission issue or running out of disk space.");
164 }
165 SetComplete();
166 return Status::OK;
167}
168
169Status SingleSegmentSegmenter::DoFinalizeSegment(int64_t segment_number) {
170 DCHECK(sidx());
171 DCHECK(fragment_buffer());
172 // sidx() contains pre-generated segment references with one reference per
173 // fragment. In VOD, this segment is converted into a subsegment, i.e. one
174 // reference, which contains all the fragments in sidx().
175 std::vector<SegmentReference>& refs = sidx()->references;
176 SegmentReference& vod_ref = refs[0];
177 int64_t first_sap_time =
178 refs[0].sap_delta_time + refs[0].earliest_presentation_time;
179 for (uint32_t i = 1; i < refs.size(); ++i) {
180 vod_ref.referenced_size += refs[i].referenced_size;
181 // NOTE: We calculate subsegment duration based on the total duration of
182 // this subsegment instead of subtracting earliest_presentation_time as
183 // indicated in the spec.
184 vod_ref.subsegment_duration += refs[i].subsegment_duration;
185 vod_ref.earliest_presentation_time = std::min(
186 vod_ref.earliest_presentation_time, refs[i].earliest_presentation_time);
187
188 if (vod_ref.sap_type == SegmentReference::TypeUnknown &&
189 refs[i].sap_type != SegmentReference::TypeUnknown) {
190 vod_ref.sap_type = refs[i].sap_type;
191 first_sap_time =
192 refs[i].sap_delta_time + refs[i].earliest_presentation_time;
193 }
194 }
195 // Calculate sap delta time w.r.t. earliest_presentation_time.
196 if (vod_ref.sap_type != SegmentReference::TypeUnknown) {
197 vod_ref.sap_delta_time =
198 first_sap_time - vod_ref.earliest_presentation_time;
199 }
200
201 // Create segment if it does not exist yet.
202 if (vod_sidx_ == NULL) {
203 vod_sidx_.reset(new SegmentIndex());
204 vod_sidx_->reference_id = sidx()->reference_id;
205 vod_sidx_->timescale = sidx()->timescale;
206 vod_sidx_->earliest_presentation_time = vod_ref.earliest_presentation_time;
207 }
208 vod_sidx_->references.push_back(vod_ref);
209
210 if (muxer_listener()) {
211 for (const KeyFrameInfo& key_frame_info : key_frame_infos()) {
212 // Unlike multisegment-segmenter, there is no (sub)segment header (styp,
213 // sidx), so this is already the offset within the (sub)segment.
214 muxer_listener()->OnKeyFrame(key_frame_info.timestamp,
215 key_frame_info.start_byte_offset,
216 key_frame_info.size);
217 }
218 }
219 // Append fragment buffer to temp file.
220 size_t segment_size = fragment_buffer()->Size();
221 Status status = fragment_buffer()->WriteToFile(temp_file_.get());
222 if (!status.ok()) 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