// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//#define LOG_NDEBUG 0
#define LOG_TAG "V4L2EncodeComponent"

#include <v4l2_codec2/components/V4L2EncodeComponent.h>

#include <inttypes.h>

#include <algorithm>
#include <utility>

#include <C2AllocatorGralloc.h>
#include <C2PlatformSupport.h>
#include <C2Work.h>
#include <android/hardware/graphics/common/1.0/types.h>
#include <base/bind.h>
#include <base/bind_helpers.h>
#include <log/log.h>
#include <media/stagefright/MediaDefs.h>
#include <ui/GraphicBuffer.h>

#include <fourcc.h>
#include <h264_parser.h>
#include <rect.h>
#include <v4l2_codec2/common/Common.h>
#include <v4l2_codec2/common/EncodeHelpers.h>
#include <v4l2_device.h>
#include <video_pixel_format.h>

using android::hardware::graphics::common::V1_0::BufferUsage;

namespace android {

namespace {

const media::VideoPixelFormat kInputPixelFormat = media::VideoPixelFormat::PIXEL_FORMAT_NV12;

// Get the video frame layout from the specified |inputBlock|.
// TODO(dstaessens): Clean up code extracting layout from a C2GraphicBlock.
std::optional<std::vector<VideoFramePlane>> getVideoFrameLayout(const C2ConstGraphicBlock& block,
                                                                media::VideoPixelFormat* format) {
    ALOGV("%s()", __func__);

    // Get the C2PlanarLayout from the graphics block. The C2GraphicView returned by block.map()
    // needs to be released before calling getGraphicBlockInfo(), or the lockYCbCr() call will block
    // Indefinitely.
    C2PlanarLayout layout = block.map().get().layout();

    // The above layout() cannot fill layout information and memset 0 instead if the input format is
    // IMPLEMENTATION_DEFINED and its backed format is RGB. We fill the layout by using
    // ImplDefinedToRGBXMap in the case.
    if (layout.type == C2PlanarLayout::TYPE_UNKNOWN) {
        std::unique_ptr<ImplDefinedToRGBXMap> idMap = ImplDefinedToRGBXMap::Create(block);
        if (idMap == nullptr) {
            ALOGE("Unable to parse RGBX_8888 from IMPLEMENTATION_DEFINED");
            return std::nullopt;
        }
        layout.type = C2PlanarLayout::TYPE_RGB;
        // These parameters would be used in TYPE_GRB case below.
        layout.numPlanes = 3;   // same value as in C2AllocationGralloc::map()
        layout.rootPlanes = 1;  // same value as in C2AllocationGralloc::map()
        layout.planes[C2PlanarLayout::PLANE_R].offset = idMap->offset();
        layout.planes[C2PlanarLayout::PLANE_R].rowInc = idMap->rowInc();
    }

    std::vector<uint32_t> offsets(layout.numPlanes, 0u);
    std::vector<uint32_t> strides(layout.numPlanes, 0u);
    switch (layout.type) {
    case C2PlanarLayout::TYPE_YUV: {
        android_ycbcr ycbcr = getGraphicBlockInfo(block);
        offsets[C2PlanarLayout::PLANE_Y] =
                static_cast<uint32_t>(reinterpret_cast<uintptr_t>(ycbcr.y));
        offsets[C2PlanarLayout::PLANE_U] =
                static_cast<uint32_t>(reinterpret_cast<uintptr_t>(ycbcr.cb));
        offsets[C2PlanarLayout::PLANE_V] =
                static_cast<uint32_t>(reinterpret_cast<uintptr_t>(ycbcr.cr));
        strides[C2PlanarLayout::PLANE_Y] = static_cast<uint32_t>(ycbcr.ystride);
        strides[C2PlanarLayout::PLANE_U] = static_cast<uint32_t>(ycbcr.cstride);
        strides[C2PlanarLayout::PLANE_V] = static_cast<uint32_t>(ycbcr.cstride);

        bool crcb = false;
        if (offsets[C2PlanarLayout::PLANE_U] > offsets[C2PlanarLayout::PLANE_V]) {
            // Swap offsets, no need to swap strides as they are identical for both chroma planes.
            std::swap(offsets[C2PlanarLayout::PLANE_U], offsets[C2PlanarLayout::PLANE_V]);
            crcb = true;
        }

        bool semiplanar = false;
        if (ycbcr.chroma_step >
            offsets[C2PlanarLayout::PLANE_V] - offsets[C2PlanarLayout::PLANE_U]) {
            semiplanar = true;
        }

        if (!crcb && !semiplanar) {
            *format = media::VideoPixelFormat::PIXEL_FORMAT_I420;
        } else if (!crcb && semiplanar) {
            *format = media::VideoPixelFormat::PIXEL_FORMAT_NV12;
        } else if (crcb && !semiplanar) {
            // HACK: pretend YV12 is I420 now since VEA only accepts I420. (YV12 will be used
            //       for input byte-buffer mode).
            // TODO(dstaessens): Is this hack still necessary now we're not using the VEA directly?
            //format = media::VideoPixelFormat::PIXEL_FORMAT_YV12;
            *format = media::VideoPixelFormat::PIXEL_FORMAT_I420;
        } else {
            *format = media::VideoPixelFormat::PIXEL_FORMAT_NV21;
        }
        break;
    }
    case C2PlanarLayout::TYPE_RGB: {
        offsets[C2PlanarLayout::PLANE_R] = layout.planes[C2PlanarLayout::PLANE_R].offset;
        strides[C2PlanarLayout::PLANE_R] =
                static_cast<uint32_t>(layout.planes[C2PlanarLayout::PLANE_R].rowInc);
        *format = media::VideoPixelFormat::PIXEL_FORMAT_ARGB;
        break;
    }
    default:
        ALOGW("Unknown layout type: %u", static_cast<uint32_t>(layout.type));
        return std::nullopt;
    }

    std::vector<VideoFramePlane> planes;
    for (uint32_t i = 0; i < layout.rootPlanes; ++i) {
        planes.push_back({offsets[i], strides[i]});
    }
    return planes;
}

// The maximum size for output buffer, which is chosen empirically for a 1080p video.
constexpr size_t kMaxBitstreamBufferSizeInBytes = 2 * 1024 * 1024;  // 2MB
// The frame size for 1080p (FHD) video in pixels.
constexpr int k1080PSizeInPixels = 1920 * 1080;
// The frame size for 1440p (QHD) video in pixels.
constexpr int k1440PSizeInPixels = 2560 * 1440;

// Use quadruple size of kMaxBitstreamBufferSizeInBytes when the input frame size is larger than
// 1440p, double if larger than 1080p. This is chosen empirically for some 4k encoding use cases and
// the Android CTS VideoEncoderTest (crbug.com/927284).
size_t GetMaxOutputBufferSize(const media::Size& size) {
    if (size.GetArea() > k1440PSizeInPixels) return kMaxBitstreamBufferSizeInBytes * 4;
    if (size.GetArea() > k1080PSizeInPixels) return kMaxBitstreamBufferSizeInBytes * 2;
    return kMaxBitstreamBufferSizeInBytes;
}

// These are rather subjectively tuned.
constexpr size_t kInputBufferCount = 2;
constexpr size_t kOutputBufferCount = 2;

// Define V4L2_CID_MPEG_VIDEO_H264_SPS_PPS_BEFORE_IDR control code if not present in header files.
#ifndef V4L2_CID_MPEG_VIDEO_H264_SPS_PPS_BEFORE_IDR
#define V4L2_CID_MPEG_VIDEO_H264_SPS_PPS_BEFORE_IDR (V4L2_CID_MPEG_BASE + 388)
#endif

}  // namespace

// static
std::unique_ptr<V4L2EncodeComponent::InputFrame> V4L2EncodeComponent::InputFrame::Create(
        const C2ConstGraphicBlock& block) {
    std::vector<int> fds;
    const C2Handle* const handle = block.handle();
    for (int i = 0; i < handle->numFds; i++) {
        fds.emplace_back(handle->data[i]);
    }

    return std::unique_ptr<InputFrame>(new InputFrame(std::move(fds)));
}

// static
std::shared_ptr<C2Component> V4L2EncodeComponent::create(
        C2String name, c2_node_id_t id, std::shared_ptr<C2ReflectorHelper> helper,
        C2ComponentFactory::ComponentDeleter deleter) {
    ALOGV("%s(%s)", __func__, name.c_str());

    auto interface = std::make_shared<V4L2EncodeInterface>(name, std::move(helper));
    if (interface->status() != C2_OK) {
        ALOGE("Component interface initialization failed (error code %d)", interface->status());
        return nullptr;
    }

    return std::shared_ptr<C2Component>(new V4L2EncodeComponent(name, id, std::move(interface)),
                                        deleter);
}

V4L2EncodeComponent::V4L2EncodeComponent(C2String name, c2_node_id_t id,
                                         std::shared_ptr<V4L2EncodeInterface> interface)
      : mName(name),
        mId(id),
        mInterface(std::move(interface)),
        mComponentState(ComponentState::LOADED) {
    ALOGV("%s(%s)", __func__, name.c_str());
}

V4L2EncodeComponent::~V4L2EncodeComponent() {
    ALOGV("%s()", __func__);

    // Stop encoder thread and invalidate pointers if component wasn't stopped before destroying.
    if (mEncoderThread.IsRunning()) {
        mEncoderTaskRunner->PostTask(
                FROM_HERE, ::base::BindOnce(
                                   [](::base::WeakPtrFactory<V4L2EncodeComponent>* weakPtrFactory) {
                                       weakPtrFactory->InvalidateWeakPtrs();
                                   },
                                   &mWeakThisFactory));
        mEncoderThread.Stop();
    }
    ALOGV("%s(): done", __func__);
}

c2_status_t V4L2EncodeComponent::start() {
    ALOGV("%s()", __func__);

    // Lock while starting, to synchronize start/stop/reset/release calls.
    std::lock_guard<std::mutex> lock(mComponentLock);

    // According to the specification start() should only be called in the LOADED state.
    if (mComponentState != ComponentState::LOADED) {
        return C2_BAD_STATE;
    }

    if (!mEncoderThread.Start()) {
        ALOGE("Failed to start encoder thread");
        return C2_CORRUPTED;
    }
    mEncoderTaskRunner = mEncoderThread.task_runner();
    mWeakThis = mWeakThisFactory.GetWeakPtr();

    // Initialize the encoder on the encoder thread.
    ::base::WaitableEvent done;
    bool success = false;
    mEncoderTaskRunner->PostTask(
            FROM_HERE, ::base::Bind(&V4L2EncodeComponent::startTask, mWeakThis, &success, &done));
    done.Wait();

    if (!success) {
        ALOGE("Failed to initialize encoder");
        return C2_CORRUPTED;
    }

    setComponentState(ComponentState::RUNNING);
    return C2_OK;
}

c2_status_t V4L2EncodeComponent::stop() {
    ALOGV("%s()", __func__);

    // Lock while stopping, to synchronize start/stop/reset/release calls.
    std::lock_guard<std::mutex> lock(mComponentLock);

    if (mComponentState != ComponentState::RUNNING && mComponentState != ComponentState::ERROR) {
        return C2_BAD_STATE;
    }

    // Return immediately if the component is already stopped.
    if (!mEncoderThread.IsRunning()) {
        return C2_OK;
    }

    // Wait for the component to stop.
    ::base::WaitableEvent done;
    mEncoderTaskRunner->PostTask(
            FROM_HERE, ::base::BindOnce(&V4L2EncodeComponent::stopTask, mWeakThis, &done));
    done.Wait();
    mEncoderThread.Stop();

    setComponentState(ComponentState::LOADED);

    ALOGV("%s() - done", __func__);
    return C2_OK;
}

c2_status_t V4L2EncodeComponent::reset() {
    ALOGV("%s()", __func__);

    // The interface specification says: "This method MUST be supported in all (including tripped)
    // states other than released".
    if (mComponentState == ComponentState::UNLOADED) {
        return C2_BAD_STATE;
    }

    // TODO(dstaessens): Reset the component's interface to default values.
    stop();

    return C2_OK;
}

c2_status_t V4L2EncodeComponent::release() {
    ALOGV("%s()", __func__);

    // The interface specification says: "This method MUST be supported in stopped state.", but the
    // release method seems to be called in other states as well.
    reset();

    setComponentState(ComponentState::UNLOADED);
    return C2_OK;
}

c2_status_t V4L2EncodeComponent::queue_nb(std::list<std::unique_ptr<C2Work>>* const items) {
    ALOGV("%s()", __func__);

    if (mComponentState != ComponentState::RUNNING) {
        ALOGE("Trying to queue work item while component is not running");
        return C2_BAD_STATE;
    }

    while (!items->empty()) {
        mEncoderTaskRunner->PostTask(FROM_HERE,
                                     ::base::BindOnce(&V4L2EncodeComponent::queueTask, mWeakThis,
                                                      std::move(items->front())));
        items->pop_front();
    }

    return C2_OK;
}

c2_status_t V4L2EncodeComponent::drain_nb(drain_mode_t mode) {
    ALOGV("%s()", __func__);

    if (mode == DRAIN_CHAIN) {
        return C2_OMITTED;  // Tunneling is not supported for now.
    }

    if (mComponentState != ComponentState::RUNNING) {
        return C2_BAD_STATE;
    }

    mEncoderTaskRunner->PostTask(
            FROM_HERE, ::base::BindOnce(&V4L2EncodeComponent::drainTask, mWeakThis, mode));
    return C2_OK;
}

c2_status_t V4L2EncodeComponent::flush_sm(flush_mode_t mode,
                                          std::list<std::unique_ptr<C2Work>>* const flushedWork) {
    ALOGV("%s()", __func__);

    if (mode != FLUSH_COMPONENT) {
        return C2_OMITTED;  // Tunneling is not supported by now
    }

    if (mComponentState != ComponentState::RUNNING) {
        return C2_BAD_STATE;
    }

    // Work that can be immediately discarded should be returned in |flushedWork|. This method may
    // be momentarily blocking but must return within 5ms, which should give us enough time to
    // immediately abandon all non-started work on the encoder thread. We can return all work that
    // can't be immediately discarded using onWorkDone() later.
    ::base::WaitableEvent done;
    mEncoderTaskRunner->PostTask(FROM_HERE, ::base::BindOnce(&V4L2EncodeComponent::flushTask,
                                                             mWeakThis, &done, flushedWork));
    done.Wait();

    return C2_OK;
}

c2_status_t V4L2EncodeComponent::announce_nb(const std::vector<C2WorkOutline>& items) {
    return C2_OMITTED;  // Tunneling is not supported by now
}

c2_status_t V4L2EncodeComponent::setListener_vb(const std::shared_ptr<Listener>& listener,
                                                c2_blocking_t mayBlock) {
    ALOG_ASSERT(mComponentState != ComponentState::UNLOADED);

    // Lock so we're sure the component isn't currently starting or stopping.
    std::lock_guard<std::mutex> lock(mComponentLock);

    // If the encoder thread is not running it's safe to update the listener directly.
    if (!mEncoderThread.IsRunning()) {
        mListener = listener;
        return C2_OK;
    }

    // The listener should be updated before exiting this function. If called while the component is
    // currently running we should be allowed to block, as we can only change the listener on the
    // encoder thread.
    ALOG_ASSERT(mayBlock == c2_blocking_t::C2_MAY_BLOCK);

    ::base::WaitableEvent done;
    mEncoderTaskRunner->PostTask(FROM_HERE, ::base::BindOnce(&V4L2EncodeComponent::setListenerTask,
                                                             mWeakThis, listener, &done));
    done.Wait();

    return C2_OK;
}

std::shared_ptr<C2ComponentInterface> V4L2EncodeComponent::intf() {
    return std::make_shared<SimpleInterface<V4L2EncodeInterface>>(mName.c_str(), mId, mInterface);
}

void V4L2EncodeComponent::startTask(bool* success, ::base::WaitableEvent* done) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoderState == EncoderState::UNINITIALIZED);

    *success = initializeEncoder();
    done->Signal();
}

