diff --git a/src/Lib/AbstractMediaContext.hh b/src/Lib/AbstractMediaContext.hh new file mode 100644 index 0000000000000000000000000000000000000000..bc896bfcc193203a6f1732eb93116c34c1e1178a --- /dev/null +++ b/src/Lib/AbstractMediaContext.hh @@ -0,0 +1,217 @@ +#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> +#include <QJsonObject> +#include <QJsonDocument> +#include <QJsonArray> + +namespace Vivy +{ +static inline Utils::DeleterFunctionType<double> dataDeleter = + std::bind_front(Utils::freePtrIfNotNull<double>, av_free); + +static inline Utils::DeleterFunctionType<AVCodecContext> codecContexteleter = + std::bind_front(Utils::freePPtrIfNotNull<AVCodecContext>, avcodec_free_context); + +static inline Utils::DeleterFunctionType<AVFrame> avFrameDeleter = + std::bind_front(Utils::freePPtrIfNotNull<AVFrame>, av_frame_free); + +static inline Utils::DeleterFunctionType<SwrContext> swrContenxtDeleter = + std::bind_front(Utils::freePPtrIfNotNull<SwrContext>, swr_free); + +static inline Utils::DeleterFunctionType<AVFormatContext> avFormatContextDeleter = + std::bind_front(Utils::freePtrIfNotNull<AVFormatContext>, avformat_free_context); + +using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(avFormatContextDeleter)>; +using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(codecContexteleter)>; +using DataPtr = std::shared_ptr<double[]>; +using AVFramePtr = std::unique_ptr<AVFrame, decltype(avFrameDeleter)>; +using SwrContextPtr = std::unique_ptr<SwrContext, decltype(swrContenxtDeleter)>; + +// Simple abstract class to be inherited by video streams and audio streams. +template <size_t AVMEDIA_TYPE> class AbstractMediaStream { + VIVY_UNMOVABLE_OBJECT(AbstractMediaStream) + +public: + static inline constexpr AVMediaType avMediaType = static_cast<AVMediaType>(AVMEDIA_TYPE); + +protected: + AbstractMediaStream(AVCodec *streamCodec, AVFormatContext *formatArg, AVStream *streamArg, + int index) + : codecId(streamArg->codecpar->codec_id) + , codec(streamCodec) + , codecParams(streamArg->codecpar) + , stream(streamArg) + , dataFormat(formatArg) + , streamIndexInContext(index) + { + if (codec == nullptr) + throw std::runtime_error("failed to find a decoder for stream"); + + codecContext.reset(avcodec_alloc_context3(codec)); + if (!codecContext) + throw std::runtime_error("failed to allocate codec context"); + + if (avcodec_parameters_to_context(codecContext.get(), codecParams) < 0) + throw std::runtime_error("failed to copy parameters to codec context"); + + if (avcodec_open2(codecContext.get(), codec, nullptr) < 0) + throw std::runtime_error("failed to open audio decoder for a stream"); + + qDebug() << "[Stream] Codec" << codec->name << "id:" << codecId; + qDebug() << "[Stream] sample rate:" << codecParams->sample_rate; + qDebug() << "[Stream] bit rate: " << codecParams->bit_rate; + qDebug() << "[Stream] channels: " << codecParams->channels; + } + +public: + virtual ~AbstractMediaStream() noexcept = default; + int getStreamIndex() const noexcept { return streamIndexInContext; } + QString getName() const noexcept { return QString::fromUtf8(codec->name); } + AVCodecID getCodecId() const noexcept { return codecId; } + + virtual QJsonObject getProperties() const noexcept + { + QJsonObject ret; + ret.insert("Codec name", codec->name); + return ret; + } + +protected: + // Codec related informations + AVCodecID codecId{ AV_CODEC_ID_NONE }; + AVCodec *codec{ nullptr }; + AVCodecParameters *codecParams{ nullptr }; + AVCodecContextPtr codecContext{ nullptr }; + AVFormatContext *dataFormat{ nullptr }; + + // Stream is held by AudioContext + AVStream *stream{ nullptr }; + + // Store the index of this stream + int streamIndexInContext; +}; + +template <typename Stream> class AbstractMediaContext { + VIVY_UNMOVABLE_OBJECT(AbstractMediaContext) + +public: + static inline constexpr AVMediaType avMediaType = Stream::avMediaType; + using StreamPtr = std::shared_ptr<Stream>; + using StreamWeakPtr = std::weak_ptr<Stream>; + +public: + AbstractMediaContext(const QString &path) + : filePath(path) + { + if (!format) + throw std::runtime_error("out of memory, can't create allocate the AVFormatContext"); + + const std::string filePathStdHolder = filePath.toStdString(); + const char *filename = filePathStdHolder.c_str(); + AVFormatContext *formatPtr = format.get(); + + // Get the format from the 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 audio stream info"); + } + + // Populate all the stream indexes + for (uint i = 0; i < format->nb_streams; ++i) { + AVStream *itFmt = format->streams[i]; + AVCodecParameters *params = itFmt->codecpar; + AVCodec *streamCodec = avcodec_find_decoder(params->codec_id); + if (streamCodec && streamCodec->type == avMediaType) + audioStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFmt, i)); + } + + // Get the default stream + defaultStreamIndex = av_find_best_stream(formatPtr, avMediaType, + -1, // Let AV find one stream + -1, // We don't want related streams + nullptr, 0); + if (defaultStreamIndex < 0) + qCritical() << "Could not find the best stream"; + + qDebug() << "Opened context for" << path << "with duration" << formatPtr->duration + << "and default stream index" << defaultStreamIndex; + } + + StreamWeakPtr getStream(int index) const noexcept + { + if (index < 0) + return StreamWeakPtr{ spareNullSreamPtr }; + + uint unsignedIndex = static_cast<uint>(index); + const auto found = audioStreams.find(unsignedIndex); + + if (found != audioStreams.end()) + return StreamWeakPtr{ *found }; + + return StreamWeakPtr{ spareNullSreamPtr }; + } + + StreamWeakPtr getDefaultStream() const noexcept + { + return (defaultStreamIndex < 0) ? StreamWeakPtr{ spareNullSreamPtr } + : getStream(defaultStreamIndex); + } + + virtual QString getElementName() const noexcept + { + return "Context: " + QFileInfo(filePath).baseName(); + } + + virtual QJsonDocument getProperties() const noexcept + { + QJsonDocument ret; + QJsonArray streams; + + QFileInfo file(filePath); + + for (const auto &audioStreamPtr : audioStreams) { + streams.append(audioStreamPtr->getProperties()); + } + + QJsonObject self{ { "Streams", streams }, { "Base name", file.baseName() } }; + + ret.setObject(self); + return ret; + } + +private: + AVFormatContextPtr format{ avformat_alloc_context(), avFormatContextDeleter }; + + const QString filePath; // Usefull information + QMap<uint, StreamPtr> audioStreams{}; // THe audio streams of the file + + int defaultStreamIndex{ -1 }; + + // Spare always null shared pointer, to be used when the audioStream[i] was + // not found. + StreamPtr spareNullSreamPtr{ nullptr }; +}; +}