diff --git a/src/Lib/Video.cc b/src/Lib/Video.cc new file mode 100644 index 0000000000000000000000000000000000000000..d6fe6e57eda1ae7d358d8d4be4a5ef2ccbd99fd5 --- /dev/null +++ b/src/Lib/Video.cc @@ -0,0 +1,50 @@ +#include "Video.hh" +#include "Utils.hh" + +#include <QJsonObject> +#include <QJsonArray> + +using namespace Vivy; + +VideoContext::VideoContext(const QString &path) + : filePath(path) +{ + if (!format) + throw std::runtime_error("out of memory, can't create allocate the AVFormatContext"); + + const std::string stdFilename = filePath.toStdString(); + const char *filename = stdFilename.c_str(); + AVFormatContext *formatPtr = format.get(); + + // Get info from video file + if (avformat_open_input(&formatPtr, filename, nullptr, nullptr) != 0) { + [[maybe_unused]] void *relatedOnFailure = format.release(); // freed by avformat_open_input + throw std::runtime_error("failed to open file"); + } + + if (avformat_find_stream_info(formatPtr, nullptr) < 0) { + throw std::runtime_error("failed to get video stream info"); + } + + // Populate all the stream indexes + for (uint i = 0; i < format->nb_streams; ++i) { + AVStream *itFormat = format->streams[i]; + AVCodecParameters *params = itFormat->codecpar; + AVCodec *streamCodec = avcodec_find_decoder(params->codec_id); + if (streamCodec && streamCodec->type == AVMEDIA_TYPE_VIDEO) { + videoStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFormat, i)); + } + } + + // Get the default stream + defaultStreamIndex = av_find_best_stream(formatPtr, AVMEDIA_TYPE_VIDEO, + -1, // Let AV find one stream + -1, // We don't want related streams + nullptr, 0); + if (defaultStreamIndex < 0) { + qCritical() << "Could not find the best video stream"; + } + + qDebug() << "Opened video context for" << path << "with duration" << formatPtr->duration + << "and default stream index" << defaultStreamIndex; +} diff --git a/src/Lib/Video.hh b/src/Lib/Video.hh new file mode 100644 index 0000000000000000000000000000000000000000..8dbb47580865610cb94b14c54c59ad1fdfaefe0d --- /dev/null +++ b/src/Lib/Video.hh @@ -0,0 +1,88 @@ +#pragma once + +#ifndef __cplusplus +#error "This is a C++ header" +#endif + +extern "C" { +#include <libavutil/opt.h> +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libswresample/swresample.h> +#include <libavcodec/avfft.h> +#include <memory.h> +} + +#include "Utils.hh" +#include <QtGlobal> +#include <QMap> +#include <QVector> +#include <QString> + +namespace Vivy +{ +class VideoContext; + +// Like an audio context, but for videos. +class VideoContext final { + VIVY_UNMOVABLE_OBJECT(VideoContext) + +public: + // Hold all the data for a video stream. Should only be owned by the parent + // VideoContext instance. + class Stream final { + VIVY_UNMOVABLE_OBJECT(Stream) + + public: + Stream(AVCodec *, AVFormatContext *, AVStream *, int index); + ~Stream() noexcept; + + size_t getWidth() const noexcept; + size_t getHeight() const noexcept; + size_t getDuration() const noexcept; + size_t getFramesPerSecond() const noexcept; + + QJsonObject getProperties() const noexcept; + + static inline Utils::DeleterFunctionType<AVCodecContext> codecContexteleter = + std::bind_front(Utils::freePPtrIfNotNull<AVCodecContext>, avcodec_free_context); + + using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(codecContexteleter)>; + + private: + AVCodecID codecId{ AV_CODEC_ID_NONE }; + AVCodec *codec{ nullptr }; + AVCodecParameters *codecParams{ nullptr }; + AVCodecContextPtr codecContext{ nullptr }; + + AVStream *videoStream{ nullptr }; + + int streamIndexInVideoContext; + }; + + using StreamPtr = std::shared_ptr<Stream>; + using StreamWeakPtr = std::weak_ptr<Stream>; + +public: + VideoContext(const QString &path); + + StreamWeakPtr getStream(int) const noexcept; + StreamWeakPtr getDefaultStream() const noexcept; + + QString getElementName() const noexcept; + QJsonDocument getProperties() const noexcept; + +private: + static inline Utils::DeleterFunctionType<AVFormatContext> avFormatContextDeleter = + std::bind_front(Utils::freePtrIfNotNull<AVFormatContext>, avformat_free_context); + using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(avFormatContextDeleter)>; + AVFormatContextPtr format{ avformat_alloc_context(), avFormatContextDeleter }; + + const QString filePath; + QMap<uint, StreamPtr> videoStreams{}; + + int defaultStreamIndex{ -1 }; + + StreamPtr spareNullSreamPtr{ nullptr }; +}; +};