audacia/src/AudioIOBase.h
David Bailes 6b9c8e79cc Bug 1954: Clicks may occur starting/pausing play-at-speed or Scrub
Problem:
On Windows, after 50ms, there is a short period of roughly zero introduced into the output. On Linux, there is also a spike which sounds like a crackle.

In AudioIO::FillBuffers(), Mixer::SetTimesAndSpeed() is called, which sets mT0 and mT1 to a small interval.
In Mixer::MixVariableRates(), all the samples in the interval are used, which means the Resample::Process() is called with last equal to true.
So when Mixer::MixVariableRates() is called again, the resampler is being reused after a call to Process() in which last is true.
It is not stated in the soxr documentation if the resampler will produce valid results in this case, and it's only the scrubbing code which does this.
I think this is the problem, and so the partial fix below avoids this happening.

Partial fix for play-at-speed and keyboard scrubbing:
For these, there is no need to reset the values of mT0 and mT1. (There is no need to allow for the sample position being used to potentially jump around.)
So for these cases, Mixer::SetSpeed() is called, rather than Mixer::SetTimesAndSpeed().
2020-01-15 11:12:40 +00:00

494 lines
17 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
AudioIOBase.h
Paul Licameli split from AudioIO.h
**********************************************************************/
#ifndef __AUDACITY_AUDIO_IO_BASE__
#define __AUDACITY_AUDIO_IO_BASE__
#include "Audacity.h" // for USE_* macros
#include "Experimental.h"
#include <atomic>
#include <cfloat>
#include <functional>
#include <memory>
#include <vector>
#include <wx/string.h>
#include <wx/weakref.h> // member variable
#include "portaudio.h"
#if USE_PORTMIXER
#include "../lib-src/portmixer/include/portmixer.h"
#endif
class AudioIOBase;
class AudacityProject;
class AudioIOListener;
class BoundedEnvelope;
class MeterPanelBase;
using PRCrossfadeData = std::vector< std::vector < float > >;
#define BAD_STREAM_TIME (-DBL_MAX)
// For putting an increment of work in the scrubbing queue
struct ScrubbingOptions {
ScrubbingOptions() {}
bool adjustStart {};
// usually from TrackList::GetEndTime()
double maxTime {};
double minTime {};
bool bySpeed {};
bool isPlayingAtSpeed{};
bool isKeyboardScrubbing{};
double delay {};
// Limiting values for the speed of a scrub interval:
double minSpeed { 0.0 };
double maxSpeed { 1.0 };
// When maximum speed scrubbing skips to follow the mouse,
// this is the minimum amount of playback allowed at the maximum speed:
double minStutterTime {};
static double MaxAllowedScrubSpeed()
{ return 32.0; } // Is five octaves enough for your amusement?
static double MinAllowedScrubSpeed()
{ return 0.01; } // Mixer needs a lower bound speed. Scrub no slower than this.
};
// To avoid growing the argument list of StartStream, add fields here
struct AudioIOStartStreamOptions
{
explicit
AudioIOStartStreamOptions(AudacityProject *pProject_, double rate_)
: pProject{ pProject_ }
, envelope(nullptr)
, rate(rate_)
, playLooped(false)
, cutPreviewGapStart(0.0)
, cutPreviewGapLen(0.0)
, pStartTime(NULL)
, preRoll(0.0)
{}
AudacityProject *pProject{};
MeterPanelBase *captureMeter{}, *playbackMeter{};
BoundedEnvelope *envelope; // for time warping
std::shared_ptr< AudioIOListener > listener;
double rate;
bool playLooped;
double cutPreviewGapStart;
double cutPreviewGapLen;
double * pStartTime;
double preRoll;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
// Non-null value indicates that scrubbing will happen
// (do not specify a time track, looping, or recording, which
// are all incompatible with scrubbing):
ScrubbingOptions *pScrubbingOptions {};
#endif
// contents may get swapped with empty vector
PRCrossfadeData *pCrossfadeData{};
// An unfortunate thing needed just to make scrubbing work on Linux when
// we can't use a separate polling thread.
// The return value is a number of milliseconds to sleep before calling again
std::function< unsigned long() > playbackStreamPrimer;
};
///\brief A singleton object supporting queries of the state of any active
/// audio streams, and audio device capabilities
class AUDACITY_DLL_API AudioIOBase /* not final */
{
public:
static AudioIOBase *Get();
virtual ~AudioIOBase();
void SetCaptureMeter(AudacityProject *project, MeterPanelBase *meter);
void SetPlaybackMeter(AudacityProject *project, MeterPanelBase *meter);
/** \brief update state after changing what audio devices are selected
*
* Called when the devices stored in the preferences are changed to update
* the audio mixer capabilities
*
* \todo: Make this do a sample rate query and store the result in the
* AudioIO object to avoid doing it later? Would simplify the
* GetSupported*Rate functions considerably */
void HandleDeviceChange();
/** \brief Get a list of sample rates the output (playback) device
* supports.
*
* If no information about available sample rates can be fetched,
* an empty list is returned.
*
* You can explicitly give the index of the device. If you don't
* give it, the currently selected device from the preferences will be used.
*
* You may also specify a rate for which to check in addition to the
* standard rates.
*/
static std::vector<long> GetSupportedPlaybackRates(int DevIndex = -1,
double rate = 0.0);
/** \brief Get a list of sample rates the input (recording) device
* supports.
*
* If no information about available sample rates can be fetched,
* an empty list is returned.
*
* You can explicitly give the index of the device. If you don't
* give it, the currently selected device from the preferences will be used.
*
* You may also specify a rate for which to check in addition to the
* standard rates.
*/
static std::vector<long> GetSupportedCaptureRates(int devIndex = -1,
double rate = 0.0);
/** \brief Get a list of sample rates the current input/output device
* combination supports.
*
* Since there is no concept (yet) for different input/output
* sample rates, this currently returns only sample rates that are
* supported on both the output and input device. If no information
* about available sample rates can be fetched, it returns a default
* list.
* You can explicitly give the indexes of the playDevice/recDevice.
* If you don't give them, the selected devices from the preferences
* will be used.
* You may also specify a rate for which to check in addition to the
* standard rates.
*/
static std::vector<long> GetSupportedSampleRates(int playDevice = -1,
int recDevice = -1,
double rate = 0.0);
/** \brief Get a supported sample rate which can be used a an optimal
* default.
*
* Currently, this uses the first supported rate in the list
* [44100, 48000, highest sample rate]. Used in Project as a default value
* for project rates if one cannot be retrieved from the preferences.
* So all in all not that useful or important really
*/
static int GetOptimalSupportedSampleRate();
/** \brief During playback, the track time most recently played
*
* When playing looped, this will start from t0 again,
* too. So the returned time should be always between
* t0 and t1
*/
double GetStreamTime();
/** \brief Array of common audio sample rates
*
* These are the rates we will always support, regardless of hardware support
* for them (by resampling in audacity if needed) */
static const int StandardRates[];
/** \brief How many standard sample rates there are */
static const int NumStandardRates;
/** \brief Get diagnostic information on all the available audio I/O devices
*
*/
wxString GetDeviceInfo();
#ifdef EXPERIMENTAL_MIDI_OUT
/** \brief Get diagnostic information on all the available MIDI I/O devices */
wxString GetMidiDeviceInfo();
#endif
/** \brief Find out if playback / recording is currently paused */
bool IsPaused() const;
virtual void StopStream() = 0;
/** \brief Returns true if audio i/o is busy starting, stopping, playing,
* or recording.
*
* When this is false, it's safe to start playing or recording */
bool IsBusy() const;
/** \brief Returns true if the audio i/o is running at all, but not during
* cleanup
*
* Doesn't return true if the device has been closed but some disk i/o or
* cleanup is still going on. If you want to know if it's safe to start a
* NEW stream, use IsBusy() */
bool IsStreamActive() const;
bool IsStreamActive(int token) const;
/** \brief Returns true if the stream is active, or even if audio I/O is
* busy cleaning up its data or writing to disk.
*
* This is used by TrackPanel to determine when a track has been completely
* recorded, and it's safe to flush to disk. */
bool IsAudioTokenActive(int token) const;
/** \brief Returns true if we're monitoring input (but not recording or
* playing actual audio) */
bool IsMonitoring() const;
/* Mixer services are always available. If no stream is running, these
* methods use whatever device is specified by the preferences. If a
* stream *is* running, naturally they manipulate the mixer associated
* with that stream. If no mixer is available, output is emulated and
* input is stuck at 1.0f (a gain is applied to output samples).
*/
void SetMixer(int inputSource);
protected:
static std::unique_ptr<AudioIOBase> ugAudioIO;
static wxString DeviceName(const PaDeviceInfo* info);
static wxString HostName(const PaDeviceInfo* info);
AudacityProject *mOwningProject;
/// True if audio playback is paused
bool mPaused;
/// True when output reaches mT1
bool mMidiOutputComplete{ true };
/// mMidiStreamActive tells when mMidiStream is open for output
bool mMidiStreamActive;
volatile int mStreamToken;
/// Audio playback rate in samples per second
double mRate;
PaStream *mPortStreamV19;
wxWeakRef<MeterPanelBase> mInputMeter{};
wxWeakRef<MeterPanelBase> mOutputMeter{};
#if USE_PORTMIXER
PxMixer *mPortMixer;
float mPreviousHWPlaythrough;
#endif /* USE_PORTMIXER */
bool mEmulateMixerOutputVol;
/** @brief Can we control the hardware input level?
*
* This flag is set to true if using portmixer to control the
* input volume seems to be working (and so we offer the user the control),
* and to false (locking the control out) otherwise. This avoids stupid
* scaled clipping problems when trying to do software emulated input volume
* control */
bool mInputMixerWorks;
float mMixerOutputVol;
// For cacheing supported sample rates
static int mCachedPlaybackIndex;
static std::vector<long> mCachedPlaybackRates;
static int mCachedCaptureIndex;
static std::vector<long> mCachedCaptureRates;
static std::vector<long> mCachedSampleRates;
static double mCachedBestRateIn;
struct RecordingSchedule {
double mPreRoll{};
double mLatencyCorrection{}; // negative value usually
double mDuration{};
PRCrossfadeData mCrossfadeData;
// These are initialized by the main thread, then updated
// only by the thread calling FillBuffers:
double mPosition{};
bool mLatencyCorrected{};
double TotalCorrection() const { return mLatencyCorrection - mPreRoll; }
double ToConsume() const;
double Consumed() const;
double ToDiscard() const;
};
struct PlaybackSchedule {
/// Playback starts at offset of mT0, which is measured in seconds.
double mT0;
/// Playback ends at offset of mT1, which is measured in seconds. Note that mT1 may be less than mT0 during scrubbing.
double mT1;
/// Current track time position during playback, in seconds.
/// Initialized by the main thread but updated by worker threads during
/// playback or recording, and periodically reread by the main thread for
/// purposes such as display update.
std::atomic<double> mTime;
/// Accumulated real time (not track position), starting at zero (unlike
/// mTime), and wrapping back to zero each time around looping play.
/// Thus, it is the length in real seconds between mT0 and mTime.
double mWarpedTime;
/// Real length to be played (if looping, for each pass) after warping via a
/// time track, computed just once when starting the stream.
/// Length in real seconds between mT0 and mT1. Always positive.
double mWarpedLength;
// mWarpedTime and mWarpedLength are irrelevant when scrubbing,
// else they are used in updating mTime,
// and when not scrubbing or playing looped, mTime is also used
// in the test for termination of playback.
// with ComputeWarpedLength, it is now possible the calculate the warped length with 100% accuracy
// (ignoring accumulated rounding errors during playback) which fixes the 'missing sound at the end' bug
const BoundedEnvelope *mEnvelope;
volatile enum {
PLAY_STRAIGHT,
PLAY_LOOPED,
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
PLAY_SCRUB,
PLAY_AT_SPEED, // a version of PLAY_SCRUB.
PLAY_KEYBOARD_SCRUB,
#endif
} mPlayMode { PLAY_STRAIGHT };
double mCutPreviewGapStart;
double mCutPreviewGapLen;
void Init(
double t0, double t1,
const AudioIOStartStreamOptions &options,
const RecordingSchedule *pRecordingSchedule );
/** \brief True if the end time is before the start time */
bool ReversedTime() const
{
return mT1 < mT0;
}
/** \brief Get current track time value, unadjusted
*
* Returns a time in seconds.
*/
double GetTrackTime() const
{ return mTime.load(std::memory_order_relaxed); }
/** \brief Set current track time value, unadjusted
*/
void SetTrackTime( double time )
{ mTime.store(time, std::memory_order_relaxed); }
/** \brief Clamps argument to be between mT0 and mT1
*
* Returns the bound if the value is out of bounds; does not wrap.
* Returns a time in seconds.
*/
double ClampTrackTime( double trackTime ) const;
/** \brief Clamps mTime to be between mT0 and mT1
*
* Returns the bound if the value is out of bounds; does not wrap.
* Returns a time in seconds.
*/
double LimitTrackTime() const;
/** \brief Normalizes mTime, clamping it and handling gaps from cut preview.
*
* Clamps the time (unless scrubbing), and skips over the cut section.
* Returns a time in seconds.
*/
double NormalizeTrackTime() const;
void ResetMode() { mPlayMode = PLAY_STRAIGHT; }
bool PlayingStraight() const { return mPlayMode == PLAY_STRAIGHT; }
bool Looping() const { return mPlayMode == PLAY_LOOPED; }
bool Scrubbing() const { return mPlayMode == PLAY_SCRUB || mPlayMode == PLAY_KEYBOARD_SCRUB; }
bool PlayingAtSpeed() const { return mPlayMode == PLAY_AT_SPEED; }
bool Interactive() const { return Scrubbing() || PlayingAtSpeed(); }
// Returns true if a loop pass, or the sole pass of straight play,
// is completed at the current value of mTime
bool PassIsComplete() const;
// Returns true if time equals t1 or is on opposite side of t1, to t0
bool Overruns( double trackTime ) const;
// Compute the NEW track time for the given one and a real duration,
// taking into account whether the schedule is for looping
double AdvancedTrackTime(
double trackTime, double realElapsed, double speed) const;
// Use the function above in the callback after consuming samples from the
// playback ring buffers, during usual straight or looping play
void TrackTimeUpdate(double realElapsed);
// Convert a nonnegative real duration to an increment of track time
// relative to mT0.
double TrackDuration(double realElapsed) const;
// Convert time between mT0 and argument to real duration, according to
// time track if one is given; result is always nonnegative
double RealDuration(double trackTime1) const;
// How much real time left?
double RealTimeRemaining() const;
// Advance the real time position
void RealTimeAdvance( double increment );
// Determine starting duration within the first pass -- sometimes not
// zero
void RealTimeInit( double trackTime );
void RealTimeRestart();
} mPlaybackSchedule;
/** \brief get the index of the supplied (named) recording device, or the
* device selected in the preferences if none given.
*
* Pure utility function, but it comes round a number of times in the code
* and would be neater done once. If the device isn't found, return the
* default device index.
*/
static int getRecordDevIndex(const wxString &devName = {});
/** \brief get the index of the device selected in the preferences.
*
* If the device isn't found, returns -1
*/
#if USE_PORTMIXER
static int getRecordSourceIndex(PxMixer *portMixer);
#endif
/** \brief get the index of the supplied (named) playback device, or the
* device selected in the preferences if none given.
*
* Pure utility function, but it comes round a number of times in the code
* and would be neater done once. If the device isn't found, return the
* default device index.
*/
static int getPlayDevIndex(const wxString &devName = {});
/** \brief Array of audio sample rates to try to use
*
* These are the rates we will check if a device supports, and is as long
* as I can think of (to try and work out what the card can do) */
static const int RatesToTry[];
/** \brief How many sample rates to try */
static const int NumRatesToTry;
};
#endif