summaryrefslogtreecommitdiffstats
path: root/dom/media/mp3/MP3FrameParser.h
blob: d0d7a372d2fdbf9cb980f85a0f551bfff6084ad1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef MP3_FRAME_PARSER_H_
#define MP3_FRAME_PARSER_H_

#include <vector>

#include "mozilla/Maybe.h"
#include "mozilla/Result.h"
#include "BufferReader.h"

namespace mozilla {

// ID3 header parser state machine used by FrameParser.
// The header contains the following format (one byte per term):
// 'I' 'D' '3' MajorVersion MinorVersion Flags Size1 Size2 Size3 Size4
// For more details see https://id3.org/id3v2.4.0-structure
class ID3Parser {
 public:
  // Holds the ID3 header and its parsing state.
  class ID3Header {
   public:
    // The header size is static, see class comment.
    static const int SIZE = 10;
    static const int ID3v1_SIZE = 128;

    // Constructor.
    ID3Header();

    // Resets the state to allow for a new parsing session.
    void Reset();

    // The ID3 tags are versioned like this: ID3vMajorVersion.MinorVersion.
    uint8_t MajorVersion() const;
    uint8_t MinorVersion() const;

    // The ID3 flags field.
    uint8_t Flags() const;

    // The derived size based on the provided size fields.
    uint32_t Size() const;

    // To see whether we have parsed the value of the size from header.
    bool HasSizeBeenSet() const;

    // Returns the size of an ID3v2.4 footer if present and zero otherwise.
    uint8_t FooterSize() const;

    // The total size of the ID3 tag including header/footer, or zero if
    // none has been found.
    uint32_t TotalTagSize() const;

    // Returns whether the parsed data is a valid ID3 header up to the given
    // byte position.
    bool IsValid(int aPos) const;

    // Returns whether the parsed data is a complete and valid ID3 header.
    bool IsValid() const;

    // Parses the next provided byte.
    // Returns whether the byte creates a valid sequence up to this point.
    bool ParseNext(uint8_t c);

   private:
    // Updates the parser state machine with the provided next byte.
    // Returns whether the provided byte is a valid next byte in the sequence.
    bool Update(uint8_t c);

    // The currently parsed byte sequence.
    uint8_t mRaw[SIZE] = {};

    // The derived size as provided by the size fields.
    // The header size fields holds a 4 byte sequence with each MSB set to 0,
    // this bits need to be ignored when deriving the actual size.
    Maybe<uint32_t> mSize;

    // The current byte position in the parsed sequence. Reset via Reset and
    // incremented via Update.
    int mPos = 0;
  };

  // Check if the buffer is starting with ID3v2 tag.
  static bool IsBufferStartingWithID3Tag(BufferReader* aReader);
  // Similarly, if the buffer is starting with ID3v1 tag.
  static bool IsBufferStartingWithID3v1Tag(BufferReader* aReader);

  // Returns the parsed ID3 header. Note: check for validity.
  const ID3Header& Header() const;

  // Returns the size of all parsed ID3 headers.
  uint32_t TotalHeadersSize() const;

  // Parses contents of given BufferReader for a valid ID3v2 header.
  // Returns the parsed ID3v2 tag size if successful and zero otherwise.
  uint32_t Parse(BufferReader* aReader);

  // Resets the state to allow for a new parsing session.
  void Reset();

 private:
  uint32_t ParseInternal(BufferReader* aReader);

  // The currently parsed ID3 header. Reset via Reset, updated via Parse.
  ID3Header mHeader;
  // If a file contains multiple ID3 headers, then we would only select the
  // latest one, but keep the size of former abandoned in order to return the
  // correct size offset.
  uint32_t mFormerID3Size = 0;
};

// MPEG audio frame parser.
// The MPEG frame header has the following format (one bit per character):
// 11111111 111VVLLC BBBBSSPR MMEETOHH
// {   sync   } - 11 sync bits
//   VV         - MPEG audio version ID (0->2.5, 1->reserved, 2->2, 3->1)
//   LL         - Layer description (0->reserved, 1->III, 2->II, 3->I)
//   C          - CRC protection bit (0->protected, 1->not protected)
//   BBBB       - Bitrate index (see table in implementation)
//   SS         - Sampling rate index (see table in implementation)
//   P          - Padding bit (0->not padded, 1->padded by 1 slot size)
//   R          - Private bit (ignored)
//   MM         - Channel mode (0->stereo, 1->joint stereo, 2->dual channel,
//                3->single channel)
//   EE         - Mode extension for joint stereo (ignored)
//   T          - Copyright (0->disabled, 1->enabled)
//   O          - Original (0->copy, 1->original)
//   HH         - Emphasis (0->none, 1->50/15 ms, 2->reserved, 3->CCIT J.17)
class FrameParser {
 public:
  // Holds the frame header and its parsing state.
  class FrameHeader {
   public:
    // The header size is static, see class comments.
    static const int SIZE = 4;

