summaryrefslogtreecommitdiff
path: root/apps/plugins/mpegplayer/pcm_output.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/mpegplayer/pcm_output.c')
-rw-r--r--apps/plugins/mpegplayer/pcm_output.c396
1 files changed, 0 insertions, 396 deletions
diff --git a/apps/plugins/mpegplayer/pcm_output.c b/apps/plugins/mpegplayer/pcm_output.c
deleted file mode 100644
index 5e95d16316..0000000000
--- a/apps/plugins/mpegplayer/pcm_output.c
+++ /dev/null
@@ -1,396 +0,0 @@
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}