A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2011 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "config.h"
22#include "system.h"
23#include "general.h"
24#include "kernel.h"
25#include "pcm.h"
26#include "pcm-internal.h"
27#include "pcm_mixer.h"
28#include "pcm_sampr.h"
29
30/* Channels use standard-style PCM callback interface but a latency of one
31 frame by double-buffering is introduced in order to facilitate mixing and
32 keep the hardware fed. There must be sufficient time to perform operations
33 before the last samples are sent to the codec and so things are done in
34 parallel (as much as possible) with sending-out data. */
35
36static unsigned int mixer_sampr = HW_SAMPR_DEFAULT;
37static unsigned int mix_frame_size = MIX_FRAME_SAMPLES*4;
38
39/* Define this to nonzero to add a marker pulse at each frame start */
40#define FRAME_BOUNDARY_MARKERS 0
41
42/* Descriptor for each channel */
43struct mixer_channel
44{
45 const void *start; /* Buffer pointer */
46 size_t size; /* Bytes remaining */
47 size_t last_size; /* Size of consumed data in prev. cycle */
48 pcm_play_callback_type get_more; /* Registered callback */
49 enum channel_status status; /* Playback status */
50 uint32_t amplitude; /* Amp. factor: 0x0000 = mute, 0x10000 = unity */
51 chan_buffer_hook_fn_type buffer_hook; /* Callback for new buffer */
52};
53
54#if (defined(HW_HAVE_192) || defined(HW_HAVE_176))
55#define FRAME_SIZE_MULT 4
56#elif (defined(HW_HAVE_96) || defined(HW_HAVE_88))
57#define FRAME_SIZE_MULT 2
58#else
59#define FRAME_SIZE_MULT 1
60#endif
61
62#define MAX_MIX_FRAME_SAMPLES (MIX_FRAME_SAMPLES * FRAME_SIZE_MULT)
63
64/* Because of the double-buffering, playback is always from here, otherwise a
65 mechanism for the channel callbacks not to free buffers too early would be
66 needed (if we _really_ want it and it's worth it, we _can_ do that ;-) ) */
67static uint32_t downmix_buf[2][MAX_MIX_FRAME_SAMPLES] DOWNMIX_BUF_IBSS MEM_ALIGN_ATTR;
68static int downmix_index = 0; /* Which downmix_buf? */
69static size_t next_size = 0; /* Size of buffer to play next time */
70
71/* Descriptors for all available channels */
72static struct mixer_channel channels[PCM_MIXER_NUM_CHANNELS] IBSS_ATTR;
73
74/* Packed pointer array of all playing (active) channels in "channels" array */
75static struct mixer_channel * active_channels[PCM_MIXER_NUM_CHANNELS+1] IBSS_ATTR;
76
77/* Number of silence frames to play after all data has played */
78#define MAX_IDLE_FRAMES (mixer_sampr*3 / (mix_frame_size / 4))
79static unsigned int idle_counter = 0;
80
81/** Mixing routines, CPU optmized **/
82#include "asm/pcm-mixer.c"
83
84/** Private generic routines **/
85
86/* Mark channel active to mix its data */
87static void mixer_activate_channel(struct mixer_channel *chan)
88{
89 void **elem = find_array_ptr((void **)active_channels, chan);
90
91 if (!*elem)
92 {
93 idle_counter = 0;
94 *elem = chan;
95 }
96}
97
98/* Stop channel from mixing */
99static void mixer_deactivate_channel(struct mixer_channel *chan)
100{
101 remove_array_ptr((void **)active_channels, chan);
102}
103
104/* Deactivate channel and change it to stopped state */
105static void channel_stopped(struct mixer_channel *chan)
106{
107 mixer_deactivate_channel(chan);
108 chan->size = 0;
109 chan->start = NULL;
110 chan->status = CHANNEL_STOPPED;
111}
112
113/* Main PCM callback - sends the current prepared frame to play */
114static void mixer_pcm_callback(const void **addr, size_t *size)
115{
116 *addr = downmix_buf[downmix_index];
117 *size = next_size;
118}
119
120static inline void chan_call_buffer_hook(struct mixer_channel *chan)
121{
122 if (UNLIKELY(chan->buffer_hook))
123 chan->buffer_hook(chan->start, chan->size);
124}
125
126/* Buffering callback - calls sub-callbacks and mixes the data for next
127 buffer to be sent from mixer_pcm_callback() */
128static enum pcm_dma_status MIXER_CALLBACK_ICODE
129mixer_buffer_callback(enum pcm_dma_status status)
130{
131 if (status != PCM_DMAST_STARTED)
132 return status;
133
134 downmix_index ^= 1; /* Next buffer */
135
136 void *mixptr = downmix_buf[downmix_index];
137 size_t mixsize = mix_frame_size;
138 struct mixer_channel **chan_p;
139
140 next_size = 0;
141
142 /* "Loop" back here if one round wasn't enough to fill a frame */
143fill_frame:
144 chan_p = active_channels;
145
146 while (*chan_p)
147 {
148 /* Find the active channel with the least data remaining and call any
149 callbacks for channels that ran out - stopping whichever report
150 "no more" */
151 struct mixer_channel *chan = *chan_p;
152 chan->start += chan->last_size;
153 chan->size -= chan->last_size;
154
155 if (chan->size == 0)
156 {
157 if (chan->get_more)
158 {
159 chan->get_more(&chan->start, &chan->size);
160 ALIGN_AUDIOBUF(chan->start, chan->size);
161 }
162
163 if (!(chan->start && chan->size))
164 {
165 /* Channel is stopping */
166 channel_stopped(chan);
167 continue;
168 }
169
170 chan_call_buffer_hook(chan);
171 }
172
173 /* Channel will play for at least part of this frame */
174
175 /* Channel with least amount of data remaining determines the downmix
176 size */
177 if (chan->size < mixsize)
178 mixsize = chan->size;
179
180 chan_p++;
181 }
182
183 /* Add all still-active channels to the downmix */
184 chan_p = active_channels;
185
186 if (LIKELY(*chan_p))
187 {
188 struct mixer_channel *chan = *chan_p++;
189
190 if (LIKELY(!*chan_p))
191 {
192 write_samples(mixptr, chan->start, chan->amplitude, mixsize);
193 }
194 else
195 {
196 const void *src0, *src1;
197 unsigned int amp0, amp1;
198
199 /* Mix first two channels with each other as the downmix */
200 src0 = chan->start;
201 amp0 = chan->amplitude;
202 chan->last_size = mixsize;
203
204 chan = *chan_p++;
205 src1 = chan->start;
206 amp1 = chan->amplitude;
207
208 while (1)
209 {
210 mix_samples(mixptr, src0, amp0, src1, amp1, mixsize);
211
212 if (!*chan_p)
213 break;
214
215 /* More channels to mix - mix each with existing downmix */
216 chan->last_size = mixsize;
217 chan = *chan_p++;
218 src0 = mixptr;
219 amp0 = MIX_AMP_UNITY;
220 src1 = chan->start;
221 amp1 = chan->amplitude;
222 }
223 }
224
225 chan->last_size = mixsize;
226 next_size += mixsize;
227
228 if (next_size < mix_frame_size)
229 {
230 /* There is still space remaining in this frame */
231 mixptr += mixsize;
232 mixsize = mix_frame_size - next_size;
233 goto fill_frame;
234 }
235 }
236 else if (idle_counter++ < MAX_IDLE_FRAMES)
237 {
238 /* Pad incomplete frames with silence */
239 if (idle_counter <= 3)
240 memset(mixptr, 0, mix_frame_size - next_size);
241
242 next_size = mix_frame_size;
243 }
244 /* else silence period ran out - go to sleep */
245
246#if FRAME_BOUNDARY_MARKERS != 0
247 if (next_size)
248 *downmix_buf[downmix_index] = downmix_index ? 0x7fff7fff : 0x80008000;
249#endif
250
251 /* Certain SoC's have to do cleanup */
252 mixer_buffer_callback_exit();
253
254 return PCM_DMAST_OK;
255}
256
257/* Start PCM driver if it's not currently playing */
258static void mixer_start_pcm(void)
259{
260 if (pcm_is_playing())
261 return;
262
263#if defined(HAVE_RECORDING)
264 if (pcm_is_recording())
265 return;
266#endif
267
268 /* Requires a shared global sample rate for all channels */
269 pcm_set_frequency(mixer_sampr);
270
271 /* Prepare initial frames and set up the double buffer */
272 mixer_buffer_callback(PCM_DMAST_STARTED);
273
274 /* Save the previous call's output */
275 void *start = downmix_buf[downmix_index];
276
277 mixer_buffer_callback(PCM_DMAST_STARTED);
278
279 pcm_play_data(mixer_pcm_callback, mixer_buffer_callback,
280 start, mix_frame_size);
281}
282
283/** Public interfaces **/
284
285/* Start playback on a channel */
286void mixer_channel_play_data(enum pcm_mixer_channel channel,
287 pcm_play_callback_type get_more,
288 const void *start, size_t size)
289{
290 struct mixer_channel *chan = &channels[channel];
291
292 ALIGN_AUDIOBUF(start, size);
293
294 if (!(start && size) && get_more)
295 {
296 /* Initial buffer not passed - call the callback now */
297 pcm_play_lock();
298 mixer_deactivate_channel(chan); /* Protect chan struct if active;
299 may also be same callback which
300 must not be reentered */
301 pcm_play_unlock(); /* Allow playback while doing callback */
302
303 size = 0;
304 get_more(&start, &size);
305 ALIGN_AUDIOBUF(start, size);
306 }
307
308 pcm_play_lock();
309
310 if (start && size)
311 {
312 /* We have data - start the channel */
313 chan->status = CHANNEL_PLAYING;
314 chan->start = start;
315 chan->size = size;
316 chan->last_size = 0;
317 chan->get_more = get_more;
318
319 mixer_activate_channel(chan);
320 chan_call_buffer_hook(chan);
321 mixer_start_pcm();
322 }
323 else
324 {
325 /* Never had anything - stop it now */
326 channel_stopped(chan);
327 }
328
329 pcm_play_unlock();
330}
331
332/* Pause or resume a channel (when started) */
333void mixer_channel_play_pause(enum pcm_mixer_channel channel, bool play)
334{
335 struct mixer_channel *chan = &channels[channel];
336
337 pcm_play_lock();
338
339 if (play == (chan->status == CHANNEL_PAUSED) &&
340 chan->status != CHANNEL_STOPPED)
341 {
342 if (play)
343 {
344 chan->status = CHANNEL_PLAYING;
345 mixer_activate_channel(chan);
346 mixer_start_pcm();
347 }
348 else
349 {
350 mixer_deactivate_channel(chan);
351 chan->status = CHANNEL_PAUSED;
352 }
353 }
354
355 pcm_play_unlock();
356}
357
358/* Stop playback on a channel */
359void mixer_channel_stop(enum pcm_mixer_channel channel)
360{
361 struct mixer_channel *chan = &channels[channel];
362
363 pcm_play_lock();
364 channel_stopped(chan);
365 pcm_play_unlock();
366}
367
368/* Set channel's amplitude factor */
369void mixer_channel_set_amplitude(enum pcm_mixer_channel channel,
370 unsigned int amplitude)
371{
372 channels[channel].amplitude = MIN(amplitude, MIX_AMP_UNITY);
373}
374
375/* Return channel's playback status */
376enum channel_status mixer_channel_status(enum pcm_mixer_channel channel)
377{
378 return channels[channel].status;
379}
380
381/* Returns amount data remaining in channel before next callback */
382size_t mixer_channel_get_bytes_waiting(enum pcm_mixer_channel channel)
383{
384 return channels[channel].size;
385}
386
387/* Return pointer to channel's playing audio data and the size remaining */
388const void * mixer_channel_get_buffer(enum pcm_mixer_channel channel, int *count)
389{
390 struct mixer_channel *chan = &channels[channel];
391 const void * buf = *(const void * volatile *)&chan->start;
392 size_t size = *(size_t volatile *)&chan->size;
393 const void * buf2 = *(const void * volatile *)&chan->start;
394
395 /* Still same buffer? */
396 if (buf == buf2)
397 {
398 *count = size >> 2;
399 return buf;
400 }
401 /* else can't be sure buf and size are related */
402
403 *count = 0;
404 return NULL;
405}
406
407/* Calculate peak values for channel */
408void mixer_channel_calculate_peaks(enum pcm_mixer_channel channel,
409 struct pcm_peaks *peaks)
410{
411 int count;
412 const void *addr = mixer_channel_get_buffer(channel, &count);
413
414 pcm_do_peak_calculation(peaks,
415 channels[channel].status == CHANNEL_PLAYING,
416 addr, count);
417}
418
419/* Adjust channel pointer by a given offset to support movable buffers */
420void mixer_adjust_channel_address(enum pcm_mixer_channel channel,
421 off_t offset)
422{
423 pcm_play_lock();
424 /* Makes no difference if it's stopped */
425 channels[channel].start += offset;
426 pcm_play_unlock();
427}
428
429/* Set a hook that is called upon getting a new source buffer for a channel
430 NOTE: Called for each buffer, not each mixer chunk */
431void mixer_channel_set_buffer_hook(enum pcm_mixer_channel channel,
432 chan_buffer_hook_fn_type fn)
433{
434 struct mixer_channel *chan = &channels[channel];
435
436 pcm_play_lock();
437 chan->buffer_hook = fn;
438 pcm_play_unlock();
439}
440
441/* Stop ALL channels and PCM and reset state */
442void mixer_reset(void)
443{
444 pcm_play_stop();
445
446 while (*active_channels)
447 channel_stopped(*active_channels);
448
449 idle_counter = 0;
450}
451
452/* Set output samplerate */
453void mixer_set_frequency(unsigned int samplerate)
454{
455 pcm_set_frequency(samplerate);
456 samplerate = pcm_get_frequency();
457
458 if (samplerate == mixer_sampr)
459 return;
460
461 /* All data is now invalid */
462 mixer_reset();
463 mixer_sampr = samplerate;
464
465 /* Work out how much space we really need */
466 if (samplerate > SAMPR_96)
467 mix_frame_size = 4;
468 else if (samplerate > SAMPR_48)
469 mix_frame_size = 2;
470 else
471 mix_frame_size = 1;
472
473 mix_frame_size *= MIX_FRAME_SAMPLES * 4;
474}
475
476/* Get output samplerate */
477unsigned int mixer_get_frequency(void)
478{
479 return mixer_sampr;
480}