2 * Copyright (c) 2017 Ketmar // Invisible Vector
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * modified by Ketmar // Invisible Vector
27 //version = alsa_simple;
28 //version = alsa_debug;
29 //version = alsa_time_debug;
31 __gshared snd_pcm_t
* pcm
;
32 __gshared
uint pcmChans
= 2;
33 __gshared
uint pcmRate
;
34 version(alsa_simple
) {} else {
35 __gshared
bool sndJustStarted
= true;
36 __gshared snd_pcm_uframes_t realBufSize
;
41 public bool soundrvInit (bool stereo
, ref uint sampleRate
) {
43 pcmChans
= (stereo ?
2 : 1);
44 version(alsa_simple
) {
46 if ((err
= snd_pcm_open(&pcm
, "default", SND_PCM_STREAM_PLAYBACK
, 0)) < 0) {
47 conwriteln("ALSA error: ", snd_strerror(err
));
48 pcm
= null; // just in case
51 if ((err
= snd_pcm_set_params(pcm
, SND_PCM_FORMAT_S16_LE
, SND_PCM_ACCESS_RW_INTERLEAVED
, pcmChans
, sampleRate
, 0, /*500000*/1000000/50)) < 0) {
52 conwriteln("ALSA error: ", snd_strerror(err
));
59 sndJustStarted
= true;
62 pcm
= alsaInit(null, sr
, cast(ubyte)pcmChans
);
63 } catch (Exception e
) {
64 conwriteln("ALSA error: ", e
.msg
);
75 public void soundrvDeinit () {
84 public void soundrvFrame (const(short)* smpbuf
, int frmcount
) nothrow @trusted {
85 auto frms
= frmcount
; // now in frames
86 version(alsa_simple
) {
87 version(alsa_time_debug
) auto stc = clockMilli
;
89 snd_pcm_sframes_t frames
= snd_pcm_writei(pcm
, smpbuf
, frms
);
91 frames
= snd_pcm_recover(pcm
, cast(int)frames
, 0);
93 import core
.stdc
.stdio
: printf
;
94 import core
.stdc
.stdlib
: exit
, EXIT_FAILURE
;
95 printf("snd_pcm_writei failed: %s\n", snd_strerror(cast(int)frames
));
101 smpbuf
+= frames
*pcmChans
;
106 if (sndJustStarted
) {
107 sndJustStarted
= false;
108 __gshared
short[1024] sbuf
;
109 if (snd_pcm_prepare(pcm
) != 0) assert(0, "oops"); // alas
110 if (snd_pcm_start(pcm
) != 0) assert(0, "oops"); // alas
112 auto avail
= snd_pcm_avail
/*_update*/(pcm
); // "_update" for mmaped i/o
114 import core
.stdc
.errno
: EPIPE
, EINTR
, ESTRPIPE
;
115 import core
.stdc
.stdio
;
116 if (avail
!= -EPIPE
&& avail
!= -EINTR
&& avail
!= -ESTRPIPE
) {
117 //fprintf(stderr, "ALSA ERROR: %s\n", snd_strerror(cast(int)avail));
120 snd_pcm_recover(pcm
, cast(int)avail
, 1);
121 if (++averrcount
> 16) assert(0);
125 version(alsa_time_debug
) { import core
.stdc
.stdio
; stderr
.fprintf("first fill: %u\n", cast(uint)avail
); }
126 //sbuf.length = avail*pcmChans;
129 auto wrx
= cast(uint)(avail
> sbuf
.length
/2 ? sbuf
.length
/2 : avail
);
130 auto xerr
= snd_pcm_writei(pcm
, sbuf
.ptr
, wrx
);
132 snd_pcm_recover(pcm
, cast(int)xerr
, 1);
134 version(alsa_time_debug
) { import core
.stdc
.stdio
; stderr
.fprintf("first wrote: %u\n", cast(uint)xerr
); }
141 version(alsa_time_debug
) auto stc = clockMilli
;
142 auto fleft
= frmcount
;
146 auto avail
= snd_pcm_avail
/*_update*/(pcm
); // "_update" for mmaped i/o
147 //{ import core.stdc.stdio : stderr, fprintf; stderr.fprintf("AVAIL: %d\n", cast(int)avail); }
149 /*{ import core.stdc.stdio : stderr, fprintf; stderr.fprintf("AVAILBAD: %d\n", cast(int)avail); }*/
150 import core
.stdc
.errno
: EPIPE
, EINTR
, ESTRPIPE
;
151 import core
.stdc
.stdio
;
152 if (avail
!= -EPIPE
&& avail
!= -EINTR
&& avail
!= -ESTRPIPE
) {
153 //fprintf(stderr, "ALSA ERROR: %s\n", snd_strerror(cast(int)avail));
156 snd_pcm_recover(pcm
, cast(int)avail
, 1);
157 if (++averrcount
> 16) assert(0);
162 auto used
= realBufSize
-avail
;
163 if (used
<= fleft || awaitcount
>= 2) {
165 version(alsa_debug
) { import core
.stdc
.stdio
; stderr
.fprintf("avail: %u; used: %u\n", cast(uint)avail
, cast(uint)used
); }
166 auto err
= snd_pcm_writei(pcm
, smpbuf
, fleft
);
168 import core
.stdc
.stdio
: fprintf
, stderr
;
169 import core
.stdc
.errno
: EPIPE
, EINTR
, ESTRPIPE
;
170 if (err
== -EPIPE
/*|| err == -EINTR || err == -ESTRPIPE*/) {
171 } else if (err
== -11) {
172 // we can't write, that's wrong
173 fprintf(stderr
, "ALSA: write failed (%s)\n", snd_strerror(cast(int)err
));
175 fprintf(stderr
, "ALSA: write failed %d (%s)\n", cast(int)err
, snd_strerror(cast(int)err
));
177 snd_pcm_recover(pcm
, cast(int)err
, 1);
178 //fleft = sndSamplesSize/2/*numchans*/;
179 //bpos = sndsilence; // write silence instead
181 //continue waitnwrite;
184 //version(follin_write_debug) { import core.stdc.stdio; printf("Follin: written %u of %u frames\n", cast(uint)err, cast(uint)fleft); }
185 smpbuf
+= cast(uint)(err
*pcmChans
);
190 if (used
> frmcount
) {
191 uint over
= used
-frmcount
;
192 uint mcswait
= 1000_0*over
/(pcmRate
/100)+100; //441; // for 48000 it will just wait a little less
193 version(alsa_debug
) { import core
.stdc
.stdio
; stderr
.fprintf("avail: %u; need: %u; used: %u; over: %u; mcswait=%u; avc=%u\n", cast(uint)avail
, cast(uint)fleft
, cast(uint)used
, over
, mcswait
, cast(uint)awaitcount
); }
194 clockSleepMicro(mcswait
);
196 conwriteln("!!!ALSA: used=", used
, "; frmcount=", frmcount
);
201 version(alsa_time_debug
) {
202 auto ste
= clockMilli
;
203 { import core
.stdc
.stdio
; stderr
.fprintf("frame time: %u msecs for %u frames\n", cast(uint)(ste
-stc), frmcount
); }
208 version(alsa_simple
) {} else
209 snd_pcm_t
* alsaInit (const(char)* alsaDev
, ref uint srate
, ubyte chans
) {
210 snd_pcm_t
* apcm
= null;
211 snd_pcm_hw_params_t
* hw_params
= null;
212 snd_pcm_sw_params_t
* sw_params
= null;
213 snd_pcm_uframes_t rlbufsize
;
216 if (chans
< 1 || chans
> 2) throw new Exception("invalid number of channels");
217 if (alsaDev
is null ||
!alsaDev
[0]) alsaDev
= "default";
219 static void alsaCall (int err
, string msg
) @trusted {
221 import std
.string
: fromStringz
;
222 throw new Exception((msg
~" ("~snd_strerror(err
).fromStringz
~")").idup
);
226 fuck_alsa_messages();
228 alsaCall(snd_pcm_open(&apcm
, alsaDev
, SND_PCM_STREAM_PLAYBACK
, 0), "cannot open audio device");
229 scope(failure
) snd_pcm_close(apcm
);
231 alsaCall(snd_pcm_hw_params_malloc(&hw_params
), "cannot allocate hardware parameter structure");
232 scope(exit
) snd_pcm_hw_params_free(hw_params
);
234 alsaCall(snd_pcm_hw_params_any(apcm
, hw_params
), "cannot initialize hardware parameter structure");
235 alsaCall(snd_pcm_hw_params_set_access(apcm
, hw_params
, SND_PCM_ACCESS_RW_INTERLEAVED
), "cannot set access type");
236 alsaCall(snd_pcm_hw_params_set_format(apcm
, hw_params
, SND_PCM_FORMAT_S16_LE
), "cannot set sample format");
237 if (alsaDev
[0] == 'p' && alsaDev
[1] == 'l' && alsaDev
[2] == 'u' && alsaDev
[3] == 'g' && alsaDev
[4] == ':') {
238 alsaCall(snd_pcm_hw_params_set_rate_resample(apcm
, hw_params
, 1), "cannot turn on resampling");
239 alsaCall(snd_pcm_hw_params_set_rate(apcm
, hw_params
, sr
, 0), "cannot set sample rate");
241 alsaCall(snd_pcm_hw_params_set_rate_resample(apcm
, hw_params
, 0), "cannot turn off resampling");
242 alsaCall(snd_pcm_hw_params_set_rate_near(apcm
, hw_params
, &sr
, null), "cannot set sample rate");
244 alsaCall(snd_pcm_hw_params_set_channels(apcm
, hw_params
, chans
), "cannot set channel count");
246 //{ import core.stdc.stdio : fprintf, stderr; stderr.fprintf("sampling rate: %u (%u)\n", cast(uint)sr, cast(uint)srate); }
248 //alsaCall(snd_pcm_hw_params_set_buffer_size_near(apcm, hw_params, &rlbufsize), "cannot set buffer size");
249 rlbufsize
= sr
/50; // in frames
251 if (rlbufsize
< 1) assert(0, "wtf?!");
252 auto wantbs
= rlbufsize
;
253 alsaCall(snd_pcm_hw_params_set_buffer_size_near(apcm
, hw_params
, &rlbufsize
), "cannot set buffer size");
254 if (rlbufsize
< wantbs
) throw new Exception("ALSA: alas");
255 if (rlbufsize
> wantbs
*3) throw new Exception("ALSA: alas");
256 realBufSize
= rlbufsize
;
257 //alsaCall(snd_pcm_hw_params_set_buffer_size(apcm, hw_params, rlbufsize), "cannot set buffer size");
258 auto latency
= 1000*rlbufsize
/sr
;
261 import core.stdc.stdio : fprintf, stderr;
262 stderr.fprintf("ALSA: sample rate: %u (%u); frames in buffer: %u (%u); latency: %u\n", cast(uint)sr, cast(uint)srate, cast(uint)rlbufsize, cast(uint)wantbs, cast(uint)latency);
265 alsaCall(snd_pcm_hw_params(apcm
, hw_params
), "cannot set parameters");
267 alsaCall(snd_pcm_sw_params_malloc(&sw_params
), "cannot allocate software parameters structure");
268 scope(exit
) snd_pcm_sw_params_free(sw_params
);
270 alsaCall(snd_pcm_sw_params_current(apcm
, sw_params
), "cannot initialize software parameters structure");
271 alsaCall(snd_pcm_sw_params_set_avail_min(apcm
, sw_params
, rlbufsize
), "cannot set minimum available count");
272 alsaCall(snd_pcm_sw_params_set_start_threshold(apcm
, sw_params
, rlbufsize
), "cannot set start mode");
273 alsaCall(snd_pcm_sw_params(apcm
, sw_params
), "cannot set software parameters");
274 alsaCall(snd_pcm_nonblock(apcm
, 0), "cannot set blocking mode");
275 //alsaCall(snd_pcm_nonblock(apcm, 1), "cannot set non-blocking mode");