source: roaraudio/plugins/alsa/pcm_roar.c @ 5961:06e7fd9e4c25

Last change on this file since 5961:06e7fd9e4c25 was 5961:06e7fd9e4c25, checked in by phi, 10 years ago

Updates of copyright and license headers

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