[Qt] PulseAudio (Linux) sound driver
This commit is contained in:
parent
1b50f1aa44
commit
efed708824
|
@ -6,6 +6,7 @@ TARGET = fbaqt
|
|||
|
||||
linux:QT += x11extras
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# DRIVERS
|
||||
#===============================================================================
|
||||
|
@ -20,7 +21,7 @@ DRV_SMS = true
|
|||
DRV_MEGADRIVE = true
|
||||
DRV_NEOGEO = true
|
||||
DRV_PCE = true
|
||||
DRV_PGM = true
|
||||
DRV_PGM = false
|
||||
DRV_PRE90S = true
|
||||
DRV_PST90S = true
|
||||
DRV_SEGA = true
|
||||
|
@ -86,6 +87,9 @@ DEFINES += BUILD_QT \
|
|||
WITH_QTCREATOR \
|
||||
INCLUDE_LIB_PNGH
|
||||
|
||||
linux: DEFINES += BUILD_QT_LINUX
|
||||
macx: DEFINES += BUILD_QT_MACX
|
||||
|
||||
# no warnings...
|
||||
QMAKE_CXXFLAGS += -w
|
||||
QMAKE_CFLAGS += -w
|
||||
|
@ -352,6 +356,7 @@ PRE_TARGETDEPS += \
|
|||
$$M68K_LIB.target \
|
||||
|
||||
LIBS += -lSDL
|
||||
linux:LIBS += -lpulse-simple
|
||||
|
||||
QMAKE_CLEAN += $$GEN/*
|
||||
#===============================================================================
|
||||
|
@ -754,7 +759,9 @@ SOURCES += \
|
|||
../../src/dep/libs/zlib/zutil.c \
|
||||
../../src/burn/devices/nmk004.cpp \
|
||||
../../src/cpu/tlcs90/tlcs90.cpp \
|
||||
../../src/cpu/tlcs90_intf.cpp
|
||||
../../src/cpu/tlcs90_intf.cpp \
|
||||
../../src/burn/devices/kaneko_tmap.cpp \
|
||||
../../src/intf/audio/linux/aud_pulse_simple.cpp
|
||||
|
||||
HEADERS += \
|
||||
../../src/burn/devices/8255ppi.h \
|
||||
|
@ -923,7 +930,9 @@ HEADERS += \
|
|||
../../src/dep/libs/zlib/zconf.h.in \
|
||||
../../src/dep/libs/zlib/zlib.h \
|
||||
../../src/dep/libs/zlib/zutil.h \
|
||||
../../src/burn/devices/nmk004.h
|
||||
../../src/burn/devices/nmk004.h \
|
||||
../../src/burn/devices/kaneko_tmap.h \
|
||||
../../src/intf/audio/linux/ringbuffer.h
|
||||
|
||||
OTHER_FILES +=
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
show();
|
||||
|
||||
ruby::video.driver("X-Video");
|
||||
ruby::video.driver("OpenGL");
|
||||
ruby::video.set(ruby::Video::Handle, m_viewport->id());
|
||||
ruby::video.set(ruby::Video::Depth, 24u);
|
||||
|
||||
|
|
|
@ -24,6 +24,9 @@ static UINT32 nAudActive = 0;
|
|||
#elif defined (_XBOX)
|
||||
extern struct AudOut AudOutXAudio2;
|
||||
#elif defined (BUILD_QT)
|
||||
#ifdef BUILD_QT_LINUX
|
||||
extern struct AudOut AudOutPulseSimple;
|
||||
#endif
|
||||
extern struct AudOut AudOutQtSound;
|
||||
#endif
|
||||
|
||||
|
@ -37,6 +40,9 @@ static struct AudOut *pAudOut[]=
|
|||
#elif defined (_XBOX)
|
||||
&AudOutXAudio2,
|
||||
#elif defined (BUILD_QT)
|
||||
#ifdef BUILD_QT_LINUX
|
||||
&AudOutPulseSimple,
|
||||
#endif
|
||||
&AudOutQtSound,
|
||||
#endif
|
||||
};
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
// C++11 simple pulse audio driver
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <pulse/simple.h>
|
||||
#include <pulse/error.h>
|
||||
#include "burner.h"
|
||||
#include "ringbuffer.h"
|
||||
|
||||
static ring_buffer<short> *buffer = nullptr;
|
||||
static pa_simple *pa_stream = nullptr;
|
||||
static std::thread *streamer_thread = nullptr;
|
||||
static volatile bool streamer_stop = false;
|
||||
static volatile bool streamer_is_running = false;
|
||||
|
||||
// Samples per segment
|
||||
static int samples_per_segment = 0;
|
||||
static unsigned int pas_sound_fps;
|
||||
static int (*pas_get_next_sound)(int);
|
||||
|
||||
static int pas_default_sound_filler(int)
|
||||
{
|
||||
if (nAudNextSound == nullptr)
|
||||
return 1;
|
||||
memset(nAudNextSound, 0, nAudSegLen * 4);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pas_blank_sound()
|
||||
{
|
||||
if (nAudNextSound != nullptr)
|
||||
AudWriteSilence();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pas_sound_check()
|
||||
{
|
||||
// 5 segments ahead...
|
||||
while (buffer->size() > (samples_per_segment * 5)) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(5));
|
||||
return 0;
|
||||
}
|
||||
pas_get_next_sound(1);
|
||||
buffer->write(nAudNextSound, samples_per_segment);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pas_exit()
|
||||
{
|
||||
nAudNextSound = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pas_set_callback(int (*callback)(int))
|
||||
{
|
||||
if (callback == NULL) {
|
||||
pas_get_next_sound = pas_default_sound_filler;
|
||||
} else {
|
||||
pas_get_next_sound = callback;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pas_audio_streamer(void)
|
||||
{
|
||||
short *buf = new short[samples_per_segment];
|
||||
streamer_is_running = true;
|
||||
|
||||
while (!streamer_stop) {
|
||||
// playing...
|
||||
if (bAudPlaying) {
|
||||
|
||||
if (buffer->size() >= samples_per_segment) {
|
||||
buffer->read(buf, samples_per_segment);
|
||||
} else {
|
||||
memset(buf, 0, samples_per_segment * 2);
|
||||
}
|
||||
|
||||
pa_simple_write(pa_stream, buf, samples_per_segment * 2, NULL);
|
||||
} else {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(10));
|
||||
}
|
||||
}
|
||||
delete [] buf;
|
||||
streamer_is_running = false;
|
||||
streamer_stop = false;
|
||||
}
|
||||
|
||||
static int pas_init()
|
||||
{
|
||||
pas_sound_fps = nAppVirtualFps;
|
||||
nAudSegLen = (nAudSampleRate[0] * 100 + (pas_sound_fps / 2)) / pas_sound_fps;
|
||||
|
||||
// samples per segment
|
||||
samples_per_segment = nAudSegLen * 2;
|
||||
|
||||
// seglen * 2 channels * 2 bytes per sample (16bits)
|
||||
nAudAllocSegLen = samples_per_segment * 2;
|
||||
|
||||
nAudNextSound = new short[samples_per_segment];
|
||||
|
||||
pas_set_callback(nullptr);
|
||||
pas_default_sound_filler(0);
|
||||
|
||||
pBurnSoundOut = nAudNextSound;
|
||||
nBurnSoundRate = nAudSampleRate[0];
|
||||
nBurnSoundLen = nAudAllocSegLen;
|
||||
|
||||
pa_sample_spec specs;
|
||||
specs.channels = 2;
|
||||
specs.format = PA_SAMPLE_S16LE;
|
||||
specs.rate = nAudSampleRate[0];
|
||||
|
||||
pa_buffer_attr attributes;
|
||||
attributes.maxlength = nAudAllocSegLen * nAudSegCount;
|
||||
attributes.minreq = nAudAllocSegLen;
|
||||
attributes.prebuf = nAudAllocSegLen;
|
||||
attributes.tlength = nAudAllocSegLen * 2;
|
||||
|
||||
if (streamer_thread) {
|
||||
streamer_stop = true;
|
||||
while (streamer_is_running) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
streamer_stop = false;
|
||||
delete streamer_thread;
|
||||
}
|
||||
|
||||
// destroy previous pulse audio stream
|
||||
if (pa_stream) {
|
||||
pa_simple_flush(pa_stream, NULL);
|
||||
pa_simple_free(pa_stream);
|
||||
}
|
||||
|
||||
// destroy previous ring buffer
|
||||
if (buffer) {
|
||||
delete buffer;
|
||||
}
|
||||
|
||||
// 6 segmentos no buffer
|
||||
buffer = new ring_buffer<short>(samples_per_segment * nAudSegCount);
|
||||
pa_stream = pa_simple_new(NULL,
|
||||
"fbalpha",
|
||||
PA_STREAM_PLAYBACK,
|
||||
NULL,
|
||||
"fbalpha",
|
||||
&specs,
|
||||
NULL,
|
||||
&attributes,
|
||||
NULL);
|
||||
streamer_thread = new std::thread(pas_audio_streamer);
|
||||
streamer_thread->detach();
|
||||
bAudOkay = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pas_play()
|
||||
{
|
||||
bAudPlaying = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pas_stop()
|
||||
{
|
||||
bAudPlaying = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pas_set_volume()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int pas_get_settings(InterfaceInfo *)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct AudOut AudOutPulseSimple = {
|
||||
pas_blank_sound,
|
||||
pas_sound_check,
|
||||
pas_init,
|
||||
pas_set_callback,
|
||||
pas_play,
|
||||
pas_stop,
|
||||
pas_exit,
|
||||
pas_set_volume,
|
||||
pas_get_settings,
|
||||
_T("PulseAudio (Simple) audio output")
|
||||
};
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef RINGBUFFER_H
|
||||
#define RINGBUFFER_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
template<class T>
|
||||
class ring_buffer {
|
||||
T *buffer;
|
||||
uint64_t head;
|
||||
uint64_t tail;
|
||||
size_t buffer_size;
|
||||
|
||||
public:
|
||||
ring_buffer(size_t buffer_size_) : buffer_size(buffer_size_) {
|
||||
buffer = new T[buffer_size];
|
||||
head = 0;
|
||||
tail = 0;
|
||||
}
|
||||
~ring_buffer() {
|
||||
delete [] buffer;
|
||||
}
|
||||
|
||||
bool available() {
|
||||
return (tail > head);
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return tail - head;
|
||||
}
|
||||
|
||||
void write(const T *buf, size_t lenght) {
|
||||
uint64_t tail_ = tail;
|
||||
for (auto i = 0; i < lenght; i++) {
|
||||
buffer[tail_ % buffer_size] = buf[i];
|
||||
++tail_;
|
||||
}
|
||||
tail = tail_;
|
||||
}
|
||||
|
||||
size_t read(T *buf, size_t lenght) {
|
||||
uint64_t head_ = head;
|
||||
if (lenght > size())
|
||||
lenght = size();
|
||||
|
||||
for (auto i = 0; i < lenght; i++) {
|
||||
buf[i] = buffer[head_ % buffer_size];
|
||||
++head_;
|
||||
}
|
||||
head = head_;
|
||||
return lenght;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // RINGBUFFER_H
|
Loading…
Reference in New Issue