1 /*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 //#define LOG_NDEBUG 0
18 #define LOG_TAG "WAVExtractor"
19 #include <utils/Log.h>
20
21 #include "WAVExtractor.h"
22
23 #include <android/binder_ibinder.h> // for AIBinder_getCallingUid
24 #include <audio_utils/primitives.h>
25 #include <media/stagefright/foundation/ADebug.h>
26 #include <media/stagefright/MediaDefs.h>
27 #include <media/stagefright/MediaErrors.h>
28 #include <media/stagefright/MetaData.h>
29 #include <private/android_filesystem_config.h> // for AID_MEDIA
30 #include <system/audio.h>
31 #include <utils/String8.h>
32 #include <cutils/bitops.h>
33
34 #define CHANNEL_MASK_USE_CHANNEL_ORDER 0
35
36 // NOTE: This code assumes the device processor is little endian.
37
38 namespace android {
39
40 // MediaServer is capable of handling float extractor output, but general processes
41 // may not be able to do so.
42 // TODO: Improve API to set extractor float output.
43 // (Note: duplicated with FLACExtractor.cpp)
shouldExtractorOutputFloat(int bitsPerSample)44 static inline bool shouldExtractorOutputFloat(int bitsPerSample)
45 {
46 return bitsPerSample > 16 && AIBinder_getCallingUid() == AID_MEDIA;
47 }
48
49 enum {
50 WAVE_FORMAT_PCM = 0x0001,
51 WAVE_FORMAT_IEEE_FLOAT = 0x0003,
52 WAVE_FORMAT_ALAW = 0x0006,
53 WAVE_FORMAT_MULAW = 0x0007,
54 WAVE_FORMAT_MSGSM = 0x0031,
55 WAVE_FORMAT_EXTENSIBLE = 0xFFFE
56 };
57
58 static const char* WAVEEXT_SUBFORMAT = "\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71";
59 static const char* AMBISONIC_SUBFORMAT = "\x00\x00\x21\x07\xD3\x11\x86\x44\xC8\xC1\xCA\x00\x00\x00";
60
U32_LE_AT(const uint8_t * ptr)61 static uint32_t U32_LE_AT(const uint8_t *ptr) {
62 return ptr[3] << 24 | ptr[2] << 16 | ptr[1] << 8 | ptr[0];
63 }
64
U16_LE_AT(const uint8_t * ptr)65 static uint16_t U16_LE_AT(const uint8_t *ptr) {
66 return ptr[1] << 8 | ptr[0];
67 }
68
69 struct WAVSource : public MediaTrackHelper {
70 WAVSource(
71 DataSourceHelper *dataSource,
72 AMediaFormat *meta,
73 uint16_t waveFormat,
74 bool outputFloat,
75 off64_t offset, size_t size);
76
77 virtual media_status_t start();
78 virtual media_status_t stop();
79 virtual media_status_t getFormat(AMediaFormat *meta);
80
81 virtual media_status_t read(
82 MediaBufferHelper **buffer, const ReadOptions *options = NULL);
83
supportsNonBlockingReadandroid::WAVSource84 bool supportsNonBlockingRead() override { return false; }
85
86 protected:
87 virtual ~WAVSource();
88
89 private:
90 static const size_t kMaxFrameSize;
91
92 DataSourceHelper *mDataSource;
93 AMediaFormat *mMeta;
94 uint16_t mWaveFormat;
95 const bool mOutputFloat;
96 int32_t mSampleRate;
97 int32_t mNumChannels;
98 int32_t mBitsPerSample;
99 off64_t mOffset;
100 size_t mSize;
101 bool mStarted;
102 off64_t mCurrentPos;
103
104 WAVSource(const WAVSource &);
105 WAVSource &operator=(const WAVSource &);
106 };
107
WAVExtractor(DataSourceHelper * source)108 WAVExtractor::WAVExtractor(DataSourceHelper *source)
109 : mDataSource(source),
110 mValidFormat(false),
111 mChannelMask(CHANNEL_MASK_USE_CHANNEL_ORDER) {
112 mTrackMeta = AMediaFormat_new();
113 mInitCheck = init();
114 }
115
~WAVExtractor()116 WAVExtractor::~WAVExtractor() {
117 delete mDataSource;
118 AMediaFormat_delete(mTrackMeta);
119 }
120
getMetaData(AMediaFormat * meta)121 media_status_t WAVExtractor::getMetaData(AMediaFormat *meta) {
122 AMediaFormat_clear(meta);
123 if (mInitCheck == OK) {
124 AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_CONTAINER_WAV);
125 }
126
127 return AMEDIA_OK;
128 }
129
countTracks()130 size_t WAVExtractor::countTracks() {
131 return mInitCheck == OK ? 1 : 0;
132 }
133
getTrack(size_t index)134 MediaTrackHelper *WAVExtractor::getTrack(size_t index) {
135 if (mInitCheck != OK || index > 0) {
136 return NULL;
137 }
138
139 return new WAVSource(
140 mDataSource, mTrackMeta,
141 mWaveFormat, shouldExtractorOutputFloat(mBitsPerSample), mDataOffset, mDataSize);
142 }
143
getTrackMetaData(AMediaFormat * meta,size_t index,uint32_t)144 media_status_t WAVExtractor::getTrackMetaData(
145 AMediaFormat *meta,
146 size_t index, uint32_t /* flags */) {
147 if (mInitCheck != OK || index > 0) {
148 return AMEDIA_ERROR_UNKNOWN;
149 }
150
151 const media_status_t status = AMediaFormat_copy(meta, mTrackMeta);
152 if (status == OK) {
153 AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_PCM_ENCODING,
154 shouldExtractorOutputFloat(mBitsPerSample)
155 ? kAudioEncodingPcmFloat : kAudioEncodingPcm16bit);
156 }
157 return status;
158 }
159
init()160 status_t WAVExtractor::init() {
161 uint8_t header[12];
162 if (mDataSource->readAt(
163 0, header, sizeof(header)) < (ssize_t)sizeof(header)) {
164 return NO_INIT;
165 }
166
167 if (memcmp(header, "RIFF", 4) || memcmp(&header[8], "WAVE", 4)) {
168 return NO_INIT;
169 }
170
171 size_t totalSize = U32_LE_AT(&header[4]);
172
173 off64_t offset = 12;
174 size_t remainingSize = totalSize;
175 while (remainingSize >= 8) {
176 uint8_t chunkHeader[8];
177 if (mDataSource->readAt(offset, chunkHeader, 8) < 8) {
178 return NO_INIT;
179 }
180
181 remainingSize -= 8;
182 offset += 8;
183
184 uint32_t chunkSize = U32_LE_AT(&chunkHeader[4]);
185
186 if (chunkSize > remainingSize) {
187 return NO_INIT;
188 }
189
190 if (!memcmp(chunkHeader, "fmt ", 4)) {
191 if (chunkSize < 16) {
192 return NO_INIT;
193 }
194
195 uint8_t formatSpec[40];
196 if (mDataSource->readAt(offset, formatSpec, 2) < 2) {
197 return NO_INIT;
198 }
199
200 mWaveFormat = U16_LE_AT(formatSpec);
201 if (mWaveFormat != WAVE_FORMAT_PCM
202 && mWaveFormat != WAVE_FORMAT_IEEE_FLOAT
203 && mWaveFormat != WAVE_FORMAT_ALAW
204 && mWaveFormat != WAVE_FORMAT_MULAW
205 && mWaveFormat != WAVE_FORMAT_MSGSM
206 && mWaveFormat != WAVE_FORMAT_EXTENSIBLE) {
207 return AMEDIA_ERROR_UNSUPPORTED;
208 }
209
210 uint8_t fmtSize = 16;
211 if (mWaveFormat == WAVE_FORMAT_EXTENSIBLE) {
212 fmtSize = 40;
213 }
214 if (mDataSource->readAt(offset, formatSpec, fmtSize) < fmtSize) {
215 return NO_INIT;
216 }
217
218 mNumChannels = U16_LE_AT(&formatSpec[2]);
219
220 if (mNumChannels < 1 || mNumChannels > FCC_8) {
221 ALOGE("Unsupported number of channels (%d)", mNumChannels);
222 return AMEDIA_ERROR_UNSUPPORTED;
223 }
224
225 if (mWaveFormat != WAVE_FORMAT_EXTENSIBLE) {
226 if (mNumChannels != 1 && mNumChannels != FCC_2) {
227 ALOGW("More than 2 channels (%d) in non-WAVE_EXT, unknown channel mask",
228 mNumChannels);
229 }
230 }
231
232 mSampleRate = U32_LE_AT(&formatSpec[4]);
233
234 if (mSampleRate == 0) {
235 return ERROR_MALFORMED;
236 }
237
238 mBitsPerSample = U16_LE_AT(&formatSpec[14]);
239
240 if (mWaveFormat == WAVE_FORMAT_EXTENSIBLE) {
241 uint16_t validBitsPerSample = U16_LE_AT(&formatSpec[18]);
242 if (validBitsPerSample != mBitsPerSample) {
243 if (validBitsPerSample != 0) {
244 ALOGE("validBits(%d) != bitsPerSample(%d) are not supported",
245 validBitsPerSample, mBitsPerSample);
246 return AMEDIA_ERROR_UNSUPPORTED;
247 } else {
248 // we only support valitBitsPerSample == bitsPerSample but some WAV_EXT
249 // writers don't correctly set the valid bits value, and leave it at 0.
250 ALOGW("WAVE_EXT has 0 valid bits per sample, ignoring");
251 }
252 }
253
254 mChannelMask = U32_LE_AT(&formatSpec[20]);
255 ALOGV("numChannels=%d channelMask=0x%x", mNumChannels, mChannelMask);
256 if ((mChannelMask >> 18) != 0) {
257 ALOGE("invalid channel mask 0x%x", mChannelMask);
258 return ERROR_MALFORMED;
259 }
260
261 if ((mChannelMask != CHANNEL_MASK_USE_CHANNEL_ORDER)
262 && (popcount(mChannelMask) != mNumChannels)) {
263 ALOGE("invalid number of channels (%d) in channel mask (0x%x)",
264 popcount(mChannelMask), mChannelMask);
265 return ERROR_MALFORMED;
266 }
267
268 // In a WAVE_EXT header, the first two bytes of the GUID stored at byte 24 contain
269 // the sample format, using the same definitions as a regular WAV header
270 mWaveFormat = U16_LE_AT(&formatSpec[24]);
271 if (memcmp(&formatSpec[26], WAVEEXT_SUBFORMAT, 14) &&
272 memcmp(&formatSpec[26], AMBISONIC_SUBFORMAT, 14)) {
273 ALOGE("unsupported GUID");
274 return ERROR_UNSUPPORTED;
275 }
276 }
277
278 if (mWaveFormat == WAVE_FORMAT_PCM) {
279 if (mBitsPerSample != 8 && mBitsPerSample != 16
280 && mBitsPerSample != 24 && mBitsPerSample != 32) {
281 return ERROR_UNSUPPORTED;
282 }
283 } else if (mWaveFormat == WAVE_FORMAT_IEEE_FLOAT) {
284 if (mBitsPerSample != 32) { // TODO we don't support double
285 return ERROR_UNSUPPORTED;
286 }
287 }
288 else if (mWaveFormat == WAVE_FORMAT_MSGSM) {
289 if (mBitsPerSample != 0) {
290 return ERROR_UNSUPPORTED;
291 }
292 } else if (mWaveFormat == WAVE_FORMAT_MULAW || mWaveFormat == WAVE_FORMAT_ALAW) {
293 if (mBitsPerSample != 8) {
294 return ERROR_UNSUPPORTED;
295 }
296 } else {
297 return ERROR_UNSUPPORTED;
298 }
299
300 mValidFormat = true;
301 } else if (!memcmp(chunkHeader, "data", 4)) {
302 if (mValidFormat) {
303 mDataOffset = offset;
304 mDataSize = chunkSize;
305
306 AMediaFormat_clear(mTrackMeta);
307
308 switch (mWaveFormat) {
309 case WAVE_FORMAT_PCM:
310 case WAVE_FORMAT_IEEE_FLOAT:
311 AMediaFormat_setString(mTrackMeta,
312 AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_RAW);
313 break;
314 case WAVE_FORMAT_ALAW:
315 AMediaFormat_setString(mTrackMeta,
316 AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_G711_ALAW);
317 break;
318 case WAVE_FORMAT_MSGSM:
319 AMediaFormat_setString(mTrackMeta,
320 AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_MSGSM);
321 break;
322 default:
323 CHECK_EQ(mWaveFormat, (uint16_t)WAVE_FORMAT_MULAW);
324 AMediaFormat_setString(mTrackMeta,
325 AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_G711_MLAW);
326 break;
327 }
328
329 AMediaFormat_setInt32(mTrackMeta, AMEDIAFORMAT_KEY_CHANNEL_COUNT, mNumChannels);
330 AMediaFormat_setInt32(mTrackMeta, AMEDIAFORMAT_KEY_CHANNEL_MASK, mChannelMask);
331 AMediaFormat_setInt32(mTrackMeta, AMEDIAFORMAT_KEY_SAMPLE_RATE, mSampleRate);
332 AMediaFormat_setInt32(mTrackMeta, AMEDIAFORMAT_KEY_BITS_PER_SAMPLE, mBitsPerSample);
333 int64_t durationUs = 0;
334 if (mWaveFormat == WAVE_FORMAT_MSGSM) {
335 // 65 bytes decode to 320 8kHz samples
336 durationUs =
337 1000000LL * (mDataSize / 65 * 320) / 8000;
338 } else {
339 size_t bytesPerSample = mBitsPerSample >> 3;
340
341 if (!bytesPerSample || !mNumChannels)
342 return AMEDIA_ERROR_MALFORMED;
343
344 size_t num_samples = mDataSize / (mNumChannels * bytesPerSample);
345
346 if (!mSampleRate)
347 return AMEDIA_ERROR_MALFORMED;
348
349 durationUs =
350 1000000LL * num_samples / mSampleRate;
351 }
352
353 AMediaFormat_setInt64(mTrackMeta, AMEDIAFORMAT_KEY_DURATION, durationUs);
354
355 return OK;
356 }
357 }
358
359 offset += chunkSize;
360 }
361
362 return NO_INIT;
363 }
364
365 const size_t WAVSource::kMaxFrameSize = 32768;
366
WAVSource(DataSourceHelper * dataSource,AMediaFormat * meta,uint16_t waveFormat,bool outputFloat,off64_t offset,size_t size)367 WAVSource::WAVSource(
368 DataSourceHelper *dataSource,
369 AMediaFormat *meta,
370 uint16_t waveFormat,
371 bool outputFloat,
372 off64_t offset, size_t size)
373 : mDataSource(dataSource),
374 mMeta(meta),
375 mWaveFormat(waveFormat),
376 mOutputFloat(outputFloat),
377 mOffset(offset),
378 mSize(size),
379 mStarted(false) {
380 CHECK(AMediaFormat_getInt32(mMeta, AMEDIAFORMAT_KEY_SAMPLE_RATE, &mSampleRate));
381 CHECK(AMediaFormat_getInt32(mMeta, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &mNumChannels));
382 CHECK(AMediaFormat_getInt32(mMeta, AMEDIAFORMAT_KEY_BITS_PER_SAMPLE, &mBitsPerSample));
383 }
384
~WAVSource()385 WAVSource::~WAVSource() {
386 if (mStarted) {
387 stop();
388 }
389 }
390
start()391 media_status_t WAVSource::start() {
392 ALOGV("WAVSource::start");
393
394 CHECK(!mStarted);
395
396 // some WAV files may have large audio buffers that use shared memory transfer.
397 if (!mBufferGroup->init(4 /* buffers */, kMaxFrameSize)) {
398 return AMEDIA_ERROR_UNKNOWN;
399 }
400
401 mCurrentPos = mOffset;
402
403 mStarted = true;
404
405 return AMEDIA_OK;
406 }
407
stop()408 media_status_t WAVSource::stop() {
409 ALOGV("WAVSource::stop");
410
411 CHECK(mStarted);
412
413 mStarted = false;
414
415 return AMEDIA_OK;
416 }
417
getFormat(AMediaFormat * meta)418 media_status_t WAVSource::getFormat(AMediaFormat *meta) {
419 ALOGV("WAVSource::getFormat");
420
421 const media_status_t status = AMediaFormat_copy(meta, mMeta);
422 if (status == OK) {
423 AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, kMaxFrameSize);
424 AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_PCM_ENCODING,
425 mOutputFloat ? kAudioEncodingPcmFloat : kAudioEncodingPcm16bit);
426 }
427 return status;
428 }
429
read(MediaBufferHelper ** out,const ReadOptions * options)430 media_status_t WAVSource::read(
431 MediaBufferHelper **out, const ReadOptions *options) {
432 *out = NULL;
433
434 if (options != nullptr && options->getNonBlocking() && !mBufferGroup->has_buffers()) {
435 return AMEDIA_ERROR_WOULD_BLOCK;
436 }
437
438 int64_t seekTimeUs;
439 ReadOptions::SeekMode mode;
440 if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) {
441 int64_t pos = 0;
442
443 if (mWaveFormat == WAVE_FORMAT_MSGSM) {
444 // 65 bytes decode to 320 8kHz samples
445 int64_t samplenumber = (seekTimeUs * mSampleRate) / 1000000;
446 int64_t framenumber = samplenumber / 320;
447 pos = framenumber * 65;
448 } else {
449 pos = (seekTimeUs * mSampleRate) / 1000000 * mNumChannels * (mBitsPerSample >> 3);
450 }
451 if (pos > (off64_t)mSize) {
452 pos = mSize;
453 }
454 mCurrentPos = pos + mOffset;
455 }
456
457 MediaBufferHelper *buffer;
458 media_status_t err = mBufferGroup->acquire_buffer(&buffer);
459 if (err != OK) {
460 return err;
461 }
462
463 // maxBytesToRead may be reduced so that in-place data conversion will fit in buffer size.
464 const size_t bufferSize = std::min(buffer->size(), kMaxFrameSize);
465 size_t maxBytesToRead;
466 if (mOutputFloat) { // destination is float at 4 bytes per sample, source may be less.
467 maxBytesToRead = (mBitsPerSample / 8) * (bufferSize / 4);
468 } else { // destination is int16_t at 2 bytes per sample, only source of 8 bits is less.
469 maxBytesToRead = mBitsPerSample == 8 ? bufferSize / 2 : bufferSize;
470 }
471
472 const size_t maxBytesAvailable =
473 (mCurrentPos - mOffset >= (off64_t)mSize)
474 ? 0 : mSize - (mCurrentPos - mOffset);
475
476 if (maxBytesToRead > maxBytesAvailable) {
477 maxBytesToRead = maxBytesAvailable;
478 }
479
480 if (mWaveFormat == WAVE_FORMAT_MSGSM) {
481 // Microsoft packs 2 frames into 65 bytes, rather than using separate 33-byte frames,
482 // so read multiples of 65, and use smaller buffers to account for ~10:1 expansion ratio
483 if (maxBytesToRead > 1024) {
484 maxBytesToRead = 1024;
485 }
486 maxBytesToRead = (maxBytesToRead / 65) * 65;
487 } else {
488 // read only integral amounts of audio unit frames.
489 const size_t inputUnitFrameSize = mNumChannels * mBitsPerSample / 8;
490 maxBytesToRead -= maxBytesToRead % inputUnitFrameSize;
491 }
492
493 ssize_t n = mDataSource->readAt(
494 mCurrentPos, buffer->data(),
495 maxBytesToRead);
496
497 if (n <= 0) {
498 buffer->release();
499 buffer = NULL;
500
501 return AMEDIA_ERROR_END_OF_STREAM;
502 }
503
504 buffer->set_range(0, n);
505
506 // TODO: add capability to return data as float PCM instead of 16 bit PCM.
507 if (mWaveFormat == WAVE_FORMAT_PCM) {
508 const size_t bytesPerFrame = (mBitsPerSample >> 3) * mNumChannels;
509 const size_t numFrames = n / bytesPerFrame;
510 const size_t numSamples = numFrames * mNumChannels;
511 if (mOutputFloat) {
512 float *fdest = (float *)buffer->data();
513 buffer->set_range(0, 4 * numSamples);
514 switch (mBitsPerSample) {
515 case 8: {
516 memcpy_to_float_from_u8(fdest, (const uint8_t *)buffer->data(), numSamples);
517 } break;
518 case 16: {
519 memcpy_to_float_from_i16(fdest, (const int16_t *)buffer->data(), numSamples);
520 } break;
521 case 24: {
522 memcpy_to_float_from_p24(fdest, (const uint8_t *)buffer->data(), numSamples);
523 } break;
524 case 32: { // buffer range is correct
525 memcpy_to_float_from_i32(fdest, (const int32_t *)buffer->data(), numSamples);
526 } break;
527 }
528 } else {
529 int16_t *idest = (int16_t *)buffer->data();
530 buffer->set_range(0, 2 * numSamples);
531 switch (mBitsPerSample) {
532 case 8: {
533 memcpy_to_i16_from_u8(idest, (const uint8_t *)buffer->data(), numSamples);
534 } break;
535 case 16:
536 // no conversion needed
537 break;
538 case 24: {
539 memcpy_to_i16_from_p24(idest, (const uint8_t *)buffer->data(), numSamples);
540 } break;
541 case 32: {
542 memcpy_to_i16_from_i32(idest, (const int32_t *)buffer->data(), numSamples);
543 } break;
544 }
545 }
546 } else if (mWaveFormat == WAVE_FORMAT_IEEE_FLOAT) {
547 if (!mOutputFloat) { // mBitsPerSample == 32
548 int16_t *idest = (int16_t *)buffer->data();
549 const size_t numSamples = n / 4;
550 memcpy_to_i16_from_float(idest, (const float *)buffer->data(), numSamples);
551 buffer->set_range(0, 2 * numSamples);
552 }
553 // Note: if output encoding is float, no need to convert if source is float.
554 }
555
556 int64_t timeStampUs = 0;
557
558 if (mWaveFormat == WAVE_FORMAT_MSGSM) {
559 timeStampUs = 1000000LL * (mCurrentPos - mOffset) * 320 / 65 / mSampleRate;
560 } else {
561 size_t bytesPerSample = mBitsPerSample >> 3;
562 timeStampUs = 1000000LL * (mCurrentPos - mOffset)
563 / (mNumChannels * bytesPerSample) / mSampleRate;
564 }
565
566 AMediaFormat *meta = buffer->meta_data();
567 AMediaFormat_setInt64(meta, AMEDIAFORMAT_KEY_TIME_US, timeStampUs);
568 AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_IS_SYNC_FRAME, 1);
569
570 mCurrentPos += n;
571
572 *out = buffer;
573
574 return AMEDIA_OK;
575 }
576
577 ////////////////////////////////////////////////////////////////////////////////
578
CreateExtractor(CDataSource * source,void *)579 static CMediaExtractor* CreateExtractor(
580 CDataSource *source,
581 void *) {
582 return wrap(new WAVExtractor(new DataSourceHelper(source)));
583 }
584
Sniff(CDataSource * source,float * confidence,void **,FreeMetaFunc *)585 static CreatorFunc Sniff(
586 CDataSource *source,
587 float *confidence,
588 void **,
589 FreeMetaFunc *) {
590 DataSourceHelper *helper = new DataSourceHelper(source);
591 char header[12];
592 if (helper->readAt(0, header, sizeof(header)) < (ssize_t)sizeof(header)) {
593 delete helper;
594 return NULL;
595 }
596
597 if (memcmp(header, "RIFF", 4) || memcmp(&header[8], "WAVE", 4)) {
598 delete helper;
599 return NULL;
600 }
601
602 WAVExtractor *extractor = new WAVExtractor(helper); // extractor owns the helper
603 int numTracks = extractor->countTracks();
604 delete extractor;
605 if (numTracks == 0) {
606 return NULL;
607 }
608
609 *confidence = 0.3f;
610
611 return CreateExtractor;
612 }
613
614 static const char *extensions[] = {
615 "wav",
616 NULL
617 };
618
619 extern "C" {
620 // This is the only symbol that needs to be exported
621 __attribute__ ((visibility ("default")))
GETEXTRACTORDEF()622 ExtractorDef GETEXTRACTORDEF() {
623 return {
624 EXTRACTORDEF_VERSION,
625 UUID("7d613858-5837-4a38-84c5-332d1cddee27"),
626 1, // version
627 "WAV Extractor",
628 { .v3 = {Sniff, extensions} },
629 };
630 }
631
632 } // extern "C"
633
634 } // namespace android
635