| /* |
| * 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 <utils/Log.h> |
| |
| #include "Sound.h" |
| |
| #include <media/NdkMediaCodec.h> |
| #include <media/NdkMediaExtractor.h> |
| #include <media/NdkMediaFormat.h> |
| |
| 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<MemoryHeapBase>& heap, |
| size_t *sizeInBytes) { |
| ALOGV("%s(fd=%d, offset=%lld, length=%lld, ...)", |
| __func__, fd, (long long)offset, (long long)length); |
| std::unique_ptr<AMediaExtractor, decltype(&AMediaExtractor_delete)> 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<AMediaFormat, decltype(&AMediaFormat_delete)> 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<AMediaCodec, decltype(&AMediaCodec_delete)> 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<uint8_t*>(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(available, (size_t)std::max(info.size, 0)); |
| 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; |
| } |
| int32_t mediaFormatChannelMask; |
| if (AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_CHANNEL_MASK, |
| &mediaFormatChannelMask)) { |
| *channelMask = audio_channel_mask_from_media_format_mask(mediaFormatChannelMask); |
| } else { |
| *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 |