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