/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/effects/animation_value.h" #include "core/file_location.h" #include "data/data_audio_msg_id.h" #include "base/bytes.h" #include "base/timer.h" #include namespace Ui { struct PreparedFileInformation; } // namespace Ui namespace Media { struct ExternalSoundData; struct ExternalSoundPart; } // namespace Media namespace Media { namespace Streaming { struct TimePoint; } // namespace Streaming } // namespace Media namespace Media { namespace Audio { class Instance; // Thread: Main. void Start(not_null instance); void Finish(not_null instance); // Thread: Main. Locks: AudioMutex. bool IsAttachedToDevice(); // Thread: Any. Must be locked: AudioMutex. bool AttachToDevice(); // Thread: Any. void ScheduleDetachFromDeviceSafe(); void ScheduleDetachIfNotUsedSafe(); void StopDetachIfNotUsedSafe(); bool SupportsSpeedControl(); } // namespace Audio namespace Player { constexpr auto kDefaultFrequency = 48000; // 48 kHz constexpr auto kTogetherLimit = 4; constexpr auto kWaveformSamplesCount = 100; class Fader; class Loaders; [[nodiscard]] rpl::producer Updated(); float64 ComputeVolume(AudioMsgId::Type type); enum class State { Stopped = 0x01, StoppedAtEnd = 0x02, StoppedAtError = 0x03, StoppedAtStart = 0x04, Starting = 0x08, Playing = 0x10, Stopping = 0x18, Pausing = 0x20, Paused = 0x28, PausedAtEnd = 0x30, Resuming = 0x38, }; inline bool IsStopped(State state) { return (state == State::Stopped) || (state == State::StoppedAtEnd) || (state == State::StoppedAtError) || (state == State::StoppedAtStart); } inline bool IsStoppedOrStopping(State state) { return IsStopped(state) || (state == State::Stopping); } inline bool IsStoppedAtEnd(State state) { return (state == State::StoppedAtEnd); } inline bool IsPaused(State state) { return (state == State::Paused) || (state == State::PausedAtEnd); } inline bool IsPausedOrPausing(State state) { return IsPaused(state) || (state == State::Pausing); } inline bool IsFading(State state) { return (state == State::Starting) || (state == State::Stopping) || (state == State::Pausing) || (state == State::Resuming); } inline bool IsActive(State state) { return !IsStopped(state) && !IsPaused(state); } inline bool ShowPauseIcon(State state) { return !IsStoppedOrStopping(state) && !IsPausedOrPausing(state); } struct TrackState { AudioMsgId id; State state = State::Stopped; int64 position = 0; int64 receivedTill = 0; int64 length = 0; int frequency = kDefaultFrequency; int fileHeaderSize = 0; bool waitingForData = false; }; class Mixer final : public QObject { Q_OBJECT public: explicit Mixer(not_null instance); void play( const AudioMsgId &audio, std::unique_ptr externalData, crl::time positionMs); void pause(const AudioMsgId &audio, bool fast = false); void resume(const AudioMsgId &audio, bool fast = false); void stop(const AudioMsgId &audio); void stop(const AudioMsgId &audio, State state); // External player audio stream interface. void feedFromExternal(ExternalSoundPart &&part); void forceToBufferExternal(const AudioMsgId &audioId); // Thread: Main. Locks: AudioMutex. void setSpeedFromExternal(const AudioMsgId &audioId, float64 speed); Streaming::TimePoint getExternalSyncTimePoint( const AudioMsgId &audio) const; crl::time getExternalCorrectedTime( const AudioMsgId &id, crl::time frameMs, crl::time systemMs); void stopAndClear(); TrackState currentState(AudioMsgId::Type type); // Thread: Main. Must be locked: AudioMutex. void prepareToCloseDevice(); // Thread: Main. Must be locked: AudioMutex. void reattachIfNeeded(); // Thread: Any. Must be locked: AudioMutex. void reattachTracks(); // Thread: Any. void setSongVolume(float64 volume); float64 getSongVolume() const; void setVideoVolume(float64 volume); float64 getVideoVolume() const; void scheduleFaderCallback(); ~Mixer(); private Q_SLOTS: void onError(const AudioMsgId &audio); void onStopped(const AudioMsgId &audio); void onUpdated(const AudioMsgId &audio); Q_SIGNALS: void updated(const AudioMsgId &audio); void stoppedOnError(const AudioMsgId &audio); void loaderOnStart(const AudioMsgId &audio, qint64 positionMs); void loaderOnCancel(const AudioMsgId &audio); void suppressSong(); void unsuppressSong(); void suppressAll(qint64 duration); private: class Track { public: static constexpr int kBuffersCount = 3; // Thread: Any. Must be locked: AudioMutex. void reattach(AudioMsgId::Type type); // Thread: Main. Must be locked: AudioMutex. void detach(); void clear(); void started(); bool isStreamCreated() const; void ensureStreamCreated(AudioMsgId::Type type); int getNotQueuedBufferIndex(); // Thread: Main. Must be locked: AudioMutex. void setExternalData(std::unique_ptr data); void updateStatePosition(); void updateWithSpeedPosition(); [[nodiscard]] static int64 SpeedIndependentPosition( int64 position, float64 speed); [[nodiscard]] static int64 SpeedDependentPosition( int64 position, float64 speed); ~Track(); TrackState state; Core::FileLocation file; QByteArray data; int format = 0; bool loading = false; bool loaded = false; bool waitingForBuffer = false; // Speed dependent values. float64 speed = 1.; float64 nextSpeed = 1.; struct WithSpeed { int64 fineTunedPosition = 0; int64 position = 0; int64 length = 0; int64 bufferedPosition = 0; int64 bufferedLength = 0; int64 fadeStartPosition = 0; int samples[kBuffersCount] = { 0 }; QByteArray buffered[kBuffersCount]; }; WithSpeed withSpeed; struct Stream { uint32 source = 0; uint32 buffers[kBuffersCount] = { 0 }; }; Stream stream; std::unique_ptr externalData; crl::time lastUpdateWhen = 0; crl::time lastUpdatePosition = 0; private: void createStream(AudioMsgId::Type type); void destroyStream(); void resetStream(); }; bool fadedStop(AudioMsgId::Type type, bool *fadedStart = 0); void resetFadeStartPosition(AudioMsgId::Type type, int positionInBuffered = -1); bool checkCurrentALError(AudioMsgId::Type type); void externalSoundProgress(const AudioMsgId &audio); // Thread: Any. Must be locked: AudioMutex. void setStoppedState(Track *current, State state = State::Stopped); Track *trackForType(AudioMsgId::Type type, int index = -1); // -1 uses currentIndex(type) const Track *trackForType(AudioMsgId::Type type, int index = -1) const; int *currentIndex(AudioMsgId::Type type); const int *currentIndex(AudioMsgId::Type type) const; const not_null _instance; int _audioCurrent = 0; Track _audioTracks[kTogetherLimit]; int _songCurrent = 0; Track _songTracks[kTogetherLimit]; Track _videoTrack; QAtomicInt _volumeVideo; QAtomicInt _volumeSong; friend class Fader; friend class Loaders; QThread _faderThread, _loaderThread; Fader *_fader; Loaders *_loader; rpl::lifetime _lifetime; }; Mixer *mixer(); class Fader : public QObject { Q_OBJECT public: Fader(QThread *thread); void songVolumeChanged(); void videoVolumeChanged(); Q_SIGNALS: void error(const AudioMsgId &audio); void playPositionUpdated(const AudioMsgId &audio); void audioStopped(const AudioMsgId &audio); void needToPreload(const AudioMsgId &audio); public Q_SLOTS: void onInit(); void onTimer(); void onSuppressSong(); void onUnsuppressSong(); void onSuppressAll(qint64 duration); private: enum { EmitError = 0x01, EmitStopped = 0x02, EmitPositionUpdated = 0x04, EmitNeedToPreload = 0x08, }; int32 updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasFading, float64 volumeMultiplier, bool volumeChanged); void setStoppedState(Mixer::Track *track, State state = State::Stopped); QTimer _timer; bool _volumeChangedSong = false; bool _volumeChangedVideo = false; bool _suppressAll = false; bool _suppressAllAnim = false; bool _suppressSong = false; bool _suppressSongAnim = false; anim::value _suppressVolumeAll; anim::value _suppressVolumeSong; crl::time _suppressAllStart = 0; crl::time _suppressAllEnd = 0; crl::time _suppressSongStart = 0; }; [[nodiscard]] Ui::PreparedFileInformation PrepareForSending( const QString &fname, const QByteArray &data); namespace internal { // Thread: Any. Must be locked: AudioMutex. bool CheckAudioDeviceConnected(); // Thread: Main. Locks: AudioMutex. void DetachFromDevice(not_null instance); // Thread: Any. QMutex *audioPlayerMutex(); // Thread: Any. bool audioCheckError(); } // namespace internal } // namespace Player } // namespace Media VoiceWaveform audioCountWaveform(const Core::FileLocation &file, const QByteArray &data); namespace Media { namespace Audio { TG_FORCE_INLINE uint16 ReadOneSample(uchar data) { return qAbs((static_cast(data) - 0x80) * 0x100); } TG_FORCE_INLINE uint16 ReadOneSample(int16 data) { return qAbs(data); } template void IterateSamples(bytes::const_span bytes, Callback &&callback) { auto samplesPointer = reinterpret_cast(bytes.data()); auto samplesCount = bytes.size() / sizeof(SampleType); auto samplesData = gsl::make_span(samplesPointer, samplesCount); for (auto sampleData : samplesData) { callback(ReadOneSample(sampleData)); } } } // namespace Audio } // namespace Media