void V4L2EncodeComponent::stopTask(::base::WaitableEvent* done) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    // Flushing the encoder will abort all pending work and stop polling and streaming on the V4L2
    // device queues.
    flush();

    // Deallocate all V4L2 device input and output buffers.
    destroyInputBuffers();
    destroyOutputBuffers();

    // Invalidate all weak pointers so no more functions will be executed on the encoder thread.
    mWeakThisFactory.InvalidateWeakPtrs();

    setEncoderState(EncoderState::UNINITIALIZED);
    done->Signal();
}

void V4L2EncodeComponent::queueTask(std::unique_ptr<C2Work> work) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoderState != EncoderState::UNINITIALIZED);

    // If we're in the error state we can immediately return, freeing all buffers in the work item.
    if (mEncoderState == EncoderState::ERROR) {
        return;
    }

    ALOGV("Queued work item (index: %llu, timestamp: %llu, EOS: %d)",
          work->input.ordinal.frameIndex.peekull(), work->input.ordinal.timestamp.peekull(),
          work->input.flags & C2FrameData::FLAG_END_OF_STREAM);

    mInputWorkQueue.push(std::move(work));

    // If we were waiting for work, start encoding again.
    if (mEncoderState == EncoderState::WAITING_FOR_INPUT) {
        setEncoderState(EncoderState::ENCODING);
        mEncoderTaskRunner->PostTask(
                FROM_HERE,
                ::base::BindOnce(&V4L2EncodeComponent::scheduleNextEncodeTask, mWeakThis));
    }
}