    // Constructor.
    FrameHeader();

    // Raw field access, see class comments for details.
    uint8_t Sync1() const;
    uint8_t Sync2() const;
    uint8_t RawVersion() const;
    uint8_t RawLayer() const;
    uint8_t RawProtection() const;
    uint8_t RawBitrate() const;
    uint8_t RawSampleRate() const;
    uint8_t Padding() const;
    uint8_t Private() const;
    uint8_t RawChannelMode() const;

    // Sampling rate frequency in Hz.
    uint32_t SampleRate() const;

    // Number of audio channels.
    uint32_t Channels() const;

    // Samples per frames, static depending on MPEG version and layer.
    uint32_t SamplesPerFrame() const;

    // Slot size used for padding, static depending on MPEG layer.
    uint32_t SlotSize() const;

    // Bitrate in kbps, can vary between frames.
    uint32_t Bitrate() const;

    // MPEG layer (0->invalid, 1->I, 2->II, 3->III).
    uint32_t Layer() const;

    // Returns whether the parsed data is a valid frame header up to the given
    // byte position.
    bool IsValid(const int aPos) const;

    // Returns whether the parsed data is a complete and valid frame header.
    bool IsValid() const;

    // Resets the state to allow for a new parsing session.
    void Reset();

    // Parses the next provided byte.
    // Returns whether the byte creates a valid sequence up to this point.
    bool ParseNext(const uint8_t c);

   private:
    // Updates the parser state machine with the provided next byte.
    // Returns whether the provided byte is a valid next byte in the sequence.
    bool Update(const uint8_t c);

    // The currently parsed byte sequence.
    uint8_t mRaw[SIZE] = {};

    // The current byte position in the parsed sequence. Reset via Reset and
    // incremented via Update.
    int mPos = 0;
  };

  // VBR frames may contain Xing or VBRI headers for additional info, we use
  // this class to parse them and access this info.
  class VBRHeader {
   public:
    // Synchronize with vbr_header TYPE_STR on change.
    enum VBRHeaderType { NONE = 0, XING, VBRI };

    // Constructor.
    VBRHeader();

    // Returns the parsed VBR header type, or NONE if no valid header found.
    VBRHeaderType Type() const;

    // Returns the total number of audio frames (excluding the VBR header frame)
    // expected in the stream/file.
    const Maybe<uint32_t>& NumAudioFrames() const;

    // Returns the expected size of the stream.
    const Maybe<uint32_t>& NumBytes() const;

    // Returns the VBR scale factor (0: best quality, 100: lowest quality).
    const Maybe<uint32_t>& Scale() const;

    // Returns true iff Xing/Info TOC (table of contents) is present.
    bool IsTOCPresent() const;

    // Returns whether the header is valid (type XING or VBRI).
    bool IsValid() const;

    // Returns whether the header is valid and contains reasonable non-zero
    // field values.
    bool IsComplete() const;

    // Returns the byte offset for the given duration percentage as a factor
    // (0: begin, 1.0: end).
    int64_t Offset(media::TimeUnit aTime, media::TimeUnit aDuration) const;

    // Parses contents of given ByteReader for a valid VBR header.
    // The offset of the passed ByteReader needs to point to an MPEG frame
    // begin, as a VBRI-style header is searched at a fixed offset relative to
    // frame begin. Returns whether a valid VBR header was found in the range.
    bool Parse(BufferReader* aReader, size_t aFrameSize);

    uint32_t EncoderDelay() const { return mEncoderDelay; }
    uint32_t EncoderPadding() const { return mEncoderPadding; }

   private:
    // Parses contents of given ByteReader for a valid Xing header.
    // The initial ByteReader offset will be preserved.
    // Returns whether a valid Xing header was found in the range.
    Result<bool, nsresult> ParseXing(BufferReader* aReader, size_t aFrameSize);

