Skip to content

File AsyncHapDecoder.h

File List > cpp > input > AsyncHapDecoder.h

Go to the documentation of this file

/*
 * SPDX-FileCopyrightText: 2026 Stagelab Coop SCCL
 * SPDX-License-Identifier: GPL-3.0-or-later
 * SPDX-FileContributor: Ion Reguera <ion@stagelab.coop>
 *
 * This file is part of cuems-videocomposer.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

#ifndef VIDEOCOMPOSER_ASYNCHAPDECODER_H
#define VIDEOCOMPOSER_ASYNCHAPDECODER_H

#include "../hap/HapDecoder.h"
#include "../video/GPUTextureFrameBuffer.h"
#include <cuems_mediadecoder/MediaFileReader.h>
#include <string>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <deque>
#include <vector>
#include <cstdint>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}

namespace videocomposer {

struct HapDecodedFrame {
    int64_t frameNumber;
    HapVariant variant;
    std::vector<HapDecodedTexture> textures;  // 1 for HAP/HAP_Q/HAP_ALPHA, 2 for HAP_Q_ALPHA
    bool ready;

    HapDecodedFrame() : frameNumber(-1), variant(HapVariant::NONE), ready(false) {}
    // Copyable + movable. Queue insertion uses std::move so copies only happen
    // where intentional (borrowFrame copies into the borrowed-slot for the
    // render thread). If a non-copyable field is added in the future, that's
    // when to revisit; today the explicit deep copy is the desired behaviour.
};

class AsyncHapDecoder {
public:
    AsyncHapDecoder();
    ~AsyncHapDecoder();

    AsyncHapDecoder(const AsyncHapDecoder&) = delete;
    AsyncHapDecoder& operator=(const AsyncHapDecoder&) = delete;

    bool open(const std::string& filename);

    void close();

    const HapDecodedFrame* getFrame(int64_t frameNumber, int maxWaitMs = 0);

    void seek(int64_t frameNumber);

    bool hasFrame(int64_t frameNumber) const;
    void setTargetFrame(int64_t frameNumber);

    void setLoopMode(bool loop);

    bool isReady() const { return ready_; }
    int getWidth() const { return width_; }
    int getHeight() const { return height_; }
    double getFramerate() const { return framerate_; }

    size_t getQueueSize() const;
    int64_t getOldestFrame() const;
    int64_t getNewestFrame() const;

private:
    void decodeThreadFunc();
    bool decodeNextFrame();
    bool seekInternal(int64_t frameNumber);
    void insertFrameSorted(HapDecodedFrame&& qf);
    const HapDecodedFrame* borrowFrame(const HapDecodedFrame& src);

    // Owned demuxer (separate from HAPVideoInput::mediaReader_)
    cuems_mediadecoder::MediaFileReader mediaReader_;
    AVCodecContext* codecCtx_;      // used only for avcodec_flush_buffers on seek
    int videoStream_;
    AVRational timeBase_;
    AVRational frameRateQ_;         // canonical: num=fps_num, den=fps_den

    // Decoder
    HapDecoder hapDecoder_;         // per-worker — not thread-safe for concurrent calls
    int width_;
    int height_;
    double framerate_;
    int64_t frameCount_;
    bool ready_;
    std::string filename_;

    // Queue
    static constexpr size_t MAX_QUEUE_SIZE = 8;
    static constexpr int64_t FORWARD_JUMP_THRESHOLD = 15;
    std::deque<HapDecodedFrame> frameQueue_;
    mutable std::mutex queueMutex_;
    std::condition_variable queueCond_;

    // Worker control
    std::unique_ptr<std::thread> decodeThread_;
    std::atomic<bool> threadStop_{false};
    std::atomic<int64_t> targetFrame_{0};
    std::atomic<int64_t> lastDecodedFrame_{-1};
    std::atomic<bool> seekRequested_{false};
    std::atomic<int64_t> seekTarget_{0};

    std::atomic<bool> loopMode_{false};
    std::atomic<bool> eofReached_{false};
    // Set when the worker does an EOF-loop seek and the render thread hasn't
    // observed the wrap yet. Suppresses the forward-jump check during that
    // race window (current=high, lastDec=low — looks like a giant forward
    // jump but is actually a benign wrap-skew). Cleared once render's
    // current value drops to the new loop's early portion.
    std::atomic<bool> justEofLooped_{false};

    // Borrow slot: held under queueMutex_ during copy; render-thread-only consumer
    HapDecodedFrame borrowedFrame_;
};

} // namespace videocomposer

#endif // VIDEOCOMPOSER_ASYNCHAPDECODER_H