2009-12-03 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / audio-alsa.cpp
blobc843e252a2f6724384030bcb940dfee1464a473d
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * audio-alsa.cpp:
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2008 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
13 #include <config.h>
15 #include <dlfcn.h>
17 #include "audio-alsa.h"
18 #include "runtime.h"
19 #include "clock.h"
20 #include "debug.h"
22 typedef int (dyn_snd_pcm_open) (snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode);
23 typedef int (dyn_snd_pcm_close) (snd_pcm_t *pcm);
24 typedef int (dyn_snd_pcm_get_params) (snd_pcm_t *pcm, snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t *period_size);
25 typedef int (dyn_snd_pcm_poll_descriptors_count) (snd_pcm_t *pcm);
26 typedef int (dyn_snd_pcm_poll_descriptors) (snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space);
27 typedef int (dyn_snd_output_stdio_attach) (snd_output_t **outputp, FILE *fp, int _close);
28 typedef int (dyn_snd_pcm_hw_params_malloc) (snd_pcm_hw_params_t **ptr);
29 typedef int (dyn_snd_pcm_hw_params_any) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
30 typedef int (dyn_snd_pcm_hw_params_dump) (snd_pcm_hw_params_t *params, snd_output_t *out);
31 typedef int (dyn_snd_pcm_hw_params_set_rate_resample) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
32 typedef int (dyn_snd_pcm_hw_params_test_access) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access);
33 typedef int (dyn_snd_pcm_hw_params_set_access) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access);
34 typedef int (dyn_snd_pcm_hw_params_set_format) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val);
35 typedef int (dyn_snd_pcm_hw_params_set_channels) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
36 typedef int (dyn_snd_pcm_hw_params_set_rate_near) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
37 typedef int (dyn_snd_pcm_hw_params_set_buffer_time_near) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
38 typedef int (dyn_snd_pcm_hw_params) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
39 typedef int (dyn_snd_pcm_hw_params_can_pause) (const snd_pcm_hw_params_t *params);
40 typedef void (dyn_snd_pcm_hw_params_free) (snd_pcm_hw_params_t *obj);
41 typedef snd_pcm_state_t (dyn_snd_pcm_state) (snd_pcm_t *pcm);
42 typedef const char * (dyn_snd_pcm_state_name) (const snd_pcm_state_t state);
43 typedef int (dyn_snd_pcm_drop) (snd_pcm_t *pcm);
44 typedef snd_pcm_sframes_t (dyn_snd_pcm_writei) (snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
45 typedef int (dyn_snd_pcm_mmap_begin) (snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames);
46 typedef snd_pcm_sframes_t (dyn_snd_pcm_mmap_commit) (snd_pcm_t *pcm, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames);
47 typedef int (dyn_snd_pcm_prepare) (snd_pcm_t *pcm);
48 typedef int (dyn_snd_pcm_resume) (snd_pcm_t *pcm);
49 typedef snd_pcm_sframes_t (dyn_snd_pcm_avail_update) (snd_pcm_t *pcm);
50 typedef int (dyn_snd_pcm_start) (snd_pcm_t *pcm);
51 typedef int (dyn_snd_pcm_delay) (snd_pcm_t *pcm, snd_pcm_sframes_t *delayp);
52 typedef const char * (dyn_snd_strerror) (int errnum);
53 typedef const char * (dyn_snd_asoundlib_version) (void);
55 dyn_snd_pcm_open * d_snd_pcm_open = NULL;
56 dyn_snd_pcm_close * d_snd_pcm_close = NULL;
57 dyn_snd_pcm_get_params * d_snd_pcm_get_params = NULL;
58 dyn_snd_pcm_poll_descriptors_count * d_snd_pcm_poll_descriptors_count = NULL;
59 dyn_snd_pcm_poll_descriptors * d_snd_pcm_poll_descriptors = NULL;
60 dyn_snd_output_stdio_attach * d_snd_output_stdio_attach = NULL;
61 dyn_snd_strerror * d_snd_strerror = NULL;
62 dyn_snd_pcm_hw_params_malloc * d_snd_pcm_hw_params_malloc = NULL;
63 dyn_snd_pcm_hw_params_any * d_snd_pcm_hw_params_any = NULL;
64 dyn_snd_pcm_hw_params_dump * d_snd_pcm_hw_params_dump = NULL;
65 dyn_snd_pcm_hw_params_set_rate_resample * d_snd_pcm_hw_params_set_rate_resample = NULL;
66 dyn_snd_pcm_hw_params_test_access * d_snd_pcm_hw_params_test_access = NULL;
67 dyn_snd_pcm_hw_params_set_access * d_snd_pcm_hw_params_set_access = NULL;
68 dyn_snd_pcm_hw_params_set_format * d_snd_pcm_hw_params_set_format = NULL;
69 dyn_snd_pcm_hw_params_set_channels * d_snd_pcm_hw_params_set_channels = NULL;
70 dyn_snd_pcm_hw_params_set_rate_near * d_snd_pcm_hw_params_set_rate_near = NULL;
71 dyn_snd_pcm_hw_params_set_buffer_time_near * d_snd_pcm_hw_params_set_buffer_time_near = NULL;
72 dyn_snd_pcm_hw_params * d_snd_pcm_hw_params = NULL;
73 dyn_snd_pcm_hw_params_can_pause * d_snd_pcm_hw_params_can_pause = NULL;
74 dyn_snd_pcm_hw_params_free * d_snd_pcm_hw_params_free = NULL;
75 dyn_snd_pcm_state * d_snd_pcm_state = NULL;
76 dyn_snd_pcm_state_name * d_snd_pcm_state_name = NULL;
77 dyn_snd_pcm_drop * d_snd_pcm_drop = NULL;
78 dyn_snd_pcm_writei * d_snd_pcm_writei = NULL;
79 dyn_snd_pcm_mmap_begin * d_snd_pcm_mmap_begin = NULL;
80 dyn_snd_pcm_mmap_commit * d_snd_pcm_mmap_commit = NULL;
81 dyn_snd_pcm_prepare * d_snd_pcm_prepare = NULL;
82 dyn_snd_pcm_resume * d_snd_pcm_resume = NULL;
83 dyn_snd_pcm_avail_update * d_snd_pcm_avail_update = NULL;
84 dyn_snd_pcm_start * d_snd_pcm_start = NULL;
85 dyn_snd_pcm_delay * d_snd_pcm_delay = NULL;
86 dyn_snd_asoundlib_version * d_snd_asoundlib_version = NULL;
88 #define snd_pcm_open d_snd_pcm_open
89 #define snd_pcm_close d_snd_pcm_close
90 #define snd_pcm_get_params d_snd_pcm_get_params
91 #define snd_pcm_poll_descriptors_count d_snd_pcm_poll_descriptors_count
92 #define snd_pcm_poll_descriptors d_snd_pcm_poll_descriptors
93 #define snd_output_stdio_attach d_snd_output_stdio_attach
94 #define snd_strerror d_snd_strerror
95 #define snd_pcm_hw_params_malloc d_snd_pcm_hw_params_malloc
96 #define snd_pcm_hw_params_any d_snd_pcm_hw_params_any
97 #define snd_pcm_hw_params_dump d_snd_pcm_hw_params_dump
98 #define snd_pcm_hw_params_set_rate_resample d_snd_pcm_hw_params_set_rate_resample
99 #define snd_pcm_hw_params_test_access d_snd_pcm_hw_params_test_access
100 #define snd_pcm_hw_params_test_access d_snd_pcm_hw_params_test_access
101 #define snd_pcm_hw_params_set_access d_snd_pcm_hw_params_set_access
102 #define snd_pcm_hw_params_set_format d_snd_pcm_hw_params_set_format
103 #define snd_pcm_hw_params_set_channels d_snd_pcm_hw_params_set_channels
104 #define snd_pcm_hw_params_set_rate_near d_snd_pcm_hw_params_set_rate_near
105 #define snd_pcm_hw_params_set_buffer_time_near d_snd_pcm_hw_params_set_buffer_time_near
106 #define snd_pcm_hw_params d_snd_pcm_hw_params
107 #define snd_pcm_hw_params_can_pause d_snd_pcm_hw_params_can_pause
108 #define snd_pcm_hw_params_free d_snd_pcm_hw_params_free
109 #define snd_pcm_state d_snd_pcm_state
110 #define snd_pcm_state_name d_snd_pcm_state_name
111 #define snd_pcm_drop d_snd_pcm_drop
112 #define snd_pcm_writei d_snd_pcm_writei
113 #define snd_pcm_mmap_begin d_snd_pcm_mmap_begin
114 #define snd_pcm_mmap_commit d_snd_pcm_mmap_commit
115 #define snd_pcm_prepare d_snd_pcm_prepare
116 #define snd_pcm_resume d_snd_pcm_resume
117 #define snd_pcm_avail_update d_snd_pcm_avail_update
118 #define snd_pcm_start d_snd_pcm_start
119 #define snd_pcm_delay d_snd_pcm_delay
120 #define snd_asoundlib_version d_snd_asoundlib_version
123 * AlsaSource
126 AlsaSource::AlsaSource (AlsaPlayer *player, MediaPlayer *mplayer, AudioStream *stream)
127 : AudioSource (Type::ALSASOURCE, player, mplayer, stream), mutex (true)
129 LOG_ALSA ("AlsaSource::AlsaSource (%p, %p)\n", player, stream);
131 this->player = player;
133 pcm = NULL;
134 period_size = 0;
135 buffer_size = 0;
137 mmap = false;
138 udfs = NULL;
139 ndfs = 0;
141 started = false;
142 drop_pending = false;
143 initialized = false;
146 AlsaSource::~AlsaSource ()
148 LOG_ALSA ("AlsaSource::~AlsaSource ()\n");
150 CloseAlsa ();
153 bool
154 AlsaSource::InitializeInternal ()
156 LOG_AUDIO ("AlsaSource::InitializeInternal ()\n");
157 // this is a no-op, initialization is done when needed.
158 return true;
161 bool
162 AlsaSource::InitializeAlsa ()
164 bool res = false;
165 int result;
166 AudioStream *stream = NULL;
168 LOG_AUDIO ("AlsaSource::InitializeAlsa (%p) initialized: %i\n", this, initialized);
170 mutex.Lock ();
172 if (initialized) {
173 result = true;
174 goto cleanup;
177 stream = GetStreamReffed ();
179 if (stream == NULL) {
180 // Shouldn't really happen, but handle this case anyway.
181 LOG_AUDIO ("AlsaSource::Initialize (): trying to initialize an audio device, but there's no audio to play.\n");
182 goto cleanup;
185 // Open a pcm device
186 result = snd_pcm_open (&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0 /*SND_PCM_NONBLOCK*/);
187 if (result != 0) {
188 LOG_AUDIO ("AlsaSource::Initialize (): cannot open audio device: %s\n", snd_strerror (result));
189 pcm = NULL;
190 goto cleanup;
193 // Configure the hardware
194 if (!SetupHW ()) {
195 LOG_AUDIO ("AlsaSource::Initialize (): could not configure hardware for audio playback\n");
196 Close ();
197 goto cleanup;
200 result = snd_pcm_get_params (pcm, &buffer_size, &period_size);
201 if (result != 0) {
202 LOG_AUDIO ("AlsaSource::Initialize (): error while getting parameters: %s\n", snd_strerror (result));
203 Close ();
204 goto cleanup;
207 // Get the file descriptors to poll on
208 ndfs = snd_pcm_poll_descriptors_count (pcm);
209 if (ndfs <= 0) {
210 LOG_AUDIO ("AlsaSource::Initialize(): Unable to initialize audio for playback (could not get poll descriptor count).\n");
211 Close ();
212 goto cleanup;
215 udfs = (pollfd *) g_malloc0 (sizeof (pollfd) * ndfs);
216 if (snd_pcm_poll_descriptors (pcm, udfs, ndfs) < 0) {
217 LOG_AUDIO ("AlsaSource::Initialize (): Unable to initialize audio for playback (could not get poll descriptors).\n");
218 Close ();
219 goto cleanup;
222 LOG_AUDIO ("AlsaSource::Initialize (%p): Succeeded. Buffer size: %lu, period size: %lu\n", this, buffer_size, period_size);
224 res = true;
225 initialized = true;
227 cleanup:
229 mutex.Unlock ();
231 if (stream)
232 stream->unref ();
234 return res;
237 bool
238 AlsaSource::SetupHW ()
240 bool result = false;
241 bool rw_available = false;
242 bool mmap_available = false;
243 #if DEBUG
244 bool debug = debug_flags & RUNTIME_DEBUG_AUDIO;
245 #else
246 bool debug = false;
247 #endif
249 snd_pcm_hw_params_t *params = NULL;
250 snd_output_t *output = NULL;
251 guint32 buffer_time = 100000; // request 0.1 seconds of buffer time.
252 int err = 0;
253 int dir = 0;
254 unsigned int rate = GetSampleRate ();
255 unsigned int actual_rate = rate;
256 guint32 channels = GetChannels ();
258 if (debug) {
259 err = snd_output_stdio_attach (&output, stdout, 0);
260 if (err < 0)
261 LOG_AUDIO ("AlsaSource::SetupHW (): Could not create alsa output: %s\n", snd_strerror (err));
264 err = snd_pcm_hw_params_malloc (&params);
265 if (err < 0) {
266 LOG_AUDIO ("AlsaSource::SetupHW (): Audio HW setup failed (malloc): %s\n", snd_strerror (err));
267 return false;
270 // choose all parameters
271 err = snd_pcm_hw_params_any (pcm, params);
272 if (err < 0) {
273 LOG_AUDIO ("AlsaSource::SetupHW (): Audio HW setup failed (no configurations available): %s\n", snd_strerror (err));
274 goto cleanup;
277 if (debug && output != NULL) {
278 LOG_AUDIO ("AlsaSource::SetupHW (): hw configurations:\n");
279 snd_pcm_hw_params_dump (params, output);
282 // enable software resampling
283 err = snd_pcm_hw_params_set_rate_resample (pcm, params, 1);
284 if (err < 0) {
285 LOG_AUDIO ("AlsaSource::SetupHW (): Audio HW setup failed (could not enable resampling): %s\n", snd_strerror (err));
286 goto cleanup;
289 // test for available transfer modes
290 if (!(moonlight_flags & RUNTIME_INIT_AUDIO_ALSA_MMAP)) {
291 err = snd_pcm_hw_params_test_access (pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED);
292 if (err < 0) {
293 LOG_AUDIO ("AlsaSource::SetupHW (): Audio HW setup: RW access mode not supported (%s).\n", snd_strerror (err));
294 } else {
295 rw_available = true;
298 if (!(moonlight_flags & RUNTIME_INIT_AUDIO_ALSA_RW)) {
299 err = snd_pcm_hw_params_test_access (pcm, params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
300 if (err < 0) {
301 LOG_AUDIO ("AlsaSource::SetupHW (): Audio HW setup: MMAP access mode not supported (%s).\n", snd_strerror (err));
302 } else {
303 mmap_available = true;
306 if (mmap_available) {
307 mmap = true;
308 } else if (rw_available) {
309 mmap = false;
310 } else {
311 LOG_AUDIO ("AlsaSource::SetupHW (): Audio HW setup failed, no available access mode\n");
312 goto cleanup;
315 LOG_AUDIO ("AlsaSource::SetupHW (): Audio HW setup: using %s access mode.\n", mmap ? "MMAP" : "RW");
317 // set transfer mode (mmap or rw in our case)
318 err = snd_pcm_hw_params_set_access (pcm, params, mmap ? SND_PCM_ACCESS_MMAP_INTERLEAVED : SND_PCM_ACCESS_RW_INTERLEAVED);
319 if (err < 0) {
320 LOG_AUDIO ("AlsaSource::SetupHW (): Audio HW setup failed (access type not available for playback): %s\n", snd_strerror (err));
321 goto cleanup;
324 // set audio format
325 switch (GetInputBytesPerSample ()) {
326 case 2: // 16 bit audio
327 err = snd_pcm_hw_params_set_format (pcm, params, SND_PCM_FORMAT_S16);
328 SetOutputBytesPerSample (2);
329 break;
330 case 3: // 24 bit audio
331 // write as 32 bit audio, this is a lot easier to write to than 24 bit.
332 err = snd_pcm_hw_params_set_format (pcm, params, SND_PCM_FORMAT_S32);
333 SetOutputBytesPerSample (4);
334 break;
335 default:
336 LOG_AUDIO ("AlsaSource::SetupHW (): Invalid input bytes per sample, expected 2 or 3, got %i\n", GetInputBytesPerSample ());
337 goto cleanup;
340 if (err < 0) {
341 LOG_AUDIO ("AlsaSource::SetupHW (): Audio HW setup failed (sample format not available for playback): %s\n", snd_strerror (err));
342 goto cleanup;
345 // set channel count
346 err = snd_pcm_hw_params_set_channels (pcm, params, channels);
347 if (err < 0) {
348 LOG_AUDIO ("AlsaSource::SetupHW (): Audio HW setup failed (channels count %i not available for playback): %s\n", channels, snd_strerror (err));
349 goto cleanup;
352 // set sample rate
353 err = snd_pcm_hw_params_set_rate_near (pcm, params, &actual_rate, 0);
354 if (err < 0) {
355 LOG_AUDIO ("AlsaSource::SetupHW (): Audio HW setup failed (sample rate %i Hz not available for playback): %s\n", rate, snd_strerror (err));
356 goto cleanup;
357 } else if (actual_rate != rate) {
358 LOG_AUDIO ("AlsaSource::SetupHW (): Audio HW setup failed (sample rate %i Hz not available for playback, only got %i Hz).\n", rate, actual_rate);
359 goto cleanup;
362 // set the buffer time
363 err = snd_pcm_hw_params_set_buffer_time_near (pcm, params, &buffer_time, &dir);
364 if (err < 0) {
365 LOG_AUDIO ("AudioNode::SetupHW (): Audio HW setup failed (unable to set buffer time %i for playback: %s\n", buffer_time, snd_strerror (err));
366 goto cleanup;
369 // write the parameters to device
370 err = snd_pcm_hw_params (pcm, params);
371 if (err < 0) {
372 LOG_AUDIO ("AlsaSource::SetupHW (): Audio HW setup failed (unable to set hw params for playback: %s)\n", snd_strerror (err));
373 if (debug && output != NULL) {
374 LOG_AUDIO ("AlsaSource::SetupHW (): current hw configurations:\n");
375 snd_pcm_hw_params_dump (params, output);
377 goto cleanup;
380 if (debug) {
381 LOG_AUDIO ("AlsaSource::SetupHW (): hardware pause support: %s\n", snd_pcm_hw_params_can_pause (params) == 0 ? "no" : "yes");
382 LOG_AUDIO ("AlsaSource::SetupHW (): succeeded\n");
383 if (output != NULL)
384 snd_pcm_hw_params_dump (params, output);
387 result = true;
389 cleanup:
390 snd_pcm_hw_params_free (params);
392 return result;
395 void
396 AlsaSource::StateChanged (AudioState old_state)
398 if (GetState () == AudioPlaying)
399 InitializeAlsa ();
400 player->UpdatePollList ();
403 void
404 AlsaSource::Played ()
406 InitializeAlsa ();
407 player->UpdatePollList ();
410 void
411 AlsaSource::Paused ()
413 mutex.Lock ();
414 drop_pending = true;
415 mutex.Unlock ();
418 void
419 AlsaSource::Stopped ()
421 CloseAlsa ();
422 mutex.Lock ();
423 drop_pending = true;
424 mutex.Unlock ();
427 void
428 AlsaSource::DropAlsa ()
430 int err;
432 LOG_ALSA ("AlsaSource::DropAlsa ()\n");
434 mutex.Lock ();
435 drop_pending = false;
437 if (pcm != NULL && snd_pcm_state (pcm) == SND_PCM_STATE_RUNNING) {
438 err = snd_pcm_drop (pcm);
439 if (err < 0)
440 LOG_AUDIO ("AlsaSource::DropAlsa (): Could not stop/drain pcm: %s\n", snd_strerror (err));
442 mutex.Unlock ();
445 void
446 AlsaSource::CloseInternal ()
448 CloseAlsa ();
451 void
452 AlsaSource::CloseAlsa ()
454 mutex.Lock ();
455 if (pcm != NULL) {
456 snd_pcm_close (pcm);
457 pcm = NULL;
460 g_free (udfs);
461 udfs = NULL;
463 initialized = false;
464 started = false;
465 mutex.Unlock ();
468 bool
469 AlsaSource::WriteRW ()
471 snd_pcm_sframes_t avail;
472 snd_pcm_sframes_t commitres = 0;
473 guint32 frames;
474 void *buffer;
476 if (GetState () != AudioPlaying) {
477 LOG_ALSA ("AlsaSource::WriteRW (): trying to write when we're not playing (state: %i)\n", GetState ());
478 return false;
481 if (!PreparePcm (&avail))
482 return false;
484 LOG_ALSA ("AlsaSource::WriteRW (): entering play loop, avail: %" G_GINT64_FORMAT ", sample size: %i\n", (gint64) avail, (int) period_size);
486 buffer = g_malloc (avail * GetOutputBytesPerFrame ());
488 frames = Write (buffer, (guint32) avail);
490 mutex.Lock ();
491 if (initialized)
492 commitres = snd_pcm_writei (pcm, buffer, frames);
493 mutex.Unlock ();
495 g_free (buffer);
497 LOG_ALSA ("AlsaSource::WriteRW (): played %i samples, of %i available samples, result: %i.\n", (int) frames, (int) avail, (int) commitres);
499 if (commitres < 0 || (snd_pcm_uframes_t) commitres != frames) {
500 if (commitres == -EAGAIN)
501 LOG_AUDIO ("AlsaSource::WriteRW (): not enough space for all the data\n");
502 if (!XrunRecovery (commitres >= 0 ? -EPIPE : commitres)) {
503 LOG_AUDIO ("AudioPlayer: could not write audio data: %s, commitres: %li, frames: %u\n", snd_strerror (commitres), commitres, frames);
504 return false;
506 started = false;
509 return frames != 0;
512 bool
513 AlsaSource::WriteMmap ()
515 snd_pcm_channel_area_t *areas = NULL;
516 snd_pcm_uframes_t offset = 0;
517 snd_pcm_uframes_t frames;
518 snd_pcm_sframes_t available_samples;
519 snd_pcm_sframes_t commitres = 0;
520 guint32 channels = GetChannels ();
521 int err = 0;
522 AudioData *data [channels + 1];
524 if (GetState () != AudioPlaying) {
525 LOG_ALSA ("AlsaSource::WriteMmap (): trying to write when we're not playing (state: %i)\n", GetState ());
526 return false;
529 if (!PreparePcm (&available_samples))
530 return false;
532 if (GetFlag (AudioEnded)) {
533 Underflowed ();
534 return false;
537 LOG_ALSA_EX ("AlsaSource::WriteMmap (): entering play loop, avail: %" G_GINT64_FORMAT ", sample size: %i\n", (gint64) available_samples, (int) period_size);
539 frames = available_samples;
541 mutex.Lock ();
542 if (!initialized)
543 goto cleanup;
545 err = snd_pcm_mmap_begin (pcm, (const snd_pcm_channel_area_t** ) &areas, &offset, &frames);
546 if (err < 0) {
547 if (!XrunRecovery (err)) {
548 LOG_AUDIO ("AudioPlayer: could not get mmapped memory: %s\n", snd_strerror (err));
549 goto cleanup;
551 started = false;
554 LOG_ALSA_EX ("AlsaSource::WriteMmap (): can write %lu frames, avail: %lu\n", frames, available_samples);
556 for (guint32 channel = 0; channel < channels; channel++) {
557 data [channel] = (AudioData *) g_malloc (sizeof (AudioData));
558 // pointer to the first sample to write to
559 data [channel]->dest = ((gint8 *) areas [channel].addr) + (areas [channel].first / 8) + offset * areas [channel].step / 8;
560 // distance (in bytes) between samples
561 data [channel]->distance = areas [channel].step / 8;
563 data [channels] = NULL;
565 frames = WriteFull (data, frames);
567 for (guint32 channel = 0; channel < channels; channel++) {
568 g_free (data [channel]);
571 commitres = snd_pcm_mmap_commit (pcm, offset, frames);
573 LOG_ALSA_EX ("AlsaSource::WriteMmap (): played %i samples, of %i available samples, result: %i.\n", (int) frames, (int) 0, (int) commitres);
575 if (commitres < 0 || (snd_pcm_uframes_t) commitres != frames) {
576 if (!XrunRecovery (commitres >= 0 ? -EPIPE : commitres)) {
577 LOG_AUDIO ("AudioPlayer: could not commit mmapped memory: %s\n", snd_strerror(err));
578 commitres = 0; // so that we end up returning false
579 goto cleanup;
581 started = false;
584 cleanup:
586 mutex.Unlock ();
588 return commitres > 0;
591 bool
592 AlsaSource::WriteAlsa ()
594 if (mmap)
595 return WriteMmap ();
596 else
597 return WriteRW ();
600 bool
601 AlsaSource::XrunRecovery (int err)
603 switch (err) {
604 case -EPIPE: // under-run
605 Underflowed ();
606 mutex.Lock ();
607 if (initialized) {
608 err = snd_pcm_prepare (pcm);
609 if (err < 0) {
610 LOG_AUDIO ("AlsaPlayer: Can't recover from underrun, prepare failed: %s.\n", snd_strerror (err));
612 } else {
613 LOG_AUDIO ("AlsaPlayer: Can't recover from underrun, pcm has been closed.\n");
615 mutex.Unlock ();
616 break;
617 case -ESTRPIPE:
618 mutex.Lock ();
619 if (initialized) {
620 while ((err = snd_pcm_resume (pcm)) == -EAGAIN) {
621 LOG_AUDIO ("XrunRecovery: waiting for resume\n");
622 sleep (1); // wait until the suspend flag is released
624 if (err < 0) {
625 err = snd_pcm_prepare (pcm);
626 if (err < 0) {
627 LOG_AUDIO ("AlsaPlayer: Can't recover from suspend, prepare failed: %s.\n", snd_strerror (err));
630 } else {
631 LOG_AUDIO ("AlsaPlayer: Can't recover from suspend, pcm has been closed.\n");
633 mutex.Unlock ();
634 break;
635 default:
636 LOG_AUDIO ("AlsaPlayer: Can't recover from underrun: %s\n", snd_strerror (err));
637 break;
640 return err >= 0;
643 bool
644 AlsaSource::PreparePcm (snd_pcm_sframes_t *avail)
646 int err = 0;
647 bool closed = false;
648 snd_pcm_state_t state;
650 mutex.Lock ();
651 if (initialized) {
652 state = snd_pcm_state (pcm);
653 } else {
654 LOG_ALSA ("AlsaSource::PreparePcm (): pcm has been closed.\n");
655 closed = true;
657 mutex.Unlock ();
659 if (closed)
660 return false;
662 switch (state) {
663 case SND_PCM_STATE_XRUN:
664 LOG_ALSA ("AlsaSource::PreparePcm (): SND_PCM_STATE_XRUN.\n");
666 if (!XrunRecovery (-EPIPE))
667 return false;
669 started = false;
670 break;
671 case SND_PCM_STATE_SUSPENDED:
672 if (!XrunRecovery (-ESTRPIPE))
673 return false;
674 break;
675 case SND_PCM_STATE_SETUP:
676 if (!XrunRecovery (-EPIPE))
677 return false;
679 started = false;
680 break;
681 case SND_PCM_STATE_RUNNING:
682 started = true; // We might have gotten started automatically after writing a certain number of samples.
683 case SND_PCM_STATE_PREPARED:
684 break;
685 case SND_PCM_STATE_PAUSED:
686 case SND_PCM_STATE_DRAINING:
687 default:
688 LOG_ALSA ("AlsaSource::PreparePcm (): state: %s (prepare failed)\n", snd_pcm_state_name (state));
689 return false;
692 err = 0;
693 mutex.Lock ();
694 if (initialized) {
695 *avail = snd_pcm_avail_update (pcm);
696 } else {
697 closed = true;
699 mutex.Unlock ();
701 if (closed)
702 return false;
704 if (*avail < 0) {
705 if (!XrunRecovery (*avail))
706 return false;
708 started = false;
709 return false;
712 if ((snd_pcm_uframes_t) *avail < period_size) {
713 if (!started) {
714 LOG_ALSA ("AlsaSource::PreparePcm (): starting pcm (period size: %li, available: %li)\n", period_size, *avail);
716 mutex.Lock ();
717 if (initialized) {
718 err = snd_pcm_start (pcm);
719 } else {
720 closed = true;
722 mutex.Unlock ();
724 if (closed)
725 return false;
727 if (err < 0) {
728 LOG_AUDIO ("AlsaPlayer: Could not start pcm: %s\n", snd_strerror (err));
729 return false;
731 started = true;
732 } else {
733 return false;
735 return false;
738 LOG_ALSA ("AlsaSource::PreparePcm (): Prepared, avail: %li, started: %i\n", *avail, (int) started);
740 return true;
743 guint64
744 AlsaSource::GetDelayInternal ()
746 snd_pcm_sframes_t delay;
747 int err;
748 guint64 result;
749 bool update_error = false;
750 bool not_initialized = false;
752 mutex.Lock ();
753 if (!initialized) {
754 not_initialized = true;
755 } else {
756 err = snd_pcm_avail_update (pcm);
758 if (err < 0) {
759 LOG_AUDIO ("AlsaSource::GetDelayInternal (): Could not update delay (%s)\n", snd_strerror (err));
760 update_error = true;
761 } else {
762 err = snd_pcm_delay (pcm, &delay);
765 mutex.Unlock ();
767 if (not_initialized) {
768 LOG_AUDIO ("AlsaSource::GetDelayInternal (): pcm has been closed.\n");
769 return G_MAXUINT64;
772 if (update_error)
773 return G_MAXUINT64;
775 if (err < 0) {
776 LOG_AUDIO ("AlsaSource::GetDelayInternal (): Could not get delay (%s)\n", snd_strerror (err));
777 result = G_MAXUINT64;
778 } else if (delay < 0) {
779 LOG_AUDIO ("AlsaSource::GetDelayInternal (): Got negative delay (%li)\n", delay);
780 result = G_MAXUINT64;
781 } else {
782 result = (guint64) TIMESPANTICKS_IN_SECOND * (guint64) delay / (guint64) GetSampleRate ();
785 return result;
789 * AlsaPlayer
792 AlsaPlayer::AlsaPlayer ()
794 LOG_ALSA ("AlsaPlayer::AlsaPlayer ()\n");
796 audio_thread = NULL;
797 shutdown = false;
798 update_poll_pending = true;
799 udfs = NULL;
800 ndfs = 0;
801 fds [0] = -1;
802 fds [1] = -1;
805 AlsaPlayer::~AlsaPlayer ()
807 LOG_ALSA ("AlsaPlayer::~AlsaPlayer ()\n");
810 bool
811 AlsaPlayer::Initialize ()
813 bool result;
815 LOG_ALSA ("AlsaPlayer::Initialize ()\n");
817 // Create our spipe
818 if (pipe (fds) != 0) {
819 LOG_AUDIO ("AlsaPlayer::Initialize (): Unable to create pipe (%s).\n", strerror (errno));
820 return false;
823 // Make the writer pipe non-blocking.
824 fcntl (fds [1], F_SETFL, fcntl (fds [1], F_GETFL) | O_NONBLOCK);
826 // Create the audio thread
827 audio_thread = (pthread_t *) g_malloc (sizeof (pthread_t));
828 result = pthread_create (audio_thread, NULL, Loop, this);
829 if (result != 0) {
830 LOG_AUDIO ("AlsaPlayer::Initialize (): could not create audio thread (error code: %i = '%s').\n", result, strerror (result));
831 g_free (audio_thread);
832 audio_thread = NULL;
833 return false;
836 LOG_ALSA ("AlsaPlayer::Initialize (): the audio player has been initialized.\n");
838 return true;
841 static int is_alsa_usable = 0; // 0 = not tested, 1 = tested, usable, 2 = tested, not usable
842 static void *libalsa = NULL;
844 bool
845 AlsaPlayer::IsInstalled ()
847 bool result = false;
848 const char *version;
850 switch (is_alsa_usable) {
851 case 0:
852 libalsa = dlopen ("libasound.so.2", RTLD_LAZY);
853 if (libalsa == NULL) {
854 is_alsa_usable = 2;
855 return false;
857 result = true;
859 result &= NULL != (d_snd_pcm_open = (dyn_snd_pcm_open *) dlsym (libalsa, "snd_pcm_open"));
860 result &= NULL != (d_snd_pcm_close = (dyn_snd_pcm_close *) dlsym (libalsa, "snd_pcm_close"));
861 result &= NULL != (d_snd_pcm_get_params = (dyn_snd_pcm_get_params *) dlsym (libalsa, "snd_pcm_get_params"));
862 result &= NULL != (d_snd_pcm_poll_descriptors_count = (dyn_snd_pcm_poll_descriptors_count *) dlsym (libalsa, "snd_pcm_poll_descriptors_count"));
863 result &= NULL != (d_snd_pcm_poll_descriptors = (dyn_snd_pcm_poll_descriptors *) dlsym (libalsa, "snd_pcm_poll_descriptors"));
864 result &= NULL != (d_snd_output_stdio_attach = (dyn_snd_output_stdio_attach *) dlsym (libalsa, "snd_output_stdio_attach"));
865 result &= NULL != (d_snd_pcm_hw_params_malloc = (dyn_snd_pcm_hw_params_malloc *) dlsym (libalsa, "snd_pcm_hw_params_malloc"));
866 result &= NULL != (d_snd_pcm_hw_params_any = (dyn_snd_pcm_hw_params_any *) dlsym (libalsa, "snd_pcm_hw_params_any"));
867 result &= NULL != (d_snd_pcm_hw_params_dump = (dyn_snd_pcm_hw_params_dump *) dlsym (libalsa, "snd_pcm_hw_params_dump"));
868 result &= NULL != (d_snd_pcm_hw_params_set_rate_resample = (dyn_snd_pcm_hw_params_set_rate_resample *) dlsym (libalsa, "snd_pcm_hw_params_set_rate_resample"));
869 result &= NULL != (d_snd_pcm_hw_params_test_access = (dyn_snd_pcm_hw_params_test_access *) dlsym (libalsa, "snd_pcm_hw_params_test_access"));
870 result &= NULL != (d_snd_pcm_hw_params_set_access = (dyn_snd_pcm_hw_params_set_access *) dlsym (libalsa, "snd_pcm_hw_params_set_access"));
871 result &= NULL != (d_snd_pcm_hw_params_set_format = (dyn_snd_pcm_hw_params_set_format *) dlsym (libalsa, "snd_pcm_hw_params_set_format"));
872 result &= NULL != (d_snd_pcm_hw_params_set_channels = (dyn_snd_pcm_hw_params_set_channels *) dlsym (libalsa, "snd_pcm_hw_params_set_channels"));
873 result &= NULL != (d_snd_pcm_hw_params_set_rate_near = (dyn_snd_pcm_hw_params_set_rate_near *) dlsym (libalsa, "snd_pcm_hw_params_set_rate_near"));
874 result &= NULL != (d_snd_pcm_hw_params_set_buffer_time_near = (dyn_snd_pcm_hw_params_set_buffer_time_near *) dlsym (libalsa, "snd_pcm_hw_params_set_buffer_time_near"));
875 result &= NULL != (d_snd_pcm_hw_params = (dyn_snd_pcm_hw_params *) dlsym (libalsa, "snd_pcm_hw_params"));
876 result &= NULL != (d_snd_pcm_hw_params_can_pause = (dyn_snd_pcm_hw_params_can_pause *) dlsym (libalsa, "snd_pcm_hw_params_can_pause"));
877 result &= NULL != (d_snd_pcm_hw_params_free = (dyn_snd_pcm_hw_params_free *) dlsym (libalsa, "snd_pcm_hw_params_free"));
878 result &= NULL != (d_snd_pcm_state = (dyn_snd_pcm_state *) dlsym (libalsa, "snd_pcm_state"));
879 result &= NULL != (d_snd_pcm_state_name = (dyn_snd_pcm_state_name *) dlsym (libalsa, "snd_pcm_state_name"));
880 result &= NULL != (d_snd_pcm_drop = (dyn_snd_pcm_drop *) dlsym (libalsa, "snd_pcm_drop"));
881 result &= NULL != (d_snd_pcm_writei = (dyn_snd_pcm_writei *) dlsym (libalsa, "snd_pcm_writei"));
882 result &= NULL != (d_snd_pcm_mmap_begin = (dyn_snd_pcm_mmap_begin *) dlsym (libalsa, "snd_pcm_mmap_begin"));
883 result &= NULL != (d_snd_pcm_mmap_commit = (dyn_snd_pcm_mmap_commit *) dlsym (libalsa, "snd_pcm_mmap_commit"));
884 result &= NULL != (d_snd_pcm_prepare = (dyn_snd_pcm_prepare *) dlsym (libalsa, "snd_pcm_prepare"));
885 result &= NULL != (d_snd_pcm_resume = (dyn_snd_pcm_resume *) dlsym (libalsa, "snd_pcm_resume"));
886 result &= NULL != (d_snd_pcm_avail_update = (dyn_snd_pcm_avail_update *) dlsym (libalsa, "snd_pcm_avail_update"));
887 result &= NULL != (d_snd_pcm_start = (dyn_snd_pcm_start *) dlsym (libalsa, "snd_pcm_start"));
888 result &= NULL != (d_snd_pcm_delay = (dyn_snd_pcm_delay *) dlsym (libalsa, "snd_pcm_delay"));
889 result &= NULL != (d_snd_asoundlib_version = (dyn_snd_asoundlib_version *) dlsym (libalsa, "snd_asoundlib_version"));
890 result &= NULL != (d_snd_strerror = (dyn_snd_strerror *) dlsym (libalsa, "snd_strerror"));
892 if (d_snd_asoundlib_version != NULL) {
893 version = d_snd_asoundlib_version ();
894 LOG_AUDIO ("AlsaPlayer: Found alsa/asound version: '%s'\n", version);
897 if (!result)
898 LOG_AUDIO ("AlsaPlayer: Failed to load one or more required functions in libasound.so.");
900 is_alsa_usable = result ? 1 : 2;
901 return result;
902 case 1:
903 return true;
904 default:
905 return false;
908 return true;
911 void
912 AlsaPlayer::AddInternal (AudioSource *source)
914 update_poll_pending = true;
915 WakeUp ();
918 void
919 AlsaPlayer::RemoveInternal (AudioSource *source)
921 update_poll_pending = true;
922 WakeUp ();
925 void
926 AlsaPlayer::PrepareShutdownInternal ()
928 int result = 0;
930 LOG_ALSA ("AlsaPlayer::PrepareShutdownInternal ().\n");
932 // Wait for the audio thread to finish
933 shutdown = true;
934 if (audio_thread != NULL) {
935 WakeUp ();
936 result = pthread_join (*audio_thread, NULL);
937 if (result != 0) {
938 LOG_AUDIO ("AudioPlayer::Shutdown (): failed to join the audio thread (error code: %i).\n", result);
939 } else {
940 // Only free the thread if we could join it.
941 g_free (audio_thread);
943 audio_thread = NULL;
947 void
948 AlsaPlayer::FinishShutdownInternal ()
950 LOG_ALSA ("AlsaPlayer::FinishShutdownInternal ().\n");
952 if (fds [0] != -1) {
953 close (fds [0]);
954 fds [0] = -1;
956 if (fds [1] != -1) {
957 close (fds [1]);
958 fds [1] = -1;
961 g_free (udfs);
962 udfs = NULL;
963 ndfs = 0;
966 void
967 AlsaPlayer::UpdatePollList ()
969 update_poll_pending = true;
970 WakeUp ();
973 AudioSource *
974 AlsaPlayer::CreateNode (MediaPlayer *mplayer, AudioStream *stream)
976 return new AlsaSource (this, mplayer, stream);
979 void*
980 AlsaPlayer::Loop (void *data)
982 ((AlsaPlayer *) data)->Loop ();
983 return NULL;
986 void
987 AlsaPlayer::Loop ()
989 AlsaSource *source = NULL;
991 bool played_something;
992 int result;
993 int buffer;
994 int current;
996 LOG_ALSA ("AlsaPlayer: entering audio loop.\n");
998 while (!shutdown) {
999 sources.StartEnumeration ();
1001 played_something = false;
1002 while ((source = (AlsaSource *) sources.GetNext (false)) != NULL) {
1003 if (source->GetState () == AudioPlaying) {
1004 if (source->WriteAlsa ())
1005 played_something = true;
1006 } else if (source->IsDropPending ()) {
1007 source->DropAlsa ();
1009 source->unref ();
1012 if (played_something)
1013 continue;
1015 // None of the audio nodes in the list played anything
1016 // (or there are no audio nodes), so wait for something
1017 // to happen. We handle spurious wakeups correctly, so
1018 // there is no find out exactly what happened.
1020 while (!shutdown && update_poll_pending) {
1022 * We need to update the list of file descriptors we poll on
1023 * to only include audio nodes which are playing.
1025 update_poll_pending = false;
1027 ndfs = 1;
1028 current = 1;
1029 sources.StartEnumeration ();
1030 while ((source = (AlsaSource *) sources.GetNext (true)) != NULL) {
1031 ndfs += source->ndfs;
1032 source->unref ();
1035 g_free (udfs);
1036 udfs = (pollfd*) g_malloc0 (sizeof (pollfd) * ndfs);
1037 udfs [0].fd = fds [0];
1038 udfs [0].events = POLLIN;
1040 sources.StartEnumeration ();
1041 while (!update_poll_pending && (source = (AlsaSource *) sources.GetNext (true)) != NULL) {
1042 if (current + source->ndfs > ndfs) {
1043 // the list of sources changed.
1044 update_poll_pending = true;
1045 } else {
1046 memcpy (&udfs [current], source->udfs, source->ndfs * sizeof (pollfd));
1047 current += source->ndfs;
1049 source->unref ();
1052 if (current != ndfs) {
1053 // The list of sources changed
1054 update_poll_pending = true;
1058 do {
1059 udfs [0].events = POLLIN;
1060 udfs [0].revents = 0;
1062 LOG_ALSA_EX ("AlsaPlayer::Loop (): polling... ndfs: %i\n", ndfs);
1064 result = poll (udfs, ndfs, 10000); // Have a timeout of 10 seconds, just in case something goes wrong.
1066 LOG_ALSA_EX ("AlsaPlayer::Loop (): poll result: %i, fd: %i, fd [0].revents: %i, errno: %i, err: %s, ndfs = %i, shutdown: %i\n",
1067 result, udfs [0].fd, (int) udfs [0].revents, errno, strerror (errno), ndfs, shutdown);
1069 if (result == 0) { // Timed out
1070 LOG_ALSA_EX ("AlsaPlayer::Loop (): poll timed out.\n");
1071 } else if (result < 0) { // Some error poll exit condition
1072 // Doesn't matter what happened (happens quite often due to interrupts)
1073 LOG_ALSA_EX ("AlsaPlayer::Loop (): poll failed: %i (%s)\n", errno, strerror (errno));
1074 } else { // Something woke up the poll
1075 if (udfs [0].revents & POLLIN) {
1076 // We were asked to wake up by the audio player
1077 // Read whatever was written into the pipe so that the pipe doesn't fill up.
1078 read (udfs [0].fd, &buffer, sizeof (int));
1079 LOG_ALSA_EX ("AlsaPlayer::Loop (): woken up by ourselves.\n");
1080 } else {
1081 // Something happened on any of the audio streams
1084 } while (result == -1 && errno == EINTR);
1087 LOG_ALSA ("AlsaPlayer: exiting audio loop.\n");
1090 void
1091 AlsaPlayer::WakeUp ()
1093 int result;
1095 LOG_ALSA_EX ("AlsaPlayer::WakeUp ().\n");
1097 // Write until something has been written.
1098 do {
1099 result = write (fds [1], "c", 1);
1100 } while (result == 0);
1102 if (result == -1)
1103 LOG_AUDIO ("AlsaPlayer::WakeUp (): Could not wake up audio thread: %s\n", strerror (errno));
1105 LOG_ALSA_EX ("AlsaPlayer::WakeUp (): thread should now wake up (or have woken up already).\n");