2014-09-04 07:33:44 +00:00
/*
This file is part of Telegram Desktop ,
2018-01-03 10:23:14 +00:00
the official desktop application for the Telegram messaging service .
2014-09-04 07:33:44 +00:00
2018-01-03 10:23:14 +00:00
For license and copyright information please follow this link :
https : //github.com/telegramdesktop/tdesktop/blob/master/LEGAL
2014-09-04 07:33:44 +00:00
*/
2019-02-13 12:36:59 +00:00
# include "media/audio/media_audio.h"
2016-03-24 12:57:10 +00:00
2019-02-13 12:36:59 +00:00
# include "media/audio/media_audio_ffmpeg_loader.h"
# include "media/audio/media_child_ffmpeg_loader.h"
# include "media/audio/media_audio_loaders.h"
# include "media/audio/media_audio_track.h"
2019-02-21 14:57:00 +00:00
# include "media/streaming/media_streaming_utility.h"
2017-09-26 11:49:16 +00:00
# include "data/data_document.h"
2019-01-04 11:09:48 +00:00
# include "data/data_file_origin.h"
2019-04-25 12:45:15 +00:00
# include "data/data_session.h"
2017-01-24 21:24:39 +00:00
# include "platform/platform_audio.h"
2019-01-21 13:42:21 +00:00
# include "core/application.h"
2019-07-24 11:45:24 +00:00
# include "main/main_session.h"
2014-09-04 07:33:44 +00:00
# include <AL/al.h>
# include <AL/alc.h>
2015-06-03 12:18:46 +00:00
# include <AL/alext.h>
2016-08-30 05:24:16 +00:00
# include <numeric>
2017-01-19 08:24:43 +00:00
Q_DECLARE_METATYPE ( AudioMsgId ) ;
Q_DECLARE_METATYPE ( VoiceWaveform ) ;
2014-09-04 07:33:44 +00:00
namespace {
2015-05-29 18:52:43 +00:00
2017-05-18 20:18:59 +00:00
constexpr auto kSuppressRatioAll = 0.2 ;
constexpr auto kSuppressRatioSong = 0.05 ;
2019-02-21 13:40:09 +00:00
constexpr auto kWaveformCounterBufferSize = 256 * 1024 ;
QMutex AudioMutex ;
ALCdevice * AudioDevice = nullptr ;
ALCcontext * AudioContext = nullptr ;
2017-05-18 20:18:59 +00:00
auto VolumeMultiplierAll = 1. ;
auto VolumeMultiplierSong = 1. ;
2015-05-29 18:52:43 +00:00
2019-02-21 09:17:25 +00:00
// Value for AL_PITCH_SHIFTER_COARSE_TUNE effect, 0.5 <= speed <= 2.
int CoarseTuneForSpeed ( float64 speed ) {
Expects ( speed > = 0.5 & & speed < = 2. ) ;
constexpr auto kTuneSteps = 12 ;
const auto tuneRatio = std : : log ( speed ) / std : : log ( 2. ) ;
return - int ( std : : round ( kTuneSteps * tuneRatio ) ) ;
}
2017-01-19 08:24:43 +00:00
} // namespace
2015-06-30 21:07:05 +00:00
2017-01-19 08:24:43 +00:00
namespace Media {
2017-05-03 11:36:39 +00:00
namespace Audio {
2017-01-19 08:24:43 +00:00
namespace {
2014-09-04 07:33:44 +00:00
2017-05-03 11:36:39 +00:00
Player : : Mixer * MixerInstance = nullptr ;
// Thread: Any.
bool ContextErrorHappened ( ) {
ALenum errCode ;
if ( ( errCode = alcGetError ( AudioDevice ) ) ! = ALC_NO_ERROR ) {
LOG ( ( " Audio Context Error: %1, %2 " ) . arg ( errCode ) . arg ( ( const char * ) alcGetString ( AudioDevice , errCode ) ) ) ;
return true ;
}
return false ;
}
// Thread: Any.
bool PlaybackErrorHappened ( ) {
ALenum errCode ;
if ( ( errCode = alGetError ( ) ) ! = AL_NO_ERROR ) {
LOG ( ( " Audio Playback Error: %1, %2 " ) . arg ( errCode ) . arg ( ( const char * ) alGetString ( errCode ) ) ) ;
return true ;
}
return false ;
}
void EnumeratePlaybackDevices ( ) {
auto deviceNames = QStringList ( ) ;
auto devices = alcGetString ( nullptr , ALC_DEVICE_SPECIFIER ) ;
2017-08-17 09:06:26 +00:00
Assert ( devices ! = nullptr ) ;
2017-05-03 11:36:39 +00:00
while ( * devices ! = 0 ) {
auto deviceName8Bit = QByteArray ( devices ) ;
auto deviceName = QString : : fromLocal8Bit ( deviceName8Bit ) ;
deviceNames . append ( deviceName ) ;
devices + = deviceName8Bit . size ( ) + 1 ;
}
LOG ( ( " Audio Playback Devices: %1 " ) . arg ( deviceNames . join ( ' ; ' ) ) ) ;
if ( auto device = alcGetString ( nullptr , ALC_DEFAULT_DEVICE_SPECIFIER ) ) {
LOG ( ( " Audio Playback Default Device: %1 " ) . arg ( QString : : fromLocal8Bit ( device ) ) ) ;
} else {
LOG ( ( " Audio Playback Default Device: (null) " ) ) ;
}
}
void EnumerateCaptureDevices ( ) {
auto deviceNames = QStringList ( ) ;
auto devices = alcGetString ( nullptr , ALC_CAPTURE_DEVICE_SPECIFIER ) ;
2017-08-17 09:06:26 +00:00
Assert ( devices ! = nullptr ) ;
2017-05-03 11:36:39 +00:00
while ( * devices ! = 0 ) {
auto deviceName8Bit = QByteArray ( devices ) ;
auto deviceName = QString : : fromLocal8Bit ( deviceName8Bit ) ;
deviceNames . append ( deviceName ) ;
devices + = deviceName8Bit . size ( ) + 1 ;
}
LOG ( ( " Audio Capture Devices: %1 " ) . arg ( deviceNames . join ( ' ; ' ) ) ) ;
if ( auto device = alcGetString ( nullptr , ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER ) ) {
LOG ( ( " Audio Capture Default Device: %1 " ) . arg ( QString : : fromLocal8Bit ( device ) ) ) ;
} else {
LOG ( ( " Audio Capture Default Device: (null) " ) ) ;
}
}
// Thread: Any. Must be locked: AudioMutex.
void DestroyPlaybackDevice ( ) {
if ( AudioContext ) {
alcMakeContextCurrent ( nullptr ) ;
alcDestroyContext ( AudioContext ) ;
AudioContext = nullptr ;
}
if ( AudioDevice ) {
alcCloseDevice ( AudioDevice ) ;
AudioDevice = nullptr ;
}
}
// Thread: Any. Must be locked: AudioMutex.
bool CreatePlaybackDevice ( ) {
if ( AudioDevice ) return true ;
AudioDevice = alcOpenDevice ( nullptr ) ;
if ( ! AudioDevice ) {
LOG ( ( " Audio Error: Could not create default playback device, enumerating.. " ) ) ;
EnumeratePlaybackDevices ( ) ;
return false ;
}
2017-12-02 09:27:19 +00:00
ALCint attributes [ ] = {
ALC_STEREO_SOURCES , 128 ,
ALC_FREQUENCY , Media : : Player : : kDefaultFrequency ,
0
} ;
2017-05-03 11:36:39 +00:00
AudioContext = alcCreateContext ( AudioDevice , attributes ) ;
alcMakeContextCurrent ( AudioContext ) ;
if ( ContextErrorHappened ( ) ) {
DestroyPlaybackDevice ( ) ;
return false ;
}
ALfloat v [ ] = { 0.f , 0.f , - 1.f , 0.f , 1.f , 0.f } ;
alListener3f ( AL_POSITION , 0.f , 0.f , 0.f ) ;
alListener3f ( AL_VELOCITY , 0.f , 0.f , 0.f ) ;
alListenerfv ( AL_ORIENTATION , v ) ;
alDistanceModel ( AL_NONE ) ;
return true ;
}
2017-01-24 21:24:39 +00:00
2017-05-03 11:36:39 +00:00
// Thread: Main. Must be locked: AudioMutex.
2019-02-01 07:09:55 +00:00
void ClosePlaybackDevice ( not_null < Instance * > instance ) {
2017-01-19 08:24:43 +00:00
if ( ! AudioDevice ) return ;
2014-09-04 07:33:44 +00:00
2017-05-03 11:36:39 +00:00
LOG ( ( " Audio Info: Closing audio playback device. " ) ) ;
2018-10-26 15:43:24 +00:00
2017-05-03 11:36:39 +00:00
if ( Player : : mixer ( ) ) {
Player : : mixer ( ) - > detachTracks ( ) ;
2014-09-04 07:33:44 +00:00
}
2019-02-01 07:09:55 +00:00
instance - > detachTracks ( ) ;
2014-09-04 07:33:44 +00:00
2017-05-03 11:36:39 +00:00
DestroyPlaybackDevice ( ) ;
2017-01-19 08:24:43 +00:00
}
2015-06-30 21:07:05 +00:00
2017-01-19 08:24:43 +00:00
} // namespace
2015-06-30 21:07:05 +00:00
2017-05-03 11:36:39 +00:00
// Thread: Main.
2019-02-01 07:09:55 +00:00
void Start ( not_null < Instance * > instance ) {
2017-08-17 09:06:26 +00:00
Assert ( AudioDevice = = nullptr ) ;
2014-09-04 07:33:44 +00:00
2015-06-30 21:07:05 +00:00
qRegisterMetaType < AudioMsgId > ( ) ;
2016-02-12 16:35:06 +00:00
qRegisterMetaType < VoiceWaveform > ( ) ;
2015-06-30 21:07:05 +00:00
2017-01-27 07:08:59 +00:00
auto loglevel = getenv ( " ALSOFT_LOGLEVEL " ) ;
2017-01-30 15:27:13 +00:00
LOG ( ( " OpenAL Logging Level: %1 " ) . arg ( loglevel ? loglevel : " (not set) " ) ) ;
2017-01-27 07:08:59 +00:00
2017-01-19 08:24:43 +00:00
EnumeratePlaybackDevices ( ) ;
EnumerateCaptureDevices ( ) ;
2017-01-24 21:24:39 +00:00
2019-02-01 07:09:55 +00:00
MixerInstance = new Player : : Mixer ( instance ) ;
2017-01-24 21:24:39 +00:00
Platform : : Audio : : Init ( ) ;
2014-09-04 07:33:44 +00:00
}
2017-05-03 11:36:39 +00:00
// Thread: Main.
2019-02-01 07:09:55 +00:00
void Finish ( not_null < Instance * > instance ) {
2017-01-24 21:24:39 +00:00
Platform : : Audio : : DeInit ( ) ;
2017-06-28 12:03:13 +00:00
// MixerInstance variable should be modified under AudioMutex protection.
// So it is modified in the ~Mixer() destructor after all tracks are cleared.
delete MixerInstance ;
2017-05-03 11:36:39 +00:00
// No sync required already.
2019-02-01 07:09:55 +00:00
ClosePlaybackDevice ( instance ) ;
2017-01-19 08:24:43 +00:00
}
2014-09-04 07:33:44 +00:00
2017-05-03 11:36:39 +00:00
// Thread: Main. Locks: AudioMutex.
bool IsAttachedToDevice ( ) {
QMutexLocker lock ( & AudioMutex ) ;
return ( AudioDevice ! = nullptr ) ;
2014-09-04 07:33:44 +00:00
}
2017-05-03 11:36:39 +00:00
// Thread: Any. Must be locked: AudioMutex.
bool AttachToDevice ( ) {
if ( AudioDevice ) {
return true ;
}
LOG ( ( " Audio Info: recreating audio device and reattaching the tracks " ) ) ;
2014-09-04 07:33:44 +00:00
2017-05-03 11:36:39 +00:00
CreatePlaybackDevice ( ) ;
2017-01-19 08:24:43 +00:00
if ( ! AudioDevice ) {
return false ;
2014-09-04 07:33:44 +00:00
}
2017-01-19 08:24:43 +00:00
2017-05-03 11:36:39 +00:00
if ( auto m = Player : : mixer ( ) ) {
m - > reattachTracks ( ) ;
emit m - > faderOnTimer ( ) ;
2014-09-04 07:33:44 +00:00
}
2017-12-30 21:28:38 +00:00
crl : : on_main ( [ ] {
2019-01-21 13:42:21 +00:00
if ( ! App : : quitting ( ) ) {
2017-12-30 21:28:38 +00:00
Current ( ) . reattachTracks ( ) ;
}
2017-05-03 11:36:39 +00:00
} ) ;
2017-01-19 08:24:43 +00:00
return true ;
}
2017-05-03 11:36:39 +00:00
void ScheduleDetachFromDeviceSafe ( ) {
2017-12-30 21:28:38 +00:00
crl : : on_main ( [ ] {
2019-01-21 13:42:21 +00:00
if ( ! App : : quitting ( ) ) {
2017-12-30 21:28:38 +00:00
Current ( ) . scheduleDetachFromDevice ( ) ;
}
2017-05-03 11:36:39 +00:00
} ) ;
2017-01-24 21:24:39 +00:00
}
2017-05-03 11:36:39 +00:00
void ScheduleDetachIfNotUsedSafe ( ) {
2017-12-30 21:28:38 +00:00
crl : : on_main ( [ ] {
2019-01-21 13:42:21 +00:00
if ( ! App : : quitting ( ) ) {
2017-12-30 21:28:38 +00:00
Current ( ) . scheduleDetachIfNotUsed ( ) ;
}
2017-05-03 11:36:39 +00:00
} ) ;
}
void StopDetachIfNotUsedSafe ( ) {
2017-12-30 21:28:38 +00:00
crl : : on_main ( [ ] {
2019-01-21 13:42:21 +00:00
if ( ! App : : quitting ( ) ) {
2017-12-30 21:28:38 +00:00
Current ( ) . stopDetachIfNotUsed ( ) ;
}
2017-05-03 11:36:39 +00:00
} ) ;
2017-01-24 21:24:39 +00:00
}
2019-02-21 16:01:55 +00:00
bool SupportsSpeedControl ( ) {
# ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
return true ;
# else // TDESKTOP_DISABLE_OPENAL_EFFECTS
return false ;
# endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
}
2017-05-03 11:36:39 +00:00
} // namespace Audio
namespace Player {
namespace {
2017-05-18 20:18:59 +00:00
constexpr auto kVolumeRound = 10000 ;
2017-12-02 09:27:19 +00:00
constexpr auto kPreloadSamples = 2LL * kDefaultFrequency ; // preload next part if less than 2 seconds remains
2019-02-19 06:57:53 +00:00
constexpr auto kFadeDuration = crl : : time ( 500 ) ;
constexpr auto kCheckPlaybackPositionTimeout = crl : : time ( 100 ) ; // 100ms per check audio position
2017-05-03 11:36:39 +00:00
constexpr auto kCheckPlaybackPositionDelta = 2400LL ; // update position called each 2400 samples
2019-02-19 06:57:53 +00:00
constexpr auto kCheckFadingTimeout = crl : : time ( 7 ) ; // 7ms
2017-05-03 11:36:39 +00:00
base : : Observable < AudioMsgId > UpdatedObservable ;
} // namespace
base : : Observable < AudioMsgId > & Updated ( ) {
return UpdatedObservable ;
}
2017-05-18 20:18:59 +00:00
// Thread: Any. Must be locked: AudioMutex.
2017-01-24 21:24:39 +00:00
float64 ComputeVolume ( AudioMsgId : : Type type ) {
switch ( type ) {
2017-05-18 20:18:59 +00:00
case AudioMsgId : : Type : : Voice : return VolumeMultiplierAll ;
case AudioMsgId : : Type : : Song : return VolumeMultiplierSong * mixer ( ) - > getSongVolume ( ) ;
case AudioMsgId : : Type : : Video : return mixer ( ) - > getVideoVolume ( ) ;
2017-01-24 21:24:39 +00:00
}
return 1. ;
}
2017-01-19 08:24:43 +00:00
Mixer * mixer ( ) {
2017-05-03 11:36:39 +00:00
return Audio : : MixerInstance ;
2014-09-04 07:33:44 +00:00
}
2018-11-08 13:06:22 +00:00
void Mixer : : Track : : createStream ( AudioMsgId : : Type type ) {
2017-01-24 21:24:39 +00:00
alGenSources ( 1 , & stream . source ) ;
alSourcef ( stream . source , AL_PITCH , 1.f ) ;
alSource3f ( stream . source , AL_POSITION , 0 , 0 , 0 ) ;
alSource3f ( stream . source , AL_VELOCITY , 0 , 0 , 0 ) ;
alSourcei ( stream . source , AL_LOOPING , 0 ) ;
2018-11-13 15:15:12 +00:00
alSourcei ( stream . source , AL_DIRECT_CHANNELS_SOFT , 1 ) ;
2017-01-24 21:24:39 +00:00
alGenBuffers ( 3 , stream . buffers ) ;
2019-02-21 16:01:55 +00:00
# ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
2019-03-26 12:50:00 +00:00
if ( speedEffect ) {
2019-02-21 16:01:55 +00:00
applySourceSpeedEffect ( ) ;
} else {
removeSourceSpeedEffect ( ) ;
}
2019-03-26 12:50:00 +00:00
# endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
2019-02-21 16:01:55 +00:00
}
# ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
void Mixer : : Track : : removeSourceSpeedEffect ( ) {
alSource3i ( stream . source , AL_AUXILIARY_SEND_FILTER , AL_EFFECTSLOT_NULL , 0 , 0 ) ;
alSourcei ( stream . source , AL_DIRECT_FILTER , AL_FILTER_NULL ) ;
2019-03-26 12:50:00 +00:00
alSourcef ( stream . source , AL_PITCH , 1.f ) ;
2019-02-21 16:01:55 +00:00
}
void Mixer : : Track : : applySourceSpeedEffect ( ) {
Expects ( speedEffect ! = nullptr ) ;
if ( ! speedEffect - > effect | | ! alIsEffect ( speedEffect - > effect ) ) {
2019-02-21 09:17:25 +00:00
alGenAuxiliaryEffectSlots ( 1 , & speedEffect - > effectSlot ) ;
alGenEffects ( 1 , & speedEffect - > effect ) ;
alGenFilters ( 1 , & speedEffect - > filter ) ;
alEffecti ( speedEffect - > effect , AL_EFFECT_TYPE , AL_EFFECT_PITCH_SHIFTER ) ;
alFilteri ( speedEffect - > filter , AL_FILTER_TYPE , AL_FILTER_LOWPASS ) ;
alFilterf ( speedEffect - > filter , AL_LOWPASS_GAIN , 0.f ) ;
2018-11-08 13:06:22 +00:00
}
2019-02-21 16:01:55 +00:00
alEffecti ( speedEffect - > effect , AL_PITCH_SHIFTER_COARSE_TUNE , speedEffect - > coarseTune ) ;
alAuxiliaryEffectSloti ( speedEffect - > effectSlot , AL_EFFECTSLOT_EFFECT , speedEffect - > effect ) ;
alSourcef ( stream . source , AL_PITCH , speedEffect - > speed ) ;
alSource3i ( stream . source , AL_AUXILIARY_SEND_FILTER , speedEffect - > effectSlot , 0 , 0 ) ;
alSourcei ( stream . source , AL_DIRECT_FILTER , speedEffect - > filter ) ;
2017-01-24 21:24:39 +00:00
}
2019-02-21 16:01:55 +00:00
# endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
2017-01-24 21:24:39 +00:00
void Mixer : : Track : : destroyStream ( ) {
if ( isStreamCreated ( ) ) {
alDeleteBuffers ( 3 , stream . buffers ) ;
alDeleteSources ( 1 , & stream . source ) ;
}
stream . source = 0 ;
for ( auto i = 0 ; i ! = 3 ; + + i ) {
stream . buffers [ i ] = 0 ;
}
2019-02-21 16:01:55 +00:00
# ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
2019-02-21 14:57:00 +00:00
resetSpeedEffect ( ) ;
2019-02-21 16:01:55 +00:00
# endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
2019-02-21 09:17:25 +00:00
}
2019-02-21 16:01:55 +00:00
# ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
2019-02-21 14:57:00 +00:00
void Mixer : : Track : : resetSpeedEffect ( ) {
2019-02-21 09:17:25 +00:00
if ( ! speedEffect ) {
return ;
2019-02-21 16:01:55 +00:00
} else if ( speedEffect - > effect & & alIsEffect ( speedEffect - > effect ) ) {
if ( isStreamCreated ( ) ) {
removeSourceSpeedEffect ( ) ;
}
2019-02-21 09:17:25 +00:00
alDeleteEffects ( 1 , & speedEffect - > effect ) ;
alDeleteAuxiliaryEffectSlots ( 1 , & speedEffect - > effectSlot ) ;
alDeleteFilters ( 1 , & speedEffect - > filter ) ;
}
2019-02-21 14:57:00 +00:00
speedEffect - > effect = speedEffect - > effectSlot = speedEffect - > filter = 0 ;
2017-01-24 21:24:39 +00:00
}
2019-02-21 16:01:55 +00:00
# endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
2017-01-24 21:24:39 +00:00
void Mixer : : Track : : reattach ( AudioMsgId : : Type type ) {
2019-02-28 21:03:25 +00:00
if ( isStreamCreated ( )
| | ( ! samplesCount [ 0 ] & & ! state . id . externalPlayId ( ) ) ) {
2017-01-24 21:24:39 +00:00
return ;
}
2018-11-08 13:06:22 +00:00
createStream ( type ) ;
2017-01-24 21:24:39 +00:00
for ( auto i = 0 ; i ! = kBuffersCount ; + + i ) {
if ( ! samplesCount [ i ] ) {
break ;
}
alBufferData ( stream . buffers [ i ] , format , bufferSamples [ i ] . constData ( ) , bufferSamples [ i ] . size ( ) , frequency ) ;
alSourceQueueBuffers ( stream . source , 1 , stream . buffers + i ) ;
}
alSourcei ( stream . source , AL_SAMPLE_OFFSET , qMax ( state . position - bufferedPosition , 0LL ) ) ;
2019-02-22 14:28:10 +00:00
if ( ! IsStopped ( state . state )
& & ( state . state ! = State : : PausedAtEnd )
& & ! state . waitingForData ) {
2017-01-24 21:24:39 +00:00
alSourcef ( stream . source , AL_GAIN , ComputeVolume ( type ) ) ;
alSourcePlay ( stream . source ) ;
2017-05-18 17:20:07 +00:00
if ( IsPaused ( state . state ) ) {
// We must always start the source if we want the AL_SAMPLE_OFFSET to be applied.
// Otherwise it won't be read by alGetSource and we'll get a corrupt position.
// So in case of a paused source we start it and then immediately pause it.
alSourcePause ( stream . source ) ;
}
2017-01-24 21:24:39 +00:00
}
}
void Mixer : : Track : : detach ( ) {
2019-02-22 14:28:10 +00:00
getNotQueuedBufferIndex ( ) ;
2017-01-24 21:24:39 +00:00
resetStream ( ) ;
destroyStream ( ) ;
}
void Mixer : : Track : : clear ( ) {
detach ( ) ;
state = TrackState ( ) ;
2015-11-26 17:34:52 +00:00
file = FileLocation ( ) ;
2015-11-24 16:19:18 +00:00
data = QByteArray ( ) ;
2017-01-24 21:24:39 +00:00
bufferedPosition = 0 ;
bufferedLength = 0 ;
2015-11-24 16:19:18 +00:00
loading = false ;
2017-01-24 21:24:39 +00:00
loaded = false ;
fadeStartPosition = 0 ;
format = 0 ;
frequency = kDefaultFrequency ;
for ( int i = 0 ; i ! = kBuffersCount ; + + i ) {
samplesCount [ i ] = 0 ;
bufferSamples [ i ] = QByteArray ( ) ;
2015-11-24 16:19:18 +00:00
}
2016-07-05 17:44:02 +00:00
2019-02-28 21:03:25 +00:00
setExternalData ( nullptr ) ;
2017-05-18 20:18:59 +00:00
lastUpdateWhen = 0 ;
2019-02-21 11:15:44 +00:00
lastUpdatePosition = 0 ;
2015-11-24 16:19:18 +00:00
}
2017-01-24 21:24:39 +00:00
void Mixer : : Track : : started ( ) {
resetStream ( ) ;
bufferedPosition = 0 ;
bufferedLength = 0 ;
loaded = false ;
fadeStartPosition = 0 ;
format = 0 ;
frequency = kDefaultFrequency ;
for ( auto i = 0 ; i ! = kBuffersCount ; + + i ) {
samplesCount [ i ] = 0 ;
bufferSamples [ i ] = QByteArray ( ) ;
}
}
bool Mixer : : Track : : isStreamCreated ( ) const {
return alIsSource ( stream . source ) ;
}
2018-11-08 13:06:22 +00:00
void Mixer : : Track : : ensureStreamCreated ( AudioMsgId : : Type type ) {
2017-01-24 21:24:39 +00:00
if ( ! isStreamCreated ( ) ) {
2018-11-08 13:06:22 +00:00
createStream ( type ) ;
2017-01-24 21:24:39 +00:00
}
}
int Mixer : : Track : : getNotQueuedBufferIndex ( ) {
// See if there are no free buffers right now.
while ( samplesCount [ kBuffersCount - 1 ] ! = 0 ) {
// Try to unqueue some buffer.
ALint processed = 0 ;
alGetSourcei ( stream . source , AL_BUFFERS_PROCESSED , & processed ) ;
if ( processed < 1 ) { // No processed buffers, wait.
return - 1 ;
}
// Unqueue some processed buffer.
ALuint buffer = 0 ;
alSourceUnqueueBuffers ( stream . source , 1 , & buffer ) ;
// Find it in the list and clear it.
bool found = false ;
for ( auto i = 0 ; i ! = kBuffersCount ; + + i ) {
if ( stream . buffers [ i ] = = buffer ) {
auto samplesInBuffer = samplesCount [ i ] ;
bufferedPosition + = samplesInBuffer ;
bufferedLength - = samplesInBuffer ;
for ( auto j = i + 1 ; j ! = kBuffersCount ; + + j ) {
samplesCount [ j - 1 ] = samplesCount [ j ] ;
stream . buffers [ j - 1 ] = stream . buffers [ j ] ;
bufferSamples [ j - 1 ] = bufferSamples [ j ] ;
}
samplesCount [ kBuffersCount - 1 ] = 0 ;
stream . buffers [ kBuffersCount - 1 ] = buffer ;
bufferSamples [ kBuffersCount - 1 ] = QByteArray ( ) ;
found = true ;
break ;
}
}
if ( ! found ) {
LOG ( ( " Audio Error: Could not find the unqueued buffer! Buffer %1 in source %2 with processed count %3 " ) . arg ( buffer ) . arg ( stream . source ) . arg ( processed ) ) ;
return - 1 ;
}
}
for ( auto i = 0 ; i ! = kBuffersCount ; + + i ) {
if ( ! samplesCount [ i ] ) {
return i ;
}
}
return - 1 ;
}
2019-02-28 21:03:25 +00:00
void Mixer : : Track : : setExternalData (
std : : unique_ptr < ExternalSoundData > data ) {
2019-02-21 16:01:55 +00:00
# ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
changeSpeedEffect ( data ? data - > speed : 1. ) ;
# endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
2019-02-28 21:03:25 +00:00
externalData = std : : move ( data ) ;
2019-02-21 16:01:55 +00:00
}
# ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
void Mixer : : Track : : changeSpeedEffect ( float64 speed ) {
if ( speed ! = 1. ) {
if ( ! speedEffect ) {
speedEffect = std : : make_unique < SpeedEffect > ( ) ;
}
speedEffect - > speed = speed ;
speedEffect - > coarseTune = CoarseTuneForSpeed ( speed ) ;
if ( isStreamCreated ( ) ) {
applySourceSpeedEffect ( ) ;
}
} else if ( speedEffect ) {
resetSpeedEffect ( ) ;
2019-02-21 14:57:00 +00:00
speedEffect = nullptr ;
2019-02-21 09:17:25 +00:00
}
}
2019-02-21 16:01:55 +00:00
# endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
2019-02-21 09:17:25 +00:00
2017-01-24 21:24:39 +00:00
void Mixer : : Track : : resetStream ( ) {
if ( isStreamCreated ( ) ) {
alSourceStop ( stream . source ) ;
alSourcei ( stream . source , AL_BUFFER , AL_NONE ) ;
}
}
2017-05-18 20:18:59 +00:00
Mixer : : Track : : ~ Track ( ) = default ;
2019-02-01 07:09:55 +00:00
Mixer : : Mixer ( not_null < Audio : : Instance * > instance )
: _instance ( instance )
, _volumeVideo ( kVolumeRound )
2017-05-18 20:18:59 +00:00
, _volumeSong ( kVolumeRound )
2017-04-15 19:51:53 +00:00
, _fader ( new Fader ( & _faderThread ) )
2017-01-19 08:24:43 +00:00
, _loader ( new Loaders ( & _loaderThread ) ) {
2017-01-24 21:24:39 +00:00
connect ( this , SIGNAL ( faderOnTimer ( ) ) , _fader , SLOT ( onTimer ( ) ) , Qt : : QueuedConnection ) ;
2015-06-30 21:07:05 +00:00
connect ( this , SIGNAL ( suppressSong ( ) ) , _fader , SLOT ( onSuppressSong ( ) ) ) ;
connect ( this , SIGNAL ( unsuppressSong ( ) ) , _fader , SLOT ( onUnsuppressSong ( ) ) ) ;
2017-05-03 13:01:15 +00:00
connect ( this , SIGNAL ( suppressAll ( qint64 ) ) , _fader , SLOT ( onSuppressAll ( qint64 ) ) ) ;
2016-10-12 19:34:25 +00:00
subscribe ( Global : : RefSongVolumeChanged ( ) , [ this ] {
QMetaObject : : invokeMethod ( _fader , " onSongVolumeChanged " ) ;
} ) ;
subscribe ( Global : : RefVideoVolumeChanged ( ) , [ this ] {
QMetaObject : : invokeMethod ( _fader , " onVideoVolumeChanged " ) ;
} ) ;
2017-01-19 08:24:43 +00:00
connect ( this , SIGNAL ( loaderOnStart ( const AudioMsgId & , qint64 ) ) , _loader , SLOT ( onStart ( const AudioMsgId & , qint64 ) ) ) ;
2015-06-30 21:07:05 +00:00
connect ( this , SIGNAL ( loaderOnCancel ( const AudioMsgId & ) ) , _loader , SLOT ( onCancel ( const AudioMsgId & ) ) ) ;
2014-09-04 07:33:44 +00:00
connect ( _loader , SIGNAL ( needToCheck ( ) ) , _fader , SLOT ( onTimer ( ) ) ) ;
2015-06-30 21:07:05 +00:00
connect ( _loader , SIGNAL ( error ( const AudioMsgId & ) ) , this , SLOT ( onError ( const AudioMsgId & ) ) ) ;
connect ( _fader , SIGNAL ( needToPreload ( const AudioMsgId & ) ) , _loader , SLOT ( onLoad ( const AudioMsgId & ) ) ) ;
connect ( _fader , SIGNAL ( playPositionUpdated ( const AudioMsgId & ) ) , this , SIGNAL ( updated ( const AudioMsgId & ) ) ) ;
connect ( _fader , SIGNAL ( audioStopped ( const AudioMsgId & ) ) , this , SLOT ( onStopped ( const AudioMsgId & ) ) ) ;
connect ( _fader , SIGNAL ( error ( const AudioMsgId & ) ) , this , SLOT ( onError ( const AudioMsgId & ) ) ) ;
2016-07-10 13:02:22 +00:00
connect ( this , SIGNAL ( stoppedOnError ( const AudioMsgId & ) ) , this , SIGNAL ( updated ( const AudioMsgId & ) ) , Qt : : QueuedConnection ) ;
2016-09-21 11:44:20 +00:00
connect ( this , SIGNAL ( updated ( const AudioMsgId & ) ) , this , SLOT ( onUpdated ( const AudioMsgId & ) ) ) ;
2016-10-12 19:34:25 +00:00
2014-09-04 07:33:44 +00:00
_loaderThread . start ( ) ;
_faderThread . start ( ) ;
}
2017-05-03 11:36:39 +00:00
// Thread: Main. Locks: AudioMutex.
2017-01-19 08:24:43 +00:00
Mixer : : ~ Mixer ( ) {
2014-09-04 07:33:44 +00:00
{
2017-01-19 08:24:43 +00:00
QMutexLocker lock ( & AudioMutex ) ;
2014-09-04 07:33:44 +00:00
2017-01-24 21:24:39 +00:00
for ( auto i = 0 ; i ! = kTogetherLimit ; + + i ) {
trackForType ( AudioMsgId : : Type : : Voice , i ) - > clear ( ) ;
trackForType ( AudioMsgId : : Type : : Song , i ) - > clear ( ) ;
2014-09-04 07:33:44 +00:00
}
2017-01-24 21:24:39 +00:00
_videoTrack . clear ( ) ;
2016-06-30 12:03:32 +00:00
2019-02-01 07:09:55 +00:00
Audio : : ClosePlaybackDevice ( _instance ) ;
2017-05-03 11:36:39 +00:00
Audio : : MixerInstance = nullptr ;
2014-09-04 07:33:44 +00:00
}
2016-07-05 17:44:02 +00:00
2014-09-04 07:33:44 +00:00
_faderThread . quit ( ) ;
_loaderThread . quit ( ) ;
_faderThread . wait ( ) ;
_loaderThread . wait ( ) ;
}
2017-01-19 08:24:43 +00:00
void Mixer : : onUpdated ( const AudioMsgId & audio ) {
2019-02-28 21:03:25 +00:00
if ( audio . externalPlayId ( ) ) {
externalSoundProgress ( audio ) ;
2016-09-21 11:44:20 +00:00
}
2017-01-19 08:24:43 +00:00
Media : : Player : : Updated ( ) . notify ( audio ) ;
2016-09-21 11:44:20 +00:00
}
2017-01-19 08:24:43 +00:00
void Mixer : : onError ( const AudioMsgId & audio ) {
2015-07-03 08:47:16 +00:00
emit stoppedOnError ( audio ) ;
2017-05-18 20:18:59 +00:00
QMutexLocker lock ( & AudioMutex ) ;
auto type = audio . type ( ) ;
if ( type = = AudioMsgId : : Type : : Voice ) {
if ( auto current = trackForType ( type ) ) {
if ( current - > state . id = = audio ) {
emit unsuppressSong ( ) ;
}
}
2016-06-30 12:03:32 +00:00
}
2015-06-30 21:07:05 +00:00
}
2017-01-19 08:24:43 +00:00
void Mixer : : onStopped ( const AudioMsgId & audio ) {
2016-07-10 13:02:22 +00:00
emit updated ( audio ) ;
2017-05-18 20:18:59 +00:00
QMutexLocker lock ( & AudioMutex ) ;
auto type = audio . type ( ) ;
if ( type = = AudioMsgId : : Type : : Voice ) {
if ( auto current = trackForType ( type ) ) {
if ( current - > state . id = = audio ) {
emit unsuppressSong ( ) ;
}
}
2016-06-30 12:03:32 +00:00
}
2014-09-04 07:33:44 +00:00
}
2017-01-24 21:24:39 +00:00
Mixer : : Track * Mixer : : trackForType ( AudioMsgId : : Type type , int index ) {
2016-07-23 06:39:46 +00:00
if ( index < 0 ) {
if ( auto indexPtr = currentIndex ( type ) ) {
index = * indexPtr ;
} else {
return nullptr ;
}
}
2016-06-30 12:03:32 +00:00
switch ( type ) {
2017-01-24 21:24:39 +00:00
case AudioMsgId : : Type : : Voice : return & _audioTracks [ index ] ;
case AudioMsgId : : Type : : Song : return & _songTracks [ index ] ;
case AudioMsgId : : Type : : Video : return & _videoTrack ;
2016-06-30 12:03:32 +00:00
}
return nullptr ;
2015-06-30 21:07:05 +00:00
}
2017-01-24 21:24:39 +00:00
const Mixer : : Track * Mixer : : trackForType ( AudioMsgId : : Type type , int index ) const {
return const_cast < Mixer * > ( this ) - > trackForType ( type , index ) ;
2016-06-30 12:03:32 +00:00
}
2017-01-19 08:24:43 +00:00
int * Mixer : : currentIndex ( AudioMsgId : : Type type ) {
2015-06-30 21:07:05 +00:00
switch ( type ) {
2016-06-30 12:03:32 +00:00
case AudioMsgId : : Type : : Voice : return & _audioCurrent ;
case AudioMsgId : : Type : : Song : return & _songCurrent ;
2016-07-05 17:44:02 +00:00
case AudioMsgId : : Type : : Video : { static int videoIndex = 0 ; return & videoIndex ; }
2015-06-30 21:07:05 +00:00
}
2016-06-30 12:03:32 +00:00
return nullptr ;
}
2017-01-19 08:24:43 +00:00
const int * Mixer : : currentIndex ( AudioMsgId : : Type type ) const {
return const_cast < Mixer * > ( this ) - > currentIndex ( type ) ;
2016-06-30 12:03:32 +00:00
}
2017-01-24 21:24:39 +00:00
void Mixer : : resetFadeStartPosition ( AudioMsgId : : Type type , int positionInBuffered ) {
auto track = trackForType ( type ) ;
if ( ! track ) return ;
if ( positionInBuffered < 0 ) {
2017-05-03 11:36:39 +00:00
Audio : : AttachToDevice ( ) ;
2017-01-24 21:24:39 +00:00
if ( track - > isStreamCreated ( ) ) {
2019-02-22 14:28:10 +00:00
ALint alSampleOffset = 0 ;
ALint alState = AL_INITIAL ;
alGetSourcei ( track - > stream . source , AL_SAMPLE_OFFSET , & alSampleOffset ) ;
alGetSourcei ( track - > stream . source , AL_SOURCE_STATE , & alState ) ;
2017-05-03 11:36:39 +00:00
if ( Audio : : PlaybackErrorHappened ( ) ) {
2017-01-24 21:24:39 +00:00
setStoppedState ( track , State : : StoppedAtError ) ;
onError ( track - > state . id ) ;
return ;
2019-02-22 14:28:10 +00:00
} else if ( ( alState = = AL_STOPPED )
& & ( alSampleOffset = = 0 )
& & ! internal : : CheckAudioDeviceConnected ( ) ) {
2017-01-24 21:24:39 +00:00
track - > fadeStartPosition = track - > state . position ;
return ;
}
2015-06-30 21:07:05 +00:00
2019-02-27 11:36:19 +00:00
const auto stoppedAtEnd = track - > state . waitingForData
| | ( ( alState = = AL_STOPPED )
& & ( ! IsStopped ( track - > state . state )
| | IsStoppedAtEnd ( track - > state . state ) ) ) ;
2019-02-22 14:28:10 +00:00
positionInBuffered = stoppedAtEnd
? track - > bufferedLength
: alSampleOffset ;
2014-09-04 07:33:44 +00:00
} else {
2017-01-24 21:24:39 +00:00
positionInBuffered = 0 ;
2015-06-30 21:07:05 +00:00
}
2014-09-04 07:33:44 +00:00
}
2019-02-28 21:03:25 +00:00
auto fullPosition = track - > samplesCount [ 0 ]
? ( track - > bufferedPosition + positionInBuffered )
: track - > state . position ;
2017-01-24 21:24:39 +00:00
track - > state . position = fullPosition ;
track - > fadeStartPosition = fullPosition ;
2014-09-04 07:33:44 +00:00
}
2017-01-19 08:24:43 +00:00
bool Mixer : : fadedStop ( AudioMsgId : : Type type , bool * fadedStart ) {
2017-01-24 21:24:39 +00:00
auto current = trackForType ( type ) ;
2015-06-30 21:07:05 +00:00
if ( ! current ) return false ;
2014-09-04 07:33:44 +00:00
2017-01-24 21:24:39 +00:00
switch ( current - > state . state ) {
case State : : Starting :
case State : : Resuming :
case State : : Playing : {
2017-05-21 13:16:39 +00:00
current - > state . state = State : : Stopping ;
2017-01-24 21:24:39 +00:00
resetFadeStartPosition ( type ) ;
if ( fadedStart ) * fadedStart = true ;
} break ;
case State : : Pausing : {
2017-05-21 13:16:39 +00:00
current - > state . state = State : : Stopping ;
2017-01-24 21:24:39 +00:00
if ( fadedStart ) * fadedStart = true ;
} break ;
case State : : Paused :
case State : : PausedAtEnd : {
setStoppedState ( current ) ;
} return true ;
2015-06-30 21:07:05 +00:00
}
return false ;
}
2017-12-10 08:52:38 +00:00
void Mixer : : play (
const AudioMsgId & audio ,
2019-02-28 21:03:25 +00:00
std : : unique_ptr < ExternalSoundData > externalData ,
2019-02-19 06:57:53 +00:00
crl : : time positionMs ) {
2019-03-12 05:09:53 +00:00
Expects ( externalData ! = nullptr ) ;
Expects ( audio . externalPlayId ( ) ! = 0 ) ;
setSongVolume ( Global : : SongVolume ( ) ) ;
setVideoVolume ( Global : : VideoVolume ( ) ) ;
2017-05-18 20:18:59 +00:00
2016-06-30 12:03:32 +00:00
auto type = audio . type ( ) ;
2015-06-30 21:07:05 +00:00
AudioMsgId stopped ;
2016-10-12 19:34:25 +00:00
auto notLoadedYet = false ;
2015-05-30 16:30:47 +00:00
{
2017-01-19 08:24:43 +00:00
QMutexLocker lock ( & AudioMutex ) ;
2017-05-03 11:36:39 +00:00
Audio : : AttachToDevice ( ) ;
2017-01-24 21:24:39 +00:00
if ( ! AudioDevice ) return ;
2015-05-30 16:30:47 +00:00
2017-05-18 20:18:59 +00:00
auto fadedStart = false ;
2017-01-24 21:24:39 +00:00
auto current = trackForType ( type ) ;
2016-06-30 12:03:32 +00:00
if ( ! current ) return ;
2017-01-24 21:24:39 +00:00
if ( current - > state . id ! = audio ) {
2016-06-30 12:03:32 +00:00
if ( fadedStop ( type , & fadedStart ) ) {
2017-01-24 21:24:39 +00:00
stopped = current - > state . id ;
2015-05-30 16:30:47 +00:00
}
2017-01-24 21:24:39 +00:00
if ( current - > state . id ) {
emit loaderOnCancel ( current - > state . id ) ;
2015-05-30 16:30:47 +00:00
emit faderOnTimer ( ) ;
}
2019-03-26 11:23:35 +00:00
if ( type ! = AudioMsgId : : Type : : Video ) {
2017-05-18 20:18:59 +00:00
auto foundCurrent = currentIndex ( type ) ;
auto index = 0 ;
for ( ; index ! = kTogetherLimit ; + + index ) {
if ( trackForType ( type , index ) - > state . id = = audio ) {
* foundCurrent = index ;
break ;
}
2015-06-30 21:07:05 +00:00
}
2017-05-18 20:18:59 +00:00
if ( index = = kTogetherLimit & & + + * foundCurrent > = kTogetherLimit ) {
* foundCurrent - = kTogetherLimit ;
}
current = trackForType ( type ) ;
2015-05-30 16:30:47 +00:00
}
}
2017-05-18 20:18:59 +00:00
2019-03-26 11:23:35 +00:00
current - > clear ( ) ; // Clear all previous state.
current - > state . id = audio ;
2017-05-18 20:18:59 +00:00
current - > lastUpdateWhen = 0 ;
2019-02-21 11:15:44 +00:00
current - > lastUpdatePosition = 0 ;
2019-02-28 21:03:25 +00:00
if ( externalData ) {
current - > setExternalData ( std : : move ( externalData ) ) ;
2017-05-18 20:18:59 +00:00
} else {
2019-02-28 21:03:25 +00:00
current - > setExternalData ( nullptr ) ;
2017-05-18 20:18:59 +00:00
current - > file = audio . audio ( ) - > location ( true ) ;
current - > data = audio . audio ( ) - > data ( ) ;
notLoadedYet = ( current - > file . isEmpty ( ) & & current - > data . isEmpty ( ) ) ;
}
if ( notLoadedYet ) {
2019-02-28 21:03:25 +00:00
auto newState = ( type = = AudioMsgId : : Type : : Song )
? State : : Stopped
: State : : StoppedAtError ;
2017-05-18 20:18:59 +00:00
setStoppedState ( current , newState ) ;
2015-07-03 08:47:16 +00:00
} else {
2017-12-10 08:52:38 +00:00
current - > state . position = ( positionMs * current - > state . frequency )
/ 1000LL ;
2019-02-28 21:03:25 +00:00
current - > state . state = current - > externalData
? State : : Paused
: fadedStart
? State : : Starting
: State : : Playing ;
2015-06-30 21:07:05 +00:00
current - > loading = true ;
2017-12-10 08:52:38 +00:00
emit loaderOnStart ( current - > state . id , positionMs ) ;
2016-06-30 12:03:32 +00:00
if ( type = = AudioMsgId : : Type : : Voice ) {
emit suppressSong ( ) ;
}
2014-09-04 07:33:44 +00:00
}
}
2016-10-12 19:34:25 +00:00
if ( notLoadedYet ) {
2017-05-18 20:18:59 +00:00
if ( type = = AudioMsgId : : Type : : Song | | type = = AudioMsgId : : Type : : Video ) {
2018-07-13 21:25:47 +00:00
DocumentOpenClickHandler : : Open (
audio . contextId ( ) ,
audio . audio ( ) ,
2019-04-25 12:45:15 +00:00
Auth ( ) . data ( ) . message ( audio . contextId ( ) ) ) ;
2016-10-12 19:34:25 +00:00
} else {
onError ( audio ) ;
}
}
if ( stopped ) {
emit updated ( stopped ) ;
}
2014-09-04 07:33:44 +00:00
}
2019-02-28 21:03:25 +00:00
void Mixer : : feedFromExternal ( ExternalSoundPart & & part ) {
_loader - > feedFromExternal ( std : : move ( part ) ) ;
2017-05-18 20:18:59 +00:00
}
2016-07-05 17:44:02 +00:00
2019-02-28 21:03:25 +00:00
void Mixer : : forceToBufferExternal ( const AudioMsgId & audioId ) {
_loader - > forceToBufferExternal ( audioId ) ;
2019-02-21 13:40:09 +00:00
}
2019-02-28 21:03:25 +00:00
void Mixer : : setSpeedFromExternal ( const AudioMsgId & audioId , float64 speed ) {
2019-02-21 16:01:55 +00:00
# ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
QMutexLocker lock ( & AudioMutex ) ;
2019-02-28 21:03:25 +00:00
const auto track = trackForType ( audioId . type ( ) ) ;
2019-02-21 16:01:55 +00:00
if ( track - > state . id = = audioId ) {
track - > changeSpeedEffect ( speed ) ;
}
# endif // TDESKTOP_DISABLE_OPENAL_EFFECTS
}
2019-02-28 21:03:25 +00:00
Streaming : : TimePoint Mixer : : getExternalSyncTimePoint (
2019-02-21 11:15:44 +00:00
const AudioMsgId & audio ) const {
2019-02-28 21:03:25 +00:00
Expects ( audio . externalPlayId ( ) ! = 0 ) ;
2019-02-21 11:15:44 +00:00
2019-02-21 16:01:55 +00:00
auto result = Streaming : : TimePoint ( ) ;
2019-02-28 21:03:25 +00:00
const auto type = audio . type ( ) ;
2019-02-21 11:15:44 +00:00
QMutexLocker lock ( & AudioMutex ) ;
2019-02-28 21:03:25 +00:00
const auto track = trackForType ( type ) ;
if ( track & & track - > state . id = = audio & & track - > lastUpdateWhen > 0 ) {
2019-02-21 14:57:00 +00:00
result . trackTime = track - > lastUpdatePosition ;
result . worldTime = track - > lastUpdateWhen ;
2019-02-21 11:15:44 +00:00
}
return result ;
}
2019-02-28 21:03:25 +00:00
crl : : time Mixer : : getExternalCorrectedTime ( const AudioMsgId & audio , crl : : time frameMs , crl : : time systemMs ) {
2017-05-18 20:18:59 +00:00
auto result = frameMs ;
2019-02-28 21:03:25 +00:00
const auto type = audio . type ( ) ;
2017-01-24 21:24:39 +00:00
2017-05-18 20:18:59 +00:00
QMutexLocker lock ( & AudioMutex ) ;
2019-02-28 21:03:25 +00:00
const auto track = trackForType ( type ) ;
2017-05-18 20:18:59 +00:00
if ( track & & track - > state . id = = audio & & track - > lastUpdateWhen > 0 ) {
2019-02-21 11:15:44 +00:00
result = static_cast < crl : : time > ( track - > lastUpdatePosition ) ;
2017-05-18 20:18:59 +00:00
if ( systemMs > track - > lastUpdateWhen ) {
result + = ( systemMs - track - > lastUpdateWhen ) ;
2016-07-14 11:20:46 +00:00
}
2017-05-18 20:18:59 +00:00
}
return result ;
}
2016-07-05 17:44:02 +00:00
2019-02-28 21:03:25 +00:00
void Mixer : : externalSoundProgress ( const AudioMsgId & audio ) {
const auto type = audio . type ( ) ;
2016-07-05 17:44:02 +00:00
2017-05-18 20:18:59 +00:00
QMutexLocker lock ( & AudioMutex ) ;
2019-02-28 21:03:25 +00:00
const auto current = trackForType ( type ) ;
2017-05-18 20:18:59 +00:00
if ( current & & current - > state . length & & current - > state . frequency ) {
if ( current - > state . id = = audio & & current - > state . state = = State : : Playing ) {
2019-02-19 06:57:53 +00:00
current - > lastUpdateWhen = crl : : now ( ) ;
2019-02-21 11:15:44 +00:00
current - > lastUpdatePosition = ( current - > state . position * 1000ULL ) / current - > state . frequency ;
2017-05-18 20:18:59 +00:00
}
2016-07-05 17:44:02 +00:00
}
}
2017-05-18 20:18:59 +00:00
bool Mixer : : checkCurrentALError ( AudioMsgId : : Type type ) {
if ( ! Audio : : PlaybackErrorHappened ( ) ) return true ;
2016-07-12 18:04:34 +00:00
2019-02-28 21:03:25 +00:00
const auto data = trackForType ( type ) ;
2017-05-18 20:18:59 +00:00
if ( ! data ) {
setStoppedState ( data , State : : StoppedAtError ) ;
onError ( data - > state . id ) ;
2016-07-12 18:04:34 +00:00
}
2017-05-18 20:18:59 +00:00
return false ;
2016-07-12 18:04:34 +00:00
}
2017-05-18 20:18:59 +00:00
void Mixer : : pause ( const AudioMsgId & audio , bool fast ) {
2016-07-13 11:24:31 +00:00
AudioMsgId current ;
{
2017-01-19 08:24:43 +00:00
QMutexLocker lock ( & AudioMutex ) ;
2017-05-18 20:18:59 +00:00
auto type = audio . type ( ) ;
2017-01-24 21:24:39 +00:00
auto track = trackForType ( type ) ;
2017-05-18 20:18:59 +00:00
if ( ! track | | track - > state . id ! = audio ) {
2016-07-13 11:24:31 +00:00
return ;
}
2017-01-24 21:24:39 +00:00
current = track - > state . id ;
switch ( track - > state . state ) {
case State : : Starting :
case State : : Resuming :
case State : : Playing : {
2017-05-18 20:18:59 +00:00
track - > state . state = fast ? State : : Paused : State : : Pausing ;
2017-01-24 21:24:39 +00:00
resetFadeStartPosition ( type ) ;
2017-05-18 20:18:59 +00:00
if ( type = = AudioMsgId : : Type : : Voice ) {
emit unsuppressSong ( ) ;
}
} break ;
2017-01-24 21:24:39 +00:00
2017-05-23 18:00:57 +00:00
case State : : Pausing :
2017-05-21 13:16:39 +00:00
case State : : Stopping : {
2017-05-18 20:18:59 +00:00
track - > state . state = fast ? State : : Paused : State : : Pausing ;
} break ;
}
2017-01-24 21:24:39 +00:00
2017-05-18 20:18:59 +00:00
if ( fast & & track - > isStreamCreated ( ) ) {
ALint state = AL_INITIAL ;
alGetSourcei ( track - > stream . source , AL_SOURCE_STATE , & state ) ;
if ( ! checkCurrentALError ( type ) ) return ;
if ( state = = AL_PLAYING ) {
alSourcePause ( track - > stream . source ) ;
if ( ! checkCurrentALError ( type ) ) return ;
2016-07-13 11:24:31 +00:00
}
}
2017-05-18 20:18:59 +00:00
2016-07-13 11:24:31 +00:00
emit faderOnTimer ( ) ;
2016-07-19 10:54:43 +00:00
2017-05-18 20:18:59 +00:00
track - > lastUpdateWhen = 0 ;
2019-02-21 11:15:44 +00:00
track - > lastUpdatePosition = 0 ;
2016-07-13 11:24:31 +00:00
}
if ( current ) emit updated ( current ) ;
}
2017-05-18 20:18:59 +00:00
void Mixer : : resume ( const AudioMsgId & audio , bool fast ) {
2016-07-13 11:24:31 +00:00
AudioMsgId current ;
{
2017-01-19 08:24:43 +00:00
QMutexLocker lock ( & AudioMutex ) ;
2017-05-18 20:18:59 +00:00
auto type = audio . type ( ) ;
2017-01-24 21:24:39 +00:00
auto track = trackForType ( type ) ;
2017-05-18 20:18:59 +00:00
if ( ! track | | track - > state . id ! = audio ) {
2016-07-13 11:24:31 +00:00
return ;
}
2017-01-24 21:24:39 +00:00
current = track - > state . id ;
switch ( track - > state . state ) {
case State : : Pausing :
case State : : Paused :
case State : : PausedAtEnd : {
if ( track - > state . state = = State : : Paused ) {
2017-05-03 11:36:39 +00:00
// This calls Audio::AttachToDevice().
2017-01-24 21:24:39 +00:00
resetFadeStartPosition ( type ) ;
} else {
2017-05-03 11:36:39 +00:00
Audio : : AttachToDevice ( ) ;
2016-07-13 11:24:31 +00:00
}
2017-05-18 20:18:59 +00:00
track - > state . state = fast ? State : : Playing : State : : Resuming ;
2016-07-13 11:24:31 +00:00
2017-01-24 21:24:39 +00:00
if ( track - > isStreamCreated ( ) ) {
// When starting the video audio is in paused state and
// gets resumed before the stream is created with any data.
ALint state = AL_INITIAL ;
alGetSourcei ( track - > stream . source , AL_SOURCE_STATE , & state ) ;
2016-07-13 11:24:31 +00:00
if ( ! checkCurrentALError ( type ) ) return ;
2017-01-24 21:24:39 +00:00
if ( state ! = AL_PLAYING ) {
if ( state = = AL_STOPPED & & ! internal : : CheckAudioDeviceConnected ( ) ) {
return ;
}
alSourcef ( track - > stream . source , AL_GAIN , ComputeVolume ( type ) ) ;
if ( ! checkCurrentALError ( type ) ) return ;
2019-02-22 11:58:26 +00:00
if ( state = = AL_STOPPED ) {
alSourcei ( track - > stream . source , AL_SAMPLE_OFFSET , qMax ( track - > state . position - track - > bufferedPosition , 0LL ) ) ;
if ( ! checkCurrentALError ( type ) ) return ;
}
2017-01-24 21:24:39 +00:00
alSourcePlay ( track - > stream . source ) ;
if ( ! checkCurrentALError ( type ) ) return ;
}
2017-05-18 20:18:59 +00:00
if ( type = = AudioMsgId : : Type : : Voice ) {
emit suppressSong ( ) ;
}
2016-07-13 11:24:31 +00:00
}
} break ;
}
emit faderOnTimer ( ) ;
}
if ( current ) emit updated ( current ) ;
}
2019-03-12 05:09:53 +00:00
//
// Right now all the music is played in the streaming player.
//
//void Mixer::seek(AudioMsgId::Type type, crl::time positionMs) {
// QMutexLocker lock(&AudioMutex);
//
// const auto current = trackForType(type);
// const auto audio = current->state.id;
//
// Audio::AttachToDevice();
// const auto streamCreated = current->isStreamCreated();
// const auto position = (positionMs * current->frequency) / 1000LL;
// const auto fastSeek = [&] {
// const auto loadedStart = current->bufferedPosition;
// const auto loadedLength = current->bufferedLength;
// const auto skipBack = (current->loaded ? 0 : kDefaultFrequency);
// const auto availableEnd = loadedStart + loadedLength - skipBack;
// if (position < loadedStart) {
// return false;
// } else if (position >= availableEnd) {
// return false;
// } else if (!streamCreated) {
// return false;
// } else if (IsStoppedOrStopping(current->state.state)) {
// return false;
// }
// return true;
// }();
// if (fastSeek) {
// alSourcei(current->stream.source, AL_SAMPLE_OFFSET, position - current->bufferedPosition);
// if (!checkCurrentALError(type)) return;
//
// alSourcef(current->stream.source, AL_GAIN, ComputeVolume(type));
// if (!checkCurrentALError(type)) return;
//
// resetFadeStartPosition(type, position - current->bufferedPosition);
// } else {
// setStoppedState(current);
// }
// switch (current->state.state) {
// case State::Pausing:
// case State::Paused:
// case State::PausedAtEnd: {
// if (current->state.state == State::PausedAtEnd) {
// current->state.state = State::Paused;
// }
// lock.unlock();
// return resume(audio, true);
// } break;
// case State::Starting:
// case State::Resuming:
// case State::Playing: {
// current->state.state = State::Pausing;
// resetFadeStartPosition(type);
// if (type == AudioMsgId::Type::Voice) {
// emit unsuppressSong();
// }
// } break;
// case State::Stopping:
// case State::Stopped:
// case State::StoppedAtEnd:
// case State::StoppedAtError:
// case State::StoppedAtStart: {
// lock.unlock();
// } return play(audio, positionMs);
// }
// emit faderOnTimer();
//}
2015-07-03 08:47:16 +00:00
2017-05-18 20:18:59 +00:00
void Mixer : : stop ( const AudioMsgId & audio ) {
2016-06-30 12:03:32 +00:00
AudioMsgId current ;
{
2017-01-19 08:24:43 +00:00
QMutexLocker lock ( & AudioMutex ) ;
2017-05-18 20:18:59 +00:00
auto type = audio . type ( ) ;
2017-01-24 21:24:39 +00:00
auto track = trackForType ( type ) ;
2017-05-18 20:18:59 +00:00
if ( ! track | | track - > state . id ! = audio ) {
return ;
}
2016-07-05 17:44:22 +00:00
2019-02-28 21:03:25 +00:00
current = audio ;
2016-06-30 12:03:32 +00:00
fadedStop ( type ) ;
2017-05-18 20:18:59 +00:00
if ( type = = AudioMsgId : : Type : : Voice ) {
emit unsuppressSong ( ) ;
} else if ( type = = AudioMsgId : : Type : : Video ) {
2017-01-24 21:24:39 +00:00
track - > clear ( ) ;
2019-02-28 21:03:25 +00:00
emit loaderOnCancel ( audio ) ;
2016-07-05 17:44:22 +00:00
}
2015-11-24 16:19:18 +00:00
}
2016-06-30 12:03:32 +00:00
if ( current ) emit updated ( current ) ;
2015-11-24 16:19:18 +00:00
}
2017-05-21 13:16:39 +00:00
void Mixer : : stop ( const AudioMsgId & audio , State state ) {
Expects ( IsStopped ( state ) ) ;
AudioMsgId current ;
{
QMutexLocker lock ( & AudioMutex ) ;
auto type = audio . type ( ) ;
auto track = trackForType ( type ) ;
2019-02-28 21:03:25 +00:00
if ( ! track
| | track - > state . id ! = audio
| | IsStopped ( track - > state . state ) ) {
2017-05-21 13:16:39 +00:00
return ;
}
2019-02-28 21:03:25 +00:00
current = audio ;
2017-05-21 13:16:39 +00:00
setStoppedState ( track , state ) ;
if ( type = = AudioMsgId : : Type : : Voice ) {
emit unsuppressSong ( ) ;
} else if ( type = = AudioMsgId : : Type : : Video ) {
track - > clear ( ) ;
}
}
if ( current ) emit updated ( current ) ;
}
2017-01-19 08:24:43 +00:00
void Mixer : : stopAndClear ( ) {
2017-01-24 21:24:39 +00:00
Track * current_audio = nullptr , * current_song = nullptr ;
2015-11-24 16:19:18 +00:00
{
2017-01-19 08:24:43 +00:00
QMutexLocker lock ( & AudioMutex ) ;
2017-01-24 21:24:39 +00:00
if ( ( current_audio = trackForType ( AudioMsgId : : Type : : Voice ) ) ) {
2015-11-24 16:19:18 +00:00
setStoppedState ( current_audio ) ;
}
2017-01-24 21:24:39 +00:00
if ( ( current_song = trackForType ( AudioMsgId : : Type : : Song ) ) ) {
2015-11-24 16:19:18 +00:00
setStoppedState ( current_song ) ;
}
}
if ( current_song ) {
2017-01-24 21:24:39 +00:00
emit updated ( current_song - > state . id ) ;
2015-11-24 16:19:18 +00:00
}
if ( current_audio ) {
2017-01-24 21:24:39 +00:00
emit updated ( current_audio - > state . id ) ;
2015-11-24 16:19:18 +00:00
}
{
2017-01-19 08:24:43 +00:00
QMutexLocker lock ( & AudioMutex ) ;
2016-06-30 12:03:32 +00:00
auto clearAndCancel = [ this ] ( AudioMsgId : : Type type , int index ) {
2017-01-24 21:24:39 +00:00
auto track = trackForType ( type , index ) ;
if ( track - > state . id ) {
emit loaderOnCancel ( track - > state . id ) ;
2015-11-24 16:19:18 +00:00
}
2017-01-24 21:24:39 +00:00
track - > clear ( ) ;
2016-06-30 12:03:32 +00:00
} ;
2017-01-24 21:24:39 +00:00
for ( auto index = 0 ; index ! = kTogetherLimit ; + + index ) {
2016-06-30 12:03:32 +00:00
clearAndCancel ( AudioMsgId : : Type : : Voice , index ) ;
clearAndCancel ( AudioMsgId : : Type : : Song , index ) ;
2015-11-24 16:19:18 +00:00
}
2017-01-24 21:24:39 +00:00
_videoTrack . clear ( ) ;
2015-07-03 08:47:16 +00:00
}
}
2017-01-24 21:24:39 +00:00
TrackState Mixer : : currentState ( AudioMsgId : : Type type ) {
2017-01-19 08:24:43 +00:00
QMutexLocker lock ( & AudioMutex ) ;
2017-01-24 21:24:39 +00:00
auto current = trackForType ( type ) ;
if ( ! current ) {
return TrackState ( ) ;
}
return current - > state ;
2015-06-30 21:07:05 +00:00
}
2017-01-24 21:24:39 +00:00
void Mixer : : setStoppedState ( Track * current , State state ) {
current - > state . state = state ;
current - > state . position = 0 ;
2017-05-23 14:04:59 +00:00
if ( current - > isStreamCreated ( ) ) {
alSourceStop ( current - > stream . source ) ;
alSourcef ( current - > stream . source , AL_GAIN , 1 ) ;
}
2019-03-01 12:22:47 +00:00
if ( current - > state . id ) {
emit loaderOnCancel ( current - > state . id ) ;
}
2015-07-03 08:47:16 +00:00
}
2017-05-03 11:36:39 +00:00
// Thread: Main. Must be locked: AudioMutex.
2017-01-24 21:24:39 +00:00
void Mixer : : detachTracks ( ) {
for ( auto i = 0 ; i ! = kTogetherLimit ; + + i ) {
trackForType ( AudioMsgId : : Type : : Voice , i ) - > detach ( ) ;
trackForType ( AudioMsgId : : Type : : Song , i ) - > detach ( ) ;
}
_videoTrack . detach ( ) ;
}
2017-05-03 11:36:39 +00:00
// Thread: Main. Must be locked: AudioMutex.
2017-01-24 21:24:39 +00:00
void Mixer : : reattachIfNeeded ( ) {
2017-05-03 11:36:39 +00:00
Audio : : Current ( ) . stopDetachIfNotUsed ( ) ;
2017-01-24 21:24:39 +00:00
auto reattachNeeded = [ this ] {
auto isPlayingState = [ ] ( const Track & track ) {
auto state = track . state . state ;
2017-05-21 13:16:39 +00:00
return ( state = = State : : Playing ) | | IsFading ( state ) ;
2017-01-24 21:24:39 +00:00
} ;
for ( auto i = 0 ; i ! = kTogetherLimit ; + + i ) {
if ( isPlayingState ( * trackForType ( AudioMsgId : : Type : : Voice , i ) )
| | isPlayingState ( * trackForType ( AudioMsgId : : Type : : Song , i ) ) ) {
return true ;
}
}
return isPlayingState ( _videoTrack ) ;
} ;
2017-05-03 11:36:39 +00:00
if ( reattachNeeded ( ) | | Audio : : Current ( ) . hasActiveTracks ( ) ) {
Audio : : AttachToDevice ( ) ;
2017-01-24 21:24:39 +00:00
}
}
2017-05-03 11:36:39 +00:00
// Thread: Any. Must be locked: AudioMutex.
2017-01-24 21:24:39 +00:00
void Mixer : : reattachTracks ( ) {
2017-05-03 11:36:39 +00:00
for ( auto i = 0 ; i ! = kTogetherLimit ; + + i ) {
trackForType ( AudioMsgId : : Type : : Voice , i ) - > reattach ( AudioMsgId : : Type : : Voice ) ;
trackForType ( AudioMsgId : : Type : : Song , i ) - > reattach ( AudioMsgId : : Type : : Song ) ;
2015-05-24 17:58:39 +00:00
}
2017-05-03 11:36:39 +00:00
_videoTrack . reattach ( AudioMsgId : : Type : : Video ) ;
2014-09-04 07:33:44 +00:00
}
2017-05-18 20:18:59 +00:00
void Mixer : : setSongVolume ( float64 volume ) {
_volumeSong . storeRelease ( qRound ( volume * kVolumeRound ) ) ;
}
float64 Mixer : : getSongVolume ( ) const {
return float64 ( _volumeSong . loadAcquire ( ) ) / kVolumeRound ;
}
2017-04-15 19:51:53 +00:00
void Mixer : : setVideoVolume ( float64 volume ) {
2017-05-18 20:18:59 +00:00
_volumeVideo . storeRelease ( qRound ( volume * kVolumeRound ) ) ;
2017-04-15 19:51:53 +00:00
}
float64 Mixer : : getVideoVolume ( ) const {
2017-05-18 20:18:59 +00:00
return float64 ( _volumeVideo . loadAcquire ( ) ) / kVolumeRound ;
2017-04-15 19:51:53 +00:00
}
2017-01-19 08:24:43 +00:00
Fader : : Fader ( QThread * thread ) : QObject ( )
2016-10-12 19:34:25 +00:00
, _timer ( this )
2017-05-18 20:18:59 +00:00
, _suppressVolumeAll ( 1. , 1. )
, _suppressVolumeSong ( 1. , 1. ) {
2014-09-04 07:33:44 +00:00
moveToThread ( thread ) ;
_timer . moveToThread ( thread ) ;
2016-10-12 19:34:25 +00:00
connect ( thread , SIGNAL ( started ( ) ) , this , SLOT ( onInit ( ) ) ) ;
connect ( thread , SIGNAL ( finished ( ) ) , this , SLOT ( deleteLater ( ) ) ) ;
2015-01-10 13:08:30 +00:00
2014-09-04 07:33:44 +00:00
_timer . setSingleShot ( true ) ;
connect ( & _timer , SIGNAL ( timeout ( ) ) , this , SLOT ( onTimer ( ) ) ) ;
}
2017-01-19 08:24:43 +00:00
void Fader : : onInit ( ) {
2014-09-04 07:33:44 +00:00
}
2017-01-19 08:24:43 +00:00
void Fader : : onTimer ( ) {
QMutexLocker lock ( & AudioMutex ) ;
2017-01-24 21:24:39 +00:00
if ( ! mixer ( ) ) return ;
2014-09-04 07:33:44 +00:00
2017-05-18 20:18:59 +00:00
auto volumeChangedAll = false ;
auto volumeChangedSong = false ;
2015-06-30 21:07:05 +00:00
if ( _suppressAll | | _suppressSongAnim ) {
2019-02-19 06:57:53 +00:00
auto ms = crl : : now ( ) ;
2015-06-30 21:07:05 +00:00
if ( _suppressAll ) {
2017-05-03 13:01:15 +00:00
if ( ms > = _suppressAllEnd | | ms < _suppressAllStart ) {
2015-06-30 21:07:05 +00:00
_suppressAll = _suppressAllAnim = false ;
2017-05-18 20:18:59 +00:00
_suppressVolumeAll = anim : : value ( 1. , 1. ) ;
2017-05-03 13:01:15 +00:00
} else if ( ms > _suppressAllEnd - kFadeDuration ) {
2017-05-18 20:18:59 +00:00
if ( _suppressVolumeAll . to ( ) ! = 1. ) _suppressVolumeAll . start ( 1. ) ;
_suppressVolumeAll . update ( 1. - ( ( _suppressAllEnd - ms ) / float64 ( kFadeDuration ) ) , anim : : linear ) ;
2016-10-04 18:18:08 +00:00
} else if ( ms > = _suppressAllStart + st : : mediaPlayerSuppressDuration ) {
2015-06-30 21:07:05 +00:00
if ( _suppressAllAnim ) {
2017-05-18 20:18:59 +00:00
_suppressVolumeAll . finish ( ) ;
2015-06-30 21:07:05 +00:00
_suppressAllAnim = false ;
2014-09-04 07:33:44 +00:00
}
2015-06-30 21:07:05 +00:00
} else if ( ms > _suppressAllStart ) {
2017-05-18 20:18:59 +00:00
_suppressVolumeAll . update ( ( ms - _suppressAllStart ) / float64 ( st : : mediaPlayerSuppressDuration ) , anim : : linear ) ;
2014-09-04 07:33:44 +00:00
}
2017-05-18 20:18:59 +00:00
auto wasVolumeMultiplierAll = VolumeMultiplierAll ;
VolumeMultiplierAll = _suppressVolumeAll . current ( ) ;
volumeChangedAll = ( VolumeMultiplierAll ! = wasVolumeMultiplierAll ) ;
2015-06-30 21:07:05 +00:00
}
if ( _suppressSongAnim ) {
2017-01-24 21:24:39 +00:00
if ( ms > = _suppressSongStart + kFadeDuration ) {
2017-05-18 20:18:59 +00:00
_suppressVolumeSong . finish ( ) ;
2015-06-30 21:07:05 +00:00
_suppressSongAnim = false ;
} else {
2017-05-18 20:18:59 +00:00
_suppressVolumeSong . update ( ( ms - _suppressSongStart ) / float64 ( kFadeDuration ) , anim : : linear ) ;
2014-09-04 07:33:44 +00:00
}
}
2017-05-18 20:18:59 +00:00
auto wasVolumeMultiplierSong = VolumeMultiplierSong ;
VolumeMultiplierSong = _suppressVolumeSong . current ( ) ;
accumulate_min ( VolumeMultiplierSong , VolumeMultiplierAll ) ;
volumeChangedSong = ( VolumeMultiplierSong ! = wasVolumeMultiplierSong ) ;
2014-09-04 07:33:44 +00:00
}
2017-05-18 20:18:59 +00:00
auto hasFading = ( _suppressAll | | _suppressSongAnim ) ;
auto hasPlaying = false ;
2015-06-30 21:07:05 +00:00
2017-05-18 20:18:59 +00:00
auto updatePlayback = [ this , & hasPlaying , & hasFading ] ( AudioMsgId : : Type type , int index , float64 volumeMultiplier , bool suppressGainChanged ) {
2017-01-24 21:24:39 +00:00
auto track = mixer ( ) - > trackForType ( type , index ) ;
if ( IsStopped ( track - > state . state ) | | track - > state . state = = State : : Paused | | ! track - > isStreamCreated ( ) ) return ;
2015-06-30 21:07:05 +00:00
2017-05-18 20:18:59 +00:00
auto emitSignals = updateOnePlayback ( track , hasPlaying , hasFading , volumeMultiplier , suppressGainChanged ) ;
2017-01-24 21:24:39 +00:00
if ( emitSignals & EmitError ) emit error ( track - > state . id ) ;
if ( emitSignals & EmitStopped ) emit audioStopped ( track - > state . id ) ;
if ( emitSignals & EmitPositionUpdated ) emit playPositionUpdated ( track - > state . id ) ;
if ( emitSignals & EmitNeedToPreload ) emit needToPreload ( track - > state . id ) ;
2016-06-30 12:03:32 +00:00
} ;
2017-05-18 20:18:59 +00:00
auto suppressGainForMusic = ComputeVolume ( AudioMsgId : : Type : : Song ) ;
auto suppressGainForMusicChanged = volumeChangedSong | | _volumeChangedSong ;
2017-01-24 21:24:39 +00:00
for ( auto i = 0 ; i ! = kTogetherLimit ; + + i ) {
2017-05-18 20:18:59 +00:00
updatePlayback ( AudioMsgId : : Type : : Voice , i , VolumeMultiplierAll , volumeChangedAll ) ;
2016-06-30 12:03:32 +00:00
updatePlayback ( AudioMsgId : : Type : : Song , i , suppressGainForMusic , suppressGainForMusicChanged ) ;
2015-06-30 21:07:05 +00:00
}
2017-05-18 20:18:59 +00:00
auto suppressGainForVideo = ComputeVolume ( AudioMsgId : : Type : : Video ) ;
auto suppressGainForVideoChanged = volumeChangedAll | | _volumeChangedVideo ;
2016-07-12 14:11:59 +00:00
updatePlayback ( AudioMsgId : : Type : : Video , 0 , suppressGainForVideo , suppressGainForVideoChanged ) ;
2015-06-30 21:07:05 +00:00
2017-05-18 20:18:59 +00:00
_volumeChangedSong = _volumeChangedVideo = false ;
2015-06-30 21:07:05 +00:00
2014-09-04 07:33:44 +00:00
if ( hasFading ) {
2017-01-24 21:24:39 +00:00
_timer . start ( kCheckFadingTimeout ) ;
2017-05-03 11:36:39 +00:00
Audio : : StopDetachIfNotUsedSafe ( ) ;
2014-09-04 07:33:44 +00:00
} else if ( hasPlaying ) {
2017-01-24 21:24:39 +00:00
_timer . start ( kCheckPlaybackPositionTimeout ) ;
2017-05-03 11:36:39 +00:00
Audio : : StopDetachIfNotUsedSafe ( ) ;
2015-01-10 13:08:30 +00:00
} else {
2017-05-03 11:36:39 +00:00
Audio : : ScheduleDetachIfNotUsedSafe ( ) ;
2014-09-04 07:33:44 +00:00
}
}
2017-05-18 20:18:59 +00:00
int32 Fader : : updateOnePlayback ( Mixer : : Track * track , bool & hasPlaying , bool & hasFading , float64 volumeMultiplier , bool volumeChanged ) {
2019-02-22 14:28:10 +00:00
const auto errorHappened = [ & ] {
2017-05-03 11:36:39 +00:00
if ( Audio : : PlaybackErrorHappened ( ) ) {
2017-01-24 21:24:39 +00:00
setStoppedState ( track , State : : StoppedAtError ) ;
return true ;
}
return false ;
} ;
2019-02-22 14:28:10 +00:00
ALint alSampleOffset = 0 ;
ALint alState = AL_INITIAL ;
alGetSourcei ( track - > stream . source , AL_SAMPLE_OFFSET , & alSampleOffset ) ;
alGetSourcei ( track - > stream . source , AL_SOURCE_STATE , & alState ) ;
if ( errorHappened ( ) ) {
return EmitError ;
} else if ( ( alState = = AL_STOPPED )
& & ( alSampleOffset = = 0 )
& & ! internal : : CheckAudioDeviceConnected ( ) ) {
return 0 ;
2017-01-24 21:24:39 +00:00
}
2019-02-22 14:28:10 +00:00
int32 emitSignals = 0 ;
2019-02-27 11:36:19 +00:00
const auto stoppedAtEnd = track - > state . waitingForData
| | ( ( alState = = AL_STOPPED )
& & ( ! IsStopped ( track - > state . state )
| | IsStoppedAtEnd ( track - > state . state ) ) ) ;
2019-02-22 14:28:10 +00:00
const auto positionInBuffered = stoppedAtEnd
? track - > bufferedLength
: alSampleOffset ;
const auto waitingForDataOld = track - > state . waitingForData ;
track - > state . waitingForData = stoppedAtEnd
& & ( track - > state . state ! = State : : Stopping ) ;
const auto fullPosition = track - > bufferedPosition + positionInBuffered ;
auto playing = ( track - > state . state = = State : : Playing ) ;
auto fading = IsFading ( track - > state . state ) ;
if ( alState ! = AL_PLAYING & & ! track - > loading ) {
2017-05-21 13:16:39 +00:00
if ( fading | | playing ) {
2015-06-30 21:07:05 +00:00
fading = false ;
2017-05-21 13:16:39 +00:00
playing = false ;
2017-01-24 21:24:39 +00:00
if ( track - > state . state = = State : : Pausing ) {
2017-05-23 14:04:59 +00:00
setStoppedState ( track , State : : PausedAtEnd ) ;
2017-05-21 13:16:39 +00:00
} else if ( track - > state . state = = State : : Stopping ) {
setStoppedState ( track , State : : Stopped ) ;
2015-07-03 08:47:16 +00:00
} else {
2017-01-24 21:24:39 +00:00
setStoppedState ( track , State : : StoppedAtEnd ) ;
2015-06-30 21:07:05 +00:00
}
2017-05-23 14:04:59 +00:00
if ( errorHappened ( ) ) return EmitError ;
2015-06-30 21:07:05 +00:00
emitSignals | = EmitStopped ;
2017-05-21 13:16:39 +00:00
}
2019-02-22 14:28:10 +00:00
} else if ( fading & & alState = = AL_PLAYING ) {
2017-05-21 13:16:39 +00:00
auto fadingForSamplesCount = ( fullPosition - track - > fadeStartPosition ) ;
2019-02-19 06:57:53 +00:00
if ( crl : : time ( 1000 ) * fadingForSamplesCount > = kFadeDuration * track - > state . frequency ) {
2015-06-30 21:07:05 +00:00
fading = false ;
2017-05-18 20:18:59 +00:00
alSourcef ( track - > stream . source , AL_GAIN , 1. * volumeMultiplier ) ;
2017-01-24 21:24:39 +00:00
if ( errorHappened ( ) ) return EmitError ;
switch ( track - > state . state ) {
2017-05-21 13:16:39 +00:00
case State : : Stopping : {
2017-01-24 21:24:39 +00:00
setStoppedState ( track ) ;
2019-02-22 14:28:10 +00:00
alState = AL_STOPPED ;
2017-01-24 21:24:39 +00:00
} break ;
case State : : Pausing : {
alSourcePause ( track - > stream . source ) ;
if ( errorHappened ( ) ) return EmitError ;
track - > state . state = State : : Paused ;
} break ;
case State : : Starting :
case State : : Resuming : {
track - > state . state = State : : Playing ;
playing = true ;
} break ;
2015-06-30 21:07:05 +00:00
}
} else {
2019-02-19 06:57:53 +00:00
auto newGain = crl : : time ( 1000 ) * fadingForSamplesCount / float64 ( kFadeDuration * track - > state . frequency ) ;
2017-05-21 13:16:39 +00:00
if ( track - > state . state = = State : : Pausing | | track - > state . state = = State : : Stopping ) {
2015-06-30 21:07:05 +00:00
newGain = 1. - newGain ;
}
2017-05-18 20:18:59 +00:00
alSourcef ( track - > stream . source , AL_GAIN , newGain * volumeMultiplier ) ;
2017-01-24 21:24:39 +00:00
if ( errorHappened ( ) ) return EmitError ;
2015-06-30 21:07:05 +00:00
}
2019-02-22 14:28:10 +00:00
} else if ( playing & & alState = = AL_PLAYING ) {
2017-05-21 13:16:39 +00:00
if ( volumeChanged ) {
2017-05-18 20:18:59 +00:00
alSourcef ( track - > stream . source , AL_GAIN , 1. * volumeMultiplier ) ;
2017-01-24 21:24:39 +00:00
if ( errorHappened ( ) ) return EmitError ;
2015-06-30 21:07:05 +00:00
}
}
2019-02-22 14:28:10 +00:00
if ( alState = = AL_PLAYING & & fullPosition > = track - > state . position + kCheckPlaybackPositionDelta ) {
2017-01-24 21:24:39 +00:00
track - > state . position = fullPosition ;
2015-06-30 21:07:05 +00:00
emitSignals | = EmitPositionUpdated ;
2019-02-22 14:28:10 +00:00
} else if ( track - > state . waitingForData & & ! waitingForDataOld ) {
if ( fullPosition > track - > state . position ) {
track - > state . position = fullPosition ;
}
// When stopped because of insufficient data while streaming,
// inform the player about the last position we were at.
emitSignals | = EmitPositionUpdated ;
2015-06-30 21:07:05 +00:00
}
2017-01-24 21:24:39 +00:00
if ( playing | | track - > state . state = = State : : Starting | | track - > state . state = = State : : Resuming ) {
if ( ! track - > loaded & & ! track - > loading ) {
auto needPreload = ( track - > state . position + kPreloadSamples > track - > bufferedPosition + track - > bufferedLength ) ;
if ( needPreload ) {
track - > loading = true ;
emitSignals | = EmitNeedToPreload ;
}
2015-06-30 21:07:05 +00:00
}
}
if ( playing ) hasPlaying = true ;
if ( fading ) hasFading = true ;
return emitSignals ;
}
2017-01-24 21:24:39 +00:00
void Fader : : setStoppedState ( Mixer : : Track * track , State state ) {
2017-05-23 14:04:59 +00:00
mixer ( ) - > setStoppedState ( track , state ) ;
2015-07-03 08:47:16 +00:00
}
2017-01-19 08:24:43 +00:00
void Fader : : onSuppressSong ( ) {
2015-06-30 21:07:05 +00:00
if ( ! _suppressSong ) {
_suppressSong = true ;
_suppressSongAnim = true ;
2019-02-19 06:57:53 +00:00
_suppressSongStart = crl : : now ( ) ;
2017-05-18 20:18:59 +00:00
_suppressVolumeSong . start ( kSuppressRatioSong ) ;
2015-06-30 21:07:05 +00:00
onTimer ( ) ;
}
}
2017-01-19 08:24:43 +00:00
void Fader : : onUnsuppressSong ( ) {
2015-06-30 21:07:05 +00:00
if ( _suppressSong ) {
_suppressSong = false ;
_suppressSongAnim = true ;
2019-02-19 06:57:53 +00:00
_suppressSongStart = crl : : now ( ) ;
2017-05-18 20:18:59 +00:00
_suppressVolumeSong . start ( 1. ) ;
2015-06-30 21:07:05 +00:00
onTimer ( ) ;
}
}
2017-05-03 13:01:15 +00:00
void Fader : : onSuppressAll ( qint64 duration ) {
2015-06-30 21:07:05 +00:00
_suppressAll = true ;
2019-02-19 06:57:53 +00:00
auto now = crl : : now ( ) ;
2017-05-03 13:01:15 +00:00
if ( _suppressAllEnd < now + kFadeDuration ) {
_suppressAllStart = now ;
}
_suppressAllEnd = now + duration ;
2017-05-18 20:18:59 +00:00
_suppressVolumeAll . start ( kSuppressRatioAll ) ;
2015-06-30 21:07:05 +00:00
onTimer ( ) ;
}
2017-01-19 08:24:43 +00:00
void Fader : : onSongVolumeChanged ( ) {
2017-05-18 20:18:59 +00:00
_volumeChangedSong = true ;
2015-07-03 08:47:16 +00:00
onTimer ( ) ;
}
2017-01-19 08:24:43 +00:00
void Fader : : onVideoVolumeChanged ( ) {
2017-05-18 20:18:59 +00:00
_volumeChangedVideo = true ;
2016-07-12 14:11:59 +00:00
onTimer ( ) ;
}
2017-01-19 08:24:43 +00:00
namespace internal {
2016-02-12 16:35:06 +00:00
2017-05-03 11:36:39 +00:00
// Thread: Any.
2017-01-19 08:24:43 +00:00
QMutex * audioPlayerMutex ( ) {
return & AudioMutex ;
2015-05-29 18:52:43 +00:00
}
2017-05-03 11:36:39 +00:00
// Thread: Any.
2017-01-24 21:24:39 +00:00
bool audioCheckError ( ) {
2017-05-03 11:36:39 +00:00
return ! Audio : : PlaybackErrorHappened ( ) ;
2015-05-29 18:52:43 +00:00
}
2017-05-03 11:36:39 +00:00
// Thread: Any. Must be locked: AudioMutex.
2017-01-24 21:24:39 +00:00
bool audioDeviceIsConnected ( ) {
if ( ! AudioDevice ) {
return false ;
}
2017-05-03 11:36:39 +00:00
auto isConnected = ALint ( 0 ) ;
alcGetIntegerv ( AudioDevice , ALC_CONNECTED , 1 , & isConnected ) ;
if ( Audio : : ContextErrorHappened ( ) ) {
2017-01-24 21:24:39 +00:00
return false ;
}
2017-05-03 11:36:39 +00:00
return ( isConnected ! = 0 ) ;
2016-07-22 15:01:24 +00:00
}
2017-05-03 11:36:39 +00:00
// Thread: Any. Must be locked: AudioMutex.
2017-01-24 21:24:39 +00:00
bool CheckAudioDeviceConnected ( ) {
if ( audioDeviceIsConnected ( ) ) {
return true ;
}
2017-05-03 11:36:39 +00:00
Audio : : ScheduleDetachFromDeviceSafe ( ) ;
2017-01-24 21:24:39 +00:00
return false ;
2016-07-22 15:01:24 +00:00
}
2017-05-03 11:36:39 +00:00
// Thread: Main. Locks: AudioMutex.
2019-02-01 07:09:55 +00:00
void DetachFromDevice ( not_null < Audio : : Instance * > instance ) {
2017-05-03 11:36:39 +00:00
QMutexLocker lock ( & AudioMutex ) ;
2019-02-01 07:09:55 +00:00
Audio : : ClosePlaybackDevice ( instance ) ;
2017-05-03 11:36:39 +00:00
if ( mixer ( ) ) {
mixer ( ) - > reattachIfNeeded ( ) ;
}
}
2017-01-19 08:24:43 +00:00
} // namespace internal
2015-06-30 21:07:05 +00:00
2017-05-03 11:36:39 +00:00
} // namespace Player
2016-02-12 16:35:06 +00:00
class FFMpegAttributesReader : public AbstractFFMpegLoader {
2015-06-30 21:07:05 +00:00
public :
2018-01-02 16:18:53 +00:00
FFMpegAttributesReader ( const FileLocation & file , const QByteArray & data )
2018-03-27 12:16:00 +00:00
: AbstractFFMpegLoader ( file , data , bytes : : vector ( ) ) {
2015-06-30 21:07:05 +00:00
}
2019-02-19 06:57:53 +00:00
bool open ( crl : : time positionMs ) override {
2017-12-10 08:52:38 +00:00
if ( ! AbstractFFMpegLoader : : open ( positionMs ) ) {
2015-06-30 21:07:05 +00:00
return false ;
}
int res = 0 ;
char err [ AV_ERROR_MAX_STRING_SIZE ] = { 0 } ;
2016-02-12 16:35:06 +00:00
int videoStreamId = av_find_best_stream ( fmtContext , AVMEDIA_TYPE_VIDEO , - 1 , - 1 , & codec , 0 ) ;
if ( videoStreamId > = 0 ) {
2017-05-03 11:36:39 +00:00
DEBUG_LOG ( ( " Audio Read Error: Found video stream in file '%1', data size '%2', error %3, %4 " ) . arg ( _file . name ( ) ) . arg ( _data . size ( ) ) . arg ( videoStreamId ) . arg ( av_make_error_string ( err , sizeof ( err ) , streamId ) ) ) ;
2015-06-30 21:07:05 +00:00
return false ;
}
for ( int32 i = 0 , l = fmtContext - > nb_streams ; i < l ; + + i ) {
2018-01-02 16:18:53 +00:00
const auto stream = fmtContext - > streams [ i ] ;
2015-06-30 21:07:05 +00:00
if ( stream - > disposition & AV_DISPOSITION_ATTACHED_PIC ) {
2018-01-02 16:18:53 +00:00
const auto & packet = stream - > attached_pic ;
2015-06-30 21:07:05 +00:00
if ( packet . size ) {
2018-01-02 16:18:53 +00:00
const auto coverBytes = QByteArray (
( const char * ) packet . data ,
packet . size ) ;
auto format = QByteArray ( ) ;
auto animated = false ;
_cover = App : : readImage (
coverBytes ,
& format ,
true ,
& animated ) ;
2015-06-30 21:07:05 +00:00
if ( ! _cover . isNull ( ) ) {
2018-01-02 16:18:53 +00:00
_coverBytes = coverBytes ;
2015-06-30 21:07:05 +00:00
_coverFormat = format ;
break ;
}
}
}
}
extractMetaData ( fmtContext - > streams [ streamId ] - > metadata ) ;
extractMetaData ( fmtContext - > metadata ) ;
return true ;
}
void trySet ( QString & to , AVDictionary * dict , const char * key ) {
if ( ! to . isEmpty ( ) ) return ;
2019-02-28 21:03:25 +00:00
if ( AVDictionaryEntry * tag = av_dict_get ( dict , key , nullptr , 0 ) ) {
2015-06-30 21:07:05 +00:00
to = QString : : fromUtf8 ( tag - > value ) ;
}
}
void extractMetaData ( AVDictionary * dict ) {
trySet ( _title , dict , " title " ) ;
trySet ( _performer , dict , " artist " ) ;
trySet ( _performer , dict , " performer " ) ;
trySet ( _performer , dict , " album_artist " ) ;
2015-09-23 17:43:08 +00:00
//for (AVDictionaryEntry *tag = av_dict_get(dict, "", 0, AV_DICT_IGNORE_SUFFIX); tag; tag = av_dict_get(dict, "", tag, AV_DICT_IGNORE_SUFFIX)) {
// const char *key = tag->key;
// const char *value = tag->value;
// QString tmp = QString::fromUtf8(value);
//}
2015-06-30 21:07:05 +00:00
}
2018-01-02 16:18:53 +00:00
int format ( ) override {
2015-06-30 21:07:05 +00:00
return 0 ;
}
QString title ( ) {
return _title ;
}
2016-01-05 06:59:57 +00:00
2015-06-30 21:07:05 +00:00
QString performer ( ) {
return _performer ;
}
QImage cover ( ) {
return _cover ;
}
QByteArray coverBytes ( ) {
return _coverBytes ;
}
QByteArray coverFormat ( ) {
return _coverFormat ;
}
2016-07-05 17:44:02 +00:00
ReadResult readMore ( QByteArray & result , int64 & samplesAdded ) override {
2015-06-30 21:07:05 +00:00
DEBUG_LOG ( ( " Audio Read Error: should not call this " ) ) ;
2016-07-05 17:44:02 +00:00
return ReadResult : : Error ;
2015-06-30 21:07:05 +00:00
}
~ FFMpegAttributesReader ( ) {
}
private :
QString _title , _performer ;
QImage _cover ;
QByteArray _coverBytes , _coverFormat ;
2016-02-12 16:35:06 +00:00
} ;
2015-06-30 21:07:05 +00:00
2017-03-10 14:14:10 +00:00
namespace Player {
2017-12-19 16:57:42 +00:00
FileMediaInformation : : Song PrepareForSending ( const QString & fname , const QByteArray & data ) {
auto result = FileMediaInformation : : Song ( ) ;
2017-03-04 11:28:21 +00:00
FFMpegAttributesReader reader ( FileLocation ( fname ) , data ) ;
2019-02-19 06:57:53 +00:00
const auto positionMs = crl : : time ( 0 ) ;
2017-12-10 08:52:38 +00:00
if ( reader . open ( positionMs ) & & reader . samplesCount ( ) > 0 ) {
2017-05-03 13:01:15 +00:00
result . duration = reader . samplesCount ( ) / reader . samplesFrequency ( ) ;
2017-03-10 14:14:10 +00:00
result . title = reader . title ( ) ;
result . performer = reader . performer ( ) ;
result . cover = reader . cover ( ) ;
2016-02-12 16:35:06 +00:00
}
2017-03-10 14:14:10 +00:00
return result ;
2016-02-12 16:35:06 +00:00
}
2015-06-30 21:07:05 +00:00
2017-03-10 14:14:10 +00:00
} // namespace Player
2016-02-12 16:35:06 +00:00
class FFMpegWaveformCounter : public FFMpegLoader {
public :
2018-03-27 12:16:00 +00:00
FFMpegWaveformCounter ( const FileLocation & file , const QByteArray & data ) : FFMpegLoader ( file , data , bytes : : vector ( ) ) {
2016-02-12 16:35:06 +00:00
}
2019-02-19 06:57:53 +00:00
bool open ( crl : : time positionMs ) override {
2017-12-10 08:52:38 +00:00
if ( ! FFMpegLoader : : open ( positionMs ) ) {
2016-02-12 16:35:06 +00:00
return false ;
2015-06-30 21:07:05 +00:00
}
2016-02-12 16:35:06 +00:00
QByteArray buffer ;
2019-02-21 13:40:09 +00:00
buffer . reserve ( kWaveformCounterBufferSize ) ;
2018-01-02 17:22:13 +00:00
int64 countbytes = sampleSize ( ) * samplesCount ( ) ;
int64 processed = 0 ;
int64 sumbytes = 0 ;
2017-05-03 13:01:15 +00:00
if ( samplesCount ( ) < Media : : Player : : kWaveformSamplesCount ) {
2016-02-12 16:35:06 +00:00
return false ;
}
2015-06-30 21:07:05 +00:00
2016-02-12 16:35:06 +00:00
QVector < uint16 > peaks ;
2017-02-10 22:37:37 +00:00
peaks . reserve ( Media : : Player : : kWaveformSamplesCount ) ;
2015-06-30 21:07:05 +00:00
2017-05-07 19:09:20 +00:00
auto fmt = format ( ) ;
auto peak = uint16 ( 0 ) ;
2018-01-02 17:22:13 +00:00
auto callback = [ & ] ( uint16 sample ) {
2017-05-07 19:09:20 +00:00
accumulate_max ( peak , sample ) ;
sumbytes + = Media : : Player : : kWaveformSamplesCount ;
if ( sumbytes > = countbytes ) {
sumbytes - = countbytes ;
peaks . push_back ( peak ) ;
peak = 0 ;
}
} ;
2016-02-12 16:35:06 +00:00
while ( processed < countbytes ) {
buffer . resize ( 0 ) ;
int64 samples = 0 ;
2016-07-05 17:44:02 +00:00
auto res = readMore ( buffer , samples ) ;
2016-07-30 10:03:44 +00:00
if ( res = = ReadResult : : Error | | res = = ReadResult : : EndOfFile ) {
2016-02-12 16:35:06 +00:00
break ;
}
if ( buffer . isEmpty ( ) ) {
continue ;
}
2018-03-27 12:16:00 +00:00
auto sampleBytes = bytes : : make_span ( buffer ) ;
2016-02-12 16:35:06 +00:00
if ( fmt = = AL_FORMAT_MONO8 | | fmt = = AL_FORMAT_STEREO8 ) {
2017-05-07 19:09:20 +00:00
Media : : Audio : : IterateSamples < uchar > ( sampleBytes , callback ) ;
2016-02-12 16:35:06 +00:00
} else if ( fmt = = AL_FORMAT_MONO16 | | fmt = = AL_FORMAT_STEREO16 ) {
2017-05-07 19:09:20 +00:00
Media : : Audio : : IterateSamples < int16 > ( sampleBytes , callback ) ;
2016-02-12 16:35:06 +00:00
}
2018-01-02 17:22:13 +00:00
processed + = sampleSize ( ) * samples ;
2015-06-30 21:07:05 +00:00
}
2017-02-10 22:37:37 +00:00
if ( sumbytes > 0 & & peaks . size ( ) < Media : : Player : : kWaveformSamplesCount ) {
2016-02-12 16:35:06 +00:00
peaks . push_back ( peak ) ;
2015-06-30 21:07:05 +00:00
}
2016-02-12 16:35:06 +00:00
if ( peaks . isEmpty ( ) ) {
return false ;
}
2015-06-30 21:07:05 +00:00
2017-01-27 07:25:43 +00:00
auto sum = std : : accumulate ( peaks . cbegin ( ) , peaks . cend ( ) , 0LL ) ;
2016-02-12 16:35:06 +00:00
peak = qMax ( int32 ( sum * 1.8 / peaks . size ( ) ) , 2500 ) ;
2015-06-30 21:07:05 +00:00
2016-02-12 16:35:06 +00:00
result . resize ( peaks . size ( ) ) ;
for ( int32 i = 0 , l = peaks . size ( ) ; i ! = l ; + + i ) {
result [ i ] = char ( qMin ( 31U , uint32 ( qMin ( peaks . at ( i ) , peak ) ) * 31 / peak ) ) ;
2015-06-30 21:07:05 +00:00
}
2016-02-12 16:35:06 +00:00
return true ;
}
const VoiceWaveform & waveform ( ) const {
return result ;
2015-06-30 21:07:05 +00:00
}
2016-02-12 16:35:06 +00:00
~ FFMpegWaveformCounter ( ) {
}
private :
VoiceWaveform result ;
2015-06-30 21:07:05 +00:00
} ;
2019-02-28 21:03:25 +00:00
} // namespace Media
VoiceWaveform audioCountWaveform (
const FileLocation & file ,
const QByteArray & data ) {
Media : : FFMpegWaveformCounter counter ( file , data ) ;
2019-02-19 06:57:53 +00:00
const auto positionMs = crl : : time ( 0 ) ;
2017-12-10 08:52:38 +00:00
if ( counter . open ( positionMs ) ) {
2016-02-12 16:35:06 +00:00
return counter . waveform ( ) ;
2015-06-30 21:07:05 +00:00
}
2016-02-12 16:35:06 +00:00
return VoiceWaveform ( ) ;
2015-06-30 21:07:05 +00:00
}