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