|  | /* | 
|  | * Copyright 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 "FrameReassembler" | 
|  |  | 
|  | #include <log/log.h> | 
|  |  | 
|  | #include <media/stagefright/foundation/AMessage.h> | 
|  |  | 
|  | #include "FrameReassembler.h" | 
|  |  | 
|  | namespace android { | 
|  |  | 
|  | static constexpr uint64_t kToleranceUs = 1000;  // 1ms | 
|  |  | 
|  | FrameReassembler::FrameReassembler() | 
|  | : mUsage{0, 0}, | 
|  | mSampleRate(0u), | 
|  | mChannelCount(0u), | 
|  | mEncoding(C2Config::PCM_16), | 
|  | mCurrentOrdinal({0, 0, 0}) { | 
|  | } | 
|  |  | 
|  | void FrameReassembler::init( | 
|  | const std::shared_ptr<C2BlockPool> &pool, | 
|  | C2MemoryUsage usage, | 
|  | uint32_t frameSize, | 
|  | uint32_t sampleRate, | 
|  | uint32_t channelCount, | 
|  | C2Config::pcm_encoding_t encoding) { | 
|  | mBlockPool = pool; | 
|  | mUsage = usage; | 
|  | mFrameSize = frameSize; | 
|  | mSampleRate = sampleRate; | 
|  | mChannelCount = channelCount; | 
|  | mEncoding = encoding; | 
|  | } | 
|  |  | 
|  | void FrameReassembler::updateFrameSize(uint32_t frameSize) { | 
|  | finishCurrentBlock(&mPendingWork); | 
|  | mFrameSize = frameSize; | 
|  | } | 
|  |  | 
|  | void FrameReassembler::updateSampleRate(uint32_t sampleRate) { | 
|  | finishCurrentBlock(&mPendingWork); | 
|  | mSampleRate = sampleRate; | 
|  | } | 
|  |  | 
|  | void FrameReassembler::updateChannelCount(uint32_t channelCount) { | 
|  | finishCurrentBlock(&mPendingWork); | 
|  | mChannelCount = channelCount; | 
|  | } | 
|  |  | 
|  | void FrameReassembler::updatePcmEncoding(C2Config::pcm_encoding_t encoding) { | 
|  | finishCurrentBlock(&mPendingWork); | 
|  | mEncoding = encoding; | 
|  | } | 
|  |  | 
|  | void FrameReassembler::reset() { | 
|  | flush(); | 
|  | mCurrentOrdinal = {0, 0, 0}; | 
|  | mBlockPool.reset(); | 
|  | mFrameSize.reset(); | 
|  | mSampleRate = 0u; | 
|  | mChannelCount = 0u; | 
|  | mEncoding = C2Config::PCM_16; | 
|  | } | 
|  |  | 
|  | FrameReassembler::operator bool() const { | 
|  | return mFrameSize.has_value(); | 
|  | } | 
|  |  | 
|  | c2_status_t FrameReassembler::process( | 
|  | const sp<MediaCodecBuffer> &buffer, | 
|  | std::list<std::unique_ptr<C2Work>> *items) { | 
|  | int64_t timeUs; | 
|  | if (!buffer->meta()->findInt64("timeUs", &timeUs)) { | 
|  | return C2_BAD_VALUE; | 
|  | } | 
|  |  | 
|  | items->splice(items->end(), mPendingWork); | 
|  |  | 
|  | // Fill mCurrentBlock | 
|  | if (mCurrentBlock) { | 
|  | // First check the timestamp | 
|  | c2_cntr64_t endTimestampUs = mCurrentOrdinal.timestamp; | 
|  | endTimestampUs += bytesToSamples(mWriteView->size()) * 1000000 / mSampleRate; | 
|  | if (timeUs < endTimestampUs.peek()) { | 
|  | uint64_t diffUs = (endTimestampUs - timeUs).peeku(); | 
|  | if (diffUs > kToleranceUs) { | 
|  | // The timestamp is going back in time in large amount. | 
|  | // TODO: b/145702136 | 
|  | ALOGW("timestamp going back in time! from %lld to %lld", | 
|  | endTimestampUs.peekll(), (long long)timeUs); | 
|  | } | 
|  | } else {  // timeUs >= endTimestampUs.peek() | 
|  | uint64_t diffUs = (timeUs - endTimestampUs).peeku(); | 
|  | if (diffUs > kToleranceUs) { | 
|  | // The timestamp is going forward; add silence as necessary. | 
|  | size_t gapSamples = usToSamples(diffUs); | 
|  | size_t remainingSamples = | 
|  | (mWriteView->capacity() - mWriteView->size()) | 
|  | / mChannelCount / bytesPerSample(); | 
|  | if (gapSamples < remainingSamples) { | 
|  | size_t gapBytes = gapSamples * mChannelCount * bytesPerSample(); | 
|  | memset(mWriteView->base() + mWriteView->size(), 0u, gapBytes); | 
|  | mWriteView->setSize(mWriteView->size() + gapBytes); | 
|  | } else { | 
|  | finishCurrentBlock(items); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (mCurrentBlock) { | 
|  | // Append the data at the end of the current block | 
|  | size_t copySize = std::min( | 
|  | buffer->size(), | 
|  | size_t(mWriteView->capacity() - mWriteView->size())); | 
|  | memcpy(mWriteView->base() + mWriteView->size(), buffer->data(), copySize); | 
|  | buffer->setRange(buffer->offset() + copySize, buffer->size() - copySize); | 
|  | mWriteView->setSize(mWriteView->size() + copySize); | 
|  | if (mWriteView->size() == mWriteView->capacity()) { | 
|  | finishCurrentBlock(items); | 
|  | } | 
|  | timeUs += bytesToSamples(copySize) * 1000000 / mSampleRate; | 
|  | } | 
|  |  | 
|  | if (buffer->size() > 0) { | 
|  | mCurrentOrdinal.timestamp = timeUs; | 
|  | mCurrentOrdinal.customOrdinal = timeUs; | 
|  | } | 
|  |  | 
|  | size_t frameSizeBytes = mFrameSize.value() * mChannelCount * bytesPerSample(); | 
|  | while (buffer->size() > 0) { | 
|  | LOG_ALWAYS_FATAL_IF( | 
|  | mCurrentBlock, | 
|  | "There's remaining data but the pending block is not filled & finished"); | 
|  | std::unique_ptr<C2Work> work(new C2Work); | 
|  | c2_status_t err = mBlockPool->fetchLinearBlock(frameSizeBytes, mUsage, &mCurrentBlock); | 
|  | if (err != C2_OK) { | 
|  | return err; | 
|  | } | 
|  | size_t copySize = std::min(buffer->size(), frameSizeBytes); | 
|  | mWriteView = mCurrentBlock->map().get(); | 
|  | if (mWriteView->error() != C2_OK) { | 
|  | return mWriteView->error(); | 
|  | } | 
|  | ALOGV("buffer={offset=%zu size=%zu} copySize=%zu", | 
|  | buffer->offset(), buffer->size(), copySize); | 
|  | memcpy(mWriteView->base(), buffer->data(), copySize); | 
|  | mWriteView->setOffset(0u); | 
|  | mWriteView->setSize(copySize); | 
|  | buffer->setRange(buffer->offset() + copySize, buffer->size() - copySize); | 
|  | if (copySize == frameSizeBytes) { | 
|  | finishCurrentBlock(items); | 
|  | } | 
|  | } | 
|  |  | 
|  | int32_t eos = 0; | 
|  | if (buffer->meta()->findInt32("eos", &eos) && eos) { | 
|  | finishCurrentBlock(items); | 
|  | } | 
|  |  | 
|  | return C2_OK; | 
|  | } | 
|  |  | 
|  | void FrameReassembler::flush() { | 
|  | mPendingWork.clear(); | 
|  | mWriteView.reset(); | 
|  | mCurrentBlock.reset(); | 
|  | } | 
|  |  | 
|  | uint64_t FrameReassembler::bytesToSamples(size_t numBytes) const { | 
|  | return numBytes / mChannelCount / bytesPerSample(); | 
|  | } | 
|  |  | 
|  | size_t FrameReassembler::usToSamples(uint64_t us) const { | 
|  | return (us * mChannelCount * mSampleRate / 1000000); | 
|  | } | 
|  |  | 
|  | uint32_t FrameReassembler::bytesPerSample() const { | 
|  | return (mEncoding == C2Config::PCM_8) ? 1 | 
|  | : (mEncoding == C2Config::PCM_16) ? 2 | 
|  | : (mEncoding == C2Config::PCM_FLOAT) ? 4 : 0; | 
|  | } | 
|  |  | 
|  | void FrameReassembler::finishCurrentBlock(std::list<std::unique_ptr<C2Work>> *items) { | 
|  | if (!mCurrentBlock) { | 
|  | // No-op | 
|  | return; | 
|  | } | 
|  | if (mWriteView->size() < mWriteView->capacity()) { | 
|  | memset(mWriteView->base() + mWriteView->size(), 0u, | 
|  | mWriteView->capacity() - mWriteView->size()); | 
|  | mWriteView->setSize(mWriteView->capacity()); | 
|  | } | 
|  | std::unique_ptr<C2Work> work{std::make_unique<C2Work>()}; | 
|  | work->input.ordinal = mCurrentOrdinal; | 
|  | work->input.buffers.push_back(C2Buffer::CreateLinearBuffer( | 
|  | mCurrentBlock->share(0, mCurrentBlock->capacity(), C2Fence()))); | 
|  | work->worklets.clear(); | 
|  | work->worklets.emplace_back(new C2Worklet); | 
|  | items->push_back(std::move(work)); | 
|  |  | 
|  | ++mCurrentOrdinal.frameIndex; | 
|  | mCurrentOrdinal.timestamp += mFrameSize.value() * 1000000 / mSampleRate; | 
|  | mCurrentOrdinal.customOrdinal = mCurrentOrdinal.timestamp; | 
|  | mCurrentBlock.reset(); | 
|  | mWriteView.reset(); | 
|  | } | 
|  |  | 
|  | }  // namespace android |