// TODO(dstaessens): Investigate improving drain logic after draining the virtio device is fixed.
void V4L2EncodeComponent::drainTask(drain_mode_t /*drainMode*/) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    // We can only start draining if all the work in our input queue has been queued on the V4L2
    // device input queue, so we mark the last item in the input queue as EOS.
    if (!mInputWorkQueue.empty()) {
        ALOGV("Marking last item in input work queue as EOS");
        mInputWorkQueue.back()->input.flags = static_cast<C2FrameData::flags_t>(
                mInputWorkQueue.back()->input.flags | C2FrameData::FLAG_END_OF_STREAM);
        return;
    }

    // If the input queue is empty and there is only a single empty EOS work item in the output
    // queue we can immediately consider flushing done.
    if ((mOutputWorkQueue.size() == 1) && mOutputWorkQueue.back()->input.buffers.empty()) {
        ALOG_ASSERT(mOutputWorkQueue.back()->input.flags & C2FrameData::FLAG_END_OF_STREAM);
        setEncoderState(EncoderState::DRAINING);
        mEncoderTaskRunner->PostTask(
                FROM_HERE, ::base::BindOnce(&V4L2EncodeComponent::onDrainDone, mWeakThis, true));
        return;
    }

    // If the input queue is empty all work that needs to be drained has already been queued in the
    // V4L2 device, so we can immediately request a drain.
    if (!mOutputWorkQueue.empty()) {
        // Mark the last item in the output work queue as EOS, so we will only report it as
        // finished after draining has completed.
        ALOGV("Starting drain and marking last item in output work queue as EOS");
        mOutputWorkQueue.back()->input.flags = C2FrameData::FLAG_END_OF_STREAM;
        drain();
    }
}

void V4L2EncodeComponent::onDrainDone(bool done) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoderState == EncoderState::DRAINING || mEncoderState == EncoderState::ERROR);

    if (mEncoderState == EncoderState::ERROR) {
        return;
    }

    if (!done) {
        ALOGE("draining the encoder failed");
        reportError(C2_CORRUPTED);
        return;
    }

    // The last work item in the output work queue should be an EOS request.
    if (mOutputWorkQueue.empty() ||
        !(mOutputWorkQueue.back()->input.flags & C2FrameData::FLAG_END_OF_STREAM)) {
        ALOGE("The last item in the output work queue should be marked EOS");
        reportError(C2_CORRUPTED);
        return;
    }

    // Mark the last item in the output work queue as EOS done.
    C2Work* eosWork = mOutputWorkQueue.back().get();
    eosWork->worklets.back()->output.flags = C2FrameData::FLAG_END_OF_STREAM;

    // Draining is done which means all buffers on the device output queue have been returned, but
    // not all buffers on the device input queue might have been returned yet.
    if ((mOutputWorkQueue.size() > 1) || !isWorkDone(*eosWork)) {
        ALOGV("Draining done, waiting for input buffers to be returned");
        return;
    }

    ALOGV("Draining done");
    reportWork(std::move(mOutputWorkQueue.front()));
    mOutputWorkQueue.pop_front();

    // Draining the encoder is now done, we can start encoding again.
    if (!mInputWorkQueue.empty()) {
        setEncoderState(EncoderState::ENCODING);
        mEncoderTaskRunner->PostTask(
                FROM_HERE,
                ::base::BindOnce(&V4L2EncodeComponent::scheduleNextEncodeTask, mWeakThis));
    } else {
        setEncoderState(EncoderState::WAITING_FOR_INPUT);
    }
}

void V4L2EncodeComponent::flushTask(::base::WaitableEvent* done,
                                    std::list<std::unique_ptr<C2Work>>* const flushedWork) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    // Move all work that can immediately be aborted to flushedWork, and notify the caller.
    if (flushedWork) {
        while (!mInputWorkQueue.empty()) {
            std::unique_ptr<C2Work> work = std::move(mInputWorkQueue.front());
            work->input.buffers.clear();
            flushedWork->push_back(std::move(work));
            mInputWorkQueue.pop();
        }
    }
    done->Signal();

    flush();
}

void V4L2EncodeComponent::setListenerTask(const std::shared_ptr<Listener>& listener,
                                          ::base::WaitableEvent* done) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    mListener = listener;
    done->Signal();
}

bool V4L2EncodeComponent::initializeEncoder() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoderState == EncoderState::UNINITIALIZED);

    mVisibleSize = mInterface->getInputVisibleSize();
    mKeyFramePeriod = mInterface->getKeyFramePeriod();
    mKeyFrameCounter = 0;
    mCSDSubmitted = false;

    // Open the V4L2 device for encoding to the requested output format.
    // TODO(dstaessens): Do we need to close the device first if already opened?
    // TODO(dstaessens): Avoid conversion to VideoCodecProfile and use C2Config::profile_t directly.
    media::VideoCodecProfile outputProfile =
            c2ProfileToVideoCodecProfile(mInterface->getOutputProfile());
    uint32_t outputPixelFormat =
            media::V4L2Device::VideoCodecProfileToV4L2PixFmt(outputProfile, false);
    if (!outputPixelFormat) {
        ALOGE("Invalid output profile %s", media::GetProfileName(outputProfile).c_str());
        return false;
    }

    mDevice = media::V4L2Device::Create();
    if (!mDevice) {
        ALOGE("Failed to create V4L2 device");
        return false;
    }

    if (!mDevice->Open(media::V4L2Device::Type::kEncoder, outputPixelFormat)) {
        ALOGE("Failed to open device for profile %s (%s)",
              media::GetProfileName(outputProfile).c_str(),
              media::FourccToString(outputPixelFormat).c_str());
        return false;
    }

    // Make sure the device has all required capabilities (multi-planar Memory-To-Memory and
    // streaming I/O), and whether flushing is supported.
    if (!mDevice->HasCapabilities(V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING)) {
        ALOGE("Device doesn't have the required capabilities");
        return false;
    }
    if (!mDevice->IsCommandSupported(V4L2_ENC_CMD_STOP)) {
        ALOGE("Device does not support flushing (V4L2_ENC_CMD_STOP)");
        return false;
    }

    // Get input/output queues so we can send encode request to the device and get back the results.
    mInputQueue = mDevice->GetQueue(V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
    mOutputQueue = mDevice->GetQueue(V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
    if (!mInputQueue || !mOutputQueue) {
        ALOGE("Failed to get V4L2 device queues");
        return false;
    }

    // First try to configure the specified output format, as changing the output format can affect
    // the configured input format.
    if (!configureOutputFormat(outputProfile)) return false;

    // Configure the input format. If the device doesn't support the specified format we'll use one
    // of the device's preferred formats in combination with an input format convertor.
    if (!configureInputFormat(kInputPixelFormat)) return false;

    // Create input and output buffers.
    // TODO(dstaessens): Avoid allocating output buffers, encode directly into blockpool buffers.
    if (!createInputBuffers() || !createOutputBuffers()) return false;

    // Configure the device, setting all required controls.
    uint8_t level = c2LevelToLevelIDC(mInterface->getOutputLevel());
    if (!configureDevice(outputProfile, level)) return false;

    // We're ready to start encoding now.
    setEncoderState(EncoderState::WAITING_FOR_INPUT);

    // As initialization is asynchronous work might have already be queued.
    if (!mInputWorkQueue.empty()) {
        setEncoderState(EncoderState::ENCODING);
        mEncoderTaskRunner->PostTask(
                FROM_HERE, ::base::Bind(&V4L2EncodeComponent::scheduleNextEncodeTask, mWeakThis));
    }
    return true;
}

bool V4L2EncodeComponent::configureInputFormat(media::VideoPixelFormat inputFormat) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoderState == EncoderState::UNINITIALIZED);
    ALOG_ASSERT(!mInputQueue->IsStreaming());
    ALOG_ASSERT(!mVisibleSize.IsEmpty());
    ALOG_ASSERT(!mInputFormatConverter);

    // First try to use the requested pixel format directly.
    ::base::Optional<struct v4l2_format> format;
    auto fourcc = media::Fourcc::FromVideoPixelFormat(inputFormat, false);
    if (fourcc) {
        format = mInputQueue->SetFormat(fourcc->ToV4L2PixFmt(), mVisibleSize, 0);
    }

    // If the device doesn't support the requested input format we'll try the device's preferred
    // input pixel formats and use a format convertor. We need to try all formats as some formats
    // might not be supported for the configured output format.
    if (!format) {
        std::vector<uint32_t> preferredFormats =
                mDevice->PreferredInputFormat(media::V4L2Device::Type::kEncoder);
        for (uint32_t i = 0; !format && i < preferredFormats.size(); ++i) {
            format = mInputQueue->SetFormat(preferredFormats[i], mVisibleSize, 0);
        }
    }

    if (!format) {
        ALOGE("Failed to set input format to %s",
              media::VideoPixelFormatToString(inputFormat).c_str());
        return false;
    }

    // Check whether the negotiated input format is valid. The coded size might be adjusted to match
    // encoder minimums, maximums and alignment requirements of the currently selected formats.
    auto layout = media::V4L2Device::V4L2FormatToVideoFrameLayout(*format);
    if (!layout) {
        ALOGE("Invalid input layout");
        return false;
    }

    mInputLayout = layout.value();
    if (!media::Rect(mInputLayout->coded_size()).Contains(media::Rect(mVisibleSize))) {
        ALOGE("Input size %s exceeds encoder capability, encoder can handle %s",
              mVisibleSize.ToString().c_str(), mInputLayout->coded_size().ToString().c_str());
        return false;
    }

    // Calculate the input coded size from the format.
    // TODO(dstaessens): How is this different from mInputLayout->coded_size()?
    mInputCodedSize = media::V4L2Device::AllocatedSizeFromV4L2Format(*format);

    // Add an input format convertor if the device doesn't support the requested input format.
    // Note: The amount of input buffers in the convertor should match the amount of buffers on the
    // device input queue, to simplify logic.
    // TODO(dstaessens): Currently an input format convertor is always required. Mapping an input
    // buffer always seems to fail unless we copy it into a new a buffer first. As a temporary
    // workaround the line below is commented, but this should be undone once the issue is fixed.
    //if (mInputLayout->format() != inputFormat) {
    ALOGV("Creating input format convertor (%s)",
          media::VideoPixelFormatToString(mInputLayout->format()).c_str());
    mInputFormatConverter =
            FormatConverter::Create(inputFormat, mVisibleSize, kInputBufferCount, mInputCodedSize);
    if (!mInputFormatConverter) {
        ALOGE("Failed to created input format convertor");
        return false;
    }
    //}

    // The coded input size might be different from the visible size due to alignment requirements,
    // So we need to specify the visible rectangle. Note that this rectangle might still be adjusted
    // due to hardware limitations.
    // TODO(dstaessens): Overwrite mVisibleSize with the adapted visible size here?
    media::Rect visibleRectangle(mVisibleSize.width(), mVisibleSize.height());

    struct v4l2_rect rect;
    rect.left = visibleRectangle.x();
    rect.top = visibleRectangle.y();
    rect.width = visibleRectangle.width();
    rect.height = visibleRectangle.height();

    // Try to adjust the visible rectangle using the VIDIOC_S_SELECTION command. If this is not
    // supported we'll try to use the VIDIOC_S_CROP command instead. The visible rectangle might be
    // adjusted to conform to hardware limitations (e.g. round to closest horizontal and vertical
    // offsets, width and height).
    struct v4l2_selection selection_arg;
    memset(&selection_arg, 0, sizeof(selection_arg));
    selection_arg.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
    selection_arg.target = V4L2_SEL_TGT_CROP;
    selection_arg.r = rect;
    if (mDevice->Ioctl(VIDIOC_S_SELECTION, &selection_arg) == 0) {
        visibleRectangle = media::Rect(selection_arg.r.left, selection_arg.r.top,
                                       selection_arg.r.width, selection_arg.r.height);
    } else {
        struct v4l2_crop crop;
        memset(&crop, 0, sizeof(v4l2_crop));
        crop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
        crop.c = rect;
        if (mDevice->Ioctl(VIDIOC_S_CROP, &crop) != 0 ||
            mDevice->Ioctl(VIDIOC_G_CROP, &crop) != 0) {
            ALOGE("Failed to crop to specified visible rectangle");
            return false;
        }
        visibleRectangle = media::Rect(crop.c.left, crop.c.top, crop.c.width, crop.c.height);
    }

    ALOGV("Input format set to %s (size: %s, adjusted size: %dx%d, coded size: %s)",
          media::VideoPixelFormatToString(mInputLayout->format()).c_str(),
          mVisibleSize.ToString().c_str(), visibleRectangle.width(), visibleRectangle.height(),
          mInputCodedSize.ToString().c_str());

    mVisibleSize.SetSize(visibleRectangle.width(), visibleRectangle.height());
    return true;
}

