Clean up multiple definitions of RAM size. Remove -DMEM (make) and MEM (code), use...
[maemo-rb.git] / apps / plugins / mpegplayer / pcm_output.c
blobfb7ff434aa5cec51d696fdac5d2ea200b56b38b0
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * PCM output buffer definitions
12 * Copyright (c) 2007 Michael Sevakis
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.
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
22 ****************************************************************************/
23 #include "plugin.h"
24 #include "mpegplayer.h"
26 /* Pointers */
28 /* Start of buffer */
29 static struct pcm_frame_header * ALIGNED_ATTR(4) pcm_buffer;
30 /* End of buffer (not guard) */
31 static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_end;
32 /* Read pointer */
33 static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_head IBSS_ATTR;
34 /* Write pointer */
35 static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_tail IBSS_ATTR;
37 /* Bytes */
38 static ssize_t pcmbuf_curr_size IBSS_ATTR; /* Size of currently playing frame */
39 static ssize_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */
40 static ssize_t pcmbuf_written IBSS_ATTR; /* Number of bytes written by source */
41 static ssize_t pcmbuf_threshold IBSS_ATTR; /* Non-silence threshold */
43 /* Clock */
44 static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */
45 static uint32_t volatile clock_tick IBSS_ATTR; /* Our base clock */
46 static uint32_t volatile clock_time IBSS_ATTR; /* Timestamp adjusted */
48 static int pcm_skipped = 0;
49 static int pcm_underruns = 0;
51 /* Small silence clip. ~5.80ms @ 44.1kHz */
52 static int16_t silence[256*2] ALIGNED_ATTR(4) = { 0 };
54 /* Delete all buffer contents */
55 static void pcm_reset_buffer(void)
57 pcmbuf_threshold = PCMOUT_PLAY_WM;
58 pcmbuf_curr_size = pcmbuf_read = pcmbuf_written = 0;
59 pcmbuf_head = pcmbuf_tail = pcm_buffer;
60 pcm_skipped = pcm_underruns = 0;
63 /* Advance a PCM buffer pointer by size bytes circularly */
64 static inline void pcm_advance_buffer(struct pcm_frame_header **p,
65 size_t size)
67 *p = SKIPBYTES(*p, size);
68 if (*p >= pcmbuf_end)
69 *p = SKIPBYTES(*p, -PCMOUT_BUFSIZE);
72 /* Return physical space used */
73 static inline ssize_t pcm_output_bytes_used(void)
75 return pcmbuf_written - pcmbuf_read; /* wrap-safe */
78 /* Return physical space free */
79 static inline ssize_t pcm_output_bytes_free(void)
81 return PCMOUT_BUFSIZE - pcm_output_bytes_used();
84 /* Audio DMA handler */
85 static void get_more(unsigned char **start, size_t *size)
87 ssize_t sz;
89 /* Free-up the last frame played frame if any */
90 pcmbuf_read += pcmbuf_curr_size;
91 pcmbuf_curr_size = 0;
93 sz = pcm_output_bytes_used();
95 if (sz > pcmbuf_threshold)
97 pcmbuf_threshold = PCMOUT_LOW_WM;
99 while (1)
101 uint32_t time = pcmbuf_head->time;
102 int32_t offset = time - clock_time;
104 sz = pcmbuf_head->size;
106 if (sz < (ssize_t)(PCM_HDR_SIZE + 4) ||
107 (sz & 3) != 0)
109 /* Just show a warning about this - will never happen
110 * without a corrupted buffer */
111 DEBUGF("get_more: invalid size (%ld)\n", (long)sz);
114 if (offset < -100*CLOCK_RATE/1000)
116 /* Frame more than 100ms late - drop it */
117 pcm_advance_buffer(&pcmbuf_head, sz);
118 pcmbuf_read += sz;
119 pcm_skipped++;
120 if (pcm_output_bytes_used() > 0)
121 continue;
123 /* Ran out so revert to default watermark */
124 pcmbuf_threshold = PCMOUT_PLAY_WM;
125 pcm_underruns++;
127 else if (offset < 100*CLOCK_RATE/1000)
129 /* Frame less than 100ms early - play it */
130 struct pcm_frame_header *head = pcmbuf_head;
132 pcm_advance_buffer(&pcmbuf_head, sz);
133 pcmbuf_curr_size = sz;
135 sz -= PCM_HDR_SIZE;
137 /* Audio is time master - keep clock synchronized */
138 clock_time = time + (sz >> 2);
140 /* Update base clock */
141 clock_tick += sz >> 2;
143 *start = head->data;
144 *size = sz;
145 return;
147 /* Frame will be dropped - play silence clip */
148 break;
151 else
153 /* Ran out so revert to default watermark */
154 if (pcmbuf_threshold == PCMOUT_LOW_WM)
155 pcm_underruns++;
157 pcmbuf_threshold = PCMOUT_PLAY_WM;
160 /* Keep clock going at all times */
161 clock_time += sizeof (silence) / 4;
162 clock_tick += sizeof (silence) / 4;
164 *start = (unsigned char *)silence;
165 *size = sizeof (silence);
167 if (sz < 0)
168 pcmbuf_read = pcmbuf_written;
171 /** Public interface **/
173 /* Return a buffer pointer if at least size bytes are available and if so,
174 * give the actual free space */
175 unsigned char * pcm_output_get_buffer(ssize_t *size)
177 ssize_t sz = *size;
178 ssize_t free = pcm_output_bytes_free() - PCM_HDR_SIZE;
180 if (sz >= 0 && free >= sz)
182 *size = free; /* return actual free space (- header) */
183 return pcmbuf_tail->data;
186 /* Leave *size alone so caller doesn't have to reinit */
187 return NULL;
190 /* Commit the buffer returned by pcm_ouput_get_buffer; timestamp is PCM
191 * clock time units, not video format time units */
192 bool pcm_output_commit_data(ssize_t size, uint32_t timestamp)
194 if (size <= 0 || (size & 3))
195 return false; /* invalid */
197 size += PCM_HDR_SIZE;
199 if (size > pcm_output_bytes_free())
200 return false; /* too big */
202 pcmbuf_tail->size = size;
203 pcmbuf_tail->time = timestamp;
205 pcm_advance_buffer(&pcmbuf_tail, size);
206 pcmbuf_written += size;
208 return true;
211 /* Returns 'true' if the buffer is completely empty */
212 bool pcm_output_empty(void)
214 return pcm_output_bytes_used() <= 0;
217 /* Flushes the buffer - clock keeps counting */
218 void pcm_output_flush(void)
220 bool playing, paused;
222 rb->pcm_play_lock();
224 playing = rb->pcm_is_playing();
225 paused = rb->pcm_is_paused();
227 /* Stop PCM to clear current buffer */
228 if (playing)
229 rb->pcm_play_stop();
231 pcm_reset_buffer();
233 /* Restart if playing state was current */
234 if (playing && !paused)
235 rb->pcm_play_data(get_more, NULL, 0);
237 rb->pcm_play_unlock();
240 /* Seek the reference clock to the specified time - next audio data ready to
241 go to DMA should be on the buffer with the same time index or else the PCM
242 buffer should be empty */
243 void pcm_output_set_clock(uint32_t time)
245 rb->pcm_play_lock();
247 clock_start = time;
248 clock_tick = time;
249 clock_time = time;
251 rb->pcm_play_unlock();
254 /* Return the clock as synchronized by audio frame timestamps */
255 uint32_t pcm_output_get_clock(void)
257 uint32_t time, rem;
259 /* Reread if data race detected - rem will be 0 if driver hasn't yet
260 * updated to the new buffer size. Also be sure pcm state doesn't
261 * cause indefinite loop.
263 * FYI: NOT scrutinized for rd/wr reordering on different cores. */
266 time = clock_time;
267 rem = rb->pcm_get_bytes_waiting() >> 2;
269 while (UNLIKELY(time != clock_time ||
270 (rem == 0 && rb->pcm_is_playing() && !rb->pcm_is_paused())));
272 return time - rem;
276 /* Return the raw clock as counted from the last pcm_output_set_clock
277 * call */
278 uint32_t pcm_output_get_ticks(uint32_t *start)
280 uint32_t tick, rem;
282 /* Same procedure as pcm_output_get_clock */
285 tick = clock_tick;
286 rem = rb->pcm_get_bytes_waiting() >> 2;
288 while (UNLIKELY(tick != clock_tick ||
289 (rem == 0 && rb->pcm_is_playing() && !rb->pcm_is_paused())));
291 if (start)
292 *start = clock_start;
294 return tick - rem;
297 /* Pauses/Starts pcm playback - and the clock */
298 void pcm_output_play_pause(bool play)
300 rb->pcm_play_lock();
302 if (rb->pcm_is_playing())
304 rb->pcm_play_pause(play);
306 else if (play)
308 rb->pcm_play_data(get_more, NULL, 0);
311 rb->pcm_play_unlock();
314 /* Stops all playback and resets the clock */
315 void pcm_output_stop(void)
317 rb->pcm_play_lock();
319 if (rb->pcm_is_playing())
320 rb->pcm_play_stop();
322 pcm_output_flush();
323 pcm_output_set_clock(0);
325 rb->pcm_play_unlock();
328 /* Drains any data if the start threshold hasn't been reached */
329 void pcm_output_drain(void)
331 rb->pcm_play_lock();
332 pcmbuf_threshold = PCMOUT_LOW_WM;
333 rb->pcm_play_unlock();
336 bool pcm_output_init(void)
338 pcm_buffer = mpeg_malloc(PCMOUT_ALLOC_SIZE, MPEG_ALLOC_PCMOUT);
339 if (pcm_buffer == NULL)
340 return false;
342 pcmbuf_end = SKIPBYTES(pcm_buffer, PCMOUT_BUFSIZE);
344 pcm_reset_buffer();
346 /* Some targets could play at the movie frequency without resampling but
347 * as of now DSP assumes a certain frequency (always 44100Hz) so
348 * resampling will be needed for other movie audio rates. */
349 rb->pcm_set_frequency(NATIVE_FREQUENCY);
351 #if INPUT_SRC_CAPS != 0
352 /* Select playback */
353 rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK);
354 rb->audio_set_output_source(AUDIO_SRC_PLAYBACK);
355 #endif
357 #if SILENCE_TEST_TONE
358 /* Make the silence clip a square wave */
359 const int16_t silence_amp = INT16_MAX / 16;
360 unsigned i;
362 for (i = 0; i < ARRAYLEN(silence); i += 2)
364 if (i < ARRAYLEN(silence)/2)
366 silence[i] = silence_amp;
367 silence[i+1] = silence_amp;
369 else
371 silence[i] = -silence_amp;
372 silence[i+1] = -silence_amp;
375 #endif
377 return true;
380 void pcm_output_exit(void)
382 rb->pcm_set_frequency(HW_SAMPR_DEFAULT);