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

Commit 767e3170 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Transcoder: Added PassthroughTrackTranscoder and unit tests."

parents 35cb7a6e c6221dbe
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