diff --git a/libraries/lib-ffmpeg-support/FFmpegFunctions.cpp b/libraries/lib-ffmpeg-support/FFmpegFunctions.cpp index 9e3edb127f972e97da556f496bee94ef76f0b1ea..66d085a0b7ec534b026e07d1a6d0b3dd5d9fd585 100644 --- a/libraries/lib-ffmpeg-support/FFmpegFunctions.cpp +++ b/libraries/lib-ffmpeg-support/FFmpegFunctions.cpp @@ -82,13 +82,13 @@ struct EnvSetter final static const wxString VariableName; static const wxString Separator; - EnvSetter() + explicit EnvSetter(bool fromUserPathOnly) { ValueExisted = wxGetEnv(VariableName, &OldValue); wxString value; - for (const wxString& path : FFmpegFunctions::GetSearchPaths()) + for (const wxString& path : FFmpegFunctions::GetSearchPaths(fromUserPathOnly)) { if (!value.empty()) value += Separator; @@ -148,7 +148,7 @@ struct FFmpegFunctions::Private final AVCodecFactories CodecFactories; AVUtilFactories UtilFactories; - std::shared_ptr<wxDynamicLibrary> LibraryWithSymbol(const char* symbol) const + std::shared_ptr<wxDynamicLibrary> LibraryWithSymbol(const char* symbol, bool fromUserPathOnly) const { if (AVFormatLibrary->HasSymbol(symbol)) return AVFormatLibrary; @@ -163,21 +163,21 @@ struct FFmpegFunctions::Private final if (path.empty()) return nullptr; - return LoadLibrary(wxFileNameFromPath(path)); + return LoadLibrary(wxFileNameFromPath(path), fromUserPathOnly); } - bool Load(FFmpegFunctions& functions, const wxString& path) + bool Load(FFmpegFunctions& functions, const wxString& path, bool fromUserPathOnly) { // We start by loading AVFormat - AVFormatLibrary = LoadLibrary(path); + AVFormatLibrary = LoadLibrary(path, fromUserPathOnly); if (AVFormatLibrary == nullptr) return false; - if ((AVCodecLibrary = LibraryWithSymbol("avcodec_version")) == nullptr) + if ((AVCodecLibrary = LibraryWithSymbol("avcodec_version", fromUserPathOnly)) == nullptr) return false; - if ((AVUtilLibrary = LibraryWithSymbol("avutil_version")) == nullptr) + if ((AVUtilLibrary = LibraryWithSymbol("avutil_version", fromUserPathOnly)) == nullptr) return false; if ( @@ -218,12 +218,12 @@ struct FFmpegFunctions::Private final return true; } - std::shared_ptr<wxDynamicLibrary> LoadLibrary(const wxString& libraryName) const + std::shared_ptr<wxDynamicLibrary> LoadLibrary(const wxString& libraryName, bool fromUserPathOnly) const { #if defined(__WXMAC__) // On macOS dyld reads environment only when application starts. // Let's emulate the process manually - for(const wxString& path : FFmpegFunctions::GetSearchPaths()) + for (const wxString& path : FFmpegFunctions::GetSearchPaths(fromUserPathOnly)) { const wxString fullName = wxFileName(path, libraryName).GetFullPath(); @@ -259,7 +259,7 @@ FFmpegFunctions::~FFmpegFunctions() { } -std::shared_ptr<FFmpegFunctions> FFmpegFunctions::Load() +std::shared_ptr<FFmpegFunctions> FFmpegFunctions::Load(bool fromUserPathOnly) { static std::weak_ptr<FFmpegFunctions> weakFunctions; @@ -275,14 +275,14 @@ std::shared_ptr<FFmpegFunctions> FFmpegFunctions::Load() FFmpegAPIResolver::Get().GetSuportedAVFormatVersions(); #if !defined(__WXMAC__) - EnvSetter envSetter; + EnvSetter envSetter(fromUserPathOnly); #endif for (int version : supportedVersions) { for (const wxString& path : BuildAVFormatPaths(version)) { - if (ffmpeg->mPrivate->Load(*ffmpeg, path)) + if (ffmpeg->mPrivate->Load(*ffmpeg, path, fromUserPathOnly)) { weakFunctions = ffmpeg; return ffmpeg; @@ -295,14 +295,25 @@ std::shared_ptr<FFmpegFunctions> FFmpegFunctions::Load() StringSetting AVFormatPath { L"/FFmpeg/FFmpegLibPath", L"" }; -std::vector<wxString> FFmpegFunctions::GetSearchPaths() +std::vector<wxString> FFmpegFunctions::GetSearchPaths(bool fromUserPathOnly) { std::vector<wxString> paths; const wxString userAVFormatFullPath = AVFormatPath.Read(); if (!userAVFormatFullPath.empty()) - paths.emplace_back(wxPathOnly(userAVFormatFullPath)); + { + // For some directories, wxPathOnly will fail. + // For example, if path is `c:\ffmpeg-4.4` + // wxPathOnly will return `c:\` + if (wxDirExists(userAVFormatFullPath)) + paths.emplace_back(userAVFormatFullPath); + else + paths.emplace_back(wxPathOnly(userAVFormatFullPath)); + } + + if (fromUserPathOnly) + return paths; #if defined(__WXMSW__) wxRegKey reg(wxT("HKEY_LOCAL_MACHINE\\Software\\FFmpeg for Audacity")); diff --git a/libraries/lib-ffmpeg-support/FFmpegFunctions.h b/libraries/lib-ffmpeg-support/FFmpegFunctions.h index be0e59cf040412dc3ce8ce0526bdf0bfc5dea745..371a1dda504285fe350767c6014db1483f658604 100644 --- a/libraries/lib-ffmpeg-support/FFmpegFunctions.h +++ b/libraries/lib-ffmpeg-support/FFmpegFunctions.h @@ -86,12 +86,12 @@ struct FFMPEG_SUPPORT_API FFmpegFunctions : FFmpegFunctions(); ~FFmpegFunctions(); - static std::shared_ptr<FFmpegFunctions> Load(); + static std::shared_ptr<FFmpegFunctions> Load(bool fromUserPathOnly = false); AVCodecIDFwd (*GetAVCodecID)(AudacityAVCodecID) = nullptr; AudacityAVCodecID (*GetAudacityCodecID)(AVCodecIDFwd) = nullptr; - static std::vector<wxString> GetSearchPaths(); + static std::vector<wxString> GetSearchPaths(bool fromUserPathOnly); std::unique_ptr<AVIOContextWrapper> CreateAVIOContext() const; std::unique_ptr<AVFormatContextWrapper> CreateAVFormatContext() const; diff --git a/src/AdornedRulerPanel.cpp b/src/AdornedRulerPanel.cpp index aa519c562caa6b018708d7f35ce27dfc73b35bba..56359c1aba89910e8477e6730afd94193be17f5e 100644 --- a/src/AdornedRulerPanel.cpp +++ b/src/AdornedRulerPanel.cpp @@ -502,14 +502,15 @@ void AdornedRulerPanel::ScrubbingRulerOverlay::Update() mNewIndicatorSnapped == -1 && ii < ruler->mNumGuides; ++ii) { if (ruler->mIsSnapped[ii]) { mNewIndicatorSnapped = ii; - mNewQPIndicatorPos = ruler->Time2Pos(ruler->mQuickPlayPos[ii]); } } + mNewQPIndicatorPos = ruler->Time2Pos( + ruler->mQuickPlayPos[std::max(0, mNewIndicatorSnapped)]); // These determine which shape is drawn on the ruler, and whether // in the scrub or the qp zone mNewScrub = - !ruler->Target() && // not doing some other drag in the ruler + !ruler->IsMouseCaptured() && // not doing some other drag in the ruler (ruler->LastCell() == ruler->mScrubbingCell || (scrubber.HasMark())); mNewSeek = mNewScrub && @@ -618,13 +619,12 @@ void AdornedRulerPanel::TrackPanelGuidelineOverlay::Draw( mOldIndicatorSnapped = mPartner->mNewIndicatorSnapped; mOldPreviewingScrub = mNewPreviewingScrub; - if ( !(mOldPreviewingScrub || mOldIndicatorSnapped >= 0) ) - return; - if (mOldQPIndicatorPos >= 0) { mOldPreviewingScrub ? AColor::IndicatorColor(&dc, true) // Draw green line for preview. - : AColor::SnapGuidePen(&dc); // Yellow snap guideline + : (mOldIndicatorSnapped >= 0) + ? AColor::SnapGuidePen(&dc) // Yellow snap guideline + : AColor::Light(&dc, false); // Draw indicator in all visible tracks auto pCellularPanel = dynamic_cast<CellularPanel*>( &panel ); diff --git a/src/FFmpeg.cpp b/src/FFmpeg.cpp index 3503e4035ef82d81f9995e869689c2145b91b54b..07f21124c72de3775c94b10ce4fe81e213d2b4fe 100644 --- a/src/FFmpeg.cpp +++ b/src/FFmpeg.cpp @@ -116,7 +116,7 @@ public: FindFFmpegDialog(wxWindow *parent, const wxString &path, const wxString &name) : wxDialogWrapper(parent, wxID_ANY, XO("Locate FFmpeg")) , mName(name) - , mFullPath(path, name) + , mFullPath(path, {}) { SetName(); @@ -183,7 +183,7 @@ public: # if defined(__WXMSW__) { XO("Only avformat.dll"), { wxT("avformat-*.dll") } }, # elif defined(__WXMAC__) - { XO("Only ffmpeg.*.dylib"), { wxT("ffmpeg.*.dylib") } }, + { XO("Only ffmpeg.*.dylib"), { wxT("ffmpeg.*.dylib"), wxT("libavformat.*.dylib") } }, # else { XO("Only libavformat.so"), { wxT("libavformat.so.*") } }, # endif @@ -222,7 +222,12 @@ public: void UpdatePath() { - mFullPath = mPathText->GetValue(); + const wxString path = mPathText->GetValue(); + + if (wxDirExists(path)) + mFullPath = wxFileName(path, {}, wxPATH_NATIVE); + else + mFullPath = mPathText->GetValue(); } wxString GetLibPath() @@ -302,22 +307,53 @@ BEGIN_EVENT_TABLE(FFmpegNotFoundDialog, wxDialogWrapper) EVT_BUTTON(wxID_OK, FFmpegNotFoundDialog::OnOk) END_EVENT_TABLE() +struct SafeAVFormatPathUpdater final +{ + explicit SafeAVFormatPathUpdater(const wxString& newPath) + { + mHasOldValue = AVFormatPath.Read(&mOldValue); + + AVFormatPath.Write(newPath); + } + + ~SafeAVFormatPathUpdater() + { + if (mRevertToOld) + { + if (mHasOldValue) + AVFormatPath.Write(mOldValue); + else + AVFormatPath.Delete(); + } + + gPrefs->Flush(); + } + + void CommitValue() noexcept + { + mRevertToOld = false; + } + + wxString mOldValue; + bool mHasOldValue; + bool mRevertToOld { true }; +}; bool FindFFmpegLibs(wxWindow* parent) { wxString path; #if defined(__WXMSW__) - const wxString name = wxT("avformat-*.dll"); + const wxString name = wxT("avformat.dll"); #elif defined(__WXMAC__) - const wxString name = wxT("ffmpeg.*.64bit.dylib"); + const wxString name = wxT("ffmpeg.64bit.dylib"); #else - const wxString name = wxT("libavformat.so.*"); + const wxString name = wxT("libavformat.so"); #endif wxLogMessage(wxT("Looking for FFmpeg libraries...")); - auto searchPaths = FFmpegFunctions::GetSearchPaths(); + auto searchPaths = FFmpegFunctions::GetSearchPaths(false); if (!searchPaths.empty()) path = searchPaths.front(); @@ -331,17 +367,25 @@ bool FindFFmpegLibs(wxWindow* parent) path = fd.GetLibPath(); + const wxFileName fileName(path); + + if (fileName.FileExists()) + path = fileName.GetPath(); + wxLogMessage(wxT("User-specified path = '%s'"), path); - if (!::wxFileExists(path)) { - wxLogError(wxT("User-specified file does not exist. Failed to find FFmpeg libraries.")); + SafeAVFormatPathUpdater updater(path); + + // Try to load FFmpeg from the user provided path + if (!FFmpegFunctions::Load(true)) + { + wxLogError(wxT("User-specified path does not contain FFmpeg libraries.")); return false; } - wxLogMessage(wxT("User-specified FFmpeg file exists. Success.")); + updater.CommitValue(); - AVFormatPath.Write(path); - gPrefs->Flush(); + wxLogMessage(wxT("User-specified FFmpeg file exists. Success.")); return true; } diff --git a/src/PlaybackSchedule.cpp b/src/PlaybackSchedule.cpp index 531b8ec35e3134afa039ae9327777c725f3be90c..cb0601522b461317a4b8042e22e2780b59d85473 100644 --- a/src/PlaybackSchedule.cpp +++ b/src/PlaybackSchedule.cpp @@ -14,7 +14,7 @@ #include "Envelope.h" #include "Mix.h" #include "Project.h" -#include "ProjectSettings.h" +#include "ProjectAudioIO.h" #include "SampleCount.h" #include "ViewInfo.h" // for PlayRegionEvent @@ -172,7 +172,7 @@ void NewDefaultPlaybackPolicy::Initialize( ViewInfo::Get( mProject ).playRegion.Bind( EVT_PLAY_REGION_CHANGE, &NewDefaultPlaybackPolicy::OnPlayRegionChange, this); if (mVariableSpeed) - mProject.Bind( EVT_PROJECT_SETTINGS_CHANGE, + mProject.Bind( EVT_PLAY_SPEED_CHANGE, &NewDefaultPlaybackPolicy::OnPlaySpeedChange, this); } @@ -215,18 +215,18 @@ NewDefaultPlaybackPolicy::GetPlaybackSlice( { // How many samples to produce for each channel. const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining()); - mRemaining = realTimeRemaining * mRate; + mRemaining = realTimeRemaining * mRate / mLastPlaySpeed; - if (RevertToOldDefault(schedule)) + if (mLastPlaySpeed == 1.0 && RevertToOldDefault(schedule)) return PlaybackPolicy::GetPlaybackSlice(schedule, available); auto frames = available; auto toProduce = frames; - double deltat = frames / mRate; + double deltat = (frames / mRate) * mLastPlaySpeed; if (deltat > realTimeRemaining) { - toProduce = frames = mRemaining; + toProduce = frames = (realTimeRemaining * mRate) / mLastPlaySpeed; schedule.RealTimeAdvance( realTimeRemaining ); } else @@ -332,7 +332,7 @@ bool NewDefaultPlaybackPolicy::RepositionPlayback( schedule.RealTimeInit(newTime); const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining()); - mRemaining = realTimeRemaining * mRate; + mRemaining = realTimeRemaining * mRate / mLastPlaySpeed; } else if (speedChange) // Don't return early @@ -397,7 +397,7 @@ void NewDefaultPlaybackPolicy::WriteMessage() double NewDefaultPlaybackPolicy::GetPlaySpeed() { return mVariableSpeed - ? ProjectSettings::Get(mProject).GetPlaySpeed() + ? ProjectAudioIO::Get(mProject).GetPlaySpeed() : 1.0; } diff --git a/src/ProjectAudioIO.cpp b/src/ProjectAudioIO.cpp index ce19f19e3f86a77d1d756008c8981d1f32f5cdcd..b1711d15c10d3dbaf64b7d511c532ea9127ef196 100644 --- a/src/ProjectAudioIO.cpp +++ b/src/ProjectAudioIO.cpp @@ -13,6 +13,8 @@ Paul Licameli split from AudacityProject.cpp #include "AudioIOBase.h" #include "Project.h" +wxDEFINE_EVENT( EVT_PLAY_SPEED_CHANGE, wxCommandEvent); + static const AudacityProject::AttachedObjects::RegisteredFactory sAudioIOKey{ []( AudacityProject &parent ){ return std::make_shared< ProjectAudioIO >( parent ); @@ -89,3 +91,12 @@ void ProjectAudioIO::SetCaptureMeter( gAudioIO->SetCaptureMeter( project.shared_from_this(), mCaptureMeter ); } } + +void ProjectAudioIO::SetPlaySpeed(double value) +{ + if (auto oldValue = GetPlaySpeed(); value != oldValue) { + mPlaySpeed.store( value, std::memory_order_relaxed ); + wxCommandEvent evt{ EVT_PLAY_SPEED_CHANGE }; + mProject.ProcessEvent(evt); + } +} diff --git a/src/ProjectAudioIO.h b/src/ProjectAudioIO.h index d8b0c611bd83f3cdf68b4579188bc0c721778a3f..8e46ccf570aa2fe27675804e9dfe3244b0ab1121 100644 --- a/src/ProjectAudioIO.h +++ b/src/ProjectAudioIO.h @@ -13,11 +13,17 @@ Paul Licameli split from AudacityProject.h #include "ClientData.h" // to inherit #include <wx/weakref.h> +#include <wx/event.h> // to declare custom event type +#include <atomic> #include <memory> class AudacityProject; class Meter; +// Sent to the project when the play speed changes +wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API, + EVT_PLAY_SPEED_CHANGE, wxCommandEvent); + ///\ brief Holds per-project state needed for interaction with AudioIO, /// including the audio stream token and pointers to meters class AUDACITY_DLL_API ProjectAudioIO final @@ -43,6 +49,11 @@ public: void SetCaptureMeter( const std::shared_ptr<Meter> &capture); + // Speed play + double GetPlaySpeed() const { + return mPlaySpeed.load( std::memory_order_relaxed ); } + void SetPlaySpeed( double value ); + private: AudacityProject &mProject; @@ -50,6 +61,10 @@ private: std::shared_ptr<Meter> mPlaybackMeter; std::shared_ptr<Meter> mCaptureMeter; + // This is atomic because scrubber may read it in a separate thread from + // the main + std::atomic<double> mPlaySpeed{}; + int mAudioIOToken{ -1 }; }; diff --git a/src/ProjectSettings.cpp b/src/ProjectSettings.cpp index 970aa3c2ebc982b5dcb7559d1fd816af4c01ee19..a90b8401a89f6d898fba9563ce4fb6398f635664 100644 --- a/src/ProjectSettings.cpp +++ b/src/ProjectSettings.cpp @@ -140,14 +140,6 @@ void ProjectSettings::SetBandwidthSelectionFormatName( mBandwidthSelectionFormatName = formatName; } -void ProjectSettings::SetPlaySpeed(double value) -{ - if (auto oldValue = GetPlaySpeed(); value != oldValue) { - mPlaySpeed.store( value, std::memory_order_relaxed ); - Notify( mProject, ChangedPlaySpeed, oldValue ); - } -} - void ProjectSettings::SetSelectionFormat(const NumericFormatSymbol & format) { mSelectionFormat = format; diff --git a/src/ProjectSettings.h b/src/ProjectSettings.h index ecfaff54f064230915b3f1ada5a4226ce62a7f60..858d76ca6f163fe5ada1568cffc5068d17d8ae79 100644 --- a/src/ProjectSettings.h +++ b/src/ProjectSettings.h @@ -66,7 +66,6 @@ public: enum EventCode : int { ChangedSyncLock, ChangedTool, - ChangedPlaySpeed, }; explicit ProjectSettings( AudacityProject &project ); @@ -102,11 +101,6 @@ public: void SetOvertones(bool isSelected) { mbOvertones = isSelected; } bool IsOvertones() const { return mbOvertones; } - - // Speed play - double GetPlaySpeed() const { - return mPlaySpeed.load( std::memory_order_relaxed ); } - void SetPlaySpeed( double value ); // Selection Format void SetSelectionFormat(const NumericFormatSymbol & format); @@ -142,10 +136,6 @@ private: wxString mSoloPref; - // This is atomic because scrubber may read it in a separate thread from - // the main - std::atomic<double> mPlaySpeed{}; - int mSnapTo; int mCurrentTool; diff --git a/src/toolbars/TranscriptionToolBar.cpp b/src/toolbars/TranscriptionToolBar.cpp index 73a25334649023d4481fd58ad28248f9aa44752e..376fc44a2e70ffc76b631c7e476fad81ce49117f 100644 --- a/src/toolbars/TranscriptionToolBar.cpp +++ b/src/toolbars/TranscriptionToolBar.cpp @@ -37,8 +37,8 @@ #include "../KeyboardCapture.h" #include "NoteTrack.h" #include "Project.h" +#include "../ProjectAudioIO.h" #include "../ProjectAudioManager.h" -#include "../ProjectSettings.h" #include "Envelope.h" #include "ViewInfo.h" #include "../WaveTrack.h" @@ -157,7 +157,7 @@ void TranscriptionToolBar::Create(wxWindow * parent) void TranscriptionToolBar::SetPlaySpeed( double value ) { mPlaySpeed = value; - ProjectSettings::Get( mProject ).SetPlaySpeed( GetPlaySpeed() ); + ProjectAudioIO::Get( mProject ).SetPlaySpeed( GetPlaySpeed() ); } /// This is a convenience function that allows for button creation in diff --git a/src/tracks/ui/Scrubbing.cpp b/src/tracks/ui/Scrubbing.cpp index 0f33d37d9913b5af464c6813c7ea577d30f50525..657669915b2258dc68c2082479031ee3b381ae1a 100644 --- a/src/tracks/ui/Scrubbing.cpp +++ b/src/tracks/ui/Scrubbing.cpp @@ -20,7 +20,6 @@ Paul Licameli split from TrackPanel.cpp #include "../../ProjectAudioManager.h" #include "../../ProjectHistory.h" #include "../../ProjectWindows.h" -#include "../../ProjectSettings.h" #include "ProjectStatus.h" #include "../../ScrubState.h" #include "Track.h" @@ -599,8 +598,8 @@ void Scrubber::ContinueScrubbingPoll() // default speed of 1.3 set, so that we can hear there is a problem // when playAtSpeedTB not found. double speed = 1.3; - const auto &settings = ProjectSettings::Get( *mProject ); - speed = settings.GetPlaySpeed(); + const auto &projectAudioIO = ProjectAudioIO::Get( *mProject ); + speed = projectAudioIO.GetPlaySpeed(); mOptions.minSpeed = speed -0.01; mOptions.maxSpeed = speed +0.01; mOptions.adjustStart = false;