From 3dfea0c315e9113f908478245b9d698a41cb030f Mon Sep 17 00:00:00 2001 From: Ristellise Date: Wed, 10 Aug 2022 16:31:02 +0800 Subject: [PATCH 01/19] Add XAudio2 --- CMakeLists.txt | 2 +- meson.build | 41 ++- meson_options.txt | 3 +- src/audio_player.cpp | 4 + src/audio_player_xaudio2.cpp | 694 +++++++++++++++++++++++++++++++++++ 5 files changed, 730 insertions(+), 14 deletions(-) create mode 100644 src/audio_player_xaudio2.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4647a9147..01e89b529 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -497,7 +497,7 @@ set_property(SOURCE src/subtitles_provider_csri.cpp PROPERTY INCLUDE_DIRECTORIES if(MSVC) target_link_libraries (Aegisub dsound) add_definitions("-DWITH_DIRECTSOUND") - target_sources(Aegisub PRIVATE src/audio_player_dsound.cpp src/audio_player_dsound2.cpp) + target_sources(Aegisub PRIVATE src/audio_player_dsound.cpp src/audio_player_dsound2.cpp src/audio_player_xaudio2.cpp) endif(MSVC) find_package(FFMS2) diff --git a/meson.build b/meson.build index 616c86053..5527cfbe8 100644 --- a/meson.build +++ b/meson.build @@ -237,20 +237,37 @@ if host_machine.system() == 'windows' and get_option('avisynth').enabled() deps_inc += avs.include_directories('AviSynth-Headers') endif -if host_machine.system() == 'windows' and not get_option('directsound').disabled() - dsound_dep = cc.find_library('dsound', required: get_option('directsound')) - winmm_dep = cc.find_library('winmm', required: get_option('directsound')) - ole32_dep = cc.find_library('ole32', required: get_option('directsound')) - have_dsound_h = cc.has_header('dsound.h') - if not have_dsound_h and get_option('directsound').enabled() - error('DirectSound enabled but dsound.h not found') +if host_machine.system() == 'windows' + + if not get_option('directsound').disabled() + dsound_dep = cc.find_library('dsound', required: get_option('directsound')) + winmm_dep = cc.find_library('winmm', required: get_option('directsound')) + ole32_dep = cc.find_library('ole32', required: get_option('directsound')) + have_dsound_h = cc.has_header('dsound.h') + if not have_dsound_h and get_option('directsound').enabled() + error('DirectSound enabled but dsound.h not found') + endif + + dxguid_dep = cc.find_library('dxguid', required: true) + if dsound_dep.found() and winmm_dep.found() and ole32_dep.found() and dxguid_dep.found() and have_dsound_h + deps += [dsound_dep, winmm_dep, ole32_dep, dxguid_dep] + conf.set('WITH_DIRECTSOUND', 1) + dep_avail += 'DirectSound' + endif endif - dxguid_dep = cc.find_library('dxguid', required: true) - if dsound_dep.found() and winmm_dep.found() and ole32_dep.found() and dxguid_dep.found() and have_dsound_h - deps += [dsound_dep, winmm_dep, ole32_dep, dxguid_dep] - conf.set('WITH_DIRECTSOUND', 1) - dep_avail += 'DirectSound' + if not get_option('xaudio2').disabled() + have_xaudio_h = cc.has_header('xaudio2.h') + xaudio2_dep = cc.find_library('xaudio2', required: true) + if have_xaudio_h + deps += [xaudio2_dep] + conf.set('WITH_XAUDIO2', 1) + dep_avail += 'XAudio2' + endif + + if not have_dsound_h and get_option('xaudio2').enabled() + error('xaudio2 enabled but xaudio2.h not found') + endif endif endif diff --git a/meson_options.txt b/meson_options.txt index 3bc0461cd..97a183f22 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -3,7 +3,8 @@ option('openal', type: 'feature', description: 'OpenAL audio output') option('libpulse', type: 'feature', description: 'PulseAudio audio output') option('portaudio', type: 'feature', description: 'PortAudio audio output') option('directsound', type: 'feature', description: 'DirectSound audio output') -option('default_audio_output', type: 'combo', choices: ['auto', 'ALSA', 'OpenAL', 'PulseAudio', 'PortAudio', 'DirectSound'], description: 'Default audio output') +option('xaudio2', type: 'feature', description: 'XAudio2 audio output') +option('default_audio_output', type: 'combo', choices: ['auto', 'ALSA', 'OpenAL', 'PulseAudio', 'PortAudio', 'DirectSound', 'XAudio2'], description: 'Default audio output') option('ffms2', type: 'feature', description: 'FFMS2 video source') option('avisynth', type: 'feature', description: 'AviSynth video source') diff --git a/src/audio_player.cpp b/src/audio_player.cpp index f5a8327ca..c46ba0a40 100644 --- a/src/audio_player.cpp +++ b/src/audio_player.cpp @@ -43,6 +43,7 @@ std::unique_ptr CreateAlsaPlayer(agi::AudioProvider *providers, wxWindow *window); std::unique_ptr CreateDirectSoundPlayer(agi::AudioProvider *providers, wxWindow *window); std::unique_ptr CreateDirectSound2Player(agi::AudioProvider *providers, wxWindow *window); +std::unique_ptr CreateXAudio2Player(agi::AudioProvider *providers, wxWindow *window); std::unique_ptr CreateOpenALPlayer(agi::AudioProvider *providers, wxWindow *window); std::unique_ptr CreatePortAudioPlayer(agi::AudioProvider *providers, wxWindow *window); std::unique_ptr CreatePulseAudioPlayer(agi::AudioProvider *providers, wxWindow *window); @@ -63,6 +64,9 @@ namespace { {"DirectSound-old", CreateDirectSoundPlayer, false}, {"DirectSound", CreateDirectSound2Player, false}, #endif +#ifdef WITH_XAUDIO2 + {"Xaudio2", CreateXAudio2Player, false}, +#endif #ifdef WITH_OPENAL {"OpenAL", CreateOpenALPlayer, false}, #endif diff --git a/src/audio_player_xaudio2.cpp b/src/audio_player_xaudio2.cpp new file mode 100644 index 000000000..a7cbf0f28 --- /dev/null +++ b/src/audio_player_xaudio2.cpp @@ -0,0 +1,694 @@ +// Copyright (c) 2019, Qirui Wang +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Aegisub Project http://www.aegisub.org/ + +#ifdef WITH_XAUDIO2 +#include "include/aegisub/audio_player.h" + +#include "options.h" + +#include +#include +#include +#include + +#ifndef XAUDIO2_REDIST +#include +#else +#include +#endif + +namespace { +class XAudio2Thread; + +/// @class XAudio2Player +/// @brief XAudio2-based audio player +/// +/// The core design idea is to have a playback thread that performs all playback operations, and use the player object as a proxy to send commands to the playback thread. +class XAudio2Player final : public AudioPlayer { + /// The playback thread + std::unique_ptr thread; + + /// Desired length in milliseconds to write ahead of the playback cursor + int WantedLatency; + + /// Multiplier for WantedLatency to get total buffer length + int BufferLength; + + /// @brief Tell whether playback thread is alive + /// @return True if there is a playback thread and it's ready + bool IsThreadAlive(); + +public: + /// @brief Constructor + XAudio2Player(agi::AudioProvider* provider); + /// @brief Destructor + ~XAudio2Player() = default; + + /// @brief Start playback + /// @param start First audio frame to play + /// @param count Number of audio frames to play + void Play(int64_t start, int64_t count); + + /// @brief Stop audio playback + /// @param timerToo Whether to also stop the playback update timer + void Stop(); + + /// @brief Tell whether playback is active + /// @return True if audio is playing back + bool IsPlaying(); + + /// @brief Get playback end position + /// @return Audio frame index + /// + /// Returns 0 if playback is stopped or there is no playback thread + int64_t GetEndPosition(); + /// @brief Get approximate playback position + /// @return Index of audio frame user is currently hearing + /// + /// Returns 0 if playback is stopped or there is no playback thread + int64_t GetCurrentPosition(); + + /// @brief Change playback end position + /// @param pos New end position + void SetEndPosition(int64_t pos); + + /// @brief Change playback volume + /// @param vol Amplification factor + void SetVolume(double vol); +}; + +/// @brief RAII support class to init and de-init the COM library +struct COMInitialization { + + /// Flag set if an inited COM library is managed + bool inited = false; + + /// @brief Destructor, de-inits COM if it is inited + ~COMInitialization() { + if (inited) CoUninitialize(); + } + + /// @brief Initialise the COM library as single-threaded apartment if isn't already inited by us + bool Init() { + if (!inited && SUCCEEDED(CoInitialize(nullptr))) + inited = true; + return inited; + } +}; + +struct ReleaseCOMObject { + void operator()(IUnknown* obj) { + if (obj) obj->Release(); + } +}; + +/// @brief RAII wrapper around Win32 HANDLE type +struct Win32KernelHandle final : public agi::scoped_holder { + /// @brief Create with a managed handle + /// @param handle Win32 handle to manage + Win32KernelHandle(HANDLE handle = 0) :scoped_holder(handle, CloseHandle) {} + + Win32KernelHandle& operator=(HANDLE new_handle) { + scoped_holder::operator=(new_handle); + return *this; + } +}; + +/// @class XAudio2Thread +/// @brief Playback thread class for XAudio2Player +/// +/// Not based on wxThread, but uses Win32 threads directly +class XAudio2Thread :public IXAudio2VoiceCallback { + /// @brief Win32 thread entry point + /// @param parameter Pointer to our thread object + /// @return Thread return value, always 0 here + static unsigned int __stdcall ThreadProc(void* parameter); + /// @brief Thread entry point + void Run(); + + /// @brief Check for error state and throw exception if one occurred + void CheckError(); + + /// Win32 handle to the thread + Win32KernelHandle thread_handle; + + /// Event object, world to thread, set to start playback + Win32KernelHandle event_start_playback; + + /// Event object, world to thread, set to stop playback + Win32KernelHandle event_stop_playback; + + /// Event object, world to thread, set if playback end time was updated + Win32KernelHandle event_update_end_time; + + /// Event object, world to thread, set if the volume was changed + Win32KernelHandle event_set_volume; + + /// Event object, world to thread, set if the thread should end as soon as possible + Win32KernelHandle event_buffer_end; + + /// Event object, world to thread, set if the thread should end as soon as possible + Win32KernelHandle event_kill_self; + + /// Event object, thread to world, set when the thread has entered its main loop + Win32KernelHandle thread_running; + + /// Event object, thread to world, set when playback is ongoing + Win32KernelHandle is_playing; + + /// Event object, thread to world, set if an error state has occurred (implies thread is dying) + Win32KernelHandle error_happened; + + /// Statically allocated error message text describing reason for error_happened being set + const char* error_message = nullptr; + + /// Playback volume, 1.0 is "unchanged" + double volume = 1.0; + + /// Audio frame to start playback at + int64_t start_frame = 0; + + /// Audio frame to end playback at + int64_t end_frame = 0; + + /// Desired length in milliseconds to write ahead of the playback cursor + int wanted_latency; + + /// Multiplier for WantedLatency to get total buffer length + int buffer_length; + + /// System millisecond timestamp of last playback start, used to calculate playback position + ULONGLONG last_playback_restart; + + /// Audio provider to take sample data from + agi::AudioProvider* provider; + + /// Buffer occupied indicator + std::vector buffer_occupied; + +public: + /// @brief Constructor, creates and starts playback thread + /// @param provider Audio provider to take sample data from + /// @param WantedLatency Desired length in milliseconds to write ahead of the playback cursor + /// @param BufferLength Multiplier for WantedLatency to get total buffer length + XAudio2Thread(agi::AudioProvider* provider, int WantedLatency, int BufferLength); + /// @brief Destructor, waits for thread to have died + ~XAudio2Thread(); + + // IXAudio2VoiceCallback + void STDMETHODCALLTYPE OnVoiceProcessingPassStart(UINT32 BytesRequired) override {} + void STDMETHODCALLTYPE OnVoiceProcessingPassEnd() override {} + void STDMETHODCALLTYPE OnStreamEnd() override {} + void STDMETHODCALLTYPE OnBufferStart(void* pBufferContext) override {} + void STDMETHODCALLTYPE OnBufferEnd(void* pBufferContext) override { + intptr_t i = reinterpret_cast(pBufferContext); + buffer_occupied[i] = false; + SetEvent(event_buffer_end); + } + void STDMETHODCALLTYPE OnLoopEnd(void* pBufferContext) override {} + void STDMETHODCALLTYPE OnVoiceError(void* pBufferContext, HRESULT Error) override {} + + /// @brief Start audio playback + /// @param start Audio frame to start playback at + /// @param count Number of audio frames to play + void Play(int64_t start, int64_t count); + + /// @brief Stop audio playback + void Stop(); + + /// @brief Change audio playback end point + /// @param new_end_frame New last audio frame to play + /// + /// Playback stops instantly if new_end_frame is before the current playback position + void SetEndFrame(int64_t new_end_frame); + + /// @brief Change audio playback volume + /// @param new_volume New playback amplification factor, 1.0 is "unchanged" + void SetVolume(double new_volume); + + /// @brief Tell whether audio playback is active + /// @return True if audio is being played back, false if it is not + bool IsPlaying(); + + /// @brief Get approximate current audio frame being heard by the user + /// @return Audio frame index + /// + /// Returns 0 if not playing + int64_t GetCurrentFrame(); + + /// @brief Get audio playback end point + /// @return Audio frame index + int64_t GetEndFrame(); + + /// @brief Tell whether playback thread has died + /// @return True if thread is no longer running + bool IsDead(); +}; + +unsigned int __stdcall XAudio2Thread::ThreadProc(void* parameter) { + static_cast(parameter)->Run(); + return 0; +} + +/// Macro used to set error_message, error_happened and end the thread +#define REPORT_ERROR(msg) \ +{ \ + ResetEvent(is_playing); \ + error_message = "XAudio2Thread: " msg; \ + SetEvent(error_happened); \ + return; \ +} + +void XAudio2Thread::Run() { + COMInitialization COM_library; + if (!COM_library.Init()) { + REPORT_ERROR("Could not initialise COM") + } + IXAudio2* pXAudio2; + IXAudio2SourceVoice* pSourceVoice; + HRESULT hr; + if (FAILED(hr = XAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR))) { + REPORT_ERROR("Failed initializing XAudio2") + } + IXAudio2MasteringVoice* pMasterVoice = NULL; + if (FAILED(hr = pXAudio2->CreateMasteringVoice(&pMasterVoice))) { + REPORT_ERROR("Failed initializing XAudio2 MasteringVoice") + } + + // Describe the wave format + WAVEFORMATEX wfx; + wfx.nSamplesPerSec = provider->GetSampleRate(); + wfx.cbSize = 0; + bool original = true; + wfx.wFormatTag = provider->AreSamplesFloat() ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM; + wfx.nChannels = provider->GetChannels(); + wfx.wBitsPerSample = provider->GetBytesPerSample() * 8; + wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + + if (FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, &wfx, 0, 2, this))) { + if (hr == XAUDIO2_E_INVALID_CALL) { + // Retry with 16bit mono + original = false; + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = 1; + wfx.wBitsPerSample = sizeof(int16_t) * 8; + wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + if (FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, &wfx, 0, 2, this))) { + REPORT_ERROR("Failed initializing XAudio2 SourceVoice") + } + } + else { + REPORT_ERROR("Failed initializing XAudio2 SourceVoice") + } + } + + // Now we're ready to roll! + SetEvent(thread_running); + bool running = true; + + HANDLE events_to_wait[] = { + event_start_playback, + event_stop_playback, + event_update_end_time, + event_set_volume, + event_buffer_end, + event_kill_self + }; + + int64_t next_input_frame = 0; + DWORD buffer_offset = 0; + bool playback_should_be_running = false; + int current_latency = wanted_latency; + const int wanted_frames = wanted_latency * wfx.nSamplesPerSec / 1000; + const DWORD wanted_latency_bytes = wanted_frames * wfx.nBlockAlign; + std::vector > buff(buffer_length); + for (auto& i : buff) + i.resize(wanted_latency_bytes); + + while (running) { + DWORD wait_result = WaitForMultipleObjects(sizeof(events_to_wait) / sizeof(HANDLE), events_to_wait, FALSE, INFINITE); + + switch (wait_result) { + case WAIT_OBJECT_0 + 0: + // Start or restart playback + pSourceVoice->Stop(); + pSourceVoice->FlushSourceBuffers(); + + next_input_frame = start_frame; + playback_should_be_running = true; + pSourceVoice->Start(); + SetEvent(is_playing); + goto do_fill_buffer; + + case WAIT_OBJECT_0 + 1: + stop_playback: + // Stop playing + ResetEvent(is_playing); + pSourceVoice->Stop(); + pSourceVoice->FlushSourceBuffers(); + playback_should_be_running = false; + break; + + case WAIT_OBJECT_0 + 2: + // Set end frame + if (end_frame <= next_input_frame) + goto stop_playback; + goto do_fill_buffer; + + case WAIT_OBJECT_0 + 3: + // Change volume + pSourceVoice->SetVolume(volume); + break; + + case WAIT_OBJECT_0 + 4: + // Buffer end + do_fill_buffer: + // Time to fill more into buffer + if (!playback_should_be_running) + break; + + for (int i = 0; i < buffer_length; ++i) { + if (!buffer_occupied[i]) { + int fill_len = std::min(end_frame - next_input_frame, wanted_frames); + if (fill_len <= 0) + break; + buffer_occupied[i] = true; + if (original) + provider->GetAudio(buff[i].data(), next_input_frame, fill_len); + else + provider->GetInt16MonoAudio(reinterpret_cast(buff[i].data()), next_input_frame, fill_len); + next_input_frame += fill_len; + XAUDIO2_BUFFER xbf; + xbf.Flags = fill_len + next_input_frame == end_frame ? XAUDIO2_END_OF_STREAM : 0; + xbf.AudioBytes = fill_len * wfx.nBlockAlign; + xbf.pAudioData = buff[i].data(); + xbf.PlayBegin = 0; + xbf.PlayLength = 0; + xbf.LoopBegin = 0; + xbf.LoopLength = 0; + xbf.LoopCount = 0; + xbf.pContext = reinterpret_cast(static_cast(i)); + if (FAILED(hr = pSourceVoice->SubmitSourceBuffer(&xbf))) { + REPORT_ERROR("Failed initializing Submit Buffer") + } + } + } + break; + + case WAIT_OBJECT_0 + 5: + // Perform suicide + running = false; + pXAudio2->Release(); + ResetEvent(is_playing); + playback_should_be_running = false; + break; + + default: + REPORT_ERROR("Something bad happened while waiting on events in playback loop, either the wait failed or an event object was abandoned.") + break; + } + } +} + +#undef REPORT_ERROR + +void XAudio2Thread::CheckError() +{ + try { + switch (WaitForSingleObject(error_happened, 0)) + { + case WAIT_OBJECT_0: + throw error_message; + + case WAIT_ABANDONED: + throw "The XAudio2Thread error signal event was abandoned, somehow. This should not happen."; + + case WAIT_FAILED: + throw "Failed checking state of XAudio2Thread error signal event."; + + case WAIT_TIMEOUT: + default: + return; + } + } + catch (...) { + ResetEvent(is_playing); + ResetEvent(thread_running); + throw; + } +} + +XAudio2Thread::XAudio2Thread(agi::AudioProvider* provider, int WantedLatency, int BufferLength) + : event_start_playback(CreateEvent(0, FALSE, FALSE, 0)) + , event_stop_playback(CreateEvent(0, FALSE, FALSE, 0)) + , event_update_end_time(CreateEvent(0, FALSE, FALSE, 0)) + , event_set_volume(CreateEvent(0, FALSE, FALSE, 0)) + , event_buffer_end(CreateEvent(0, FALSE, FALSE, 0)) + , event_kill_self(CreateEvent(0, FALSE, FALSE, 0)) + , thread_running(CreateEvent(0, TRUE, FALSE, 0)) + , is_playing(CreateEvent(0, TRUE, FALSE, 0)) + , error_happened(CreateEvent(0, FALSE, FALSE, 0)) + , wanted_latency(WantedLatency) + , buffer_length(BufferLength < XAUDIO2_MAX_QUEUED_BUFFERS ? BufferLength : XAUDIO2_MAX_QUEUED_BUFFERS) + , provider(provider) + , buffer_occupied(BufferLength) +{ + if (!(thread_handle = (HANDLE)_beginthreadex(0, 0, ThreadProc, this, 0, 0))) { + throw AudioPlayerOpenError("Failed creating playback thread in XAudio2Player. This is bad."); + } + + HANDLE running_or_error[] = { thread_running, error_happened }; + switch (WaitForMultipleObjects(2, running_or_error, FALSE, INFINITE)) { + case WAIT_OBJECT_0: + // running, all good + return; + + case WAIT_OBJECT_0 + 1: + // error happened, we fail + throw AudioPlayerOpenError(error_message ? error_message : "Failed wait for thread start or thread error in XAudio2Player. This is bad."); + + default: + throw AudioPlayerOpenError("Failed wait for thread start or thread error in XAudio2Player. This is bad."); + } +} + +XAudio2Thread::~XAudio2Thread() { + SetEvent(event_kill_self); + WaitForSingleObject(thread_handle, INFINITE); +} + +void XAudio2Thread::Play(int64_t start, int64_t count) +{ + CheckError(); + + start_frame = start; + end_frame = start + count; + SetEvent(event_start_playback); + + last_playback_restart = GetTickCount64(); + + // Block until playback actually begins to avoid race conditions with + // checking if playback is in progress + HANDLE events_to_wait[] = { is_playing, error_happened }; + switch (WaitForMultipleObjects(2, events_to_wait, FALSE, INFINITE)) { + case WAIT_OBJECT_0 + 0: // Playing + LOG_D("audio/player/xaudio2") << "Playback begun"; + break; + case WAIT_OBJECT_0 + 1: // Error + throw error_message; + default: + throw agi::InternalError("Unexpected result from WaitForMultipleObjects in XAudio2Thread::Play"); + } +} + +void XAudio2Thread::Stop() { + CheckError(); + + SetEvent(event_stop_playback); +} + +void XAudio2Thread::SetEndFrame(int64_t new_end_frame) { + CheckError(); + + end_frame = new_end_frame; + SetEvent(event_update_end_time); +} + +void XAudio2Thread::SetVolume(double new_volume) { + CheckError(); + + volume = new_volume; + SetEvent(event_set_volume); +} + +bool XAudio2Thread::IsPlaying() { + CheckError(); + + switch (WaitForSingleObject(is_playing, 0)) + { + case WAIT_ABANDONED: + throw "The XAudio2Thread playback state event was abandoned, somehow. This should not happen."; + + case WAIT_FAILED: + throw "Failed checking state of XAudio2Thread playback state event."; + + case WAIT_OBJECT_0: + return true; + + case WAIT_TIMEOUT: + default: + return false; + } +} + +int64_t XAudio2Thread::GetCurrentFrame() { + CheckError(); + if (!IsPlaying()) return 0; + ULONGLONG milliseconds_elapsed = GetTickCount64() - last_playback_restart; + return start_frame + milliseconds_elapsed * provider->GetSampleRate() / 1000; +} + +int64_t XAudio2Thread::GetEndFrame() { + CheckError(); + return end_frame; +} + +bool XAudio2Thread::IsDead() { + switch (WaitForSingleObject(thread_running, 0)) + { + case WAIT_OBJECT_0: + return false; + default: + return true; + } +} + +XAudio2Player::XAudio2Player(agi::AudioProvider* provider) :AudioPlayer(provider) { + // The buffer will hold BufferLength times WantedLatency milliseconds of audio + WantedLatency = OPT_GET("Player/Audio/DirectSound/Buffer Latency")->GetInt(); + BufferLength = OPT_GET("Player/Audio/DirectSound/Buffer Length")->GetInt(); + + // sanity checking + if (WantedLatency <= 0) + WantedLatency = 100; + if (BufferLength <= 0) + BufferLength = 5; + + try { + thread = agi::make_unique(provider, WantedLatency, BufferLength); + } + catch (const char* msg) { + LOG_E("audio/player/xaudio2") << msg; + throw AudioPlayerOpenError(msg); + } +} + +bool XAudio2Player::IsThreadAlive() { + if (thread && thread->IsDead()) + thread.reset(); + return static_cast(thread); +} + +void XAudio2Player::Play(int64_t start, int64_t count) { + try { + thread->Play(start, count); + } + catch (const char* msg) { + LOG_E("audio/player/xaudio2") << msg; + } +} + +void XAudio2Player::Stop() { + try { + if (IsThreadAlive()) thread->Stop(); + } + catch (const char* msg) { + LOG_E("audio/player/xaudio2") << msg; + } +} + +bool XAudio2Player::IsPlaying() { + try { + if (!IsThreadAlive()) return false; + return thread->IsPlaying(); + } + catch (const char* msg) { + LOG_E("audio/player/xaudio2") << msg; + return false; + } +} + +int64_t XAudio2Player::GetEndPosition() { + try { + if (!IsThreadAlive()) return 0; + return thread->GetEndFrame(); + } + catch (const char* msg) { + LOG_E("audio/player/xaudio2") << msg; + return 0; + } +} + +int64_t XAudio2Player::GetCurrentPosition() { + try { + if (!IsThreadAlive()) return 0; + return thread->GetCurrentFrame(); + } + catch (const char* msg) { + LOG_E("audio/player/xaudio2") << msg; + return 0; + } +} + +void XAudio2Player::SetEndPosition(int64_t pos) { + try { + if (IsThreadAlive()) thread->SetEndFrame(pos); + } + catch (const char* msg) { + LOG_E("audio/player/xaudio2") << msg; + } +} + +void XAudio2Player::SetVolume(double vol) { + try { + if (IsThreadAlive()) thread->SetVolume(vol); + } + catch (const char* msg) { + LOG_E("audio/player/xaudio2") << msg; + } +} +} + +std::unique_ptr CreateXAudio2Player(agi::AudioProvider* provider, wxWindow*) { + return agi::make_unique(provider); +} + +#endif // WITH_XAUDIO2 From fd28458ed81a08fe92d0aa9405b4e6c6a994bbed Mon Sep 17 00:00:00 2001 From: Ristellise Date: Wed, 10 Aug 2022 21:09:41 +0800 Subject: [PATCH 02/19] Implement wangqr Audio Changes: - To allow for XAudio2 to work properly, we need to rework how does provider work since they only are used to be able to take in mono audio. - Other providers have been dumbed down to accept single channel audio since originally aegisub only accepted 1 channel audio. - meson.build has been modified to accommodate for xaudio, as we currently don't accept redistributable forms of xaudio, we need to work around the WinNT version. - There has been 1 more fix to res.rc to allow for compiling on non tagged releases. --- CMakeLists.txt | 1 + libaegisub/audio/provider.cpp | 138 +++++++++++++++++- libaegisub/audio/provider_convert.cpp | 127 ++-------------- libaegisub/audio/provider_hd.cpp | 8 +- libaegisub/audio/provider_ram.cpp | 12 +- .../include/libaegisub/audio/provider.h | 7 +- meson.build | 14 +- src/audio_player_alsa.cpp | 13 +- src/audio_player_dsound.cpp | 17 ++- src/audio_player_dsound2.cpp | 21 +-- src/audio_player_openal.cpp | 6 +- src/audio_player_oss.cpp | 10 +- src/audio_player_portaudio.cpp | 6 +- src/audio_player_pulse.cpp | 8 +- src/meson.build | 2 +- src/res/res.rc | 5 + tests/tests/audio.cpp | 36 ++--- 17 files changed, 241 insertions(+), 190 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 01e89b529..75182bbe8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -497,6 +497,7 @@ set_property(SOURCE src/subtitles_provider_csri.cpp PROPERTY INCLUDE_DIRECTORIES if(MSVC) target_link_libraries (Aegisub dsound) add_definitions("-DWITH_DIRECTSOUND") + add_definitions("-DWITH_XAUDIO2") target_sources(Aegisub PRIVATE src/audio_player_dsound.cpp src/audio_player_dsound2.cpp src/audio_player_xaudio2.cpp) endif(MSVC) diff --git a/libaegisub/audio/provider.cpp b/libaegisub/audio/provider.cpp index 961e5bf89..b7ad7847c 100644 --- a/libaegisub/audio/provider.cpp +++ b/libaegisub/audio/provider.cpp @@ -21,13 +21,108 @@ #include "libaegisub/log.h" #include "libaegisub/util.h" -namespace agi { -void AudioProvider::GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const { - GetAudio(buf, start, count); +namespace { +template +class ConvertFloatToInt16 { + Source* src; +public: + ConvertFloatToInt16(Source* src) :src(src) {} + int16_t operator[](size_t idx) const { + Source expanded = src[idx] * 32768; + return expanded < -32768 ? -32768 : + expanded > 32767 ? 32767 : + static_cast(expanded); + } +}; + +// 8 bits per sample is assumed to be unsigned with a bias of 128, +// while everything else is assumed to be signed with zero bias +class ConvertIntToInt16 { + void* src; + int bytes_per_sample; +public: + ConvertIntToInt16(void* src, int bytes_per_sample) :src(src), bytes_per_sample(bytes_per_sample) {} + const int16_t& operator[](size_t idx) const { + return *reinterpret_cast(reinterpret_cast(src) + (idx + 1) * bytes_per_sample - sizeof(int16_t)); + } +}; +class ConvertUInt8ToInt16 { + uint8_t* src; +public: + ConvertUInt8ToInt16(uint8_t* src) :src(src) {} + int16_t operator[](size_t idx) const { + return int16_t(src[idx]-128) << 8; + } +}; + +template +class DownmixToMono { + Source src; + int channels; +public: + DownmixToMono(Source src, int channels) :src(src), channels(channels) {} + int16_t operator[](size_t idx) const { + int ret = 0; + // Just average the channels together + for (int i = 0; i < channels; ++i) + ret += src[idx * channels + i]; + return ret / channels; + } +}; +} + +namespace agi { + +void AudioProvider::FillBufferInt16Mono(int16_t* buf, int64_t start, int64_t count) const { + if (!float_samples && bytes_per_sample == 2 && channels == 1) { + FillBuffer(buf, start, count); + return; + } + void* buff = malloc(bytes_per_sample * count * channels); + FillBuffer(buff, start, count); + if (channels == 1) { + if (float_samples) { + if (bytes_per_sample == sizeof(float)) + for (int64_t i = 0; i < count; ++i) + buf[i] = ConvertFloatToInt16(reinterpret_cast(buff))[i]; + else if (bytes_per_sample == sizeof(double)) + for (int64_t i = 0; i < count; ++i) + buf[i] = ConvertFloatToInt16(reinterpret_cast(buff))[i]; + } + else { + if (bytes_per_sample == sizeof(uint8_t)) + for (int64_t i = 0; i < count; ++i) + buf[i] = ConvertUInt8ToInt16(reinterpret_cast(buff))[i]; + else + for (int64_t i = 0; i < count; ++i) + buf[i] = ConvertIntToInt16(buff, bytes_per_sample)[i]; + } + } + else { + if (float_samples) { + if (bytes_per_sample == sizeof(float)) + for (int64_t i = 0; i < count; ++i) + buf[i] = DownmixToMono >(ConvertFloatToInt16(reinterpret_cast(buff)), channels)[i]; + else if (bytes_per_sample == sizeof(double)) + for (int64_t i = 0; i < count; ++i) + buf[i] = DownmixToMono >(ConvertFloatToInt16(reinterpret_cast(buff)), channels)[i]; + } + else { + if (bytes_per_sample == sizeof(uint8_t)) + for (int64_t i = 0; i < count; ++i) + buf[i] = DownmixToMono(ConvertUInt8ToInt16(reinterpret_cast(buff)), channels)[i]; + else + for (int64_t i = 0; i < count; ++i) + buf[i] = DownmixToMono(ConvertIntToInt16(buff, bytes_per_sample), channels)[i]; + } + } + free(buff); +} + +void AudioProvider::GetInt16MonoAudioWithVolume(int16_t *buf, int64_t start, int64_t count, double volume) const { + GetInt16MonoAudio(buf, start, count); if (volume == 1.0) return; - if (bytes_per_sample != 2) - throw agi::InternalError("GetAudioWithVolume called on unconverted audio stream"); auto buffer = static_cast(buf); for (size_t i = 0; i < (size_t)count; ++i) @@ -75,6 +170,39 @@ void AudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const { } } +void AudioProvider::GetInt16MonoAudio(int16_t* buf, int64_t start, int64_t count) const { + if (start < 0) { + memset(buf, 0, sizeof(int16_t) * std::min(-start, count)); + buf -= start; + count += start; + start = 0; + } + + if (start + count > num_samples) { + int64_t zero_count = std::min(count, start + count - num_samples); + count -= zero_count; + memset(buf + count, 0, sizeof(int16_t) * zero_count); + } + + if (count <= 0) return; + + try { + FillBufferInt16Mono(buf, start, count); + } + catch (AudioDecodeError const& e) { + // We don't have any good way to report errors here, so just log the + // failure and return silence + LOG_E("audio_provider") << e.GetMessage(); + memset(buf, 0, sizeof(int16_t) * count); + return; + } + catch (...) { + LOG_E("audio_provider") << "Unknown audio decoding error"; + memset(buf, 0, sizeof(int16_t) * count); + return; + } +} + namespace { class writer { io::Save outfile; diff --git a/libaegisub/audio/provider_convert.cpp b/libaegisub/audio/provider_convert.cpp index b45d8a852..21603c28a 100644 --- a/libaegisub/audio/provider_convert.cpp +++ b/libaegisub/audio/provider_convert.cpp @@ -22,119 +22,19 @@ #include using namespace agi; - -/// Anything integral -> 16 bit signed machine-endian audio converter namespace { -template -class BitdepthConvertAudioProvider final : public AudioProviderWrapper { - int src_bytes_per_sample; - mutable std::vector src_buf; - +class ConvertAudioProvider final : public AudioProviderWrapper { public: - BitdepthConvertAudioProvider(std::unique_ptr src) : AudioProviderWrapper(std::move(src)) { - if (bytes_per_sample > 8) - throw AudioProviderError("Audio format converter: audio with bitdepths greater than 64 bits/sample is currently unsupported"); - - src_bytes_per_sample = bytes_per_sample; - bytes_per_sample = sizeof(Target); - } - - void FillBuffer(void *buf, int64_t start, int64_t count64) const override { - auto count = static_cast(count64); - assert(count == count64); - - src_buf.resize(count * src_bytes_per_sample * channels); - source->GetAudio(src_buf.data(), start, count); - - auto dest = static_cast(buf); - - for (int64_t i = 0; i < count * channels; ++i) { - int64_t sample = 0; - - // 8 bits per sample is assumed to be unsigned with a bias of 127, - // while everything else is assumed to be signed with zero bias - if (src_bytes_per_sample == 1) - sample = src_buf[i] - 128; - else { - for (int j = src_bytes_per_sample; j > 0; --j) { - sample <<= 8; - sample += src_buf[i * src_bytes_per_sample + j - 1]; - } - } - - if (static_cast(src_bytes_per_sample) > sizeof(Target)) - sample /= 1LL << (src_bytes_per_sample - sizeof(Target)) * 8; - else if (static_cast(src_bytes_per_sample) < sizeof(Target)) - sample *= 1LL << (sizeof(Target) - src_bytes_per_sample ) * 8; - - dest[i] = static_cast(sample); - } - } -}; - -/// Floating point -> 16 bit signed machine-endian audio converter -template -class FloatConvertAudioProvider final : public AudioProviderWrapper { - mutable std::vector src_buf; - -public: - FloatConvertAudioProvider(std::unique_ptr src) : AudioProviderWrapper(std::move(src)) { - bytes_per_sample = sizeof(Target); + ConvertAudioProvider(std::unique_ptr src) : AudioProviderWrapper(std::move(src)) { float_samples = false; - } - - void FillBuffer(void *buf, int64_t start, int64_t count64) const override { - auto count = static_cast(count64); - assert(count == count64); - - src_buf.resize(count * channels); - source->GetAudio(&src_buf[0], start, count); - - auto dest = static_cast(buf); - - for (size_t i = 0; i < static_cast(count * channels); ++i) { - Source expanded; - if (src_buf[i] < 0) - expanded = static_cast(-src_buf[i] * std::numeric_limits::min()); - else - expanded = static_cast(src_buf[i] * std::numeric_limits::max()); - - dest[i] = expanded < std::numeric_limits::min() ? std::numeric_limits::min() : - expanded > std::numeric_limits::max() ? std::numeric_limits::max() : - static_cast(expanded); - } - } -}; - -/// Non-mono 16-bit signed machine-endian -> mono 16-bit signed machine endian converter -class DownmixAudioProvider final : public AudioProviderWrapper { - int src_channels; - mutable std::vector src_buf; - -public: - DownmixAudioProvider(std::unique_ptr src) : AudioProviderWrapper(std::move(src)) { - src_channels = channels; channels = 1; + bytes_per_sample = sizeof(int16_t); } - void FillBuffer(void *buf, int64_t start, int64_t count64) const override { - auto count = static_cast(count64); - assert(count == count64); - - src_buf.resize(count * src_channels); - source->GetAudio(&src_buf[0], start, count); - - auto dst = static_cast(buf); - // Just average the channels together - while (count-- > 0) { - int sum = 0; - for (int c = 0; c < src_channels; ++c) - sum += src_buf[count * src_channels + c]; - dst[count] = static_cast(sum / src_channels); - } + void FillBuffer(void *buf, int64_t start, int64_t count) const override { + source->GetInt16MonoAudio(reinterpret_cast(buf), start, count); } }; - /// Sample doubler with linear interpolation for the samples provider /// Requires 16-bit mono input class SampleDoublingAudioProvider final : public AudioProviderWrapper { @@ -177,26 +77,23 @@ std::unique_ptr CreateConvertAudioProvider(std::unique_ptrAreSamplesFloat()) { LOG_D("audio_provider") << "Converting float to S16"; - if (provider->GetBytesPerSample() == sizeof(float)) - provider = agi::make_unique>(std::move(provider)); - else - provider = agi::make_unique>(std::move(provider)); } if (provider->GetBytesPerSample() != 2) { - LOG_D("audio_provider") << "Converting " << provider->GetBytesPerSample() << " bytes per sample or wrong endian to S16"; - provider = agi::make_unique>(std::move(provider)); + LOG_D("audio_provider") << "Converting " << provider->GetBytesPerSample() << " bytes per sample to S16"; } // We currently only support mono audio if (provider->GetChannels() != 1) { LOG_D("audio_provider") << "Downmixing to mono from " << provider->GetChannels() << " channels"; - provider = agi::make_unique(std::move(provider)); } // Some players don't like low sample rate audio - while (provider->GetSampleRate() < 32000) { - LOG_D("audio_provider") << "Doubling sample rate"; - provider = agi::make_unique(std::move(provider)); + if (provider->GetSampleRate() < 32000) { + provider = agi::make_unique(std::move(provider)); + while (provider->GetSampleRate() < 32000) { + LOG_D("audio_provider") << "Doubling sample rate"; + provider = agi::make_unique(std::move(provider)); + } } return provider; diff --git a/libaegisub/audio/provider_hd.cpp b/libaegisub/audio/provider_hd.cpp index 19e33eeed..2969b244a 100644 --- a/libaegisub/audio/provider_hd.cpp +++ b/libaegisub/audio/provider_hd.cpp @@ -43,15 +43,15 @@ class HDAudioProvider final : public AudioProviderWrapper { } if (count > 0) { - start *= bytes_per_sample; - count *= bytes_per_sample; + start *= bytes_per_sample * channels; + count *= bytes_per_sample * channels; memcpy(buf, file.read(start, count), count); } } fs::path CacheFilename(fs::path const& dir) { // Check free space - if ((uint64_t)num_samples * bytes_per_sample > fs::FreeSpace(dir)) + if ((uint64_t)num_samples * bytes_per_sample * channels > fs::FreeSpace(dir)) throw AudioProviderError("Not enough free disk space in " + dir.string() + " to cache the audio"); return format("audio-%lld-%lld", time(nullptr), @@ -61,7 +61,7 @@ class HDAudioProvider final : public AudioProviderWrapper { public: HDAudioProvider(std::unique_ptr src, agi::fs::path const& dir) : AudioProviderWrapper(std::move(src)) - , file(dir / CacheFilename(dir), num_samples * bytes_per_sample) + , file(dir / CacheFilename(dir), num_samples * bytes_per_sample * channels) { decoded_samples = 0; decoder = std::thread([&] { diff --git a/libaegisub/audio/provider_ram.cpp b/libaegisub/audio/provider_ram.cpp index 0c1da546c..3f070c87e 100644 --- a/libaegisub/audio/provider_ram.cpp +++ b/libaegisub/audio/provider_ram.cpp @@ -71,20 +71,22 @@ public: void RAMAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const { auto charbuf = static_cast(buf); - for (int64_t bytes_remaining = count * bytes_per_sample; bytes_remaining; ) { + for (int64_t bytes_remaining = count * bytes_per_sample * channels; bytes_remaining; ) { if (start >= decoded_samples) { memset(charbuf, 0, bytes_remaining); break; } - const int i = (start * bytes_per_sample) >> CacheBits; - const int start_offset = (start * bytes_per_sample) & (CacheBlockSize-1); - const int read_size = std::min(bytes_remaining, CacheBlockSize - start_offset); + const int64_t samples_per_block = CacheBlockSize / bytes_per_sample / channels; + + const size_t i = start / samples_per_block; + const int start_offset = (start % samples_per_block) * bytes_per_sample * channels; + const int read_size = std::min(bytes_remaining, samples_per_block * bytes_per_sample * channels - start_offset); memcpy(charbuf, &blockcache[i][start_offset], read_size); charbuf += read_size; bytes_remaining -= read_size; - start += read_size / bytes_per_sample; + start += read_size / bytes_per_sample / channels; } } } diff --git a/libaegisub/include/libaegisub/audio/provider.h b/libaegisub/include/libaegisub/audio/provider.h index 70460a723..fc2e0ddb3 100644 --- a/libaegisub/include/libaegisub/audio/provider.h +++ b/libaegisub/include/libaegisub/audio/provider.h @@ -20,7 +20,6 @@ #include #include -#include #include namespace agi { @@ -37,6 +36,7 @@ protected: bool float_samples = false; virtual void FillBuffer(void *buf, int64_t start, int64_t count) const = 0; + virtual void FillBufferInt16Mono(int16_t* buf, int64_t start, int64_t count) const; void ZeroFill(void *buf, int64_t count) const; @@ -44,7 +44,8 @@ public: virtual ~AudioProvider() = default; void GetAudio(void *buf, int64_t start, int64_t count) const; - void GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const; + void GetInt16MonoAudio(int16_t* buf, int64_t start, int64_t count) const; + void GetInt16MonoAudioWithVolume(int16_t *buf, int64_t start, int64_t count, double volume) const; int64_t GetNumSamples() const { return num_samples; } int64_t GetDecodedSamples() const { return decoded_samples; } @@ -93,4 +94,4 @@ std::unique_ptr CreateHDAudioProvider(std::unique_ptr CreateRAMAudioProvider(std::unique_ptr source_provider); void SaveAudioClip(AudioProvider const& provider, fs::path const& path, int start_time, int end_time); -} +} \ No newline at end of file diff --git a/meson.build b/meson.build index 5527cfbe8..64cc9d3ca 100644 --- a/meson.build +++ b/meson.build @@ -7,7 +7,7 @@ project('Aegisub', ['c', 'cpp'], cmake = import('cmake') if host_machine.system() == 'windows' - add_project_arguments('-DNOMINMAX', '-D_WIN32_WINNT=0x0601', language: 'cpp') + add_project_arguments('-DNOMINMAX', language: 'cpp') if not get_option('csri').disabled() add_global_arguments('-DCSRI_NO_EXPORT', language: 'c') @@ -259,16 +259,26 @@ if host_machine.system() == 'windows' if not get_option('xaudio2').disabled() have_xaudio_h = cc.has_header('xaudio2.h') xaudio2_dep = cc.find_library('xaudio2', required: true) - if have_xaudio_h + if have_xaudio_h and xaudio2_dep.found() deps += [xaudio2_dep] conf.set('WITH_XAUDIO2', 1) dep_avail += 'XAudio2' + # XAudio2 needs Windows 8 or newer, so we tell meson not to define an older windows or else it can break things. + add_project_arguments('-D_WIN32_WINNT=0x0602', language: 'cpp') + else + # Windows 8 not required if XAudio2 fails to be found. revert for compat. + add_project_arguments('-D_WIN32_WINNT=0x0601', language: 'cpp') endif if not have_dsound_h and get_option('xaudio2').enabled() error('xaudio2 enabled but xaudio2.h not found') endif + else + # Windows 8 not required if XAudio2 is disabled. revert for compat. + add_project_arguments('-D_WIN32_WINNT=0x0601', language: 'cpp') endif + + endif if host_machine.system() == 'darwin' diff --git a/src/audio_player_alsa.cpp b/src/audio_player_alsa.cpp index 5a1705622..5a98d9ed7 100644 --- a/src/audio_player_alsa.cpp +++ b/src/audio_player_alsa.cpp @@ -127,7 +127,7 @@ void AlsaPlayer::PlaybackThread() do_setup: snd_pcm_format_t pcm_format; - switch (provider->GetBytesPerSample()) + switch (/*provider->GetBytesPerSample()*/ sizeof(int16_t)) { case 1: LOG_D("audio/player/alsa") << "format U8"; @@ -143,7 +143,7 @@ do_setup: if (snd_pcm_set_params(pcm, pcm_format, SND_PCM_ACCESS_RW_INTERLEAVED, - provider->GetChannels(), + /*provider->GetChannels()*/ 1, provider->GetSampleRate(), 1, // allow resample 100*1000 // 100 milliseconds latency @@ -151,7 +151,8 @@ do_setup: return; LOG_D("audio/player/alsa") << "set pcm params"; - size_t framesize = provider->GetChannels() * provider->GetBytesPerSample(); + //size_t framesize = provider->GetChannels() * provider->GetBytesPerSample(); + size_t framesize = sizeof(int16_t); while (true) { @@ -175,7 +176,7 @@ do_setup: { auto avail = std::min(snd_pcm_avail(pcm), (snd_pcm_sframes_t)(end_position-position)); decode_buffer.resize(avail * framesize); - provider->GetAudioWithVolume(decode_buffer.data(), position, avail, volume); + provider->GetInt16MonoAudioWithVolume(reinterpret_cast(decode_buffer.data()), position, avail, volume); snd_pcm_sframes_t written = 0; while (written <= 0) @@ -235,7 +236,7 @@ do_setup: { decode_buffer.resize(avail * framesize); - provider->GetAudioWithVolume(decode_buffer.data(), position, avail, volume); + provider->GetInt16MonoAudioWithVolume(reinterpret_cast(decode_buffer.data()), position, avail, volume); snd_pcm_sframes_t written = 0; while (written <= 0) { @@ -352,4 +353,4 @@ std::unique_ptr CreateAlsaPlayer(agi::AudioProvider *provider, wxWi return agi::make_unique(provider); } -#endif // WITH_ALSA +#endif // WITH_ALSA \ No newline at end of file diff --git a/src/audio_player_dsound.cpp b/src/audio_player_dsound.cpp index 01b47b354..8b50492a0 100644 --- a/src/audio_player_dsound.cpp +++ b/src/audio_player_dsound.cpp @@ -45,6 +45,7 @@ #include #include +#include namespace { class DirectSoundPlayer; @@ -111,8 +112,10 @@ DirectSoundPlayer::DirectSoundPlayer(agi::AudioProvider *provider, wxWindow *par WAVEFORMATEX waveFormat; waveFormat.wFormatTag = WAVE_FORMAT_PCM; waveFormat.nSamplesPerSec = provider->GetSampleRate(); - waveFormat.nChannels = provider->GetChannels(); - waveFormat.wBitsPerSample = provider->GetBytesPerSample() * 8; + //waveFormat.nChannels = provider->GetChannels(); + //waveFormat.wBitsPerSample = provider->GetBytesPerSample() * 8; + waveFormat.nChannels = 1; + waveFormat.wBitsPerSample = sizeof(int16_t) * 8; waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; waveFormat.cbSize = sizeof(waveFormat); @@ -160,7 +163,7 @@ bool DirectSoundPlayer::FillBuffer(bool fill) { HRESULT res; void *ptr1, *ptr2; unsigned long int size1, size2; - int bytesps = provider->GetBytesPerSample(); + int bytesps = /*provider->GetBytesPerSample()*/ sizeof(int16_t); // To write length int toWrite = 0; @@ -223,8 +226,8 @@ RetryLock: LOG_D_IF(!count1 && !count2, "audio/player/dsound1") << "DS fill: nothing"; // Get source wave - if (count1) provider->GetAudioWithVolume(ptr1, playPos, count1, volume); - if (count2) provider->GetAudioWithVolume(ptr2, playPos+count1, count2, volume); + if (count1) provider->GetInt16MonoAudioWithVolume(reinterpret_cast(ptr1), playPos, count1, volume); + if (count2) provider->GetInt16MonoAudioWithVolume(reinterpret_cast(ptr2), playPos+count1, count2, volume); playPos += count1+count2; buffer->Unlock(ptr1,count1*bytesps,ptr2,count2*bytesps); @@ -254,7 +257,7 @@ void DirectSoundPlayer::Play(int64_t start,int64_t count) { FillBuffer(true); DWORD play_flag = 0; - if (count*provider->GetBytesPerSample() > bufSize) { + if (count*/*provider->GetBytesPerSample()*/sizeof(int16_t) > bufSize) { // Start thread thread = new DirectSoundPlayerThread(this); thread->Create(); @@ -371,4 +374,4 @@ std::unique_ptr CreateDirectSoundPlayer(agi::AudioProvider *provide return agi::make_unique(provider, parent); } -#endif // WITH_DIRECTSOUND +#endif // WITH_DIRECTSOUND \ No newline at end of file diff --git a/src/audio_player_dsound2.cpp b/src/audio_player_dsound2.cpp index dd7bf8680..e0d585818 100644 --- a/src/audio_player_dsound2.cpp +++ b/src/audio_player_dsound2.cpp @@ -48,6 +48,7 @@ #include #include #include +#include namespace { class DirectSoundPlayer2Thread; @@ -319,8 +320,10 @@ void DirectSoundPlayer2Thread::Run() WAVEFORMATEX waveFormat; waveFormat.wFormatTag = WAVE_FORMAT_PCM; waveFormat.nSamplesPerSec = provider->GetSampleRate(); - waveFormat.nChannels = provider->GetChannels(); - waveFormat.wBitsPerSample = provider->GetBytesPerSample() * 8; + //waveFormat.nChannels = provider->GetChannels(); + //waveFormat.wBitsPerSample = provider->GetBytesPerSample() * 8; + waveFormat.nChannels = 1; + waveFormat.wBitsPerSample = sizeof(int16_t) * 8; waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; waveFormat.cbSize = sizeof(waveFormat); @@ -369,7 +372,7 @@ void DirectSoundPlayer2Thread::Run() DWORD buffer_offset = 0; bool playback_should_be_running = false; int current_latency = wanted_latency; - const DWORD wanted_latency_bytes = wanted_latency*waveFormat.nSamplesPerSec*provider->GetBytesPerSample()/1000; + const DWORD wanted_latency_bytes = wanted_latency*waveFormat.nSamplesPerSec*/*provider->GetBytesPerSample()*/sizeof(int16_t)/1000; while (running) { @@ -422,7 +425,7 @@ void DirectSoundPlayer2Thread::Run() if (bytes_filled < wanted_latency_bytes) { // Very short playback length, do without streaming playback - current_latency = (bytes_filled*1000) / (waveFormat.nSamplesPerSec*provider->GetBytesPerSample()); + current_latency = (bytes_filled*1000) / (waveFormat.nSamplesPerSec*/*provider->GetBytesPerSample()*/sizeof(int16_t)); if (FAILED(bfr->Play(0, 0, 0))) REPORT_ERROR("Could not start single-buffer playback.") } @@ -553,7 +556,7 @@ do_fill_buffer: else if (bytes_filled < wanted_latency_bytes) { // Didn't fill as much as we wanted to, let's get back to filling sooner than normal - current_latency = (bytes_filled*1000) / (waveFormat.nSamplesPerSec*provider->GetBytesPerSample()); + current_latency = (bytes_filled*1000) / (waveFormat.nSamplesPerSec*/*provider->GetBytesPerSample()*/sizeof(int16_t)); } else { @@ -577,7 +580,7 @@ DWORD DirectSoundPlayer2Thread::FillAndUnlockBuffers(void *buf1, DWORD buf1sz, v { // Assume buffers have been locked and are ready to be filled - DWORD bytes_per_frame = provider->GetChannels() * provider->GetBytesPerSample(); + DWORD bytes_per_frame = /*provider->GetChannels() * provider->GetBytesPerSample()*/sizeof(int16_t); DWORD buf1szf = buf1sz / bytes_per_frame; DWORD buf2szf = buf2sz / bytes_per_frame; @@ -608,7 +611,7 @@ DWORD DirectSoundPlayer2Thread::FillAndUnlockBuffers(void *buf1, DWORD buf1sz, v buf2sz = 0; } - provider->GetAudioWithVolume(buf1, input_frame, buf1szf, volume); + provider->GetInt16MonoAudioWithVolume(reinterpret_cast(buf1), input_frame, buf1szf, volume); input_frame += buf1szf; } @@ -621,7 +624,7 @@ DWORD DirectSoundPlayer2Thread::FillAndUnlockBuffers(void *buf1, DWORD buf1sz, v buf2sz = buf2szf * bytes_per_frame; } - provider->GetAudioWithVolume(buf2, input_frame, buf2szf, volume); + provider->GetInt16MonoAudioWithVolume(reinterpret_cast(buf2), input_frame, buf2szf, volume); input_frame += buf2szf; } @@ -932,4 +935,4 @@ std::unique_ptr CreateDirectSound2Player(agi::AudioProvider *provid return agi::make_unique(provider, parent); } -#endif // WITH_DIRECTSOUND +#endif // WITH_DIRECTSOUND \ No newline at end of file diff --git a/src/audio_player_openal.cpp b/src/audio_player_openal.cpp index b0f8372bd..d95d804ce 100644 --- a/src/audio_player_openal.cpp +++ b/src/audio_player_openal.cpp @@ -125,7 +125,7 @@ public: OpenALPlayer::OpenALPlayer(agi::AudioProvider *provider) : AudioPlayer(provider) , samplerate(provider->GetSampleRate()) -, bpf(provider->GetChannels() * provider->GetBytesPerSample()) +, bpf(/*provider->GetChannels() * provider->GetBytesPerSample()*/sizeof(int16_t)) { device = alcOpenDevice(nullptr); if (!device) throw AudioPlayerOpenError("Failed opening default OpenAL device"); @@ -241,7 +241,7 @@ void OpenALPlayer::FillBuffers(ALsizei count) if (fill_len > 0) // Get fill_len frames of audio - provider->GetAudioWithVolume(&decode_buffer[0], cur_frame, fill_len, volume); + provider->GetInt16MonoAudioWithVolume(reinterpret_cast(decode_buffer.data()), cur_frame, fill_len, volume); if ((size_t)fill_len * bpf < decode_buffer.size()) // And zerofill the rest memset(&decode_buffer[fill_len * bpf], 0, decode_buffer.size() - fill_len * bpf); @@ -308,4 +308,4 @@ std::unique_ptr CreateOpenALPlayer(agi::AudioProvider *provider, wx return agi::make_unique(provider); } -#endif // WITH_OPENAL +#endif // WITH_OPENAL \ No newline at end of file diff --git a/src/audio_player_oss.cpp b/src/audio_player_oss.cpp index 93950baef..0934ce197 100644 --- a/src/audio_player_oss.cpp +++ b/src/audio_player_oss.cpp @@ -131,7 +131,7 @@ public: while (!TestDestroy() && parent->cur_frame < parent->end_frame) { int rsize = std::min(wsize, parent->end_frame - parent->cur_frame); - parent->provider->GetAudioWithVolume(buf, parent->cur_frame, + parent->provider->GetInt16MonoAudioWithVolume(reinterpret_cast(buf), parent->cur_frame, rsize, parent->volume); int written = ::write(parent->dspdev, buf, rsize * parent->bpf); parent->cur_frame += written / parent->bpf; @@ -146,7 +146,7 @@ public: void OSSPlayer::OpenStream() { - bpf = provider->GetChannels() * provider->GetBytesPerSample(); + bpf = /*provider->GetChannels() * provider->GetBytesPerSample()*/sizeof(int16_t); // Open device wxString device = to_wx(OPT_GET("Player/Audio/OSS/Device")->GetString()); @@ -162,14 +162,14 @@ void OSSPlayer::OpenStream() #endif // Set number of channels - int channels = provider->GetChannels(); + int channels = /*provider->GetChannels()*/1; if (ioctl(dspdev, SNDCTL_DSP_CHANNELS, &channels) < 0) { throw AudioPlayerOpenError("OSS player: setting channels failed"); } // Set sample format int sample_format; - switch (provider->GetBytesPerSample()) { + switch (/*provider->GetBytesPerSample()*/sizeof(int16_t)) { case 1: sample_format = AFMT_S8; break; @@ -283,4 +283,4 @@ std::unique_ptr CreateOSSPlayer(agi::AudioProvider *provider, wxWin return agi::make_unique(provider); } -#endif // WITH_OSS +#endif // WITH_OSS \ No newline at end of file diff --git a/src/audio_player_portaudio.cpp b/src/audio_player_portaudio.cpp index 7a5babcdc..513b8b28b 100644 --- a/src/audio_player_portaudio.cpp +++ b/src/audio_player_portaudio.cpp @@ -140,7 +140,7 @@ void PortAudioPlayer::OpenStream() { const PaDeviceInfo *device_info = Pa_GetDeviceInfo((*device_ids)[i]); PaStreamParameters pa_output_p; pa_output_p.device = (*device_ids)[i]; - pa_output_p.channelCount = provider->GetChannels(); + pa_output_p.channelCount = /*provider->GetChannels()*/ 1; pa_output_p.sampleFormat = paInt16; pa_output_p.suggestedLatency = device_info->defaultLowOutputLatency; pa_output_p.hostApiSpecificStreamInfo = nullptr; @@ -222,7 +222,7 @@ int PortAudioPlayer::paCallback(const void *inputBuffer, void *outputBuffer, // Play something if (lenAvailable > 0) { - player->provider->GetAudioWithVolume(outputBuffer, player->current, lenAvailable, player->GetVolume()); + player->provider->GetInt16MonoAudioWithVolume(reinterpret_cast(outputBuffer), player->current, lenAvailable, player->GetVolume()); // Set play position player->current += lenAvailable; @@ -283,4 +283,4 @@ std::unique_ptr CreatePortAudioPlayer(agi::AudioProvider *provider, return agi::make_unique(provider); } -#endif // WITH_PORTAUDIO +#endif // WITH_PORTAUDIO \ No newline at end of file diff --git a/src/audio_player_pulse.cpp b/src/audio_player_pulse.cpp index 7174356bd..09882cb74 100644 --- a/src/audio_player_pulse.cpp +++ b/src/audio_player_pulse.cpp @@ -133,11 +133,11 @@ PulseAudioPlayer::PulseAudioPlayer(agi::AudioProvider *provider) : AudioPlayer(p } // Set up stream - bpf = provider->GetChannels() * provider->GetBytesPerSample(); + bpf = /*provider->GetChannels() * provider->GetBytesPerSample()*/sizeof(int16_t); pa_sample_spec ss; ss.format = PA_SAMPLE_S16LE; // FIXME ss.rate = provider->GetSampleRate(); - ss.channels = provider->GetChannels(); + ss.channels = /*provider->GetChannels()*/1; pa_channel_map map; pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_DEFAULT); @@ -308,7 +308,7 @@ void PulseAudioPlayer::pa_stream_write(pa_stream *p, size_t length, PulseAudioPl unsigned long maxframes = thread->end_frame - thread->cur_frame; if (frames > maxframes) frames = maxframes; void *buf = malloc(frames * bpf); - thread->provider->GetAudioWithVolume(buf, thread->cur_frame, frames, thread->volume); + thread->provider->GetInt16MonoAudioWithVolume(reinterpret_cast(buf), thread->cur_frame, frames, thread->volume); ::pa_stream_write(p, buf, frames*bpf, free, 0, PA_SEEK_RELATIVE); thread->cur_frame += frames; } @@ -324,4 +324,4 @@ void PulseAudioPlayer::pa_stream_notify(pa_stream *p, PulseAudioPlayer *thread) std::unique_ptr CreatePulseAudioPlayer(agi::AudioProvider *provider, wxWindow *) { return agi::make_unique(provider); } -#endif // WITH_LIBPULSE +#endif // WITH_LIBPULSE \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 5287fb198..4844a0ae8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -236,7 +236,7 @@ opt_src = [ ['OSS', 'audio_player_oss.cpp'], ['DirectSound', ['audio_player_dsound.cpp', 'audio_player_dsound2.cpp']], - + ['XAudio2', 'audio_player_xaudio2.cpp'], ['FFMS2', ['audio_provider_ffmpegsource.cpp', 'video_provider_ffmpegsource.cpp', 'ffmpegsource_common.cpp']], diff --git a/src/res/res.rc b/src/res/res.rc index 26f268165..0fdf8cb21 100644 --- a/src/res/res.rc +++ b/src/res/res.rc @@ -39,8 +39,13 @@ eyedropper_cursor CURSOR "../bitmaps/windows/eyedropper.cur" #endif VS_VERSION_INFO VERSIONINFO +#ifdef TAGGED_RELEASE FILEVERSION RESOURCE_BASE_VERSION, BUILD_GIT_VERSION_NUMBER PRODUCTVERSION RESOURCE_BASE_VERSION, 0 +#else +FILEVERSION BUILD_GIT_VERSION_NUMBER, BUILD_GIT_VERSION_NUMBER +PRODUCTVERSION BUILD_GIT_VERSION_NUMBER, 0 +#endif FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEFLAGS (AGI_RC_FLAG_DEBUG|AGI_RC_FLAG_PRERELEASE) FILEOS VOS__WINDOWS32 diff --git a/tests/tests/audio.cpp b/tests/tests/audio.cpp index 23255c042..300834e5a 100644 --- a/tests/tests/audio.cpp +++ b/tests/tests/audio.cpp @@ -172,21 +172,21 @@ TEST(lagi_audio, save_audio_clip_out_of_audio_range) { TEST(lagi_audio, get_with_volume) { TestAudioProvider<> provider; - uint16_t buff[4]; + int16_t buff[4]; - provider.GetAudioWithVolume(buff, 0, 4, 1.0); + provider.GetInt16MonoAudioWithVolume(buff, 0, 4, 1.0); EXPECT_EQ(0, buff[0]); EXPECT_EQ(1, buff[1]); EXPECT_EQ(2, buff[2]); EXPECT_EQ(3, buff[3]); - provider.GetAudioWithVolume(buff, 0, 4, 0.0); + provider.GetInt16MonoAudioWithVolume(buff, 0, 4, 0.0); EXPECT_EQ(0, buff[0]); EXPECT_EQ(0, buff[1]); EXPECT_EQ(0, buff[2]); EXPECT_EQ(0, buff[3]); - provider.GetAudioWithVolume(buff, 0, 4, 2.0); + provider.GetInt16MonoAudioWithVolume(buff, 0, 4, 2.0); EXPECT_EQ(0, buff[0]); EXPECT_EQ(2, buff[1]); EXPECT_EQ(4, buff[2]); @@ -195,8 +195,8 @@ TEST(lagi_audio, get_with_volume) { TEST(lagi_audio, volume_should_clamp_rather_than_wrap) { TestAudioProvider<> provider; - uint16_t buff[1]; - provider.GetAudioWithVolume(buff, 30000, 1, 2.0); + int16_t buff[1]; + provider.GetInt16MonoAudioWithVolume(buff, 30000, 1, 2.0); EXPECT_EQ(SHRT_MAX, buff[0]); } @@ -232,7 +232,7 @@ TEST(lagi_audio, convert_8bit) { auto provider = agi::CreateConvertAudioProvider(agi::make_unique>()); int16_t data[256]; - provider->GetAudio(data, 0, 256); + provider->GetInt16MonoAudio(data, 0, 256); for (int i = 0; i < 256; ++i) ASSERT_EQ((i - 128) * 256, data[i]); } @@ -243,13 +243,13 @@ TEST(lagi_audio, convert_32bit) { auto provider = agi::CreateConvertAudioProvider(std::move(src)); int16_t sample; - provider->GetAudio(&sample, 0, 1); + provider->GetInt16MonoAudio(&sample, 0, 1); EXPECT_EQ(SHRT_MIN, sample); - provider->GetAudio(&sample, 1LL << 31, 1); + provider->GetInt16MonoAudio(&sample, 1LL << 31, 1); EXPECT_EQ(0, sample); - provider->GetAudio(&sample, (1LL << 32) - 1, 1); + provider->GetInt16MonoAudio(&sample, (1LL << 32) - 1, 1); EXPECT_EQ(SHRT_MAX, sample); } @@ -310,10 +310,10 @@ TEST(lagi_audio, stereo_downmix) { }; auto provider = agi::CreateConvertAudioProvider(agi::make_unique()); - EXPECT_EQ(1, provider->GetChannels()); + EXPECT_EQ(2, provider->GetChannels()); int16_t samples[100]; - provider->GetAudio(samples, 0, 100); + provider->GetInt16MonoAudio(samples, 0, 100); for (int i = 0; i < 100; ++i) EXPECT_EQ(i, samples[i]); } @@ -333,27 +333,27 @@ struct FloatAudioProvider : agi::AudioProvider { auto out = static_cast(buf); for (int64_t end = start + count; start < end; ++start) { auto shifted = start + SHRT_MIN; - *out++ = (Float)(1.0 * shifted / (shifted < 0 ? -SHRT_MIN : SHRT_MAX)); + *out++ = (Float)(shifted) / (-SHRT_MIN); } } }; TEST(lagi_audio, float_conversion) { auto provider = agi::CreateConvertAudioProvider(agi::make_unique>()); - EXPECT_FALSE(provider->AreSamplesFloat()); + EXPECT_TRUE(provider->AreSamplesFloat()); int16_t samples[1 << 16]; - provider->GetAudio(samples, 0, 1 << 16); + provider->GetInt16MonoAudio(samples, 0, 1 << 16); for (int i = 0; i < (1 << 16); ++i) ASSERT_EQ(i + SHRT_MIN, samples[i]); } TEST(lagi_audio, double_conversion) { auto provider = agi::CreateConvertAudioProvider(agi::make_unique>()); - EXPECT_FALSE(provider->AreSamplesFloat()); + EXPECT_TRUE(provider->AreSamplesFloat()); int16_t samples[1 << 16]; - provider->GetAudio(samples, 0, 1 << 16); + provider->GetInt16MonoAudio(samples, 0, 1 << 16); for (int i = 0; i < (1 << 16); ++i) ASSERT_EQ(i + SHRT_MIN, samples[i]); } @@ -551,4 +551,4 @@ TEST(lagi_audio, wave64_truncated) { } agi::fs::Remove(path); -} +} \ No newline at end of file From 994e50048a21755d84ef46a25c8b5f7919881cab Mon Sep 17 00:00:00 2001 From: Ristellise Date: Wed, 10 Aug 2022 21:23:56 +0800 Subject: [PATCH 03/19] Finish up XAudio with a round of bugfixes --- libaegisub/audio/provider.cpp | 5 ++--- libaegisub/audio/provider_lock.cpp | 5 +++++ libaegisub/audio/provider_ram.cpp | 6 +++--- src/audio_provider_ffmpegsource.cpp | 13 ++++++++----- src/audio_renderer_spectrum.cpp | 4 ++-- src/audio_renderer_waveform.cpp | 2 +- 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/libaegisub/audio/provider.cpp b/libaegisub/audio/provider.cpp index b7ad7847c..0abb67eeb 100644 --- a/libaegisub/audio/provider.cpp +++ b/libaegisub/audio/provider.cpp @@ -73,7 +73,6 @@ public: } namespace agi { - void AudioProvider::FillBufferInt16Mono(int16_t* buf, int64_t start, int64_t count) const { if (!float_samples && bytes_per_sample == 2 && channels == 1) { FillBuffer(buf, start, count); @@ -242,7 +241,7 @@ void SaveAudioClip(AudioProvider const& provider, fs::path const& path, int star out.write("WAVEfmt "); out.write(16); // Size of chunk - out.write(1); // compression format (PCM) + out.write(provider.AreSamplesFloat() ? 3 : 1); // compression format (1: WAVE_FORMAT_PCM, 3: WAVE_FORMAT_IEEE_FLOAT) out.write(provider.GetChannels()); out.write(provider.GetSampleRate()); out.write(provider.GetSampleRate() * provider.GetChannels() * provider.GetBytesPerSample()); @@ -262,4 +261,4 @@ void SaveAudioClip(AudioProvider const& provider, fs::path const& path, int star out.write(buf); } } -} +} \ No newline at end of file diff --git a/libaegisub/audio/provider_lock.cpp b/libaegisub/audio/provider_lock.cpp index eb397e410..e405487a1 100644 --- a/libaegisub/audio/provider_lock.cpp +++ b/libaegisub/audio/provider_lock.cpp @@ -29,6 +29,11 @@ class LockAudioProvider final : public agi::AudioProviderWrapper { source->GetAudio(buf, start, count); } + void FillBufferInt16Mono(int16_t *buf, int64_t start, int64_t count) const override { + std::unique_lock lock(mutex); + source->GetInt16MonoAudio(buf, start, count); + } + public: LockAudioProvider(std::unique_ptr src) : AudioProviderWrapper(std::move(src)) diff --git a/libaegisub/audio/provider_ram.cpp b/libaegisub/audio/provider_ram.cpp index 3f070c87e..e708b0112 100644 --- a/libaegisub/audio/provider_ram.cpp +++ b/libaegisub/audio/provider_ram.cpp @@ -46,14 +46,14 @@ public: decoded_samples = 0; try { - blockcache.resize((source->GetNumSamples() * source->GetBytesPerSample() + CacheBlockSize - 1) >> CacheBits); + blockcache.resize((num_samples * bytes_per_sample * channels + CacheBlockSize - 1) >> CacheBits); } catch (std::bad_alloc const&) { throw AudioProviderError("Not enough memory available to cache in RAM"); } decoder = std::thread([&] { - int64_t readsize = CacheBlockSize / source->GetBytesPerSample(); + int64_t readsize = CacheBlockSize / bytes_per_sample / channels; for (size_t i = 0; i < blockcache.size(); i++) { if (cancelled) break; auto actual_read = std::min(readsize, num_samples - i * readsize); @@ -95,4 +95,4 @@ namespace agi { std::unique_ptr CreateRAMAudioProvider(std::unique_ptr src) { return agi::make_unique(std::move(src)); } -} +} \ No newline at end of file diff --git a/src/audio_provider_ffmpegsource.cpp b/src/audio_provider_ffmpegsource.cpp index 46e1412f5..1c109220f 100644 --- a/src/audio_provider_ffmpegsource.cpp +++ b/src/audio_provider_ffmpegsource.cpp @@ -126,8 +126,10 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) { // reindex if the error handling mode has changed FFMS_IndexErrorHandling ErrorHandling = GetErrorHandlingMode(); +#if FFMS_VERSION >= ((2 << 24) | (17 << 16) | (2 << 8) | 0) if (Index && FFMS_GetErrorHandling(Index) != ErrorHandling) Index = nullptr; +#endif // moment of truth if (!Index) { @@ -165,22 +167,23 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) { throw agi::AudioProviderError("unknown or unsupported sample format"); } +#if FFMS_VERSION >= ((2 << 24) | (17 << 16) | (4 << 8) | 0) if (OPT_GET("Provider/Audio/FFmpegSource/Downmix")->GetBool()) { - if (channels > 2 || bytes_per_sample != 2 || float_samples) { + if (channels > 1 || bytes_per_sample != 2 || float_samples) { std::unique_ptr opt(FFMS_CreateResampleOptions(AudioSource), FFMS_DestroyResampleOptions); - if (channels > 2) - opt->ChannelLayout = FFMS_CH_FRONT_LEFT | FFMS_CH_FRONT_RIGHT; + opt->ChannelLayout = FFMS_CH_FRONT_CENTER; opt->SampleFormat = FFMS_FMT_S16; // Might fail if FFMS2 wasn't built with libavresample if (!FFMS_SetOutputFormatA(AudioSource, opt.get(), nullptr)) { - channels = channels > 2 ? 2 : channels; + channels = 1; bytes_per_sample = 2; float_samples = false; } } } +#endif } } @@ -189,4 +192,4 @@ std::unique_ptr CreateFFmpegSourceAudioProvider(agi::fs::pat return agi::make_unique(file, br); } -#endif /* WITH_FFMS2 */ +#endif /* WITH_FFMS2 */ \ No newline at end of file diff --git a/src/audio_renderer_spectrum.cpp b/src/audio_renderer_spectrum.cpp index 217090a6f..7190c448a 100644 --- a/src/audio_renderer_spectrum.cpp +++ b/src/audio_renderer_spectrum.cpp @@ -208,8 +208,8 @@ void AudioSpectrumRenderer::FillBlock(size_t block_index, float *block) assert(block); int64_t first_sample = (((int64_t)block_index) << derivation_dist) - ((int64_t)1 << derivation_size); - provider->GetAudio(&audio_scratch[0], first_sample, 2 << derivation_size); - + provider->GetInt16MonoAudio(audio_scratch.data(), first_sample, 2 << derivation_size); + // Because the FFTs used here are unnormalized DFTs, we have to compensate // the possible length difference between derivation_size used in the // calculations and its user-provided counterpart. Thus, the display is diff --git a/src/audio_renderer_waveform.cpp b/src/audio_renderer_waveform.cpp index d5bb802fb..789dca024 100644 --- a/src/audio_renderer_waveform.cpp +++ b/src/audio_renderer_waveform.cpp @@ -88,7 +88,7 @@ void AudioWaveformRenderer::Render(wxBitmap &bmp, int start, AudioRenderingStyle for (int x = 0; x < rect.width; ++x) { - provider->GetAudio(audio_buffer.get(), (int64_t)cur_sample, (int64_t)pixel_samples); + provider->GetInt16MonoAudio(reinterpret_cast(audio_buffer.get()), (int64_t)cur_sample, (int64_t)pixel_samples); cur_sample += pixel_samples; int peak_min = 0, peak_max = 0; From 6906b6e5d7c2bfa3c6fdacc1b2bc7682cecfa1d5 Mon Sep 17 00:00:00 2001 From: Ristellise Date: Thu, 11 Aug 2022 12:28:35 +0800 Subject: [PATCH 04/19] [Shinon] Enable Directsound2 player to use more than 1 channel audio. - DS2 Player has a similar structure to XAudio, so I don't see any reason why not to enable 1 channel+ audio. - Haven't tried with 5.1 channel sources but I believe it should be the same as 2 channel (As in, Directsound will downmix the audio to 2 channel) - Moved the volume setting to using the player directly and from some quick audio tests, -10000 is too soft. I tried with -5000 instead which seems to be alright. --- src/audio_player_dsound2.cpp | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/audio_player_dsound2.cpp b/src/audio_player_dsound2.cpp index e0d585818..e8a144eb2 100644 --- a/src/audio_player_dsound2.cpp +++ b/src/audio_player_dsound2.cpp @@ -48,7 +48,6 @@ #include #include #include -#include namespace { class DirectSoundPlayer2Thread; @@ -318,15 +317,14 @@ void DirectSoundPlayer2Thread::Run() // Describe the wave format WAVEFORMATEX waveFormat; - waveFormat.wFormatTag = WAVE_FORMAT_PCM; waveFormat.nSamplesPerSec = provider->GetSampleRate(); - //waveFormat.nChannels = provider->GetChannels(); - //waveFormat.wBitsPerSample = provider->GetBytesPerSample() * 8; - waveFormat.nChannels = 1; - waveFormat.wBitsPerSample = sizeof(int16_t) * 8; + waveFormat.cbSize = 0; + waveFormat.wFormatTag = provider->AreSamplesFloat() ? 3 : WAVE_FORMAT_PCM; // Eh fuck it. + waveFormat.nChannels = provider->GetChannels(); + waveFormat.wBitsPerSample = provider->GetBytesPerSample() * 8; waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; - waveFormat.cbSize = sizeof(waveFormat); + //waveFormat.cbSize = sizeof(waveFormat); // And the buffer itself int aim = waveFormat.nAvgBytesPerSec * (wanted_latency*buffer_length)/1000; @@ -335,7 +333,7 @@ void DirectSoundPlayer2Thread::Run() DWORD bufSize = mid(min,aim,max); // size of entire playback buffer DSBUFFERDESC desc; desc.dwSize = sizeof(DSBUFFERDESC); - desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS; + desc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS; desc.dwBufferBytes = bufSize; desc.dwReserved = 0; desc.lpwfxFormat = &waveFormat; @@ -372,7 +370,7 @@ void DirectSoundPlayer2Thread::Run() DWORD buffer_offset = 0; bool playback_should_be_running = false; int current_latency = wanted_latency; - const DWORD wanted_latency_bytes = wanted_latency*waveFormat.nSamplesPerSec*/*provider->GetBytesPerSample()*/sizeof(int16_t)/1000; + const DWORD wanted_latency_bytes = wanted_latency*waveFormat.nSamplesPerSec*provider->GetBytesPerSample()/1000; while (running) { @@ -425,7 +423,7 @@ void DirectSoundPlayer2Thread::Run() if (bytes_filled < wanted_latency_bytes) { // Very short playback length, do without streaming playback - current_latency = (bytes_filled*1000) / (waveFormat.nSamplesPerSec*/*provider->GetBytesPerSample()*/sizeof(int16_t)); + current_latency = (bytes_filled*1000) / (waveFormat.nSamplesPerSec*provider->GetBytesPerSample()); if (FAILED(bfr->Play(0, 0, 0))) REPORT_ERROR("Could not start single-buffer playback.") } @@ -464,6 +462,16 @@ stop_playback: goto do_fill_buffer; case WAIT_OBJECT_0+3: + { + LONG invert_volume = (LONG)((this->volume - 1.0) * 5000.0); // Hrmm weirdly it's half? + // Look, I would have used a min max but it just errored out for me lol. + if (invert_volume > DSBVOLUME_MAX) + invert_volume = DSBVOLUME_MAX; + else if (invert_volume < DSBVOLUME_MIN / 2) + invert_volume = DSBVOLUME_MIN / 2; + LOG_I("DS2") << "Earrape vlume: " <SetVolume(invert_volume); + } // Change volume // We aren't thread safe right now, filling the buffers grabs volume directly // from the field set by the controlling thread, but it shouldn't be a major @@ -556,7 +564,7 @@ do_fill_buffer: else if (bytes_filled < wanted_latency_bytes) { // Didn't fill as much as we wanted to, let's get back to filling sooner than normal - current_latency = (bytes_filled*1000) / (waveFormat.nSamplesPerSec*/*provider->GetBytesPerSample()*/sizeof(int16_t)); + current_latency = (bytes_filled*1000) / (waveFormat.nSamplesPerSec*provider->GetBytesPerSample()); } else { @@ -580,7 +588,7 @@ DWORD DirectSoundPlayer2Thread::FillAndUnlockBuffers(void *buf1, DWORD buf1sz, v { // Assume buffers have been locked and are ready to be filled - DWORD bytes_per_frame = /*provider->GetChannels() * provider->GetBytesPerSample()*/sizeof(int16_t); + DWORD bytes_per_frame = provider->GetChannels() * provider->GetBytesPerSample(); DWORD buf1szf = buf1sz / bytes_per_frame; DWORD buf2szf = buf2sz / bytes_per_frame; @@ -611,7 +619,7 @@ DWORD DirectSoundPlayer2Thread::FillAndUnlockBuffers(void *buf1, DWORD buf1sz, v buf2sz = 0; } - provider->GetInt16MonoAudioWithVolume(reinterpret_cast(buf1), input_frame, buf1szf, volume); + provider->GetAudio(buf1, input_frame, buf1szf); input_frame += buf1szf; } @@ -624,7 +632,7 @@ DWORD DirectSoundPlayer2Thread::FillAndUnlockBuffers(void *buf1, DWORD buf1sz, v buf2sz = buf2szf * bytes_per_frame; } - provider->GetInt16MonoAudioWithVolume(reinterpret_cast(buf2), input_frame, buf2szf, volume); + provider->GetAudio(buf2, input_frame, buf2szf); input_frame += buf2szf; } From 9ebe154964c4d335e74026f653de24beb3699dab Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Sun, 14 Aug 2022 16:24:50 +0200 Subject: [PATCH 05/19] Fix width of lua float edits Float edits with a spinner are by default sized to fit their full min-max range of possible values, so this makes min and max default to 0 and 100 (like it'd done internally in wx) instead of -DOUBLE_MAX and DOUBLE_MAX. Note that this does change the behavior of lua dialogs, but does not contradict existing documentation or specification. It should only affect scripts who either disobey the specification by specifying only one value out of max/min, or scripts displaying these large float edits by specifing a step, but no max or min. --- src/auto4_lua_dialog.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/auto4_lua_dialog.cpp b/src/auto4_lua_dialog.cpp index 7985c49b9..7b4b5fa0c 100644 --- a/src/auto4_lua_dialog.cpp +++ b/src/auto4_lua_dialog.cpp @@ -284,6 +284,10 @@ namespace Automation4 { max = DBL_MAX; min = -DBL_MAX; } + if (step != 0.0) { + min = min == -DBL_MAX ? 0.0 : min; + max = max == DBL_MAX ? 100.0 : max; + } } bool CanSerialiseValue() const override { return true; } From fb24bcda6d5fdf84552ea4b1e5a599afda5a8c5f Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Mon, 15 Aug 2022 21:23:42 +0200 Subject: [PATCH 06/19] Delete CMakeLists The file was mistakenly added when pulling wangqr's "time to video" feature. I fixed the branch, but instead of merging the whole branch again, let's just delete the file here. --- CMakeLists.txt | 541 ------------------------------------------------- 1 file changed, 541 deletions(-) delete mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 4647a9147..000000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,541 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -cmake_policy(SET CMP0074 NEW) - -project(Aegisub) - -set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) - -include_directories("build") -include_directories("libaegisub/include") -include_directories("vendor/luajit/include") - -add_library(libaegisub STATIC - libaegisub/common/parser.cpp - libaegisub/ass/dialogue_parser.cpp - libaegisub/ass/time.cpp - libaegisub/ass/uuencode.cpp - libaegisub/audio/provider.cpp - libaegisub/audio/provider_convert.cpp - libaegisub/audio/provider_dummy.cpp - libaegisub/audio/provider_hd.cpp - libaegisub/audio/provider_lock.cpp - libaegisub/audio/provider_pcm.cpp - libaegisub/audio/provider_ram.cpp - libaegisub/common/cajun/elements.cpp - libaegisub/common/cajun/reader.cpp - libaegisub/common/cajun/writer.cpp - libaegisub/lua/modules/lfs.cpp - libaegisub/lua/modules/re.cpp - libaegisub/lua/modules/unicode.cpp - libaegisub/lua/modules/lpeg.c - libaegisub/lua/modules.cpp - libaegisub/lua/script_reader.cpp - libaegisub/lua/utils.cpp - libaegisub/common/calltip_provider.cpp - libaegisub/common/character_count.cpp - libaegisub/common/charset.cpp - libaegisub/common/charset_6937.cpp - libaegisub/common/charset_conv.cpp - libaegisub/common/color.cpp - libaegisub/common/file_mapping.cpp - libaegisub/common/format.cpp - libaegisub/common/fs.cpp - libaegisub/common/hotkey.cpp - libaegisub/common/io.cpp - libaegisub/common/json.cpp - libaegisub/common/kana_table.cpp - libaegisub/common/karaoke_matcher.cpp - libaegisub/common/keyframe.cpp - libaegisub/common/line_iterator.cpp - libaegisub/common/log.cpp - libaegisub/common/mru.cpp - libaegisub/common/option.cpp - libaegisub/common/option_value.cpp - libaegisub/common/path.cpp - libaegisub/common/thesaurus.cpp - libaegisub/common/util.cpp - libaegisub/common/vfr.cpp - libaegisub/common/ycbcr_conv.cpp - libaegisub/common/dispatch.cpp -) -if (UNIX) - target_sources(libaegisub PRIVATE - libaegisub/unix/access.cpp - libaegisub/unix/fs.cpp - libaegisub/unix/log.cpp - libaegisub/unix/path.cpp - libaegisub/unix/util.cpp - ) -elseif(WIN32) - target_sources(libaegisub PRIVATE - libaegisub/windows/access.cpp - libaegisub/windows/charset_conv_win.cpp - libaegisub/windows/fs.cpp - libaegisub/windows/lagi_pre.cpp - libaegisub/windows/log_win.cpp - libaegisub/windows/path_win.cpp - libaegisub/windows/util_win.cpp - ) -endif(UNIX) -SET_TARGET_PROPERTIES(libaegisub PROPERTIES PREFIX "") - -add_library(luabins STATIC - vendor/luabins/src/fwrite.c - vendor/luabins/src/load.c - vendor/luabins/src/luabins.c - vendor/luabins/src/luainternals.c - vendor/luabins/src/save.c - vendor/luabins/src/savebuffer.c - vendor/luabins/src/write.c -) - -add_executable(luajit-minilua vendor/luajit/src/host/minilua.c) -if (NOT MSVC) -target_link_libraries(luajit-minilua m) -endif(NOT MSVC) -add_custom_command(TARGET luajit-minilua POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen - COMMAND luajit-minilua ../dynasm/dynasm.lua -D P64 -D JIT -D FFI -D FPU -D HFABI -D VER= -o gen/buildvm_arch.h vm_x86.dasc - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src -) -add_custom_command(TARGET luajit-minilua POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/src/libresrc/default_config_win.json ${PROJECT_SOURCE_DIR}/src/libresrc/default_config_platform.json - COMMAND luajit-minilua ../../tools/respack.lua manifest.respack default_config.cpp default_config.h - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/libresrc - BYPRODUCTS ${PROJECT_SOURCE_DIR}/src/libresrc/default_config.cpp ${PROJECT_SOURCE_DIR}/src/libresrc/default_config.h -) -add_custom_command(TARGET luajit-minilua POST_BUILD - COMMAND luajit-minilua ../../tools/respack.lua manifest.respack ../libresrc/bitmap.cpp ../libresrc/bitmap.h - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/bitmaps - BYPRODUCTS ${PROJECT_SOURCE_DIR}/src/libresrc/bitmap.cpp ${PROJECT_SOURCE_DIR}/src/libresrc/bitmap.h -) - -add_executable(luajit-buildvm - vendor/luajit/src/host/buildvm.c - vendor/luajit/src/host/buildvm_asm.c - vendor/luajit/src/host/buildvm_peobj.c - vendor/luajit/src/host/buildvm_lib.c - vendor/luajit/src/host/buildvm_fold.c -) -target_include_directories(luajit-buildvm PRIVATE vendor/luajit/src vendor/luajit/src/gen) -add_dependencies(luajit-buildvm luajit-minilua) -if(UNIX) - add_custom_command(TARGET luajit-buildvm POST_BUILD - COMMAND luajit-buildvm -m elfasm -o lj_vm.s - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src - BYPRODUCTS ${PROJECT_SOURCE_DIR}/vendor/luajit/src/lj_vm.s - ) - set_property(SOURCE vendor/luajit/src/lj_vm.s PROPERTY LANGUAGE C) -elseif(MSVC) - add_custom_command(TARGET luajit-buildvm POST_BUILD - COMMAND luajit-buildvm -m peobj -o lj_vm.obj - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src - BYPRODUCTS ${PROJECT_SOURCE_DIR}/vendor/luajit/src/lj_vm.obj - ) -endif(UNIX) -add_custom_command(TARGET luajit-buildvm POST_BUILD - COMMAND luajit-buildvm -m ffdef -o gen/lj_ffdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c - COMMAND luajit-buildvm -m bcdef -o gen/lj_bcdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c - COMMAND luajit-buildvm -m folddef -o gen/lj_folddef.h lj_opt_fold.c - COMMAND luajit-buildvm -m recdef -o gen/lj_recdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c - COMMAND luajit-buildvm -m libdef -o gen/lj_libdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c - COMMAND luajit-buildvm -m vmdef -o jit/vmdef.lua lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src -) - - -add_library(luajit STATIC - vendor/luajit/src/lj_gc.c - vendor/luajit/src/lj_err.c - vendor/luajit/src/lj_char.c - vendor/luajit/src/lj_bc.c - vendor/luajit/src/lj_obj.c - vendor/luajit/src/lj_str.c - vendor/luajit/src/lj_tab.c - vendor/luajit/src/lj_func.c - vendor/luajit/src/lj_udata.c - vendor/luajit/src/lj_meta.c - vendor/luajit/src/lj_debug.c - vendor/luajit/src/lj_state.c - vendor/luajit/src/lj_dispatch.c - vendor/luajit/src/lj_vmevent.c - vendor/luajit/src/lj_vmmath.c - vendor/luajit/src/lj_strscan.c - vendor/luajit/src/lj_api.c - vendor/luajit/src/lj_lex.c - vendor/luajit/src/lj_parse.c - vendor/luajit/src/lj_bcread.c - vendor/luajit/src/lj_bcwrite.c - vendor/luajit/src/lj_load.c - vendor/luajit/src/lj_ir.c - vendor/luajit/src/lj_opt_mem.c - vendor/luajit/src/lj_opt_fold.c - vendor/luajit/src/lj_opt_narrow.c - vendor/luajit/src/lj_opt_dce.c - vendor/luajit/src/lj_opt_loop.c - vendor/luajit/src/lj_opt_split.c - vendor/luajit/src/lj_opt_sink.c - vendor/luajit/src/lj_mcode.c - vendor/luajit/src/lj_snap.c - vendor/luajit/src/lj_record.c - vendor/luajit/src/lj_crecord.c - vendor/luajit/src/lj_ffrecord.c - vendor/luajit/src/lj_asm.c - vendor/luajit/src/lj_trace.c - vendor/luajit/src/lj_gdbjit.c - vendor/luajit/src/lj_ctype.c - vendor/luajit/src/lj_cdata.c - vendor/luajit/src/lj_cconv.c - vendor/luajit/src/lj_ccall.c - vendor/luajit/src/lj_ccallback.c - vendor/luajit/src/lj_carith.c - vendor/luajit/src/lj_clib.c - vendor/luajit/src/lj_cparse.c - vendor/luajit/src/lj_lib.c - vendor/luajit/src/lj_alloc.c - vendor/luajit/src/lib_aux.c - vendor/luajit/src/lib_base.c - vendor/luajit/src/lib_math.c - vendor/luajit/src/lib_bit.c - vendor/luajit/src/lib_string.c - vendor/luajit/src/lib_table.c - vendor/luajit/src/lib_io.c - vendor/luajit/src/lib_os.c - vendor/luajit/src/lib_package.c - vendor/luajit/src/lib_debug.c - vendor/luajit/src/lib_jit.c - vendor/luajit/src/lib_ffi.c - vendor/luajit/src/lib_init.c -) -if(MSVC) - target_sources(luajit PRIVATE vendor/luajit/src/lj_vm.obj) -else(MSVC) - target_sources(luajit PRIVATE vendor/luajit/src/lj_vm.s) - set_property(SOURCE vendor/luajit/src/lj_vm.s PROPERTY LANGUAGE C) - target_link_libraries(luajit dl) -endif(MSVC) -target_include_directories(luajit PRIVATE vendor/luajit/src/gen) -add_dependencies(luajit luajit-buildvm) -target_compile_definitions(luajit PRIVATE LUAJIT_ENABLE_LUA52COMPAT) - -add_library(resrc STATIC - src/libresrc/bitmap.cpp - src/libresrc/default_config.cpp - src/libresrc/libresrc.cpp -) -add_dependencies(resrc luajit-minilua) - -add_library(csri STATIC - vendor/csri/lib/list.c - vendor/csri/lib/wrap.c - vendor/csri/subhelp/logging.c -) -target_include_directories(csri PRIVATE "vendor/csri/include") -IF (WIN32) - target_include_directories(csri PRIVATE "vendor/csri/lib/win32") - target_sources(csri PRIVATE vendor/csri/lib/win32/enumerate.c) -ELSE() - target_include_directories(csri PRIVATE "vendor/csri/lib/posix") - target_sources(csri PRIVATE vendor/csri/lib/posix/enumerate.c) -ENDIF() - -add_executable(Aegisub WIN32 - src/command/app.cpp - src/command/audio.cpp - src/command/automation.cpp - src/command/command.cpp - src/command/edit.cpp - src/command/grid.cpp - src/command/help.cpp - src/command/keyframe.cpp - src/command/recent.cpp - src/command/subtitle.cpp - src/command/time.cpp - src/command/timecode.cpp - src/command/tool.cpp - src/command/video.cpp - src/command/vis_tool.cpp - src/dialog_about.cpp - src/dialog_align.cpp - src/dialog_attachments.cpp - src/dialog_automation.cpp - src/dialog_autosave.cpp - src/dialog_colorpicker.cpp - src/dialog_detached_video.cpp - src/dialog_dummy_video.cpp - src/dialog_export.cpp - src/dialog_export_ebu3264.cpp - src/dialog_fonts_collector.cpp - src/dialog_jumpto.cpp - src/dialog_kara_timing_copy.cpp - src/dialog_log.cpp - src/dialog_paste_over.cpp - src/dialog_progress.cpp - src/dialog_properties.cpp - src/dialog_resample.cpp - src/dialog_search_replace.cpp - src/dialog_selected_choices.cpp - src/dialog_selection.cpp - src/dialog_shift_times.cpp - src/dialog_spellchecker.cpp - src/dialog_style_editor.cpp - src/dialog_style_manager.cpp - src/dialog_styling_assistant.cpp - src/dialog_text_import.cpp - src/dialog_timing_processor.cpp - src/dialog_translation.cpp - src/dialog_version_check.cpp - src/dialog_video_details.cpp - src/dialog_video_properties.cpp - src/subtitle_format.cpp - src/subtitle_format_ass.cpp - src/subtitle_format_ebu3264.cpp - src/subtitle_format_encore.cpp - src/subtitle_format_microdvd.cpp - src/subtitle_format_mkv.cpp - src/subtitle_format_srt.cpp - src/subtitle_format_ssa.cpp - src/subtitle_format_transtation.cpp - src/subtitle_format_ttxt.cpp - src/subtitle_format_txt.cpp - src/visual_tool.cpp - src/visual_tool_clip.cpp - src/visual_tool_cross.cpp - src/visual_tool_drag.cpp - src/visual_tool_rotatexy.cpp - src/visual_tool_rotatez.cpp - src/visual_tool_scale.cpp - src/visual_tool_vector_clip.cpp - src/MatroskaParser.c - src/aegisublocale.cpp - src/ass_attachment.cpp - src/ass_dialogue.cpp - src/ass_entry.cpp - src/ass_export_filter.cpp - src/ass_exporter.cpp - src/ass_file.cpp - src/ass_karaoke.cpp - src/ass_override.cpp - src/ass_parser.cpp - src/ass_style.cpp - src/ass_style_storage.cpp - src/async_video_provider.cpp - src/audio_box.cpp - src/audio_colorscheme.cpp - src/audio_controller.cpp - src/audio_display.cpp - src/audio_karaoke.cpp - src/audio_marker.cpp - src/audio_player.cpp - src/audio_provider_factory.cpp - src/audio_renderer.cpp - src/audio_renderer_spectrum.cpp - src/audio_renderer_waveform.cpp - src/audio_timing_dialogue.cpp - src/audio_timing_karaoke.cpp - src/auto4_base.cpp - src/auto4_lua.cpp - src/auto4_lua_assfile.cpp - src/auto4_lua_dialog.cpp - src/auto4_lua_progresssink.cpp - src/base_grid.cpp - src/charset_detect.cpp - src/colorspace.cpp - src/colour_button.cpp - src/compat.cpp - src/context.cpp - src/export_fixstyle.cpp - src/export_framerate.cpp - src/fft.cpp - src/font_file_lister.cpp - src/frame_main.cpp - src/gl_text.cpp - src/gl_wrap.cpp - src/grid_column.cpp - src/help_button.cpp - src/hotkey.cpp - src/hotkey_data_view_model.cpp - src/image_position_picker.cpp - src/initial_line_state.cpp - src/main.cpp - src/menu.cpp - src/mkv_wrap.cpp - src/pen.cpp - src/persist_location.cpp - src/preferences.cpp - src/preferences_base.cpp - src/project.cpp - src/resolution_resampler.cpp - src/search_replace_engine.cpp - src/selection_controller.cpp - src/spellchecker.cpp - src/spline.cpp - src/spline_curve.cpp - src/string_codec.cpp - src/subs_controller.cpp - src/subs_edit_box.cpp - src/subs_edit_ctrl.cpp - src/subs_preview.cpp - src/subtitles_provider.cpp - src/subtitles_provider_libass.cpp - src/text_file_reader.cpp - src/text_file_writer.cpp - src/text_selection_controller.cpp - src/thesaurus.cpp - src/timeedit_ctrl.cpp - src/toggle_bitmap.cpp - src/toolbar.cpp - src/tooltip_manager.cpp - src/utils.cpp - src/validators.cpp - src/vector2d.cpp - src/version.cpp - src/video_box.cpp - src/video_controller.cpp - src/video_display.cpp - src/video_frame.cpp - src/video_out_gl.cpp - src/video_provider_cache.cpp - src/video_provider_dummy.cpp - src/video_provider_manager.cpp - src/video_provider_yuv4mpeg.cpp - src/video_slider.cpp - src/visual_feature.cpp -) -target_link_libraries(Aegisub ${CMAKE_DL_LIBS} libaegisub luabins luajit resrc csri) - -if (MSVC) - set_target_properties(libaegisub PROPERTIES COMPILE_FLAGS "/Yu${PROJECT_SOURCE_DIR}/libaegisub/lagi_pre.h" COMPILE_FLAGS "/FI${PROJECT_SOURCE_DIR}/libaegisub/lagi_pre.h") -else(MSVC) - target_compile_options(libaegisub PRIVATE -include "${PROJECT_SOURCE_DIR}/libaegisub/lagi_pre.h") -endif(MSVC) - -set_property( - SOURCE libaegisub/unix/path.cpp - PROPERTY COMPILE_DEFINITIONS - P_DATA="${CMAKE_INSTALL_PREFIX}/share/aegisub/" -) - -if (MSVC) - add_definitions("-DNOMINMAX -MP -DINITGUID") - set_target_properties(Aegisub PROPERTIES COMPILE_FLAGS "/Yu${PROJECT_SOURCE_DIR}/src/agi_pre.h" COMPILE_FLAGS "/FI${PROJECT_SOURCE_DIR}/src/agi_pre.h") - target_link_libraries (Aegisub Usp10) - #target_sources(Aegisub PRIVATE src/res/res.rc src/res/strings.rc src/crash_writer_minidump.cpp) - target_sources(Aegisub PRIVATE src/res/res.rc src/res/strings.rc src/crash_writer.cpp src/dpi_aware.manifest) - set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Aegisub) -else(MSVC) - target_sources(Aegisub PRIVATE src/crash_writer.cpp) - target_compile_options(Aegisub PRIVATE -include "${PROJECT_SOURCE_DIR}/src/agi_pre.h") -endif(MSVC) - -if (WIN32) - target_sources(Aegisub PRIVATE src/font_file_lister_gdi.cpp) -else (WIN32) - find_package(Fontconfig REQUIRED) - target_link_libraries (Aegisub ${Fontconfig_LIBRARIES}) - target_sources(Aegisub PRIVATE src/font_file_lister_fontconfig.cpp) - set_property(SOURCE src/font_file_lister_fontconfig.cpp PROPERTY INCLUDE_DIRECTORIES "${Fontconfig_INCLUDE_DIRS}") -endif (WIN32) - -find_package(ass REQUIRED) -include_directories(${ass_INCLUDE_DIRS}) -target_link_libraries (Aegisub ${ass_LIBRARIES}) - -find_package(Boost REQUIRED chrono filesystem locale regex system thread) -include_directories(${Boost_INCLUDE_DIRS}) -target_link_directories(Aegisub PRIVATE ${Boost_LIBRARY_DIRS}) -target_link_libraries(Aegisub ${Boost_LIBRARIES}) - -find_package(OpenGL REQUIRED) -include_directories(${OPENGL_INCLUDE_DIR}) -target_link_libraries (Aegisub ${OPENGL_LIBRARIES}) - -find_package(Hunspell REQUIRED) -include_directories(${HUNSPELL_INCLUDE_DIR}) -target_link_libraries (Aegisub ${HUNSPELL_LIBRARIES}) -add_definitions("-DWITH_HUNSPELL") -target_sources(Aegisub PRIVATE src/spellchecker_hunspell.cpp) - -find_package(Iconv REQUIRED) -include_directories(${Iconv_INCLUDE_DIRS}) -target_link_libraries (Aegisub ${Iconv_LIBRARIES}) -add_definitions("-DHAVE_ICONV") -if (NOT Iconv_IS_BUILT_IN) -set_property( - SOURCE libaegisub/common/charset_conv.cpp - PROPERTY COMPILE_DEFINITIONS AGI_ICONV_CONST -) -endif (NOT Iconv_IS_BUILT_IN) - -find_package(ICU REQUIRED uc dt in) -include_directories(${ICU_INCLUDE_DIRS}) -target_link_libraries (Aegisub ${ICU_LIBRARIES}) - -find_package(wxWidgets REQUIRED adv base core gl stc xml) -include(${wxWidgets_USE_FILE}) -target_link_libraries(Aegisub ${wxWidgets_LIBRARIES}) - -find_package(ZLIB REQUIRED) -include_directories(${ZLIB_INCLUDE_DIRS}) -target_link_libraries (Aegisub ${ZLIB_LIBRARIES}) - -find_package(ALSA) -if (ALSA_FOUND) - include_directories(${ALSA_INCLUDE_DIRS}) - target_link_libraries (Aegisub ${ALSA_LIBRARIES}) - add_definitions("-DWITH_ALSA") - target_sources(Aegisub PRIVATE src/audio_player_alsa.cpp) -endif(ALSA_FOUND) - -# target_compile_definitions(Aegisub PRIVATE "WITH_AVISYNTH") -# target_sources(Aegisub PRIVATE src/audio_provider_avs.cpp src/avisynth_wrap.cpp src/video_provider_avs.cpp) - -target_compile_definitions(Aegisub PRIVATE "WITH_CSRI") -target_sources(Aegisub PRIVATE src/subtitles_provider_csri.cpp) -set_property(SOURCE src/subtitles_provider_csri.cpp PROPERTY INCLUDE_DIRECTORIES "${PROJECT_SOURCE_DIR}/vendor/csri/include") - -if(MSVC) - target_link_libraries (Aegisub dsound) - add_definitions("-DWITH_DIRECTSOUND") - target_sources(Aegisub PRIVATE src/audio_player_dsound.cpp src/audio_player_dsound2.cpp) -endif(MSVC) - -find_package(FFMS2) -if (FFMS2_FOUND) - include_directories(${FFMS2_INCLUDE_DIRS}) - target_link_libraries (Aegisub ${FFMS2_LIBRARIES}) - add_definitions("-DWITH_FFMS2") - target_sources(Aegisub PRIVATE src/audio_provider_ffmpegsource.cpp src/ffmpegsource_common.cpp src/video_provider_ffmpegsource.cpp) -endif(FFMS2_FOUND) - -find_package(FFTW) -if (FFTW_FOUND) - include_directories(${FFTW_INCLUDES}) - target_link_libraries (Aegisub ${FFTW_LIBRARIES}) - add_definitions("-DWITH_FFTW3") -endif(FFTW_FOUND) - -#ifdef WITH_LIBPULSE -#add_definitions("-DWITH_LIBPULSE") -#target_sources(Aegisub PRIVATE src/audio_player_pulse.cpp) - -find_package(OpenAL) -if (OPENAL_FOUND) - include_directories(${OPENAL_INCLUDE_DIR}) - target_link_libraries (Aegisub ${OPENAL_LIBRARY}) - add_definitions("-DWITH_OPENAL") - target_sources(Aegisub PRIVATE src/audio_player_openal.cpp) -endif(OPENAL_FOUND) - -#ifdef WITH_OSS -#ifdef WITH_PORTAUDIO -#ifdef WITH_STARTUPLOG - -find_package(uchardet) -if (uchardet_FOUND) - include_directories(${uchardet_INCLUDE_DIRS}) - target_link_libraries (Aegisub ${uchardet_LIBRARIES}) - add_definitions("-DWITH_UCHARDET") -endif(uchardet_FOUND) - -#ifdef WITH_UPDATE_CHECKER From 25bd040494a9a2a49c94485b82a2d61a290df5ce Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Tue, 16 Aug 2022 16:07:25 +0200 Subject: [PATCH 07/19] Fix color picker option --- src/dialog_colorpicker.cpp | 4 ++-- src/libresrc/default_config.json | 4 +--- src/libresrc/osx/default_config.json | 4 +--- src/preferences.cpp | 4 ++-- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/dialog_colorpicker.cpp b/src/dialog_colorpicker.cpp index a9f20afef..378bbb534 100644 --- a/src/dialog_colorpicker.cpp +++ b/src/dialog_colorpicker.cpp @@ -388,7 +388,7 @@ void ColorPickerScreenDropper::DropFromScreenXY(int x, int y) { #ifndef __WXMAC__ std::unique_ptr screen; - if (!OPT_GET("Tool/Color Picker/Restrict to Window")->GetBool()) { + if (!OPT_GET("Tool/Colour Picker/Restrict to Window")->GetBool()) { screen = agi::make_unique(); } else { wxWindow *superparent = GetParent(); @@ -397,7 +397,7 @@ void ColorPickerScreenDropper::DropFromScreenXY(int x, int y) { } superparent->ScreenToClient(&x, &y); - screen = agi::make_unique(superparent); + screen = agi::make_unique(superparent); } capdc.StretchBlit(0, 0, resx * magnification, resy * magnification, screen.get(), x - resx / 2, y - resy / 2, resx, resy); diff --git a/src/libresrc/default_config.json b/src/libresrc/default_config.json index 7b8e9228e..009215701 100644 --- a/src/libresrc/default_config.json +++ b/src/libresrc/default_config.json @@ -448,6 +448,7 @@ "X" : -1, "Y" : -1 }, + "Restrict to Window" : false, "Maximized" : false }, "Fonts Collector" : { @@ -573,9 +574,6 @@ "Maximized" : false, "Skip Whitespace" : true }, - "Color Picker" : { - "Restrict to Window" : false - }, "Visual" : { "Autohide": false } diff --git a/src/libresrc/osx/default_config.json b/src/libresrc/osx/default_config.json index 9e89707b5..b282fec18 100644 --- a/src/libresrc/osx/default_config.json +++ b/src/libresrc/osx/default_config.json @@ -448,6 +448,7 @@ "X" : -1, "Y" : -1 }, + "Restrict to Window" : false, "Maximized" : false }, "Fonts Collector" : { @@ -573,9 +574,6 @@ "Maximized" : false, "Skip Whitespace" : true }, - "Color Picker" : { - "Restrict to Window" : false - }, "Visual" : { "Autohide": false } diff --git a/src/preferences.cpp b/src/preferences.cpp index d595caa63..14b84aaa6 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -228,8 +228,8 @@ void Interface(wxTreebook *book, Preferences *parent) { auto tl_assistant = p->PageSizer(_("Translation Assistant")); p->OptionAdd(tl_assistant, _("Skip over whitespace"), "Tool/Translation Assistant/Skip Whitespace"); - auto color_picker = p->PageSizer(_("Color Picker")); - p->OptionAdd(color_picker, _("Restrict Screen Picker to Window"), "Tool/Color Picker/Restrict to Window"); + auto color_picker = p->PageSizer(_("Colour Picker")); + p->OptionAdd(color_picker, _("Restrict Screen Picker to Window"), "Tool/Colour Picker/Restrict to Window"); p->SetSizerAndFit(p->sizer); } From 33b9a6e395198ac856c932404a65e6e049d6b146 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Mon, 8 Aug 2022 02:14:31 +0200 Subject: [PATCH 08/19] Add bestsource video provider Co-authored-by: Asada shinon --- .gitignore | 1 + meson.build | 17 ++ meson_options.txt | 1 + src/meson.build | 4 + src/video_frame.h | 2 + src/video_provider_bestsource.cpp | 188 ++++++++++++++++++ src/video_provider_manager.cpp | 4 + subprojects/bestsource.wrap | 7 + .../packagefiles/bestsource/libp2p/p2p_api.h | 156 +++++++++++++++ .../packagefiles/bestsource/meson.build | 28 +++ 10 files changed, 408 insertions(+) create mode 100644 src/video_provider_bestsource.cpp create mode 100644 subprojects/bestsource.wrap create mode 100644 subprojects/packagefiles/bestsource/libp2p/p2p_api.h create mode 100644 subprojects/packagefiles/bestsource/meson.build diff --git a/.gitignore b/.gitignore index a2d7c9b86..fd239df2d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ tools/repack-thes-dict.dSYM # Meson build*/ +subprojects/bestsource/ subprojects/boost*/ subprojects/cairo* subprojects/ffmpeg diff --git a/meson.build b/meson.build index 9029f5070..6115d1329 100644 --- a/meson.build +++ b/meson.build @@ -220,6 +220,23 @@ foreach dep: [ endif endforeach +needs_ffmpeg = false + +if get_option('bestsource').enabled() + conf.set('WITH_BESTSOURCE', 1) + bs = subproject('bestsource') + deps += bs.get_variable('bestsource_dep') + needs_ffmpeg = true +endif + +if needs_ffmpeg + conf.set('WITH_FFMPEG', 1) + deps += [ + dependency('libavutil', default_options: ['tests=disabled']), + dependency('libswscale', default_options: ['tests=disabled']), + ] +endif + if host_machine.system() == 'windows' and get_option('avisynth').enabled() conf.set('WITH_AVISYNTH', 1) # bundled separately with installer endif diff --git a/meson_options.txt b/meson_options.txt index 3bc0461cd..19c447af1 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -7,6 +7,7 @@ option('default_audio_output', type: 'combo', choices: ['auto', 'ALSA', 'OpenAL' option('ffms2', type: 'feature', description: 'FFMS2 video source') option('avisynth', type: 'feature', description: 'AviSynth video source') +option('bestsource', type: 'feature', description: 'BestSource video source') option('fftw3', type: 'feature', description: 'FFTW3 support') option('hunspell', type: 'feature', description: 'Hunspell spell checker') diff --git a/src/meson.build b/src/meson.build index 72587d366..2d5145dbf 100644 --- a/src/meson.build +++ b/src/meson.build @@ -224,6 +224,10 @@ if conf.has('WITH_CSRI') aegisub_src += files('subtitles_provider_csri.cpp') endif +if conf.has('WITH_BESTSOURCE') + aegisub_src += files('video_provider_bestsource.cpp') +endif + opt_src = [ ['ALSA', 'audio_player_alsa.cpp'], ['PortAudio', 'audio_player_portaudio.cpp'], diff --git a/src/video_frame.h b/src/video_frame.h index 2a47ed69c..2f4f21e90 100644 --- a/src/video_frame.h +++ b/src/video_frame.h @@ -14,6 +14,8 @@ // // Aegisub Project http://www.aegisub.org/ +#pragma once + #include class wxImage; diff --git a/src/video_provider_bestsource.cpp b/src/video_provider_bestsource.cpp new file mode 100644 index 000000000..8bf8786d5 --- /dev/null +++ b/src/video_provider_bestsource.cpp @@ -0,0 +1,188 @@ +// 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 video_provider_bestsource.cpp +/// @brief BestSource-based video provider +/// @ingroup video_input bestsource +/// + +#ifdef WITH_BESTSOURCE +#include "include/aegisub/video_provider.h" + +#include "videosource.h" +#include "BSRational.h" + +extern "C" { +#include +#include +#include +} + +/* #include "utils.h" */ +#include "options.h" +#include "compat.h" +#include "video_frame.h" +namespace agi { class BackgroundRunner; } + +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +/// @class BSVideoProvider +/// @brief Implements video loading through BestSource. +class BSVideoProvider final : public VideoProvider { + std::map bsopts; + BestVideoSource bs; + VideoProperties properties; + + std::vector Keyframes; + agi::vfr::Framerate Timecodes; + + std::string GetCacheFile(agi::fs::path const& filename); + +public: + BSVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br); + + void GetFrame(int n, VideoFrame &out) override; + + void SetColorSpace(std::string const& matrix) override { } + + int GetFrameCount() const override { return properties.NumFrames; }; + + int GetWidth() const override { return properties.Width; }; + int GetHeight() const override { return properties.Height; }; + double GetDAR() const override { return ((double) properties.Width * properties.SAR.Num) / (properties.Height * properties.SAR.Den); }; + + agi::vfr::Framerate GetFPS() const override { return Timecodes; }; + std::string GetColorSpace() const override { return "TV.709"; }; // TODO + std::string GetRealColorSpace() const override { return "TV.709"; }; + std::vector GetKeyFrames() const override { return Keyframes; }; + std::string GetDecoderName() const override { return "BestSource"; }; + bool WantsCaching() const override { return false; }; + bool HasAudio() const override { return false; }; +}; + +BSVideoProvider::BSVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br) try +: bsopts() +, bs(filename.string(), "", -1, false, 0, GetCacheFile(filename), &bsopts) +{ + + properties = bs.GetVideoProperties(); + + if (properties.NumFrames == -1) { + LOG_D("bs") << "File not cached or varying samples, creating cache."; + br->Run([&](agi::ProgressSink *ps) { + ps->SetTitle(from_wx(_("Exacting"))); + ps->SetMessage(from_wx(_("Creating cache... This can take a while!"))); + ps->SetIndeterminate(); + if (bs.GetExactDuration()) { + LOG_D("bs") << "File cached and has exact samples."; + } + }); + properties = bs.GetVideoProperties(); + } + + br->Run([&](agi::ProgressSink *ps) { + ps->SetTitle(from_wx(_("Scanning"))); + ps->SetMessage(from_wx(_("Finding Keyframes and Timecodes..."))); + + std::vector TimecodesVector; + for (int n = 0; n < properties.NumFrames; n++) { + if (ps->IsCancelled()) { + return; + } + std::unique_ptr frame(bs.GetFrame(n)); + if (frame == nullptr) { + throw VideoOpenError("Couldn't read frame!"); + } + + if (frame->GetAVFrame()->key_frame) { + Keyframes.push_back(n); + } + + TimecodesVector.push_back((int) frame->GetAVFrame()->pts); + ps->SetProgress(n, properties.NumFrames); + } + + if (TimecodesVector.size() < 2 || TimecodesVector.front() == TimecodesVector.back()) { + Timecodes = (double) properties.FPS.Num / properties.FPS.Den; + } else { + Timecodes = agi::vfr::Framerate(TimecodesVector); + } + }); +} +catch (VideoException const& err) { + throw VideoOpenError("Failed to create BestVideoSource"); +} + +std::string BSVideoProvider::GetCacheFile(agi::fs::path const& filename) { + // BS can store all its index data in a single file, but we make a separate index file + // for each video file to ensure that the old index is invalidated if the file is modified. + // While BS does check the filesize of the files, it doesn't check the modification time. + uintmax_t len = agi::fs::Size(filename); + boost::crc_32_type hash; + hash.process_bytes(filename.string().c_str(), filename.string().size()); + + auto result = config::path->Decode("?local/bsindex/" + filename.filename().string() + "_" + std::to_string(hash.checksum()) + "_" + std::to_string(len) + "_" + std::to_string(agi::fs::ModifiedTime(filename)) + ".json"); + agi::fs::CreateDirectory(result.parent_path()); + + return result.string(); +} + +void BSVideoProvider::GetFrame(int n, VideoFrame &out) { + std::unique_ptr bsframe(bs.GetFrame(n)); + if (bsframe == nullptr) { + throw VideoDecodeError("Couldn't read frame!"); + } + const AVFrame *frame = bsframe->GetAVFrame(); + AVFrame *newframe = av_frame_alloc(); + + SwsContext *context = sws_getContext( + frame->width, frame->height, (AVPixelFormat) frame->format, // TODO figure out aegi's color space forcing. + frame->width, frame->height, AV_PIX_FMT_BGR0, + SWS_BICUBIC, nullptr, nullptr, nullptr); + + if (context == nullptr) { + throw VideoDecodeError("Couldn't convert frame!"); + } + + sws_scale_frame(context, newframe, frame); + + out.width = newframe->width; + out.height = newframe->height; + out.pitch = newframe->width * 4; + out.flipped = false; // TODO figure out flipped + + out.data.assign(newframe->data[0], newframe->data[0] + newframe->linesize[0] * newframe->height); + + sws_freeContext(context); + av_frame_free(&newframe); +} + +} + +std::unique_ptr CreateBSVideoProvider(agi::fs::path const& path, std::string const& colormatrix, agi::BackgroundRunner *br) { + return agi::make_unique(path, colormatrix, br); +} + +#endif /* WITH_BESTSOURCE */ diff --git a/src/video_provider_manager.cpp b/src/video_provider_manager.cpp index 3739a7293..8ec7c18fc 100644 --- a/src/video_provider_manager.cpp +++ b/src/video_provider_manager.cpp @@ -29,6 +29,7 @@ std::unique_ptr CreateDummyVideoProvider(agi::fs::path const&, st std::unique_ptr CreateYUV4MPEGVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); std::unique_ptr CreateFFmpegSourceVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); std::unique_ptr CreateAvisynthVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); +std::unique_ptr CreateBSVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); std::unique_ptr CreateCacheVideoProvider(std::unique_ptr); @@ -47,6 +48,9 @@ namespace { #endif #ifdef WITH_AVISYNTH {"Avisynth", CreateAvisynthVideoProvider, false}, +#endif +#ifdef WITH_BESTSOURCE + {"BestSource", CreateBSVideoProvider, false}, #endif }; } diff --git a/subprojects/bestsource.wrap b/subprojects/bestsource.wrap new file mode 100644 index 000000000..fa0e5ef45 --- /dev/null +++ b/subprojects/bestsource.wrap @@ -0,0 +1,7 @@ +[wrap-git] +url = https://github.com/vapoursynth/bestsource +revision = head +patch_directory = bestsource + +[provide] +bestsource = bestsource_dep diff --git a/subprojects/packagefiles/bestsource/libp2p/p2p_api.h b/subprojects/packagefiles/bestsource/libp2p/p2p_api.h new file mode 100644 index 000000000..f66166538 --- /dev/null +++ b/subprojects/packagefiles/bestsource/libp2p/p2p_api.h @@ -0,0 +1,156 @@ +// Since we don't use ExportAsPlanar, we don't actually need libp2p. +// So instead of adding another wrap and meson build file, and *also* +// patching the include statement in videosource.cpp, we throw a dummy +// header file in the folder that should contain the checkout of libp2p. + +#ifndef P2P_API_H_ +#define P2P_API_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Notation: [Xa-Ya-Za] + * + * [] denotes a machine word of the specified endianness. Xa-Ya-Za denote + * component X, Y, and Z packed in the word, with bit depths a, b, c, in order + * from MSB to LSB. Padding bits are represented by the component '!'. + */ +enum p2p_packing { + /** [R8-G8-B8] */ + p2p_rgb24_be, /* RGB */ + p2p_rgb24_le, /* BGR */ + p2p_rgb24, + /** [A8-R8-G8-B8] */ + p2p_argb32_be, /* ARGB */ + p2p_argb32_le, /* BGRA */ + p2p_argb32, + /** [A8-Y8-U8-V8] */ + p2p_ayuv_be, /* AYUV */ + p2p_ayuv_le, /* VUYA */ + p2p_ayuv, + /** [R16-G16-B16] */ + p2p_rgb48_be, /* RGB, big-endian components */ + p2p_rgb48_le, /* BGR, little-endian components */ + p2p_rgb48, + /** [A16-R16-G16-B16] */ + p2p_argb64_be, /* ARGB big-endian components */ + p2p_argb64_le, /* BGRA little-endian components */ + p2p_argb64, + /** [A2-R10-G10-B10] */ + p2p_rgb30_be, /* ARGB packed in big-endian DWORD */ + p2p_rgb30_le, /* ARGB packed in little-endian DWORD */ + p2p_rgb30, + /** [A2-V10-Y10-U10] */ + p2p_y410_be, /* AVYU packed in big-endian DWORD */ + p2p_y410_le, /* AVYU packed in little-endian DWORD */ + p2p_y410, + /** [A16-V16-Y16-U16] */ + p2p_y416_be, /* AVYU, big-endian components */ + p2p_y416_le, /* UYVA, little-endian components */ + p2p_y416, + /** [Y8] [U8] [Y8] [V8] */ + p2p_yuy2, + /** [U8] [Y8] [V8] [Y8] */ + p2p_uyvy, + /** [Y10-!6] [U10-!6] [Y10-!6] [V10-!6] */ + p2p_y210_be, /* YUYV, big-endian components, lower 6 bits zero */ + p2p_y210_le, /* YUYV, little-endian components, lower 6 bits zero. Microsoft Y210. */ + p2p_y210, + /** [Y16] [U16] [Y16] [V16] */ + p2p_y216_be, /* YUYV, big-endian components */ + p2p_y216_le, /* YUYV, little-endian components. Microsoft Y216. */ + p2p_y216, + /** [!2-V10-Y10-U10] [!2-Y10-U10-Y10] [!2-U10-Y10-V10] [!2-Y10-V10-Y10] */ + p2p_v210_be, /* v210 with big-endian DWORDs */ + p2p_v210_le, /* Apple/QuickTime v210 */ + p2p_v210, + /** [U16] [Y16] [V16] [Y16] */ + p2p_v216_be, /* UYVY, big-endian components */ + p2p_v216_le, /* UYVY, little-endian components. Apple/QuickTime v216. */ + p2p_v216, + /** [U8-V8] */ + p2p_nv12_be, /* aka NV21, V first */ + p2p_nv12_le, /* NV12 */ + p2p_nv12, + /** [U10-!6-V10-!6] */ + p2p_p010_be, /* NV21, big-endian components, lower 6 bits zero */ + p2p_p010_le, /* NV12, little-endian components, lower 6 bits zero. Microsoft P010. */ + p2p_p010, + /** [U16-V16] */ + p2p_p016_be, /* NV21, big-endian components */ + p2p_p016_le, /* NV12, little-endian components. Microsoft P016. */ + p2p_p016, + /** [U10-!6-V10-!6] */ + p2p_p210_be, /* NV21, big-endian components, lower 6 bits zero */ + p2p_p210_le, /* NV12, little-endian components, lower 6 bits zero. Microsoft P210. */ + p2p_p210, + /** [U16-V16] */ + p2p_p216_be, /* NV21, big-endian components */ + p2p_p216_le, /* NV12, little-endian components. Microsoft P216. */ + p2p_p216, + /** [R8-G8-B8-A8] */ + p2p_rgba32_be, /* RGBA */ + p2p_rgba32_le, /* ABGR */ + p2p_rgba32, + /** [R16-G16-B16-A16] */ + p2p_rgba64_be, /* RGBA, big-endian components */ + p2p_rgba64_le, /* ABGR, little-endian components */ + p2p_rgba64, + /** [A16-B16-G16-R16] */ + p2p_abgr64_be, /* ABGR, big-endian components */ + p2p_abgr64_le, /* RGBA, little-endian components */ + p2p_abgr64, + /** [B16-G16-R16] */ + p2p_bgr48_be, /* BGR, big-endian components */ + p2p_bgr48_le, /* RGB, little-endian components */ + p2p_bgr48, + /** [B16-G16-R16-A16] */ + p2p_bgra64_be, /* BGRA, big-endian components */ + p2p_bgra64_le, /* ARGB, little-endian components */ + p2p_bgra64, + + p2p_packing_max, +}; + +struct p2p_buffer_param { + /** + * Planar order: R-G-B-A or Y-U-V-A. Alpha is optional. + * Packed order: Y-UV if NV12/21, else single plane. Y optional for NV12/21. + */ + const void *src[4]; + void *dst[4]; + ptrdiff_t src_stride[4]; + ptrdiff_t dst_stride[4]; + unsigned width; + unsigned height; + enum p2p_packing packing; +}; + +/** Pack/unpack a range of pixels from a scanline. */ +typedef void (*p2p_unpack_func)(const void *src, void * const dst[4], unsigned left, unsigned right); +typedef void (*p2p_pack_func)(const void * const src[4], void *dst, unsigned left, unsigned right); + +/** Select a line pack/unpack function. */ +p2p_unpack_func p2p_select_unpack_func(enum p2p_packing packing); +p2p_pack_func p2p_select_pack_func(enum p2p_packing packing); +p2p_pack_func p2p_select_pack_func_ex(enum p2p_packing packing, int alpha_one_fill); + + +/** When processing formats like NV12, ignore the unpacked plane. */ +#define P2P_SKIP_UNPACKED_PLANES (1UL << 0) +/** When packing, store a bit pattern of all ones in the alpha channel instead of all zeros. */ +#define P2P_ALPHA_SET_ONE (1UL << 1) + +/** Helper function to pack/unpack between memory locations. */ +void p2p_unpack_frame(const struct p2p_buffer_param *param, unsigned long flags) {}; +void p2p_pack_frame(const struct p2p_buffer_param *param, unsigned long flags) {}; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* P2P_API_H_ */ diff --git a/subprojects/packagefiles/bestsource/meson.build b/subprojects/packagefiles/bestsource/meson.build new file mode 100644 index 000000000..559e93a27 --- /dev/null +++ b/subprojects/packagefiles/bestsource/meson.build @@ -0,0 +1,28 @@ +project('BestSource', 'cpp', + default_options: ['buildtype=release', 'b_ndebug=if-release', 'cpp_std=c++14'], + meson_version: '>=0.48.0' +) + +libs = [] + +sources = [ + 'src/audiosource.cpp', + 'src/videosource.cpp', + 'src/SrcAttribCache.cpp', + 'src/BSRational.cpp', +] + +deps = [ + dependency('jansson', version: '>= 2.7', required: true), + dependency('libavcodec'), + dependency('libavformat'), + dependency('libavutil'), +] + +bs_lib = static_library('bestsource', sources, + dependencies: deps, + gnu_symbol_visibility: 'hidden' +) + +bestsource_dep = declare_dependency(link_with: bs_lib, include_directories: include_directories('src')) + From e4261faf73346a9bd26015413a7e64094ddf552e Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Mon, 8 Aug 2022 03:32:29 +0200 Subject: [PATCH 09/19] bestsource: Add jannson wrap --- .gitignore | 1 + subprojects/jansson.wrap | 4 ++++ subprojects/packagefiles/bestsource/meson.build | 12 ++++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 subprojects/jansson.wrap diff --git a/.gitignore b/.gitignore index fd239df2d..76f92ff5c 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ subprojects/glib* subprojects/googletest-* subprojects/harfbuzz subprojects/icu +subprojects/jansson subprojects/libass subprojects/libffi* subprojects/libpng-* diff --git a/subprojects/jansson.wrap b/subprojects/jansson.wrap new file mode 100644 index 000000000..51ff9a82b --- /dev/null +++ b/subprojects/jansson.wrap @@ -0,0 +1,4 @@ +[wrap-git] +directory = jansson +url = https://github.com/akheron/jansson.git +revision = v2.14 diff --git a/subprojects/packagefiles/bestsource/meson.build b/subprojects/packagefiles/bestsource/meson.build index 559e93a27..b4f77c93f 100644 --- a/subprojects/packagefiles/bestsource/meson.build +++ b/subprojects/packagefiles/bestsource/meson.build @@ -3,7 +3,7 @@ project('BestSource', 'cpp', meson_version: '>=0.48.0' ) -libs = [] +cmake = import('cmake') sources = [ 'src/audiosource.cpp', @@ -13,12 +13,20 @@ sources = [ ] deps = [ - dependency('jansson', version: '>= 2.7', required: true), dependency('libavcodec'), dependency('libavformat'), dependency('libavutil'), ] +jansson_dep = dependency('jansson', version: '>= 2.7', required: false) + +if jansson_dep.found() + deps += jansson_dep +else + jansson = cmake.subproject('jansson') + deps += jansson.dependency('jansson') +endif + bs_lib = static_library('bestsource', sources, dependencies: deps, gnu_symbol_visibility: 'hidden' From 4c6e3527d46a6be3edfffa01a3cf644f015ac39f Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Mon, 8 Aug 2022 03:32:33 +0200 Subject: [PATCH 10/19] bestsource: Switch to manual sws_scale This was to work with ffmpeg 4.4, but it also saves one frame copy operation. It does, however, lose the slice threading. --- src/video_provider_bestsource.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/video_provider_bestsource.cpp b/src/video_provider_bestsource.cpp index 8bf8786d5..61784bf36 100644 --- a/src/video_provider_bestsource.cpp +++ b/src/video_provider_bestsource.cpp @@ -155,7 +155,6 @@ void BSVideoProvider::GetFrame(int n, VideoFrame &out) { throw VideoDecodeError("Couldn't read frame!"); } const AVFrame *frame = bsframe->GetAVFrame(); - AVFrame *newframe = av_frame_alloc(); SwsContext *context = sws_getContext( frame->width, frame->height, (AVPixelFormat) frame->format, // TODO figure out aegi's color space forcing. @@ -166,17 +165,17 @@ void BSVideoProvider::GetFrame(int n, VideoFrame &out) { throw VideoDecodeError("Couldn't convert frame!"); } - sws_scale_frame(context, newframe, frame); + out.data.resize(frame->width * frame->height * 4); + uint8_t *data[1] = {&out.data[0]}; + int stride[1] = {frame->width * 4}; + sws_scale(context, frame->data, frame->linesize, 0, frame->height, data, stride); - out.width = newframe->width; - out.height = newframe->height; - out.pitch = newframe->width * 4; + out.width = frame->width; + out.height = frame->height; + out.pitch = stride[0]; out.flipped = false; // TODO figure out flipped - out.data.assign(newframe->data[0], newframe->data[0] + newframe->linesize[0] * newframe->height); - sws_freeContext(context); - av_frame_free(&newframe); } } From e41647c78ca3fdc4679814d9f104e936f7e35417 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Tue, 9 Aug 2022 04:01:02 +0200 Subject: [PATCH 11/19] bestsource: Detect audio and color space --- src/video_provider_bestsource.cpp | 42 ++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/video_provider_bestsource.cpp b/src/video_provider_bestsource.cpp index 61784bf36..7cea1e147 100644 --- a/src/video_provider_bestsource.cpp +++ b/src/video_provider_bestsource.cpp @@ -23,6 +23,7 @@ #include "include/aegisub/video_provider.h" #include "videosource.h" +#include "audiosource.h" #include "BSRational.h" extern "C" { @@ -57,6 +58,8 @@ class BSVideoProvider final : public VideoProvider { std::vector Keyframes; agi::vfr::Framerate Timecodes; + std::string colorspace; + bool has_audio = false; std::string GetCacheFile(agi::fs::path const& filename); @@ -65,7 +68,7 @@ public: void GetFrame(int n, VideoFrame &out) override; - void SetColorSpace(std::string const& matrix) override { } + void SetColorSpace(std::string const& matrix) override { } // TODO Follow Aegisub's colorspace forcing? int GetFrameCount() const override { return properties.NumFrames; }; @@ -74,18 +77,45 @@ public: double GetDAR() const override { return ((double) properties.Width * properties.SAR.Num) / (properties.Height * properties.SAR.Den); }; agi::vfr::Framerate GetFPS() const override { return Timecodes; }; - std::string GetColorSpace() const override { return "TV.709"; }; // TODO - std::string GetRealColorSpace() const override { return "TV.709"; }; + std::string GetColorSpace() const override { return colorspace; }; + std::string GetRealColorSpace() const override { return colorspace; }; std::vector GetKeyFrames() const override { return Keyframes; }; std::string GetDecoderName() const override { return "BestSource"; }; bool WantsCaching() const override { return false; }; - bool HasAudio() const override { return false; }; + bool HasAudio() const override { return has_audio; }; }; +// Match the logic from the ffms2 provider, but directly use libavutil's constants and don't abort when encountering an unknown color space +std::string colormatrix_description(const AVFrame *frame) { + // Assuming TV for unspecified + std::string str = frame->color_range == AVCOL_RANGE_JPEG ? "PC" : "TV"; + LOG_D("bestsource") << frame->colorspace; + + switch (frame->colorspace) { + case AVCOL_SPC_BT709: + return str + ".709"; + case AVCOL_SPC_FCC: + return str + ".FCC"; + case AVCOL_SPC_BT470BG: + case AVCOL_SPC_SMPTE170M: + return str + ".601"; + case AVCOL_SPC_SMPTE240M: + return str + ".240M"; + default: + return "None"; + } +} + BSVideoProvider::BSVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br) try : bsopts() , bs(filename.string(), "", -1, false, 0, GetCacheFile(filename), &bsopts) { + try { + BestAudioSource dummysource(filename.string(), -1, 0, ""); + has_audio = true; + } catch (AudioException const& err) { + has_audio = false; + } properties = bs.GetVideoProperties(); @@ -130,6 +160,10 @@ BSVideoProvider::BSVideoProvider(agi::fs::path const& filename, std::string cons Timecodes = agi::vfr::Framerate(TimecodesVector); } }); + + // Decode the first frame to get the color space + std::unique_ptr frame(bs.GetFrame(0)); + colorspace = colormatrix_description(frame->GetAVFrame()); } catch (VideoException const& err) { throw VideoOpenError("Failed to create BestVideoSource"); From 70fd08aabe303f3072db7201c2fb4aecbe068f44 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Wed, 10 Aug 2022 02:40:02 +0200 Subject: [PATCH 12/19] bestsource: Expose some options --- src/libresrc/default_config.json | 5 +++++ src/libresrc/osx/default_config.json | 5 +++++ src/preferences.cpp | 7 +++++++ src/video_provider_bestsource.cpp | 4 +++- 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/libresrc/default_config.json b/src/libresrc/default_config.json index 318f8d3ee..aeac73b4a 100644 --- a/src/libresrc/default_config.json +++ b/src/libresrc/default_config.json @@ -353,6 +353,11 @@ "FFmpegSource" : { "Decoding Threads" : -1, "Unsafe Seeking" : false + }, + "BestSource" : { + "Max Cache Size" : 1024, + "Threads" : 0, + "Seek Preroll" : 12 } } }, diff --git a/src/libresrc/osx/default_config.json b/src/libresrc/osx/default_config.json index 59f2ed05f..a774470fc 100644 --- a/src/libresrc/osx/default_config.json +++ b/src/libresrc/osx/default_config.json @@ -353,6 +353,11 @@ "FFmpegSource" : { "Decoding Threads" : -1, "Unsafe Seeking" : false + }, + "BestSource" : { + "Max Cache Size" : 1024, + "Threads" : 0, + "Seek Preroll" : 12 } } }, diff --git a/src/preferences.cpp b/src/preferences.cpp index f6320dd75..e0d064c03 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -452,6 +452,13 @@ void Advanced_Video(wxTreebook *book, Preferences *parent) { p->OptionAdd(ffms, _("Enable unsafe seeking"), "Provider/Video/FFmpegSource/Unsafe Seeking"); #endif +#ifdef WITH_BESTSOURCE + auto bs = p->PageSizer("BestSource"); + p->OptionAdd(bs, _("Max cache size (MB)"), "Provider/Video/BestSource/Max Cache Size"); + p->OptionAdd(bs, _("Decoder Threads (0 to autodetect)"), "Provider/Video/BestSource/Threads"); + p->OptionAdd(bs, _("Seek preroll (Frames)"), "Provider/Video/BestSource/Seek Preroll"); +#endif + p->SetSizerAndFit(p->sizer); } diff --git a/src/video_provider_bestsource.cpp b/src/video_provider_bestsource.cpp index 7cea1e147..c6143a0ec 100644 --- a/src/video_provider_bestsource.cpp +++ b/src/video_provider_bestsource.cpp @@ -108,8 +108,10 @@ std::string colormatrix_description(const AVFrame *frame) { BSVideoProvider::BSVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br) try : bsopts() -, bs(filename.string(), "", -1, false, 0, GetCacheFile(filename), &bsopts) +, bs(filename.string(), "", -1, false, OPT_GET("Provider/Video/BestSource/Threads")->GetInt(), GetCacheFile(filename), &bsopts) { + bs.SetMaxCacheSize(OPT_GET("Provider/Video/BestSource/Max Cache Size")->GetInt() << 20); + bs.SetSeekPreRoll(OPT_GET("Provider/Video/BestSource/Seek Preroll")->GetInt()); try { BestAudioSource dummysource(filename.string(), -1, 0, ""); has_audio = true; From 6af0b41f4749bdafd3aa67aab0194e629aef326e Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Thu, 11 Aug 2022 02:05:16 +0200 Subject: [PATCH 13/19] bestsource: Add audio provider --- meson.build | 1 + src/audio_provider_bestsource.cpp | 118 +++++++++++++++++++++++++++ src/audio_provider_factory.cpp | 4 + src/bestsource_common.cpp | 49 +++++++++++ src/bestsource_common.h | 28 +++++++ src/libresrc/default_config.json | 4 + src/libresrc/osx/default_config.json | 4 + src/meson.build | 7 +- src/preferences.cpp | 7 ++ src/video_provider_bestsource.cpp | 23 +----- 10 files changed, 220 insertions(+), 25 deletions(-) create mode 100644 src/audio_provider_bestsource.cpp create mode 100644 src/bestsource_common.cpp create mode 100644 src/bestsource_common.h diff --git a/meson.build b/meson.build index 6115d1329..21293e1b9 100644 --- a/meson.build +++ b/meson.build @@ -226,6 +226,7 @@ if get_option('bestsource').enabled() conf.set('WITH_BESTSOURCE', 1) bs = subproject('bestsource') deps += bs.get_variable('bestsource_dep') + dep_avail += 'BestSource' needs_ffmpeg = true endif diff --git a/src/audio_provider_bestsource.cpp b/src/audio_provider_bestsource.cpp new file mode 100644 index 000000000..7229df827 --- /dev/null +++ b/src/audio_provider_bestsource.cpp @@ -0,0 +1,118 @@ +// 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_bestsource.cpp +/// @brief BS-based audio provider +/// @ingroup audio_input bestsource +/// + +#ifdef WITH_BESTSOURCE +#include + +#include "audiosource.h" + +#include "bestsource_common.h" +#include "compat.h" +#include "options.h" + +#include +#include +#include +#include + +#include + +namespace { +class BSAudioProvider final : public agi::AudioProvider { + std::map bsopts; + BestAudioSource bs; + AudioProperties properties; + + void FillBuffer(void *Buf, int64_t Start, int64_t Count) const override; +public: + BSAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *br); + + bool NeedsCache() const override { return OPT_GET("Provider/Audio/BestSource/Aegisub Cache")->GetBool(); } +}; + +/// @brief Constructor +/// @param filename The filename to open +BSAudioProvider::BSAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *br) try +: bsopts() +, bs(filename.string(), -1, -1, GetBSCacheFile(filename), &bsopts) +{ + bs.SetMaxCacheSize(OPT_GET("Provider/Audio/BestSource/Max Cache Size")->GetInt() << 20); + br->Run([&](agi::ProgressSink *ps) { + ps->SetTitle(from_wx(_("Exacting"))); + ps->SetMessage(from_wx(_("Creating cache... This can take a while!"))); + ps->SetIndeterminate(); + if (bs.GetExactDuration()) { + LOG_D("bs") << "File cached and has exact samples."; + } + }); + properties = bs.GetAudioProperties(); + float_samples = properties.IsFloat; + bytes_per_sample = properties.BytesPerSample; + sample_rate = properties.SampleRate; + channels = properties.Channels; + num_samples = properties.NumSamples; + decoded_samples = OPT_GET("Provider/Audio/BestSource/Aegisub Cache")->GetBool() ? 0 : num_samples; +} +catch (AudioException const& err) { + throw agi::AudioProviderError("Failed to create BestAudioSource"); +} + +// Taken from BestSource code and reversed +template +static void PackChannels(const uint8_t *Src, void *Dst, size_t Length, size_t Channels) { + const T *S = reinterpret_cast(Src); + T *D = reinterpret_cast(Dst); + for (size_t i = 0; i < Length; i++) { + for (size_t c = 0; c < Channels; c++) + D[c] = S[Length * c]; + S += 1; + D += Channels; + } +} + +void BSAudioProvider::FillBuffer(void *Buf, int64_t Start, int64_t Count) const { + // BS unpacked the channels, so until it gets a feature to disable that, let's just + // pack them in the same way they were unpacked + std::vector unpacked_buf(channels * bytes_per_sample * Count); + std::vector bufs(channels); + for (int i = 0; i < channels; i++) { + bufs[i] = unpacked_buf.data() + i * bytes_per_sample * Count; + } + const_cast(bs).GetAudio(bufs.data(), Start, Count); + + if (bytes_per_sample == 1) + PackChannels(unpacked_buf.data(), Buf, Count, channels); + else if (bytes_per_sample == 2) + PackChannels(unpacked_buf.data(), Buf, Count, channels); + else if (bytes_per_sample == 4) + PackChannels(unpacked_buf.data(), Buf, Count, channels); + else if (bytes_per_sample == 8) + PackChannels(unpacked_buf.data(), Buf, Count, channels); +} + +} + +std::unique_ptr CreateBSAudioProvider(agi::fs::path const& file, agi::BackgroundRunner *br) { + return agi::make_unique(file, br); +} + +#endif /* WITH_BESTSOURCE */ + diff --git a/src/audio_provider_factory.cpp b/src/audio_provider_factory.cpp index 887783644..d0508aa6f 100644 --- a/src/audio_provider_factory.cpp +++ b/src/audio_provider_factory.cpp @@ -31,6 +31,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 *); namespace { struct factory { @@ -48,6 +49,9 @@ const factory providers[] = { #ifdef WITH_AVISYNTH {"Avisynth", CreateAvisynthAudioProvider, false}, #endif +#ifdef WITH_BESTSOURCE + {"BestSource", CreateBSAudioProvider, false}, +#endif }; } diff --git a/src/bestsource_common.cpp b/src/bestsource_common.cpp new file mode 100644 index 000000000..69d627015 --- /dev/null +++ b/src/bestsource_common.cpp @@ -0,0 +1,49 @@ +// 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 ffmpegsource_common.cpp +/// @brief Shared code for ffms video and audio providers +/// @ingroup video_input audio_input ffms +/// + +#ifdef WITH_BESTSOURCE +#include "bestsource_common.h" + +#include "options.h" + +#include +#include + +#include +#include + + +std::string GetBSCacheFile(agi::fs::path const& filename) { + // BS can store all its index data in a single file, but we make a separate index file + // for each video file to ensure that the old index is invalidated if the file is modified. + // While BS does check the filesize of the files, it doesn't check the modification time. + uintmax_t len = agi::fs::Size(filename); + boost::crc_32_type hash; + hash.process_bytes(filename.string().c_str(), filename.string().size()); + + auto result = config::path->Decode("?local/bsindex/" + filename.filename().string() + "_" + std::to_string(hash.checksum()) + "_" + std::to_string(len) + "_" + std::to_string(agi::fs::ModifiedTime(filename)) + ".json"); + agi::fs::CreateDirectory(result.parent_path()); + + return result.string(); +} + + +#endif // WITH_BESTSOURCE diff --git a/src/bestsource_common.h b/src/bestsource_common.h new file mode 100644 index 000000000..b93ccc815 --- /dev/null +++ b/src/bestsource_common.h @@ -0,0 +1,28 @@ +// 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 ffmpegsource_common.h +/// @see ffmpegsource_common.cpp +/// @ingroup video_input audio_input ffms +/// + +#ifdef WITH_BESTSOURCE + +#include + +std::string GetBSCacheFile(agi::fs::path const& filename); + +#endif /* WITH_BESTSOURCE */ diff --git a/src/libresrc/default_config.json b/src/libresrc/default_config.json index aeac73b4a..a530abb17 100644 --- a/src/libresrc/default_config.json +++ b/src/libresrc/default_config.json @@ -332,6 +332,10 @@ }, "FFmpegSource" : { "Decode Error Handling" : "ignore" + }, + "BestSource": { + "Max Cache Size" : 100, + "Aegisub Cache" : true } }, "Avisynth" : { diff --git a/src/libresrc/osx/default_config.json b/src/libresrc/osx/default_config.json index a774470fc..a857a6bf0 100644 --- a/src/libresrc/osx/default_config.json +++ b/src/libresrc/osx/default_config.json @@ -332,6 +332,10 @@ }, "FFmpegSource" : { "Decode Error Handling" : "ignore" + }, + "BestSource": { + "Max Cache Size" : 100, + "Aegisub Cache" : true } }, "Avisynth" : { diff --git a/src/meson.build b/src/meson.build index 2d5145dbf..51a5753cd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -224,10 +224,6 @@ if conf.has('WITH_CSRI') aegisub_src += files('subtitles_provider_csri.cpp') endif -if conf.has('WITH_BESTSOURCE') - aegisub_src += files('video_provider_bestsource.cpp') -endif - opt_src = [ ['ALSA', 'audio_player_alsa.cpp'], ['PortAudio', 'audio_player_portaudio.cpp'], @@ -240,6 +236,9 @@ opt_src = [ ['FFMS2', ['audio_provider_ffmpegsource.cpp', 'video_provider_ffmpegsource.cpp', 'ffmpegsource_common.cpp']], + ['BestSource', ['audio_provider_bestsource.cpp', + 'video_provider_bestsource.cpp', + 'bestsource_common.cpp']], ['Hunspell', 'spellchecker_hunspell.cpp'], ] diff --git a/src/preferences.cpp b/src/preferences.cpp index e0d064c03..1945055e0 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -403,6 +403,13 @@ void Advanced_Audio(wxTreebook *book, Preferences *parent) { p->OptionAdd(ffms, _("Always index all audio tracks"), "Provider/FFmpegSource/Index All Tracks"); #endif +#ifdef WITH_BESTSOURCE + auto bs = p->PageSizer("BestSource"); + p->OptionAdd(bs, _("Max BS cache size (MB)"), "Provider/Audio/BestSource/Max Cache Size"); + p->OptionAdd(bs, _("Use Aegisub's Cache"), "Provider/Audio/BestSource/Aegisub Cache"); +#endif + + #ifdef WITH_PORTAUDIO auto portaudio = p->PageSizer("Portaudio"); p->OptionChoice(portaudio, _("Portaudio device"), PortAudioPlayer::GetOutputDevices(), "Player/Audio/PortAudio/Device Name"); diff --git a/src/video_provider_bestsource.cpp b/src/video_provider_bestsource.cpp index c6143a0ec..3b5e0834c 100644 --- a/src/video_provider_bestsource.cpp +++ b/src/video_provider_bestsource.cpp @@ -32,7 +32,7 @@ extern "C" { #include } -/* #include "utils.h" */ +#include "bestsource_common.h" #include "options.h" #include "compat.h" #include "video_frame.h" @@ -44,9 +44,6 @@ namespace agi { class BackgroundRunner; } #include #include -#include -#include - namespace { /// @class BSVideoProvider @@ -61,8 +58,6 @@ class BSVideoProvider final : public VideoProvider { std::string colorspace; bool has_audio = false; - std::string GetCacheFile(agi::fs::path const& filename); - public: BSVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br); @@ -108,7 +103,7 @@ std::string colormatrix_description(const AVFrame *frame) { BSVideoProvider::BSVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br) try : bsopts() -, bs(filename.string(), "", -1, false, OPT_GET("Provider/Video/BestSource/Threads")->GetInt(), GetCacheFile(filename), &bsopts) +, bs(filename.string(), "", -1, false, OPT_GET("Provider/Video/BestSource/Threads")->GetInt(), GetBSCacheFile(filename), &bsopts) { bs.SetMaxCacheSize(OPT_GET("Provider/Video/BestSource/Max Cache Size")->GetInt() << 20); bs.SetSeekPreRoll(OPT_GET("Provider/Video/BestSource/Seek Preroll")->GetInt()); @@ -171,20 +166,6 @@ catch (VideoException const& err) { throw VideoOpenError("Failed to create BestVideoSource"); } -std::string BSVideoProvider::GetCacheFile(agi::fs::path const& filename) { - // BS can store all its index data in a single file, but we make a separate index file - // for each video file to ensure that the old index is invalidated if the file is modified. - // While BS does check the filesize of the files, it doesn't check the modification time. - uintmax_t len = agi::fs::Size(filename); - boost::crc_32_type hash; - hash.process_bytes(filename.string().c_str(), filename.string().size()); - - auto result = config::path->Decode("?local/bsindex/" + filename.filename().string() + "_" + std::to_string(hash.checksum()) + "_" + std::to_string(len) + "_" + std::to_string(agi::fs::ModifiedTime(filename)) + ".json"); - agi::fs::CreateDirectory(result.parent_path()); - - return result.string(); -} - void BSVideoProvider::GetFrame(int n, VideoFrame &out) { std::unique_ptr bsframe(bs.GetFrame(n)); if (bsframe == nullptr) { From 4a5a212f35cc0a38c129c67b2507f857ca6e7054 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Mon, 15 Aug 2022 03:04:07 +0200 Subject: [PATCH 14/19] bestsource: Switch to GetPackedAudio() After vapoursynth/bestsource#7 , BestSource now has a function to get the packed audio instead, so we just use that directly. --- src/audio_provider_bestsource.cpp | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/src/audio_provider_bestsource.cpp b/src/audio_provider_bestsource.cpp index 7229df827..5390788dc 100644 --- a/src/audio_provider_bestsource.cpp +++ b/src/audio_provider_bestsource.cpp @@ -75,37 +75,8 @@ catch (AudioException const& err) { throw agi::AudioProviderError("Failed to create BestAudioSource"); } -// Taken from BestSource code and reversed -template -static void PackChannels(const uint8_t *Src, void *Dst, size_t Length, size_t Channels) { - const T *S = reinterpret_cast(Src); - T *D = reinterpret_cast(Dst); - for (size_t i = 0; i < Length; i++) { - for (size_t c = 0; c < Channels; c++) - D[c] = S[Length * c]; - S += 1; - D += Channels; - } -} - void BSAudioProvider::FillBuffer(void *Buf, int64_t Start, int64_t Count) const { - // BS unpacked the channels, so until it gets a feature to disable that, let's just - // pack them in the same way they were unpacked - std::vector unpacked_buf(channels * bytes_per_sample * Count); - std::vector bufs(channels); - for (int i = 0; i < channels; i++) { - bufs[i] = unpacked_buf.data() + i * bytes_per_sample * Count; - } - const_cast(bs).GetAudio(bufs.data(), Start, Count); - - if (bytes_per_sample == 1) - PackChannels(unpacked_buf.data(), Buf, Count, channels); - else if (bytes_per_sample == 2) - PackChannels(unpacked_buf.data(), Buf, Count, channels); - else if (bytes_per_sample == 4) - PackChannels(unpacked_buf.data(), Buf, Count, channels); - else if (bytes_per_sample == 8) - PackChannels(unpacked_buf.data(), Buf, Count, channels); + const_cast(bs).GetPackedAudio(reinterpret_cast(Buf), Start, Count); } } From 3f298bf03abf6f1b977c3ab9595be1630defbff1 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Tue, 16 Aug 2022 15:49:29 +0200 Subject: [PATCH 15/19] Add vapoursynth video provider --- .gitignore | 1 + meson.build | 7 + meson_options.txt | 1 + .../win_installer/fragment_associations.iss | 2 + src/command/video.cpp | 2 +- src/meson.build | 2 + src/vapoursynth_wrap.cpp | 105 +++++++ src/vapoursynth_wrap.h | 42 +++ src/video_provider_manager.cpp | 4 + src/video_provider_vs.cpp | 282 ++++++++++++++++++ .../packagefiles/vapoursynth/meson.build | 3 + subprojects/vapoursynth.wrap | 4 + 12 files changed, 454 insertions(+), 1 deletion(-) create mode 100644 src/vapoursynth_wrap.cpp create mode 100644 src/vapoursynth_wrap.h create mode 100644 src/video_provider_vs.cpp create mode 100644 subprojects/packagefiles/vapoursynth/meson.build create mode 100644 subprojects/vapoursynth.wrap diff --git a/.gitignore b/.gitignore index 76f92ff5c..f0468aa12 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ subprojects/zlib-* subprojects/dirent-* subprojects/hunspell-* subprojects/uchardet-* +subprojects/vapoursynth diff --git a/meson.build b/meson.build index 21293e1b9..ae78bda05 100644 --- a/meson.build +++ b/meson.build @@ -242,6 +242,13 @@ if host_machine.system() == 'windows' and get_option('avisynth').enabled() conf.set('WITH_AVISYNTH', 1) # bundled separately with installer endif +if get_option('vapoursynth').enabled() + conf.set('WITH_VAPOURSYNTH', 1) + vs_sub = subproject('vapoursynth') + deps_inc += vs_sub.get_variable('vs_inc') + dep_avail += 'VapourSynth' +endif + if host_machine.system() == 'windows' and not get_option('directsound').disabled() dsound_dep = cc.find_library('dsound', required: get_option('directsound')) winmm_dep = cc.find_library('winmm', required: get_option('directsound')) diff --git a/meson_options.txt b/meson_options.txt index 19c447af1..8be9ca584 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -8,6 +8,7 @@ option('default_audio_output', type: 'combo', choices: ['auto', 'ALSA', 'OpenAL' option('ffms2', type: 'feature', description: 'FFMS2 video source') option('avisynth', type: 'feature', description: 'AviSynth video source') option('bestsource', type: 'feature', description: 'BestSource video source') +option('vapoursynth', type: 'feature', description: 'VapourSynth video source') option('fftw3', type: 'feature', description: 'FFTW3 support') option('hunspell', type: 'feature', description: 'Hunspell spell checker') diff --git a/packages/win_installer/fragment_associations.iss b/packages/win_installer/fragment_associations.iss index c766011da..ec33599b9 100644 --- a/packages/win_installer/fragment_associations.iss +++ b/packages/win_installer/fragment_associations.iss @@ -29,6 +29,7 @@ Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".wav"; ValueData: ""; Flags: uninsdeletekey Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".ogg"; ValueData: ""; Flags: uninsdeletekey Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".avs"; ValueData: ""; Flags: uninsdeletekey +Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".vpy"; ValueData: ""; Flags: uninsdeletekey Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".opus"; ValueData: ""; Flags: uninsdeletekey Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".h264"; ValueData: ""; Flags: uninsdeletekey Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub.exe\SupportedTypes"; ValueType: string; ValueName: ".hevc"; ValueData: ""; Flags: uninsdeletekey @@ -165,6 +166,7 @@ Root: HKLM; Subkey: "SOFTWARE\Classes\.m4a\OpenWithProgids"; ValueType: string; Root: HKLM; Subkey: "SOFTWARE\Classes\.wav\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Audio.1"; Flags: uninsdeletevalue Root: HKLM; Subkey: "SOFTWARE\Classes\.ogg\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Media.1"; Flags: uninsdeletevalue Root: HKLM; Subkey: "SOFTWARE\Classes\.avs\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Video.1"; Flags: uninsdeletevalue +Root: HKLM; Subkey: "SOFTWARE\Classes\.vpy\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Media.1"; Flags: uninsdeletevalue Root: HKLM; Subkey: "SOFTWARE\Classes\.opus\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Audio.1"; Flags: uninsdeletevalue Root: HKLM; Subkey: "SOFTWARE\Classes\.h264\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Video.1"; Flags: uninsdeletevalue Root: HKLM; Subkey: "SOFTWARE\Classes\.hevc\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Video.1"; Flags: uninsdeletevalue diff --git a/src/command/video.cpp b/src/command/video.cpp index d3ffaf7fe..589bae337 100644 --- a/src/command/video.cpp +++ b/src/command/video.cpp @@ -564,7 +564,7 @@ struct video_open final : public Command { STR_HELP("Open a video file") void operator()(agi::Context *c) override { - auto str = from_wx(_("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.h264,*.hevc,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts,*.y4m,*.yuv)|*.asf;*.avi;*.avs;*.d2v;*.h264;*.hevc;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts;*.y4m;*.yuv|" + auto str = from_wx(_("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.h264,*.hevc,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts,*.vpy,*.y4m,*.yuv)|*.asf;*.avi;*.avs;*.d2v;*.h264;*.hevc;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts;*.vpy;*.y4m;*.yuv|" + _("All Files") + " (*.*)|*.*"); auto filename = OpenFileSelector(_("Open video file"), "Path/Last/Video", "", "", str, c->parent); if (!filename.empty()) diff --git a/src/meson.build b/src/meson.build index 51a5753cd..a9e2ebcf2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -239,6 +239,8 @@ opt_src = [ ['BestSource', ['audio_provider_bestsource.cpp', 'video_provider_bestsource.cpp', 'bestsource_common.cpp']], + ['VapourSynth', ['vapoursynth_wrap.cpp', + 'video_provider_vs.cpp']], ['Hunspell', 'spellchecker_hunspell.cpp'], ] diff --git a/src/vapoursynth_wrap.cpp b/src/vapoursynth_wrap.cpp new file mode 100644 index 000000000..d0d0fbd8b --- /dev/null +++ b/src/vapoursynth_wrap.cpp @@ -0,0 +1,105 @@ +// 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 vapoursynth_wrap.cpp +/// @brief Wrapper-layer for Vapoursynth +/// @ingroup video_input audio_input +/// + +#ifdef WITH_VAPOURSYNTH +#include "vapoursynth_wrap.h" + +#include "VSScript4.h" + +#include "options.h" + +#include + +#ifndef _WIN32 +#include +#endif + +#ifdef _WIN32 +#define VSSCRIPT_SO "vsscript.dll" +#else +#define VSSCRIPT_SO "libvapoursynth-script.so" +#endif + +// Allocate storage for and initialise static members +namespace { + bool vs_loaded = false; +#ifdef _WIN32 + HINSTANCE hLib = nullptr; +#else + void* hLib = nullptr; +#endif + const VSAPI *api = nullptr; + VSSCRIPTAPI *scriptapi = nullptr; + std::mutex VapourSynthMutex; +} + +typedef VSSCRIPTAPI* VS_CC FUNC(int); + +VapourSynthWrapper::VapourSynthWrapper() { + // VSScript assumes it's only loaded once, so unlike AVS we can't unload it when the refcount reaches zero + if (!vs_loaded) { + vs_loaded = true; +#ifdef _WIN32 +#define CONCATENATE(x, y) x ## y +#define _Lstr(x) CONCATENATE(L, x) + hLib = LoadLibraryW(_Lstr(VSSCRIPT_SO)); +#undef _Lstr +#undef CONCATENATE +#else + hLib = dlopen(VSSCRIPT_SO, RTLD_LAZY | RTLD_GLOBAL | RTLD_DEEPBIND); +#endif + + if (!hLib) + throw VapoursynthError("Could not load " VSSCRIPT_SO); + +#ifdef _WIN32 + FUNC* getVSScriptAPI = (FUNC*)GetProcAddress(hLib, "getVSScriptAPI"); +#else + FUNC* getVSScriptAPI = (FUNC*)dlsym(hLib, "getVSScriptAPI"); +#endif + if (!getVSScriptAPI) + throw VapoursynthError("Failed to get address of getVSScriptAPI from " VSSCRIPT_SO); + + scriptapi = getVSScriptAPI(VSSCRIPT_API_VERSION); + + if (!scriptapi) + throw VapoursynthError("Failed to get Vapoursynth ScriptAPI"); + + api = scriptapi->getVSAPI(VAPOURSYNTH_API_VERSION); + + if (!api) + throw VapoursynthError("Failed to get Vapoursynth API"); + } +} + +std::mutex& VapourSynthWrapper::GetMutex() const { + return VapourSynthMutex; +} + +const VSAPI *VapourSynthWrapper::GetAPI() const { + return api; +} + +const VSSCRIPTAPI *VapourSynthWrapper::GetScriptAPI() const { + return scriptapi; +} + +#endif diff --git a/src/vapoursynth_wrap.h b/src/vapoursynth_wrap.h new file mode 100644 index 000000000..802cb3d58 --- /dev/null +++ b/src/vapoursynth_wrap.h @@ -0,0 +1,42 @@ +// 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 vapoursynth_wrap.h +/// @see vapoursynth_wrap.cpp +/// @ingroup video_input audio_input +/// + +#ifdef WITH_VAPOURSYNTH + +#include + +DEFINE_EXCEPTION(VapoursynthError, agi::Exception); + +struct VSAPI; +struct VSSCRIPTAPI; +namespace std { class mutex; } + +class VapourSynthWrapper { + VapourSynthWrapper(VapourSynthWrapper const&); +public: + std::mutex& GetMutex() const; + const VSAPI *GetAPI() const; + const VSSCRIPTAPI *GetScriptAPI() const; + + VapourSynthWrapper(); +}; + +#endif diff --git a/src/video_provider_manager.cpp b/src/video_provider_manager.cpp index 8ec7c18fc..c239163b8 100644 --- a/src/video_provider_manager.cpp +++ b/src/video_provider_manager.cpp @@ -30,6 +30,7 @@ std::unique_ptr CreateYUV4MPEGVideoProvider(agi::fs::path const&, std::unique_ptr CreateFFmpegSourceVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); std::unique_ptr CreateAvisynthVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); std::unique_ptr CreateBSVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); +std::unique_ptr CreateVapoursynthVideoProvider(agi::fs::path const&, std::string const&, agi::BackgroundRunner *); std::unique_ptr CreateCacheVideoProvider(std::unique_ptr); @@ -51,6 +52,9 @@ namespace { #endif #ifdef WITH_BESTSOURCE {"BestSource", CreateBSVideoProvider, false}, +#endif +#ifdef WITH_VAPOURSYNTH + {"Vapoursynth", CreateVapoursynthVideoProvider, false}, #endif }; } diff --git a/src/video_provider_vs.cpp b/src/video_provider_vs.cpp new file mode 100644 index 000000000..3f9839e38 --- /dev/null +++ b/src/video_provider_vs.cpp @@ -0,0 +1,282 @@ +// 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/ + +#ifdef WITH_VAPOURSYNTH +#include "include/aegisub/video_provider.h" + +#include "options.h" +#include "video_frame.h" + +#include +#include +#include +#include +#include + +#include + +#include "vapoursynth_wrap.h" +#include "VSScript4.h" +#include "VSHelper4.h" +#include "VSConstants4.h" + +namespace { +class VapoursynthVideoProvider: public VideoProvider { + VapourSynthWrapper vs; + VSScript *script = nullptr; + VSNode *node = nullptr; + const VSVideoInfo *vi = nullptr; + + double dar = 0; + agi::vfr::Framerate fps; + std::vector keyframes; + std::string colorspace; + std::string real_colorspace; + + const VSFrame *GetVSFrame(int n); + void SetResizeArg(VSMap *args, const VSMap *props, const char *arg_name, const char *prop_name, int deflt, int unspecified = -1); + +public: + VapoursynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix); + ~VapoursynthVideoProvider(); + + void GetFrame(int n, VideoFrame &frame) override; + + void SetColorSpace(std::string const& matrix) override { } + + int GetFrameCount() const override { return vi->numFrames; } + agi::vfr::Framerate GetFPS() const override { return fps; } + int GetWidth() const override { return vi->width; } + int GetHeight() const override { return vi->height; } + double GetDAR() const override { return dar; } + std::vector GetKeyFrames() const override { return keyframes; } + std::string GetColorSpace() const override { return colorspace; } + std::string GetRealColorSpace() const override { return colorspace; } + bool HasAudio() const override { return false; } + virtual bool WantsCaching() const override { return true; } + virtual std::string GetDecoderName() const override { return "VapourSynth"; } +}; + +std::string colormatrix_description(int colorFamily, int colorRange, int matrix) { + if (colorFamily != cfYUV) { + return "None"; + } + // Assuming TV for unspecified + std::string str = colorRange == VSC_RANGE_FULL ? "PC" : "TV"; + + switch (matrix) { + case VSC_MATRIX_RGB: + return "None"; + case VSC_MATRIX_BT709: + return str + ".709"; + case VSC_MATRIX_FCC: + return str + ".FCC"; + case VSC_MATRIX_BT470_BG: + case VSC_MATRIX_ST170_M: + return str + ".601"; + case VSC_MATRIX_ST240_M: + return str + ".240M"; + default: + return "None"; + } +} + +// Adds an argument to the rescaler if the corresponding frameprop does not exist or is set as unspecified +void VapoursynthVideoProvider::SetResizeArg(VSMap *args, const VSMap *props, const char *arg_name, const char *prop_name, int deflt, int unspecified) { + int err; + int result = vs.GetAPI()->mapGetInt(props, prop_name, 0, &err); + if (err != 0 || result == unspecified) { + result = deflt; + vs.GetAPI()->mapSetInt(args, arg_name, result, maAppend); + } +} + +VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix) 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) != mtVideo) { + vs.GetAPI()->freeNode(node); + vs.GetScriptAPI()->freeScript(script); + throw VapoursynthError("Output node isn't a video node"); + } + vi = vs.GetAPI()->getVideoInfo(node); + if (!vsh::isConstantVideoFormat(vi)) { + vs.GetAPI()->freeNode(node); + vs.GetScriptAPI()->freeScript(script); + throw VapoursynthError("Video doesn't have constant format"); + } + + // Assume constant frame rate, since handling VFR would require going through all frames when loading. + // Users can load custom timecodes files to deal with VFR. + // Alternatively (TODO) the provider could read timecodes and keyframes from a second output node. + fps = (double) vi->fpsNum / vi->fpsDen; + + // Find the first frame to get some info + const VSFrame *frame; + try { + frame = GetVSFrame(0); + } catch (VapoursynthError const& err) { + vs.GetAPI()->freeNode(node); + vs.GetScriptAPI()->freeScript(script); + throw err; + } + int err1, err2; + const VSMap *props = vs.GetAPI()->getFramePropertiesRO(frame); + int sarn = vs.GetAPI()->mapGetInt(props, "_SARNum", 0, &err1); + int sard = vs.GetAPI()->mapGetInt(props, "_SARDen", 0, &err2); + if (!err1 && !err2) { + dar = ((double) vi->width * sarn) / (vi->height * sard); + } + + int range = vs.GetAPI()->mapGetInt(props, "_ColorRange", 0, &err1); + int matrix = vs.GetAPI()->mapGetInt(props, "_Matrix", 0, &err2); + colorspace = colormatrix_description(vi->format.colorFamily, err1 == 0 ? range : -1, err2 == 0 ? matrix : -1); + + vs.GetAPI()->freeFrame(frame); + + if (vi->format.colorFamily != cfRGB || vi->format.bitsPerSample != 8) { + // Convert to RGB24 format + VSPlugin *resize = vs.GetAPI()->getPluginByID(VSH_RESIZE_PLUGIN_ID, vs.GetScriptAPI()->getCore(script)); + if (resize == nullptr) { + throw VapoursynthError("Couldn't find resize plugin"); + } + VSMap *args = vs.GetAPI()->createMap(); + if (args == nullptr) { + throw VapoursynthError("Failed to create argument map"); + } + + vs.GetAPI()->mapSetNode(args, "clip", node, maAppend); + vs.GetAPI()->mapSetInt(args, "format", pfRGB24, maAppend); + if (vi->format.colorFamily != cfGray) + SetResizeArg(args, props, "matrix_in", "_Matrix", VSC_MATRIX_BT709, VSC_MATRIX_UNSPECIFIED); + SetResizeArg(args, props, "transfer_in", "_Transfer", VSC_TRANSFER_BT709, VSC_TRANSFER_UNSPECIFIED); + SetResizeArg(args, props, "primaries_in", "_Primaries", VSC_PRIMARIES_BT709, VSC_PRIMARIES_UNSPECIFIED); + SetResizeArg(args, props, "range_in", "_ColorRange", VSC_RANGE_LIMITED); + SetResizeArg(args, props, "chromaloc_in", "_ChromaLocation", VSC_CHROMA_LEFT); + + VSMap *result = vs.GetAPI()->invoke(resize, "Bicubic", args); + vs.GetAPI()->freeMap(args); + const char *error = vs.GetAPI()->mapGetError(result); + if (error) { + vs.GetAPI()->freeMap(result); + vs.GetAPI()->freeNode(node); + vs.GetScriptAPI()->freeScript(script); + throw VideoProviderError(agi::format("Failed to convert to RGB24: %s", error)); + } + int err; + vs.GetAPI()->freeNode(node); + node = vs.GetAPI()->mapGetNode(result, "clip", 0, &err); + vs.GetAPI()->freeMap(result); + if (err) { + vs.GetScriptAPI()->freeScript(script); + throw VideoProviderError("Failed to get resize output node"); + } + + // Finally, try to get the first frame again, so if the filter does crash, it happens before loading finishes + const VSFrame *rgbframe; + try { + rgbframe = GetVSFrame(0); + } catch (VapoursynthError const& err) { + vs.GetAPI()->freeNode(node); + vs.GetScriptAPI()->freeScript(script); + throw err; + } + vs.GetAPI()->freeFrame(rgbframe); + } +} +catch (VapoursynthError const& err) { + throw VideoProviderError(agi::format("Vapoursynth error: %s", err.GetMessage())); +} + +const VSFrame *VapoursynthVideoProvider::GetVSFrame(int n) { + 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)); + } + return frame; +} + +void VapoursynthVideoProvider::GetFrame(int n, VideoFrame &out) { + std::lock_guard lock(vs.GetMutex()); + + const VSFrame *frame = GetVSFrame(n); + + const VSVideoFormat *format = vs.GetAPI()->getVideoFrameFormat(frame); + if (format->colorFamily != cfRGB || format->numPlanes != 3 || format->bitsPerSample != 8 || format->subSamplingH != 0 || format->subSamplingW != 0) { + throw VapoursynthError("Frame not in RGB24 format"); + } + + out.width = vs.GetAPI()->getFrameWidth(frame, 0); + out.height = vs.GetAPI()->getFrameHeight(frame, 0); + out.pitch = out.width * 4; + out.flipped = false; + + out.data.resize(out.pitch * out.height); + + for (int p = 0; p < format->numPlanes; p++) { + ptrdiff_t stride = vs.GetAPI()->getStride(frame, p); + const uint8_t *readPtr = vs.GetAPI()->getReadPtr(frame, p); + uint8_t *writePtr = &out.data[2 - p]; + int rows = vs.GetAPI()->getFrameHeight(frame, p); + int cols = vs.GetAPI()->getFrameWidth(frame, p); + + for (int row = 0; row < rows; row++) { + const uint8_t *rowPtr = readPtr; + uint8_t *rowWritePtr = writePtr; + for (int col = 0; col < cols; col++) { + *rowWritePtr = *rowPtr++; + rowWritePtr += 4; + } + readPtr += stride; + writePtr += out.pitch; + } + } + + vs.GetAPI()->freeFrame(frame); +} + +VapoursynthVideoProvider::~VapoursynthVideoProvider() { + if (node != nullptr) { + vs.GetAPI()->freeNode(node); + } + if (script != nullptr) { + vs.GetScriptAPI()->freeScript(script); + } +} +} + +namespace agi { class BackgroundRunner; } +std::unique_ptr CreateVapoursynthVideoProvider(agi::fs::path const& path, std::string const& colormatrix, agi::BackgroundRunner *) { + return agi::make_unique(path, colormatrix); +} +#endif // HAVE_VAPOURSYNTH diff --git a/subprojects/packagefiles/vapoursynth/meson.build b/subprojects/packagefiles/vapoursynth/meson.build new file mode 100644 index 000000000..5dfef44f7 --- /dev/null +++ b/subprojects/packagefiles/vapoursynth/meson.build @@ -0,0 +1,3 @@ +project('vapoursynth', 'cpp') + +vs_inc = include_directories('include') diff --git a/subprojects/vapoursynth.wrap b/subprojects/vapoursynth.wrap new file mode 100644 index 000000000..592446ccf --- /dev/null +++ b/subprojects/vapoursynth.wrap @@ -0,0 +1,4 @@ +[wrap-git] +url = https://github.com/Vapoursynth/vapoursynth.git +revision = R59 +patch_directory = vapoursynth From ff43f3d60118c2b41095f9c6d830493da9a0ac91 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Tue, 16 Aug 2022 18:55:59 +0200 Subject: [PATCH 16/19] 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'], From 6cdf360e766c5929b107e13ef28f002067231897 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Tue, 16 Aug 2022 19:58:11 +0200 Subject: [PATCH 17/19] Add downmix option on OSX This fixes wangqr's commit dabd9f that adds an option to enable or disable downmixing --- src/libresrc/osx/default_config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libresrc/osx/default_config.json b/src/libresrc/osx/default_config.json index 59f2ed05f..3fff450e9 100644 --- a/src/libresrc/osx/default_config.json +++ b/src/libresrc/osx/default_config.json @@ -331,7 +331,8 @@ "Sample Rate" : 0 }, "FFmpegSource" : { - "Decode Error Handling" : "ignore" + "Decode Error Handling" : "ignore", + "Downmix" : false } }, "Avisynth" : { From afbd626e40a5c0190a0a7e6d310c696bb9e676b5 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Tue, 16 Aug 2022 20:54:49 +0200 Subject: [PATCH 18/19] README: New audio+video interfaces --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 478ee14b8..95a453820 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,13 @@ The `cibuilds` branch makes some CI builds of snapshots of `feature` at relevant - [`lua_api`](https://github.com/arch1t3cht/Aegisub/tree/lua_api): Add new functions to the Lua automation API, like controlling the selection or cursor in the text edit box - [`vector_clip_actions`](https://github.com/arch1t3cht/Aegisub/tree/vector_clip_actions): Make the different modes of the vector clip tool (lines, bezier curves, adding points, etc) bindable to hotkeys - [`color_picker_fix2`](https://github.com/arch1t3cht/Aegisub/tree/color_picker_fix2): Add an option (under "Interface") to restrict the color picker to the window, which fixes the color picker on Linux in a lot of cases. -- [`avisynth`](https://github.com/arch1t3cht/Aegisub/tree/avisynth): Reenable Avisynth support on Windows (Still occasionally crashes) -- [`bugfixes`](https://github.com/arch1t3cht/Aegisub/tree/bugfixes): Various fixes, mostly relevant for compilation +- [`avisynth`](https://github.com/arch1t3cht/Aegisub/tree/avisynth): Reenable Avisynth support on Windows and enable Avisynth on Linux +- [`bestsource`](https://github.com/arch1t3cht/Aegisub/tree/bestsource): Add BestSource audio and video source. This source is slower than others by multiple orders of magnitude, but in exchange it can guarantee exact seeking. +- [`vapoursynth`](https://github.com/arch1t3cht/Aegisub/tree/vapourssynth): Add Vapoursynth audio and video source +- [`bugfixes`](https://github.com/arch1t3cht/Aegisub/tree/bugfixes): Various fixes necessary for compilation. Most branches are based on this. +- [`fixes`](https://github.com/arch1t3cht/Aegisub/tree/fixes): Miscellaneous bugfixes - [`misc_dc`](https://github.com/arch1t3cht/Aegisub/tree/misc_dc): Miscellaneous changes taken from AegisubDC +- [`xa-ds2`](https://github.com/arch1t3cht/Aegisub/tree/xa-ds2): Add XAudio2 backend and allow stereo playback for some other backends, by wangqr and Shinon. - [`video_panning_feature`](https://github.com/arch1t3cht/Aegisub/tree/video_panning_feature): Merge [moex3's video zoom and panning](https://github.com/TypesettingTools/Aegisub/pull/150), with an OSX fix and more options to control zoom behavior - [`spectrum-frequency-mapping`](https://github.com/arch1t3cht/Aegisub/tree/spectrum-frequency-mapping): Merge EleonoreMizo's [spectrum display improvements](https://github.com/TypesettingTools/Aegisub/pull/94), and also make Shift+Scroll vertically zoom the audio display - [`wangqr_time_video`](https://github.com/arch1t3cht/Aegisub/tree/wangqr_time_video): Merge wangqr's feature adding a tool for timing subtitles to changes in the video @@ -43,7 +47,9 @@ The exact way of switching depends on your Linux distribution, but essentially y The changes to `default_config.json` or similar files weren't detected by meson due to missing regen dependencies. You can either merge the `bugfixes` branch or rebuild from scratch. #### The video is desynced / Frames don't appear at the right time -This is probably due to the ffms2 seeking bug ([#394](https://github.com/FFMS/ffms2/issues/394)). On Windows, this shouldn't happen anymore. On Linux, you need to install the latest git version of ffms2 - for example the [`ffms2-git`](https://aur.archlinux.org/packages/ffms2-git) AUR package on Arch linux, or just compile it yourself. +This is probably due to the ffms2 seeking bug ([#394](https://github.com/FFMS/ffms2/issues/394)). On Windows, this specific regression shouldn't happen anymore. On Linux, you need to install the latest git version of ffms2 - for example the [`ffms2-git`](https://aur.archlinux.org/packages/ffms2-git) AUR package on Arch linux, or just compile it yourself. + +If it's not because of this particular bug, you can also try an alternative video source like LSMASHSource via Avisynth or Vapoursynth, or BestSource. #### On Windows: Aegisub crashes whenever I open a video If you're compiling yourself, try adding `--force-fallback-for=zlib` to the meson options. From 0b8f5ad8fa0b75c341e128e5b9569e536b998284 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Tue, 16 Aug 2022 20:34:00 +0200 Subject: [PATCH 19/19] XAudio PR: Revert some changes and fix on linux Mostly synchronizing with the updates made to the ffmpeg audio provider and removing the CMakeLists.txt --- CMakeLists.txt | 542 ------------------ .../include/libaegisub/audio/provider.h | 3 +- meson.build | 3 - src/audio_player_dsound2.cpp | 1 - src/audio_provider_ffmpegsource.cpp | 13 +- 5 files changed, 7 insertions(+), 555 deletions(-) delete mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 75182bbe8..000000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,542 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -cmake_policy(SET CMP0074 NEW) - -project(Aegisub) - -set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) - -include_directories("build") -include_directories("libaegisub/include") -include_directories("vendor/luajit/include") - -add_library(libaegisub STATIC - libaegisub/common/parser.cpp - libaegisub/ass/dialogue_parser.cpp - libaegisub/ass/time.cpp - libaegisub/ass/uuencode.cpp - libaegisub/audio/provider.cpp - libaegisub/audio/provider_convert.cpp - libaegisub/audio/provider_dummy.cpp - libaegisub/audio/provider_hd.cpp - libaegisub/audio/provider_lock.cpp - libaegisub/audio/provider_pcm.cpp - libaegisub/audio/provider_ram.cpp - libaegisub/common/cajun/elements.cpp - libaegisub/common/cajun/reader.cpp - libaegisub/common/cajun/writer.cpp - libaegisub/lua/modules/lfs.cpp - libaegisub/lua/modules/re.cpp - libaegisub/lua/modules/unicode.cpp - libaegisub/lua/modules/lpeg.c - libaegisub/lua/modules.cpp - libaegisub/lua/script_reader.cpp - libaegisub/lua/utils.cpp - libaegisub/common/calltip_provider.cpp - libaegisub/common/character_count.cpp - libaegisub/common/charset.cpp - libaegisub/common/charset_6937.cpp - libaegisub/common/charset_conv.cpp - libaegisub/common/color.cpp - libaegisub/common/file_mapping.cpp - libaegisub/common/format.cpp - libaegisub/common/fs.cpp - libaegisub/common/hotkey.cpp - libaegisub/common/io.cpp - libaegisub/common/json.cpp - libaegisub/common/kana_table.cpp - libaegisub/common/karaoke_matcher.cpp - libaegisub/common/keyframe.cpp - libaegisub/common/line_iterator.cpp - libaegisub/common/log.cpp - libaegisub/common/mru.cpp - libaegisub/common/option.cpp - libaegisub/common/option_value.cpp - libaegisub/common/path.cpp - libaegisub/common/thesaurus.cpp - libaegisub/common/util.cpp - libaegisub/common/vfr.cpp - libaegisub/common/ycbcr_conv.cpp - libaegisub/common/dispatch.cpp -) -if (UNIX) - target_sources(libaegisub PRIVATE - libaegisub/unix/access.cpp - libaegisub/unix/fs.cpp - libaegisub/unix/log.cpp - libaegisub/unix/path.cpp - libaegisub/unix/util.cpp - ) -elseif(WIN32) - target_sources(libaegisub PRIVATE - libaegisub/windows/access.cpp - libaegisub/windows/charset_conv_win.cpp - libaegisub/windows/fs.cpp - libaegisub/windows/lagi_pre.cpp - libaegisub/windows/log_win.cpp - libaegisub/windows/path_win.cpp - libaegisub/windows/util_win.cpp - ) -endif(UNIX) -SET_TARGET_PROPERTIES(libaegisub PROPERTIES PREFIX "") - -add_library(luabins STATIC - vendor/luabins/src/fwrite.c - vendor/luabins/src/load.c - vendor/luabins/src/luabins.c - vendor/luabins/src/luainternals.c - vendor/luabins/src/save.c - vendor/luabins/src/savebuffer.c - vendor/luabins/src/write.c -) - -add_executable(luajit-minilua vendor/luajit/src/host/minilua.c) -if (NOT MSVC) -target_link_libraries(luajit-minilua m) -endif(NOT MSVC) -add_custom_command(TARGET luajit-minilua POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen - COMMAND luajit-minilua ../dynasm/dynasm.lua -D P64 -D JIT -D FFI -D FPU -D HFABI -D VER= -o gen/buildvm_arch.h vm_x86.dasc - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src -) -add_custom_command(TARGET luajit-minilua POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/src/libresrc/default_config_win.json ${PROJECT_SOURCE_DIR}/src/libresrc/default_config_platform.json - COMMAND luajit-minilua ../../tools/respack.lua manifest.respack default_config.cpp default_config.h - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/libresrc - BYPRODUCTS ${PROJECT_SOURCE_DIR}/src/libresrc/default_config.cpp ${PROJECT_SOURCE_DIR}/src/libresrc/default_config.h -) -add_custom_command(TARGET luajit-minilua POST_BUILD - COMMAND luajit-minilua ../../tools/respack.lua manifest.respack ../libresrc/bitmap.cpp ../libresrc/bitmap.h - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/bitmaps - BYPRODUCTS ${PROJECT_SOURCE_DIR}/src/libresrc/bitmap.cpp ${PROJECT_SOURCE_DIR}/src/libresrc/bitmap.h -) - -add_executable(luajit-buildvm - vendor/luajit/src/host/buildvm.c - vendor/luajit/src/host/buildvm_asm.c - vendor/luajit/src/host/buildvm_peobj.c - vendor/luajit/src/host/buildvm_lib.c - vendor/luajit/src/host/buildvm_fold.c -) -target_include_directories(luajit-buildvm PRIVATE vendor/luajit/src vendor/luajit/src/gen) -add_dependencies(luajit-buildvm luajit-minilua) -if(UNIX) - add_custom_command(TARGET luajit-buildvm POST_BUILD - COMMAND luajit-buildvm -m elfasm -o lj_vm.s - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src - BYPRODUCTS ${PROJECT_SOURCE_DIR}/vendor/luajit/src/lj_vm.s - ) - set_property(SOURCE vendor/luajit/src/lj_vm.s PROPERTY LANGUAGE C) -elseif(MSVC) - add_custom_command(TARGET luajit-buildvm POST_BUILD - COMMAND luajit-buildvm -m peobj -o lj_vm.obj - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src - BYPRODUCTS ${PROJECT_SOURCE_DIR}/vendor/luajit/src/lj_vm.obj - ) -endif(UNIX) -add_custom_command(TARGET luajit-buildvm POST_BUILD - COMMAND luajit-buildvm -m ffdef -o gen/lj_ffdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c - COMMAND luajit-buildvm -m bcdef -o gen/lj_bcdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c - COMMAND luajit-buildvm -m folddef -o gen/lj_folddef.h lj_opt_fold.c - COMMAND luajit-buildvm -m recdef -o gen/lj_recdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c - COMMAND luajit-buildvm -m libdef -o gen/lj_libdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c - COMMAND luajit-buildvm -m vmdef -o jit/vmdef.lua lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src -) - - -add_library(luajit STATIC - vendor/luajit/src/lj_gc.c - vendor/luajit/src/lj_err.c - vendor/luajit/src/lj_char.c - vendor/luajit/src/lj_bc.c - vendor/luajit/src/lj_obj.c - vendor/luajit/src/lj_str.c - vendor/luajit/src/lj_tab.c - vendor/luajit/src/lj_func.c - vendor/luajit/src/lj_udata.c - vendor/luajit/src/lj_meta.c - vendor/luajit/src/lj_debug.c - vendor/luajit/src/lj_state.c - vendor/luajit/src/lj_dispatch.c - vendor/luajit/src/lj_vmevent.c - vendor/luajit/src/lj_vmmath.c - vendor/luajit/src/lj_strscan.c - vendor/luajit/src/lj_api.c - vendor/luajit/src/lj_lex.c - vendor/luajit/src/lj_parse.c - vendor/luajit/src/lj_bcread.c - vendor/luajit/src/lj_bcwrite.c - vendor/luajit/src/lj_load.c - vendor/luajit/src/lj_ir.c - vendor/luajit/src/lj_opt_mem.c - vendor/luajit/src/lj_opt_fold.c - vendor/luajit/src/lj_opt_narrow.c - vendor/luajit/src/lj_opt_dce.c - vendor/luajit/src/lj_opt_loop.c - vendor/luajit/src/lj_opt_split.c - vendor/luajit/src/lj_opt_sink.c - vendor/luajit/src/lj_mcode.c - vendor/luajit/src/lj_snap.c - vendor/luajit/src/lj_record.c - vendor/luajit/src/lj_crecord.c - vendor/luajit/src/lj_ffrecord.c - vendor/luajit/src/lj_asm.c - vendor/luajit/src/lj_trace.c - vendor/luajit/src/lj_gdbjit.c - vendor/luajit/src/lj_ctype.c - vendor/luajit/src/lj_cdata.c - vendor/luajit/src/lj_cconv.c - vendor/luajit/src/lj_ccall.c - vendor/luajit/src/lj_ccallback.c - vendor/luajit/src/lj_carith.c - vendor/luajit/src/lj_clib.c - vendor/luajit/src/lj_cparse.c - vendor/luajit/src/lj_lib.c - vendor/luajit/src/lj_alloc.c - vendor/luajit/src/lib_aux.c - vendor/luajit/src/lib_base.c - vendor/luajit/src/lib_math.c - vendor/luajit/src/lib_bit.c - vendor/luajit/src/lib_string.c - vendor/luajit/src/lib_table.c - vendor/luajit/src/lib_io.c - vendor/luajit/src/lib_os.c - vendor/luajit/src/lib_package.c - vendor/luajit/src/lib_debug.c - vendor/luajit/src/lib_jit.c - vendor/luajit/src/lib_ffi.c - vendor/luajit/src/lib_init.c -) -if(MSVC) - target_sources(luajit PRIVATE vendor/luajit/src/lj_vm.obj) -else(MSVC) - target_sources(luajit PRIVATE vendor/luajit/src/lj_vm.s) - set_property(SOURCE vendor/luajit/src/lj_vm.s PROPERTY LANGUAGE C) - target_link_libraries(luajit dl) -endif(MSVC) -target_include_directories(luajit PRIVATE vendor/luajit/src/gen) -add_dependencies(luajit luajit-buildvm) -target_compile_definitions(luajit PRIVATE LUAJIT_ENABLE_LUA52COMPAT) - -add_library(resrc STATIC - src/libresrc/bitmap.cpp - src/libresrc/default_config.cpp - src/libresrc/libresrc.cpp -) -add_dependencies(resrc luajit-minilua) - -add_library(csri STATIC - vendor/csri/lib/list.c - vendor/csri/lib/wrap.c - vendor/csri/subhelp/logging.c -) -target_include_directories(csri PRIVATE "vendor/csri/include") -IF (WIN32) - target_include_directories(csri PRIVATE "vendor/csri/lib/win32") - target_sources(csri PRIVATE vendor/csri/lib/win32/enumerate.c) -ELSE() - target_include_directories(csri PRIVATE "vendor/csri/lib/posix") - target_sources(csri PRIVATE vendor/csri/lib/posix/enumerate.c) -ENDIF() - -add_executable(Aegisub WIN32 - src/command/app.cpp - src/command/audio.cpp - src/command/automation.cpp - src/command/command.cpp - src/command/edit.cpp - src/command/grid.cpp - src/command/help.cpp - src/command/keyframe.cpp - src/command/recent.cpp - src/command/subtitle.cpp - src/command/time.cpp - src/command/timecode.cpp - src/command/tool.cpp - src/command/video.cpp - src/command/vis_tool.cpp - src/dialog_about.cpp - src/dialog_align.cpp - src/dialog_attachments.cpp - src/dialog_automation.cpp - src/dialog_autosave.cpp - src/dialog_colorpicker.cpp - src/dialog_detached_video.cpp - src/dialog_dummy_video.cpp - src/dialog_export.cpp - src/dialog_export_ebu3264.cpp - src/dialog_fonts_collector.cpp - src/dialog_jumpto.cpp - src/dialog_kara_timing_copy.cpp - src/dialog_log.cpp - src/dialog_paste_over.cpp - src/dialog_progress.cpp - src/dialog_properties.cpp - src/dialog_resample.cpp - src/dialog_search_replace.cpp - src/dialog_selected_choices.cpp - src/dialog_selection.cpp - src/dialog_shift_times.cpp - src/dialog_spellchecker.cpp - src/dialog_style_editor.cpp - src/dialog_style_manager.cpp - src/dialog_styling_assistant.cpp - src/dialog_text_import.cpp - src/dialog_timing_processor.cpp - src/dialog_translation.cpp - src/dialog_version_check.cpp - src/dialog_video_details.cpp - src/dialog_video_properties.cpp - src/subtitle_format.cpp - src/subtitle_format_ass.cpp - src/subtitle_format_ebu3264.cpp - src/subtitle_format_encore.cpp - src/subtitle_format_microdvd.cpp - src/subtitle_format_mkv.cpp - src/subtitle_format_srt.cpp - src/subtitle_format_ssa.cpp - src/subtitle_format_transtation.cpp - src/subtitle_format_ttxt.cpp - src/subtitle_format_txt.cpp - src/visual_tool.cpp - src/visual_tool_clip.cpp - src/visual_tool_cross.cpp - src/visual_tool_drag.cpp - src/visual_tool_rotatexy.cpp - src/visual_tool_rotatez.cpp - src/visual_tool_scale.cpp - src/visual_tool_vector_clip.cpp - src/MatroskaParser.c - src/aegisublocale.cpp - src/ass_attachment.cpp - src/ass_dialogue.cpp - src/ass_entry.cpp - src/ass_export_filter.cpp - src/ass_exporter.cpp - src/ass_file.cpp - src/ass_karaoke.cpp - src/ass_override.cpp - src/ass_parser.cpp - src/ass_style.cpp - src/ass_style_storage.cpp - src/async_video_provider.cpp - src/audio_box.cpp - src/audio_colorscheme.cpp - src/audio_controller.cpp - src/audio_display.cpp - src/audio_karaoke.cpp - src/audio_marker.cpp - src/audio_player.cpp - src/audio_provider_factory.cpp - src/audio_renderer.cpp - src/audio_renderer_spectrum.cpp - src/audio_renderer_waveform.cpp - src/audio_timing_dialogue.cpp - src/audio_timing_karaoke.cpp - src/auto4_base.cpp - src/auto4_lua.cpp - src/auto4_lua_assfile.cpp - src/auto4_lua_dialog.cpp - src/auto4_lua_progresssink.cpp - src/base_grid.cpp - src/charset_detect.cpp - src/colorspace.cpp - src/colour_button.cpp - src/compat.cpp - src/context.cpp - src/export_fixstyle.cpp - src/export_framerate.cpp - src/fft.cpp - src/font_file_lister.cpp - src/frame_main.cpp - src/gl_text.cpp - src/gl_wrap.cpp - src/grid_column.cpp - src/help_button.cpp - src/hotkey.cpp - src/hotkey_data_view_model.cpp - src/image_position_picker.cpp - src/initial_line_state.cpp - src/main.cpp - src/menu.cpp - src/mkv_wrap.cpp - src/pen.cpp - src/persist_location.cpp - src/preferences.cpp - src/preferences_base.cpp - src/project.cpp - src/resolution_resampler.cpp - src/search_replace_engine.cpp - src/selection_controller.cpp - src/spellchecker.cpp - src/spline.cpp - src/spline_curve.cpp - src/string_codec.cpp - src/subs_controller.cpp - src/subs_edit_box.cpp - src/subs_edit_ctrl.cpp - src/subs_preview.cpp - src/subtitles_provider.cpp - src/subtitles_provider_libass.cpp - src/text_file_reader.cpp - src/text_file_writer.cpp - src/text_selection_controller.cpp - src/thesaurus.cpp - src/timeedit_ctrl.cpp - src/toggle_bitmap.cpp - src/toolbar.cpp - src/tooltip_manager.cpp - src/utils.cpp - src/validators.cpp - src/vector2d.cpp - src/version.cpp - src/video_box.cpp - src/video_controller.cpp - src/video_display.cpp - src/video_frame.cpp - src/video_out_gl.cpp - src/video_provider_cache.cpp - src/video_provider_dummy.cpp - src/video_provider_manager.cpp - src/video_provider_yuv4mpeg.cpp - src/video_slider.cpp - src/visual_feature.cpp -) -target_link_libraries(Aegisub ${CMAKE_DL_LIBS} libaegisub luabins luajit resrc csri) - -if (MSVC) - set_target_properties(libaegisub PROPERTIES COMPILE_FLAGS "/Yu${PROJECT_SOURCE_DIR}/libaegisub/lagi_pre.h" COMPILE_FLAGS "/FI${PROJECT_SOURCE_DIR}/libaegisub/lagi_pre.h") -else(MSVC) - target_compile_options(libaegisub PRIVATE -include "${PROJECT_SOURCE_DIR}/libaegisub/lagi_pre.h") -endif(MSVC) - -set_property( - SOURCE libaegisub/unix/path.cpp - PROPERTY COMPILE_DEFINITIONS - P_DATA="${CMAKE_INSTALL_PREFIX}/share/aegisub/" -) - -if (MSVC) - add_definitions("-DNOMINMAX -MP -DINITGUID") - set_target_properties(Aegisub PROPERTIES COMPILE_FLAGS "/Yu${PROJECT_SOURCE_DIR}/src/agi_pre.h" COMPILE_FLAGS "/FI${PROJECT_SOURCE_DIR}/src/agi_pre.h") - target_link_libraries (Aegisub Usp10) - #target_sources(Aegisub PRIVATE src/res/res.rc src/res/strings.rc src/crash_writer_minidump.cpp) - target_sources(Aegisub PRIVATE src/res/res.rc src/res/strings.rc src/crash_writer.cpp src/dpi_aware.manifest) - set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Aegisub) -else(MSVC) - target_sources(Aegisub PRIVATE src/crash_writer.cpp) - target_compile_options(Aegisub PRIVATE -include "${PROJECT_SOURCE_DIR}/src/agi_pre.h") -endif(MSVC) - -if (WIN32) - target_sources(Aegisub PRIVATE src/font_file_lister_gdi.cpp) -else (WIN32) - find_package(Fontconfig REQUIRED) - target_link_libraries (Aegisub ${Fontconfig_LIBRARIES}) - target_sources(Aegisub PRIVATE src/font_file_lister_fontconfig.cpp) - set_property(SOURCE src/font_file_lister_fontconfig.cpp PROPERTY INCLUDE_DIRECTORIES "${Fontconfig_INCLUDE_DIRS}") -endif (WIN32) - -find_package(ass REQUIRED) -include_directories(${ass_INCLUDE_DIRS}) -target_link_libraries (Aegisub ${ass_LIBRARIES}) - -find_package(Boost REQUIRED chrono filesystem locale regex system thread) -include_directories(${Boost_INCLUDE_DIRS}) -target_link_directories(Aegisub PRIVATE ${Boost_LIBRARY_DIRS}) -target_link_libraries(Aegisub ${Boost_LIBRARIES}) - -find_package(OpenGL REQUIRED) -include_directories(${OPENGL_INCLUDE_DIR}) -target_link_libraries (Aegisub ${OPENGL_LIBRARIES}) - -find_package(Hunspell REQUIRED) -include_directories(${HUNSPELL_INCLUDE_DIR}) -target_link_libraries (Aegisub ${HUNSPELL_LIBRARIES}) -add_definitions("-DWITH_HUNSPELL") -target_sources(Aegisub PRIVATE src/spellchecker_hunspell.cpp) - -find_package(Iconv REQUIRED) -include_directories(${Iconv_INCLUDE_DIRS}) -target_link_libraries (Aegisub ${Iconv_LIBRARIES}) -add_definitions("-DHAVE_ICONV") -if (NOT Iconv_IS_BUILT_IN) -set_property( - SOURCE libaegisub/common/charset_conv.cpp - PROPERTY COMPILE_DEFINITIONS AGI_ICONV_CONST -) -endif (NOT Iconv_IS_BUILT_IN) - -find_package(ICU REQUIRED uc dt in) -include_directories(${ICU_INCLUDE_DIRS}) -target_link_libraries (Aegisub ${ICU_LIBRARIES}) - -find_package(wxWidgets REQUIRED adv base core gl stc xml) -include(${wxWidgets_USE_FILE}) -target_link_libraries(Aegisub ${wxWidgets_LIBRARIES}) - -find_package(ZLIB REQUIRED) -include_directories(${ZLIB_INCLUDE_DIRS}) -target_link_libraries (Aegisub ${ZLIB_LIBRARIES}) - -find_package(ALSA) -if (ALSA_FOUND) - include_directories(${ALSA_INCLUDE_DIRS}) - target_link_libraries (Aegisub ${ALSA_LIBRARIES}) - add_definitions("-DWITH_ALSA") - target_sources(Aegisub PRIVATE src/audio_player_alsa.cpp) -endif(ALSA_FOUND) - -# target_compile_definitions(Aegisub PRIVATE "WITH_AVISYNTH") -# target_sources(Aegisub PRIVATE src/audio_provider_avs.cpp src/avisynth_wrap.cpp src/video_provider_avs.cpp) - -target_compile_definitions(Aegisub PRIVATE "WITH_CSRI") -target_sources(Aegisub PRIVATE src/subtitles_provider_csri.cpp) -set_property(SOURCE src/subtitles_provider_csri.cpp PROPERTY INCLUDE_DIRECTORIES "${PROJECT_SOURCE_DIR}/vendor/csri/include") - -if(MSVC) - target_link_libraries (Aegisub dsound) - add_definitions("-DWITH_DIRECTSOUND") - add_definitions("-DWITH_XAUDIO2") - target_sources(Aegisub PRIVATE src/audio_player_dsound.cpp src/audio_player_dsound2.cpp src/audio_player_xaudio2.cpp) -endif(MSVC) - -find_package(FFMS2) -if (FFMS2_FOUND) - include_directories(${FFMS2_INCLUDE_DIRS}) - target_link_libraries (Aegisub ${FFMS2_LIBRARIES}) - add_definitions("-DWITH_FFMS2") - target_sources(Aegisub PRIVATE src/audio_provider_ffmpegsource.cpp src/ffmpegsource_common.cpp src/video_provider_ffmpegsource.cpp) -endif(FFMS2_FOUND) - -find_package(FFTW) -if (FFTW_FOUND) - include_directories(${FFTW_INCLUDES}) - target_link_libraries (Aegisub ${FFTW_LIBRARIES}) - add_definitions("-DWITH_FFTW3") -endif(FFTW_FOUND) - -#ifdef WITH_LIBPULSE -#add_definitions("-DWITH_LIBPULSE") -#target_sources(Aegisub PRIVATE src/audio_player_pulse.cpp) - -find_package(OpenAL) -if (OPENAL_FOUND) - include_directories(${OPENAL_INCLUDE_DIR}) - target_link_libraries (Aegisub ${OPENAL_LIBRARY}) - add_definitions("-DWITH_OPENAL") - target_sources(Aegisub PRIVATE src/audio_player_openal.cpp) -endif(OPENAL_FOUND) - -#ifdef WITH_OSS -#ifdef WITH_PORTAUDIO -#ifdef WITH_STARTUPLOG - -find_package(uchardet) -if (uchardet_FOUND) - include_directories(${uchardet_INCLUDE_DIRS}) - target_link_libraries (Aegisub ${uchardet_LIBRARIES}) - add_definitions("-DWITH_UCHARDET") -endif(uchardet_FOUND) - -#ifdef WITH_UPDATE_CHECKER diff --git a/libaegisub/include/libaegisub/audio/provider.h b/libaegisub/include/libaegisub/audio/provider.h index fc2e0ddb3..741979b12 100644 --- a/libaegisub/include/libaegisub/audio/provider.h +++ b/libaegisub/include/libaegisub/audio/provider.h @@ -21,6 +21,7 @@ #include #include +#include namespace agi { class AudioProvider { @@ -94,4 +95,4 @@ std::unique_ptr CreateHDAudioProvider(std::unique_ptr CreateRAMAudioProvider(std::unique_ptr source_provider); void SaveAudioClip(AudioProvider const& provider, fs::path const& path, int start_time, int end_time); -} \ No newline at end of file +} diff --git a/meson.build b/meson.build index 64cc9d3ca..01f5e3935 100644 --- a/meson.build +++ b/meson.build @@ -238,7 +238,6 @@ if host_machine.system() == 'windows' and get_option('avisynth').enabled() endif if host_machine.system() == 'windows' - if not get_option('directsound').disabled() dsound_dep = cc.find_library('dsound', required: get_option('directsound')) winmm_dep = cc.find_library('winmm', required: get_option('directsound')) @@ -277,8 +276,6 @@ if host_machine.system() == 'windows' # Windows 8 not required if XAudio2 is disabled. revert for compat. add_project_arguments('-D_WIN32_WINNT=0x0601', language: 'cpp') endif - - endif if host_machine.system() == 'darwin' diff --git a/src/audio_player_dsound2.cpp b/src/audio_player_dsound2.cpp index e8a144eb2..ad9149803 100644 --- a/src/audio_player_dsound2.cpp +++ b/src/audio_player_dsound2.cpp @@ -469,7 +469,6 @@ stop_playback: invert_volume = DSBVOLUME_MAX; else if (invert_volume < DSBVOLUME_MIN / 2) invert_volume = DSBVOLUME_MIN / 2; - LOG_I("DS2") << "Earrape vlume: " <SetVolume(invert_volume); } // Change volume diff --git a/src/audio_provider_ffmpegsource.cpp b/src/audio_provider_ffmpegsource.cpp index 1c109220f..46e1412f5 100644 --- a/src/audio_provider_ffmpegsource.cpp +++ b/src/audio_provider_ffmpegsource.cpp @@ -126,10 +126,8 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) { // reindex if the error handling mode has changed FFMS_IndexErrorHandling ErrorHandling = GetErrorHandlingMode(); -#if FFMS_VERSION >= ((2 << 24) | (17 << 16) | (2 << 8) | 0) if (Index && FFMS_GetErrorHandling(Index) != ErrorHandling) Index = nullptr; -#endif // moment of truth if (!Index) { @@ -167,23 +165,22 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) { throw agi::AudioProviderError("unknown or unsupported sample format"); } -#if FFMS_VERSION >= ((2 << 24) | (17 << 16) | (4 << 8) | 0) if (OPT_GET("Provider/Audio/FFmpegSource/Downmix")->GetBool()) { - if (channels > 1 || bytes_per_sample != 2 || float_samples) { + if (channels > 2 || bytes_per_sample != 2 || float_samples) { std::unique_ptr opt(FFMS_CreateResampleOptions(AudioSource), FFMS_DestroyResampleOptions); - opt->ChannelLayout = FFMS_CH_FRONT_CENTER; + if (channels > 2) + opt->ChannelLayout = FFMS_CH_FRONT_LEFT | FFMS_CH_FRONT_RIGHT; opt->SampleFormat = FFMS_FMT_S16; // Might fail if FFMS2 wasn't built with libavresample if (!FFMS_SetOutputFormatA(AudioSource, opt.get(), nullptr)) { - channels = 1; + channels = channels > 2 ? 2 : channels; bytes_per_sample = 2; float_samples = false; } } } -#endif } } @@ -192,4 +189,4 @@ std::unique_ptr CreateFFmpegSourceAudioProvider(agi::fs::pat return agi::make_unique(file, br); } -#endif /* WITH_FFMS2 */ \ No newline at end of file +#endif /* WITH_FFMS2 */