diff options
Diffstat (limited to 'apps/plugins/mpegplayer/pcm_output.c')
-rw-r--r-- | apps/plugins/mpegplayer/pcm_output.c | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/apps/plugins/mpegplayer/pcm_output.c b/apps/plugins/mpegplayer/pcm_output.c new file mode 100644 index 0000000000..5e95d16316 --- /dev/null +++ b/apps/plugins/mpegplayer/pcm_output.c | |||
@@ -0,0 +1,396 @@ | |||
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 */ | ||
32 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcm_buffer; | ||
33 | /* End of buffer (not guard) */ | ||
34 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_end; | ||
35 | /* Read pointer */ | ||
36 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_head IBSS_ATTR; | ||
37 | /* Write pointer */ | ||
38 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_tail IBSS_ATTR; | ||
39 | |||
40 | /* Bytes */ | ||
41 | static ssize_t pcmbuf_curr_size IBSS_ATTR; /* Size of currently playing frame */ | ||
42 | static ssize_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */ | ||
43 | static ssize_t pcmbuf_written IBSS_ATTR; /* Number of bytes written by source */ | ||
44 | static ssize_t pcmbuf_threshold IBSS_ATTR; /* Non-silence threshold */ | ||
45 | |||
46 | /* Clock */ | ||
47 | static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */ | ||
48 | static uint32_t volatile clock_tick IBSS_ATTR; /* Our base clock */ | ||
49 | static uint32_t volatile clock_time IBSS_ATTR; /* Timestamp adjusted */ | ||
50 | |||
51 | static int pcm_skipped = 0; | ||
52 | static int pcm_underruns = 0; | ||
53 | |||
54 | static unsigned int old_sampr = 0; | ||
55 | |||
56 | /* Small silence clip. ~5.80ms @ 44.1kHz */ | ||
57 | static int16_t silence[256*2] ALIGNED_ATTR(4) = { 0 }; | ||
58 | |||
59 | /* Delete all buffer contents */ | ||
60 | static 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 */ | ||
69 | static 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 */ | ||
78 | static inline ssize_t pcm_output_bytes_used(void) | ||
79 | { | ||
80 | return pcmbuf_written - pcmbuf_read; /* wrap-safe */ | ||
81 | } | ||
82 | |||
83 | /* Return physical space free */ | ||
84 | static inline ssize_t pcm_output_bytes_free(void) | ||
85 | { | ||
86 | return PCMOUT_BUFSIZE - pcm_output_bytes_used(); | ||
87 | } | ||
88 | |||
89 | /* Audio DMA handler */ | ||
90 | static 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 */ | ||
180 | void * 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 */ | ||
197 | bool 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 */ | ||
217 | bool pcm_output_empty(void) | ||
218 | { | ||
219 | return pcm_output_bytes_used() <= 0; | ||
220 | } | ||
221 | |||
222 | /* Flushes the buffer - clock keeps counting */ | ||
223 | void 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 */ | ||
246 | void 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 */ | ||
258 | uint32_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 */ | ||
283 | uint32_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 */ | ||
305 | void 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 */ | ||
328 | void 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 */ | ||
342 | void pcm_output_drain(void) | ||
343 | { | ||
344 | rb->pcm_play_lock(); | ||
345 | pcmbuf_threshold = PCMOUT_LOW_WM; | ||
346 | rb->pcm_play_unlock(); | ||
347 | } | ||
348 | |||
349 | bool 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 | |||
391 | void pcm_output_exit(void) | ||
392 | { | ||
393 | rb->pcmbuf_fade(false, false); | ||
394 | if (old_sampr != 0) | ||
395 | rb->mixer_set_frequency(old_sampr); | ||
396 | } | ||