bool V4L2EncodeComponent::configureOutputFormat(media::VideoCodecProfile outputProfile) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoderState == EncoderState::UNINITIALIZED);
    ALOG_ASSERT(!mOutputQueue->IsStreaming());
    ALOG_ASSERT(!mVisibleSize.IsEmpty());

    auto format = mOutputQueue->SetFormat(
            media::V4L2Device::VideoCodecProfileToV4L2PixFmt(outputProfile, false), mVisibleSize,
            GetMaxOutputBufferSize(mVisibleSize));
    if (!format) {
        ALOGE("Failed to set output format to %s", media::GetProfileName(outputProfile).c_str());
        return false;
    }

    // The device might adjust the requested output buffer size to match hardware requirements.
    mOutputBufferSize = ::base::checked_cast<size_t>(format->fmt.pix_mp.plane_fmt[0].sizeimage);

    ALOGV("Output format set to %s (buffer size: %u)", media::GetProfileName(outputProfile).c_str(),
          mOutputBufferSize);
    return true;
}

bool V4L2EncodeComponent::configureDevice(media::VideoCodecProfile outputProfile,
                                          std::optional<const uint8_t> outputH264Level) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    // Enable frame-level bitrate control. This is the only mandatory general control.
    if (!mDevice->SetExtCtrls(V4L2_CTRL_CLASS_MPEG,
                              {media::V4L2ExtCtrl(V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE, 1)})) {
        ALOGW("Failed enabling bitrate control");
        // TODO(b/161508368): V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE is currently not supported yet,
        // assume the operation was successful for now.
    }

    // Additional optional controls:
    // - Enable macroblock-level bitrate control.
    // - Set GOP length to 0 to disable periodic key frames.
    mDevice->SetExtCtrls(V4L2_CTRL_CLASS_MPEG,
                         {media::V4L2ExtCtrl(V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE, 1),
                          media::V4L2ExtCtrl(V4L2_CID_MPEG_VIDEO_GOP_SIZE, 0)});

    // All controls below are H.264-specific, so we can return here if the profile is not H.264.
    if (outputProfile < media::H264PROFILE_MIN || outputProfile > media::H264PROFILE_MAX) {
        return true;
    }

    // When encoding H.264 we want to prepend SPS and PPS to each IDR for resilience. Some
    // devices support this through the V4L2_CID_MPEG_VIDEO_H264_SPS_PPS_BEFORE_IDR control.
    // TODO(b/161495502): V4L2_CID_MPEG_VIDEO_H264_SPS_PPS_BEFORE_IDR is currently not supported
    // yet, just log a warning if the operation was unsuccessful for now.
    if (mDevice->IsCtrlExposed(V4L2_CID_MPEG_VIDEO_H264_SPS_PPS_BEFORE_IDR)) {
        if (!mDevice->SetExtCtrls(
                    V4L2_CTRL_CLASS_MPEG,
                    {media::V4L2ExtCtrl(V4L2_CID_MPEG_VIDEO_H264_SPS_PPS_BEFORE_IDR, 1)})) {
            ALOGE("Failed to configure device to prepend SPS and PPS to each IDR");
            return false;
        }
        ALOGV("Device supports prepending SPS and PPS to each IDR");
    } else {
        ALOGW("Device doesn't support prepending SPS and PPS to IDR");
    }

    std::vector<media::V4L2ExtCtrl> h264Ctrls;

    // No B-frames, for lowest decoding latency.
    h264Ctrls.emplace_back(V4L2_CID_MPEG_VIDEO_B_FRAMES, 0);
    // Quantization parameter maximum value (for variable bitrate control).
    h264Ctrls.emplace_back(V4L2_CID_MPEG_VIDEO_H264_MAX_QP, 51);

    // Set H.264 profile.
    int32_t profile = media::V4L2Device::VideoCodecProfileToV4L2H264Profile(outputProfile);
    if (profile < 0) {
        ALOGE("Trying to set invalid H.264 profile");
        return false;
    }
    h264Ctrls.emplace_back(V4L2_CID_MPEG_VIDEO_H264_PROFILE, profile);

    // Set H.264 output level. Use Level 4.0 as fallback default.
    // TODO(dstaessens): Investigate code added by hiroh@ recently to select level in Chrome VEA.
    uint8_t h264Level = outputH264Level.value_or(media::H264SPS::kLevelIDC4p0);
    h264Ctrls.emplace_back(V4L2_CID_MPEG_VIDEO_H264_LEVEL,
                           media::V4L2Device::H264LevelIdcToV4L2H264Level(h264Level));

    // Ask not to put SPS and PPS into separate bitstream buffers.
    h264Ctrls.emplace_back(V4L2_CID_MPEG_VIDEO_HEADER_MODE,
                           V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME);

    // Ignore return value as these controls are optional.
    mDevice->SetExtCtrls(V4L2_CTRL_CLASS_MPEG, std::move(h264Ctrls));

    return true;
}

