From a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Sat, 29 Dec 2007 19:46:35 +0000 Subject: mpegplayer: Make playback engine fully seekable and frame-accurate and split into logical parts. Be sure to have all current features work. Actual UI for seeking will be added soon. Recommended GOP size is about 15-30 frames depending on target or seeking can be slow with really long GOPs (nature of MPEG video). More refined encoding recommendations for a particular player should be posted soon. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15977 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/mpegplayer/pcm_output.c | 278 +++++++++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 apps/plugins/mpegplayer/pcm_output.c (limited to 'apps/plugins/mpegplayer/pcm_output.c') diff --git a/apps/plugins/mpegplayer/pcm_output.c b/apps/plugins/mpegplayer/pcm_output.c new file mode 100644 index 0000000000..281f7ddb72 --- /dev/null +++ b/apps/plugins/mpegplayer/pcm_output.c @@ -0,0 +1,278 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * PCM output buffer definitions + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 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 */ + +/* 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 = pcm_buffer; +} + +/* 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 = 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 (%ld)\n", sz); + } + + if (offset < -100*CLOCK_RATE/1000) + { + /* Frame more than 100ms late - drop it */ + pcm_advance_buffer(&pcmbuf_head, sz); + pcmbuf_read += sz; + if (pcmbuf_read < pcmbuf_written) + continue; + } + else if (offset < 100*CLOCK_RATE/1000) + { + /* Frame less than 100ms early - play it */ + *start = (unsigned char *)pcmbuf_head->data; + + pcm_advance_buffer(&pcmbuf_head, sz); + pcmbuf_read += 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 */ + 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) +{ + rb->pcm_play_lock(); + + pcmbuf_threshold = PCMOUT_PLAY_WM; + pcmbuf_read = pcmbuf_written = 0; + pcmbuf_head = pcmbuf_tail = pcm_buffer; + + 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_read = 0; + pcmbuf_written = 0; + + rb->pcm_set_frequency(SAMPR_44); + +#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 = 32767 / 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); +} -- cgit v1.2.3