From 7aea8f6cf1490c524c11f537f513e937be5de9d7 Mon Sep 17 00:00:00 2001 From: snt Date: Sat, 20 Apr 2024 20:19:16 +0200 Subject: [PATCH] WIP miniaudio working, some sigsev while playing... --- docs/changelog.txt | 22 ++-- docs/compiling.txt | 4 +- docs/lms-audio.xlm | 3 +- docs/roadmap.txt | 42 ++++---- libremediaserver-audio.pro | 13 ++- src/audiolayerwidget.cpp | 179 +++++++++++++++++++-------------- src/audiolayerwidget.h | 44 +++----- src/audiomasterwidget.cpp | 12 +-- src/audiomasterwidget.h | 2 +- src/audiowidget.cpp | 126 ++++++++++++++++++++--- src/audiowidget.h | 25 ++++- src/defines.h | 9 +- src/libremediaserver-audio.cpp | 91 ++++++++--------- src/libremediaserver-audio.h | 15 +-- src/libremediaserver-audio.ui | 8 +- src/main.cpp | 9 +- src/medialibrary.cpp | 25 ++--- src/medialibrary.h | 1 + src/settings.cpp | 5 + src/settings.h | 12 ++- src/settingsdialog.cpp | 56 +++++++++-- src/settingsdialog.h | 5 + src/settingsdialog.ui | 60 ++++++----- 23 files changed, 469 insertions(+), 299 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 4ecb84b..d019bb8 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -1,18 +1,14 @@ ******************************************************************************* Libre Media Server Audio - An Open source Media Server for arts and performing. -(c) Santiago Noreña 2012-2024 -Code: https://git.criptomart.net/libremediaserver +(c) Criptomart - Santiago Noreña 2012-2024 +https://git.criptomart.net/libremediaserver ******************************************************************************* Lbre Media Server ChangeLog -v 0.1.3 (28/05/2024) - -+ Ubuntu 22.04 jammy. -+ Use SFML as audio engine. -+ Qt 5.15.3. -+ pitch. -+ loop. +v 1.4 +- change engine to miniaudio. +- Select sound device output. - pan. - Show faders values. --> Hacer UI por fader: mute/centrado, valor, visualizador: @@ -20,6 +16,14 @@ v 0.1.3 (28/05/2024) - SettingsDialog. - Load/save conf file. +v 0.1.3 (19/04/2024) + ++ Ubuntu 22.04 jammy. ++ Use SFML as audio engine. ++ Qt 5.15.3. ++ pitch. ++ loop. + v 0.1.2 (12/08/2015) - GUI config diff --git a/docs/compiling.txt b/docs/compiling.txt index d44317b..0648d90 100644 --- a/docs/compiling.txt +++ b/docs/compiling.txt @@ -1,6 +1,6 @@ ******************************************************************************* -Libre Media Server Audio - An Open source Media Server. -(c) Santiago Noreña 2014-2024 +Libre Media Server Audio - An Open source Media Server for arts and performing. +(c) Criptomart - Santiago Noreña 2012-2024 https://git.criptomart.net/libremediaserver ******************************************************************************* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. diff --git a/docs/lms-audio.xlm b/docs/lms-audio.xlm index 37e53c9..49aac0e 100644 --- a/docs/lms-audio.xlm +++ b/docs/lms-audio.xlm @@ -1,5 +1,6 @@ - + + diff --git a/docs/roadmap.txt b/docs/roadmap.txt index 4dde0af..91b9a4b 100644 --- a/docs/roadmap.txt +++ b/docs/roadmap.txt @@ -1,15 +1,18 @@ ******************************************************************************* Libre Media Server Audio - An Open source Media Server for arts and performing. -(c) Santiago Noreña 2012-2024 -Code: https://git.criptomart.net/libremediaserver +(c) Criptomart - Santiago Noreña 2012-2024 +https://git.criptomart.net/libremediaserver ******************************************************************************* Libre Media Server Roadmap -(or a whislist...) +(en continuo crecimiento...) -v 0.2.1 +v 0.2.x - skin, UI/UX - live input. +- insertar/bypass/eliminar audio procesadores sin reiniciar por capa y master. (compresor, equs). +- FX en capas master para que se puedan usar como envíos de auxiliar. +- Enroutado de masters en otros masters (retorno de efectos). v 0.2.0 - Use sACN directly. @@ -17,15 +20,13 @@ v 0.2.0 + hay que empaquetar OLA, incluirlo en el binario, o implementar sACN y linkarlo estáticamente. + https://github.com/ETCLabs/sACN - Qt6. -- audio processing (eq, rev, compresor, ...). +- audio processing (eq, rev, compresor, ...) por master y capa. - CIPT/MSex, send icons play-pause-stop. - Rasp build. - Octopus Sound Card support (6 outputs - 8 inputs). v 1.5 -- Select sound device output. -- Multi device output, router layers to devices and audio outputs. - - Jack/pipewire integration? +- Multi devices output. - Rose noise and sine generator in menu to test system. - Play Mode: - Play all medias found in folder consecutevily or random, with loop. @@ -34,18 +35,19 @@ v 1.5 - loop points. - play offset. ¿stop offset? - number of layers configured in conf file, up to 256. -- Dar la opción clickeando en el widget de tiempo de poner una cuenta atrás en vez de hacia delante. -- Master Layer: - - Mute. - - Pan. +- Master Bus Layer: + - each layer will have one "Gain" prefader that acts in source, "Vol" in v 1.3. + - each layer will have one volume dmx channel for each bus layer. One aux "Send" prefader. + - mute/panic. + - fader + value + - pan. + - magicq .hed + - audio device linked, outputs will be redirected there. + - dmx address + universe settings. - Keyboards strokes, select files from ui. +- Dar la opción clickeando en el widget de tiempo de poner una cuenta atrás en vez de hacia delante. - LOGs y entrada de comandos. - Bufgix: depurar errores cuando no carga la librería de medias, cambia el númmero de capas, cambia el universo, etc. - -v 1.4 -- pan. -- Show faders values. - --> Hacer UI por fader: mute/centrado, valor, visualizador: - --> Hacer UI con la visualización de tiempos. -- SettingsDialog. -- Load/save conf file. +- Refactor AudioMasterWidget to AudioDMXReceptionWidget. Master functions will be in AudioWidget. +- New control mode without pitch control, it saves resources. MA_SOUND_FLAG_NO_PITCH +- Vumeter or indicator about audio output in layer and master. diff --git a/libremediaserver-audio.pro b/libremediaserver-audio.pro index e3c5816..5e1f73f 100644 --- a/libremediaserver-audio.pro +++ b/libremediaserver-audio.pro @@ -2,6 +2,7 @@ TEMPLATE = app TARGET = libremediaserver-audio QT += webkitwidgets widgets HEADERS += src/libremediaserver-audio.h \ + src/miniaudio.h \ src/medialibrary.h \ src/olathread.h \ src/audiolayerwidget.h \ @@ -13,6 +14,7 @@ HEADERS += src/libremediaserver-audio.h \ src/settingsdialog.h \ src/layersettingswidget.h SOURCES += src/main.cpp \ + src/miniaudio.c \ src/libremediaserver-audio.cpp \ src/medialibrary.cpp \ src/olathread.cpp \ @@ -25,12 +27,13 @@ SOURCES += src/main.cpp \ FORMS += src/libremediaserver-audio.ui \ src/settingsdialog.ui \ src/layersettingswidget.ui -LIBS += -lola -lolacommon +CCFLAG += -msse2 -mavx2 #-fsanitize=address -g -O0 +QMAKE_CXXFLAGS += $$(CXXFLAG) +#QMAKE_CXXFLAGS += -fsanitize=address -g -O0 +QMAKE_CFLAGS += $$(CCFLAG) +QMAKE_LFLAGS += $$(LDFLAG) +LIBS += -lola -lolacommon -ldl -lpthread -lm # -lcitp -LIBS += -L$$PWD/SFML/lib/ -lsfml-audio -lsfml-system -INCLUDEPATH += $$PWD/SFML/include -DEPENDPATH += $$PWD/SFML/include -PRE_TARGETDEPS += $$PWD/SFML/lib/libsfml-audio.so $$PWD/SFML/lib/libsfml-system.so OTHER_FILES += \ LICENSE.txt \ docs/compiling.txt \ diff --git a/src/audiolayerwidget.cpp b/src/audiolayerwidget.cpp index a989031..90735c4 100644 --- a/src/audiolayerwidget.cpp +++ b/src/audiolayerwidget.cpp @@ -1,31 +1,20 @@ #include "audiolayerwidget.h" -#include - -#include -#include -#include -#include AudioLayerWidget::AudioLayerWidget(QWidget *parent, QString name): QGroupBox(parent) , m_suspendResumeButton(0) - , m_watchDMX(new QTimer(this)) - , m_currentMedia(" ") - , m_running(false) + , m_refreshGUI(new QTimer(this)) + , m_currentMedia("") + , m_mediaLoaded(false) { - this->setTitle(name); - QVBoxLayout *layout = new QVBoxLayout; QHBoxLayout *status = new QHBoxLayout; m_statusLabel = new QLabel; m_statusLabel->setText(STATUS_LABEL); m_statusValue = new QLabel; -// m_receiveDMX = new QCheckBox("Receiving DMX", this); -// m_receiveDMX->setChecked(false); -// status->addWidget(m_receiveDMX); status->addWidget(m_statusLabel); status->addWidget(m_statusValue); m_loopCheck = new QCheckBox(); @@ -58,8 +47,8 @@ AudioLayerWidget::AudioLayerWidget(QWidget *parent, QString name): m_volumeLabel = new QLabel; m_volumeLabel->setText(tr(VOLUME_LABEL)); m_volumeSlider = new QSlider(Qt::Horizontal); - m_volumeSlider->setMinimum(0); - m_volumeSlider->setMaximum(90); + m_volumeSlider->setMinimum(-100); + m_volumeSlider->setMaximum(100); m_volumeSlider->setSingleStep(1); m_volumeIndicator = new QLabel; volumeBox->addWidget(m_volumeLabel, 0, 0); @@ -111,13 +100,8 @@ AudioLayerWidget::AudioLayerWidget(QWidget *parent, QString name): progressTime->addWidget(m_totalTimeValue); layout->addLayout(progressTime); - QHBoxLayout *progressSlider = new QHBoxLayout; - m_progressLabel = new QLabel; - m_progressLabel->setText(PROGRESS_LABEL); m_progressSlider = new QSlider(Qt::Horizontal); - progressSlider->addWidget(m_progressLabel); - progressSlider->addWidget(m_progressSlider); - layout->addLayout(progressSlider); + layout->addWidget(m_progressSlider); m_suspendResumeButton = new QPushButton(this); m_suspendResumeButton->setText(tr(SUSPEND_LABEL)); @@ -125,12 +109,8 @@ AudioLayerWidget::AudioLayerWidget(QWidget *parent, QString name): layout->addWidget(m_suspendResumeButton); this->setLayout(layout); - - connect(m_watchDMX, SIGNAL(timeout()), - this, SLOT(refreshGUI())); - m_watchDMX->start(100); - - m_music.setAttenuation(0); + connect(m_refreshGUI, SIGNAL(timeout()), this, SLOT(refreshGUI())); + m_refreshGUI->start(100); } AudioLayerWidget::~AudioLayerWidget() @@ -140,36 +120,42 @@ AudioLayerWidget::~AudioLayerWidget() void AudioLayerWidget::volumeChanged(int value) { - m_music.setVolume(value); + float result; + + if (m_mediaLoaded == false) + return; + result = ma_volume_linear_to_db(value); + ma_sound_group_set_volume(¤tSound, result); } void AudioLayerWidget::panChanged(int value) { - m_music.setRelativeToListener(true); - sf::Vector3f pos = m_music.getPosition(); - //m_music.setSpati(0, 0, 0); - qreal pan = (value - 128) / 64.0f; - qWarning("change pan %f", pan); - //m_music.setPosition(pan, 0.0, sqrtf(1.0 + pan*pan)); - m_music.setPosition(pan, 0.0f, -1.0f); - //pos = m_music.getPosition(); - qWarning("%f %f %f", pos.x, pos.y, pos.z); - qWarning("is rel %i", m_music.isRelativeToListener()); + float result; + if (m_mediaLoaded == false) + return; + result = (value / 128.0) - 128; + ma_sound_group_set_pan(¤tSound, result); } void AudioLayerWidget::pitchChanged(int value) { - m_music.setPitch(qreal(value / 128.0 )); + float result; + if (m_mediaLoaded == false) + return; + result = (value / 128.0) - 128; + ma_sound_group_set_pitch(¤tSound, result); } void AudioLayerWidget::loopChanged(int value) { - m_music.setLoop(value); + if (m_mediaLoaded == false) + return; + ma_sound_set_looping(¤tSound, value); } void AudioLayerWidget::setVol(qreal vol) { - m_music.setVolume(vol); + this->volumeChanged(vol); m_volumeSlider->blockSignals(true); m_volumeSlider->setValue(vol); m_volumeIndicator->setText(QString::number(vol)); @@ -186,7 +172,7 @@ void AudioLayerWidget::setPan(qreal pan) void AudioLayerWidget::setPitch(qreal pitch) { - m_music.setPitch(float(pitch / 128 )); + this->pitchChanged(pitch); m_pitchSlider->blockSignals(true); m_pitchSlider->setValue(pitch); m_pitchSlider->blockSignals(false); @@ -194,6 +180,12 @@ void AudioLayerWidget::setPitch(qreal pitch) void AudioLayerWidget::loadMedia(QString file) { + ma_result result; + ma_format *format = 0; + ma_uint32 *channels = 0; + ma_uint32 *sampleRate = 0; + + if (m_currentMedia.compare(file) == 0 ) { return; } @@ -201,21 +193,46 @@ void AudioLayerWidget::loadMedia(QString file) qWarning("Can not access to file %s", file.toLatin1().constData()); return; } - // Load an ogg music file - if (!m_music.openFromFile(file.toStdString())) { - qWarning("Can not open file %s", file.toLatin1().constData()); + ma_engine engine = AudioWidget::getInstance()->getEngine(); + if (currentSound.ownsDataSource == true) + { + ma_sound_uninit(¤tSound); + } + result = ma_sound_init_from_file(&engine, file.toLatin1(), MA_SOUND_FLAG_NO_SPATIALIZATION | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM, NULL, NULL, ¤tSound); + if (result != MA_SUCCESS) { + qWarning("WARNING: Failed to load sound %s", file.toLatin1().constData()); return; } - m_currentMedia = file; - durationChanged(m_music.getDuration().asMilliseconds()); + float pLength = this->getDuration(); + result = ma_sound_get_data_format(¤tSound, format, channels, sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + qWarning("WARNING: Failed to get data format %s", file.toLatin1().constData()); + return; + } + m_mediaLoaded = true; + durationChanged(pLength * 1000); fileLoaded(file); - // Display music informations std::cout << "File loaded: " << file.toLatin1().constData() << " : " << std::endl; - std::cout << " " << m_music.getDuration().asSeconds() << " seconds"; - std::cout << " " << m_music.getSampleRate() << " samples / sec"; - std::cout << " " << m_music.getChannelCount() << " channels" << std::endl; + std::cout << " " << pLength << " seconds"; + std::cout << " -- " << sampleRate << " samples/sec"; + std::cout << " -- " << format << " format"; + std::cout << " -- " << channels << " channels" << std::endl; +} + +float AudioLayerWidget::getDuration() +{ + float pLength; + + if (m_mediaLoaded == false) + return 0; + ma_result result = ma_sound_get_length_in_seconds(¤tSound, &pLength); + if (result != MA_SUCCESS) { + qWarning("WARNING: Failed to get total duration %s", m_currentMedia.toLatin1().constData()); + return 0; + } + return pLength; } void AudioLayerWidget::fileLoaded(QString file) @@ -236,56 +253,66 @@ void AudioLayerWidget::durationChanged(qint64 dur) void AudioLayerWidget::play(bool loop) { - m_music.play(); - m_music.setLoop(loop); + if (m_mediaLoaded == false) + return; + ma_sound_set_looping(¤tSound, loop); + ma_sound_start(¤tSound); m_loopCheck->blockSignals(true); - m_loopCheck->setChecked(m_music.getLoop()); + m_loopCheck->setChecked(loop); m_loopCheck->blockSignals(false); } void AudioLayerWidget::pause() { - m_music.pause(); + if (m_mediaLoaded == false) + return; + ma_sound_stop(¤tSound); } void AudioLayerWidget::stop() { - m_music.stop(); + if (m_mediaLoaded == false) + return; + ma_sound_stop(¤tSound); + ma_sound_seek_to_pcm_frame(¤tSound, 0); + m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(0)); + m_statusValue->setText(STOP_LABEL); + m_suspendResumeButton->setText(tr(STOP_LABEL)); + m_progressSlider->setValue(0); } void AudioLayerWidget::refreshGUI() { -// m_receiveDMX->setChecked(false); - int progress; - switch (m_music.getStatus()) { - case sf::SoundSource::Playing: - progress = m_music.getPlayingOffset().asMilliseconds(); + float progress; + + if (m_mediaLoaded == false) + return; + if (currentSound.ownsDataSource == 0) + return; + switch (ma_sound_is_playing(¤tSound)) { + case true: + ma_sound_get_cursor_in_seconds(¤tSound, &progress); + progress *= 1000; m_progressSlider->setValue(progress); m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(progress)); m_statusValue->setText(PLAY_LABEL); m_suspendResumeButton->setText(tr(SUSPEND_LABEL)); break; - case sf::SoundSource::Paused: + case false: m_statusValue->setText(PAUSE_LABEL); m_suspendResumeButton->setText(tr(RESUME_LABEL)); break; - case sf::SoundSource::Stopped: - m_progressTime->setTime(QTime::fromMSecsSinceStartOfDay(0)); - m_statusValue->setText(STOP_LABEL); - m_suspendResumeButton->setText(tr(RESUME_LABEL)); - m_progressSlider->setValue(0); - break; } - //m_loopCheck->blockSignals(true); - //m_loopCheck->setChecked(m_music.getLoop()); - //m_loopCheck->blockSignals(false); } void AudioLayerWidget::toggleSuspendResume() { - if (m_music.getStatus() == sf::SoundSource::Playing) { - m_music.pause(); + if (m_mediaLoaded == false) + return; + if (ma_sound_is_playing(¤tSound)) { + this->pause(); } else { - m_music.play(); + if (ma_sound_at_end(¤tSound)) + ma_sound_seek_to_pcm_frame(¤tSound, 0); + ma_sound_start(¤tSound); } } - diff --git a/src/audiolayerwidget.h b/src/audiolayerwidget.h index 2d214d2..7362bd6 100644 --- a/src/audiolayerwidget.h +++ b/src/audiolayerwidget.h @@ -1,22 +1,25 @@ #ifndef AUDIOLAYERWIDGET_H #define AUDIOLAYERWIDGET_H -#include +#include +#include +#include +#include #include +#include +#include #include #include #include #include -#include #include #include #include -#include -#include - #include "defines.h" +#include "audiowidget.h" +#include "miniaudio.h" class AudioLayerWidget : public QGroupBox { @@ -32,20 +35,8 @@ public: * @param file name with full path */ void loadMedia(QString file); - - /** - * @brief play - */ void play(bool loop); - - /** - * @brief stop - */ void stop(); - - /** - * @brief pause - */ void pause(); /** @@ -53,20 +44,11 @@ public: * @param vol volume range 0 -100 */ void setVol(qreal vol); - - /** - * @brief resume - */ void resume(); - void setPan(qreal pan); - void setPitch(qreal pitch); - void setLoop(bool on); - //void setEntryPoint(qreal entry); - //void setEndPoint(qreal end); public slots: @@ -120,14 +102,12 @@ private: QLabel * m_folderLabel; QLabel * m_folderValue; - //QCheckBox *m_receiveDMX; - QTimer *m_watchDMX; - + QTimer *m_refreshGUI; QString m_currentMedia; + ma_sound currentSound; + ma_bool8 m_mediaLoaded; - bool m_running; - - sf::Music m_music; + float getDuration(); private slots: diff --git a/src/audiomasterwidget.cpp b/src/audiomasterwidget.cpp index 4d0f1bb..c8a2db1 100644 --- a/src/audiomasterwidget.cpp +++ b/src/audiomasterwidget.cpp @@ -1,22 +1,12 @@ #include "audiomasterwidget.h" -#include - AudioMasterWidget::AudioMasterWidget(QWidget *parent) : QGroupBox(parent) - //, m_file(new QLabel) - //, m_folder(new QLabel) - //, m_vol(new QSlider) - //, m_mute(new QCheckBox) - //, m_status(new QLabel) , m_receiveDMX(new QCheckBox) , m_watchDMX(new QTimer) { QVBoxLayout *vbox = new QVBoxLayout; - //vbox->addWidget(m_status); - //vbox->addWidget(m_vol); - //vbox->addWidget(m_mute); - m_receiveDMX->setText("Receiving DMX"); + m_receiveDMX->setText("DMX signal"); vbox->addWidget(m_receiveDMX); this->setLayout(vbox); connect(m_watchDMX, SIGNAL(timeout()), diff --git a/src/audiomasterwidget.h b/src/audiomasterwidget.h index 4df388c..bdac3e5 100644 --- a/src/audiomasterwidget.h +++ b/src/audiomasterwidget.h @@ -6,7 +6,7 @@ #include #include #include - +#include //#include "ui_audiomasterwidget.h" diff --git a/src/audiowidget.cpp b/src/audiowidget.cpp index 1d7ddc8..f4cf44f 100644 --- a/src/audiowidget.cpp +++ b/src/audiowidget.cpp @@ -1,4 +1,5 @@ #include "audiowidget.h" +#include "miniaudio.c" AudioWidget *AudioWidget::_instance = 0; @@ -11,30 +12,117 @@ AudioWidget *AudioWidget::getInstance() { return _instance; } -AudioWidget::AudioWidget() +AudioWidget::AudioWidget() : + engineCount(0) { + this->startEngine(); layout = new QHBoxLayout(); for (int i= 0; i < Settings::getInstance()->getLayersNumber(); i++ ) { - layout->insertWidget(i, new AudioLayerWidget(this, tr("Layer %1").arg(i))); + layout->insertWidget(i, new AudioLayerWidget(this, tr("Layer %1").arg(i + 1))); } setLayout(layout); -// qDebug( "Init AudioWidget"); +} + +void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + (void)pInput; + //Do master audio processing before sending to device. + ma_engine_read_pcm_frames((ma_engine*)pDevice->pUserData, pOutput, frameCount, NULL); +} + +void AudioWidget::startEngine(int n) +{ + this->startContext(); + this->startDevice(n); +} + +void AudioWidget::startDevice(int id) +{ + ma_result result; + ma_device_config deviceConfig; + ma_engine_config engineConfig; + + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.pDeviceID = &pPlaybackDeviceInfos[id].id; + deviceConfig.playback.format = resourceManager.config.decodedFormat; + deviceConfig.playback.channels = 0; + deviceConfig.sampleRate = resourceManager.config.decodedSampleRate; + deviceConfig.dataCallback = data_callback; + deviceConfig.pUserData = &engines[engineCount]; + result = ma_device_init(&context, &deviceConfig, &devices[engineCount]); + if (result != MA_SUCCESS) { + qCritical("Failed to initialize audio device %s.", pPlaybackDeviceInfos[id].name); + return; + } + engineConfig = ma_engine_config_init(); + engineConfig.pDevice = &devices[engineCount]; + engineConfig.pResourceManager = &resourceManager; + engineConfig.noAutoStart = MA_TRUE; + result = ma_engine_init(NULL, &engines[engineCount]); + if (result != MA_SUCCESS) { + qCritical("Failed to initialize audio engine."); + return; + } + result = ma_engine_start(&engines[engineCount]); + if (result != MA_SUCCESS) { + qCritical("Failed to start audio engine %d.", engineCount); + } + //engineCount +=1; + iChosenDevice = id; + qInfo("Initialized audio device %d: %s", id, pPlaybackDeviceInfos[id].name); +} + +void AudioWidget::startContext() +{ + ma_result result; + + resourceManagerConfig = ma_resource_manager_config_init(); + resourceManagerConfig.decodedFormat = ma_format_f32; /* ma_format_f32 should almost always be used as that's what the engine (and most everything else) uses for mixing. */ + resourceManagerConfig.decodedChannels = 0; + resourceManagerConfig.decodedSampleRate = 0; + resourceManagerConfig.jobThreadCount = 0; + result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); + if (result != MA_SUCCESS) { + qCritical("Failed to initialize audio resource manager."); + return; + } + result = ma_context_init(NULL, 0, NULL, &context); + if (result != MA_SUCCESS) { + qCritical("Failed to initialize audio context."); + return; + } +} + +void AudioWidget::startEngine() +{ + this->startContext(); + this->getAllAudioDevices(); + iChosenDevice = Settings::getInstance()->getAudioDeviceId(); + this->startDevice(iChosenDevice); +} + +ma_engine AudioWidget::getEngine() +{ + return(engines[engineCount]); +} + +void AudioWidget::stopEngine() +{ + ma_engine_uninit(&engines[engineCount]); + ma_device_uninit(&devices[engineCount]); + ma_context_uninit(&context); + ma_resource_manager_uninit(&resourceManager); } void AudioWidget::mediaLoaded(int layer, QString media) { QLayoutItem * const item = layout->itemAt(layer); dynamic_cast(item->widget())->loadMedia(media); -// qDebug() << "AudioWidget::mediaLoaded Received layer: " << layer -// << "Media: " << media; } void AudioWidget::volChanged(int layer, qreal vol) { QLayoutItem * const item = layout->itemAt(layer); dynamic_cast(item->widget())->setVol(vol); -// qDebug() << "AudioWidget::volChanged Received layer: " << layer -// << "Vol : " << vol; - } void AudioWidget::panChanged(int layer, qreal vol) { @@ -65,10 +153,20 @@ void AudioWidget::playbackChanged(int layer, Status status) break; } } -/* -void AudioWidget::layerReceived(int layer) -{ - QLayoutItem * const item = layout->itemAt(layer); - dynamic_cast(item->widget())->updateWatchDMX(true); -}*/ +// enum all audio devices in system +void AudioWidget::getAllAudioDevices() +{ + ma_result result; + + result = ma_context_get_devices(&context, &pPlaybackDeviceInfos, &playbackDeviceCount, NULL, NULL); + if (result != MA_SUCCESS) { + qCritical("Failed to enumerate playback devices."); + ma_context_uninit(&context); + return; + } + for (ma_uint32 iAvailableDevice = 0; iAvailableDevice < playbackDeviceCount; iAvailableDevice += 1) { + qInfo("%d: : %s", iAvailableDevice, pPlaybackDeviceInfos[iAvailableDevice].name); + } + return ; +} diff --git a/src/audiowidget.h b/src/audiowidget.h index 2243a34..bf3c7c9 100644 --- a/src/audiowidget.h +++ b/src/audiowidget.h @@ -4,12 +4,14 @@ #include #include #include +#include + #include "audiomasterwidget.h" #include "audiolayerwidget.h" -#include "defines.h" #include "settings.h" +#include "miniaudio.h" +#include "defines.h" -#include "SFML/Audio/SoundSource.hpp" class AudioWidget : public QWidget { @@ -19,20 +21,37 @@ class AudioWidget : public QWidget public: + static AudioWidget *getInstance(); + ma_engine getEngine(); + void stopEngine(); + void startEngine(int id); + ma_device_info* pPlaybackDeviceInfos; + ma_uint32 playbackDeviceCount; + ma_uint32 iChosenDevice; + protected: - static AudioWidget *getInstance(); void mediaLoaded(int layer, QString media ); void volChanged(int layer, qreal vol); void panChanged(int layer, qreal pan); void pitchChanged(int layer, qreal pitch); void playbackChanged(int layer, Status status); + void startEngine(); private: static AudioWidget *_instance; AudioWidget(); QHBoxLayout *layout; + ma_engine engines[MAX_DEVICES]; + ma_uint32 engineCount; + ma_device devices[MAX_DEVICES]; + ma_context context; + ma_resource_manager_config resourceManagerConfig; + ma_resource_manager resourceManager; + void getAllAudioDevices(); + void startDevice(int id); + void startContext(); signals: diff --git a/src/defines.h b/src/defines.h index 961ec69..bc57975 100644 --- a/src/defines.h +++ b/src/defines.h @@ -1,7 +1,7 @@ #ifndef DEFINES_H #define DEFINES_H -#define VERSION "LibreMediaServer-Audio 0.1.3" +#define VERSION "LibreMediaServer-Audio 0.1.4" #define COPYRIGHT "(C) 2014-2024 Santi Norena lms@criptomart.net" #define LICENSE "GPL 3 License. See LICENSE.txt and credits.txt for details" @@ -9,11 +9,6 @@ #define DEFAULT_FILE "lms-audio.xlm" -const int DurationSeconds = 1; -const int ToneSampleRateHz = 600; -const int DataSampleRateHz = 44100; -const int BufferSize = 262144; - #define SUSPEND_LABEL "Pause playback" #define RESUME_LABEL "Resume playback" @@ -33,6 +28,8 @@ const int BufferSize = 262144; #define NOTIFY_INTERVAL 150 #define PULL_TIMER_INTERVAL 10 +#define MAX_DEVICES 16 +#define MAX_SOUNDS 4096 // struct where save the DMX settings for each layer struct dmxSetting { diff --git a/src/libremediaserver-audio.cpp b/src/libremediaserver-audio.cpp index d34abe9..0c223bc 100644 --- a/src/libremediaserver-audio.cpp +++ b/src/libremediaserver-audio.cpp @@ -1,7 +1,8 @@ /* - Libre Media Server - A Media Server Sotfware for stage and performing - Copyright (C) 2012-2024 Santi Noreña lms@criptomart.net + Libre Media Server Audio - An Open source Media Server for arts and performing. + (c) Criptomart - Santiago Noreña 2012-2024 + https://git.criptomart.net/libremediaserver This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,21 +20,16 @@ #include "libremediaserver-audio.h" -// QTextEdit * libreMediaServerAudio::textEdit = 0; libreMediaServerAudio::libreMediaServerAudio(QStringList args, QWidget *parent) : QMainWindow(parent) { - Q_UNUSED(args); - ui.setupUi(this); - this->setWindowTitle(VERSION); + Q_UNUSED(args); + ui.setupUi(this); + this->setWindowTitle(VERSION); - // Lee la configuración por defecto - Settings::getInstance()->readFile(); - - // Inicia el objeto de conexión a ola - ola = new olaThread(); - Q_CHECK_PTR(ola); + Settings *set = Settings::getInstance(); + set->readFile(); /* if (args.contains("-log")) { // Inicia el widget Terminal @@ -54,43 +50,35 @@ libreMediaServerAudio::libreMediaServerAudio(QStringList args, QWidget *parent) textEdit, SLOT(append(QString)), Qt::QueuedConnection); */ - this->setWindowTitle(VERSION); + this->setWindowTitle(VERSION); + qDebug() << VERSION; + qDebug() << COPYRIGHT; + qDebug() << LICENSE; -// qDebug() << QDate::currentDate().toString() << " "<< QTime::currentTime().toString(); - qDebug() << VERSION; - qDebug() << COPYRIGHT; - qDebug() << LICENSE; - - setCentralWidget(AudioWidget::getInstance()); - - // Inicia el widget Master. - amw = new AudioMasterWidget(this); - QDockWidget *topWidget = new QDockWidget(tr("Master"), this); - topWidget->setAllowedAreas(Qt::TopDockWidgetArea); - topWidget->setWidget(amw); - addDockWidget(Qt::TopDockWidgetArea, topWidget); - - // Conectamos los menus + // start audio engine + MediaLibrary::getInstance()->initMediaLibrary(); + setCentralWidget(AudioWidget::getInstance()); + amw = new AudioMasterWidget(this); + QDockWidget *topWidget = new QDockWidget(tr("Master"), this); + topWidget->setAllowedAreas(Qt::TopDockWidgetArea); + topWidget->setWidget(amw); + addDockWidget(Qt::TopDockWidgetArea, topWidget); + // ola setup + ola = new olaThread(); + Q_CHECK_PTR(ola); + connect(set, SIGNAL(registerUniverse(int)), ola, SLOT(registerUniverse(int))); + ola->registerUniverse(); // register now all the universes + ola->blockSignals(true); + connect(ola, SIGNAL (layerReceived()), amw, SLOT(updateWatchDMX())); + connect(ola, SIGNAL(dmxOutput(int, int, int)), this, SLOT(dmxInput(int, int, int))); + connect(ui.actionLaunch_OLA_Setup, SIGNAL(triggered()), this, SLOT(olasetup())); + ola->start(QThread::TimeCriticalPriority ); + ola->blockSignals(false); + // menus connect(ui.actionOpen_conf, SIGNAL(triggered()), this, SLOT(openFile())); connect(ui.actionSave_conf, SIGNAL(triggered()), this, SLOT(saveFile())); connect(ui.action_Settings, SIGNAL(triggered()), this, SLOT(settings())); - connect(ui.actionLaunch_OLA_Setup, SIGNAL(triggered()), this, SLOT(olasetup())); - - connect(Settings::getInstance(), SIGNAL( registerUniverse(int) ), - ola, SLOT( registerUniverse(int) ) ); - ola->registerUniverse(); // register now all the universes - ola->blockSignals(true); - connect(ola, SIGNAL (layerReceived()), - amw, SLOT(updateWatchDMX())); - - // Inicia la media Library - MediaLibrary::getInstance()->initMediaLibrary(); - - // Inicia la lectura de datos DMX - ola->start(QThread::TimeCriticalPriority ); - ola->blockSignals(false); - connect(ola, SIGNAL( dmxOutput(int, int, int) ), - this, SLOT( dmxInput(int, int, int) ) ); + connect(set, SIGNAL(audioDeviceChanged(int)), this, SLOT(audioDeviceChanged(int))); qDebug("Init Complete"); } @@ -101,8 +89,7 @@ libreMediaServerAudio::libreMediaServerAudio(QStringList args, QWidget *parent) libreMediaServerAudio::~libreMediaServerAudio() { ola->stop(); -// qDebug() << QDate::currentDate() << QTime::currentTime(); -// qDebug() << "********************************************************************************"; + AudioWidget::getInstance()->stopEngine(); } /////////////////////////////////////////////////////////////////// @@ -174,11 +161,11 @@ void libreMediaServerAudio::dmxInput(int layer, int channel, int value) break; case VOLUME_COARSE: f = ( value * 0x100 ) + ola->getValue(layer, VOLUME_FINE); - AudioWidget::getInstance()->volChanged(layer, f / 655.35); + AudioWidget::getInstance()->volChanged(layer, (f / 655.35)); break; case VOLUME_FINE: f = ( ola->getValue(layer, VOLUME_COARSE) * 0x100 ) + value; - AudioWidget::getInstance()->volChanged(layer, f / 655.35); + AudioWidget::getInstance()->volChanged(layer, (f / 655.35)); break; case PAN: AudioWidget::getInstance()->panChanged(layer, value); @@ -208,3 +195,9 @@ void libreMediaServerAudio::dmxInput(int layer, int channel, int value) break; } } + +void libreMediaServerAudio::audioDeviceChanged(int id) +{ + AudioWidget::getInstance()->stopEngine(); + AudioWidget::getInstance()->startEngine(id); +} diff --git a/src/libremediaserver-audio.h b/src/libremediaserver-audio.h index babed0e..ea88afe 100644 --- a/src/libremediaserver-audio.h +++ b/src/libremediaserver-audio.h @@ -1,6 +1,7 @@ /* - Libre Media Server - A Media Server Sotfware for stage and performing - Copyright (C) 2012-2014 Santiago Noreña libremediaserver@gmail.com + Libre Media Server Audio - An Open source Media Server for arts and performing. + (c) Criptomart - Santiago Noreña 2012-2024 + https://git.criptomart.net/libremediaserver This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,17 +27,17 @@ #include #include #include - +#include #include #include -#include "medialibrary.h" #include "audiowidget.h" -#include "settings.h" +#include "medialibrary.h" #include "olathread.h" +#include "settings.h" #include "audiomasterwidget.h" -#include "defines.h" #include "settingsdialog.h" +#include "defines.h" #include "ui_libremediaserver-audio.h" @@ -70,7 +71,7 @@ private: void save(QFile *file); public slots: -// inline void toTerminal(QString msg) { textEdit->append(msg); } + void audioDeviceChanged(int id); private slots: diff --git a/src/libremediaserver-audio.ui b/src/libremediaserver-audio.ui index b4cec80..3256e49 100644 --- a/src/libremediaserver-audio.ui +++ b/src/libremediaserver-audio.ui @@ -7,8 +7,8 @@ 0 0 - 126 - 89 + 199 + 218 @@ -20,8 +20,8 @@ 0 0 - 126 - 29 + 199 + 22 diff --git a/src/main.cpp b/src/main.cpp index 4797866..c82efa8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,8 @@ /* - Libre Media Server - A media server for audio playback in stage arts - controlled by lingting protocols (DMX, ArtNet, ACN,...) - Copyright (C) 2015-2024 Santi Noreña lms@criptomart.net + Libre Media Server Audio - An Open source Media Server for arts and performing. + (c) Criptomart - Santiago Noreña 2012-2024 + https://git.criptomart.net/libremediaserver This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,9 +18,6 @@ along with this program. If not, see . */ -#include -//#include -//#include #include "libremediaserver-audio.h" // Handler for pipe the stderr to a log file and texEdit diff --git a/src/medialibrary.cpp b/src/medialibrary.cpp index e196001..ee46837 100644 --- a/src/medialibrary.cpp +++ b/src/medialibrary.cpp @@ -1,7 +1,5 @@ #include "medialibrary.h" -#include - MediaLibrary *MediaLibrary::_instance = 0; MediaLibrary *MediaLibrary::getInstance() { @@ -20,7 +18,6 @@ MediaLibrary::MediaLibrary(QObject *parent) : } void MediaLibrary::initMediaLibrary() { - qDebug("starting the media library"); QDir dir; if (!dir.cd(Settings::getInstance()->getPathMedia())) { qWarning("Can not cd to the path: %s", Settings::getInstance()->getPathMedia().toLatin1().constData()); @@ -46,7 +43,7 @@ void MediaLibrary::initMediaLibrary() { } /** - * This set every media file included in one library/folder + * fill the struct with all files in one library/folder */ QList MediaLibrary::getMediaInformation(QDir dir) { @@ -60,31 +57,27 @@ QList MediaLibrary::getMediaInformation(QDir dir) // Update the data base with the new file mediainf.Number = i; mediainf.MediaName = fileInfo.absoluteFilePath(); - mediainf.MediaLength = 1000; // ¿?¿?¿?¿? + mediainf.MediaLength = 1000; mediaList.append(mediainf); } return mediaList; } -/** Selects one media path from the library - * +/** + * returns the path to a media file from the library. */ - QString MediaLibrary::requestNewFile(int folder, int file){ - // Select one mediafile from the media library if (!m_media) { - qWarning("Media Library not init. Set a correct path to medias library"); + qWarning("MediaLibrary is not init. Set a correct path to media library"); return NULL; } QString newfile; if (folder < m_media->size()) { if (file < m_media->at(folder).m_MediaInformation.size()) { newfile = m_media->at(folder).m_MediaInformation.at(file).MediaName; - } else { - qDebug("MediaLibrary::requestNewFile(): Requested file is greater than files in library"); - } - } else { - qDebug("MediaLibrary::requestNewFile(): Requested folder is greater than media libraries"); - } + } else + qInfo("requestNewFile: Requested file %i is greater than files in library %i", file, m_media->at(folder).m_MediaInformation.size()); + } else + qInfo("requestNewFile: Requested folder %i is greater than media libraries %i", folder, m_media->size()); return newfile; } diff --git a/src/medialibrary.h b/src/medialibrary.h index 2f69234..e6b0b79 100644 --- a/src/medialibrary.h +++ b/src/medialibrary.h @@ -3,6 +3,7 @@ #include #include +#include #include "defines.h" #include "settings.h" diff --git a/src/settings.cpp b/src/settings.cpp index c328237..10485d1 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -28,6 +28,7 @@ void Settings::setPathMedia(QString path) // - The number of sources/layers controlled by DMX // - The first DMX channel of each source/layer // - The universe to bind in OLA +// - Audio device id void Settings::readFromFile(QString file) { QFile* xmlFile = new QFile(file); if (!xmlFile->open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -58,6 +59,10 @@ void Settings::readFromFile(QString file) { continue; } } + if(xmlReader->name() == "audioDevice") { + m_audioDeviceId = xmlReader->attributes().value("id").toLocal8Bit().toInt(); + continue; + } QString add = "layer"; add.append(QString("%1").arg(counter)); if((xmlReader->name() == add)) { diff --git a/src/settings.h b/src/settings.h index 990b3ca..f1c8eec 100644 --- a/src/settings.h +++ b/src/settings.h @@ -8,6 +8,7 @@ #include #include "medialibrary.h" +#include "audiowidget.h" #include "defines.h" /** @@ -96,6 +97,11 @@ public: else m_layersNumber = LAYERS_NUMBER; } + + inline int getAudioDeviceId() { return m_audioDeviceId; } + inline void setAudioDeviceId(int id) { m_audioDeviceId = id; } + + private: static Settings *_instance; @@ -106,6 +112,9 @@ private: // The path to media library QString m_pathmedia; + // The SO audio device id used + uint m_audioDeviceId; + /** Constructor * */ @@ -153,8 +162,7 @@ signals: */ void registerUniverse(int universe); -public slots: - + void audioDeviceChanged(int id); }; #endif // SETTINGS_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 98904d7..e5b8de7 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -2,18 +2,20 @@ #include "ui_settingsdialog.h" SettingsDialog::SettingsDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::SettingsDialog) + QDialog(parent) + , ui(new Ui::SettingsDialog) + , m_deviceDialog(NULL) { ui->setupUi(this); this->setWindowTitle("Settings"); ui->layersNumber->setValue(Settings::getInstance()->getLayersNumber()); - QString path("Path to root folder of the media tree: /n"); - path.append(Settings::getInstance()->getPathMedia()); - ui->mediaPath->setText(path); - - connect(ui->mediaPatchButton, SIGNAL(clicked()), + //QString path("Path to root folder of the media tree: /n"); + //path.append(Settings::getInstance()->getPathMedia()); + //ui->mediaPath->setText(path); + connect(ui->mediaPathButton, SIGNAL(clicked()), this, SLOT(changeMediaPath())); + connect(ui->audioDeviceButton , SIGNAL(clicked()), + this, SLOT(changeAudioDevice())); connect(ui->layersNumber, SIGNAL(valueChanged(int)), this, SLOT(layersChanged(int))); int layer = 0; @@ -42,7 +44,45 @@ void SettingsDialog::changeMediaPath() QString file = fileNames.at(0); Settings::getInstance()->setPathMedia(file); QString desc = tr("Media Path Changed to: %1").arg(file); - qDebug("%s", desc.toLatin1().constData()); + qInfo("%s", desc.toLatin1().constData()); +} + +void SettingsDialog::changeAudioDevice() +{ + if (!m_deviceDialog) + { + m_deviceDialog = new QDialog( this ); + } + QLabel *msgLabel = new QLabel; + AudioWidget *aw = AudioWidget::getInstance(); + QString *msg = new QString; + for (uint iAvailableDevice = 0; iAvailableDevice < aw->playbackDeviceCount; iAvailableDevice += 1) { + msg->append(tr("%1 : %2\n").arg(iAvailableDevice).arg(aw->pPlaybackDeviceInfos[iAvailableDevice].name)); + } + msgLabel->setText(msg->toLatin1()); + QVBoxLayout *layout = new QVBoxLayout; + layout->addWidget(msgLabel); + QSpinBox *res = new QSpinBox; + res->setRange(0, aw->playbackDeviceCount); + res->setValue(AudioWidget::getInstance()->iChosenDevice); + layout->addWidget(res); + QPushButton *closeButton = new QPushButton(tr("Close")); + connect(closeButton, SIGNAL(clicked()), this, SLOT(closeAudioDeviceDialog())); + closeButton->setDefault(true); + layout->addWidget(closeButton); + m_deviceDialog->setLayout(layout); + m_deviceDialog->setModal(true); + m_deviceDialog->show(); +} + +void SettingsDialog::closeAudioDeviceDialog() +{ + QLayoutItem * const item = m_deviceDialog->layout()->itemAt(1); + int value = dynamic_cast(item->widget())->value(); + Settings::getInstance()->setAudioDeviceId(value); + qInfo("device selected: %i", value); + m_deviceDialog->close(); + emit Settings::getInstance()->audioDeviceChanged(value); } void SettingsDialog::layersChanged(int val) diff --git a/src/settingsdialog.h b/src/settingsdialog.h index b4c7acc..665c465 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -21,11 +21,16 @@ public: private slots: void changeMediaPath(); + void changeAudioDevice(); + void closeAudioDeviceDialog(); + private: Ui::SettingsDialog *ui; + QDialog *m_deviceDialog; private slots: void layersChanged(int val); + }; #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 6b5aaa2..325e5b7 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -7,7 +7,7 @@ 0 0 400 - 429 + 563 @@ -16,9 +16,9 @@ - 290 - 390 - 101 + 10 + 530 + 381 32 @@ -26,14 +26,14 @@ Qt::Horizontal - QDialogButtonBox::Close + QDialogButtonBox::Cancel|QDialogButtonBox::Ok 10 - 40 + 50 52 31 @@ -63,8 +63,8 @@ - 60 - 40 + 70 + 50 111 21 @@ -73,25 +73,12 @@ Layers Number - + 10 10 - 371 - 21 - - - - TextLabel - - - - - - 200 - 40 - 191 + 171 31 @@ -108,14 +95,33 @@ - 9 - 99 - 381 - 281 + 0 + 80 + 401 + 451 + + + + 210 + 10 + 181 + 31 + + + + Change Audio Device + + + false + + + false + +