OSDN Git Service

axfer: add missing header file of unit test to distribution
[android-x86/external-alsa-utils.git] / axfer / xfer-libasound-timer-mmap.c
1 // SPDX-License-Identifier: GPL-2.0
2 //
3 // xfer-libasound-irq-mmap.c - Timer-based scheduling model for mmap operation.
4 //
5 // Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
6 //
7 // Licensed under the terms of the GNU General Public License, version 2.
8
9 #include "xfer-libasound.h"
10 #include "misc.h"
11
12 struct map_layout {
13         snd_pcm_status_t *status;
14         bool need_forward_or_rewind;
15         char **vector;
16
17         unsigned int frames_per_second;
18         unsigned int samples_per_frame;
19         unsigned int frames_per_buffer;
20 };
21
22 static int timer_mmap_pre_process(struct libasound_state *state)
23 {
24         struct map_layout *layout = state->private_data;
25         snd_pcm_access_t access;
26         snd_pcm_uframes_t frame_offset;
27         snd_pcm_uframes_t avail = 0;
28         snd_pcm_uframes_t frames_per_buffer;
29         int i;
30         int err;
31
32         // This parameter, 'period event', is a software feature in alsa-lib.
33         // This switch a handler in 'hw' PCM plugin from irq-based one to
34         // timer-based one. This handler has two file descriptors for
35         // ALSA PCM character device and ALSA timer device. The latter is used
36         // to catch suspend/resume events as wakeup event.
37         err = snd_pcm_sw_params_set_period_event(state->handle,
38                                                  state->sw_params, 1);
39         if (err < 0)
40                 return err;
41
42         err = snd_pcm_status_malloc(&layout->status);
43         if (err < 0)
44                 return err;
45
46         err = snd_pcm_hw_params_get_access(state->hw_params, &access);
47         if (err < 0)
48                 return err;
49
50         err = snd_pcm_hw_params_get_channels(state->hw_params,
51                                              &layout->samples_per_frame);
52         if (err < 0)
53                 return err;
54
55         err = snd_pcm_hw_params_get_rate(state->hw_params,
56                                          &layout->frames_per_second, NULL);
57         if (err < 0)
58                 return err;
59
60         err = snd_pcm_hw_params_get_buffer_size(state->hw_params,
61                                                 &frames_per_buffer);
62         if (err < 0)
63                 return err;
64         layout->frames_per_buffer = (unsigned int)frames_per_buffer;
65
66         if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) {
67                 layout->vector = calloc(layout->samples_per_frame,
68                                         sizeof(*layout->vector));
69                 if (layout->vector == NULL)
70                         return err;
71         }
72
73         if (state->verbose) {
74                 const snd_pcm_channel_area_t *areas;
75                 err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
76                                          &avail);
77                 if (err < 0)
78                         return err;
79
80                 logging(state, "attributes for mapped page frame:\n");
81                 for (i = 0; i < layout->samples_per_frame; ++i) {
82                         const snd_pcm_channel_area_t *area = areas + i;
83
84                         logging(state, "  sample number: %d\n", i);
85                         logging(state, "    address: %p\n", area->addr);
86                         logging(state, "    bits for offset: %u\n", area->first);
87                         logging(state, "    bits/frame: %u\n", area->step);
88                 }
89         }
90
91         return 0;
92 }
93
94 static void *get_buffer(struct libasound_state *state,
95                         const snd_pcm_channel_area_t *areas,
96                         snd_pcm_uframes_t frame_offset)
97 {
98         struct map_layout *layout = state->private_data;
99         void *frame_buf;
100
101         if (layout->vector == NULL) {
102                 char *buf;
103                 buf = areas[0].addr + snd_pcm_frames_to_bytes(state->handle,
104                                                               frame_offset);
105                 frame_buf = buf;
106         } else {
107                 int i;
108                 for (i = 0; i < layout->samples_per_frame; ++i) {
109                         layout->vector[i] = areas[i].addr;
110                         layout->vector[i] += snd_pcm_samples_to_bytes(
111                                                 state->handle, frame_offset);
112                 }
113                 frame_buf = layout->vector;
114         }
115
116         return frame_buf;
117 }
118
119 static int timer_mmap_process_frames(struct libasound_state *state,
120                                      unsigned int *frame_count,
121                                      struct mapper_context *mapper,
122                                      struct container_context *cntrs)
123 {
124         struct map_layout *layout = state->private_data;
125         snd_pcm_uframes_t planned_count;
126         snd_pcm_sframes_t avail;
127         snd_pcm_uframes_t avail_count;
128         const snd_pcm_channel_area_t *areas;
129         snd_pcm_uframes_t frame_offset;
130         void *frame_buf;
131         snd_pcm_sframes_t consumed_count;
132         int err;
133
134         // Retrieve avail space on PCM buffer between kernel/user spaces.
135         // On cache incoherent architectures, still care of data
136         // synchronization.
137         avail = snd_pcm_avail_update(state->handle);
138         if (avail < 0)
139                 return (int)avail;
140
141         // Retrieve pointers of the buffer and left space up to the boundary.
142         avail_count = (snd_pcm_uframes_t)avail;
143         err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
144                                  &avail_count);
145         if (err < 0)
146                 return err;
147
148         // MEMO: Use the amount of data frames as you like.
149         planned_count = layout->frames_per_buffer * random() / RAND_MAX;
150         if (frame_offset + planned_count > layout->frames_per_buffer)
151                 planned_count = layout->frames_per_buffer - frame_offset;
152
153         // Trim up to expected frame count.
154         if (*frame_count < planned_count)
155                 planned_count = *frame_count;
156
157         // Yield this CPU till planned amount of frames become available.
158         if (avail_count < planned_count) {
159                 unsigned short revents;
160                 int timeout_msec;
161
162                 // TODO; precise granularity of timeout; e.g. ppoll(2).
163                 // Furthermore, wrap up according to granularity of reported
164                 // value for hw_ptr.
165                 timeout_msec = ((planned_count - avail_count) * 1000 +
166                                 layout->frames_per_second - 1) /
167                                layout->frames_per_second;
168
169                 // TODO: However, experimentally, the above is not enough to
170                 // keep planned amount of frames when waking up. I don't know
171                 // exactly the mechanism yet.
172                 err = xfer_libasound_wait_event(state, timeout_msec,
173                                                 &revents);
174                 if (err < 0)
175                         return err;
176                 if (revents & POLLERR) {
177                         // TODO: error reporting.
178                         return -EIO;
179                 }
180                 if (!(revents & (POLLIN | POLLOUT)))
181                         return -EAGAIN;
182
183                 // MEMO: Need to perform hwsync explicitly because hwptr is not
184                 // synchronized to actual position of data frame transmission
185                 // on hardware because IRQ handlers are not used in this
186                 // scheduling strategy.
187                 avail = snd_pcm_avail(state->handle);
188                 if (avail < 0)
189                         return (int)avail;
190                 if (avail < planned_count) {
191                         logging(state,
192                                 "Wake up but not enough space: %lu %lu %u\n",
193                                 planned_count, avail, timeout_msec);
194                         planned_count = avail;
195                 }
196         }
197
198         // Let's process data frames.
199         *frame_count = planned_count;
200         frame_buf = get_buffer(state, areas, frame_offset);
201         err = mapper_context_process_frames(mapper, frame_buf, frame_count,
202                                             cntrs);
203         if (err < 0)
204                 return err;
205
206         consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset,
207                                              *frame_count);
208         if (consumed_count != *frame_count) {
209                 logging(state,
210                         "A bug of 'hw' PCM plugin or driver for this PCM "
211                         "node.\n");
212         }
213         *frame_count = consumed_count;
214
215         return 0;
216 }
217
218 static int forward_appl_ptr(struct libasound_state *state)
219 {
220         struct map_layout *layout = state->private_data;
221         snd_pcm_uframes_t forwardable_count;
222         snd_pcm_sframes_t forward_count;
223
224         forward_count = snd_pcm_forwardable(state->handle);
225         if (forward_count < 0)
226                 return (int)forward_count;
227         forwardable_count = forward_count;
228
229         // No need to add safe-gurard because hwptr goes ahead.
230         forward_count = snd_pcm_forward(state->handle, forwardable_count);
231         if (forward_count < 0)
232                 return (int)forward_count;
233
234         if (state->verbose) {
235                 logging(state,
236                         "  forwarded: %lu/%u\n",
237                         forward_count, layout->frames_per_buffer);
238         }
239
240         return 0;
241 }
242
243 static int timer_mmap_r_process_frames(struct libasound_state *state,
244                                        unsigned *frame_count,
245                                        struct mapper_context *mapper,
246                                        struct container_context *cntrs)
247 {
248         struct map_layout *layout = state->private_data;
249         snd_pcm_state_t s;
250         int err;
251
252         // SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP suppresses any IRQ to notify
253         // period elapse for data transmission, therefore no need to care of
254         // concurrent access by IRQ context and process context, unlike
255         // IRQ-based operations.
256         // Here, this is just to query current status to hardware, for later
257         // processing.
258         err = snd_pcm_status(state->handle, layout->status);
259         if (err < 0)
260                 goto error;
261         s = snd_pcm_status_get_state(layout->status);
262
263         // TODO: if reporting something, do here with the status data.
264
265         if (s == SND_PCM_STATE_RUNNING) {
266                 // Reduce delay between sampling on hardware and handling by
267                 // this program.
268                 if (layout->need_forward_or_rewind) {
269                         err = forward_appl_ptr(state);
270                         if (err < 0)
271                                 goto error;
272                         layout->need_forward_or_rewind = false;
273                 }
274
275                 err = timer_mmap_process_frames(state, frame_count, mapper,
276                                                 cntrs);
277                 if (err < 0)
278                         goto error;
279         } else {
280                 if (s == SND_PCM_STATE_PREPARED) {
281                         // For capture direction, need to start stream
282                         // explicitly.
283                         err = snd_pcm_start(state->handle);
284                         if (err < 0)
285                                 goto error;
286                         layout->need_forward_or_rewind = true;
287                         // Not yet.
288                         *frame_count = 0;
289                 } else {
290                         err = -EPIPE;
291                         goto error;
292                 }
293         }
294
295         return 0;
296 error:
297         *frame_count = 0;
298         return err;
299 }
300
301 static int rewind_appl_ptr(struct libasound_state *state)
302 {
303         struct map_layout *layout = state->private_data;
304         snd_pcm_uframes_t rewindable_count;
305         snd_pcm_sframes_t rewind_count;
306
307         rewind_count = snd_pcm_rewindable(state->handle);
308         if (rewind_count < 0)
309                 return (int)rewind_count;
310         rewindable_count = rewind_count;
311
312         // If appl_ptr were rewound just to position of hw_ptr, at next time,
313         // hw_ptr could catch up appl_ptr. This is overrun. We need a space
314         // between these two pointers to prevent this XRUN.
315         // This space is largely affected by time to process data frames later.
316         //
317         // TODO: a generous way to estimate a good value.
318         if (rewindable_count < 32)
319                 return 0;
320         rewindable_count -= 32;
321
322         rewind_count = snd_pcm_rewind(state->handle, rewindable_count);
323         if (rewind_count < 0)
324                 return (int)rewind_count;
325
326         if (state->verbose) {
327                 logging(state,
328                         "  rewound: %lu/%u\n",
329                         rewind_count, layout->frames_per_buffer);
330         }
331
332         return 0;
333 }
334
335 static int fill_buffer_with_zero_samples(struct libasound_state *state)
336 {
337         struct map_layout *layout = state->private_data;
338         const snd_pcm_channel_area_t *areas;
339         snd_pcm_uframes_t frame_offset;
340         snd_pcm_uframes_t avail_count;
341         snd_pcm_format_t sample_format;
342         snd_pcm_uframes_t consumed_count;
343         int err;
344
345         err = snd_pcm_hw_params_get_buffer_size(state->hw_params,
346                                                 &avail_count);
347         if (err < 0)
348                 return err;
349
350         err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
351                                  &avail_count);
352         if (err < 0)
353                 return err;
354
355         err = snd_pcm_hw_params_get_format(state->hw_params, &sample_format);
356         if (err < 0)
357                 return err;
358
359         err = snd_pcm_areas_silence(areas, frame_offset,
360                                     layout->samples_per_frame, avail_count,
361                                     sample_format);
362         if (err < 0)
363                 return err;
364
365         consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset,
366                                              avail_count);
367         if (consumed_count != avail_count)
368                 logging(state, "A bug of access plugin for this PCM node.\n");
369
370         return 0;
371 }
372
373 static int timer_mmap_w_process_frames(struct libasound_state *state,
374                                        unsigned *frame_count,
375                                        struct mapper_context *mapper,
376                                        struct container_context *cntrs)
377 {
378         struct map_layout *layout = state->private_data;
379         snd_pcm_state_t s;
380         int err;
381
382         // Read my comment in 'timer_mmap_w_process_frames()'.
383         err = snd_pcm_status(state->handle, layout->status);
384         if (err < 0)
385                 goto error;
386         s = snd_pcm_status_get_state(layout->status);
387
388         // TODO: if reporting something, do here with the status data.
389
390         if (s == SND_PCM_STATE_RUNNING) {
391                 // Reduce delay between queueing by this program and presenting
392                 // on hardware.
393                 if (layout->need_forward_or_rewind) {
394                         err = rewind_appl_ptr(state);
395                         if (err < 0)
396                                 goto error;
397                         layout->need_forward_or_rewind = false;
398                 }
399
400                 err = timer_mmap_process_frames(state, frame_count, mapper,
401                                                 cntrs);
402                 if (err < 0)
403                         goto error;
404         } else {
405                 // Need to start playback stream explicitly
406                 if (s == SND_PCM_STATE_PREPARED) {
407                         err = fill_buffer_with_zero_samples(state);
408                         if (err < 0)
409                                 goto error;
410
411                         err = snd_pcm_start(state->handle);
412                         if (err < 0)
413                                 goto error;
414
415                         layout->need_forward_or_rewind = true;
416                         // Not yet.
417                         *frame_count = 0;
418                 } else {
419                         err = -EPIPE;
420                         goto error;
421                 }
422         }
423
424         return 0;
425 error:
426         *frame_count = 0;
427         return err;
428 }
429
430 static void timer_mmap_post_process(struct libasound_state *state)
431 {
432         struct map_layout *layout = state->private_data;
433
434         if (layout->status)
435                 snd_pcm_status_free(layout->status);
436         layout->status = NULL;
437
438         if (layout->vector)
439                 free(layout->vector);
440         layout->vector = NULL;
441 }
442
443 const struct xfer_libasound_ops xfer_libasound_timer_mmap_w_ops = {
444         .pre_process    = timer_mmap_pre_process,
445         .process_frames = timer_mmap_w_process_frames,
446         .post_process   = timer_mmap_post_process,
447         .private_size   = sizeof(struct map_layout),
448 };
449
450 const struct xfer_libasound_ops xfer_libasound_timer_mmap_r_ops = {
451         .pre_process    = timer_mmap_pre_process,
452         .process_frames = timer_mmap_r_process_frames,
453         .post_process   = timer_mmap_post_process,
454         .private_size   = sizeof(struct map_layout),
455 };