bool V4L2EncodeComponent::updateEncodingParameters() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    // Query the interface for the encoding parameters requested by the codec 2.0 framework.
    C2StreamBitrateInfo::output bitrateInfo;
    C2StreamFrameRateInfo::output framerateInfo;
    c2_status_t status =
            mInterface->query({&bitrateInfo, &framerateInfo}, {}, C2_DONT_BLOCK, nullptr);
    if (status != C2_OK) {
        ALOGE("Failed to query interface for encoding parameters (error code: %d)", status);
        reportError(status);
        return false;
    }

    // Ask device to change bitrate if it's different from the currently configured bitrate.
    uint32_t bitrate = bitrateInfo.value;
    if (mBitrate != bitrate) {
        ALOG_ASSERT(bitrate > 0u);
        ALOGV("Setting bitrate to %u", bitrate);
        if (!mDevice->SetExtCtrls(V4L2_CTRL_CLASS_MPEG,
                                  {media::V4L2ExtCtrl(V4L2_CID_MPEG_VIDEO_BITRATE, bitrate)})) {
            // TODO(b/161495749): V4L2_CID_MPEG_VIDEO_BITRATE is currently not supported yet, assume
            // the operation was successful for now.
            ALOGW("Requesting bitrate change failed");
        }
        mBitrate = bitrate;
    }

    // Ask device to change framerate if it's different from the currently configured framerate.
    // TODO(dstaessens): Move IOCTL to device and use helper function.
    uint32_t framerate = static_cast<uint32_t>(std::round(framerateInfo.value));
    if (mFramerate != framerate) {
        ALOG_ASSERT(framerate > 0u);
        ALOGV("Setting framerate to %u", framerate);
        struct v4l2_streamparm parms;
        memset(&parms, 0, sizeof(v4l2_streamparm));
        parms.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
        parms.parm.output.timeperframe.numerator = 1;
        parms.parm.output.timeperframe.denominator = framerate;
        if (mDevice->Ioctl(VIDIOC_S_PARM, &parms) != 0) {
            // TODO(b/161499573): VIDIOC_S_PARM is currently not supported yet, assume the operation
            // was successful for now.
            ALOGW("Requesting framerate change failed");
        }
        mFramerate = framerate;
    }

    // Check whether an explicit key frame was requested, if so reset the key frame counter to
    // immediately request a key frame.
    C2StreamRequestSyncFrameTuning::output requestKeyFrame;
    status = mInterface->query({&requestKeyFrame}, {}, C2_DONT_BLOCK, nullptr);
    if (status != C2_OK) {
        ALOGE("Failed to query interface for key frame request (error code: %d)", status);
        reportError(status);
        return false;
    }
    if (requestKeyFrame.value == C2_TRUE) {
        mKeyFrameCounter = 0;
        requestKeyFrame.value = C2_FALSE;
        std::vector<std::unique_ptr<C2SettingResult>> failures;
        status = mInterface->config({&requestKeyFrame}, C2_MAY_BLOCK, &failures);
        if (status != C2_OK) {
            ALOGE("Failed to reset key frame request on interface (error code: %d)", status);
            reportError(status);
            return false;
        }
    }

    // Request the next frame to be a key frame each time the counter reaches 0.
    if (mKeyFrameCounter == 0) {
        if (!mDevice->SetExtCtrls(V4L2_CTRL_CLASS_MPEG,
                                  {media::V4L2ExtCtrl(V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME)})) {
            // TODO(b/161498590): V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME is currently not supported
            // yet, assume the operation was successful for now.
            ALOGW("Failed requesting key frame");
        }
    }

    return true;
}

void V4L2EncodeComponent::scheduleNextEncodeTask() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoderState == EncoderState::ENCODING || mEncoderState == EncoderState::ERROR);

    // If we're in the error state we can immediately return.
    if (mEncoderState == EncoderState::ERROR) {
        return;
    }

    // Get the next work item. Currently only a single worklet per work item is supported. An input
    // buffer should always be supplied unless this is a drain or CSD request.
    ALOG_ASSERT(!mInputWorkQueue.empty());
    C2Work* work = mInputWorkQueue.front().get();
    ALOG_ASSERT(work->input.buffers.size() <= 1u && work->worklets.size() == 1u);

    // Set the default values for the output worklet.
    work->worklets.front()->output.flags = static_cast<C2FrameData::flags_t>(0);
    work->worklets.front()->output.buffers.clear();
    work->worklets.front()->output.ordinal = work->input.ordinal;

    uint64_t index = work->input.ordinal.frameIndex.peeku();
    int64_t timestamp = static_cast<int64_t>(work->input.ordinal.timestamp.peeku());
    bool endOfStream = work->input.flags & C2FrameData::FLAG_END_OF_STREAM;
    ALOGV("Scheduling next encode (index: %" PRIu64 ", timestamp: %" PRId64 ", EOS: %d)", index,
          timestamp, endOfStream);

    if (!work->input.buffers.empty()) {
        // Check if the device has free input buffers available. If not we'll switch to the
        // WAITING_FOR_INPUT_BUFFERS state, and resume encoding once we're notified buffers are
        // available in the onInputBufferDone() task. Note: The input buffers are not copied into
        // the device's input buffers, but rather a memory pointer is imported. We still have to
        // throttle the number of enqueues queued simultaneously on the device however.
        if (mInputQueue->FreeBuffersCount() == 0) {
            ALOGV("Waiting for device to return input buffers");
            setEncoderState(EncoderState::WAITING_FOR_INPUT_BUFFERS);
            return;
        }

        C2ConstGraphicBlock inputBlock =
                work->input.buffers.front()->data().graphicBlocks().front();

        // If encoding fails, we'll wait for an event (e.g. input buffers available) to start
        // encoding again.
        if (!encode(inputBlock, index, timestamp)) {
            return;
        }
    }

    // The codec 2.0 framework might queue an empty CSD request, but this is currently not
    // supported. We will return the CSD with the first encoded buffer work.
    // TODO(dstaessens): Avoid doing this, store CSD request work at start of output queue.
    if (work->input.buffers.empty() && !endOfStream) {
        ALOGV("Discarding empty CSD request");
        reportWork(std::move(mInputWorkQueue.front()));
    } else {
        mOutputWorkQueue.push_back(std::move(mInputWorkQueue.front()));
    }
    mInputWorkQueue.pop();

    // Drain the encoder if required.
    if (endOfStream) {
        drainTask(C2Component::DRAIN_COMPONENT_WITH_EOS);
    }

    if (mEncoderState == EncoderState::DRAINING) {
        return;
    } else if (mInputWorkQueue.empty()) {
        setEncoderState(EncoderState::WAITING_FOR_INPUT);
        return;
    }

    // Queue the next work item to be encoded.
    mEncoderTaskRunner->PostTask(
            FROM_HERE, ::base::BindOnce(&V4L2EncodeComponent::scheduleNextEncodeTask, mWeakThis));
}

bool V4L2EncodeComponent::encode(C2ConstGraphicBlock block, uint64_t index, int64_t timestamp) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoderState == EncoderState::ENCODING);

    // Update dynamic encoding parameters (bitrate, framerate, key frame) if requested.
    if (!updateEncodingParameters()) return false;

    mKeyFrameCounter = (mKeyFrameCounter + 1) % mKeyFramePeriod;

    // If required convert the data to the V4L2 device's configured input pixel format. We
    // allocate the same amount of buffers on the device input queue and the format convertor,
    // so we should never run out of conversion buffers if there are free buffers in the input
    // queue.
    if (mInputFormatConverter) {
        if (!mInputFormatConverter->isReady()) {
            ALOGE("Input format convertor ran out of buffers");
            reportError(C2_CORRUPTED);
            return false;
        }

        ALOGV("Converting input block (index: %" PRIu64 ")", index);
        c2_status_t status = C2_CORRUPTED;
        block = mInputFormatConverter->convertBlock(index, block, &status);
        if (status != C2_OK) {
            ALOGE("Failed to convert input block (index: %" PRIu64 ")", index);
            reportError(status);
            return false;
        }
    }

    ALOGV("Encoding input block (index: %" PRIu64 ", timestamp: %" PRId64 ", size: %dx%d)", index,
          timestamp, block.width(), block.height());

    // Create a video frame from the graphic block.
    std::unique_ptr<InputFrame> frame = InputFrame::Create(block);
    if (!frame) {
        ALOGE("Failed to create video frame from input block (index: %" PRIu64
              ", timestamp: %" PRId64 ")",
              index, timestamp);
        reportError(C2_CORRUPTED);
        return false;
    }

    // Get the video frame layout and pixel format from the graphic block.
    // TODO(dstaessens) Integrate getVideoFrameLayout() into InputFrame::Create()
    media::VideoPixelFormat format;
    std::optional<std::vector<VideoFramePlane>> planes = getVideoFrameLayout(block, &format);
    if (!planes) {
        ALOGE("Failed to get input block's layout");
        reportError(C2_CORRUPTED);
        return false;
    }

    if (!enqueueInputBuffer(std::move(frame), format, *planes, index, timestamp)) {
        ALOGE("Failed to enqueue video frame (index: %" PRIu64 ", timestamp: %" PRId64 ")", index,
              timestamp);
        reportError(C2_CORRUPTED);
        return false;
    }

    // Start streaming on the input and output queue if required.
    if (!mInputQueue->IsStreaming()) {
        ALOG_ASSERT(!mOutputQueue->IsStreaming());
        if (!mOutputQueue->Streamon() || !mInputQueue->Streamon()) {
            ALOGE("Failed to start streaming on input and output queue");
            reportError(C2_CORRUPTED);
            return false;
        }
        // Start polling on the V4L2 device.
        startDevicePoll();
    }

    // Queue all buffers on the output queue. These buffers will be used to store the encoded
    // bitstreams.
    while (mOutputQueue->FreeBuffersCount() > 0) {
        if (!enqueueOutputBuffer()) return false;
    }

    return true;
}

