diff --git a/CMakeLists.txt b/CMakeLists.txt index 75ad7037bace36ea74fa722eab71a4b32237dd82..bc73970b37abf6bfa8ceca76ddf5616be52aa301 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) -project(Vivy VERSION 0.1) +project(Vivy VERSION 0.1 LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) cmake_policy(SET CMP0100 NEW) # Let cmake use moc and uic for .hh files @@ -28,16 +28,16 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp") # Grab all files file(GLOB Vivy_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc" - "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c" "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/*.cc" "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/*.cc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/Lib/*.cc" "${CMAKE_CURRENT_SOURCE_DIR}/src/UI/*.cc" ) file(GLOB Vivy_INC - "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h" "${CMAKE_CURRENT_SOURCE_DIR}/src/*.hh" "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/*.hh" "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/*.hh" + "${CMAKE_CURRENT_SOURCE_DIR}/src/Lib/*.hh" "${CMAKE_CURRENT_SOURCE_DIR}/src/UI/*.hh" ) set(PROJECT_SOURCES @@ -70,20 +70,21 @@ target_link_libraries(Vivy PRIVATE ${AVFORMAT_LIBRARY}) target_include_directories(Vivy PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc) target_precompile_headers(Vivy PRIVATE # ASS headers - "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Ass.hh>" - "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Line.hh>" - "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Syl.hh>" - "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Style.hh>" - "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/AssFactory.hh>" - "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Char.hh>" + "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Ass.hh" + "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Line.hh" + "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Syl.hh" + "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Style.hh" + "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/AssFactory.hh" + "${CMAKE_CURRENT_SOURCE_DIR}/src/Ass/Char.hh" - # Utils - "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Utils.hh>" + # Libs + "${CMAKE_CURRENT_SOURCE_DIR}/src/Lib/Utils.hh" + "${CMAKE_CURRENT_SOURCE_DIR}/src/Lib/Audio.hh" # Document headers - "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Document/VivyDocument.hh>" - "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Document/VivyDocumentStore.hh>" - "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/src/Document/CRTPSubDocument.hh>" + "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/VivyDocument.hh" + "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/VivyDocumentStore.hh" + "${CMAKE_CURRENT_SOURCE_DIR}/src/Document/CRTPSubDocument.hh" ) # More options and warnings diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..17c97c662b07573e06dce2ff27cce04694fc2c5e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,56 @@ +# Vivy developer help + +Some rules and help. + +### Using the namespace + +All Vivy's code must live inside the `Vivy` namespace. There are some +rules to keep in mind: + +- You can use `using namespace Vivy;` inside source files, but + ***NEVER*** inside headers files +- The utility garbage must live inside its own sub-namespace + `Vivy::Utils`. You ***can't*** `using namespace Vivy::Utils` apart + from inside one of the utils source files because it will ruin the + purpose of using a different namespace for that +- You can also ***NEVER*** `using namespace std;` + +### Code dependencies inside the project + +The dependencies between the src's sub-folders are as follows: + +| folder | the folders dependencies | +| :----------- | :----------------------- | +| src | *everything* | +| src/Lib | *nothing* | +| src/Ass | src/Lib | +| src/Document | src/Ass, src/Lib | +| src/UI | src/Document, src/Lib | + +The C++ source files are `.cc` files and their corresponding header +files are `.hh` files. + +### C++ Standard + +Here we use C++20, or at least we try. Even if the stl has some good +points, the use of Qt is favored over the stl as it will result in a +more coherent code base and less conversions between the types used in +the Vivy's lib and the types used in Vivy's UI. + +#### Used or will be used features + +Some of the new features will be used: + +- concepts (of course!) +- coroutines +- likely and unlikely attributes + +#### Unused features + +Some of the feature may not be +used or not already be in use for multiple reasons: + +- the new C++20 modules feature is not used because I don't know how it +will play with `moc` + + diff --git a/README.md b/README.md index 672a97fd76cd475bda8e4facd265889899476bd0..e4a74754eb1a08f03bdb876755146a853b5e4f78 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,25 @@ To do a debug build, use the debug switch with cmake: ### Dependencies +This project depends on cmake as a build system. For dependencies on +Windows and MacOS, the easier thing to do is grab qtcreator and find a +way to add the av libraries to it. + +#### Ubuntu/debian + Vivy depends on Qt5, libav and more. On ubuntu install the following packages: `qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libavutil-dev libavcodec-dev libavformat-dev`. +#### Arch/Manjaro + +TODO! + +#### FreeBSD and other toasters + +You will need cmake, qt and av dev packages. Set your favorite C++ +compiler with `-DCMAKE_CXX_COMPILER` and you're good to go. + +--- + ## TODO! diff --git a/src/Ass/AssFactory.hh b/src/Ass/AssFactory.hh index 48f849b01cc6ef60afbaa5b4d18627b59d24f439..e5343ec97deae93cbd864fa1844ca2fb72512b4a 100644 --- a/src/Ass/AssFactory.hh +++ b/src/Ass/AssFactory.hh @@ -1,7 +1,7 @@ #ifndef VIVY_ASS_FACTORY_H #define VIVY_ASS_FACTORY_H -#include "../Utils.hh" +#include "../Lib/Utils.hh" #include "Style.hh" #include "Line.hh" diff --git a/src/AudioUtils.c b/src/AudioUtils.c deleted file mode 100644 index 92b6303676e7b8e73bb72185ce0c5bf7787210f7..0000000000000000000000000000000000000000 --- a/src/AudioUtils.c +++ /dev/null @@ -1,236 +0,0 @@ -#include "AudioUtils.h" - -#include <assert.h> -#include <libavutil/opt.h> -#include <libavcodec/avcodec.h> -#include <libavformat/avformat.h> -#include <libswresample/swresample.h> -#include <libavcodec/avfft.h> - -#define MAXPIXVALUE 7 /* Some magix AV magic stuff */ - -int -decodeAudioFile(const char *path, const int sample_rate, double **const data, size_t *const size) -{ - AVPacket packet; - AVFormatContext *format = avformat_alloc_context(); - struct SwrContext *swr = swr_alloc(); - AVFrame *frame = av_frame_alloc(); - int ret_code = -1; - ssize_t stream_index = -1; - AVStream *stream = NULL; - AVCodecContext *codec_ctx = NULL; - AVCodecParameters *codec_par = NULL; - AVCodec *codec = NULL; - - if (!frame) { - fprintf(stderr, "Error allocating the frame\n"); - goto exit_error; - } - - /* Get format from audio file */ - if (avformat_open_input(&format, path, NULL, NULL) != 0) { - fprintf(stderr, "Could not open file '%s'\n", path); - goto exit_error; - } - if (avformat_find_stream_info(format, NULL) < 0) { - fprintf(stderr, "Could not retrieve stream info from file '%s'\n", path); - return -1; - } - - /* Find the index of the first audio stream */ - for (unsigned int i = 0; i < format->nb_streams; i++) { - codec = avcodec_find_encoder(format->streams[i]->codecpar->codec_id); - if (codec->type == AVMEDIA_TYPE_AUDIO) { - stream_index = i; - break; - } - } - if (stream_index == -1) { - fprintf(stderr, "Could not retrieve audio stream from file '%s'\n", path); - goto exit_error; - } - stream = format->streams[stream_index]; - - /* Find & open codec */ - codec_ctx = stream->codec; - codec_par = stream->codecpar; - codec = avcodec_find_decoder(codec_par->codec_id); - - if (codec == NULL) { - fprintf(stderr, "Failed to find decoder for stream #%zu in file '%s'\n", stream_index, - path); - goto exit_error; - } - - if (avcodec_open2(codec_ctx, codec, NULL) < 0) { - fprintf(stderr, "Failed to open decoder for stream #%zu in file '%s'\n", stream_index, - path); - goto exit_error; - } - - /* Prepare resampler */ - av_opt_set_int(swr, "in_channel_count", codec_ctx->channels, 0); - av_opt_set_int(swr, "out_channel_count", 1, 0); - av_opt_set_int(swr, "in_channel_layout", (int64_t)codec_ctx->channel_layout, 0); - av_opt_set_int(swr, "out_channel_layout", AV_CH_LAYOUT_MONO, 0); - av_opt_set_int(swr, "in_sample_rate", codec_ctx->sample_rate, 0); - av_opt_set_int(swr, "out_sample_rate", sample_rate, 0); - av_opt_set_sample_fmt(swr, "in_sample_fmt", codec_ctx->sample_fmt, 0); - av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_DBL, 0); - swr_init(swr); - if (!swr_is_initialized(swr)) { - fprintf(stderr, "Resampler has not been properly initialized\n"); - goto exit_error; - } - - /* Prepare to read data */ - av_init_packet(&packet); - - /* Iterate through frames */ - *data = NULL; - *size = 0; - while (av_read_frame(format, &packet) >= 0) { - /* Decode one frame */ - int gotFrame; - if (avcodec_decode_audio4(codec_ctx, frame, &gotFrame, &packet) < 0) { - break; - } - if (!gotFrame) { - continue; - } - - /* Resample frames */ - double *buffer; - av_samples_alloc((uint8_t **)&buffer, NULL, 1, frame->nb_samples, AV_SAMPLE_FMT_DBL, 0); - const int frame_count_int = swr_convert(swr, (uint8_t **)&buffer, frame->nb_samples, - (const uint8_t **)frame->data, frame->nb_samples); - assert(frame_count_int >= 0); - const size_t frame_count = (size_t)frame_count_int; - - /* Append resampled frames to data */ - *data = (double *)realloc(*data, (*size + (size_t)frame->nb_samples) * sizeof(double)); - memcpy(*data + *size, buffer, frame_count * sizeof(double)); - av_free(buffer); - *size += frame_count; - } - - /* Clean up */ - ret_code = 0; -exit_error: - if (frame != NULL) - av_frame_free(&frame); - if (swr != NULL) - swr_free(&swr); - if (codec_ctx != NULL) - avcodec_close(codec_ctx); - if (format != NULL) - avformat_free_context(format); - - return ret_code; -} - -struct ___RawImageData { - const int width; /* The width of the image */ - const int height; /* The height of the image */ - const unsigned char *pixs; /* The pixels of the image */ - - /* Private stuff */ - FFTSample *chunk_data; - RDFTContext *ctx; -}; - -void -RawImageDataFree(RawImageData *const ptr) -{ - free((void *)ptr->pixs); - av_rdft_end(ptr->ctx); - av_free(ptr->chunk_data); - free(ptr); -} - -int -RawImageDataGetWidth(RawImageData *const ptr) -{ - return ptr->width; -} - -int -RawImageDataGetHeight(RawImageData *const ptr) -{ - return ptr->height; -} - -unsigned char * -RawImageDataDuplicatePixels(RawImageData *const ptr) -{ - const size_t size = sizeof(unsigned char) * (size_t)ptr->width * (size_t)ptr->height / 2; - unsigned char *ret = (unsigned char *)malloc(size); - assert(ret); - memcpy(ret, ptr->pixs, size); - return ret; -} - -#define CAP_VALUE(_value, _lower, _upper) \ - { \ - if (_value > _upper) { \ - _value = _upper; \ - } else if (_value < _lower) { \ - _value = _lower; \ - } \ - } - -RawImageData * -RawImageDataNewFromData(const double *const data, const size_t size) -{ - const unsigned int chunk_size = 512; - const unsigned int overlap = 128; - const unsigned int decal = chunk_size - overlap; - const size_t width = (size - chunk_size) / decal; - const size_t height = chunk_size; - unsigned char *pixs = (unsigned char *)malloc(sizeof(unsigned char) * width * height / 2); - FFTSample *chunk_data = (FFTSample *)av_malloc_array(2 * chunk_size, sizeof(FFTSample)); - RDFTContext *ctx = av_rdft_init((int)log2(chunk_size), DFT_R2C); - - if (!(pixs && chunk_data && ctx)) { - if (pixs) - free(pixs); - if (chunk_data) - av_free(chunk_data); - if (ctx) - av_rdft_end(ctx); - return NULL; - } - - for (size_t x = 0, i = 0; i < size - chunk_size; i += decal, ++x) { -#pragma omp parallel for - for (size_t j = 0; j < chunk_size; j++) { - const double curr_dat = data[i + j]; - const double window_modifier = 0.5 * (1 - cos(2 * M_PI * ((int)j) / (chunk_size - 1))); - float value = (float)(window_modifier * curr_dat); - CAP_VALUE(value, -1.0f, 1.0f); - chunk_data[j] = value; - } - - av_rdft_calc(ctx, chunk_data); - -#pragma omp parallel for - for (size_t j = 0; j < chunk_size / 2; j++) { - const float im = chunk_data[j * 2]; - const float re = chunk_data[j * 2 + 1]; - const float mag = sqrtf(im * im + re * re); - pixs[j * width + x] = (unsigned char)(mag)*MAXPIXVALUE; - } - } - - RawImageData *ret = (RawImageData *)malloc(sizeof(RawImageData)); - RawImageData ret_data = { - .width = (int)width, - .height = (int)height, - .pixs = pixs, - .chunk_data = chunk_data, - .ctx = ctx, - }; - memcpy(ret, &ret_data, sizeof(RawImageData)); - return ret; -} diff --git a/src/AudioUtils.h b/src/AudioUtils.h deleted file mode 100644 index 41f36a3cf0fc2c6dcb5fd76bd14be8fd8a88fca0..0000000000000000000000000000000000000000 --- a/src/AudioUtils.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef VIVY_AUDIOUTILS_H -#define VIVY_AUDIOUTILS_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include <libavcodec/avcodec.h> - -int decodeAudioFile(const char *path, const int sample_rate, double **const data, - size_t *const size); - -typedef struct ___RawImageData RawImageData; -RawImageData *RawImageDataNewFromData(const double *const data, const size_t size); -void RawImageDataFree(RawImageData *const); -int RawImageDataGetWidth(RawImageData *const); -int RawImageDataGetHeight(RawImageData *const); -unsigned char *RawImageDataDuplicatePixels(RawImageData *const); - -#ifdef __cplusplus -} -#endif - -#endif // VIVY_AUDIOUTILS_H diff --git a/src/Document/CRTPSubDocument.cc b/src/Document/CRTPSubDocument.cc new file mode 100644 index 0000000000000000000000000000000000000000..85983b5e5389a04d152ba1fe50efd10d09e387a7 --- /dev/null +++ b/src/Document/CRTPSubDocument.cc @@ -0,0 +1,73 @@ +#include "CRTPSubDocument.hh" + +using namespace Vivy; + +// AudioSubDocument implementation + +// Get the default stream index or -1 if not possible +int +AudioSubDocument::getDefaultStreamIndex() const noexcept +{ + if (auto ptr = getDefaultStream()) { + return ptr->getStreamIndex(); + } else { + return -1; + } +} + +// Get a pointer to the default stream, nullptr if not possible +AudioContext::StreamPtr +AudioSubDocument::getDefaultStream() const noexcept +{ + if (auto ptr = contextPtr->getDefaultStream().lock()) { + return ptr; + } else { + return nullptr; + } +} + +// Get the stream count, may be 0 +int +AudioSubDocument::getStreamCount() const noexcept +{ + return contextPtr->getStreamCount(); +} + +// Get the stream asked for, nullptr if no stream or if the index is invalid +AudioContext::StreamPtr +AudioSubDocument::getStream(int index) const noexcept +{ + if (auto ptr = contextPtr->getStream(index).lock()) { + return ptr; + } else { + return nullptr; + } +} + +// Init an audio sub-document from a file +void +AudioSubDocument::initFromPath(const QString &path) +{ + if (contextPtr) + qDebug() << "Replacing the audio contetx by a new one for file" << path; + contextPtr.reset(new AudioContext(path)); // May throw + + qDebug() << "Audio OK for" << path; +} + +// VideoSubDocument implementation + +// Init a video sub-document from a file +void +VideoSubDocument::initFromPath(const QString &) +{ +} + +// AssSubDocument implementation +void +AssSubDocument::initFromPath(const QString &path) +{ + Ass::AssFactory factory(path); + factory.getStyles(styles); + factory.getLines(lines); +} diff --git a/src/Document/CRTPSubDocument.hh b/src/Document/CRTPSubDocument.hh index 7d1dcebd18c52306bf41cf8f0cb4a4cd97fb1875..29b1c620eceb0e0c442b238a2d43ac37dd67cd13 100644 --- a/src/Document/CRTPSubDocument.hh +++ b/src/Document/CRTPSubDocument.hh @@ -5,31 +5,15 @@ #error "This is a C++ header" #endif -#include "../Utils.hh" +#include "../Lib/Utils.hh" +#include "../Lib/Audio.hh" #include "../Ass/Ass.hh" #include <QString> #include <memory> -/* Types for the different documents */ - namespace Vivy { -enum class AudioDocumentType : quint64 { - MP3 = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MP3), - OGG = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::OGG), - M4A = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::M4A), -}; - -enum class VideoDocumentType : quint64 { - MKV = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MKV), - MP4 = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MP4), -}; - -enum class AssDocumentType : quint64 { - ASS = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::ASS), -}; - -/* The Big CRTP class for all common things to all the subdocuments */ +// The Big CRTP class for all common things to all the subdocuments template <class CRTPSubDocumentType, class Document> class CRTPSubDocument { public: using Type = CRTPSubDocumentType; @@ -53,7 +37,14 @@ public: qDebug() << "Init document from file " << path; auto ret = std::unique_ptr<Document>(new Document()); ret->filePath = path; - ret->initFromPath(path); + + try { + ret->initFromPath(path); // May throw + } catch (const std::runtime_error &e) { + qDebug() << "Failed to init document from file" << path; + ret.reset(); + } + return ret; } @@ -68,43 +59,42 @@ public: } }; -/* Audio document */ +// Audio document class AudioSubDocument final : public CRTPSubDocument<AudioDocumentType, AudioSubDocument> { private: const QStringList &suffixList = Vivy::Utils::audioFileSuffix; - inline void initFromPath([[maybe_unused]] const QString &path) noexcept - { - qDebug() << "Audio OK for" << path; - } + void initFromPath(const QString &); explicit AudioSubDocument() = default; friend CRTPSubDocument<AudioDocumentType, AudioSubDocument>; + +private: + std::unique_ptr<AudioContext> contextPtr; + +public: + int getDefaultStreamIndex() const noexcept; + AudioContext::StreamPtr getDefaultStream() const noexcept; + int getStreamCount() const noexcept; + AudioContext::StreamPtr getStream(int index) const noexcept; }; -/* Video document */ +// Video document class VideoSubDocument final : public CRTPSubDocument<VideoDocumentType, VideoSubDocument> { private: const QStringList &suffixList = Vivy::Utils::videoFileSuffix; - inline void initFromPath([[maybe_unused]] const QString &path) noexcept - { - } + void initFromPath(const QString &); explicit VideoSubDocument() noexcept = default; friend CRTPSubDocument<VideoDocumentType, VideoSubDocument>; }; -/* ASS document */ +// ASS document class AssSubDocument final : public CRTPSubDocument<AssDocumentType, AssSubDocument> { const QStringList &suffixList = Vivy::Utils::assFileSuffix; - inline void initFromPath([[maybe_unused]] const QString &path) noexcept - { - Ass::AssFactory factory(path); - factory.getStyles(styles); - factory.getLines(lines); - } + void initFromPath(const QString &); explicit AssSubDocument() noexcept = default; friend CRTPSubDocument<AssDocumentType, AssSubDocument>; diff --git a/src/Document/VivyDocument.cc b/src/Document/VivyDocument.cc index c2f04cabf8c1986d1300c0c8768fa8ea34767638..53dd53d970cfc5c0a9deb365aa9e8380753d957b 100644 --- a/src/Document/VivyDocument.cc +++ b/src/Document/VivyDocument.cc @@ -1,5 +1,5 @@ #include "VivyDocument.hh" -#include "../Utils.hh" +#include "../Lib/Utils.hh" #include <QFileInfo> #include <QString> @@ -33,8 +33,9 @@ VivyDocument::loadSubDocument(const QString &name) noexcept return true; case Capabilities::VideoAble: - qDebug() << "Auto-detect video document for" << name; + qDebug() << "Auto-detect video (and try audio) document for" << name; setVideoSubDocument(file.absoluteFilePath()); + setAudioSubDocument(file.absoluteFilePath()); return true; case Capabilities::AssAble: @@ -192,11 +193,14 @@ VivyDocument::setAudioSubDocument(const QString filename) noexcept if (auto doc = audioDocument.get()) { // Here we may want to do some confirmation / cleanup + audioDocument.reset(); } audioDocument = AudioSubDocument::fromFile(filename); - documentType |= Capabilities::AudioAble; - documentOptions &= ~Options::UntouchedByDefault; + if (audioDocument) { + documentType |= Capabilities::AudioAble; + documentOptions &= ~Options::UntouchedByDefault; + } } void @@ -208,11 +212,14 @@ VivyDocument::setVideoSubDocument(const QString filename) noexcept if (auto doc = videoDocument.get()) { // Here we may want to do some confirmation / cleanup + videoDocument.reset(); } videoDocument = VideoSubDocument::fromFile(filename); - documentType |= Capabilities::VideoAble; - documentOptions &= ~Options::UntouchedByDefault; + if (videoDocument) { + documentType |= Capabilities::VideoAble; + documentOptions &= ~Options::UntouchedByDefault; + } } void @@ -224,9 +231,12 @@ VivyDocument::setAssSubDocument(const QString filename) noexcept if (auto doc = assDocument.get()) { // Here we may want to do some confirmation / cleanup + assDocument.reset(); } assDocument = AssSubDocument::fromFile(filename); - documentType |= Capabilities::AssAble; - documentOptions &= ~Options::UntouchedByDefault; + if (assDocument) { + documentType |= Capabilities::AssAble; + documentOptions &= ~Options::UntouchedByDefault; + } } diff --git a/src/Document/VivyDocument.hh b/src/Document/VivyDocument.hh index 16bef9dc8ed6eefea979c33d96a364209a9b8638..4d7fa9e4031e5eb3375068aae948eca0557e95e6 100644 --- a/src/Document/VivyDocument.hh +++ b/src/Document/VivyDocument.hh @@ -5,7 +5,7 @@ #error "This is a C++ header" #endif -#include "../Utils.hh" +#include "../Lib/Utils.hh" #include "CRTPSubDocument.hh" #include <memory> diff --git a/src/Document/VivyDocumentStore.hh b/src/Document/VivyDocumentStore.hh index f25fd3f7d374840596fa81ee0df07a38ddc4db66..047a49ab29172b076b88a43af606f681fde0d5fa 100644 --- a/src/Document/VivyDocumentStore.hh +++ b/src/Document/VivyDocumentStore.hh @@ -2,7 +2,7 @@ #define VIVY_DOCUMENTSTORE_H #include "VivyDocument.hh" -#include "../Utils.hh" +#include "../Lib/Utils.hh" #include <QMap> #include <QString> diff --git a/src/Lib/Audio.cc b/src/Lib/Audio.cc new file mode 100644 index 0000000000000000000000000000000000000000..65c1cdc856d01a6d6a8023dc87ea1925fd751c78 --- /dev/null +++ b/src/Lib/Audio.cc @@ -0,0 +1,343 @@ +#include "Audio.hh" + +#include <QJsonObject> +#include <QJsonArray> + +using namespace Vivy; + +// AudioContext class implementation + +// Create an audio contecxt from a file +AudioContext::AudioContext(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 audio file + if (avformat_open_input(&formatPtr, filename, NULL, NULL) != 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, NULL) < 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 *itFormat = format->streams[i]; + AVCodecParameters *params = itFormat->codecpar; + AVCodec *streamCodec = avcodec_find_decoder(params->codec_id); + if (streamCodec && streamCodec->type == AVMEDIA_TYPE_AUDIO) { + audioStreamIndexes.push_back(i); + audioStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFormat, i)); + } + } + + // Get the default stream + defaultStreamIndex = av_find_best_stream(formatPtr, AVMEDIA_TYPE_AUDIO, + -1, // Let AV find one stream + -1, // We don't want related streams + nullptr, 0); + + qDebug() << "Opened audio context for" << path << "with duration" << formatPtr->duration; +} + +// Get the number of audio streams in the audio context +int +AudioContext::getStreamCount() const noexcept +{ + return audioStreamIndexes.size(); +} + +// Get a specific audio stream, try to lock the weak pointer. if the index was +// not present the used_count will be 0 and you won't be able to lock it. +AudioContext::StreamWeakPtr +AudioContext::getStream(int index) const noexcept +{ + if (index >= audioStreamIndexes.size()) + return StreamWeakPtr{ spareNullSreamPtr }; + + const uint streamIndex = audioStreamIndexes[index]; + const auto found = audioStreams.find(streamIndex); + + if (found != audioStreams.end()) { + return StreamWeakPtr{ *found }; + } + + return StreamWeakPtr{ spareNullSreamPtr }; +} + +// Get the default stream of this audio context. If no default audio stream is +// present you won't be able to lock the weak pointer. +AudioContext::StreamWeakPtr +AudioContext::getDefaultStream() const noexcept +{ + if (defaultStreamIndex < 0) + return StreamWeakPtr{ spareNullSreamPtr }; + return getStream(defaultStreamIndex); +} + +QString +AudioContext::getElementName() const noexcept +{ + QFileInfo file(filePath); + return "AudioContext: " + file.baseName(); +} + +QJsonDocument +AudioContext::getProperties() const noexcept +{ + QJsonDocument ret; + QJsonObject self; + QJsonArray streams; + + for (const auto &audioStreamPtr : audioStreams) { + streams.append(audioStreamPtr->getProperties()); + } + + self.insert("File path", filePath); + self.insert("Streams", streams); + + ret.setObject(self); + return ret; +} + +// AudioContext::Stream class implementation + +// Constructor, need an AVFormat and an AVStream +AudioContext::Stream::Stream(AVCodec *streamCodec, AVFormatContext *format, AVStream *stream, + int index) + : codecId(stream->codecpar->codec_id) + , codec(streamCodec) + , codecParams(stream->codecpar) + , audioStream(stream) + , streamIndexInAudioContext(index) + , dataFormat(format) +{ + 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"); + + SwrContext *s = dataSwrContext.get(); + av_opt_set_int(s, "in_channel_count", codecContext->channels, 0); + av_opt_set_int(s, "out_channel_count", 1, 0); + av_opt_set_int(s, "in_channel_layout", static_cast<int64_t>(codecContext->channel_layout), 0); + av_opt_set_int(s, "out_channel_layout", AV_CH_LAYOUT_MONO, 0); + av_opt_set_int(s, "in_sample_rate", codecContext->sample_rate, 0); + av_opt_set_int(s, "out_sample_rate", resamplerSampleRate, 0); + av_opt_set_sample_fmt(s, "in_sample_fmt", codecContext->sample_fmt, 0); + av_opt_set_sample_fmt(s, "out_sample_fmt", AV_SAMPLE_FMT_DBL, 0); + swr_init(s); + if (!swr_is_initialized(s)) + throw std::runtime_error("failed to initialize SwrContext resampler"); + + 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; +} + +AudioContext::Stream::~Stream() noexcept +{ + if (dataPtr) + free(dataPtr); +} + +QJsonObject +AudioContext::Stream::getProperties() const noexcept +{ + QJsonObject ret; + ret.insert("Codec name", codec->name); + ret.insert("Sample rate", codecParams->sample_rate); + ret.insert("Bit rate", static_cast<int>(codecParams->bit_rate)); + ret.insert("Channels", codecParams->channels); + return ret; +} + +// Decode the data, don't call it if data already decoded +void +AudioContext::Stream::decodeData() +{ + if (isDecoded()) + throw std::logic_error("audio stream is already resampled"); + qDebug() << "Launch decoding of stream" << streamIndexInAudioContext; + + AVPacket packet; + av_init_packet(&packet); + + // Iterate through frames + while (av_read_frame(dataFormat, &packet) >= 0) { + // Only decode audio + if (packet.stream_index != streamIndexInAudioContext) { + av_packet_unref(&packet); + continue; + } + + // Decode one frame + int response = avcodec_send_packet(codecContext.get(), &packet); + if (response < 0) [[unlikely]] { + throw std::runtime_error( + QStringLiteral("error n°%1 while sending a packet to the decoder") + .arg(response) + .toStdString()); + } + + double *buffer = nullptr; + int oldFrameNbSamples = 0; + + while (response >= 0) { + response = avcodec_receive_frame(codecContext.get(), dataFrame.get()); + if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) [[unlikely]] { + break; + } else if (response < 0) [[unlikely]] { + throw std::runtime_error( + QStringLiteral("error n°%1 while receiving a from from the decoder") + .arg(response) + .toStdString()); + } + + // Reuse buffer if possible + if (oldFrameNbSamples < dataFrame->nb_samples) { + dataDeleter(buffer); + oldFrameNbSamples = dataFrame->nb_samples; + av_samples_alloc(reinterpret_cast<uint8_t **>(&buffer), nullptr, 1, + dataFrame->nb_samples, AV_SAMPLE_FMT_DBL, 0); + } + + // Resample frame + if (const int frame_count_int = + swr_convert(dataSwrContext.get(), reinterpret_cast<uint8_t **>(&buffer), + dataFrame->nb_samples, (const uint8_t **)dataFrame->data, + dataFrame->nb_samples); + frame_count_int < 0) [[unlikely]] { + throw std::runtime_error("error on frame count, is negative but should not be"); + } + + // Append resampled frames to data + else [[likely]] { + const size_t frame_count = static_cast<size_t>(frame_count_int); + dataPtr = reinterpret_cast<double *>( + realloc(dataPtr, (dataSize + (size_t)dataFrame->nb_samples) * sizeof(double))); + memcpy(dataPtr + dataSize, buffer, frame_count * sizeof(double)); + dataSize += frame_count; + } + } + + dataDeleter(buffer); + av_packet_unref(&packet); + } + + qDebug() << "Decoding data finished for stream" << streamIndexInAudioContext + << "dataPtr =" << dataPtr << "with dataSize =" << dataSize; +} + +// Delete decoded data, clean up thing +void +AudioContext::Stream::cleanUpData() noexcept +{ + free(dataPtr); + dataPtr = nullptr; + dataSize = 0; +} + +// Get the number of channels in the audio context stream +int +AudioContext::Stream::getChannels() const noexcept +{ + return codecContext->channels; +} + +// Get the sample rate +int +AudioContext::Stream::getSampleRate() const noexcept +{ + return codecContext->sample_rate; +} + +// Get the sample rate of the decoded data (need to call decodeData for that +// value to have a meaning). +int +AudioContext::Stream::getDataSampleRate() const noexcept +{ + return resamplerSampleRate; +} + +// Get the name of the codec used to decode the stream +QString +AudioContext::Stream::getName() const noexcept +{ + return QString(codec->name); +} + +// Get the codec's ID used to decode the stream +AVCodecID +AudioContext::Stream::getCodecId() const noexcept +{ + return codecId; +} + +// Get the bit rate of the stream +qint64 +AudioContext::Stream::getBitRate() const noexcept +{ + return codecContext->bit_rate; +} + +// Get the information about the decoded state of this stream +bool +AudioContext::Stream::isDecoded() const noexcept +{ + return dataPtr != nullptr; +} + +// Get the decoded data's size, will return 0 if the data is not decoded. Don't +// rely on this behaviour to get the decoded state of the stream! +size_t +AudioContext::Stream::getDecodedDataSize() const noexcept +{ + return dataSize; +} + +// Get the decoded data, safe to call even if the data is not decoded has the +// pointer will be null. You must check it with an `if` statement. +double * +AudioContext::Stream::getDecodedData() const noexcept +{ + return dataPtr; +} + +// Get the chunk size of the decoded data +size_t +AudioContext::Stream::getDecodedChunkSize() const noexcept +{ + return 512; +} + +// Get the decalage of the decoded data +size_t +AudioContext::Stream::getDecodedDecalage() const noexcept +{ + constexpr size_t overlap = 128; // The overlap + return getDecodedChunkSize() - overlap; +} + +int +AudioContext::Stream::getStreamIndex() const noexcept +{ + return streamIndexInAudioContext; +} diff --git a/src/Lib/Audio.hh b/src/Lib/Audio.hh new file mode 100644 index 0000000000000000000000000000000000000000..72b67d288ea24fe93cf160e9f244ee4b7f733a75 --- /dev/null +++ b/src/Lib/Audio.hh @@ -0,0 +1,159 @@ +#ifndef VIVY_AUDIO_H +#define VIVY_AUDIO_H + +#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 "Utils.hh" +#include <QtGlobal> +#include <QMap> +#include <QVector> +#include <QString> +#include <memory.h> + +namespace Vivy +{ +class AudioContext; + +// The memory representation of an audio file. It is created inplace and should +// not be moved around. It should be used to get the properties of an audio +// file and its streams. The user can use a stream from the file and get data +// from it. +class AudioContext final { + VIVY_UNMOVABLE_OBJECT(AudioContext) + +public: + // Hold all the data for a stream in an audio file. Should only be owned by + // the AudioContext class. + class Stream final { + VIVY_UNMOVABLE_OBJECT(Stream) + + // All the needed deleters + static inline constexpr auto codecContexteleter = [](AVCodecContext *ptr) noexcept -> void { + if (ptr) + avcodec_free_context(&ptr); + }; + static constexpr inline auto dataDeleter = [](double *ptr) noexcept -> void { + if (ptr) + av_free(ptr); + }; + static constexpr inline auto avFrameDeleter = [](AVFrame *ptr) noexcept -> void { + if (ptr) + av_frame_free(&ptr); + }; + + static constexpr inline auto swrContenxtDeleter = [](SwrContext *swr) noexcept -> void { + if (swr) + swr_free(&swr); + }; + + // All the used types + 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)>; + + public: + Stream(AVCodec *, AVFormatContext *, AVStream *, int index); + ~Stream() noexcept; + + // The non-owning view of the stream's data + using DataWeakPtr = std::weak_ptr<double[]>; + + // Decode the stream + void decodeData(); + void cleanUpData() noexcept; + bool isDecoded() const noexcept; + int getDataSampleRate() const noexcept; + size_t getDecodedDataSize() const noexcept; + size_t getDecodedChunkSize() const noexcept; + size_t getDecodedDecalage() const noexcept; + double *getDecodedData() const noexcept; + + // Some getters + int getChannels() const noexcept; + int getSampleRate() const noexcept; + QString getName() const noexcept; + AVCodecID getCodecId() const noexcept; + qint64 getBitRate() const noexcept; + + // Get the index from the audio context + int getStreamIndex() const noexcept; + + QJsonObject getProperties() const noexcept; + + private: + // Codec related informations + AVCodecID codecId{ AV_CODEC_ID_NONE }; + AVCodec *codec{ nullptr }; + AVCodecParameters *codecParams{ nullptr }; + AVCodecContextPtr codecContext{ nullptr }; + + // Stream is held by AudioContext + AVStream *audioStream{ nullptr }; + + // Store the index of this stream + int streamIndexInAudioContext; + + // Resampled frame data + AVFormatContext *dataFormat; + SwrContextPtr dataSwrContext{ swr_alloc(), swrContenxtDeleter }; + AVFramePtr dataFrame{ av_frame_alloc(), avFrameDeleter }; + double *dataPtr{ nullptr }; + size_t dataSize{ 0 }; + + // Constants for the resampler + static constexpr uint resamplerChunkSize = 512; + static constexpr uint resamplerOverlap = 128; + static constexpr uint resamplerDecalage = resamplerChunkSize - resamplerOverlap; + static constexpr uint resamplerSampleRate = 44100; + }; + + using StreamPtr = std::shared_ptr<Stream>; + +public: + AudioContext(const QString &path); + ~AudioContext() noexcept = default; + + // The stream non-owning view pointer + using StreamWeakPtr = std::weak_ptr<Stream>; + + int getStreamCount() const noexcept; + StreamWeakPtr getStream(int) const noexcept; + StreamWeakPtr getDefaultStream() const noexcept; + + QString getElementName() const noexcept; + QJsonDocument getProperties() const noexcept; + +private: + // Regarding the format + static inline constexpr auto avFormatContextDeleter = + [](AVFormatContext *ptr) noexcept -> void { + if (ptr) + avformat_free_context(ptr); + }; + using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(avFormatContextDeleter)>; + AVFormatContextPtr format{ avformat_alloc_context(), avFormatContextDeleter }; + + QString filePath; // Usefull information + QVector<uint> audioStreamIndexes{}; // Index all the audio streams of the file + 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 }; +}; +} + +#endif // VIVY_AUDIO_H diff --git a/src/Utils.cc b/src/Lib/Utils.cc similarity index 100% rename from src/Utils.cc rename to src/Lib/Utils.cc diff --git a/src/Utils.hh b/src/Lib/Utils.hh similarity index 63% rename from src/Utils.hh rename to src/Lib/Utils.hh index 97bfa1f57e5e1f86d8dafa6646808cc499aa7689..95f8e7488117d1242f52cf8f5e7b6d8bde94d147 100644 --- a/src/Utils.hh +++ b/src/Lib/Utils.hh @@ -6,6 +6,7 @@ #include <QStringList> #include <QDebug> #include <QMessageLogger> +#include <QJsonDocument> #include <QtGlobal> #include <type_traits> @@ -16,6 +17,19 @@ classname &operator=(const classname &) = delete; /* Copy assign */ \ classname &operator=(classname &&) = delete; /* Move assign */ +namespace Vivy +{ +// Concept for classes that can be viewd inside a property view +template <typename T> +concept PropertyConstViewable = requires(T element) +{ + // clang-format off + { element.getElementName() } -> std::same_as<QString>; + { element.getProperties() } -> std::same_as<QJsonDocument>; + // clang-format on +}; +} + namespace Vivy::Utils { static const QStringList audioFileSuffix = { "wave", "wav", "ogg", "mp3", "m4a" }; @@ -54,7 +68,26 @@ to_underlying(E e) noexcept bool detectDocumentType(const QFileInfo &, DocumentType *); } -/* All forward delcarations are placed here */ +namespace Vivy +{ +// Audio document types +enum class AudioDocumentType : quint64 { + MP3 = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MP3), + OGG = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::OGG), + M4A = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::M4A), +}; + +// Video document types +enum class VideoDocumentType : quint64 { + MKV = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MKV), + MP4 = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::MP4), +}; + +// Ass document types +enum class AssDocumentType : quint64 { + ASS = Vivy::Utils::to_underlying(Vivy::Utils::DocumentType::ASS), +}; +} class QMenu; class QAction; diff --git a/src/UI/AudioVisualizer.cc b/src/UI/AudioVisualizer.cc index b53c20d8c93734f23a08005c0727309527922814..a85ac12664f3ba222972bba19f0b99e4aca84f28 100644 --- a/src/UI/AudioVisualizer.cc +++ b/src/UI/AudioVisualizer.cc @@ -1,5 +1,5 @@ #include "AudioVisualizer.hh" -#include "../AudioUtils.h" +#include "../Lib/Audio.hh" #include <QGraphicsPixmapItem> #include <QLabel> @@ -10,9 +10,77 @@ using namespace Vivy; -AudioVisualizer::AudioVisualizer(QWidget *parent) noexcept +#define MAXPIXVALUE 7 // Some magix AV magic stuff + +#define CAP_VALUE(_value, _lower, _upper) \ + { \ + if (_value > _upper) { \ + _value = _upper; \ + } else if (_value < _lower) { \ + _value = _lower; \ + } \ + } + +AudioVisualizer::AudioVisualizer(AudioContext::StreamPtr stream, QWidget *parent) : QWidget(parent) { + if (!stream->isDecoded()) { + qDebug() << "Need to decode data for stream" << stream->getStreamIndex(); + stream->decodeData(); + } + + double *decodedData = stream->getDecodedData(); + if (decodedData == nullptr) + throw std::logic_error("the passed stream is not decoded"); + + const size_t size = stream->getDecodedDataSize(); + const size_t height = stream->getDecodedChunkSize(); + const size_t decalage = stream->getDecodedDecalage(); + const size_t width = (size - height) / decalage; + uchar *pixels = new uchar[static_cast<size_t>(width * height / 2)](); + + FFTSamplePtr chunkData( + reinterpret_cast<FFTSample *>(av_malloc_array(2 * height, sizeof(FFTSample))), + fftSampleDeleter); + RDFTContextPtr ctx(av_rdft_init((static_cast<int>(log2(static_cast<int>(height)))), DFT_R2C), + rdftContextDeleter); + + if (!pixels) { + throw std::runtime_error("out of memory"); + } else if (!(chunkData && ctx)) { + delete[] pixels; + throw std::runtime_error("out of memory"); + } + + /* Compute the image data */ + + for (size_t x = 0, i = 0; i < size - height; i += decalage, ++x) { +#pragma omp parallel for + for (size_t j = 0; j < height; j++) { + const double curr_dat = decodedData[i + j]; + const double window_modifier = + (1 - cos(2 * M_PI * static_cast<double>(j) / static_cast<double>(height - 1))) / 2; + float value = static_cast<float>(window_modifier * curr_dat); + CAP_VALUE(value, -1.0f, 1.0f); + chunkData[j] = value; + } + + av_rdft_calc(ctx.get(), chunkData.get()); + +#pragma omp parallel for + for (size_t j = 0; j < height / 2; j++) { + const float im = chunkData[j * 2]; + const float re = chunkData[j * 2 + 1]; + const float mag = sqrtf(im * im + re * re); + const size_t index = static_cast<size_t>(j * static_cast<ulong>(width) + x); + pixels[index] = (unsigned char)(mag)*MAXPIXVALUE; + } + } + + QImage img = QImage(pixels, static_cast<int>(width), static_cast<int>(height / 2), + static_cast<int>(width), QImage::Format_Grayscale8, pixelsDeleter, pixels) + .mirrored(false, true); + printSpectrum(img); } void @@ -24,43 +92,3 @@ AudioVisualizer::printSpectrum(QImage pixmap) noexcept layout->addWidget(timer); setLayout(layout); } - -AudioVisualizer * -AudioVisualizer::fromFile(const QString &filename) -{ - if (filename.isEmpty()) - return nullptr; - - const int sample_rate = 44100; - double *data = nullptr; - size_t size = 0; - int rc = decodeAudioFile(filename.toStdString().c_str(), sample_rate, &data, &size); - auto data_deleter = [](double *ptr) -> void { - if (ptr != nullptr) { - av_free(ptr); - } - }; - std::unique_ptr<double, decltype(data_deleter)> data_holder(data, data_deleter); - - if (rc != 0) { - printf("ERROR\n"); - return nullptr; - } - - auto raw_image_deleter = [](RawImageData *ptr) -> void { RawImageDataFree(ptr); }; - RawImageData *raw_image = RawImageDataNewFromData(data, size); - std::unique_ptr<RawImageData, decltype(raw_image_deleter)> raw_image_holder(raw_image, - raw_image_deleter); - - auto pixs_deleter = [](void *ptr) -> void { free(ptr); }; - const int width = RawImageDataGetWidth(raw_image); - const int height = RawImageDataGetHeight(raw_image); - uchar *pixs = RawImageDataDuplicatePixels(raw_image); - QImage img = - QImage(pixs, width, height / 2, width, QImage::Format_Grayscale8, pixs_deleter, pixs) - .mirrored(false, true); - - auto *audioVisualizer = new AudioVisualizer; - audioVisualizer->printSpectrum(img); - return audioVisualizer; -} diff --git a/src/UI/AudioVisualizer.hh b/src/UI/AudioVisualizer.hh index 8a267183e353dca5756b1a215336db8c239fa6b7..3273d285bba08de8865e08be657cf62ba414a483 100644 --- a/src/UI/AudioVisualizer.hh +++ b/src/UI/AudioVisualizer.hh @@ -6,6 +6,7 @@ #endif #include "TimingView.hh" +#include "../Lib/Audio.hh" #include <QWidget> #include <QString> @@ -14,12 +15,26 @@ namespace Vivy class AudioVisualizer final : public QWidget { Q_OBJECT +private: + static constexpr inline auto fftSampleDeleter = [](FFTSample *ptr) noexcept -> void { + if (ptr) + av_free(ptr); + }; + static constexpr inline auto rdftContextDeleter = [](RDFTContext *ptr) noexcept -> void { + if (ptr) + av_rdft_end(ptr); + }; + static constexpr inline auto pixelsDeleter = [](void *ptr) noexcept -> void { + if (ptr) + delete[](reinterpret_cast<uchar *>(ptr)); + }; + using FFTSamplePtr = std::unique_ptr<FFTSample[], decltype(fftSampleDeleter)>; + using RDFTContextPtr = std::unique_ptr<RDFTContext, decltype(rdftContextDeleter)>; + public: - explicit AudioVisualizer(QWidget *parent = nullptr) noexcept; + explicit AudioVisualizer(AudioContext::StreamPtr, QWidget *parent = nullptr); ~AudioVisualizer() noexcept = default; - [[nodiscard("allocated")]] static AudioVisualizer *fromFile(const QString &); - public slots: void printSpectrum(QImage) noexcept; }; diff --git a/src/UI/MainWindow.cc b/src/UI/MainWindow.cc index 929659291b851e1147cdaa0f2a27935f85d227f7..212abaf9f3312c909a0d2f02a33de2bde7e46c2c 100644 --- a/src/UI/MainWindow.cc +++ b/src/UI/MainWindow.cc @@ -1,7 +1,7 @@ #include "MainWindow.hh" #include "DialogHelp.hh" #include "VivyDocumentView.hh" -#include "../Utils.hh" +#include "../Lib/Utils.hh" #include "../VivyApplication.hh" #include <functional> diff --git a/src/UI/MainWindow.hh b/src/UI/MainWindow.hh index 7947e04aa3f34777dee6ec1eda10bde46f0cbcd7..fc3e60ea739a7be2ca75da3e4132855c9afd72af 100644 --- a/src/UI/MainWindow.hh +++ b/src/UI/MainWindow.hh @@ -5,7 +5,7 @@ #error "This is a C++ header" #endif -#include "../Utils.hh" +#include "../Lib/Utils.hh" #include "AudioVisualizer.hh" #include "../Document/VivyDocumentStore.hh" #include "VivyDocumentView.hh" diff --git a/src/UI/TimingScene.cc b/src/UI/TimingScene.cc index 042149bd2c27427c35f64f3d82fa8e3f4046a9f7..7d2e21156582e7c6c77de94958b53785c057cb1a 100644 --- a/src/UI/TimingScene.cc +++ b/src/UI/TimingScene.cc @@ -1,5 +1,5 @@ #include "TimingScene.hh" -#include "../Utils.hh" +#include "../Lib/Utils.hh" #include <QGraphicsLineItem> #include <QGraphicsPixmapItem> diff --git a/src/UI/TimingScene.hh b/src/UI/TimingScene.hh index ed6742f95b9bbc8addb2c18016f0591c6bf0a53d..860acecbc35628983b137e517385f82da36cf1ef 100644 --- a/src/UI/TimingScene.hh +++ b/src/UI/TimingScene.hh @@ -1,7 +1,7 @@ #ifndef VIVY_TIMING_SCENE_H #define VIVY_TIMING_SCENE_H -#include "../Utils.hh" +#include "../Lib/Utils.hh" #include "../Ass/Ass.hh" #include "TimingBar.hh" diff --git a/src/UI/TimingView.hh b/src/UI/TimingView.hh index 3358226c85aa09ca5516e89c82c2e9dffaac3c44..b2f11f792576e7d66b1e2628ae2cefb12bb51e67 100644 --- a/src/UI/TimingView.hh +++ b/src/UI/TimingView.hh @@ -5,7 +5,7 @@ #error "This is a C++ header" #endif -#include "../Utils.hh" +#include "../Lib/Utils.hh" #include "TimingBar.hh" #include "TimingScene.hh" diff --git a/src/UI/VivyDocumentView.cc b/src/UI/VivyDocumentView.cc index d9a0cc4c8e8ee992d688d4fc8dec50b198d0c294..f686215d6aa2f4e3983d4afb1ff571dce2642071 100644 --- a/src/UI/VivyDocumentView.cc +++ b/src/UI/VivyDocumentView.cc @@ -53,19 +53,26 @@ VivyDocumentView::loadAudioView() noexcept std::shared_ptr<AudioSubDocument> audioDocument = document->getAudioSubDocument(); qDebug() << "Create an audio vizualizer for the audio sub document" << audioDocument->getFilePath(); - visualizer = AudioVisualizer::fromFile(audioDocument->getFilePath()); + + AudioContext::StreamPtr stream = audioDocument->getDefaultStream(); + if (stream == nullptr) { + qCritical() << "Failed to get default audio stream"; + return; + } + + visualizer = new AudioVisualizer(stream); if (visualizer == nullptr) { qCritical() << "Failed to create visualizer for" << audioDocument->getFilePath(); return; } QVBoxLayout *layout = new QVBoxLayout(this); - if (visualizer != nullptr) { - qDebug() << "Add visualizer to the view"; - layout->addWidget(visualizer); - } + layout->addWidget(visualizer); + setLayout(layout); - } else { + } + + else { qDebug() << "The document" << document->getName() << "is not AudioAble"; } }