Shaka Packager SDK
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 
19 namespace shaka {
20 namespace media {
21 namespace mp4 {
22 
23 SingleSegmentSegmenter::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 
28 SingleSegmentSegmenter::~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 
37 bool 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 
44 bool 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 
54 std::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 
72 Status 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 
90 Status 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 
169 Status 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.
Definition: crypto_flags.cc:66
bool TempFilePath(const std::string &temp_dir, std::string *temp_file_path)
Definition: file_util.cc:43