void V4L2EncodeComponent::drain() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    if (mEncoderState == EncoderState::DRAINING || mEncoderState == EncoderState::ERROR) {
        return;
    }

    ALOG_ASSERT(mInputQueue->IsStreaming() && mOutputQueue->IsStreaming());
    ALOG_ASSERT(!mOutputWorkQueue.empty());

    // TODO(dstaessens): Move IOCTL to device class.
    struct v4l2_encoder_cmd cmd;
    memset(&cmd, 0, sizeof(v4l2_encoder_cmd));
    cmd.cmd = V4L2_ENC_CMD_STOP;
    if (mDevice->Ioctl(VIDIOC_ENCODER_CMD, &cmd) != 0) {
        ALOGE("Failed to stop encoder");
        onDrainDone(false);
        return;
    }
    ALOGV("%s(): Sent STOP command to encoder", __func__);

    setEncoderState(EncoderState::DRAINING);
}

void V4L2EncodeComponent::flush() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    // Stop the device poll thread.
    stopDevicePoll();

    // Stop streaming on the V4L2 device, which stops all currently queued work and releases all
    // buffers currently in use by the device.
    // TODO(b/160540027): Calling streamoff currently results in a bug.
    for (auto& queue : {mInputQueue, mOutputQueue}) {
        if (queue && queue->IsStreaming() && !queue->Streamoff()) {
            ALOGE("Failed to stop streaming on the device queue");
            reportError(C2_CORRUPTED);
        }
    }

    // Return all buffers to the input format convertor and clear all references to graphic blocks
    // in the input queue. We don't need to clear the output map as those buffers will still be
    // used.
    for (auto& it : mInputBuffersMap) {
        if (mInputFormatConverter && it.second) {
            mInputFormatConverter->returnBlock(it.first);
        }
        it.second = nullptr;
    }

    // Report all queued work items as aborted.
    std::list<std::unique_ptr<C2Work>> abortedWorkItems;
    while (!mInputWorkQueue.empty()) {
        std::unique_ptr<C2Work> work = std::move(mInputWorkQueue.front());
        work->result = C2_NOT_FOUND;
        work->input.buffers.clear();
        abortedWorkItems.push_back(std::move(work));
        mInputWorkQueue.pop();
    }
    while (!mOutputWorkQueue.empty()) {
        std::unique_ptr<C2Work> work = std::move(mOutputWorkQueue.front());
        work->result = C2_NOT_FOUND;
        work->input.buffers.clear();
        abortedWorkItems.push_back(std::move(work));
        mOutputWorkQueue.pop_front();
    }
    if (!abortedWorkItems.empty())
        mListener->onWorkDone_nb(shared_from_this(), std::move(abortedWorkItems));

    // Streaming and polling on the V4L2 device input and output queues will be resumed once new
    // encode work is queued.
}

std::shared_ptr<C2LinearBlock> V4L2EncodeComponent::fetchOutputBlock() {
    // TODO(dstaessens): fetchLinearBlock() might be blocking.
    ALOGV("Fetching linear block (size: %u)", mOutputBufferSize);
    std::shared_ptr<C2LinearBlock> outputBlock;
    c2_status_t status = mOutputBlockPool->fetchLinearBlock(
            mOutputBufferSize,
            C2MemoryUsage(C2MemoryUsage::CPU_READ |
                          static_cast<uint64_t>(BufferUsage::VIDEO_ENCODER)),
            &outputBlock);
    if (status != C2_OK) {
        ALOGE("Failed to fetch linear block (error: %d)", status);
        reportError(status);
        return nullptr;
    }

    return outputBlock;
}

void V4L2EncodeComponent::onInputBufferDone(uint64_t index) {
    ALOGV("%s(): Input buffer done (index: %" PRIu64 ")", __func__, index);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoderState != EncoderState::UNINITIALIZED);

    // There are no guarantees the input buffers are returned in order, so we need to find the work
    // item which this buffer belongs to.
    C2Work* work = getWorkByIndex(index);
    if (!work) {
        ALOGE("Failed to find work associated with input buffer %" PRIu64, index);
        reportError(C2_CORRUPTED);
        return;
    }

    // We're done using the input block, release reference to return the block to the client. If
    // using an input format convertor, we also need to return the block to the convertor.
    LOG_ASSERT(!work->input.buffers.empty());
    work->input.buffers.front().reset();
    if (mInputFormatConverter) {
        c2_status_t status = mInputFormatConverter->returnBlock(index);
        if (status != C2_OK) {
            reportError(status);
            return;
        }
    }

    // Return all completed work items. The work item might have been waiting for it's input buffer
    // to be returned, in which case we can report it as completed now. As input buffers are not
    // necessarily returned in order we might be able to return multiple ready work items now.
    while (!mOutputWorkQueue.empty() && isWorkDone(*mOutputWorkQueue.front())) {
        reportWork(std::move(mOutputWorkQueue.front()));
        mOutputWorkQueue.pop_front();
    }

    // We might have been waiting for input buffers to be returned after draining finished.
    if (mEncoderState == EncoderState::DRAINING && mOutputWorkQueue.empty()) {
        ALOGV("Draining done");
        mEncoderState = EncoderState::WAITING_FOR_INPUT_BUFFERS;
    }

    // If we previously used up all input queue buffers we can start encoding again now.
    if ((mEncoderState == EncoderState::WAITING_FOR_INPUT_BUFFERS) && !mInputWorkQueue.empty()) {
        setEncoderState(EncoderState::ENCODING);
        mEncoderTaskRunner->PostTask(
                FROM_HERE,
                ::base::BindOnce(&V4L2EncodeComponent::scheduleNextEncodeTask, mWeakThis));
    }
}

void V4L2EncodeComponent::onOutputBufferDone(uint32_t payloadSize, bool keyFrame, int64_t timestamp,
                                             std::shared_ptr<C2LinearBlock> outputBlock) {
    ALOGV("%s(): output buffer done (timestamp: %" PRId64 ", size: %u, key frame: %d)", __func__,
          timestamp, payloadSize, keyFrame);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    if (mEncoderState == EncoderState::ERROR) {
        return;
    }

    C2ConstLinearBlock constBlock =
            outputBlock->share(outputBlock->offset(), payloadSize, C2Fence());

    // If no CSD (content-specific-data, e.g. SPS for H.264) has been submitted yet, we expect this
    // output block to contain CSD. We only submit the CSD once, even if it's attached to each key
    // frame.
    if (!mCSDSubmitted) {
        ALOGV("No CSD submitted yet, extracting CSD");
        std::unique_ptr<C2StreamInitDataInfo::output> csd;
        C2ReadView view = constBlock.map().get();
        extractCSDInfo(&csd, view.data(), view.capacity());
        if (!csd) {
            ALOGE("Failed to extract CSD");
            reportError(C2_CORRUPTED);
            return;
        }

        // Attach the CSD to the first item in our output work queue.
        LOG_ASSERT(!mOutputWorkQueue.empty());
        C2Work* work = mOutputWorkQueue.front().get();
        work->worklets.front()->output.configUpdate.push_back(std::move(csd));
        mCSDSubmitted = true;
    }

    // Get the work item associated with the timestamp.
    C2Work* work = getWorkByTimestamp(timestamp);
    if (!work) {
        // It's possible we got an empty CSD request with timestamp 0, which we currently just
        // discard.
        // TODO(dstaessens): Investigate handling empty CSD requests.
        if (timestamp != 0) {
            reportError(C2_CORRUPTED);
        }
        return;
    }

    std::shared_ptr<C2Buffer> buffer = C2Buffer::CreateLinearBuffer(std::move(constBlock));
    if (keyFrame) {
        buffer->setInfo(
                std::make_shared<C2StreamPictureTypeMaskInfo::output>(0u, C2Config::SYNC_FRAME));
    }
    work->worklets.front()->output.buffers.emplace_back(buffer);

    // We can report the work item as completed if its associated input buffer has also been
    // released. As output buffers are not necessarily returned in order we might be able to return
    // multiple ready work items now.
    while (!mOutputWorkQueue.empty() && isWorkDone(*mOutputWorkQueue.front())) {
        reportWork(std::move(mOutputWorkQueue.front()));
        mOutputWorkQueue.pop_front();
    }
}

C2Work* V4L2EncodeComponent::getWorkByIndex(uint64_t index) {
    ALOGV("%s(): getting work item (index: %" PRIu64 ")", __func__, index);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    auto it = std::find_if(mOutputWorkQueue.begin(), mOutputWorkQueue.end(),
                           [index](const std::unique_ptr<C2Work>& w) {
                               return w->input.ordinal.frameIndex.peeku() == index;
                           });
    if (it == mOutputWorkQueue.end()) {
        ALOGE("Failed to find work (index: %" PRIu64 ")", index);
        return nullptr;
    }
    return it->get();
}

