You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

142 lines
4.6 KiB

#pragma once
#include <QObject>
#include <QHash>
#include <QString>
#include <QByteArray>
#include <vector>
#include <memory>
#include <map>
#include <array>
#include <cstdint>
#include <QTimer>
#include "audio_device.h"
struct ma_engine;
struct ma_context;
struct ma_decoder;
struct ma_sound;
class VoiceAudioIo;
class SoundManager : public QObject {
Q_OBJECT
public:
struct EventSettings {
bool enabled = true;
float volume = 0.8f;
};
static SoundManager* instance();
bool init();
void shutdown();
void setEnabled(bool enabled);
bool isEnabled() const;
void setVolume(float volume);
float volume() const;
void setEvent(const QString& name, bool enabled, float volume);
EventSettings eventSettings(const QString& name) const;
bool loadSound(const QString& name, const QString& filePath);
void play(const QString& name);
void playLoop(const QString& name);
void stopLoop();
ma_context* context();
static bool deviceId(const QString& key, ma_device_id* id);
void setDevices(const QString& output, const QString& input);
QString playbackDevice() const { return m_playbackId; }
QString captureDevice() const { return m_captureId; }
/* GUI: voice живёт до detachVoice; изменение списка выполняется при остановленном output. */
bool attachVoice(VoiceAudioIo* voice, const QString& output);
void detachVoice(VoiceAudioIo* voice);
using PlaybackId = uint64_t;
/* GUI: независимая сессия. owner удерживает PCM до detach; без owner данные копируются. */
PlaybackId playRawPcm(const int16_t* pcm, int frameCount, int sampleRate, int channels,
std::shared_ptr<std::vector<int16_t>> owner = {}, bool transient = false);
void pausePcm(PlaybackId id);
void resumePcm(PlaybackId id);
void stopPcm(PlaybackId id);
bool seekPcm(PlaybackId id, uint64_t frame);
uint64_t pcmCursor(PlaybackId id) const;
bool isPcmPlaying(PlaybackId id) const;
int pcmSampleRate(PlaybackId id) const;
struct DeviceInfo {
QString name;
bool isDefault = false;
QString id;
};
static QVector<DeviceInfo> enumeratePlaybackDevices();
static QVector<DeviceInfo> enumerateCaptureDevices();
signals:
void contextAboutToReset(); // Все зависимые устройства закрываются синхронно перед uninit контекста.
void contextRestored();
void devicesChanged();
private:
SoundManager();
~SoundManager();
struct PcmSession {
ma_sound* sound = nullptr;
ma_audio_buffer* buffer = nullptr;
std::shared_ptr<std::vector<int16_t>> owner;
uint64_t frames = 0, origin = 0;
int rate = 0;
bool transient = false;
bool detached = false;
~PcmSession();
};
PcmSession* pcmSession(PlaybackId id) const;
bool createPcmNode(PcmSession& session, bool playing);
void recoverAudio();
bool openContext();
void openOutput();
struct VoiceRoute {
AudioDevice device;
QString wanted, actual;
std::array<VoiceAudioIo*, 2> voices{};
};
QString resolveOutput(const QString& requested);
bool openVoiceRoute(VoiceRoute& route);
void mergeVoiceRoutes();
static unsigned renderOutput(ma_engine* engine, const std::array<VoiceAudioIo*, 2>& voices,
float* out, unsigned frames, int64_t firstTimeUs);
std::atomic<unsigned> m_renderErrors{0};
std::array<VoiceAudioIo*, 2> m_voices{};
std::map<QString, std::unique_ptr<VoiceRoute>> m_voiceRoutes;
std::map<VoiceAudioIo*, QString> m_voiceSelections; // Desired выбор сохраняется при объединении physical outputs.
QString m_actualOutput;
bool m_resetting = false;
bool m_restoreSelections = false;
struct LoadedSound {
std::shared_ptr<PcmSession> pcm;
};
ma_engine* m_engine = nullptr;
ma_context* m_context = nullptr;
AudioDevice m_output;
QTimer m_recoveryTimer;
QElapsedTimer m_clock;
qint64 m_contextRetryAt = 0;
int m_contextRetryDelay = 250;
QString m_playbackId, m_captureId;
ma_backend m_backend = ma_backend_null;
bool m_backendSelected = false;
std::map<PlaybackId, std::unique_ptr<PcmSession>> m_pcmSessions;
PlaybackId m_nextPlaybackId = 0;
bool m_initialized = false;
bool m_enabled = true;
float m_volume = 0.8f;
QHash<QString, EventSettings> m_eventSettings;
QHash<QString, LoadedSound> m_sounds;
QString m_loopName;
};