alsa: don't leak memory
[libpicofe.git] / linux / sndout_alsa.c
CommitLineData
32d23d03
GI
1/*
2 * (C) notaz, 2013
3 *
4 * This work is licensed under the terms of any of these licenses
5 * (at your option):
6 * - GNU GPL, version 2 or later.
7 * - GNU LGPL, version 2.1 or later.
8 * - MAME license.
9 * See the COPYING file in the top-level directory.
10 */
11
12#include <stdio.h>
13#include <alsa/asoundlib.h>
14#include <unistd.h>
15
16#include "sndout_alsa.h"
17
9028744c 18#define PFX "sndout_alsa: "
19
32d23d03
GI
20static snd_pcm_t *handle;
21static snd_pcm_uframes_t buffer_size, period_size;
32d23d03 22static void *silent_period;
9028744c 23static unsigned int channels;
24static int failure_counter;
32d23d03
GI
25
26int sndout_alsa_init(void)
27{
28 int ret;
29
30 ret = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
31 if (ret != 0)
32 return -1;
33
34 return 0;
35}
36
37int sndout_alsa_start(int rate_, int stereo)
38{
39 snd_pcm_hw_params_t *hwparams = NULL;
40 unsigned int rate = rate_;
41 int samples, shift;
42 int ret;
43
44 samples = rate * 40 / 1000;
45 for (shift = 8; (1 << shift) < samples; shift++)
46 ;
47 period_size = 1 << shift;
48 buffer_size = 8 * period_size;
49
50 snd_pcm_hw_params_alloca(&hwparams);
51
52 ret = snd_pcm_hw_params_any(handle, hwparams);
53 ret |= snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
54 ret |= snd_pcm_hw_params_set_format(handle, hwparams, SND_PCM_FORMAT_S16_LE);
55 ret |= snd_pcm_hw_params_set_channels(handle, hwparams, stereo ? 2 : 1);
56 ret |= snd_pcm_hw_params_set_rate_near(handle, hwparams, &rate, 0);
57 ret |= snd_pcm_hw_params_set_buffer_size_near(handle, hwparams, &buffer_size);
58 ret |= snd_pcm_hw_params_set_period_size_near(handle, hwparams, &period_size, NULL);
59
60 if (ret != 0) {
9028744c 61 fprintf(stderr, PFX "failed to set hwparams\n");
62 goto fail;
32d23d03
GI
63 }
64
65 ret = snd_pcm_hw_params(handle, hwparams);
66 if (ret != 0) {
9028744c 67 fprintf(stderr, PFX "failed to apply hwparams: %d\n", ret);
68 goto fail;
32d23d03
GI
69 }
70
71 snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
72 snd_pcm_hw_params_get_period_size(hwparams, &period_size, NULL);
73 snd_pcm_hw_params_get_channels(hwparams, &channels);
74
9b87077e 75 silent_period = calloc(period_size * channels, 2);
32d23d03 76
9028744c 77 ret = snd_pcm_prepare(handle);
78 if (ret != 0) {
79 fprintf(stderr, PFX "snd_pcm_prepare failed: %d\n", ret);
80 goto fail;
81 }
32d23d03 82
9028744c 83 ret = snd_pcm_start(handle);
32d23d03 84 if (ret != 0) {
9028744c 85 fprintf(stderr, PFX "snd_pcm_start failed: %d\n", ret);
86 goto fail;
32d23d03
GI
87 }
88
9028744c 89 failure_counter = 0;
90
32d23d03 91 return 0;
9028744c 92
93fail:
94 // to flush out redirected logs
95 fflush(stdout);
96 fflush(stderr);
97 return -1;
32d23d03
GI
98}
99
100void sndout_alsa_stop(void)
101{
9028744c 102 int ret = snd_pcm_drop(handle);
103 if (ret != 0)
104 fprintf(stderr, PFX "snd_pcm_drop failed: %d\n", ret);
9b87077e 105
106 free(silent_period);
107 silent_period = NULL;
32d23d03
GI
108}
109
110void sndout_alsa_wait(void)
111{
112 snd_pcm_sframes_t left;
113
114 while (1)
115 {
116 left = snd_pcm_avail(handle);
117 if (left < 0 || left >= buffer_size / 2)
118 break;
119
120 usleep(4000);
121 }
122}
123
124int sndout_alsa_write_nb(const void *samples, int len)
125{
126 snd_pcm_sframes_t left;
127 int ret;
128
129 len /= 2;
130 if (channels == 2)
131 len /= 2;
132
133 left = snd_pcm_avail(handle);
134 if (left >= 0 && left < len)
135 return 0;
136
137 ret = snd_pcm_writei(handle, samples, len);
138 if (ret < 0) {
139 ret = snd_pcm_recover(handle, ret, 1);
9028744c 140 if (ret != 0 && failure_counter++ < 5)
141 fprintf(stderr, PFX "snd_pcm_recover: %d\n", ret);
32d23d03
GI
142
143 if (silent_period)
144 snd_pcm_writei(handle, silent_period, period_size);
145 snd_pcm_writei(handle, samples, len);
146 }
147
148 return len;
149}
150
151void sndout_alsa_exit(void)
152{
153 snd_pcm_close(handle);
154 handle = NULL;
155}