/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "SoundPool::Sound" #include #include "Sound.h" #include #include #include namespace android::soundpool { constexpr uint32_t kMaxSampleRate = 192000; constexpr size_t kDefaultHeapSize = 1024 * 1024; // 1MB (compatible with low mem devices) Sound::Sound(int32_t soundID, int fd, int64_t offset, int64_t length) : mSoundID(soundID) , mFd(fcntl(fd, F_DUPFD_CLOEXEC, (int)0 /* arg */)) // dup(fd) + close on exec to prevent leaks. , mOffset(offset) , mLength(length) { ALOGV("%s(soundID=%d, fd=%d, offset=%lld, length=%lld)", __func__, soundID, fd, (long long)offset, (long long)length); ALOGW_IF(mFd == -1, "Unable to dup descriptor %d", fd); } Sound::~Sound() { ALOGV("%s(soundID=%d, fd=%d)", __func__, mSoundID, mFd.get()); } static status_t decode(int fd, int64_t offset, int64_t length, uint32_t *rate, int32_t *channelCount, audio_format_t *audioFormat, audio_channel_mask_t *channelMask, const sp& heap, size_t *sizeInBytes) { ALOGV("%s(fd=%d, offset=%lld, length=%lld, ...)", __func__, fd, (long long)offset, (long long)length); std::unique_ptr ex{ AMediaExtractor_new(), &AMediaExtractor_delete}; status_t err = AMediaExtractor_setDataSourceFd(ex.get(), fd, offset, length); if (err != AMEDIA_OK) { return err; } *audioFormat = AUDIO_FORMAT_PCM_16_BIT; // default format for audio codecs. const size_t numTracks = AMediaExtractor_getTrackCount(ex.get()); for (size_t i = 0; i < numTracks; i++) { std::unique_ptr format{ AMediaExtractor_getTrackFormat(ex.get(), i), &AMediaFormat_delete}; const char *mime; if (!AMediaFormat_getString(format.get(), AMEDIAFORMAT_KEY_MIME, &mime)) { return UNKNOWN_ERROR; } if (strncmp(mime, "audio/", 6) == 0) { std::unique_ptr codec{ AMediaCodec_createDecoderByType(mime), &AMediaCodec_delete}; if (codec == nullptr || AMediaCodec_configure(codec.get(), format.get(), nullptr /* window */, nullptr /* drm */, 0 /* flags */) != AMEDIA_OK || AMediaCodec_start(codec.get()) != AMEDIA_OK || AMediaExtractor_selectTrack(ex.get(), i) != AMEDIA_OK) { return UNKNOWN_ERROR; } bool sawInputEOS = false; bool sawOutputEOS = false; auto writePos = static_cast(heap->getBase()); size_t available = heap->getSize(); size_t written = 0; format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format. while (!sawOutputEOS) { if (!sawInputEOS) { ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec.get(), 5000); ALOGV("%s: input buffer %zd", __func__, bufidx); if (bufidx >= 0) { size_t bufsize; uint8_t * const buf = AMediaCodec_getInputBuffer( codec.get(), bufidx, &bufsize); if (buf == nullptr) { ALOGE("%s: AMediaCodec_getInputBuffer returned nullptr, short decode", __func__); break; } ssize_t sampleSize = AMediaExtractor_readSampleData(ex.get(), buf, bufsize); ALOGV("%s: read %zd", __func__, sampleSize); if (sampleSize < 0) { sampleSize = 0; sawInputEOS = true; ALOGV("%s: EOS", __func__); } const int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex.get()); const media_status_t mstatus = AMediaCodec_queueInputBuffer( codec.get(), bufidx, 0 /* offset */, sampleSize, presentationTimeUs, sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0); if (mstatus != AMEDIA_OK) { // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES } ALOGE("%s: AMediaCodec_queueInputBuffer returned status %d," "short decode", __func__, (int)mstatus); break; } (void)AMediaExtractor_advance(ex.get()); } } AMediaCodecBufferInfo info; const ssize_t status = AMediaCodec_dequeueOutputBuffer(codec.get(), &info, 1); ALOGV("%s: dequeueoutput returned: %zd", __func__, status); if (status >= 0) { if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) { ALOGV("%s: output EOS", __func__); sawOutputEOS = true; } ALOGV("%s: got decoded buffer size %d", __func__, info.size); const uint8_t * const buf = AMediaCodec_getOutputBuffer( codec.get(), status, nullptr /* out_size */); if (buf == nullptr) { ALOGE("%s: AMediaCodec_getOutputBuffer returned nullptr, short decode", __func__); break; } const size_t dataSize = std::min((size_t)info.size, available); memcpy(writePos, buf + info.offset, dataSize); writePos += dataSize; written += dataSize; available -= dataSize; const media_status_t mstatus = AMediaCodec_releaseOutputBuffer( codec.get(), status, false /* render */); if (mstatus != AMEDIA_OK) { // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES } ALOGE("%s: AMediaCodec_releaseOutputBuffer" " returned status %d, short decode", __func__, (int)mstatus); break; } if (available == 0) { // there might be more data, but there's no space for it sawOutputEOS = true; } } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) { ALOGV("%s: output buffers changed", __func__); } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) { format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format ALOGV("%s: format changed to: %s", __func__, AMediaFormat_toString(format.get())); } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) { ALOGV("%s: no output buffer right now", __func__); } else if (status <= AMEDIA_ERROR_BASE) { ALOGE("%s: decode error: %zd", __func__, status); break; } else { ALOGV("%s: unexpected info code: %zd", __func__, status); } } (void)AMediaCodec_stop(codec.get()); if (!AMediaFormat_getInt32( format.get(), AMEDIAFORMAT_KEY_SAMPLE_RATE, (int32_t*) rate) || !AMediaFormat_getInt32( format.get(), AMEDIAFORMAT_KEY_CHANNEL_COUNT, channelCount)) { return UNKNOWN_ERROR; } if (!AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_CHANNEL_MASK, (int32_t*) channelMask)) { *channelMask = AUDIO_CHANNEL_NONE; } *sizeInBytes = written; return OK; } } return UNKNOWN_ERROR; } status_t Sound::doLoad() { ALOGV("%s()", __func__); status_t status = NO_INIT; if (mFd.get() != -1) { mHeap = new MemoryHeapBase(kDefaultHeapSize); ALOGV("%s: start decode", __func__); uint32_t sampleRate; int32_t channelCount; audio_format_t format; audio_channel_mask_t channelMask; status = decode(mFd.get(), mOffset, mLength, &sampleRate, &channelCount, &format, &channelMask, mHeap, &mSizeInBytes); ALOGV("%s: close(%d)", __func__, mFd.get()); mFd.reset(); // close if (status != NO_ERROR) { ALOGE("%s: unable to load sound", __func__); } else if (sampleRate > kMaxSampleRate) { ALOGE("%s: sample rate (%u) out of range", __func__, sampleRate); status = BAD_VALUE; } else if (channelCount < 1 || channelCount > FCC_LIMIT) { ALOGE("%s: sample channel count (%d) out of range", __func__, channelCount); status = BAD_VALUE; } else { // Correctly loaded, proper parameters ALOGV("%s: pointer = %p, sizeInBytes = %zu, sampleRate = %u, channelCount = %d", __func__, mHeap->getBase(), mSizeInBytes, sampleRate, channelCount); mData = new MemoryBase(mHeap, 0, mSizeInBytes); mSampleRate = sampleRate; mChannelCount = channelCount; mFormat = format; mChannelMask = channelMask; mState = READY; // this should be last, as it is an atomic sync point return NO_ERROR; } } else { ALOGE("%s: uninitialized fd, dup failed", __func__); } // ERROR handling mHeap.clear(); mState = DECODE_ERROR; // this should be last, as it is an atomic sync point return status; } } // namespace android::soundpool