Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2020 Google Inc. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | #include <assert.h> |
16 | | #include <string> |
17 | | |
18 | | #include "include/flac_parser.h" |
19 | | |
20 | | #include <jni.h> |
21 | | |
22 | | // #include <android/log.h> |
23 | | |
24 | | #include <cassert> |
25 | | #include <cstdlib> |
26 | | #include <cstring> |
27 | | |
28 | | #include "common.h" |
29 | | |
30 | | #define LOG_TAG "FLACParser" |
31 | | |
32 | | #define LITERAL_TO_STRING_INTERNAL(x) #x |
33 | | #define LITERAL_TO_STRING(x) LITERAL_TO_STRING_INTERNAL(x) |
34 | | |
35 | 24.7k | #define CHECK(x) if (!(x)) return 0; |
36 | | |
37 | | const int endian = 1; |
38 | 4.56k | #define isBigEndian() (*(reinterpret_cast<const char *>(&endian)) == 0) |
39 | | |
40 | | // The FLAC parser calls our C++ static callbacks using C calling conventions, |
41 | | // inside FLAC__stream_decoder_process_until_end_of_metadata |
42 | | // and FLAC__stream_decoder_process_single. |
43 | | // We immediately then call our corresponding C++ instance methods |
44 | | // with the same parameter list, but discard redundant information. |
45 | | |
46 | | FLAC__StreamDecoderReadStatus FLACParser::read_callback( |
47 | | const FLAC__StreamDecoder * /* decoder */, FLAC__byte buffer[], |
48 | 86.0k | size_t *bytes, void *client_data) { |
49 | 86.0k | return reinterpret_cast<FLACParser *>(client_data) |
50 | 86.0k | ->readCallback(buffer, bytes); |
51 | 86.0k | } |
52 | | |
53 | | FLAC__StreamDecoderSeekStatus FLACParser::seek_callback( |
54 | | const FLAC__StreamDecoder * /* decoder */, |
55 | 29.8k | FLAC__uint64 absolute_byte_offset, void *client_data) { |
56 | 29.8k | return reinterpret_cast<FLACParser *>(client_data) |
57 | 29.8k | ->seekCallback(absolute_byte_offset); |
58 | 29.8k | } |
59 | | |
60 | | FLAC__StreamDecoderTellStatus FLACParser::tell_callback( |
61 | | const FLAC__StreamDecoder * /* decoder */, |
62 | 1.96M | FLAC__uint64 *absolute_byte_offset, void *client_data) { |
63 | 1.96M | return reinterpret_cast<FLACParser *>(client_data) |
64 | 1.96M | ->tellCallback(absolute_byte_offset); |
65 | 1.96M | } |
66 | | |
67 | | FLAC__StreamDecoderLengthStatus FLACParser::length_callback( |
68 | | const FLAC__StreamDecoder * /* decoder */, FLAC__uint64 *stream_length, |
69 | 0 | void *client_data) { |
70 | 0 | return reinterpret_cast<FLACParser *>(client_data) |
71 | 0 | ->lengthCallback(stream_length); |
72 | 0 | } |
73 | | |
74 | | FLAC__bool FLACParser::eof_callback(const FLAC__StreamDecoder * /* decoder */, |
75 | 86.0k | void *client_data) { |
76 | 86.0k | return reinterpret_cast<FLACParser *>(client_data)->eofCallback(); |
77 | 86.0k | } |
78 | | |
79 | | FLAC__StreamDecoderWriteStatus FLACParser::write_callback( |
80 | | const FLAC__StreamDecoder * /* decoder */, const FLAC__Frame *frame, |
81 | 25.2k | const FLAC__int32 *const buffer[], void *client_data) { |
82 | 25.2k | return reinterpret_cast<FLACParser *>(client_data) |
83 | 25.2k | ->writeCallback(frame, buffer); |
84 | 25.2k | } |
85 | | |
86 | | void FLACParser::metadata_callback(const FLAC__StreamDecoder * /* decoder */, |
87 | | const FLAC__StreamMetadata *metadata, |
88 | 13.5k | void *client_data) { |
89 | 13.5k | reinterpret_cast<FLACParser *>(client_data)->metadataCallback(metadata); |
90 | 13.5k | } |
91 | | |
92 | | void FLACParser::error_callback(const FLAC__StreamDecoder * /* decoder */, |
93 | | FLAC__StreamDecoderErrorStatus status, |
94 | 2.27M | void *client_data) { |
95 | 2.27M | reinterpret_cast<FLACParser *>(client_data)->errorCallback(status); |
96 | 2.27M | } |
97 | | |
98 | | // These are the corresponding callbacks with C++ calling conventions |
99 | | |
100 | | FLAC__StreamDecoderReadStatus FLACParser::readCallback(FLAC__byte buffer[], |
101 | 86.0k | size_t *bytes) { |
102 | 86.0k | size_t requested = *bytes; |
103 | 86.0k | ssize_t actual = mDataSource->readAt(mCurrentPos, buffer, requested); |
104 | 86.0k | if (0 > actual) { |
105 | 0 | *bytes = 0; |
106 | 0 | return FLAC__STREAM_DECODER_READ_STATUS_ABORT; |
107 | 86.0k | } else if (0 == actual) { |
108 | 34.0k | *bytes = 0; |
109 | 34.0k | mEOF = true; |
110 | 34.0k | return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; |
111 | 52.0k | } else { |
112 | 52.0k | assert(actual <= requested); |
113 | 52.0k | *bytes = actual; |
114 | 52.0k | mCurrentPos += actual; |
115 | 52.0k | return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; |
116 | 52.0k | } |
117 | 86.0k | } |
118 | | |
119 | | FLAC__StreamDecoderSeekStatus FLACParser::seekCallback( |
120 | 29.8k | FLAC__uint64 absolute_byte_offset) { |
121 | 29.8k | mCurrentPos = absolute_byte_offset; |
122 | 29.8k | mEOF = false; |
123 | 29.8k | return FLAC__STREAM_DECODER_SEEK_STATUS_OK; |
124 | 29.8k | } |
125 | | |
126 | | FLAC__StreamDecoderTellStatus FLACParser::tellCallback( |
127 | 1.96M | FLAC__uint64 *absolute_byte_offset) { |
128 | 1.96M | *absolute_byte_offset = mCurrentPos; |
129 | 1.96M | return FLAC__STREAM_DECODER_TELL_STATUS_OK; |
130 | 1.96M | } |
131 | | |
132 | | FLAC__StreamDecoderLengthStatus FLACParser::lengthCallback( |
133 | 0 | FLAC__uint64 *stream_length) { |
134 | 0 | return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; |
135 | 0 | } |
136 | | |
137 | 86.0k | FLAC__bool FLACParser::eofCallback() { return mEOF; } |
138 | | |
139 | | FLAC__StreamDecoderWriteStatus FLACParser::writeCallback( |
140 | 25.2k | const FLAC__Frame *frame, const FLAC__int32 *const buffer[]) { |
141 | 25.2k | if (mWriteRequested) { |
142 | 25.1k | mWriteRequested = false; |
143 | | // FLAC parser doesn't free or realloc buffer until next frame or finish |
144 | 25.1k | mWriteHeader = frame->header; |
145 | 25.1k | mWriteBuffer = buffer; |
146 | 25.1k | mWriteCompleted = true; |
147 | 25.1k | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; |
148 | 25.1k | } else { |
149 | 108 | return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; |
150 | 108 | } |
151 | 25.2k | } |
152 | | |
153 | 13.5k | void FLACParser::metadataCallback(const FLAC__StreamMetadata *metadata) { |
154 | 13.5k | switch (metadata->type) { |
155 | 5.45k | case FLAC__METADATA_TYPE_STREAMINFO: |
156 | 5.45k | if (!mStreamInfoValid) { |
157 | 4.66k | mStreamInfo = metadata->data.stream_info; |
158 | 4.66k | mStreamInfoValid = true; |
159 | 4.66k | } else { |
160 | 791 | break; |
161 | 791 | } |
162 | 4.66k | break; |
163 | 4.66k | case FLAC__METADATA_TYPE_SEEKTABLE: |
164 | 122 | mSeekTable = &metadata->data.seek_table; |
165 | 122 | break; |
166 | 1.37k | case FLAC__METADATA_TYPE_VORBIS_COMMENT: |
167 | 1.37k | if (!mVorbisCommentsValid) { |
168 | 143 | FLAC__StreamMetadata_VorbisComment vorbisComment = |
169 | 143 | metadata->data.vorbis_comment; |
170 | 95.0k | for (FLAC__uint32 i = 0; i < vorbisComment.num_comments; ++i) { |
171 | 94.8k | FLAC__StreamMetadata_VorbisComment_Entry vorbisCommentEntry = |
172 | 94.8k | vorbisComment.comments[i]; |
173 | 94.8k | if (vorbisCommentEntry.entry != NULL) { |
174 | 94.8k | std::string comment( |
175 | 94.8k | reinterpret_cast<char *>(vorbisCommentEntry.entry), |
176 | 94.8k | vorbisCommentEntry.length); |
177 | 94.8k | mVorbisComments.push_back(comment); |
178 | 94.8k | } |
179 | 94.8k | } |
180 | 143 | mVorbisCommentsValid = true; |
181 | 1.23k | } else { |
182 | 1.23k | break; |
183 | 1.23k | } |
184 | 143 | break; |
185 | 6.61k | case FLAC__METADATA_TYPE_PICTURE: { |
186 | 6.61k | const FLAC__StreamMetadata_Picture *parsedPicture = |
187 | 6.61k | &metadata->data.picture; |
188 | 6.61k | FlacPicture picture; |
189 | 6.61k | picture.mimeType.assign(std::string(parsedPicture->mime_type)); |
190 | 6.61k | picture.description.assign( |
191 | 6.61k | std::string((char *)parsedPicture->description)); |
192 | 6.61k | picture.data.assign(parsedPicture->data, |
193 | 6.61k | parsedPicture->data + parsedPicture->data_length); |
194 | 6.61k | picture.width = parsedPicture->width; |
195 | 6.61k | picture.height = parsedPicture->height; |
196 | 6.61k | picture.depth = parsedPicture->depth; |
197 | 6.61k | picture.colors = parsedPicture->colors; |
198 | 6.61k | picture.type = parsedPicture->type; |
199 | 6.61k | mPictures.push_back(picture); |
200 | 6.61k | mPicturesValid = true; |
201 | 6.61k | break; |
202 | 1.37k | } |
203 | 0 | default: |
204 | 0 | break; |
205 | 13.5k | } |
206 | 13.5k | } |
207 | | |
208 | 2.27M | void FLACParser::errorCallback(FLAC__StreamDecoderErrorStatus status) { |
209 | 2.27M | mErrorStatus = status; |
210 | 2.27M | } |
211 | | |
212 | | // Copy samples from FLAC native 32-bit non-interleaved to |
213 | | // correct bit-depth (non-zero padded), interleaved. |
214 | | // These are candidates for optimization if needed. |
215 | | static void copyToByteArrayBigEndian(int8_t *dst, const int *const *src, |
216 | | unsigned bytesPerSample, unsigned nSamples, |
217 | 0 | unsigned nChannels) { |
218 | 0 | for (unsigned i = 0; i < nSamples; ++i) { |
219 | 0 | for (unsigned c = 0; c < nChannels; ++c) { |
220 | | // point to the first byte of the source address |
221 | | // and then skip the first few bytes (most significant bytes) |
222 | | // depending on the bit depth |
223 | 0 | const int8_t *byteSrc = |
224 | 0 | reinterpret_cast<const int8_t *>(&src[c][i]) + 4 - bytesPerSample; |
225 | 0 | memcpy(dst, byteSrc, bytesPerSample); |
226 | 0 | dst = dst + bytesPerSample; |
227 | 0 | } |
228 | 0 | } |
229 | 0 | } |
230 | | |
231 | | static void copyToByteArrayLittleEndian(int8_t *dst, const int *const *src, |
232 | | unsigned bytesPerSample, |
233 | 24.7k | unsigned nSamples, unsigned nChannels) { |
234 | 10.5M | for (unsigned i = 0; i < nSamples; ++i) { |
235 | 21.8M | for (unsigned c = 0; c < nChannels; ++c) { |
236 | | // with little endian, the most significant bytes will be at the end |
237 | | // copy the bytes in little endian will remove the most significant byte |
238 | | // so we are good here. |
239 | 11.3M | memcpy(dst, &(src[c][i]), bytesPerSample); |
240 | 11.3M | dst = dst + bytesPerSample; |
241 | 11.3M | } |
242 | 10.5M | } |
243 | 24.7k | } |
244 | | |
245 | | static void copyTrespass(int8_t * /* dst */, const int *const * /* src */, |
246 | | unsigned /* bytesPerSample */, unsigned /* nSamples */, |
247 | 0 | unsigned /* nChannels */) { |
248 | 0 | ; |
249 | 0 | } |
250 | | |
251 | | // FLACParser |
252 | | |
253 | | FLACParser::FLACParser(DataSource *source) |
254 | 5.86k | : mDataSource(source), |
255 | 5.86k | mCopy(copyTrespass), |
256 | | mDecoder(NULL), |
257 | 5.86k | mCurrentPos(0LL), |
258 | 5.86k | mEOF(false), |
259 | 5.86k | mStreamInfoValid(false), |
260 | | mSeekTable(NULL), |
261 | 5.86k | firstFrameOffset(0LL), |
262 | 5.86k | mVorbisCommentsValid(false), |
263 | 5.86k | mPicturesValid(false), |
264 | 5.86k | mWriteRequested(false), |
265 | 5.86k | mWriteCompleted(false), |
266 | | mWriteBuffer(NULL), |
267 | 5.86k | mErrorStatus((FLAC__StreamDecoderErrorStatus)-1) { |
268 | 5.86k | memset(&mStreamInfo, 0, sizeof(mStreamInfo)); |
269 | 5.86k | memset(&mWriteHeader, 0, sizeof(mWriteHeader)); |
270 | 5.86k | } |
271 | | |
272 | 5.86k | FLACParser::~FLACParser() { |
273 | 5.86k | if (mDecoder != NULL) { |
274 | 5.86k | FLAC__stream_decoder_delete(mDecoder); |
275 | 5.86k | mDecoder = NULL; |
276 | 5.86k | } |
277 | 5.86k | } |
278 | | |
279 | 5.86k | bool FLACParser::init() { |
280 | | // setup libFLAC parser |
281 | 5.86k | mDecoder = FLAC__stream_decoder_new(); |
282 | 5.86k | if (mDecoder == NULL) { |
283 | | // The new should succeed, since probably all it does is a malloc |
284 | | // that always succeeds in Android. But to avoid dependence on the |
285 | | // libFLAC internals, we check and log here. |
286 | 0 | return false; |
287 | 0 | } |
288 | 5.86k | FLAC__stream_decoder_set_md5_checking(mDecoder, false); |
289 | 5.86k | FLAC__stream_decoder_set_metadata_ignore_all(mDecoder); |
290 | 5.86k | FLAC__stream_decoder_set_metadata_respond(mDecoder, |
291 | 5.86k | FLAC__METADATA_TYPE_STREAMINFO); |
292 | 5.86k | FLAC__stream_decoder_set_metadata_respond(mDecoder, |
293 | 5.86k | FLAC__METADATA_TYPE_SEEKTABLE); |
294 | 5.86k | FLAC__stream_decoder_set_metadata_respond(mDecoder, |
295 | 5.86k | FLAC__METADATA_TYPE_VORBIS_COMMENT); |
296 | 5.86k | FLAC__stream_decoder_set_metadata_respond(mDecoder, |
297 | 5.86k | FLAC__METADATA_TYPE_PICTURE); |
298 | 5.86k | FLAC__StreamDecoderInitStatus initStatus; |
299 | 5.86k | initStatus = FLAC__stream_decoder_init_stream( |
300 | 5.86k | mDecoder, read_callback, seek_callback, tell_callback, length_callback, |
301 | 5.86k | eof_callback, write_callback, metadata_callback, error_callback, |
302 | 5.86k | reinterpret_cast<void *>(this)); |
303 | 5.86k | if (initStatus != FLAC__STREAM_DECODER_INIT_STATUS_OK) { |
304 | | // A failure here probably indicates a programming error and so is |
305 | | // unlikely to happen. But we check and log here similarly to above. |
306 | 0 | return false; |
307 | 0 | } |
308 | 5.86k | return true; |
309 | 5.86k | } |
310 | | |
311 | 5.86k | bool FLACParser::decodeMetadata() { |
312 | | // parse all metadata |
313 | 5.86k | if (!FLAC__stream_decoder_process_until_end_of_metadata(mDecoder)) { |
314 | 1.18k | return false; |
315 | 1.18k | } |
316 | | // store first frame offset |
317 | 4.67k | FLAC__stream_decoder_get_decode_position(mDecoder, &firstFrameOffset); |
318 | | |
319 | 4.67k | if (mStreamInfoValid) { |
320 | | // check channel count |
321 | 4.62k | if (getChannels() == 0 || getChannels() > 8) { |
322 | 0 | return false; |
323 | 0 | } |
324 | | // check bit depth |
325 | 4.62k | switch (getBitsPerSample()) { |
326 | 1.06k | case 8: |
327 | 1.96k | case 16: |
328 | 2.20k | case 24: |
329 | 4.56k | case 32: |
330 | 4.56k | break; |
331 | 65 | default: |
332 | 65 | return false; |
333 | 4.62k | } |
334 | | // configure the appropriate copy function based on device endianness. |
335 | 4.56k | if (isBigEndian()) { |
336 | 0 | mCopy = copyToByteArrayBigEndian; |
337 | 4.56k | } else { |
338 | 4.56k | mCopy = copyToByteArrayLittleEndian; |
339 | 4.56k | } |
340 | 4.56k | } else { |
341 | 44 | return false; |
342 | 44 | } |
343 | 4.56k | return true; |
344 | 4.67k | } |
345 | | |
346 | 29.3k | size_t FLACParser::readBuffer(void *output, size_t output_size) { |
347 | 29.3k | mWriteRequested = true; |
348 | 29.3k | mWriteCompleted = false; |
349 | | |
350 | 29.3k | if (!FLAC__stream_decoder_process_single(mDecoder)) { |
351 | 820 | return -1; |
352 | 820 | } |
353 | 28.5k | if (!mWriteCompleted) { |
354 | 3.48k | if (FLAC__stream_decoder_get_state(mDecoder) != |
355 | 3.48k | FLAC__STREAM_DECODER_END_OF_STREAM) { |
356 | 0 | } |
357 | 3.48k | return -1; |
358 | 3.48k | } |
359 | | |
360 | | // verify that block header keeps the promises made by STREAMINFO |
361 | 25.0k | unsigned blocksize = mWriteHeader.blocksize; |
362 | 25.0k | if (blocksize == 0 || blocksize > getMaxBlockSize()) { |
363 | 58 | return -1; |
364 | 58 | } |
365 | 24.9k | if (mWriteHeader.sample_rate != getSampleRate() || |
366 | 24.9k | mWriteHeader.channels != getChannels() || |
367 | 24.9k | mWriteHeader.bits_per_sample != getBitsPerSample()) { |
368 | 179 | return -1; |
369 | 179 | } |
370 | | |
371 | 24.7k | unsigned bytesPerSample = getBitsPerSample() >> 3; |
372 | 24.7k | size_t bufferSize = blocksize * getChannels() * bytesPerSample; |
373 | 24.7k | if (bufferSize > output_size) { |
374 | 17 | return -1; |
375 | 17 | } |
376 | | |
377 | | // copy PCM from FLAC write buffer to our media buffer, with interleaving. |
378 | 24.7k | (*mCopy)(reinterpret_cast<int8_t *>(output), mWriteBuffer, bytesPerSample, |
379 | 24.7k | blocksize, getChannels()); |
380 | | |
381 | | // fill in buffer metadata |
382 | 24.7k | CHECK(mWriteHeader.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER); |
383 | | |
384 | 24.7k | return bufferSize; |
385 | 24.7k | } |
386 | | |
387 | | bool FLACParser::getSeekPositions(int64_t timeUs, |
388 | 0 | std::array<int64_t, 4> &result) { |
389 | 0 | if (!mSeekTable) { |
390 | 0 | return false; |
391 | 0 | } |
392 | | |
393 | 0 | unsigned sampleRate = getSampleRate(); |
394 | 0 | int64_t totalSamples = getTotalSamples(); |
395 | 0 | int64_t targetSampleNumber = (timeUs * sampleRate) / 1000000LL; |
396 | 0 | if (targetSampleNumber >= totalSamples) { |
397 | 0 | targetSampleNumber = totalSamples - 1; |
398 | 0 | } |
399 | |
|
400 | 0 | FLAC__StreamMetadata_SeekPoint* points = mSeekTable->points; |
401 | 0 | unsigned length = mSeekTable->num_points; |
402 | |
|
403 | 0 | for (unsigned i = length; i != 0; i--) { |
404 | 0 | int64_t sampleNumber = points[i - 1].sample_number; |
405 | 0 | if (sampleNumber == -1) { // placeholder |
406 | 0 | continue; |
407 | 0 | } |
408 | 0 | if (sampleNumber <= targetSampleNumber) { |
409 | 0 | result[0] = (sampleNumber * 1000000LL) / sampleRate; |
410 | 0 | result[1] = firstFrameOffset + points[i - 1].stream_offset; |
411 | 0 | if (sampleNumber == targetSampleNumber || i >= length || |
412 | 0 | points[i].sample_number == -1) { // placeholder |
413 | | // exact seek, or no following non-placeholder seek point |
414 | 0 | result[2] = result[0]; |
415 | 0 | result[3] = result[1]; |
416 | 0 | } else { |
417 | 0 | result[2] = (points[i].sample_number * 1000000LL) / sampleRate; |
418 | 0 | result[3] = firstFrameOffset + points[i].stream_offset; |
419 | 0 | } |
420 | 0 | return true; |
421 | 0 | } |
422 | 0 | } |
423 | 0 | result[0] = 0; |
424 | 0 | result[1] = firstFrameOffset; |
425 | 0 | result[2] = 0; |
426 | 0 | result[3] = firstFrameOffset; |
427 | 0 | return true; |
428 | 0 | } |
429 | | |
430 | | namespace { |
431 | | |
432 | | class FuzzDataSource : public DataSource { |
433 | | const uint8_t *data_; |
434 | | size_t size_; |
435 | | |
436 | | public: |
437 | 5.86k | FuzzDataSource(const uint8_t *data, size_t size) { |
438 | 5.86k | data_ = data; |
439 | 5.86k | size_ = size; |
440 | 5.86k | } |
441 | | |
442 | 86.0k | ssize_t readAt(off64_t offset, void *const data, size_t size) { |
443 | 86.0k | if (offset > size_) |
444 | 0 | return -1; |
445 | 86.0k | size_t remaining = size_ - offset; |
446 | 86.0k | if (remaining < size) |
447 | 69.0k | size = remaining; |
448 | 86.0k | memcpy(data, data_ + offset, size); |
449 | 86.0k | return size; |
450 | 86.0k | } |
451 | | }; |
452 | | |
453 | | } // namespace |
454 | | |
455 | | // Fuzz FLAC format and instrument the result as exoplayer JNI would: |
456 | | // https://github.com/google/ExoPlayer/blob/release-v2/extensions/flac/src/main/jni/ |
457 | 5.86k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |
458 | 5.86k | FuzzDataSource source(data, size); |
459 | 5.86k | FLACParser parser(&source); |
460 | | |
461 | | // Early parsing |
462 | 5.86k | if (!parser.init() || !parser.decodeMetadata()) |
463 | 1.29k | return 0; |
464 | | |
465 | 4.56k | auto streamInfo = parser.getStreamInfo(); |
466 | | |
467 | | // Similar implementation than ExoPlayer |
468 | 4.56k | int buffer_size = streamInfo.max_blocksize * streamInfo.channels * 2; |
469 | 4.56k | assert(buffer_size >= 0); // Not expected |
470 | 4.56k | auto buffer = new uint8_t[buffer_size]; |
471 | | |
472 | 29.3k | while (parser.readBuffer(buffer, buffer_size) < ((size_t)-1)); |
473 | 4.56k | delete[] buffer; |
474 | | |
475 | 4.56k | return 0; |
476 | 4.56k | } |