Coverage Report

Created: 2018-09-25 14:53

/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