-
Notifications
You must be signed in to change notification settings - Fork 20
/
plugout_alsa.c
163 lines (134 loc) · 4.29 KB
/
plugout_alsa.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*
* gbsplay is a Gameboy sound player
*
* 2006-2020 (C) by Tobias Diedrich <[email protected]>
*
* Licensed under GNU GPL v1 or, at your option, any later version.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <alloca.h>
#include <alsa/asoundlib.h>
#include "common.h"
#include "plugout.h"
/* Handle for the PCM device */
snd_pcm_t *pcm_handle;
bool can_pause;
#if GBS_BYTE_ORDER == GBS_ORDER_LITTLE_ENDIAN
#define SND_PCM_FORMAT_S16_NE SND_PCM_FORMAT_S16_LE
#else
#define SND_PCM_FORMAT_S16_NE SND_PCM_FORMAT_S16_BE
#endif
static long alsa_open(enum plugout_endian *endian, long rate, long *buffer_bytes, const struct plugout_metadata metadata)
{
const char *pcm_name = "default";
int fmt, err;
unsigned exact_rate;
snd_pcm_hw_params_t *hwparams;
snd_pcm_uframes_t buffer_frames = *buffer_bytes / 4;
snd_pcm_uframes_t period_frames;
UNUSED(metadata);
switch (*endian) {
case PLUGOUT_ENDIAN_BIG: fmt = SND_PCM_FORMAT_S16_BE; break;
case PLUGOUT_ENDIAN_LITTLE: fmt = SND_PCM_FORMAT_S16_LE; break;
default: fmt = SND_PCM_FORMAT_S16_NE; break;
}
snd_pcm_hw_params_alloca(&hwparams);
if ((err = snd_pcm_open(&pcm_handle, pcm_name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
fprintf(stderr, _("Could not open ALSA PCM device '%s': %s\n"), pcm_name, snd_strerror(err));
return -1;
}
if ((err = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) {
fprintf(stderr, _("snd_pcm_hw_params_any failed: %s\n"), snd_strerror(err));
return -1;
}
if ((err = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
fprintf(stderr, _("snd_pcm_hw_params_set_access failed: %s\n"), snd_strerror(err));
return -1;
}
if ((err = snd_pcm_hw_params_set_format(pcm_handle, hwparams, fmt)) < 0) {
fprintf(stderr, _("snd_pcm_hw_params_set_format failed: %s\n"), snd_strerror(err));
return -1;
}
exact_rate = rate;
if ((err = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0)) < 0) {
fprintf(stderr, _("snd_pcm_hw_params_set_rate_near failed: %s\n"), snd_strerror(err));
return -1;
}
if (rate != exact_rate) {
fprintf(stderr, _("Requested rate %ldHz, got %dHz.\n"),
rate, exact_rate);
}
if ((err = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2)) < 0) {
fprintf(stderr, _("snd_pcm_hw_params_set_channels failed: %s\n"), snd_strerror(err));
return -1;
}
if ((err = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_frames)) < 0) {
fprintf(stderr, _("snd_pcm_hw_params_set_buffer_size_near failed: %s\n"), snd_strerror(err));
}
period_frames = buffer_frames / 2;
if ((err = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &period_frames, 0)) < 0) {
fprintf(stderr, _("snd_pcm_hw_params_set_period_size_near failed: %s\n"), snd_strerror(err));
}
if ((err = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) {
fprintf(stderr, _("snd_pcm_hw_params failed: %s\n"), snd_strerror(err));
return -1;
}
*buffer_bytes = buffer_frames * 4;
can_pause = snd_pcm_hw_params_can_pause(hwparams);
return 0;
}
static void alsa_pause(int pause)
{
int err;
if (can_pause && (err = snd_pcm_pause(pcm_handle, pause)))
fprintf(stderr, _("snd_pcm_pause failed: %s\n"), snd_strerror(err));
}
static long is_suspended(snd_pcm_sframes_t retval)
{
#ifdef HAVE_ESTRPIPE
return retval == -ESTRPIPE;
#else
return snd_pcm_state(pcm_handle) == SND_PCM_STATE_SUSPENDED;
#endif
}
static long is_underrun(snd_pcm_sframes_t retval)
{
return retval == -EPIPE;
}
static ssize_t alsa_write(const void *buf, size_t count)
{
snd_pcm_sframes_t retval;
do {
retval = snd_pcm_writei(pcm_handle, buf, count / 4);
if (is_suspended(retval)) {
/* resume from suspend */
while (snd_pcm_resume(pcm_handle) == -EAGAIN)
sleep(1);
} else if (is_underrun(retval)) {
snd_pcm_prepare(pcm_handle);
} else break;
} while (1);
if (retval < 0) {
fprintf(stderr, _("snd_pcm_writei failed: %s\n"), snd_strerror(retval));
snd_pcm_prepare(pcm_handle);
}
return retval;
}
static void alsa_close()
{
snd_pcm_drop(pcm_handle);
snd_pcm_close(pcm_handle);
}
const struct output_plugin plugout_alsa = {
.name = "alsa",
.description = "ALSA sound driver",
.open = alsa_open,
.pause = alsa_pause,
.write = alsa_write,
.close = alsa_close,
};