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