C2Work* V4L2EncodeComponent::getWorkByTimestamp(int64_t timestamp) {
    ALOGV("%s(): getting work item (timestamp: %" PRId64 ")", __func__, timestamp);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(timestamp >= 0);

    // Find the work with specified timestamp by looping over the output work queue. This should be
    // very fast as the output work queue will never be longer then a few items. Ignore empty work
    // items that are marked as EOS, as their timestamp might clash with other work items.
    auto it = std::find_if(mOutputWorkQueue.begin(), mOutputWorkQueue.end(),
                           [timestamp](const std::unique_ptr<C2Work>& w) {
                               return !(w->input.flags & C2FrameData::FLAG_END_OF_STREAM) &&
                                      w->input.ordinal.timestamp.peeku() ==
                                              static_cast<uint64_t>(timestamp);
                           });
    if (it == mOutputWorkQueue.end()) {
        ALOGE("Failed to find work (timestamp: %" PRIu64 ")", timestamp);
        return nullptr;
    }
    return it->get();
}

bool V4L2EncodeComponent::isWorkDone(const C2Work& work) const {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    if ((work.input.flags & C2FrameData::FLAG_END_OF_STREAM) &&
        !(work.worklets.front()->output.flags & C2FrameData::FLAG_END_OF_STREAM)) {
        ALOGV("Work item %" PRIu64 " is marked as EOS but draining has not finished yet",
              work.input.ordinal.frameIndex.peeku());
        return false;
    }

    if (!work.input.buffers.empty() && work.input.buffers.front()) {
        ALOGV("Input buffer associated with work item %" PRIu64 " not returned yet",
              work.input.ordinal.frameIndex.peeku());
        return false;
    }

    // If the work item had an input buffer to be encoded, it should have an output buffer set.
    if (!work.input.buffers.empty() && work.worklets.front()->output.buffers.empty()) {
        ALOGV("Output buffer associated with work item %" PRIu64 " not returned yet",
              work.input.ordinal.frameIndex.peeku());
        return false;
    }

    return true;
}

void V4L2EncodeComponent::reportWork(std::unique_ptr<C2Work> work) {
    ALOG_ASSERT(work);
    ALOGV("%s(): Reporting work item as finished (index: %llu, timestamp: %llu)", __func__,
          work->input.ordinal.frameIndex.peekull(), work->input.ordinal.timestamp.peekull());
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    work->result = C2_OK;
    work->workletsProcessed = static_cast<uint32_t>(work->worklets.size());

    std::list<std::unique_ptr<C2Work>> finishedWorkList;
    finishedWorkList.emplace_back(std::move(work));
    mListener->onWorkDone_nb(shared_from_this(), std::move(finishedWorkList));
}

bool V4L2EncodeComponent::startDevicePoll() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    if (!mDevice->StartPolling(
                ::base::BindRepeating(&V4L2EncodeComponent::serviceDeviceTask, mWeakThis),
                ::base::BindRepeating(&V4L2EncodeComponent::onPollError, mWeakThis))) {
        ALOGE("Device poll thread failed to start");
        reportError(C2_CORRUPTED);
        return false;
    }

    ALOGV("Device poll started");
    return true;
}

bool V4L2EncodeComponent::stopDevicePoll() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    if (!mDevice->StopPolling()) {
        ALOGE("Failed to stop polling on the device");
        reportError(C2_CORRUPTED);
        return false;
    }

    ALOGV("Device poll stopped");
    return true;
}

void V4L2EncodeComponent::onPollError() {
    ALOGV("%s()", __func__);
    reportError(C2_CORRUPTED);
}

void V4L2EncodeComponent::serviceDeviceTask(bool /*event*/) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoderState != EncoderState::UNINITIALIZED);

    if (mEncoderState == EncoderState::ERROR) {
        return;
    }

    // Dequeue completed input (VIDEO_OUTPUT) buffers, and recycle to the free list.
    while (mInputQueue->QueuedBuffersCount() > 0) {
        if (!dequeueInputBuffer()) break;
    }

    // Dequeue completed output (VIDEO_CAPTURE) buffers, and recycle to the free list.
    while (mOutputQueue->QueuedBuffersCount() > 0) {
        if (!dequeueOutputBuffer()) break;
    }

    ALOGV("%s() - done", __func__);
}

bool V4L2EncodeComponent::enqueueInputBuffer(std::unique_ptr<InputFrame> frame,
                                             media::VideoPixelFormat format,
                                             const std::vector<VideoFramePlane>& planes,
                                             int64_t index, int64_t timestamp) {
    ALOGV("%s(): queuing input buffer (index: %" PRId64 ")", __func__, index);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mInputQueue->FreeBuffersCount() > 0);
    ALOG_ASSERT(mEncoderState == EncoderState::ENCODING);
    ALOG_ASSERT(mInputLayout->format() == format);
    ALOG_ASSERT(mInputLayout->planes().size() == planes.size());

    auto buffer = mInputQueue->GetFreeBuffer();
    if (!buffer) {
        ALOGE("Failed to get free buffer from device input queue");
        return false;
    }

    // Mark the buffer with the frame's timestamp so we can identify the associated output buffers.
    buffer->SetTimeStamp(
            {.tv_sec = static_cast<time_t>(timestamp / ::base::Time::kMicrosecondsPerSecond),
             .tv_usec = static_cast<time_t>(timestamp % ::base::Time::kMicrosecondsPerSecond)});
    size_t bufferId = buffer->BufferId();

    for (size_t i = 0; i < planes.size(); ++i) {
        // Single-buffer input format may have multiple color planes, so bytesUsed of the single
        // buffer should be sum of each color planes' size.
        size_t bytesUsed = 0;
        if (planes.size() == 1) {
            bytesUsed = media::VideoFrame::AllocationSize(format, mInputLayout->coded_size());
        } else {
            bytesUsed = ::base::checked_cast<size_t>(
                    media::VideoFrame::PlaneSize(format, i, mInputLayout->coded_size()).GetArea());
        }

        // TODO(crbug.com/901264): The way to pass an offset within a DMA-buf is not defined
        // in V4L2 specification, so we abuse data_offset for now. Fix it when we have the
        // right interface, including any necessary validation and potential alignment.
        buffer->SetPlaneDataOffset(i, planes[i].mOffset);
        bytesUsed += planes[i].mOffset;
        // Workaround: filling length should not be needed. This is a bug of videobuf2 library.
        buffer->SetPlaneSize(i, mInputLayout->planes()[i].size + planes[i].mOffset);
        buffer->SetPlaneBytesUsed(i, bytesUsed);
    }

    std::move(*buffer).QueueDMABuf(frame->getFDs());

    ALOGV("Queued buffer in input queue (index: %" PRId64 ", timestamp: %" PRId64
          ", bufferId: %zu)",
          index, timestamp, bufferId);

    mInputBuffersMap[bufferId] = {index, std::move(frame)};

    return true;
}

bool V4L2EncodeComponent::enqueueOutputBuffer() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mOutputQueue->FreeBuffersCount() > 0);

    auto buffer = mOutputQueue->GetFreeBuffer();
    if (!buffer) {
        ALOGE("Failed to get free buffer from device output queue");
        reportError(C2_CORRUPTED);
        return false;
    }

    std::shared_ptr<C2LinearBlock> outputBlock = fetchOutputBlock();
    if (!outputBlock) {
        ALOGE("Failed to fetch output block");
        reportError(C2_CORRUPTED);
        return false;
    }

    size_t bufferId = buffer->BufferId();

    std::vector<int> fds;
    fds.push_back(outputBlock->handle()->data[0]);
    if (!std::move(*buffer).QueueDMABuf(fds)) {
        ALOGE("Failed to queue output buffer using QueueDMABuf");
        reportError(C2_CORRUPTED);
        return false;
    }

    ALOG_ASSERT(!mOutputBuffersMap[bufferId]);
    mOutputBuffersMap[bufferId] = std::move(outputBlock);
    ALOGV("%s(): Queued buffer in output queue (bufferId: %zu)", __func__, bufferId);
    return true;
}

bool V4L2EncodeComponent::dequeueInputBuffer() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoderState != EncoderState::UNINITIALIZED);
    ALOG_ASSERT(mInputQueue->QueuedBuffersCount() > 0);

    std::pair<bool, media::V4L2ReadableBufferRef> result = mInputQueue->DequeueBuffer();
    if (!result.first) {
        ALOGE("Failed to dequeue buffer from input queue");
        reportError(C2_CORRUPTED);
        return false;
    }
    if (!result.second) {
        // No more buffers ready to be dequeued in input queue.
        return false;
    }

    const media::V4L2ReadableBufferRef buffer = std::move(result.second);
    uint64_t index = mInputBuffersMap[buffer->BufferId()].first;
    int64_t timestamp = buffer->GetTimeStamp().tv_usec +
                        buffer->GetTimeStamp().tv_sec * ::base::Time::kMicrosecondsPerSecond;
    ALOGV("Dequeued buffer from input queue (index: %" PRId64 ", timestamp: %" PRId64
          ", bufferId: %zu)",
          index, timestamp, buffer->BufferId());

    mInputBuffersMap[buffer->BufferId()].second = nullptr;
    onInputBufferDone(index);

    return true;
}

