1 /*****************************************************************
2 * gmerlin - a general purpose multimedia framework and applications
3 *
4 * Copyright (c) 2001 - 2011 Members of the Gmerlin project
5 * gmerlin-general@lists.sourceforge.net
6 * http://gmerlin.sourceforge.net
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 <http://www.gnu.org/licenses/>.
20 * *****************************************************************/
21
22 #include <stdio.h>
23 #include <string.h>
24
25 #include <sys/ioctl.h>
26 #include <sys/soundcard.h>
27 #include <unistd.h>
28 #include <stdlib.h>
29
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <fcntl.h>
33
34 #include <config.h>
35 #include <gmerlin/translation.h>
36
37 #include <gmerlin/plugin.h>
38 #include <gmerlin/utils.h>
39
40 #include <gmerlin/log.h>
41 #define LOG_DOMAIN "oa_alsa"
42
43 #include "alsa_common.h"
44
45 /* Playback modes */
46
47 #define PLAYBACK_NONE 0
48 #define PLAYBACK_GENERIC 1
49 #define PLAYBACK_USER 2
50 #define PLAYBACK_SURROUND40 3
51 #define PLAYBACK_SURROUND41 4
52 #define PLAYBACK_SURROUND50 5
53 #define PLAYBACK_SURROUND51 6
54
55
56 static const bg_parameter_info_t global_parameters[] =
57 {
58 {
59 .name = "surround40",
60 .long_name = TRS("Enable 4.0 Surround"),
61 .type = BG_PARAMETER_CHECKBUTTON,
62 .val_default = { .val_i = 1 },
63 .help_string = TRS("Use the surround 4.0 (aka quadrophonic) device")
64 },
65 {
66 .name = "surround41",
67 .long_name = TRS("Enable 4.1 Surround"),
68 .type = BG_PARAMETER_CHECKBUTTON,
69 .val_default = { .val_i = 1 },
70 .help_string = TRS("Use the surround 4.1 device")
71 },
72 {
73 .name = "surround50",
74 .long_name = TRS("Enable 5.0 Surround"),
75 .type = BG_PARAMETER_CHECKBUTTON,
76 .val_default = { .val_i = 1 },
77 .help_string = TRS("Use the surround 5.0 device")
78 },
79 {
80 .name = "surround51",
81 .long_name = TRS("Enable 5.1 Surround"),
82 .type = BG_PARAMETER_CHECKBUTTON,
83 .val_default = { .val_i = 1 },
84 .help_string = TRS("Use the surround 5.1 device")
85 },
86 {
87 .name = "user_device",
88 .long_name = TRS("User device"),
89 .type = BG_PARAMETER_STRING,
90 .help_string = TRS("Enter a custom device to use for playback. Leave empty to use the\
91 settings above"),
92 },
93 {
94 .name = "buffer_time",
95 .long_name = TRS("Buffer time"),
96 .type = BG_PARAMETER_INT,
97 .val_min = { .val_i = 10 },
98 .val_max = { .val_i = 10000 },
99 .val_default = { .val_i = 1000 },
100 .help_string = TRS("Set the buffer time (in milliseconds). Larger values \
101 improve playback performance on slow systems under load. Smaller values \
102 decrease the latency of the volume control."),
103 },
104 };
105
106 static const int num_global_parameters =
107 sizeof(global_parameters)/sizeof(global_parameters[0]);
108
109 typedef struct
110 {
111 bg_parameter_info_t * parameters;
112 gavl_audio_format_t format;
113 snd_pcm_t * pcm;
114
115 /* Configuration stuff */
116
117 int enable_surround40;
118 int enable_surround41;
119 int enable_surround50;
120 int enable_surround51;
121
122 // int card_index;
123 char * card;
124
125 char * user_device;
126 int convert_4_3;
127 uint8_t * convert_buffer;
128 int convert_buffer_alloc;
129
130 gavl_time_t buffer_time;
131 } alsa_t;
132
convert_4_to_3(alsa_t * priv,gavl_audio_frame_t * frame)133 static void convert_4_to_3(alsa_t * priv, gavl_audio_frame_t * frame)
134 {
135 int i, imax;
136 uint8_t * src, * dst;
137
138 imax = frame->valid_samples * priv->format.num_channels;
139
140 if(imax * 3 < priv->convert_buffer_alloc)
141 {
142 priv->convert_buffer_alloc = (imax+1024) * 3;
143 priv->convert_buffer = realloc(priv->convert_buffer, priv->convert_buffer_alloc);
144 }
145
146 dst = priv->convert_buffer;
147 src = frame->samples.u_8;
148
149 for(i = 0; i < imax; i++)
150 {
151 #ifndef WORDS_BIGENDIAN
152 dst[0] = src[1];
153 dst[1] = src[2];
154 dst[2] = src[3];
155 #else
156 dst[0] = src[0];
157 dst[1] = src[1];
158 dst[2] = src[2];
159 #endif
160 }
161 }
162
create_alsa()163 static void * create_alsa()
164 {
165 int i;
166 alsa_t * ret = calloc(1, sizeof(*ret));
167
168 ret->parameters = calloc(num_global_parameters+2, sizeof(*ret->parameters));
169
170 bg_alsa_create_card_parameters(ret->parameters, 0);
171
172 for(i = 0; i < num_global_parameters; i++)
173 {
174 bg_parameter_info_copy(&ret->parameters[i+1], &global_parameters[i]);
175 }
176
177 return ret;
178 }
179
start_alsa(void * data)180 static int start_alsa(void * data)
181 {
182 alsa_t * priv = data;
183
184 if(snd_pcm_prepare(priv->pcm) < 0)
185 return 0;
186 snd_pcm_start(priv->pcm);
187 return 1;
188 }
189
stop_alsa(void * data)190 static void stop_alsa(void * data)
191 {
192 alsa_t * priv = data;
193 snd_pcm_drop(priv->pcm);
194
195 }
196
open_alsa(void * data,gavl_audio_format_t * format)197 static int open_alsa(void * data, gavl_audio_format_t * format)
198 {
199 int playback_mode;
200 int num_front_channels;
201 int num_rear_channels;
202 int num_lfe_channels;
203 const char * card = NULL;
204 alsa_t * priv = data;
205
206 // bg_log(BG_LOG_DEBUG, LOG_DOMAIN, "Opening");
207
208 /* Figure out the right channel setup */
209
210 num_front_channels = gavl_front_channels(format);
211 num_rear_channels = gavl_rear_channels(format);
212 num_lfe_channels = gavl_lfe_channels(format);
213
214 if(priv->user_device)
215 {
216 playback_mode = PLAYBACK_USER;
217 }
218 else
219 {
220 playback_mode = PLAYBACK_NONE;
221
222 if(num_front_channels > 2)
223 {
224 if(num_lfe_channels)
225 {
226 if(priv->enable_surround51)
227 playback_mode = PLAYBACK_SURROUND51;
228 else if(priv->enable_surround50)
229 playback_mode = PLAYBACK_SURROUND50;
230 }
231 else if(priv->enable_surround50)
232 playback_mode = PLAYBACK_SURROUND50;
233 }
234
235 else if((playback_mode == PLAYBACK_NONE) && num_rear_channels)
236 {
237 if(num_lfe_channels)
238 {
239 if(priv->enable_surround41)
240 playback_mode = PLAYBACK_SURROUND41;
241 else if(priv->enable_surround40)
242 playback_mode = PLAYBACK_SURROUND40;
243 }
244 else if(priv->enable_surround40)
245 playback_mode = PLAYBACK_SURROUND40;
246 }
247
248 if(playback_mode == PLAYBACK_NONE)
249 playback_mode = PLAYBACK_GENERIC;
250 }
251
252 switch(playback_mode)
253 {
254 case PLAYBACK_GENERIC:
255 if(format->num_channels > 2)
256 format->num_channels = 2;
257 format->channel_locations[0] = GAVL_CHID_NONE;
258 gavl_set_channel_setup(format);
259
260 card = priv->card;
261
262
263 break;
264 case PLAYBACK_USER:
265 format->channel_locations[0] = GAVL_CHID_NONE;
266 gavl_set_channel_setup(format);
267 card = priv->user_device;
268 break;
269 case PLAYBACK_SURROUND40:
270 format->num_channels = 4;
271
272 format->channel_locations[0] = GAVL_CHID_FRONT_LEFT;
273 format->channel_locations[1] = GAVL_CHID_FRONT_RIGHT;
274 format->channel_locations[2] = GAVL_CHID_REAR_LEFT;
275 format->channel_locations[3] = GAVL_CHID_REAR_RIGHT;
276
277 card = bg_sprintf("surround40");
278
279
280 break;
281 case PLAYBACK_SURROUND41:
282 format->num_channels = 5;
283
284 format->channel_locations[0] = GAVL_CHID_FRONT_LEFT;
285 format->channel_locations[1] = GAVL_CHID_FRONT_RIGHT;
286 format->channel_locations[2] = GAVL_CHID_REAR_LEFT;
287 format->channel_locations[3] = GAVL_CHID_REAR_RIGHT;
288 format->channel_locations[4] = GAVL_CHID_LFE;
289
290 card = bg_sprintf("surround41");
291
292
293 break;
294 case PLAYBACK_SURROUND50:
295 format->num_channels = 5;
296
297 format->channel_locations[0] = GAVL_CHID_FRONT_LEFT;
298 format->channel_locations[1] = GAVL_CHID_FRONT_RIGHT;
299 format->channel_locations[2] = GAVL_CHID_REAR_LEFT;
300 format->channel_locations[3] = GAVL_CHID_REAR_RIGHT;
301 format->channel_locations[4] = GAVL_CHID_FRONT_CENTER;
302
303 card = bg_sprintf("surround50");
304
305 break;
306 case PLAYBACK_SURROUND51:
307 format->num_channels = 6;
308
309 format->channel_locations[0] = GAVL_CHID_FRONT_LEFT;
310 format->channel_locations[1] = GAVL_CHID_FRONT_RIGHT;
311 format->channel_locations[2] = GAVL_CHID_REAR_LEFT;
312 format->channel_locations[3] = GAVL_CHID_REAR_RIGHT;
313 format->channel_locations[4] = GAVL_CHID_FRONT_CENTER;
314 format->channel_locations[5] = GAVL_CHID_LFE;
315
316 card = bg_sprintf("surround51");
317 break;
318 }
319
320 if(!card)
321 card = "default";
322
323 priv->pcm = bg_alsa_open_write(card, format,
324 priv->buffer_time, &priv->convert_4_3);
325
326 if(!priv->pcm)
327 return 0;
328 gavl_audio_format_copy(&priv->format, format);
329 return 1;
330 }
331
close_alsa(void * p)332 static void close_alsa(void * p)
333 {
334 alsa_t * priv = p;
335
336 // bg_log(BG_LOG_DEBUG, LOG_DOMAIN, "Closing");
337
338 if(priv->pcm)
339 {
340 snd_pcm_close(priv->pcm);
341 priv->pcm = NULL;
342 }
343 }
344
write_frame_alsa(void * p,gavl_audio_frame_t * f)345 static void write_frame_alsa(void * p, gavl_audio_frame_t * f)
346 {
347 int result = -EPIPE;
348 alsa_t * priv = p;
349
350 if(priv->convert_4_3)
351 {
352 convert_4_to_3(priv, f);
353 while(result == -EPIPE)
354 {
355 result = snd_pcm_writei(priv->pcm,
356 priv->convert_buffer,
357 f->valid_samples);
358
359 if(result == -EPIPE)
360 {
361 // snd_pcm_drop(priv->pcm);
362 if(snd_pcm_prepare(priv->pcm) < 0)
363 return;
364 }
365 }
366 }
367 else
368 {
369 while(result == -EPIPE)
370 {
371 result = snd_pcm_writei(priv->pcm,
372 f->samples.s_8,
373 f->valid_samples);
374
375 if(result == -EPIPE)
376 {
377 // snd_pcm_drop(priv->pcm);
378 if(snd_pcm_prepare(priv->pcm) < 0)
379 return;
380 }
381 }
382 }
383
384
385 if(result < 0)
386 {
387 bg_log(BG_LOG_ERROR, LOG_DOMAIN, "snd_pcm_write returned %s", snd_strerror(result));
388 }
389 }
390
destroy_alsa(void * p)391 static void destroy_alsa(void * p)
392 {
393 alsa_t * priv = p;
394 close_alsa(priv);
395
396 if(priv->parameters)
397 bg_parameter_info_destroy_array(priv->parameters);
398 if(priv->user_device)
399 free(priv->user_device);
400 if(priv->card)
401 free(priv->card);
402 snd_config_update_free_global();
403 free(priv);
404 }
405
406 static const bg_parameter_info_t *
get_parameters_alsa(void * p)407 get_parameters_alsa(void * p)
408 {
409 alsa_t * priv = p;
410 return priv->parameters;
411 }
412
413
get_delay_alsa(void * p)414 static int get_delay_alsa(void * p)
415 {
416 int result;
417 snd_pcm_sframes_t frames;
418 alsa_t * priv;
419 priv = p;
420 result = snd_pcm_delay(priv->pcm, &frames);
421 if(!result)
422 return frames;
423 return 0;
424 }
425 /* Set parameter */
426
427 static void
set_parameter_alsa(void * p,const char * name,const bg_parameter_value_t * val)428 set_parameter_alsa(void * p, const char * name,
429 const bg_parameter_value_t * val)
430 {
431 alsa_t * priv = p;
432 if(!name)
433 return;
434
435 if(!strcmp(name, "surround40"))
436 {
437 priv->enable_surround40 = val->val_i;
438 }
439 else if(!strcmp(name, "surround41"))
440 {
441 priv->enable_surround41 = val->val_i;
442 }
443 else if(!strcmp(name, "surround50"))
444 {
445 priv->enable_surround50 = val->val_i;
446 }
447 else if(!strcmp(name, "surround51"))
448 {
449 priv->enable_surround51 = val->val_i;
450 }
451 else if(!strcmp(name, "user_device"))
452 {
453 priv->user_device = bg_strdup(priv->user_device, val->val_str);
454 }
455 else if(!strcmp(name, "buffer_time"))
456 {
457 priv->buffer_time = val->val_i;
458 priv->buffer_time *= (GAVL_TIME_SCALE/1000);
459 }
460 else if(!strcmp(name, "card"))
461 {
462 priv->card = bg_strdup(priv->card, val->val_str);
463 }
464 }
465
466 const bg_oa_plugin_t the_plugin =
467 {
468 .common =
469 {
470 BG_LOCALE,
471 .name = "oa_alsa",
472 .long_name = TRS("Alsa"),
473 .description = TRS("Alsa output plugin with support for channel configurations up to 5.1"),
474 .type = BG_PLUGIN_OUTPUT_AUDIO,
475 .flags = BG_PLUGIN_PLAYBACK | BG_PLUGIN_DEVPARAM,
476 .priority = BG_PLUGIN_PRIORITY_MAX,
477 .create = create_alsa,
478 .destroy = destroy_alsa,
479
480 .get_parameters = get_parameters_alsa,
481 .set_parameter = set_parameter_alsa,
482 },
483
484 .open = open_alsa,
485 .start = start_alsa,
486 .write_audio = write_frame_alsa,
487 .stop = stop_alsa,
488 .close = close_alsa,
489 .get_delay = get_delay_alsa,
490 };
491
492 /* Include this into all plugin modules exactly once
493 to let the plugin loader obtain the API version */
494 BG_GET_PLUGIN_API_VERSION;
495