146 lines
5.1 KiB
C++
146 lines
5.1 KiB
C++
/* Общий miniaudio-контекст и выход desktop-приложения: звуки событий, PCM и голосовые потоки.
|
|
* GUI управляет устройствами и playback-сессиями; output смешивает звук и отдаёт reference для AEC.
|
|
* init/shutdown ограничивают время жизни контекста; зависимые capture devices закрываются перед его сбросом. */
|
|
#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, bool measured);
|
|
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;
|
|
};
|