/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * PCM output buffer definitions * * Copyright (c) 2007 Michael Sevakis * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "plugin.h" #include "mpegplayer.h" /* Pointers */ /* Start of buffer */ static struct pcm_frame_header * ALIGNED_ATTR(4) pcm_buffer; /* End of buffer (not guard) */ static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_end; /* Read pointer */ static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_head IBSS_ATTR; /* Write pointer */ static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_tail IBSS_ATTR; /* Bytes */ static ssize_t pcmbuf_curr_size IBSS_ATTR; /* Size of currently playing frame */ static uint64_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */ static uint64_t pcmbuf_written IBSS_ATTR; /* Number of bytes written by source */ static ssize_t pcmbuf_threshold IBSS_ATTR; /* Non-silence threshold */ /* Clock */ static uint32_t clock_base IBSS_ATTR; /* Our base clock */ static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */ static int32_t clock_adjust IBSS_ATTR; /* Clock drift adjustment */ int pcm_skipped = 0; int pcm_underruns = 0; /* Small silence clip. ~5.80ms @ 44.1kHz */ static int16_t silence[256*2] ALIGNED_ATTR(4) = { 0 }; /* Advance a PCM buffer pointer by size bytes circularly */ static inline void pcm_advance_buffer(struct pcm_frame_header **p, size_t size) { *p = SKIPBYTES(*p, size); if (*p >= pcmbuf_end) *p = SKIPBYTES(*p, -PCMOUT_BUFSIZE); } /* Inline internally but not externally */ inline ssize_t pcm_output_used(void) { return (ssize_t)(pcmbuf_written - pcmbuf_read); } inline ssize_t pcm_output_free(void) { return (ssize_t)(PCMOUT_BUFSIZE - pcmbuf_written + pcmbuf_read); } /* Audio DMA handler */ static void get_more(unsigned char **start, size_t *size) { ssize_t sz; /* Free-up the last frame played frame if any */ pcmbuf_read += pcmbuf_curr_size; pcmbuf_curr_size = 0; sz = pcm_output_used(); if (sz > pcmbuf_threshold) { pcmbuf_threshold = PCMOUT_LOW_WM; while (1) { uint32_t time = pcmbuf_head->time; int32_t offset = time - (clock_base + clock_adjust); sz = pcmbuf_head->size; if (sz < (ssize_t)(sizeof(pcmbuf_head) + 4) || (sz & 3) != 0) { /* Just show a warning about this - will never happen * without a bug in the audio thread code or a clobbered * buffer */ DEBUGF("get_more: invalid size (%zd)\n", sz); } if (offset < -100*CLOCK_RATE/1000) { /* Frame more than 100ms late - drop it */ pcm_advance_buffer(&pcmbuf_head, sz); pcmbuf_read += sz; pcm_skipped++; if (pcmbuf_read < pcmbuf_written) continue; /* Ran out so revert to default watermark */ pcmbuf_threshold = PCMOUT_PLAY_WM; } else if (offset < 100*CLOCK_RATE/1000) { /* Frame less than 100ms early - play it */ *start = pcmbuf_head->data; pcm_advance_buffer(&pcmbuf_head, sz); pcmbuf_curr_size = sz; sz -= sizeof (struct pcm_frame_header); *size = sz; /* Audio is time master - keep clock synchronized */ clock_adjust = time - clock_base; /* Update base clock */ clock_base += sz >> 2; return; } /* Frame will be dropped - play silence clip */ break; } } else { /* Ran out so revert to default watermark */ if (pcmbuf_threshold == PCMOUT_LOW_WM) pcm_underruns++; pcmbuf_threshold = PCMOUT_PLAY_WM; } /* Keep clock going at all times */ *start = (unsigned char *)silence; *size = sizeof (silence); clock_base += sizeof (silence) / 4; if (pcmbuf_read > pcmbuf_written) pcmbuf_read = pcmbuf_written; } struct pcm_frame_header * pcm_output_get_buffer(void) { return pcmbuf_tail; } void pcm_output_add_data(void) { size_t size = pcmbuf_tail->size; pcm_advance_buffer(&pcmbuf_tail, size); pcmbuf_written += size; } /* Flushes the buffer - clock keeps counting */ void pcm_output_flush(void) { bool playing, paused; rb->pcm_play_lock(); playing = rb->pcm_is_playing(); paused = rb->pcm_is_paused(); /* Stop PCM to clear current buffer */ if (playing) rb->pcm_play_stop(); pcmbuf_threshold = PCMOUT_PLAY_WM; pcmbuf_curr_size = pcmbuf_read = pcmbuf_written = 0; pcmbuf_head = pcmbuf_tail = pcm_buffer; pcm_skipped = pcm_underruns = 0; /* Restart if playing state was current */ if (playing && !paused) rb->pcm_play_data(get_more, NULL, 0); rb->pcm_play_unlock(); } /* Seek the reference clock to the specified time - next audio data ready to go to DMA should be on the buffer with the same time index or else the PCM buffer should be empty */ void pcm_output_set_clock(uint32_t time) { rb->pcm_play_lock(); clock_base = time; clock_start = time; clock_adjust = 0; rb->pcm_play_unlock(); } uint32_t pcm_output_get_clock(void) { return clock_base + clock_adjust - (rb->pcm_get_bytes_waiting() >> 2); } uint32_t pcm_output_get_ticks(uint32_t *start) { if (start) *start = clock_start; return clock_base - (rb->pcm_get_bytes_waiting() >> 2); } /* Pauses/Starts pcm playback - and the clock */ void pcm_output_play_pause(bool play) { rb->pcm_play_lock(); if (rb->pcm_is_playing()) { rb->pcm_play_pause(play); } else if (play) { rb->pcm_play_data(get_more, NULL, 0); } rb->pcm_play_unlock(); } /* Stops all playback and resets the clock */ void pcm_output_stop(void) { rb->pcm_play_lock(); if (rb->pcm_is_playing()) rb->pcm_play_stop(); pcm_output_flush(); pcm_output_set_clock(0); rb->pcm_play_unlock(); } /* Drains any data if the start threshold hasn't been reached */ void pcm_output_drain(void) { rb->pcm_play_lock(); pcmbuf_threshold = PCMOUT_LOW_WM; rb->pcm_play_unlock(); } bool pcm_output_init(void) { pcm_buffer = mpeg_malloc(PCMOUT_ALLOC_SIZE, MPEG_ALLOC_PCMOUT); if (pcm_buffer == NULL) return false; pcmbuf_threshold = PCMOUT_PLAY_WM; pcmbuf_head = pcm_buffer; pcmbuf_tail = pcm_buffer; pcmbuf_end = SKIPBYTES(pcm_buffer, PCMOUT_BUFSIZE); pcmbuf_curr_size = pcmbuf_read = pcmbuf_written = 0; rb->pcm_set_frequency(NATIVE_FREQUENCY); #if INPUT_SRC_CAPS != 0 /* Select playback */ rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); #endif #if SILENCE_TEST_TONE /* Make the silence clip a square wave */ const int16_t silence_amp = INT16_MAX / 16; unsigned i; for (i = 0; i < ARRAYLEN(silence); i += 2) { if (i < ARRAYLEN(silence)/2) { silence[i] = silence_amp; silence[i+1] = silence_amp; } else { silence[i] = -silence_amp; silence[i+1] = -silence_amp; } } #endif return true; } void pcm_output_exit(void) { rb->pcm_set_frequency(HW_SAMPR_DEFAULT); }