/src/mozilla-central/dom/media/mp4/MP4Metadata.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | #include "mozilla/Assertions.h" |
6 | | #include "mozilla/CheckedInt.h" |
7 | | #include "mozilla/EndianUtils.h" |
8 | | #include "mozilla/Logging.h" |
9 | | #include "mozilla/RefPtr.h" |
10 | | #include "mozilla/Telemetry.h" |
11 | | #include "mozilla/UniquePtr.h" |
12 | | #include "VideoUtils.h" |
13 | | #include "MoofParser.h" |
14 | | #include "MP4Metadata.h" |
15 | | #include "ByteStream.h" |
16 | | #include "mp4parse.h" |
17 | | |
18 | | #include <limits> |
19 | | #include <stdint.h> |
20 | | #include <vector> |
21 | | |
22 | | using mozilla::media::TimeUnit; |
23 | | |
24 | | namespace mozilla { |
25 | | LazyLogModule gMP4MetadataLog("MP4Metadata"); |
26 | | |
27 | | IndiceWrapper::IndiceWrapper(Mp4parseByteData& aIndice) |
28 | 0 | { |
29 | 0 | mIndice.data = nullptr; |
30 | 0 | mIndice.length = aIndice.length; |
31 | 0 | mIndice.indices = aIndice.indices; |
32 | 0 | } |
33 | | |
34 | | size_t |
35 | | IndiceWrapper::Length() const |
36 | 0 | { |
37 | 0 | return mIndice.length; |
38 | 0 | } |
39 | | |
40 | | bool |
41 | | IndiceWrapper::GetIndice(size_t aIndex, Index::Indice& aIndice) const |
42 | 0 | { |
43 | 0 | if (aIndex >= mIndice.length) { |
44 | 0 | MOZ_LOG(gMP4MetadataLog, LogLevel::Error, ("Index overflow in indice")); |
45 | 0 | return false; |
46 | 0 | } |
47 | 0 |
|
48 | 0 | const Mp4parseIndice* indice = &mIndice.indices[aIndex]; |
49 | 0 | aIndice.start_offset = indice->start_offset; |
50 | 0 | aIndice.end_offset = indice->end_offset; |
51 | 0 | aIndice.start_composition = indice->start_composition; |
52 | 0 | aIndice.end_composition = indice->end_composition; |
53 | 0 | aIndice.start_decode = indice->start_decode; |
54 | 0 | aIndice.sync = indice->sync; |
55 | 0 | return true; |
56 | 0 | } |
57 | | |
58 | | static const char * |
59 | | TrackTypeToString(mozilla::TrackInfo::TrackType aType) |
60 | | { |
61 | | switch (aType) { |
62 | | case mozilla::TrackInfo::kAudioTrack: |
63 | | return "audio"; |
64 | | case mozilla::TrackInfo::kVideoTrack: |
65 | | return "video"; |
66 | | default: |
67 | | return "unknown"; |
68 | | } |
69 | | } |
70 | | |
71 | | bool |
72 | | StreamAdaptor::Read(uint8_t* buffer, uintptr_t size, size_t* bytes_read) |
73 | 0 | { |
74 | 0 | if (!mOffset.isValid()) { |
75 | 0 | MOZ_LOG(gMP4MetadataLog, LogLevel::Error, ("Overflow in source stream offset")); |
76 | 0 | return false; |
77 | 0 | } |
78 | 0 | bool rv = mSource->ReadAt(mOffset.value(), buffer, size, bytes_read); |
79 | 0 | if (rv) { |
80 | 0 | mOffset += *bytes_read; |
81 | 0 | } |
82 | 0 | return rv; |
83 | 0 | } |
84 | | |
85 | | // Wrapper to allow rust to call our read adaptor. |
86 | | static intptr_t |
87 | | read_source(uint8_t* buffer, uintptr_t size, void* userdata) |
88 | 0 | { |
89 | 0 | MOZ_ASSERT(buffer); |
90 | 0 | MOZ_ASSERT(userdata); |
91 | 0 |
|
92 | 0 | auto source = reinterpret_cast<StreamAdaptor*>(userdata); |
93 | 0 | size_t bytes_read = 0; |
94 | 0 | bool rv = source->Read(buffer, size, &bytes_read); |
95 | 0 | if (!rv) { |
96 | 0 | MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, ("Error reading source data")); |
97 | 0 | return -1; |
98 | 0 | } |
99 | 0 | return bytes_read; |
100 | 0 | } |
101 | | |
102 | | MP4Metadata::MP4Metadata(ByteStream* aSource) |
103 | | : mSource(aSource) |
104 | | , mSourceAdaptor(aSource) |
105 | 0 | { |
106 | 0 | DDLINKCHILD("source", aSource); |
107 | 0 |
|
108 | 0 | Mp4parseIo io = { read_source, &mSourceAdaptor }; |
109 | 0 | mParser.reset(mp4parse_new(&io)); |
110 | 0 | MOZ_ASSERT(mParser); |
111 | 0 | } |
112 | | |
113 | | MP4Metadata::~MP4Metadata() |
114 | 0 | { |
115 | 0 | } |
116 | | |
117 | | nsresult |
118 | | MP4Metadata::Parse() |
119 | 0 | { |
120 | 0 | Mp4parseStatus rv = mp4parse_read(mParser.get()); |
121 | 0 | if (rv != MP4PARSE_STATUS_OK) { |
122 | 0 | MOZ_LOG(gMP4MetadataLog, LogLevel::Debug, ("Parse failed, return code %d\n", rv)); |
123 | 0 | return rv == MP4PARSE_STATUS_OOM ? NS_ERROR_OUT_OF_MEMORY |
124 | 0 | : NS_ERROR_DOM_MEDIA_METADATA_ERR; |
125 | 0 | } |
126 | 0 |
|
127 | 0 | UpdateCrypto(); |
128 | 0 |
|
129 | 0 | return NS_OK; |
130 | 0 | } |
131 | | |
132 | | void |
133 | | MP4Metadata::UpdateCrypto() |
134 | 0 | { |
135 | 0 | Mp4parsePsshInfo info = {}; |
136 | 0 | if (mp4parse_get_pssh_info(mParser.get(), &info) != MP4PARSE_STATUS_OK) { |
137 | 0 | return; |
138 | 0 | } |
139 | 0 | |
140 | 0 | if (info.data.length == 0) { |
141 | 0 | return; |
142 | 0 | } |
143 | 0 | |
144 | 0 | mCrypto.Update(info.data.data, info.data.length); |
145 | 0 | } |
146 | | |
147 | | bool |
148 | | TrackTypeEqual(TrackInfo::TrackType aLHS, Mp4parseTrackType aRHS) |
149 | | { |
150 | | switch (aLHS) { |
151 | | case TrackInfo::kAudioTrack: |
152 | | return aRHS == MP4PARSE_TRACK_TYPE_AUDIO; |
153 | | case TrackInfo::kVideoTrack: |
154 | | return aRHS == MP4PARSE_TRACK_TYPE_VIDEO; |
155 | | default: |
156 | | return false; |
157 | | } |
158 | | } |
159 | | |
160 | | MP4Metadata::ResultAndTrackCount |
161 | | MP4Metadata::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const |
162 | 0 | { |
163 | 0 | uint32_t tracks; |
164 | 0 | auto rv = mp4parse_get_track_count(mParser.get(), &tracks); |
165 | 0 | if (rv != MP4PARSE_STATUS_OK) { |
166 | 0 | MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, |
167 | 0 | ("rust parser error %d counting tracks", rv)); |
168 | 0 | return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR, |
169 | 0 | RESULT_DETAIL("Rust parser error %d", rv)), |
170 | 0 | MP4Metadata::NumberTracksError()}; |
171 | 0 | } |
172 | 0 |
|
173 | 0 | uint32_t total = 0; |
174 | 0 | for (uint32_t i = 0; i < tracks; ++i) { |
175 | 0 | Mp4parseTrackInfo track_info; |
176 | 0 | rv = mp4parse_get_track_info(mParser.get(), i, &track_info); |
177 | 0 | if (rv != MP4PARSE_STATUS_OK) { |
178 | 0 | continue; |
179 | 0 | } |
180 | 0 | if (track_info.codec == MP4PARSE_CODEC_UNKNOWN) { |
181 | 0 | continue; |
182 | 0 | } |
183 | 0 | if (TrackTypeEqual(aType, track_info.track_type)) { |
184 | 0 | total += 1; |
185 | 0 | } |
186 | 0 | } |
187 | 0 |
|
188 | 0 | MOZ_LOG(gMP4MetadataLog, LogLevel::Info, ("%s tracks found: %u", |
189 | 0 | TrackTypeToString(aType), |
190 | 0 | total)); |
191 | 0 |
|
192 | 0 | return {NS_OK, total}; |
193 | 0 | } |
194 | | |
195 | | Maybe<uint32_t> |
196 | | MP4Metadata::TrackTypeToGlobalTrackIndex(mozilla::TrackInfo::TrackType aType, size_t aTrackNumber) const |
197 | 0 | { |
198 | 0 | uint32_t tracks; |
199 | 0 | auto rv = mp4parse_get_track_count(mParser.get(), &tracks); |
200 | 0 | if (rv != MP4PARSE_STATUS_OK) { |
201 | 0 | return Nothing(); |
202 | 0 | } |
203 | 0 | |
204 | 0 | /* The MP4Metadata API uses a per-TrackType index of tracks, but mp4parse |
205 | 0 | (and libstagefright) use a global track index. Convert the index by |
206 | 0 | counting the tracks of the requested type and returning the global |
207 | 0 | track index when a match is found. */ |
208 | 0 | uint32_t perType = 0; |
209 | 0 | for (uint32_t i = 0; i < tracks; ++i) { |
210 | 0 | Mp4parseTrackInfo track_info; |
211 | 0 | rv = mp4parse_get_track_info(mParser.get(), i, &track_info); |
212 | 0 | if (rv != MP4PARSE_STATUS_OK) { |
213 | 0 | continue; |
214 | 0 | } |
215 | 0 | if (TrackTypeEqual(aType, track_info.track_type)) { |
216 | 0 | if (perType == aTrackNumber) { |
217 | 0 | return Some(i); |
218 | 0 | } |
219 | 0 | perType += 1; |
220 | 0 | } |
221 | 0 | } |
222 | 0 |
|
223 | 0 | return Nothing(); |
224 | 0 | } |
225 | | |
226 | | MP4Metadata::ResultAndTrackInfo |
227 | | MP4Metadata::GetTrackInfo(mozilla::TrackInfo::TrackType aType, |
228 | | size_t aTrackNumber) const |
229 | 0 | { |
230 | 0 | Maybe<uint32_t> trackIndex = TrackTypeToGlobalTrackIndex(aType, aTrackNumber); |
231 | 0 | if (trackIndex.isNothing()) { |
232 | 0 | return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR, |
233 | 0 | RESULT_DETAIL("No %s tracks", |
234 | 0 | TrackTypeToStr(aType))), |
235 | 0 | nullptr}; |
236 | 0 | } |
237 | 0 |
|
238 | 0 | Mp4parseTrackInfo info; |
239 | 0 | auto rv = mp4parse_get_track_info(mParser.get(), trackIndex.value(), &info); |
240 | 0 | if (rv != MP4PARSE_STATUS_OK) { |
241 | 0 | MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, ("mp4parse_get_track_info returned %d", rv)); |
242 | 0 | return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR, |
243 | 0 | RESULT_DETAIL("Cannot find %s track #%zu", |
244 | 0 | TrackTypeToStr(aType), |
245 | 0 | aTrackNumber)), |
246 | 0 | nullptr}; |
247 | 0 | } |
248 | | #ifdef DEBUG |
249 | | const char* codec_string = "unrecognized"; |
250 | | switch (info.codec) { |
251 | | case MP4PARSE_CODEC_UNKNOWN: codec_string = "unknown"; break; |
252 | | case MP4PARSE_CODEC_AAC: codec_string = "aac"; break; |
253 | | case MP4PARSE_CODEC_OPUS: codec_string = "opus"; break; |
254 | | case MP4PARSE_CODEC_FLAC: codec_string = "flac"; break; |
255 | | case MP4PARSE_CODEC_ALAC: codec_string = "alac"; break; |
256 | | case MP4PARSE_CODEC_AVC: codec_string = "h.264"; break; |
257 | | case MP4PARSE_CODEC_VP9: codec_string = "vp9"; break; |
258 | | case MP4PARSE_CODEC_AV1: codec_string = "av1"; break; |
259 | | case MP4PARSE_CODEC_MP3: codec_string = "mp3"; break; |
260 | | case MP4PARSE_CODEC_MP4V: codec_string = "mp4v"; break; |
261 | | case MP4PARSE_CODEC_JPEG: codec_string = "jpeg"; break; |
262 | | case MP4PARSE_CODEC_AC3: codec_string = "ac-3"; break; |
263 | | case MP4PARSE_CODEC_EC3: codec_string = "ec-3"; break; |
264 | | } |
265 | | MOZ_LOG(gMP4MetadataLog, LogLevel::Debug, |
266 | | ("track codec %s (%u)\n", codec_string, info.codec)); |
267 | | #endif |
268 | | |
269 | 0 | // This specialization interface is crazy. |
270 | 0 | UniquePtr<mozilla::TrackInfo> e; |
271 | 0 | switch (aType) { |
272 | 0 | case TrackInfo::TrackType::kAudioTrack: { |
273 | 0 | Mp4parseTrackAudioInfo audio; |
274 | 0 | auto rv = mp4parse_get_track_audio_info(mParser.get(), trackIndex.value(), &audio); |
275 | 0 | if (rv != MP4PARSE_STATUS_OK) { |
276 | 0 | MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, ("mp4parse_get_track_audio_info returned error %d", rv)); |
277 | 0 | return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR, |
278 | 0 | RESULT_DETAIL("Cannot parse %s track #%zu", |
279 | 0 | TrackTypeToStr(aType), |
280 | 0 | aTrackNumber)), |
281 | 0 | nullptr}; |
282 | 0 | } |
283 | 0 | auto track = mozilla::MakeUnique<MP4AudioInfo>(); |
284 | 0 | track->Update(&info, &audio); |
285 | 0 | e = std::move(track); |
286 | 0 | } |
287 | 0 | break; |
288 | 0 | case TrackInfo::TrackType::kVideoTrack: { |
289 | 0 | Mp4parseTrackVideoInfo video; |
290 | 0 | auto rv = mp4parse_get_track_video_info(mParser.get(), trackIndex.value(), &video); |
291 | 0 | if (rv != MP4PARSE_STATUS_OK) { |
292 | 0 | MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, ("mp4parse_get_track_video_info returned error %d", rv)); |
293 | 0 | return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR, |
294 | 0 | RESULT_DETAIL("Cannot parse %s track #%zu", |
295 | 0 | TrackTypeToStr(aType), |
296 | 0 | aTrackNumber)), |
297 | 0 | nullptr}; |
298 | 0 | } |
299 | 0 | auto track = mozilla::MakeUnique<MP4VideoInfo>(); |
300 | 0 | track->Update(&info, &video); |
301 | 0 | e = std::move(track); |
302 | 0 | } |
303 | 0 | break; |
304 | 0 | default: |
305 | 0 | MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, ("unhandled track type %d", aType)); |
306 | 0 | return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR, |
307 | 0 | RESULT_DETAIL("Cannot handle %s track #%zu", |
308 | 0 | TrackTypeToStr(aType), |
309 | 0 | aTrackNumber)), |
310 | 0 | nullptr}; |
311 | 0 | } |
312 | 0 |
|
313 | 0 | // No duration in track, use fragment_duration. |
314 | 0 | if (e && !e->mDuration.IsPositive()) { |
315 | 0 | Mp4parseFragmentInfo info; |
316 | 0 | auto rv = mp4parse_get_fragment_info(mParser.get(), &info); |
317 | 0 | if (rv == MP4PARSE_STATUS_OK) { |
318 | 0 | e->mDuration = TimeUnit::FromMicroseconds(info.fragment_duration); |
319 | 0 | } |
320 | 0 | } |
321 | 0 |
|
322 | 0 | if (e && e->IsValid()) { |
323 | 0 | return {NS_OK, std::move(e)}; |
324 | 0 | } |
325 | 0 | MOZ_LOG(gMP4MetadataLog, LogLevel::Debug, ("TrackInfo didn't validate")); |
326 | 0 |
|
327 | 0 | return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR, |
328 | 0 | RESULT_DETAIL("Invalid %s track #%zu", |
329 | 0 | TrackTypeToStr(aType), |
330 | 0 | aTrackNumber)), |
331 | 0 | nullptr}; |
332 | 0 | } |
333 | | |
334 | | bool |
335 | | MP4Metadata::CanSeek() const |
336 | 0 | { |
337 | 0 | return true; |
338 | 0 | } |
339 | | |
340 | | MP4Metadata::ResultAndCryptoFile |
341 | | MP4Metadata::Crypto() const |
342 | 0 | { |
343 | 0 | return {NS_OK, &mCrypto}; |
344 | 0 | } |
345 | | |
346 | | MP4Metadata::ResultAndIndice |
347 | | MP4Metadata::GetTrackIndice(mozilla::TrackID aTrackID) |
348 | 0 | { |
349 | 0 | Mp4parseByteData indiceRawData = {}; |
350 | 0 |
|
351 | 0 | uint8_t fragmented = false; |
352 | 0 | auto rv = mp4parse_is_fragmented(mParser.get(), aTrackID, &fragmented); |
353 | 0 | if (rv != MP4PARSE_STATUS_OK) { |
354 | 0 | return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR, |
355 | 0 | RESULT_DETAIL("Cannot parse whether track id %d is " |
356 | 0 | "fragmented, mp4parse_error=%d", |
357 | 0 | int(aTrackID), int(rv))), |
358 | 0 | nullptr}; |
359 | 0 | } |
360 | 0 |
|
361 | 0 | if (!fragmented) { |
362 | 0 | rv = mp4parse_get_indice_table(mParser.get(), aTrackID, &indiceRawData); |
363 | 0 | if (rv != MP4PARSE_STATUS_OK) { |
364 | 0 | return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR, |
365 | 0 | RESULT_DETAIL("Cannot parse index table in track id %d, " |
366 | 0 | "mp4parse_error=%d", |
367 | 0 | int(aTrackID), int(rv))), |
368 | 0 | nullptr}; |
369 | 0 | } |
370 | 0 | } |
371 | 0 |
|
372 | 0 | UniquePtr<IndiceWrapper> indice; |
373 | 0 | indice = mozilla::MakeUnique<IndiceWrapper>(indiceRawData); |
374 | 0 |
|
375 | 0 | return {NS_OK, std::move(indice)}; |
376 | 0 | } |
377 | | |
378 | | /*static*/ MP4Metadata::ResultAndByteBuffer |
379 | | MP4Metadata::Metadata(ByteStream* aSource) |
380 | 0 | { |
381 | 0 | auto parser = mozilla::MakeUnique<MoofParser>(aSource, 0, false); |
382 | 0 | RefPtr<mozilla::MediaByteBuffer> buffer = parser->Metadata(); |
383 | 0 | if (!buffer) { |
384 | 0 | return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR, |
385 | 0 | RESULT_DETAIL("Cannot parse metadata")), |
386 | 0 | nullptr}; |
387 | 0 | } |
388 | 0 | return {NS_OK, std::move(buffer)}; |
389 | 0 | } |
390 | | |
391 | | } // namespace mozilla |