1 // Copyright (c) 2016 The WebM project authors. All Rights Reserved.
2 //
3 // Use of this source code is governed by a BSD-style license
4 // that can be found in the LICENSE file in the root of the source
5 // tree. An additional intellectual property rights grant can be found
6 // in the file PATENTS. All contributing project authors may
7 // be found in the AUTHORS file in the root of the source tree.
8 #include "testing/test_util.h"
9
10 #include <cstdint>
11 #include <cstdio>
12 #include <cstdlib>
13 #include <cstring>
14 #include <ios>
15 #include <string>
16
17 #include "common/libwebm_util.h"
18 #include "common/webmids.h"
19
20 #include "mkvparser/mkvparser.h"
21 #include "mkvparser/mkvreader.h"
22
23 namespace test {
24
GetTestDataDir()25 std::string GetTestDataDir() {
26 const char* test_data_path = std::getenv("LIBWEBM_TEST_DATA_PATH");
27 return test_data_path ? std::string(test_data_path) : std::string();
28 }
29
GetTestFilePath(const std::string & name)30 std::string GetTestFilePath(const std::string& name) {
31 const std::string libwebm_testdata_dir = GetTestDataDir();
32 return libwebm_testdata_dir + "/" + name;
33 }
34
CompareFiles(const std::string & file1,const std::string & file2)35 bool CompareFiles(const std::string& file1, const std::string& file2) {
36 const std::size_t kBlockSize = 4096;
37 std::uint8_t buf1[kBlockSize] = {0};
38 std::uint8_t buf2[kBlockSize] = {0};
39
40 libwebm::FilePtr f1 =
41 libwebm::FilePtr(std::fopen(file1.c_str(), "rb"), libwebm::FILEDeleter());
42 libwebm::FilePtr f2 =
43 libwebm::FilePtr(std::fopen(file2.c_str(), "rb"), libwebm::FILEDeleter());
44
45 if (!f1.get() || !f2.get()) {
46 // Files cannot match if one or both couldn't be opened.
47 return false;
48 }
49
50 do {
51 const std::size_t r1 = std::fread(buf1, 1, kBlockSize, f1.get());
52 const std::size_t r2 = std::fread(buf2, 1, kBlockSize, f2.get());
53
54 // TODO(fgalligan): Add output of which byte differs.
55 if (r1 != r2 || std::memcmp(buf1, buf2, r1)) {
56 return 0; // Files are not equal
57 }
58 } while (!std::feof(f1.get()) && !std::feof(f2.get()));
59
60 return std::feof(f1.get()) && std::feof(f2.get());
61 }
62
HasCuePoints(const mkvparser::Segment * segment,std::int64_t * cues_offset)63 bool HasCuePoints(const mkvparser::Segment* segment,
64 std::int64_t* cues_offset) {
65 if (!segment || !cues_offset) {
66 return false;
67 }
68 using mkvparser::SeekHead;
69 const SeekHead* const seek_head = segment->GetSeekHead();
70 if (!seek_head) {
71 return false;
72 }
73
74 std::int64_t offset = 0;
75 for (int i = 0; i < seek_head->GetCount(); ++i) {
76 const SeekHead::Entry* const entry = seek_head->GetEntry(i);
77 if (entry->id == libwebm::kMkvCues) {
78 offset = entry->pos;
79 }
80 }
81
82 if (offset <= 0) {
83 // No Cues found.
84 return false;
85 }
86
87 *cues_offset = offset;
88 return true;
89 }
90
ValidateCues(mkvparser::Segment * segment,mkvparser::IMkvReader * reader)91 bool ValidateCues(mkvparser::Segment* segment, mkvparser::IMkvReader* reader) {
92 if (!segment) {
93 return false;
94 }
95
96 std::int64_t cues_offset = 0;
97 if (!HasCuePoints(segment, &cues_offset)) {
98 // No cues to validate, everything is OK.
99 return true;
100 }
101
102 // Parse Cues.
103 long long cues_pos = 0; // NOLINT
104 long cues_len = 0; // NOLINT
105 if (segment->ParseCues(cues_offset, cues_pos, cues_len)) {
106 return false;
107 }
108
109 // Get a pointer to the video track if it exists. Otherwise, we assume
110 // that Cues are based on the first track (which is true for all our test
111 // files).
112 const mkvparser::Tracks* const tracks = segment->GetTracks();
113 const mkvparser::Track* cues_track = tracks->GetTrackByIndex(0);
114 for (int i = 1; i < static_cast<int>(tracks->GetTracksCount()); ++i) {
115 const mkvparser::Track* const track = tracks->GetTrackByIndex(i);
116 if (track->GetType() == mkvparser::Track::kVideo) {
117 cues_track = track;
118 break;
119 }
120 }
121
122 // Iterate through Cues and verify if they are pointing to the correct
123 // Cluster position.
124 const mkvparser::Cues* const cues = segment->GetCues();
125 const mkvparser::CuePoint* cue_point = NULL;
126 while (cues->LoadCuePoint()) {
127 if (!cue_point) {
128 cue_point = cues->GetFirst();
129 } else {
130 cue_point = cues->GetNext(cue_point);
131 }
132 const mkvparser::CuePoint::TrackPosition* const track_position =
133 cue_point->Find(cues_track);
134 const long long cluster_pos = track_position->m_pos + // NOLINT
135 segment->m_start;
136
137 // If a cluster does not begin at |cluster_pos|, then the file is
138 // incorrect.
139 long length; // NOLINT
140 const std::int64_t id = mkvparser::ReadID(reader, cluster_pos, length);
141 if (id != libwebm::kMkvCluster) {
142 return false;
143 }
144 }
145 return true;
146 }
147
~MkvParser()148 MkvParser::~MkvParser() {
149 delete segment;
150 delete reader;
151 }
152
ParseMkvFileReleaseParser(const std::string & webm_file,MkvParser * parser_out)153 bool ParseMkvFileReleaseParser(const std::string& webm_file,
154 MkvParser* parser_out) {
155 parser_out->reader = new (std::nothrow) mkvparser::MkvReader;
156 mkvparser::MkvReader& reader = *parser_out->reader;
157 if (!parser_out->reader || reader.Open(webm_file.c_str()) < 0) {
158 return false;
159 }
160
161 long long pos = 0; // NOLINT
162 mkvparser::EBMLHeader ebml_header;
163 if (ebml_header.Parse(&reader, pos)) {
164 return false;
165 }
166
167 using mkvparser::Segment;
168 Segment* segment_ptr = nullptr;
169 if (Segment::CreateInstance(&reader, pos, segment_ptr)) {
170 return false;
171 }
172
173 std::unique_ptr<Segment> segment(segment_ptr);
174 long result;
175 if ((result = segment->Load()) < 0) {
176 return false;
177 }
178
179 const mkvparser::Cluster* cluster = segment->GetFirst();
180 if (!cluster || cluster->EOS()) {
181 return false;
182 }
183
184 while (cluster && cluster->EOS() == false) {
185 if (cluster->GetTimeCode() < 0) {
186 return false;
187 }
188
189 const mkvparser::BlockEntry* block = nullptr;
190 if (cluster->GetFirst(block) < 0) {
191 return false;
192 }
193
194 while (block != NULL && block->EOS() == false) {
195 if (cluster->GetNext(block, block) < 0) {
196 return false;
197 }
198 }
199
200 cluster = segment->GetNext(cluster);
201 }
202
203 parser_out->segment = segment.release();
204 return true;
205 }
206
ParseMkvFile(const std::string & webm_file)207 bool ParseMkvFile(const std::string& webm_file) {
208 MkvParser parser;
209 const bool result = ParseMkvFileReleaseParser(webm_file, &parser);
210 delete parser.segment;
211 delete parser.reader;
212 return result;
213 }
214
215 } // namespace test
216