    // Parses contents of given ByteReader for a valid VBRI header.
    // The initial ByteReader offset will be preserved. It also needs to point
    // to the beginning of a valid MPEG frame, as VBRI headers are searched
    // at a fixed offset relative to frame begin.
    // Returns whether a valid VBRI header was found in the range.
    Result<bool, nsresult> ParseVBRI(BufferReader* aReader);

    // The total number of frames expected as parsed from a VBR header.
    Maybe<uint32_t> mNumAudioFrames;

    // The total number of bytes expected in the stream.
    Maybe<uint32_t> mNumBytes;

    // The VBR scale factor.
    Maybe<uint32_t> mScale;

    // The TOC table mapping duration percentage to byte offset.
    std::vector<int64_t> mTOC;

    // The detected VBR header type.
    VBRHeaderType mType;

    uint16_t mVBRISeekOffsetsFramesPerEntry = 0;

    // Delay and padding values found in the LAME header. The encoder delay is a
    // number of frames that has to be skipped at the beginning of the stream,
    // encoder padding is a number of frames that needs to be ignored in the
    // last packet.
    uint16_t mEncoderDelay = 0;
    uint16_t mEncoderPadding = 0;
  };

  // Frame meta container used to parse and hold a frame header and side info.
  class Frame {
   public:
    // Returns the length of the frame excluding the header in bytes.
    uint32_t Length() const;

    // Returns the parsed frame header.
    const FrameHeader& Header() const;

    // Resets the frame header and data.
    void Reset();

    // Parses the next provided byte.
    // Returns whether the byte creates a valid sequence up to this point.
    bool ParseNext(uint8_t c);

   private:
    // The currently parsed frame header.
    FrameHeader mHeader;
  };

  // Constructor.
  FrameParser();

  // Returns the currently parsed frame. Reset via Reset or EndFrameSession.
  const Frame& CurrentFrame() const;

  // Returns the previously parsed frame. Reset via Reset.
  const Frame& PrevFrame() const;

  // Returns the first parsed frame. Reset via Reset.
  const Frame& FirstFrame() const;

  // Returns the parsed ID3 header. Note: check for validity.
  const ID3Parser::ID3Header& ID3Header() const;

  // Returns whether ID3 metadata have been found, at the end of the file.
  bool ID3v1MetadataFound() const;

  // Returns the size of all parsed ID3 headers.
  uint32_t TotalID3HeaderSize() const;

  // Returns the parsed VBR header info. Note: check for validity by type.
  const VBRHeader& VBRInfo() const;

  // Resets the parser.
  void Reset();

  // Resets all frame data, but not the ID3Header.
  // Don't use between frames as first frame data is reset.
  void ResetFrameData();

  // Clear the last parsed frame to allow for next frame parsing, i.e.:
  // - sets PrevFrame to CurrentFrame
  // - resets the CurrentFrame
  // - resets ID3Header if no valid header was parsed yet
  void EndFrameSession();

  // Parses contents of given BufferReader for a valid frame header and returns
  // true if one was found. After returning, the variable passed to
  // 'aBytesToSkip' holds the amount of bytes to be skipped (if any) in order to
  // jump across a large ID3v2 tag spanning multiple buffers.
  Result<bool, nsresult> Parse(BufferReader* aReader, uint32_t* aBytesToSkip);

  // Parses contents of given BufferReader for a valid VBR header.
  // The offset of the passed BufferReader needs to point to an MPEG frame
  // begin, as a VBRI-style header is searched at a fixed offset relative to
  // frame begin. Returns whether a valid VBR header was found.
  bool ParseVBRHeader(BufferReader* aReader);

 private:
  // ID3 header parser.
  ID3Parser mID3Parser;

  // VBR header parser.
  VBRHeader mVBRHeader;

  // We keep the first parsed frame around for static info access, the
  // previously parsed frame for debugging and the currently parsed frame.
  Frame mFirstFrame;
  Frame mFrame;
  Frame mPrevFrame;
  // If this is true, ID3v1 metadata have been found at the end of the file, and
  // must be sustracted from the stream size in order to compute the stream
  // duration, when computing the duration of a CBR file based on its length in
  // bytes. This means that the duration can change at the moment we reach the
  // end of the file.
  bool mID3v1MetadataFound = false;
};

}  // namespace mozilla

#endif