Skip to content

File VideoFileInput.h

File List > cpp > input > VideoFileInput.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_VIDEOFILEINPUT_H
#define VIDEOCOMPOSER_VIDEOFILEINPUT_H

#include "InputSource.h"
#include "HardwareDecoder.h"
#include "AsyncDecodeQueue.h"
#include "../video/GPUTextureFrameBuffer.h"
#include <cuems_mediadecoder/MediaFileReader.h>
#include <cuems_mediadecoder/VideoDecoder.h>
#include <string>
#include <memory>
#include <cstdint>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <deque>
#include <sys/stat.h>

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

namespace videocomposer {

// Forward declarations
#ifdef HAVE_VAAPI_INTEROP
class VaapiInterop;
class DisplayBackend;
#endif

class VideoFileInput : public InputSource {
public:
    VideoFileInput();
    virtual ~VideoFileInput();

    enum class HardwareDecodePreference {
        AUTO,
        SOFTWARE_ONLY,
        VAAPI,
        CUDA
    };

    // InputSource interface
    bool open(const std::string& source) override;
    void close() override;
    bool isReady() const override;
    bool readFrame(int64_t frameNumber, FrameBuffer& buffer) override;
    bool seek(int64_t frameNumber) override;
    void resetSeekState() override;
    FrameInfo getFrameInfo() const override;
    int64_t getCurrentFrame() const override;
    CodecType detectCodec() const override;
    bool supportsDirectGPUTexture() const override;
    DecodeBackend getOptimalBackend() const override;

    // Hardware decoding support
    bool readFrameToTexture(int64_t frameNumber, GPUTextureFrameBuffer& textureBuffer);

    // Additional methods specific to video files
    void setIgnoreStartOffset(bool ignore) { ignoreStartOffset_ = ignore; }
    bool getIgnoreStartOffset() const { return ignoreStartOffset_; }

    void setNoIndex(bool noIndex) { noIndex_ = noIndex; }
    bool getNoIndex() const { return noIndex_; }

    // Index path helpers (used by cuems-videoindexer CLI)
    static std::string getIndexPath(const std::string& videoPath);
    static bool isCacheValid(const std::string& videoPath);

    void setHardwareDecodePreference(HardwareDecodePreference preference) { hwPreference_ = preference; }

    void setLoopMode(bool loop, int64_t totalFrames);

#ifdef HAVE_VAAPI_INTEROP
    void setDisplayBackend(DisplayBackend* displayBackend);

    bool hasVaapiZeroCopy() const;
#endif

    void prewarmGPUPipeline();

private:
    struct FrameIndex {
        int64_t pkt_pts;
        int64_t pkt_pos;
        int64_t frame_pts;
        int64_t frame_pos;
        int64_t timestamp;
        int64_t seekpts;
        int64_t seekpos;
        uint8_t key;
    };

    bool initializeFFmpeg();
    bool openCodec();
    bool openHardwareCodec();
    bool indexFrames();
    bool isIntraFrameCodec() const;  // Check if codec is intra-frame only (all keyframes)
    void setupDirectSeekMode();      // Setup direct seek mode for intra-frame codecs
    bool seekToFrame(int64_t frameNumber);
    bool seekByTimestamp(int64_t frameNumber);
    int64_t parsePTSFromFrame(AVFrame* frame);
    bool transferHardwareFrameToGPU(AVFrame* hwFrame, GPUTextureFrameBuffer& textureBuffer, bool skipSync = false);
    void cleanup();

    // Index caching
    bool loadCachedIndex();
    void saveCachedIndex() const;

    // Media decoder module
    cuems_mediadecoder::MediaFileReader mediaReader_;
    cuems_mediadecoder::VideoDecoder videoDecoder_;

    // FFmpeg objects (kept for compatibility and advanced operations)
    AVFormatContext* formatCtx_;  // Access via mediaReader_.getFormatContext()
    AVCodecContext* codecCtx_;   // Access via videoDecoder_.getCodecContext()
    AVFrame* frame_;
    AVFrame* frameFMT_;
    SwsContext* swsCtx_;
    int swsCtxWidth_;
    int swsCtxHeight_;
    AVPixelFormat swsCtxFormat_;   // Track source format for sws context recreation
    int videoStream_;

    // Hardware decoding
    AVBufferRef* hwDeviceCtx_;        // Hardware device context
    AVFrame* hwFrame_;                // Hardware frame (for hardware decoding)
    HardwareDecoder::Type hwDecoderType_;  // Type of hardware decoder in use
    bool useHardwareDecoding_;        // Whether hardware decoding is enabled
    bool codecCtxAllocated_;          // Whether codecCtx_ was allocated separately (hardware) or is part of stream (software)
    HardwareDecodePreference hwPreference_;

#ifdef HAVE_VAAPI_INTEROP
    std::unique_ptr<VaapiInterop> vaapiInterop_;  // VAAPI zero-copy interop (owned per-instance)
    DisplayBackend* displayBackend_;  // DisplayBackend for initializing interop (not owned)
#endif

    // Frame indexing
    FrameIndex* frameIndex_;
    int64_t frameCount_;
    int64_t lastDecodedPTS_;
    int64_t lastDecodedFrameNo_;
    bool scanComplete_;
    bool byteSeek_;
    bool noIndex_;

    // Video properties
    FrameInfo frameInfo_;
    std::string currentFile_;
    bool ignoreStartOffset_;
    int64_t currentFrame_;

    // Internal state
    bool ready_;
    AVRational frameRateQ_;

    // Async frame pre-buffering (like mpv's decode-ahead)
    struct CachedFrame {
        int64_t frameNumber;
        FrameBuffer buffer;
        bool valid;
    };

    static constexpr size_t FRAME_CACHE_SIZE = 4;  // Pre-buffer up to 4 frames
    std::deque<CachedFrame> frameCache_;
    std::mutex cacheMutex_;

    // Async decode thread
    std::unique_ptr<std::thread> decodeThread_;
    std::condition_variable decodeCond_;
    std::atomic<bool> decodeThreadRunning_{false};
    std::atomic<int64_t> decodeTargetFrame_{-1};
    std::atomic<bool> decodeThreadStop_{false};

    void decodeThreadFunc();
    bool decodeFrameInternal(int64_t frameNumber, FrameBuffer& buffer);
    void startAsyncDecode(int64_t startFrame);
    void stopAsyncDecode();
    CachedFrame* findCachedFrame(int64_t frameNumber);

    // Hardware decode frame tracking (per-instance, NOT static)
    int64_t lastDecodedHWFrame_;

    // NEW: Async decode queue for smooth hardware decoding
    // This provides mpv-style pre-buffering to decouple decode latency from display timing
    std::unique_ptr<AsyncDecodeQueue> asyncDecodeQueue_;
    bool useAsyncDecode_;  // Whether to use async decode (enabled for hardware decode)
};

} // namespace videocomposer

#endif // VIDEOCOMPOSER_VIDEOFILEINPUT_H