bool V4L2EncodeComponent::dequeueOutputBuffer() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(mEncoderState != EncoderState::UNINITIALIZED);
    ALOG_ASSERT(mOutputQueue->QueuedBuffersCount() > 0);

    std::pair<bool, media::V4L2ReadableBufferRef> result = mOutputQueue->DequeueBuffer();
    if (!result.first) {
        ALOGE("Failed to dequeue buffer from output queue");
        reportError(C2_CORRUPTED);
        return false;
    }
    if (!result.second) {
        // No more buffers ready to be dequeued in output queue.
        return false;
    }

    media::V4L2ReadableBufferRef buffer = std::move(result.second);
    size_t encodedDataSize = buffer->GetPlaneBytesUsed(0) - buffer->GetPlaneDataOffset(0);
    ::base::TimeDelta timestamp = ::base::TimeDelta::FromMicroseconds(
            buffer->GetTimeStamp().tv_usec +
            buffer->GetTimeStamp().tv_sec * ::base::Time::kMicrosecondsPerSecond);

    ALOGV("Dequeued buffer from output queue (timestamp: %" PRId64
          ", bufferId: %zu, data size: %zu, EOS: %d)",
          timestamp.InMicroseconds(), buffer->BufferId(), encodedDataSize, buffer->IsLast());

    if (!mOutputBuffersMap[buffer->BufferId()]) {
        ALOGE("Failed to find output block associated with output buffer");
        reportError(C2_CORRUPTED);
        return false;
    }

    std::shared_ptr<C2LinearBlock> block = std::move(mOutputBuffersMap[buffer->BufferId()]);
    if (encodedDataSize > 0) {
        onOutputBufferDone(encodedDataSize, buffer->IsKeyframe(), timestamp.InMicroseconds(),
                           std::move(block));
    }

    // If the buffer is marked as last and we were flushing the encoder, flushing is now done.
    if ((mEncoderState == EncoderState::DRAINING) && buffer->IsLast()) {
        onDrainDone(true);

        // Start the encoder again.
        struct v4l2_encoder_cmd cmd;
        memset(&cmd, 0, sizeof(v4l2_encoder_cmd));
        cmd.cmd = V4L2_ENC_CMD_START;
        if (mDevice->Ioctl(VIDIOC_ENCODER_CMD, &cmd) != 0) {
            ALOGE("Failed to restart encoder after flushing (V4L2_ENC_CMD_START)");
            reportError(C2_CORRUPTED);
            return false;
        }
    }

    // Queue a new output buffer to replace the one we dequeued.
    buffer = nullptr;
    enqueueOutputBuffer();

    return true;
}

bool V4L2EncodeComponent::createInputBuffers() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(!mInputQueue->IsStreaming());
    ALOG_ASSERT(mInputBuffersMap.empty());

    // No memory is allocated here, we just generate a list of buffers on the input queue, which
    // will hold memory handles to the real buffers.
    if (mInputQueue->AllocateBuffers(kInputBufferCount, V4L2_MEMORY_DMABUF) < kInputBufferCount) {
        ALOGE("Failed to create V4L2 input buffers.");
        return false;
    }

    mInputBuffersMap.resize(mInputQueue->AllocatedBuffersCount());
    return true;
}

bool V4L2EncodeComponent::createOutputBuffers() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(!mOutputQueue->IsStreaming());
    ALOG_ASSERT(mOutputBuffersMap.empty());

    // Fetch the output block pool.
    C2BlockPool::local_id_t poolId = mInterface->getBlockPoolId();
    c2_status_t status = GetCodec2BlockPool(poolId, shared_from_this(), &mOutputBlockPool);
    if (status != C2_OK || !mOutputBlockPool) {
        ALOGE("Failed to get output block pool, error: %d", status);
        return false;
    }

    // No memory is allocated here, we just generate a list of buffers on the output queue, which
    // will hold memory handles to the real buffers.
    if (mOutputQueue->AllocateBuffers(kOutputBufferCount, V4L2_MEMORY_DMABUF) <
        kOutputBufferCount) {
        ALOGE("Failed to create V4L2 output buffers.");
        return false;
    }

    mOutputBuffersMap.resize(mOutputQueue->AllocatedBuffersCount());
    return true;
}

void V4L2EncodeComponent::destroyInputBuffers() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(!mInputQueue->IsStreaming());

    if (!mInputQueue || mInputQueue->AllocatedBuffersCount() == 0) return;
    mInputQueue->DeallocateBuffers();
    mInputBuffersMap.clear();
}

void V4L2EncodeComponent::destroyOutputBuffers() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());
    ALOG_ASSERT(!mOutputQueue->IsStreaming());

    if (!mOutputQueue || mOutputQueue->AllocatedBuffersCount() == 0) return;
    mOutputQueue->DeallocateBuffers();
    mOutputBuffersMap.clear();
    mOutputBlockPool.reset();
}

void V4L2EncodeComponent::reportError(c2_status_t error) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    {
        std::lock_guard<std::mutex> lock(mComponentLock);
        setComponentState(ComponentState::ERROR);
    }

    // TODO(dstaessens): Report all pending work items as finished upon failure.
    if (mEncoderState != EncoderState::ERROR) {
        setEncoderState(EncoderState::ERROR);
        mListener->onError_nb(shared_from_this(), static_cast<uint32_t>(error));
    }
}

void V4L2EncodeComponent::setComponentState(ComponentState state) {
    // Check whether the state change is valid.
    switch (state) {
    case ComponentState::UNLOADED:
        ALOG_ASSERT(mComponentState == ComponentState::LOADED);
        break;
    case ComponentState::LOADED:
        ALOG_ASSERT(mComponentState == ComponentState::UNLOADED ||
                    mComponentState == ComponentState::RUNNING ||
                    mComponentState == ComponentState::ERROR);
        break;
    case ComponentState::RUNNING:
        ALOG_ASSERT(mComponentState == ComponentState::LOADED);
        break;
    case ComponentState::ERROR:
        break;
    }

    ALOGV("Changed component state from %s to %s", componentStateToString(mComponentState),
          componentStateToString(state));
    mComponentState = state;
}

void V4L2EncodeComponent::setEncoderState(EncoderState state) {
    ALOG_ASSERT(mEncoderTaskRunner->RunsTasksInCurrentSequence());

    // Check whether the state change is valid.
    switch (state) {
    case EncoderState::UNINITIALIZED:
        // TODO(dstaessens): Check all valid state changes.
        break;
    case EncoderState::WAITING_FOR_INPUT:
        ALOG_ASSERT(mEncoderState == EncoderState::UNINITIALIZED ||
                    mEncoderState == EncoderState::ENCODING ||
                    mEncoderState == EncoderState::DRAINING);
        break;
    case EncoderState::WAITING_FOR_INPUT_BUFFERS:
        ALOG_ASSERT(mEncoderState == EncoderState::ENCODING);
        break;
    case EncoderState::ENCODING:
        ALOG_ASSERT(mEncoderState == EncoderState::WAITING_FOR_INPUT ||
                    mEncoderState == EncoderState::WAITING_FOR_INPUT_BUFFERS ||
                    mEncoderState == EncoderState::DRAINING);
        break;
    case EncoderState::DRAINING:
        ALOG_ASSERT(mEncoderState == EncoderState::ENCODING);
        break;
    case EncoderState::ERROR:
        break;
    }

    ALOGV("Changed encoder state from %s to %s", encoderStateToString(mEncoderState),
          encoderStateToString(state));
    mEncoderState = state;
}

const char* V4L2EncodeComponent::componentStateToString(V4L2EncodeComponent::ComponentState state) {
    switch (state) {
    case ComponentState::UNLOADED:
        return "UNLOADED";
    case ComponentState::LOADED:
        return "LOADED";
    case ComponentState::RUNNING:
        return "RUNNING";
    case ComponentState::ERROR:
        return "ERROR";
    }
}

const char* V4L2EncodeComponent::encoderStateToString(V4L2EncodeComponent::EncoderState state) {
    switch (state) {
    case EncoderState::UNINITIALIZED:
        return "UNINITIALIZED";
    case EncoderState::WAITING_FOR_INPUT:
        return "WAITING_FOR_INPUT";
    case EncoderState::WAITING_FOR_INPUT_BUFFERS:
        return "WAITING_FOR_INPUT_BUFFERS";
    case EncoderState::ENCODING:
        return "ENCODING";
    case EncoderState::DRAINING:
        return "Draining";
    case EncoderState::ERROR:
        return "ERROR";
    }
}

}  // namespace android