hydrogen 1.2.6
PulseAudioDriver.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2025 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
24
25#if defined(H2CORE_HAVE_PULSEAUDIO) || _DOXYGEN_
26
27#include <fcntl.h>
29
30
31namespace H2Core
32{
33
35 : AudioOutput(),
36 m_callback(processCallback),
37 m_main_loop(nullptr),
38 m_ctx(nullptr),
39 m_stream(nullptr),
40 m_connected(false),
41 m_outL(nullptr),
42 m_outR(nullptr)
43{
44 pthread_mutex_init(&m_mutex, nullptr);
45 pthread_cond_init(&m_cond, nullptr);
46}
47
48
50{
51 pthread_cond_destroy(&m_cond);
52 pthread_mutex_destroy(&m_mutex);
53 delete []m_outL;
54 delete []m_outR;
55}
56
57
58int PulseAudioDriver::init( unsigned nBufferSize )
59{
60 delete []m_outL;
61 delete []m_outR;
62 m_buffer_size = nBufferSize;
64 m_outL = new float[m_buffer_size];
65 m_outR = new float[m_buffer_size];
66 return 0;
67}
68
69
71{
72 if (m_connected) {
73 ERRORLOG( "already connected" );
74 return 1;
75 }
76
77 if (pipe(m_pipe)) {
78 ERRORLOG( "unable to open pipe." );
79 return 1;
80 }
81
82 fcntl(m_pipe[0], F_SETFL, fcntl(m_pipe[0], F_GETFL) | O_NONBLOCK);
83
84 m_ready = 0;
85 if (pthread_create(&m_thread, nullptr, s_thread_body, this))
86 {
87 close(m_pipe[0]);
88 close(m_pipe[1]);
89 ERRORLOG( "unable to start thread." );
90 return 1;
91 }
92
93 pthread_mutex_lock(&m_mutex);
94 while (!m_ready) {
95 pthread_cond_wait(&m_cond, &m_mutex);
96 }
97 pthread_mutex_unlock(&m_mutex);
98
99 if (m_ready < 0)
100 {
101 pthread_join(m_thread, nullptr);
102 close(m_pipe[0]);
103 close(m_pipe[1]);
104 ERRORLOG( QString( "unable to run driver. Main loop returned %1" ).arg( m_ready ) );
105 return 1;
106 }
107
108 m_connected = true;
109 return 0;
110}
111
112
114{
115 if (m_connected)
116 {
117 int junk = 0;
118 while (write(m_pipe[1], &junk, 1) != 1) {
119 ;
120 }
121 pthread_join(m_thread, nullptr);
122 close(m_pipe[0]);
123 close(m_pipe[1]);
124 }
125}
126
127
129{
130 return m_buffer_size;
131}
132
133
135{
136 return m_sample_rate;
137}
138
139
141{
142 return m_outL;
143}
144
145
147{
148 return m_outR;
149}
150
152{
154 int r = self->thread_body();
155 if (r)
156 {
157 pthread_mutex_lock(&self->m_mutex);
158 self->m_ready = -r;
159 pthread_cond_signal(&self->m_cond);
160 pthread_mutex_unlock(&self->m_mutex);
161 }
162
163 return nullptr;
164}
165
166
168{
169 m_main_loop = pa_mainloop_new();
170 pa_mainloop_api* api = pa_mainloop_get_api(m_main_loop);
171 pa_io_event* ioev = api->io_new(api, m_pipe[0], PA_IO_EVENT_INPUT,
172 pipe_callback, this);
173 m_ctx = pa_context_new(api, "Hydrogen");
174 pa_context_set_state_callback(m_ctx, ctx_state_callback, this);
175 pa_context_connect(m_ctx, nullptr, pa_context_flags_t(0), nullptr);
176
177 int retval;
178 pa_mainloop_run(m_main_loop, &retval);
179
180 if (m_stream)
181 {
182 pa_stream_set_state_callback(m_stream, nullptr, nullptr);
183 pa_stream_set_write_callback(m_stream, nullptr, nullptr);
184 pa_stream_unref(m_stream);
185 m_stream = nullptr;
186 }
187
188 api->io_free(ioev);
189 pa_context_unref(m_ctx);
190 pa_mainloop_free(m_main_loop);
191
192 return retval;
193}
194
195
196void PulseAudioDriver::ctx_state_callback(pa_context* ctx, void* udata)
197{
198 PulseAudioDriver* self = (PulseAudioDriver*)udata;
199 pa_context_state s = pa_context_get_state(ctx);
200
201 if (s == PA_CONTEXT_READY) {
202 pa_sample_spec spec;
203 spec.format = PA_SAMPLE_S16LE;
204 spec.rate = self->m_sample_rate;
205 spec.channels = 2;
206 self->m_stream = pa_stream_new(ctx, "Hydrogen", &spec, nullptr);
207 pa_stream_set_state_callback(self->m_stream, stream_state_callback, self);
208 pa_stream_set_write_callback(self->m_stream, stream_write_callback, self);
209 pa_buffer_attr bufattr;
210 bufattr.fragsize = (uint32_t)-1;
211 bufattr.maxlength = self->m_buffer_size * 4;
212 bufattr.minreq = 0;
213 bufattr.prebuf = (uint32_t)-1;
214 bufattr.tlength = self->m_buffer_size * 4;
215 pa_stream_connect_playback(self->m_stream, nullptr, &bufattr, pa_stream_flags_t(0), nullptr, nullptr);
216 }
217 else if (s == PA_CONTEXT_FAILED) {
218 pa_mainloop_quit(self->m_main_loop, 1);
219 }
220}
221
222
223void PulseAudioDriver::stream_state_callback(pa_stream* stream, void* udata)
224{
225 PulseAudioDriver* self = (PulseAudioDriver*)udata;
226 pa_stream_state s = pa_stream_get_state(stream);
227
228 if ( s == PA_STREAM_FAILED ) {
229 pa_mainloop_quit(self->m_main_loop, 1);
230 } else if ( s == PA_STREAM_READY ) {
231 pthread_mutex_lock(&self->m_mutex);
232 self->m_ready = 1;
233 pthread_cond_signal(&self->m_cond);
234 pthread_mutex_unlock(&self->m_mutex);
235 }
236}
237
238#define FLOAT_TO_SHORT(x) short(round((std::min(std::max((x), -1.0f), 1.0f)) * 32767.0f))
239
240void PulseAudioDriver::stream_write_callback(pa_stream* stream, size_t bytes, void* udata)
241{
242 PulseAudioDriver* self = (PulseAudioDriver*)udata;
243
244 void* vdata;
245 pa_stream_begin_write(stream, &vdata, &bytes);
246 if (!vdata) return;
247
248 short* out = (short*)vdata;
249
250 unsigned num_samples = bytes / 4;
251
252 while (num_samples)
253 {
254 int n = std::min(self->m_buffer_size, num_samples);
255 self->m_callback(n, nullptr);
256 for (int i = 0; i < n; ++i)
257 {
258 *out++ = FLOAT_TO_SHORT(self->m_outL[i]);
259 *out++ = FLOAT_TO_SHORT(self->m_outR[i]);
260 }
261
262 num_samples -= n;
263 }
264
265 pa_stream_write(stream, vdata, (bytes / 4) * 4, nullptr, 0, PA_SEEK_RELATIVE);
266}
267
268
269void PulseAudioDriver::pipe_callback(pa_mainloop_api*, pa_io_event*, int fd,
270 pa_io_event_flags_t events, void *udata)
271{
272 if (!(events & PA_IO_EVENT_INPUT)) {
273 return;
274 }
275
276 char buf[16];
277 int bytes = read(fd, buf, 16);
278 if ( bytes > 0 ) {
279 PulseAudioDriver* self = (PulseAudioDriver*)udata;
280 pa_mainloop_quit(self->m_main_loop, 0);
281 }
282}
283
284} //namespace H2Core
285
286#endif //H2CORE_HAVE_PULSEAUDIO
287
#define ERRORLOG(x)
Definition Object.h:242
#define FLOAT_TO_SHORT(x)
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
unsigned m_nSampleRate
Sample rate of the audio.
virtual void disconnect() override
virtual float * getOut_L() override
virtual int init(unsigned nBufferSize) override
static void ctx_state_callback(pa_context *ctx, void *udata)
static void stream_write_callback(pa_stream *stream, size_t bytes, void *udata)
static void pipe_callback(pa_mainloop_api *, pa_io_event *, int fd, pa_io_event_flags_t events, void *udata)
audioProcessCallback m_callback
static void stream_state_callback(pa_stream *stream, void *udata)
static void * s_thread_body(void *)
virtual float * getOut_R() override
virtual unsigned getBufferSize() override
virtual int connect() override
PulseAudioDriver(audioProcessCallback processCallback)
int m_pipe[2]
File descriptors used to write data to (m_pipe[1]) and read data from (m_pipe[0]) the pipe.
virtual unsigned getSampleRate() override
int(* audioProcessCallback)(uint32_t, void *)
Definition AudioOutput.h:32