]> git.sesse.net Git - nageru/blob - alsa_output.cpp
Add a soundcard output via ALSA.
[nageru] / alsa_output.cpp
1 #include "alsa_output.h"
2
3 #include <vector>
4
5 using namespace std;
6
7 namespace {
8
9 void die_on_error(const char *func_name, int err)
10 {
11         if (err < 0) {
12                 fprintf(stderr, "%s: %s\n", func_name, snd_strerror(err));
13                 exit(1);
14         }
15 }
16
17 }  // namespace
18
19 ALSAOutput::ALSAOutput(int sample_rate, int num_channels)
20         : num_channels(num_channels)
21 {
22         die_on_error("snd_pcm_open()", snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0));
23
24         // Set format.
25         snd_pcm_hw_params_t *hw_params;
26         snd_pcm_hw_params_alloca(&hw_params);
27         die_on_error("snd_pcm_hw_params_any()", snd_pcm_hw_params_any(pcm_handle, hw_params));
28         die_on_error("snd_pcm_hw_params_set_access()", snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED));
29         die_on_error("snd_pcm_hw_params_set_format()", snd_pcm_hw_params_set_format(pcm_handle, hw_params, SND_PCM_FORMAT_FLOAT_LE));
30         die_on_error("snd_pcm_hw_params_set_rate()", snd_pcm_hw_params_set_rate(pcm_handle, hw_params, sample_rate, 0));
31         die_on_error("snd_pcm_hw_params_set_channels", snd_pcm_hw_params_set_channels(pcm_handle, hw_params, num_channels));
32
33         // Fragment size of 512 samples. (A frame at 60 fps/48 kHz is 800 samples.)
34         // We ask for four such periods.
35         unsigned int num_periods = 4;
36         int dir = 0;
37         die_on_error("snd_pcm_hw_params_set_periods_near", snd_pcm_hw_params_set_periods_near(pcm_handle, hw_params, &num_periods, &dir));
38         period_size = 512;
39         dir = 0;
40         die_on_error("snd_pcm_hw_params_set_period_size_near", snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_params, &period_size, &dir));
41         die_on_error("snd_pcm_hw_params", snd_pcm_hw_params(pcm_handle, hw_params));
42         //snd_pcm_hw_params_free(hw_params);
43
44         die_on_error("snd_pcm_nonblock", snd_pcm_nonblock(pcm_handle, 1));
45 }
46
47 void ALSAOutput::write(const vector<float> &samples)
48 {
49         buffer.insert(buffer.end(), samples.begin(), samples.end());
50
51 try_again:
52         int periods_to_write = buffer.size() / (period_size * num_channels);
53         if (periods_to_write == 0) {
54                 return;
55         }
56
57         int ret = snd_pcm_writei(pcm_handle, buffer.data(), periods_to_write * period_size);
58         if (ret == -EPIPE) {
59                 fprintf(stderr, "warning: snd_pcm_writei() reported underrun\n");
60                 snd_pcm_recover(pcm_handle, ret, 1);
61                 goto try_again;
62         } else if (ret == -EAGAIN) {
63                 ret = 0;
64         } else if (ret < 0) {
65                 fprintf(stderr, "error: snd_pcm_writei() returned '%s'\n", snd_strerror(ret));
66                 exit(1);
67         } else if (ret > 0) {
68                 buffer.erase(buffer.begin(), buffer.begin() + ret * num_channels);
69         }
70
71         if (buffer.size() >= period_size * num_channels) {  // Still more to write.
72                 if (ret == 0) {
73                         if (buffer.size() >= period_size * num_channels * 8) {
74                                 // OK, almost 100 ms. Giving up.
75                                 fprintf(stderr, "warning: ALSA overrun, dropping some audio\n");
76                                 buffer.clear();
77                         }
78                 } else if (ret > 0) {
79                         // Not a completely failure (effectively a short write),
80                         // possibly due to a signal.
81                         goto try_again;
82                 }
83         }
84 }