A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 396 lines 11 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * PCM output buffer definitions 11 * 12 * Copyright (c) 2007 Michael Sevakis 13 * 14 * This program is free software; you can redistribute it and/or 15 * modify it under the terms of the GNU General Public License 16 * as published by the Free Software Foundation; either version 2 17 * of the License, or (at your option) any later version. 18 * 19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 20 * KIND, either express or implied. 21 * 22 ****************************************************************************/ 23#include "plugin.h" 24#include "mpegplayer.h" 25 26/* PCM channel we're using */ 27#define MPEG_PCM_CHANNEL PCM_MIXER_CHAN_PLAYBACK 28 29/* Pointers */ 30 31/* Start of buffer */ 32static struct pcm_frame_header * ALIGNED_ATTR(4) pcm_buffer; 33/* End of buffer (not guard) */ 34static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_end; 35/* Read pointer */ 36static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_head IBSS_ATTR; 37/* Write pointer */ 38static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_tail IBSS_ATTR; 39 40/* Bytes */ 41static ssize_t pcmbuf_curr_size IBSS_ATTR; /* Size of currently playing frame */ 42static ssize_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */ 43static ssize_t pcmbuf_written IBSS_ATTR; /* Number of bytes written by source */ 44static ssize_t pcmbuf_threshold IBSS_ATTR; /* Non-silence threshold */ 45 46/* Clock */ 47static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */ 48static uint32_t volatile clock_tick IBSS_ATTR; /* Our base clock */ 49static uint32_t volatile clock_time IBSS_ATTR; /* Timestamp adjusted */ 50 51static int pcm_skipped = 0; 52static int pcm_underruns = 0; 53 54static unsigned int old_sampr = 0; 55 56/* Small silence clip. ~5.80ms @ 44.1kHz */ 57static int16_t silence[256*2] ALIGNED_ATTR(4) = { 0 }; 58 59/* Delete all buffer contents */ 60static void pcm_reset_buffer(void) 61{ 62 pcmbuf_threshold = PCMOUT_PLAY_WM; 63 pcmbuf_curr_size = pcmbuf_read = pcmbuf_written = 0; 64 pcmbuf_head = pcmbuf_tail = pcm_buffer; 65 pcm_skipped = pcm_underruns = 0; 66} 67 68/* Advance a PCM buffer pointer by size bytes circularly */ 69static inline void pcm_advance_buffer(struct pcm_frame_header **p, 70 size_t size) 71{ 72 *p = SKIPBYTES(*p, size); 73 if (*p >= pcmbuf_end) 74 *p = SKIPBYTES(*p, -PCMOUT_BUFSIZE); 75} 76 77/* Return physical space used */ 78static inline ssize_t pcm_output_bytes_used(void) 79{ 80 return pcmbuf_written - pcmbuf_read; /* wrap-safe */ 81} 82 83/* Return physical space free */ 84static inline ssize_t pcm_output_bytes_free(void) 85{ 86 return PCMOUT_BUFSIZE - pcm_output_bytes_used(); 87} 88 89/* Audio DMA handler */ 90static void get_more(const void **start, size_t *size) 91{ 92 ssize_t sz; 93 94 /* Free-up the last frame played frame if any */ 95 pcmbuf_read += pcmbuf_curr_size; 96 pcmbuf_curr_size = 0; 97 98 sz = pcm_output_bytes_used(); 99 100 if (sz > pcmbuf_threshold) 101 { 102 pcmbuf_threshold = PCMOUT_LOW_WM; 103 104 while (1) 105 { 106 uint32_t time = pcmbuf_head->time; 107 int32_t offset = time - clock_time; 108 109 sz = pcmbuf_head->size; 110 111 if (sz < (ssize_t)(PCM_HDR_SIZE + 4) || 112 (sz & 3) != 0) 113 { 114 /* Just show a warning about this - will never happen 115 * without a corrupted buffer */ 116 DEBUGF("get_more: invalid size (%ld)\n", (long)sz); 117 } 118 119 if (offset < -100*CLOCK_RATE/1000) 120 { 121 /* Frame more than 100ms late - drop it */ 122 pcm_advance_buffer(&pcmbuf_head, sz); 123 pcmbuf_read += sz; 124 pcm_skipped++; 125 if (pcm_output_bytes_used() > 0) 126 continue; 127 128 /* Ran out so revert to default watermark */ 129 pcmbuf_threshold = PCMOUT_PLAY_WM; 130 pcm_underruns++; 131 } 132 else if (offset < 100*CLOCK_RATE/1000) 133 { 134 /* Frame less than 100ms early - play it */ 135 struct pcm_frame_header *head = pcmbuf_head; 136 137 pcm_advance_buffer(&pcmbuf_head, sz); 138 pcmbuf_curr_size = sz; 139 140 sz -= PCM_HDR_SIZE; 141 142 /* Audio is time master - keep clock synchronized */ 143 clock_time = time + (sz >> 2); 144 145 /* Update base clock */ 146 clock_tick += sz >> 2; 147 148 *start = head->data; 149 *size = sz; 150 return; 151 } 152 /* Frame will be dropped - play silence clip */ 153 break; 154 } 155 } 156 else 157 { 158 /* Ran out so revert to default watermark */ 159 if (pcmbuf_threshold == PCMOUT_LOW_WM) 160 pcm_underruns++; 161 162 pcmbuf_threshold = PCMOUT_PLAY_WM; 163 } 164 165 /* Keep clock going at all times */ 166 clock_time += sizeof (silence) / 4; 167 clock_tick += sizeof (silence) / 4; 168 169 *start = silence; 170 *size = sizeof (silence); 171 172 if (sz < 0) 173 pcmbuf_read = pcmbuf_written; 174} 175 176/** Public interface **/ 177 178/* Return a buffer pointer if at least size bytes are available and if so, 179 * give the actual free space */ 180void * pcm_output_get_buffer(ssize_t *size) 181{ 182 ssize_t sz = *size; 183 ssize_t free = pcm_output_bytes_free() - PCM_HDR_SIZE; 184 185 if (sz >= 0 && free >= sz) 186 { 187 *size = free; /* return actual free space (- header) */ 188 return pcmbuf_tail->data; 189 } 190 191 /* Leave *size alone so caller doesn't have to reinit */ 192 return NULL; 193} 194 195/* Commit the buffer returned by pcm_ouput_get_buffer; timestamp is PCM 196 * clock time units, not video format time units */ 197bool pcm_output_commit_data(ssize_t size, uint32_t timestamp) 198{ 199 if (size <= 0 || (size & 3)) 200 return false; /* invalid */ 201 202 size += PCM_HDR_SIZE; 203 204 if (size > pcm_output_bytes_free()) 205 return false; /* too big */ 206 207 pcmbuf_tail->size = size; 208 pcmbuf_tail->time = timestamp; 209 210 pcm_advance_buffer(&pcmbuf_tail, size); 211 pcmbuf_written += size; 212 213 return true; 214} 215 216/* Returns 'true' if the buffer is completely empty */ 217bool pcm_output_empty(void) 218{ 219 return pcm_output_bytes_used() <= 0; 220} 221 222/* Flushes the buffer - clock keeps counting */ 223void pcm_output_flush(void) 224{ 225 rb->pcm_play_lock(); 226 227 enum channel_status status = rb->mixer_channel_status(MPEG_PCM_CHANNEL); 228 229 /* Stop PCM to clear current buffer */ 230 if (status != CHANNEL_STOPPED) 231 rb->mixer_channel_stop(MPEG_PCM_CHANNEL); 232 233 rb->pcm_play_unlock(); 234 235 pcm_reset_buffer(); 236 237 /* Restart if playing state was current */ 238 if (status == CHANNEL_PLAYING) 239 rb->mixer_channel_play_data(MPEG_PCM_CHANNEL, 240 get_more, NULL, 0); 241} 242 243/* Seek the reference clock to the specified time - next audio data ready to 244 go to DMA should be on the buffer with the same time index or else the PCM 245 buffer should be empty */ 246void pcm_output_set_clock(uint32_t time) 247{ 248 rb->pcm_play_lock(); 249 250 clock_start = time; 251 clock_tick = time; 252 clock_time = time; 253 254 rb->pcm_play_unlock(); 255} 256 257/* Return the clock as synchronized by audio frame timestamps */ 258uint32_t pcm_output_get_clock(void) 259{ 260 uint32_t time, rem; 261 262 /* Reread if data race detected - rem will be 0 if driver hasn't yet 263 * updated to the new buffer size. Also be sure pcm state doesn't 264 * cause indefinite loop. 265 * 266 * FYI: NOT scrutinized for rd/wr reordering on different cores. */ 267 do 268 { 269 time = clock_time; 270 rem = rb->mixer_channel_get_bytes_waiting(MPEG_PCM_CHANNEL) >> 2; 271 } 272 while (UNLIKELY(time != clock_time || 273 (rem == 0 && 274 rb->mixer_channel_status(MPEG_PCM_CHANNEL) == CHANNEL_PLAYING)) 275 ); 276 277 return time - rem; 278 279} 280 281/* Return the raw clock as counted from the last pcm_output_set_clock 282 * call */ 283uint32_t pcm_output_get_ticks(uint32_t *start) 284{ 285 uint32_t tick, rem; 286 287 /* Same procedure as pcm_output_get_clock */ 288 do 289 { 290 tick = clock_tick; 291 rem = rb->mixer_channel_get_bytes_waiting(MPEG_PCM_CHANNEL) >> 2; 292 } 293 while (UNLIKELY(tick != clock_tick || 294 (rem == 0 && 295 rb->mixer_channel_status(MPEG_PCM_CHANNEL) == CHANNEL_PLAYING)) 296 ); 297 298 if (start) 299 *start = clock_start; 300 301 return tick - rem; 302} 303 304/* Pauses/Starts pcm playback - and the clock */ 305void pcm_output_play_pause(bool play) 306{ 307 rb->pcm_play_lock(); 308 309 if (rb->mixer_channel_status(MPEG_PCM_CHANNEL) != CHANNEL_STOPPED) 310 { 311 rb->mixer_channel_play_pause(MPEG_PCM_CHANNEL, play); 312 rb->pcm_play_unlock(); 313 } 314 else 315 { 316 rb->pcm_play_unlock(); 317 318 if (play) 319 { 320 rb->mixer_channel_set_amplitude(MPEG_PCM_CHANNEL, MIX_AMP_UNITY); 321 rb->mixer_channel_play_data(MPEG_PCM_CHANNEL, 322 get_more, NULL, 0); 323 } 324 } 325} 326 327/* Stops all playback and resets the clock */ 328void pcm_output_stop(void) 329{ 330 rb->pcm_play_lock(); 331 332 if (rb->mixer_channel_status(MPEG_PCM_CHANNEL) != CHANNEL_STOPPED) 333 rb->mixer_channel_stop(MPEG_PCM_CHANNEL); 334 335 rb->pcm_play_unlock(); 336 337 pcm_output_flush(); 338 pcm_output_set_clock(0); 339} 340 341/* Drains any data if the start threshold hasn't been reached */ 342void pcm_output_drain(void) 343{ 344 rb->pcm_play_lock(); 345 pcmbuf_threshold = PCMOUT_LOW_WM; 346 rb->pcm_play_unlock(); 347} 348 349bool pcm_output_init(void) 350{ 351 pcm_buffer = mpeg_malloc(PCMOUT_ALLOC_SIZE, MPEG_ALLOC_PCMOUT); 352 if (pcm_buffer == NULL) 353 return false; 354 355 pcmbuf_end = SKIPBYTES(pcm_buffer, PCMOUT_BUFSIZE); 356 357 pcm_reset_buffer(); 358 359#if INPUT_SRC_CAPS != 0 360 /* Select playback */ 361 rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); 362 rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); 363#endif 364 365#if SILENCE_TEST_TONE 366 /* Make the silence clip a square wave */ 367 const int16_t silence_amp = INT16_MAX / 16; 368 unsigned i; 369 370 for (i = 0; i < ARRAYLEN(silence); i += 2) 371 { 372 if (i < ARRAYLEN(silence)/2) 373 { 374 silence[i] = silence_amp; 375 silence[i+1] = silence_amp; 376 } 377 else 378 { 379 silence[i] = -silence_amp; 380 silence[i+1] = -silence_amp; 381 } 382 } 383#endif 384 385 old_sampr = rb->mixer_get_frequency(); 386 rb->mixer_set_frequency(CLOCK_RATE); 387 rb->pcmbuf_fade(false, true); 388 return true; 389} 390 391void pcm_output_exit(void) 392{ 393 rb->pcmbuf_fade(false, false); 394 if (old_sampr != 0) 395 rb->mixer_set_frequency(old_sampr); 396}