]> git.sesse.net Git - nageru/blob - alsa_input.cpp
Rewrite the ALSA sequencer input loop.
[nageru] / alsa_input.cpp
1 #include "alsa_input.h"
2
3 #include <alsa/error.h>
4 #include <assert.h>
5 #include <errno.h>
6 #include <stdio.h>
7 #include <unistd.h>
8
9 #include "alsa_pool.h"
10 #include "bmusb/bmusb.h"
11 #include "timebase.h"
12
13 using namespace std;
14 using namespace std::placeholders;
15
16 #define RETURN_ON_ERROR(msg, expr) do {                                                    \
17         int err = (expr);                                                                  \
18         if (err < 0) {                                                                     \
19                 fprintf(stderr, "[%s] " msg ": %s\n", device.c_str(), snd_strerror(err));  \
20                 if (err == -ENODEV) return CaptureEndReason::DEVICE_GONE;                  \
21                 return CaptureEndReason::OTHER_ERROR;                                      \
22         }                                                                                  \
23 } while (false)
24
25 #define RETURN_FALSE_ON_ERROR(msg, expr) do {                                              \
26         int err = (expr);                                                                  \
27         if (err < 0) {                                                                     \
28                 fprintf(stderr, "[%s] " msg ": %s\n", device.c_str(), snd_strerror(err));  \
29                 return false;                                                              \
30         }                                                                                  \
31 } while (false)
32
33 #define WARN_ON_ERROR(msg, expr) do {                                                      \
34         int err = (expr);                                                                  \
35         if (err < 0) {                                                                     \
36                 fprintf(stderr, "[%s] " msg ": %s\n", device.c_str(), snd_strerror(err));  \
37         }                                                                                  \
38 } while (false)
39
40 ALSAInput::ALSAInput(const char *device, unsigned sample_rate, unsigned num_channels, audio_callback_t audio_callback, ALSAPool *parent_pool, unsigned internal_dev_index)
41         : device(device),
42           sample_rate(sample_rate),
43           num_channels(num_channels),
44           audio_callback(audio_callback),
45           parent_pool(parent_pool),
46           internal_dev_index(internal_dev_index)
47 {
48 }
49
50 bool ALSAInput::open_device()
51 {
52         RETURN_FALSE_ON_ERROR("snd_pcm_open()", snd_pcm_open(&pcm_handle, device.c_str(), SND_PCM_STREAM_CAPTURE, 0));
53
54         // Set format.
55         snd_pcm_hw_params_t *hw_params;
56         snd_pcm_hw_params_alloca(&hw_params);
57         if (!set_base_params(device.c_str(), pcm_handle, hw_params, &sample_rate)) {
58                 return false;
59         }
60
61         RETURN_FALSE_ON_ERROR("snd_pcm_hw_params_set_channels()", snd_pcm_hw_params_set_channels(pcm_handle, hw_params, num_channels));
62
63         // Fragment size of 64 samples (about 1 ms at 48 kHz; a frame at 60
64         // fps/48 kHz is 800 samples.) We ask for 64 such periods in our buffer
65         // (~85 ms buffer); more than that, and our jitter is probably so high
66         // that the resampling queue can't keep up anyway.
67         // The entire thing with periods and such is a bit mysterious to me;
68         // seemingly I can get 96 frames at a time with no problems even if
69         // the period size is 64 frames. And if I set num_periods to e.g. 1,
70         // I can't have a big buffer.
71         num_periods = 16;
72         int dir = 0;
73         RETURN_FALSE_ON_ERROR("snd_pcm_hw_params_set_periods_near()", snd_pcm_hw_params_set_periods_near(pcm_handle, hw_params, &num_periods, &dir));
74         period_size = 64;
75         dir = 0;
76         RETURN_FALSE_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));
77         buffer_frames = 64 * 64;
78         RETURN_FALSE_ON_ERROR("snd_pcm_hw_params_set_buffer_size_near()", snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hw_params, &buffer_frames));
79         RETURN_FALSE_ON_ERROR("snd_pcm_hw_params()", snd_pcm_hw_params(pcm_handle, hw_params));
80         //snd_pcm_hw_params_free(hw_params);
81
82         // Figure out which format the card actually chose.
83         RETURN_FALSE_ON_ERROR("snd_pcm_hw_params_current()", snd_pcm_hw_params_current(pcm_handle, hw_params));
84         snd_pcm_format_t chosen_format;
85         RETURN_FALSE_ON_ERROR("snd_pcm_hw_params_get_format()", snd_pcm_hw_params_get_format(hw_params, &chosen_format));
86
87         audio_format.num_channels = num_channels;
88         audio_format.bits_per_sample = 0;
89         switch (chosen_format) {
90         case SND_PCM_FORMAT_S16_LE:
91                 audio_format.bits_per_sample = 16;
92                 break;
93         case SND_PCM_FORMAT_S24_LE:
94                 audio_format.bits_per_sample = 24;
95                 break;
96         case SND_PCM_FORMAT_S32_LE:
97                 audio_format.bits_per_sample = 32;
98                 break;
99         default:
100                 assert(false);
101         }
102         //printf("num_periods=%u period_size=%u buffer_frames=%u sample_rate=%u bits_per_sample=%d\n",
103         //      num_periods, unsigned(period_size), unsigned(buffer_frames), sample_rate, audio_format.bits_per_sample);
104
105         buffer.reset(new uint8_t[buffer_frames * num_channels * audio_format.bits_per_sample / 8]);
106
107         snd_pcm_sw_params_t *sw_params;
108         snd_pcm_sw_params_alloca(&sw_params);
109         RETURN_FALSE_ON_ERROR("snd_pcm_sw_params_current()", snd_pcm_sw_params_current(pcm_handle, sw_params));
110         RETURN_FALSE_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));
111         RETURN_FALSE_ON_ERROR("snd_pcm_sw_params()", snd_pcm_sw_params(pcm_handle, sw_params));
112
113         RETURN_FALSE_ON_ERROR("snd_pcm_nonblock()", snd_pcm_nonblock(pcm_handle, 1));
114         RETURN_FALSE_ON_ERROR("snd_pcm_prepare()", snd_pcm_prepare(pcm_handle));
115         return true;
116 }
117
118 bool ALSAInput::set_base_params(const char *device_name, snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hw_params, unsigned *sample_rate)
119 {
120         int err;
121         err = snd_pcm_hw_params_any(pcm_handle, hw_params);
122         if (err < 0) {
123                 fprintf(stderr, "[%s] snd_pcm_hw_params_any(): %s\n", device_name, snd_strerror(err));
124                 return false;
125         }
126         err = snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
127         if (err < 0) {
128                 fprintf(stderr, "[%s] snd_pcm_hw_params_set_access(): %s\n", device_name, snd_strerror(err));
129                 return false;
130         }
131         snd_pcm_format_mask_t *format_mask;
132         snd_pcm_format_mask_alloca(&format_mask);
133         snd_pcm_format_mask_set(format_mask, SND_PCM_FORMAT_S16_LE);
134         snd_pcm_format_mask_set(format_mask, SND_PCM_FORMAT_S24_LE);
135         snd_pcm_format_mask_set(format_mask, SND_PCM_FORMAT_S32_LE);
136         err = snd_pcm_hw_params_set_format_mask(pcm_handle, hw_params, format_mask);
137         if (err < 0) {
138                 fprintf(stderr, "[%s] snd_pcm_hw_params_set_format_mask(): %s\n", device_name, snd_strerror(err));
139                 return false;
140         }
141         err = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, sample_rate, 0);
142         if (err < 0) {
143                 fprintf(stderr, "[%s] snd_pcm_hw_params_set_rate_near(): %s\n", device_name, snd_strerror(err));
144                 return false;
145         }
146         return true;
147 }
148
149 ALSAInput::~ALSAInput()
150 {
151         if (pcm_handle) {
152                 WARN_ON_ERROR("snd_pcm_close()", snd_pcm_close(pcm_handle));
153         }
154 }
155
156 void ALSAInput::start_capture_thread()
157 {
158         should_quit = false;
159         capture_thread = thread(&ALSAInput::capture_thread_func, this);
160 }
161
162 void ALSAInput::stop_capture_thread()
163 {
164         should_quit = true;
165         capture_thread.join();
166 }
167
168 void ALSAInput::capture_thread_func()
169 {
170         parent_pool->set_card_state(internal_dev_index, ALSAPool::Device::State::STARTING);
171
172         // If the device hasn't been opened already, we need to do so
173         // before we can capture.
174         while (!should_quit && pcm_handle == nullptr) {
175                 if (!open_device()) {
176                         fprintf(stderr, "[%s] Waiting one second and trying again...\n",
177                                 device.c_str());
178                         sleep(1);
179                 }
180         }
181
182         if (should_quit) {
183                 // Don't call free_card(); that would be a deadlock.
184                 WARN_ON_ERROR("snd_pcm_close()", snd_pcm_close(pcm_handle));
185                 pcm_handle = nullptr;
186                 return;
187         }
188
189         // Do the actual capture. (Termination condition within loop.)
190         for ( ;; ) {
191                 switch (do_capture()) {
192                 case CaptureEndReason::REQUESTED_QUIT:
193                         // Don't call free_card(); that would be a deadlock.
194                         WARN_ON_ERROR("snd_pcm_close()", snd_pcm_close(pcm_handle));
195                         pcm_handle = nullptr;
196                         return;
197                 case CaptureEndReason::DEVICE_GONE:
198                         parent_pool->free_card(internal_dev_index);
199                         WARN_ON_ERROR("snd_pcm_close()", snd_pcm_close(pcm_handle));
200                         pcm_handle = nullptr;
201                         return;
202                 case CaptureEndReason::OTHER_ERROR:
203                         parent_pool->set_card_state(internal_dev_index, ALSAPool::Device::State::STARTING);
204                         fprintf(stderr, "[%s] Sleeping one second and restarting capture...\n",
205                                 device.c_str());
206                         sleep(1);
207                         break;
208                 }
209         }
210 }
211
212 ALSAInput::CaptureEndReason ALSAInput::do_capture()
213 {
214         parent_pool->set_card_state(internal_dev_index, ALSAPool::Device::State::STARTING);
215         RETURN_ON_ERROR("snd_pcm_start()", snd_pcm_start(pcm_handle));
216         parent_pool->set_card_state(internal_dev_index, ALSAPool::Device::State::RUNNING);
217
218         uint64_t num_frames_output = 0;
219         while (!should_quit) {
220                 int ret = snd_pcm_wait(pcm_handle, /*timeout=*/100);
221                 if (ret == 0) continue;  // Timeout.
222                 if (ret == -EPIPE) {
223                         fprintf(stderr, "[%s] ALSA overrun\n", device.c_str());
224                         snd_pcm_prepare(pcm_handle);
225                         snd_pcm_start(pcm_handle);
226                         continue;
227                 }
228                 RETURN_ON_ERROR("snd_pcm_wait()", ret);
229
230                 snd_pcm_sframes_t frames = snd_pcm_readi(pcm_handle, buffer.get(), buffer_frames);
231                 if (frames == -EPIPE) {
232                         fprintf(stderr, "[%s] ALSA overrun\n", device.c_str());
233                         snd_pcm_prepare(pcm_handle);
234                         snd_pcm_start(pcm_handle);
235                         continue;
236                 }
237                 if (frames == 0) {
238                         fprintf(stderr, "snd_pcm_readi() returned 0\n");
239                         break;
240                 }
241                 RETURN_ON_ERROR("snd_pcm_readi()", frames);
242
243                 const int64_t prev_pts = frames_to_pts(num_frames_output);
244                 const int64_t pts = frames_to_pts(num_frames_output + frames);
245                 bool success;
246                 do {
247                         if (should_quit) return CaptureEndReason::REQUESTED_QUIT;
248                         success = audio_callback(buffer.get(), frames, audio_format, pts - prev_pts);
249                 } while (!success);
250                 num_frames_output += frames;
251         }
252         return CaptureEndReason::REQUESTED_QUIT;
253 }
254
255 int64_t ALSAInput::frames_to_pts(uint64_t n) const
256 {
257         return (n * TIMEBASE) / sample_rate;
258 }
259