From ff43f3d60118c2b41095f9c6d830493da9a0ac91 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Tue, 16 Aug 2022 18:55:59 +0200 Subject: [PATCH] vapoursynth: Add audio source --- src/audio_provider_factory.cpp | 4 + src/audio_provider_vs.cpp | 167 +++++++++++++++++++++++++++++++++ src/command/audio.cpp | 2 +- src/meson.build | 1 + 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/audio_provider_vs.cpp diff --git a/src/audio_provider_factory.cpp b/src/audio_provider_factory.cpp index d0508aa6f..71c34f66e 100644 --- a/src/audio_provider_factory.cpp +++ b/src/audio_provider_factory.cpp @@ -32,6 +32,7 @@ using namespace agi; std::unique_ptr CreateAvisynthAudioProvider(fs::path const& filename, BackgroundRunner *); std::unique_ptr CreateFFmpegSourceAudioProvider(fs::path const& filename, BackgroundRunner *); std::unique_ptr CreateBSAudioProvider(fs::path const& filename, BackgroundRunner *); +std::unique_ptr CreateVapoursynthAudioProvider(fs::path const& filename, BackgroundRunner *); namespace { struct factory { @@ -52,6 +53,9 @@ const factory providers[] = { #ifdef WITH_BESTSOURCE {"BestSource", CreateBSAudioProvider, false}, #endif +#ifdef WITH_VAPOURSYNTH + {"Vapoursynth", CreateVapoursynthAudioProvider, false}, +#endif }; } diff --git a/src/audio_provider_vs.cpp b/src/audio_provider_vs.cpp new file mode 100644 index 000000000..02d469b51 --- /dev/null +++ b/src/audio_provider_vs.cpp @@ -0,0 +1,167 @@ +// Copyright (c) 2022, arch1t3cht +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +/// @file audio_provider_vs.cpp +/// @brief Vapoursynth-based audio provider +/// @ingroup audio_input +/// + +#ifdef WITH_VAPOURSYNTH +#include + +#include "audio_controller.h" +#include "options.h" +#include "utils.h" + +#include +#include +#include +#include +#include + +#include + +#include "vapoursynth_wrap.h" +#include "VSScript4.h" + +namespace { +class VapoursynthAudioProvider final : public agi::AudioProvider { + VapourSynthWrapper vs; + VSScript *script = nullptr; + VSNode *node = nullptr; + const VSAudioInfo *vi = nullptr; + + void FillBufferWithFrame(void *buf, int frame, int64_t start, int64_t count) const; + void FillBuffer(void *buf, int64_t start, int64_t count) const override; +public: + VapoursynthAudioProvider(agi::fs::path const& filename); + ~VapoursynthAudioProvider(); + + bool NeedsCache() const override { return true; } +}; + +VapoursynthAudioProvider::VapoursynthAudioProvider(agi::fs::path const& filename) try { + agi::acs::CheckFileRead(filename); + std::lock_guard lock(vs.GetMutex()); + + script = vs.GetScriptAPI()->createScript(nullptr); + if (script == nullptr) { + throw VapoursynthError("Error creating script API"); + } + vs.GetScriptAPI()->evalSetWorkingDir(script, 1); + if (vs.GetScriptAPI()->evaluateFile(script, filename.string().c_str())) { + std::string msg = agi::format("Error executing VapourSynth script: %s", vs.GetScriptAPI()->getError(script)); + vs.GetScriptAPI()->freeScript(script); + throw VapoursynthError(msg); + } + node = vs.GetScriptAPI()->getOutputNode(script, 0); + if (node == nullptr) { + vs.GetScriptAPI()->freeScript(script); + throw VapoursynthError("No output node set"); + } + if (vs.GetAPI()->getNodeType(node) != mtAudio) { + vs.GetAPI()->freeNode(node); + vs.GetScriptAPI()->freeScript(script); + throw VapoursynthError("Output node isn't an audio node"); + } + vi = vs.GetAPI()->getAudioInfo(node); + float_samples = vi->format.sampleType == stFloat; + bytes_per_sample = vi->format.bytesPerSample; + sample_rate = vi->sampleRate; + channels = vi->format.numChannels; + num_samples = vi->numSamples; +} +catch (VapoursynthError const& err) { + throw agi::AudioProviderError(agi::format("Vapoursynth error: %s", err.GetMessage())); +} + +template +static void PackChannels(const uint8_t **Src, void *Dst, size_t Length, size_t Channels) { + T *D = reinterpret_cast(Dst); + for (size_t c = 0; c < Channels; c++) { + const T *S = reinterpret_cast(Src[c]); + for (size_t i = 0; i < Length; i++) { + D[Channels * i + c] = S[i]; + } + } +} + +void VapoursynthAudioProvider::FillBufferWithFrame(void *buf, int n, int64_t start, int64_t count) const { + char errorMsg[1024]; + const VSFrame *frame = vs.GetAPI()->getFrame(n, node, errorMsg, sizeof(errorMsg)); + if (frame == nullptr) { + throw VapoursynthError(agi::format("Error getting frame: %s", errorMsg)); + } + if (vs.GetAPI()->getFrameLength(frame) < count) { + vs.GetAPI()->freeFrame(frame); + throw VapoursynthError("Audio frame too short"); + } + if (vs.GetAPI()->getAudioFrameFormat(frame)->numChannels != channels || vs.GetAPI()->getAudioFrameFormat(frame)->bytesPerSample != bytes_per_sample) { + vs.GetAPI()->freeFrame(frame); + throw VapoursynthError("Audio format is not constant"); + } + + std::vector planes(channels); + for (int c = 0; c < channels; c++) { + planes[c] = vs.GetAPI()->getReadPtr(frame, c); + if (planes[c] == nullptr) { + vs.GetAPI()->freeFrame(frame); + throw VapoursynthError("Failed to read audio channel"); + } + } + + if (bytes_per_sample == 1) + PackChannels(planes.data(), buf, count, channels); + else if (bytes_per_sample == 2) + PackChannels(planes.data(), buf, count, channels); + else if (bytes_per_sample == 4) + PackChannels(planes.data(), buf, count, channels); + else if (bytes_per_sample == 8) + PackChannels(planes.data(), buf, count, channels); + + vs.GetAPI()->freeFrame(frame); +} + +void VapoursynthAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const { + int end = start + count; // exclusive + int startframe = start / VS_AUDIO_FRAME_SAMPLES; + int endframe = (end - 1) / VS_AUDIO_FRAME_SAMPLES; + int offset = start - (VS_AUDIO_FRAME_SAMPLES * startframe); + + for (int frame = startframe; frame <= endframe; frame++) { + int framestart = frame * VS_AUDIO_FRAME_SAMPLES; + int frameend = (frame + 1) * VS_AUDIO_FRAME_SAMPLES; + int fstart = framestart < start ? start - framestart : 0; + int fcount = VS_AUDIO_FRAME_SAMPLES - fstart - (frameend > end ? frameend - end : 0); + int bufstart = frame == startframe ? 0 : (frame - startframe) * VS_AUDIO_FRAME_SAMPLES - offset; + FillBufferWithFrame(reinterpret_cast(buf) + channels * bytes_per_sample * bufstart, frame, fstart, fcount); + } +} + +VapoursynthAudioProvider::~VapoursynthAudioProvider() { + if (node != nullptr) { + vs.GetAPI()->freeNode(node); + } + if (script != nullptr) { + vs.GetScriptAPI()->freeScript(script); + } +} +} + +std::unique_ptr CreateVapoursynthAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *) { + return agi::make_unique(file); +} +#endif diff --git a/src/command/audio.cpp b/src/command/audio.cpp index 386866775..a160e8eee 100644 --- a/src/command/audio.cpp +++ b/src/command/audio.cpp @@ -80,7 +80,7 @@ struct audio_open final : public Command { STR_HELP("Open an audio file") void operator()(agi::Context *c) override { - auto str = from_wx(_("Audio Formats") + " (*.aac,*.ac3,*.ape,*.dts,*.eac3,*.flac,*.m4a,*.mka,*.mp3,*.mp4,*.ogg,*.opus,*.w64,*.wav,*.wma)|*.aac;*.ac3;*.ape;*.dts;*.eac3;*.flac;*.m4a;*.mka;*.mp3;*.mp4;*.ogg;*.opus;*.w64;*.wav;*.wma|" + auto str = from_wx(_("Audio Formats") + " (*.aac,*.ac3,*.ape,*.avs,*.dts,*.eac3,*.flac,*.m4a,*.mka,*.mp3,*.mp4,*.ogg,*.opus,*.vpy,*.w64,*.wav,*.wma)|*.aac;*.ac3;*.ape;*.avs;*.dts;*.eac3;*.flac;*.m4a;*.mka;*.mp3;*.mp4;*.ogg;*.opus;*.vpy;*.w64;*.wav;*.wma|" + _("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts)|*.asf;*.avi;*.avs;*.d2v;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts|" + _("All Files") + " (*.*)|*.*"); auto filename = OpenFileSelector(_("Open Audio File"), "Path/Last/Audio", "", "", str, c->parent); diff --git a/src/meson.build b/src/meson.build index a9e2ebcf2..4f339ae47 100644 --- a/src/meson.build +++ b/src/meson.build @@ -240,6 +240,7 @@ opt_src = [ 'video_provider_bestsource.cpp', 'bestsource_common.cpp']], ['VapourSynth', ['vapoursynth_wrap.cpp', + 'audio_provider_vs.cpp', 'video_provider_vs.cpp']], ['Hunspell', 'spellchecker_hunspell.cpp'],