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.
145 lines
5.1 KiB
145 lines
5.1 KiB
/* Общий 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); |
|
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; |
|
};
|
|
|