Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c6221dbe authored by Linus Nilsson's avatar Linus Nilsson
Browse files

Transcoder: Added PassthroughTrackTranscoder and unit tests.

PassthroughTrackTranscoder is a MediaTrackTranscoder implementation for passthrough mode.
It manages an internal buffer pool to reuse buffers.
This commit also ensures that it is safe to hold on to dequeued MediaSamples
after the MediaTrackTranscoder is released.

Test: Unit test.
Bug: 152091443
Change-Id: I876957647c5fee0557caf465227ff42cdb0eceee
parent da685089
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ cc_library_shared {
        "MediaSampleQueue.cpp",
        "MediaSampleReaderNDK.cpp",
        "MediaTrackTranscoder.cpp",
        "PassthroughTrackTranscoder.cpp",
        "VideoTrackTranscoder.cpp",
    ],

+156 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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 "PassthroughTrackTranscoder"

#include <android-base/logging.h>
#include <media/PassthroughTrackTranscoder.h>

namespace android {

PassthroughTrackTranscoder::BufferPool::~BufferPool() {
    for (auto it = mAddressSizeMap.begin(); it != mAddressSizeMap.end(); ++it) {
        delete[] it->first;
    }
}

uint8_t* PassthroughTrackTranscoder::BufferPool::getBufferWithSize(size_t minimumBufferSize)
        NO_THREAD_SAFETY_ANALYSIS {
    std::unique_lock lock(mMutex);

    // Wait if maximum number of buffers are allocated but none are free.
    while (mAddressSizeMap.size() >= mMaxBufferCount && mFreeBufferMap.empty() && !mAborted) {
        mCondition.wait(lock);
    }

    if (mAborted) {
        return nullptr;
    }

    // Check if the free list contains a large enough buffer.
    auto it = mFreeBufferMap.lower_bound(minimumBufferSize);
    if (it != mFreeBufferMap.end()) {
        mFreeBufferMap.erase(it);
        return it->second;
    }

    // Allocate a new buffer.
    uint8_t* buffer = new (std::nothrow) uint8_t[minimumBufferSize];
    if (buffer == nullptr) {
        LOG(ERROR) << "Unable to allocate new buffer of size: " << minimumBufferSize;
        return nullptr;
    }

    // If the maximum buffer count is reached, remove an existing free buffer.
    if (mAddressSizeMap.size() >= mMaxBufferCount) {
        auto it = mFreeBufferMap.begin();
        mFreeBufferMap.erase(it);
        mAddressSizeMap.erase(it->second);
        delete[] it->second;
    }

    // Add the buffer to the tracking set.
    mAddressSizeMap.emplace(buffer, minimumBufferSize);
    return buffer;
}

void PassthroughTrackTranscoder::BufferPool::returnBuffer(uint8_t* buffer) {
    std::scoped_lock lock(mMutex);

    if (buffer == nullptr || mAddressSizeMap.find(buffer) == mAddressSizeMap.end()) {
        LOG(WARNING) << "Ignoring untracked buffer " << buffer;
        return;
    }

    mFreeBufferMap.emplace(mAddressSizeMap[buffer], buffer);
    mCondition.notify_one();
}

void PassthroughTrackTranscoder::BufferPool::abort() {
    std::scoped_lock lock(mMutex);
    mAborted = true;
    mCondition.notify_all();
}

media_status_t PassthroughTrackTranscoder::configureDestinationFormat(
        const std::shared_ptr<AMediaFormat>& destinationFormat __unused) {
    // Called by MediaTrackTranscoder. Passthrough doesn't care about destination so just return ok.
    return AMEDIA_OK;
}

media_status_t PassthroughTrackTranscoder::runTranscodeLoop() {
    MediaSampleInfo info;
    std::shared_ptr<MediaSample> sample;

    MediaSample::OnSampleReleasedCallback bufferReleaseCallback =
            [bufferPool = mBufferPool](MediaSample* sample) {
                bufferPool->returnBuffer(const_cast<uint8_t*>(sample->buffer));
            };

    // Move samples until EOS is reached or transcoding is stopped.
    while (!mStopRequested && !mEosFromSource) {
        media_status_t status = mMediaSampleReader->getSampleInfoForTrack(mTrackIndex, &info);

        if (status == AMEDIA_OK) {
            uint8_t* buffer = mBufferPool->getBufferWithSize(info.size);
            if (buffer == nullptr) {
                if (mStopRequested) {
                    break;
                }

                LOG(ERROR) << "Unable to get buffer from pool";
                return AMEDIA_ERROR_IO;  // TODO: Custom error codes?
            }

            sample = MediaSample::createWithReleaseCallback(
                    buffer, 0 /* offset */, 0 /* bufferId */, bufferReleaseCallback);

            status = mMediaSampleReader->readSampleDataForTrack(mTrackIndex, buffer, info.size);
            if (status != AMEDIA_OK) {
                LOG(ERROR) << "Unable to read next sample data. Aborting transcode.";
                return status;
            }

        } else if (status == AMEDIA_ERROR_END_OF_STREAM) {
            sample = std::make_shared<MediaSample>();
            mEosFromSource = true;
        } else {
            LOG(ERROR) << "Unable to get next sample info. Aborting transcode.";
            return status;
        }

        sample->info = info;
        if (mOutputQueue.enqueue(sample)) {
            LOG(ERROR) << "Output queue aborted";
            return AMEDIA_ERROR_IO;
        }

        mMediaSampleReader->advanceTrack(mTrackIndex);
    }

    if (mStopRequested && !mEosFromSource) {
        return AMEDIA_ERROR_UNKNOWN;  // TODO: Custom error codes?
    }
    return AMEDIA_OK;
}

void PassthroughTrackTranscoder::abortTranscodeLoop() {
    mStopRequested = true;
    mBufferPool->abort();
}

}  // namespace android
+24 −26
Original line number Diff line number Diff line
@@ -71,7 +71,7 @@ struct AsyncCodecCallbackDispatch {
        transcoder->mCodecMessageQueue.push([transcoder, index, codec, bufferInfo] {
            if (codec == transcoder->mDecoder) {
                transcoder->transferBuffer(index, bufferInfo);
            } else if (codec == transcoder->mEncoder) {
            } else if (codec == transcoder->mEncoder.get()) {
                transcoder->dequeueOutputSample(index, bufferInfo);
            }
        });
@@ -102,10 +102,6 @@ VideoTrackTranscoder::~VideoTrackTranscoder() {
        AMediaCodec_delete(mDecoder);
    }

    if (mEncoder != nullptr) {
        AMediaCodec_delete(mEncoder);
    }

    if (mSurface != nullptr) {
        ANativeWindow_release(mSurface);
    }
@@ -132,20 +128,22 @@ media_status_t VideoTrackTranscoder::configureDestinationFormat(
        return AMEDIA_ERROR_INVALID_PARAMETER;
    }

    mEncoder = AMediaCodec_createEncoderByType(destinationMime);
    if (mEncoder == nullptr) {
    AMediaCodec* encoder = AMediaCodec_createEncoderByType(destinationMime);
    if (encoder == nullptr) {
        LOG(ERROR) << "Unable to create encoder for type " << destinationMime;
        return AMEDIA_ERROR_UNSUPPORTED;
    }
    mEncoder = std::shared_ptr<AMediaCodec>(encoder,
                                            std::bind(AMediaCodec_delete, std::placeholders::_1));

    status = AMediaCodec_configure(mEncoder, mDestinationFormat.get(), NULL /* surface */,
    status = AMediaCodec_configure(mEncoder.get(), mDestinationFormat.get(), NULL /* surface */,
                                   NULL /* crypto */, AMEDIACODEC_CONFIGURE_FLAG_ENCODE);
    if (status != AMEDIA_OK) {
        LOG(ERROR) << "Unable to configure video encoder: " << status;
        return status;
    }

    status = AMediaCodec_createInputSurface(mEncoder, &mSurface);
    status = AMediaCodec_createInputSurface(mEncoder.get(), &mSurface);
    if (status != AMEDIA_OK) {
        LOG(ERROR) << "Unable to create an encoder input surface: %d" << status;
        return status;
@@ -185,7 +183,7 @@ media_status_t VideoTrackTranscoder::configureDestinationFormat(
        return status;
    }

    status = AMediaCodec_setAsyncNotifyCallback(mEncoder, asyncCodecCallbacks, this);
    status = AMediaCodec_setAsyncNotifyCallback(mEncoder.get(), asyncCodecCallbacks, this);
    if (status != AMEDIA_OK) {
        LOG(ERROR) << "Unable to set encoder to async mode: " << status;
        return status;
@@ -197,7 +195,7 @@ media_status_t VideoTrackTranscoder::configureDestinationFormat(
void VideoTrackTranscoder::enqueueInputSample(int32_t bufferIndex) {
    media_status_t status = AMEDIA_OK;

    if (mEOSFromSource) {
    if (mEosFromSource) {
        return;
    }

@@ -233,7 +231,7 @@ void VideoTrackTranscoder::enqueueInputSample(int32_t bufferIndex) {
        mMediaSampleReader->advanceTrack(mTrackIndex);
    } else {
        LOG(DEBUG) << "EOS from source.";
        mEOSFromSource = true;
        mEosFromSource = true;
    }

    status = AMediaCodec_queueInputBuffer(mDecoder, bufferIndex, 0, mSampleInfo.size,
@@ -253,7 +251,7 @@ void VideoTrackTranscoder::transferBuffer(int32_t bufferIndex, AMediaCodecBuffer

    if (bufferInfo.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
        LOG(DEBUG) << "EOS from decoder.";
        media_status_t status = AMediaCodec_signalEndOfInputStream(mEncoder);
        media_status_t status = AMediaCodec_signalEndOfInputStream(mEncoder.get());
        if (status != AMEDIA_OK) {
            LOG(ERROR) << "SignalEOS on encoder returned error: " << status;
            mStatus = status;
@@ -265,11 +263,15 @@ void VideoTrackTranscoder::dequeueOutputSample(int32_t bufferIndex,
                                               AMediaCodecBufferInfo bufferInfo) {
    if (bufferIndex >= 0) {
        size_t sampleSize = 0;
        uint8_t* buffer = AMediaCodec_getOutputBuffer(mEncoder, bufferIndex, &sampleSize);
        uint8_t* buffer = AMediaCodec_getOutputBuffer(mEncoder.get(), bufferIndex, &sampleSize);

        MediaSample::OnSampleReleasedCallback bufferReleaseCallback = [encoder = mEncoder](
                                                                              MediaSample* sample) {
            AMediaCodec_releaseOutputBuffer(encoder.get(), sample->bufferId, false /* render */);
        };

        std::shared_ptr<MediaSample> sample = MediaSample::createWithReleaseCallback(
                buffer, bufferInfo.offset, bufferIndex,
                std::bind(&VideoTrackTranscoder::releaseOutputSample, this, std::placeholders::_1));
                buffer, bufferInfo.offset, bufferIndex, bufferReleaseCallback);
        sample->info.size = bufferInfo.size;
        sample->info.flags = bufferInfo.flags;
        sample->info.presentationTimeUs = bufferInfo.presentationTimeUs;
@@ -281,18 +283,14 @@ void VideoTrackTranscoder::dequeueOutputSample(int32_t bufferIndex,
            return;
        }
    } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
        AMediaFormat* newFormat = AMediaCodec_getOutputFormat(mEncoder);
        AMediaFormat* newFormat = AMediaCodec_getOutputFormat(mEncoder.get());
        LOG(DEBUG) << "Encoder output format changed: " << AMediaFormat_toString(newFormat);
    }

    if (bufferInfo.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
        LOG(DEBUG) << "EOS from encoder.";
        mEOSFromEncoder = true;
    }
        mEosFromEncoder = true;
    }

void VideoTrackTranscoder::releaseOutputSample(MediaSample* sample) {
    AMediaCodec_releaseOutputBuffer(mEncoder, sample->bufferId, false /* render */);
}

media_status_t VideoTrackTranscoder::runTranscodeLoop() {
@@ -304,7 +302,7 @@ media_status_t VideoTrackTranscoder::runTranscodeLoop() {
        return status;
    }

    status = AMediaCodec_start(mEncoder);
    status = AMediaCodec_start(mEncoder.get());
    if (status != AMEDIA_OK) {
        LOG(ERROR) << "Unable to start video encoder: " << status;
        AMediaCodec_stop(mDecoder);
@@ -312,18 +310,18 @@ media_status_t VideoTrackTranscoder::runTranscodeLoop() {
    }

    // Process codec events until EOS is reached, transcoding is stopped or an error occurs.
    while (!mStopRequested && !mEOSFromEncoder && mStatus == AMEDIA_OK) {
    while (!mStopRequested && !mEosFromEncoder && mStatus == AMEDIA_OK) {
        std::function<void()> message = mCodecMessageQueue.pop();
        message();
    }

    // Return error if transcoding was stopped before it finished.
    if (mStopRequested && !mEOSFromEncoder && mStatus == AMEDIA_OK) {
    if (mStopRequested && !mEosFromEncoder && mStatus == AMEDIA_OK) {
        mStatus = AMEDIA_ERROR_UNKNOWN;  // TODO: Define custom error codes?
    }

    AMediaCodec_stop(mDecoder);
    AMediaCodec_stop(mEncoder);
    AMediaCodec_stop(mEncoder.get());
    return mStatus;
}

+2 −0
Original line number Diff line number Diff line
@@ -104,6 +104,8 @@ struct MediaSample {
    /** Media sample information. */
    MediaSampleInfo info;

    MediaSample() = default;

private:
    MediaSample(uint8_t* buffer, size_t dataOffset, uint32_t bufferId,
                OnSampleReleasedCallback releaseCallback)
+5 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@

#include <functional>
#include <memory>
#include <mutex>
#include <thread>

namespace android {
@@ -94,7 +95,10 @@ public:
     */
    bool stop();

    /** Sample output queue. */
    /**
     * Sample output queue.
     * TODO(b/155918341) Move to protected.
     */
    MediaSampleQueue mOutputQueue = {};

protected:
Loading