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
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
20namespace shaka {
21namespace media {
22namespace webm {
23namespace {
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.
28uint64_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.
46bool 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
66TwoPassSingleSegmentSegmenter::TwoPassSingleSegmentSegmenter(
67 const MuxerOptions& options)
68 : SingleSegmentSegmenter(options) {}
69
70TwoPassSingleSegmentSegmenter::~TwoPassSingleSegmentSegmenter() {}
71
72Status 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
88Status 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
141bool 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.
bool TempFilePath(const std::string &temp_dir, std::string *temp_file_path)
Definition file_util.cc:43