source: roaraudio/plugins/alsa/pcm_roar.c @ 5168:38e69b0d26e0

Last change on this file since 5168:38e69b0d26e0 was 5168:38e69b0d26e0, checked in by phi, 13 years ago

added support to set stream role

File size: 14.9 KB
Line 
1//pcm_roar.c:
2
3/*
4 *      Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2010-2011
5 *      Copyright (C) Hans-Kristian 'maister' Arntzen - 2010
6 *
7 *  This file is part of libroar a part of RoarAudio,
8 *  a cross-platform sound system for both, home and professional use.
9 *  See README for details.
10 *
11 *  This file is free software; you can redistribute it and/or modify
12 *  it under the terms of the GNU General Public License version 3
13 *  as published by the Free Software Foundation.
14 *
15 *  libroar is distributed in the hope that it will be useful,
16 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 *  GNU General Public License for more details.
19 *
20 *  You should have received a copy of the GNU General Public License
21 *  along with this software; see the file COPYING.  If not, write to
22 *  the Free Software Foundation, 51 Franklin Street, Fifth Floor,
23 *  Boston, MA 02110-1301, USA.
24 *
25 *  NOTE for everyone want's to change something and send patches:
26 *  read README and HACKING! There a addition information on
27 *  the license of this document you need to read before you send
28 *  any patches.
29 *
30 *  NOTE for uses of non-GPL (LGPL,...) software using libesd, libartsc
31 *  or libpulse*:
32 *  The libs libroaresd, libroararts and libroarpulse link this lib
33 *  and are therefore GPL. Because of this it may be illigal to use
34 *  them with any software that uses libesd, libartsc or libpulse*.
35 */
36
37//#define DEBUG
38#include "roar.h"
39
40// Equvivalent to prepare(). Starts a stream. Might/will be called several times during a program!
41/////////////////////////////////////////////////
42// Status: Should be mostly complete.
43// Condition for invalid fh is not solved here.
44////////////////////////////////////////////////
45static int roar_pcm_start (snd_pcm_ioplug_t * io) {
46 struct roar_alsa_pcm * self = io->private_data;
47 int fh;
48
49 ROAR_DBG("roar_pcm_start(*) = ?");
50
51 // If start is called several times in a row, just ignore it.
52 if (self->stream_opened)
53  return 0;
54
55 if ( roar_vio_simple_new_stream_obj(&(self->stream_vio), &(self->roar.con), &(self->stream),
56    self->info.rate, self->info.channels, self->info.bits, self->info.codec,
57    io->stream == SND_PCM_STREAM_PLAYBACK ? ROAR_DIR_PLAY : ROAR_DIR_MONITOR
58    ) == -1 ) {
59  return -EINVAL;
60 }
61
62 if ( self->stream_role != ROAR_ROLE_UNKNOWN ) {
63  if ( roar_stream_set_role(&(self->roar.con), &(self->stream), self->stream_role) == -1 ) {
64   ROAR_WARN("roar_pcm_start(*): Can not set stream role: %i: %s", self->stream_role, roar_error2str(roar_error));
65  }
66 }
67
68 if ( roar_vio_ctl(&(self->stream_vio),
69    io->stream == SND_PCM_STREAM_PLAYBACK ? ROAR_VIO_CTL_GET_SELECT_WRITE_FH :
70    ROAR_VIO_CTL_GET_SELECT_READ_FH, &fh) != 1 ) {
71  io->poll_fd = fh;
72  io->poll_events = io->stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN;
73 }
74
75 // Oh, no, what should we do here if roar_vio_ctl() fails to grab a valid fh to poll?
76 // In any case, ALSA will error out the next time it tries to poll() if we don't give it a valid fh.
77
78 snd_pcm_ioplug_reinit_status(io);
79
80 // Stream is now active, yay.
81 self->stream_opened = 1;
82
83 self->bufptr = 0;
84 self->thread_active = 1; // We have to activate the thread before starting it, because the thread lives on that thread_active is 1.
85 if ( pthread_create(&(self->thread), NULL, roar_plugin_thread, self) < 0 ) {
86  self->thread_active = 0;
87  return -1;
88 }
89
90 return 0;
91}
92
93void roar_plugin_reset(struct roar_alsa_pcm *self) {
94 if ( !self->stream_opened )
95  return;
96
97 roar_vio_close(&(self->stream_vio));
98 self->stream_opened = 0;
99 self->thread_active = 0;
100 self->bufptr = 0;
101 self->last_ptr = 0;
102 self->total_written = 0;
103 self->has_written = 0;
104}
105
106
107
108// Simply stopping the stream. Will need to be restarted to play more.
109// Will be called several times together with roar_pcm_start()
110///////////////////////////////////////////////////
111// Status: Still needs some error checking for the pthread calls, but
112// should work.
113//////////////////////////////////////////////////
114static int roar_pcm_stop (snd_pcm_ioplug_t *io) {
115 struct roar_alsa_pcm * self = io->private_data;
116
117 // If this is called several times in a row, just ignore.
118
119 ROAR_DBG("roar_pcm_stop(*) = 0");
120
121
122 if ( self->thread_active ) {
123  self->thread_active = 0;
124  pthread_cond_signal(&(self->cond));
125  pthread_join(self->thread, NULL);
126 }
127
128 roar_plugin_reset(self);
129
130 return 0;
131}
132
133///////////////////////////////
134// Status: Should be complete.
135///////////////////////////////
136static int roar_hw_constraint(struct roar_alsa_pcm * self) {
137 snd_pcm_ioplug_t *io = &(self->io);
138 static const snd_pcm_access_t access_list[] = {
139  SND_PCM_ACCESS_RW_INTERLEAVED
140 };
141 static const unsigned int formats[] = {
142  SND_PCM_FORMAT_S8,
143  SND_PCM_FORMAT_U8,
144  SND_PCM_FORMAT_A_LAW,
145  SND_PCM_FORMAT_MU_LAW,
146  SND_PCM_FORMAT_S16_LE,
147  SND_PCM_FORMAT_S16_BE,
148  SND_PCM_FORMAT_U16_LE,
149  SND_PCM_FORMAT_U16_BE,
150  SND_PCM_FORMAT_S32_LE,
151  SND_PCM_FORMAT_S32_BE,
152  SND_PCM_FORMAT_U32_LE,
153  SND_PCM_FORMAT_U32_BE,
154  SND_PCM_FORMAT_S24_3LE,
155  SND_PCM_FORMAT_S24_3BE,
156  SND_PCM_FORMAT_U24_3LE,
157  SND_PCM_FORMAT_U24_3BE,
158 };
159 int ret;
160
161 ROAR_DBG("roar_hw_constraint(*) = ?");
162
163 if ( (ret = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
164     _as(access_list), access_list)) < 0 )
165  return ret;
166
167 if ( (ret = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
168     _as(formats), formats)) < 0 )
169  return ret;
170
171 if ( (ret = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
172     1, ROAR_MAX_CHANNELS)) < 0 )
173  return ret;
174
175 if ( (ret = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE, 8000, 192000)) < 0 )
176  return ret;
177
178 // We shouldn't let ALSA use extremely low or high values, it will kill a kitty most likely. :v
179 if ( (ret = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, 1 << 6, 1 << 18)) < 0 )
180  return ret;
181
182 if ( (ret = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, 1, 1024)) < 0 )
183  return ret;
184
185 if ( (ret = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_BUFFER_BYTES, 1 << 13, 1 << 24)) < 0 )
186  return ret;
187
188 ROAR_DBG("roar_hw_constraint(*) = 0");
189
190 return 0;
191}
192
193// Referring to alsa-lib/src/pcm/pcm_ioplug.c : snd_pcm_ioplug_hw_ptr_update
194///////////////////////////////////////////////////////////
195// Status: Mostly complete, but uses a really nasty hack!
196///////////////////////////////////////////////////////////
197static snd_pcm_sframes_t roar_pcm_pointer(snd_pcm_ioplug_t *io) {
198 struct roar_alsa_pcm * self = io->private_data;
199 int ptr;
200
201 ROAR_DBG("roar_pcm_pointer(*) = ?");
202 // Did ALSA just call snd_pcm_reset() or something like that without calling the plugin?
203 // We should restart our stream as well.
204 if ( io->appl_ptr < self->last_ptr ) {
205  roar_pcm_stop(io);
206  roar_pcm_start(io);
207 }
208
209 // ALSA has a weird way of calculating how much data can be written to the audio buffer.
210 // It uses the formula:
211 // avail = bufsize + ptr - io->appl_ptr; (??!?)
212 // We really want this:
213 // avail = bufsize - ptr;
214 // This is the obvious way, so we have to manipulate ptr like this:
215 // ptr = io->appl_ptr - ptr;
216
217 pthread_mutex_lock(&(self->lock));
218 ptr = snd_pcm_bytes_to_frames(io->pcm, self->bufptr);
219 pthread_mutex_unlock(&(self->lock));
220
221 ptr = io->appl_ptr - ptr;
222 self->last_ptr = io->appl_ptr;
223
224 ROAR_DBG("roar_pcm_pointer(*) appl_ptr: %i, ptr: %i, calculated avail frames: %i", (int)io->appl_ptr, (int)ptr, (int)(io->appl_ptr - ptr));
225 ROAR_DBG("roar_pcm_pointer(*) = %i", ptr);
226 return ptr;
227}
228
229// TODO: FIXME: add support for reading data!
230//////////////////////////////////////////////////
231// Status: For writing, this should be complete.
232//////////////////////////////////////////////////
233static snd_pcm_sframes_t roar_pcm_transfer(snd_pcm_ioplug_t *io,
234  const snd_pcm_channel_area_t *areas,
235  snd_pcm_uframes_t offset,
236  snd_pcm_uframes_t size) {
237 struct roar_alsa_pcm * self = io->private_data;
238 char * buf;
239 size_t len = size * self->info.channels * self->info.bits / 8;
240 ssize_t ret;
241
242 ROAR_DBG("roar_pcm_transfer(*) = ?");
243 ROAR_DBG("roar_pcm_transfer(*): len=%lu", (long unsigned int) len);
244
245 // Weird ALSA stuff.
246 buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8;
247
248 ret = roar_plugin_write(self, buf, len);
249
250 if ( ret == -1 )
251  return -EIO;
252
253 ROAR_DBG("roar_pcm_transfer(*) = %lli", (long long int)size);
254 return size;
255}
256
257///////////////////////////////////////////////////////////////////
258// Status: Still missing proper delay measurements from the roar server.
259// Only uses a blind timer now. In ideal conditions, this will work well.
260///////////////////////////////////////////////////////////////////
261static int roar_pcm_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp) {
262 struct roar_alsa_pcm * self = io->private_data;
263
264 ROAR_DBG("roar_pcm_delay(*) = ?");
265
266 // TODO: We need to set *delayp the latency in frames.
267 pthread_mutex_lock(&(self->lock));
268 roar_plugin_drain(self);
269 *delayp = snd_pcm_bytes_to_frames(io->pcm, self->bytes_in_buffer);
270 pthread_mutex_unlock(&(self->lock));
271
272 return 0;
273}
274
275////////////////////
276// Status: Complete
277////////////////////
278static int roar_pcm_prepare(snd_pcm_ioplug_t *io) {
279 ROAR_DBG("roar_pcm_prepare(*) = ?");
280
281 return roar_pcm_start(io);
282}
283
284///////////////////////////////////////////////////////////////////////////////////////////////////////////////
285// Status: This should be mostly complete.
286// I'm not sure if hw_params can be called several times during one stream without stop() start() in between.
287// This will mean a memory leak, and possibly breakage should the buffer size change itself.
288///////////////////////////////////////////////////////////////////////////////////////////////////////////////
289static int roar_pcm_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params) {
290 struct roar_alsa_pcm * self = io->private_data;
291 snd_pcm_uframes_t buffersize;
292 int err;
293
294 ROAR_DBG("roar_pcm_hw_params(*) = ?");
295
296 self->info.channels = io->channels;
297 self->info.rate     = io->rate;
298
299 switch (io->format) {
300  case SND_PCM_FORMAT_S8:
301   self->info.codec = ROAR_CODEC_PCM_U_LE;
302   self->info.bits  = 8;
303   break;
304  case SND_PCM_FORMAT_U8:
305   self->info.codec = ROAR_CODEC_PCM_U_LE;
306   self->info.bits  = 8;
307   break;
308  case SND_PCM_FORMAT_A_LAW:
309   self->info.codec = ROAR_CODEC_ALAW;
310   self->info.bits  = 8;
311   break;
312  case SND_PCM_FORMAT_MU_LAW:
313   self->info.codec = ROAR_CODEC_MULAW;
314   self->info.bits  = 8;
315   break;
316  case SND_PCM_FORMAT_S16_LE:
317   self->info.codec = ROAR_CODEC_PCM_S_LE;
318   self->info.bits  = 16;
319   break;
320  case SND_PCM_FORMAT_S16_BE:
321   self->info.codec = ROAR_CODEC_PCM_S_BE;
322   self->info.bits  = 16;
323   break;
324  case SND_PCM_FORMAT_U16_LE:
325   self->info.codec = ROAR_CODEC_PCM_U_LE;
326   self->info.bits  = 16;
327   break;
328  case SND_PCM_FORMAT_U16_BE:
329   self->info.codec = ROAR_CODEC_PCM_U_BE;
330   self->info.bits  = 16;
331   break;
332  case SND_PCM_FORMAT_S32_LE:
333   self->info.codec = ROAR_CODEC_PCM_S_LE;
334   self->info.bits  = 32;
335   break;
336  case SND_PCM_FORMAT_S32_BE:
337   self->info.codec = ROAR_CODEC_PCM_S_BE;
338   self->info.bits  = 32;
339   break;
340  case SND_PCM_FORMAT_U32_LE:
341   self->info.codec = ROAR_CODEC_PCM_U_LE;
342   self->info.bits  = 32;
343   break;
344  case SND_PCM_FORMAT_U32_BE:
345   self->info.codec = ROAR_CODEC_PCM_U_BE;
346   self->info.bits  = 32;
347   break;
348  case SND_PCM_FORMAT_S24_3LE:
349   self->info.codec = ROAR_CODEC_PCM_S_LE;
350   self->info.bits  = 24;
351   break;
352  case SND_PCM_FORMAT_S24_3BE:
353   self->info.codec = ROAR_CODEC_PCM_S_BE;
354   self->info.bits  = 24;
355   break;
356  case SND_PCM_FORMAT_U24_3LE:
357   self->info.codec = ROAR_CODEC_PCM_U_LE;
358   self->info.bits  = 24;
359   break;
360  case SND_PCM_FORMAT_U24_3BE:
361   self->info.codec = ROAR_CODEC_PCM_U_BE;
362   self->info.bits  = 24;
363   break;
364  default:
365   return -EINVAL;
366 }
367
368 if ((err = snd_pcm_hw_params_get_buffer_size(params, &buffersize) < 0))
369  return err;
370
371 ROAR_DBG("roar_pcm_hw_params(*) buffersize (bytes): %i", (int)buffersize);
372
373 //self->bufsize = snd_pcm_frames_to_bytes(io->pcm, buffersize);
374 self->bufsize = self->info.bits * self->info.channels * buffersize / 8;
375 self->buffer = malloc(self->bufsize);
376 if (self->buffer == NULL)
377  return -1;
378 self->bufptr = 0;
379
380 ROAR_DBG("roar_pcm_hw_params(*) Setting buffersize (bytes): %i", (int)self->bufsize);
381 ROAR_DBG("roar_pcm_hw_params(*) = 0");
382 return 0;
383}
384
385///////////////////////////////////
386// Status: This should be complete.
387// This is the last cleanup function to be called by ALSA.
388///////////////////////////////////
389static int roar_pcm_close (snd_pcm_ioplug_t * io) {
390 struct roar_alsa_pcm * self = io->private_data;
391
392 ROAR_DBG("roar_pcm_close(*) = ?");
393
394 roar_disconnect(&(self->roar.con));
395
396 pthread_mutex_destroy(&(self->lock));
397 pthread_mutex_destroy(&(self->cond_lock));
398 pthread_cond_destroy(&(self->cond));
399
400 free(self->buffer);
401 free(self);
402
403 return 0;
404}
405
406static snd_pcm_ioplug_callback_t roar_pcm_callback = {
407 .start                  = roar_pcm_start,
408 .stop                   = roar_pcm_stop,
409 .pointer                = roar_pcm_pointer,
410 .transfer               = roar_pcm_transfer,
411 .delay                  = roar_pcm_delay,
412 .prepare                = roar_pcm_prepare,
413 .hw_params              = roar_pcm_hw_params,
414 .close                  = roar_pcm_close,
415};
416
417SND_PCM_PLUGIN_DEFINE_FUNC(roar) {
418 struct roar_alsa_pcm * self;
419 snd_config_iterator_t i, next;
420 snd_config_t * n;
421 const char   * para;
422 const char   * server = NULL;
423 const char   * tmp;
424 int            role = ROAR_ROLE_UNKNOWN;
425 int            ret;
426
427 (void)root;
428
429 ROAR_DBG("SND_PCM_PLUGIN_DEFINE_FUNC(roar) = ?");
430
431 snd_config_for_each(i, next, conf) {
432  n = snd_config_iterator_entry(i);
433  if ( snd_config_get_id(n, &para) < 0 )
434   continue;
435
436  if ( !strcmp(para, "type") || !strcmp(para, "comment") || !strcmp(para, "hint") )
437   continue;
438
439  if ( !strcmp(para, "server") ) {
440   if (snd_config_get_string(n, &server) < 0) {
441    return -EINVAL;
442   }
443  } else if ( !strcmp(para, "role") ) {
444   if (snd_config_get_string(n, &tmp) < 0) {
445    return -EINVAL;
446   }
447   if ( (role = roar_str2role(tmp)) == -1 ) {
448    return -EINVAL;
449   }
450  } else {
451   return -EINVAL;
452  }
453 }
454
455 errno = ENOSYS;
456
457 if ( (self = malloc(sizeof(struct roar_alsa_pcm))) == NULL )
458  return -errno;
459
460 memset(self, 0, sizeof(struct roar_alsa_pcm));
461
462 self->stream_role = role;
463
464 errno = ENOSYS;
465 if ( roar_simple_connect(&(self->roar.con), (char*)server, "ALSA Plugin") == -1 ) {
466  free(self);
467  return -errno;
468 }
469
470 self->io.version      = SND_PCM_IOPLUG_VERSION;
471 self->io.name         = "RoarAudio Plugin";
472 self->io.poll_fd      =  -1;
473 self->io.poll_events  =  POLLOUT;
474 self->io.mmap_rw      =  0;
475 self->io.callback     = &roar_pcm_callback;
476 self->io.private_data =  self;
477
478 if ( (ret = snd_pcm_ioplug_create(&(self->io), name, stream, mode)) < 0 ) {
479  roar_disconnect(&(self->roar.con));
480  free(self);
481  return ret;
482 }
483
484 pthread_mutex_init(&(self->lock), NULL);
485 pthread_mutex_init(&(self->cond_lock), NULL);
486 pthread_cond_init(&(self->cond), NULL);
487
488
489 if ( (ret = roar_hw_constraint(self)) < 0 ) {
490  snd_pcm_ioplug_delete(&(self->io));
491  roar_disconnect(&(self->roar.con));
492  free(self);
493  return ret;
494 }
495
496 *pcmp = self->io.pcm;
497
498 ROAR_DBG("SND_PCM_PLUGIN_DEFINE_FUNC(roar) = 0");
499
500 return 0;
501}
502
503SND_PCM_PLUGIN_SYMBOL(roar);
504
505//ll
Note: See TracBrowser for help on using the repository browser.