Shaka Packager SDK
two_pass_single_segment_segmenter.cc
1 // Copyright 2015 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/webm/two_pass_single_segment_segmenter.h>
8 
9 #include <algorithm>
10 
11 #include <absl/log/check.h>
12 #include <mkvmuxer/mkvmuxer.h>
13 #include <mkvmuxer/mkvmuxerutil.h>
14 
15 #include <packager/file/file_util.h>
16 #include <packager/media/base/media_sample.h>
17 #include <packager/media/base/muxer_options.h>
18 #include <packager/media/base/stream_info.h>
19 
20 namespace shaka {
21 namespace media {
22 namespace webm {
23 namespace {
24 // Cues will be inserted before clusters. All clusters will be shifted down by
25 // the size of cues. However, cluster positions affect the size of cues. This
26 // function adjusts cues size iteratively until it is stable.
27 // Returns the size of updated Cues.
28 uint64_t UpdateCues(mkvmuxer::Cues* cues) {
29  uint64_t cues_size = cues->Size();
30  uint64_t adjustment = cues_size;
31  while (adjustment != 0) {
32  for (int i = 0; i < cues->cue_entries_size(); ++i) {
33  mkvmuxer::CuePoint* cue = cues->GetCueByIndex(i);
34  cue->set_cluster_pos(cue->cluster_pos() + adjustment);
35  }
36  uint64_t new_cues_size = cues->Size();
37  DCHECK_LE(cues_size, new_cues_size);
38  adjustment = new_cues_size - cues_size;
39  cues_size = new_cues_size;
40  }
41  return cues_size;
42 }
43 
44 // Skips a given number of bytes in a file by reading. This allows
45 // forward-seeking in non-seekable files.
46 bool ReadSkip(File* file, int64_t byte_count) {
47  const int64_t kBufferSize = 0x40000; // 256KB.
48  std::unique_ptr<char[]> buffer(new char[kBufferSize]);
49  int64_t bytes_read = 0;
50  while (bytes_read < byte_count) {
51  int64_t size = std::min(kBufferSize, byte_count - bytes_read);
52  int64_t result = file->Read(buffer.get(), size);
53  // Only give success if there are no errors, not at EOF, and read exactly
54  // byte_count bytes.
55  if (result <= 0)
56  return false;
57 
58  bytes_read += result;
59  }
60 
61  DCHECK_EQ(bytes_read, byte_count);
62  return true;
63 }
64 } // namespace
65 
66 TwoPassSingleSegmentSegmenter::TwoPassSingleSegmentSegmenter(
67  const MuxerOptions& options)
68  : SingleSegmentSegmenter(options) {}
69 
70 TwoPassSingleSegmentSegmenter::~TwoPassSingleSegmentSegmenter() {}
71 
72 Status TwoPassSingleSegmentSegmenter::DoInitialize() {
73  // Assume the amount of time to copy the temp file as the same amount
74  // of time as to make it.
75  set_progress_target(duration() * 2);
76 
77  if (!TempFilePath(options().temp_dir, &temp_file_name_))
78  return Status(error::FILE_FAILURE, "Unable to create temporary file.");
79  std::unique_ptr<MkvWriter> temp(new MkvWriter);
80  Status status = temp->Open(temp_file_name_);
81  if (!status.ok())
82  return status;
83  set_writer(std::move(temp));
84 
85  return SingleSegmentSegmenter::DoInitialize();
86 }
87 
88 Status TwoPassSingleSegmentSegmenter::DoFinalize() {
89  const uint64_t header_size = init_end() + 1;
90  const uint64_t cues_pos = header_size - segment_payload_pos();
91  const uint64_t cues_size = UpdateCues(cues());
92  seek_head()->set_cues_pos(cues_pos);
93  seek_head()->set_cluster_pos(cues_pos + cues_size);
94 
95  // Write the header to the real output file.
96  std::unique_ptr<MkvWriter> real_writer(new MkvWriter);
97  Status status = real_writer->Open(options().output_file_name);
98  if (!status.ok())
99  return status;
100 
101  const uint64_t file_size = writer()->Position() + cues_size;
102  Status temp = WriteSegmentHeader(file_size, real_writer.get());
103  if (!temp.ok())
104  return temp;
105  DCHECK_EQ(real_writer->Position(), static_cast<int64_t>(header_size));
106 
107  // Write the cues to the real output file.
108  set_index_start(real_writer->Position());
109  if (!cues()->Write(real_writer.get()))
110  return Status(error::FILE_FAILURE, "Error writing Cues data.");
111  set_index_end(real_writer->Position() - 1);
112  DCHECK_EQ(real_writer->Position(),
113  static_cast<int64_t>(segment_payload_pos() + cues_pos + cues_size));
114 
115  // Close the temp file and open it for reading.
116  set_writer(std::unique_ptr<MkvWriter>());
117  std::unique_ptr<File, FileCloser> temp_reader(
118  File::Open(temp_file_name_.c_str(), "r"));
119  if (!temp_reader)
120  return Status(error::FILE_FAILURE, "Error opening temp file.");
121 
122  // Skip the header that has already been written.
123  if (!ReadSkip(temp_reader.get(), header_size))
124  return Status(error::FILE_FAILURE, "Error reading temp file.");
125 
126  // Copy the rest of the data over.
127  if (!CopyFileWithClusterRewrite(temp_reader.get(), real_writer.get(),
128  cluster()->Size())) {
129  return Status(error::FILE_FAILURE, "Error copying temp file.");
130  }
131 
132  // Close and delete the temp file.
133  temp_reader.reset();
134  if (!File::Delete(temp_file_name_.c_str())) {
135  LOG(WARNING) << "Unable to delete temporary file " << temp_file_name_;
136  }
137 
138  return real_writer->Close();
139 }
140 
141 bool TwoPassSingleSegmentSegmenter::CopyFileWithClusterRewrite(
142  File* source,
143  MkvWriter* dest,
144  uint64_t last_size) {
145  const int cluster_id_size = mkvmuxer::GetUIntSize(libwebm::kMkvCluster);
146  const int cluster_size_size = 8; // The size of the Cluster size integer.
147  const int cluster_header_size = cluster_id_size + cluster_size_size;
148 
149  // We are at the start of a cluster, so copy the ID.
150  if (dest->WriteFromFile(source, cluster_id_size) != cluster_id_size)
151  return false;
152 
153  for (int i = 0; i < cues()->cue_entries_size() - 1; ++i) {
154  // Write the size of the cluster.
155  const mkvmuxer::CuePoint* cue = cues()->GetCueByIndex(i);
156  const mkvmuxer::CuePoint* next_cue = cues()->GetCueByIndex(i + 1);
157  const int64_t cluster_payload_size =
158  next_cue->cluster_pos() - cue->cluster_pos() - cluster_header_size;
159  if (mkvmuxer::WriteUIntSize(dest, cluster_payload_size, cluster_size_size))
160  return false;
161  if (!ReadSkip(source, cluster_size_size))
162  return false;
163 
164  // Copy the cluster and the next cluster's ID.
165  int64_t to_copy = cluster_payload_size + cluster_id_size;
166  if (dest->WriteFromFile(source, to_copy) != to_copy)
167  return false;
168 
169  // Update the progress; need to convert from WebM timecode to ISO BMFF.
170  const int64_t webm_delta_time = next_cue->time() - cue->time();
171  const int64_t delta_time = FromWebMTimecode(webm_delta_time);
172  UpdateProgress(delta_time);
173  }
174 
175  // The last cluster takes up until the cues.
176  const uint64_t last_cluster_payload_size = last_size - cluster_header_size;
177  if (mkvmuxer::WriteUIntSize(dest, last_cluster_payload_size,
178  cluster_size_size))
179  return false;
180  if (!ReadSkip(source, cluster_size_size))
181  return false;
182 
183  // Copy the last cluster.
184  return dest->WriteFromFile(source) ==
185  static_cast<int64_t>(last_cluster_payload_size);
186 }
187 
188 } // namespace webm
189 } // namespace media
190 } // 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