diff options
Diffstat (limited to 'apps/plugins/mpegplayer/pcm_output.c')
-rw-r--r-- | apps/plugins/mpegplayer/pcm_output.c | 278 |
1 files changed, 278 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..281f7ddb72 --- /dev/null +++ b/apps/plugins/mpegplayer/pcm_output.c | |||
@@ -0,0 +1,278 @@ | |||
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 | * All files in this archive are subject to the GNU General Public License. | ||
15 | * See the file COPYING in the source tree root for full license agreement. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | #include "plugin.h" | ||
22 | #include "mpegplayer.h" | ||
23 | |||
24 | /* Pointers */ | ||
25 | |||
26 | /* Start of buffer */ | ||
27 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcm_buffer; | ||
28 | /* End of buffer (not guard) */ | ||
29 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_end; | ||
30 | /* Read pointer */ | ||
31 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_head IBSS_ATTR; | ||
32 | /* Write pointer */ | ||
33 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_tail IBSS_ATTR; | ||
34 | |||
35 | /* Bytes */ | ||
36 | static uint64_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */ | ||
37 | static uint64_t pcmbuf_written IBSS_ATTR; /* Number of bytes written by source */ | ||
38 | static ssize_t pcmbuf_threshold IBSS_ATTR; /* Non-silence threshold */ | ||
39 | |||
40 | /* Clock */ | ||
41 | static uint32_t clock_base IBSS_ATTR; /* Our base clock */ | ||
42 | static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */ | ||
43 | static int32_t clock_adjust IBSS_ATTR; /* Clock drift adjustment */ | ||
44 | |||
45 | /* Small silence clip. ~5.80ms @ 44.1kHz */ | ||
46 | static int16_t silence[256*2] ALIGNED_ATTR(4) = { 0 }; | ||
47 | |||
48 | /* Advance a PCM buffer pointer by size bytes circularly */ | ||
49 | static inline void pcm_advance_buffer(struct pcm_frame_header **p, | ||
50 | size_t size) | ||
51 | { | ||
52 | *p = SKIPBYTES(*p, size); | ||
53 | if (*p >= pcmbuf_end) | ||
54 | *p = pcm_buffer; | ||
55 | } | ||
56 | |||
57 | /* Inline internally but not externally */ | ||
58 | inline ssize_t pcm_output_used(void) | ||
59 | { | ||
60 | return (ssize_t)(pcmbuf_written - pcmbuf_read); | ||
61 | } | ||
62 | |||
63 | inline ssize_t pcm_output_free(void) | ||
64 | { | ||
65 | return (ssize_t)(PCMOUT_BUFSIZE - pcmbuf_written + pcmbuf_read); | ||
66 | } | ||
67 | |||
68 | /* Audio DMA handler */ | ||
69 | static void get_more(unsigned char **start, size_t *size) | ||
70 | { | ||
71 | ssize_t sz = pcm_output_used(); | ||
72 | |||
73 | if (sz > pcmbuf_threshold) | ||
74 | { | ||
75 | pcmbuf_threshold = PCMOUT_LOW_WM; | ||
76 | |||
77 | while (1) | ||
78 | { | ||
79 | uint32_t time = pcmbuf_head->time; | ||
80 | int32_t offset = time - (clock_base + clock_adjust); | ||
81 | |||
82 | sz = pcmbuf_head->size; | ||
83 | |||
84 | if (sz < (ssize_t)(sizeof(pcmbuf_head) + 4) || | ||
85 | (sz & 3) != 0) | ||
86 | { | ||
87 | /* Just show a warning about this - will never happen | ||
88 | * without a bug in the audio thread code or a clobbered | ||
89 | * buffer */ | ||
90 | DEBUGF("get_more: invalid size (%ld)\n", sz); | ||
91 | } | ||
92 | |||
93 | if (offset < -100*CLOCK_RATE/1000) | ||
94 | { | ||
95 | /* Frame more than 100ms late - drop it */ | ||
96 | pcm_advance_buffer(&pcmbuf_head, sz); | ||
97 | pcmbuf_read += sz; | ||
98 | if (pcmbuf_read < pcmbuf_written) | ||
99 | continue; | ||
100 | } | ||
101 | else if (offset < 100*CLOCK_RATE/1000) | ||
102 | { | ||
103 | /* Frame less than 100ms early - play it */ | ||
104 | *start = (unsigned char *)pcmbuf_head->data; | ||
105 | |||
106 | pcm_advance_buffer(&pcmbuf_head, sz); | ||
107 | pcmbuf_read += sz; | ||
108 | |||
109 | sz -= sizeof (struct pcm_frame_header); | ||
110 | |||
111 | *size = sz; | ||
112 | |||
113 | /* Audio is time master - keep clock synchronized */ | ||
114 | clock_adjust = time - clock_base; | ||
115 | |||
116 | /* Update base clock */ | ||
117 | clock_base += sz >> 2; | ||
118 | return; | ||
119 | } | ||
120 | /* Frame will be dropped - play silence clip */ | ||
121 | break; | ||
122 | } | ||
123 | } | ||
124 | else | ||
125 | { | ||
126 | /* Ran out so revert to default watermark */ | ||
127 | pcmbuf_threshold = PCMOUT_PLAY_WM; | ||
128 | } | ||
129 | |||
130 | /* Keep clock going at all times */ | ||
131 | *start = (unsigned char *)silence; | ||
132 | *size = sizeof (silence); | ||
133 | |||
134 | clock_base += sizeof (silence) / 4; | ||
135 | |||
136 | if (pcmbuf_read > pcmbuf_written) | ||
137 | pcmbuf_read = pcmbuf_written; | ||
138 | } | ||
139 | |||
140 | struct pcm_frame_header * pcm_output_get_buffer(void) | ||
141 | { | ||
142 | return pcmbuf_tail; | ||
143 | } | ||
144 | |||
145 | void pcm_output_add_data(void) | ||
146 | { | ||
147 | size_t size = pcmbuf_tail->size; | ||
148 | pcm_advance_buffer(&pcmbuf_tail, size); | ||
149 | pcmbuf_written += size; | ||
150 | } | ||
151 | |||
152 | /* Flushes the buffer - clock keeps counting */ | ||
153 | void pcm_output_flush(void) | ||
154 | { | ||
155 | rb->pcm_play_lock(); | ||
156 | |||
157 | pcmbuf_threshold = PCMOUT_PLAY_WM; | ||
158 | pcmbuf_read = pcmbuf_written = 0; | ||
159 | pcmbuf_head = pcmbuf_tail = pcm_buffer; | ||
160 | |||
161 | rb->pcm_play_unlock(); | ||
162 | } | ||
163 | |||
164 | /* Seek the reference clock to the specified time - next audio data ready to | ||
165 | go to DMA should be on the buffer with the same time index or else the PCM | ||
166 | buffer should be empty */ | ||
167 | void pcm_output_set_clock(uint32_t time) | ||
168 | { | ||
169 | rb->pcm_play_lock(); | ||
170 | |||
171 | clock_base = time; | ||
172 | clock_start = time; | ||
173 | clock_adjust = 0; | ||
174 | |||
175 | rb->pcm_play_unlock(); | ||
176 | } | ||
177 | |||
178 | uint32_t pcm_output_get_clock(void) | ||
179 | { | ||
180 | return clock_base + clock_adjust | ||
181 | - (rb->pcm_get_bytes_waiting() >> 2); | ||
182 | } | ||
183 | |||
184 | uint32_t pcm_output_get_ticks(uint32_t *start) | ||
185 | { | ||
186 | if (start) | ||
187 | *start = clock_start; | ||
188 | |||
189 | return clock_base - (rb->pcm_get_bytes_waiting() >> 2); | ||
190 | } | ||
191 | |||
192 | /* Pauses/Starts pcm playback - and the clock */ | ||
193 | void pcm_output_play_pause(bool play) | ||
194 | { | ||
195 | rb->pcm_play_lock(); | ||
196 | |||
197 | if (rb->pcm_is_playing()) | ||
198 | { | ||
199 | rb->pcm_play_pause(play); | ||
200 | } | ||
201 | else if (play) | ||
202 | { | ||
203 | rb->pcm_play_data(get_more, NULL, 0); | ||
204 | } | ||
205 | |||
206 | rb->pcm_play_unlock(); | ||
207 | } | ||
208 | |||
209 | /* Stops all playback and resets the clock */ | ||
210 | void pcm_output_stop(void) | ||
211 | { | ||
212 | rb->pcm_play_lock(); | ||
213 | |||
214 | if (rb->pcm_is_playing()) | ||
215 | rb->pcm_play_stop(); | ||
216 | |||
217 | pcm_output_flush(); | ||
218 | pcm_output_set_clock(0); | ||
219 | |||
220 | rb->pcm_play_unlock(); | ||
221 | } | ||
222 | |||
223 | /* Drains any data if the start threshold hasn't been reached */ | ||
224 | void pcm_output_drain(void) | ||
225 | { | ||
226 | rb->pcm_play_lock(); | ||
227 | pcmbuf_threshold = PCMOUT_LOW_WM; | ||
228 | rb->pcm_play_unlock(); | ||
229 | } | ||
230 | |||
231 | bool pcm_output_init(void) | ||
232 | { | ||
233 | pcm_buffer = mpeg_malloc(PCMOUT_ALLOC_SIZE, MPEG_ALLOC_PCMOUT); | ||
234 | if (pcm_buffer == NULL) | ||
235 | return false; | ||
236 | |||
237 | pcmbuf_threshold = PCMOUT_PLAY_WM; | ||
238 | pcmbuf_head = pcm_buffer; | ||
239 | pcmbuf_tail = pcm_buffer; | ||
240 | pcmbuf_end = SKIPBYTES(pcm_buffer, PCMOUT_BUFSIZE); | ||
241 | pcmbuf_read = 0; | ||
242 | pcmbuf_written = 0; | ||
243 | |||
244 | rb->pcm_set_frequency(SAMPR_44); | ||
245 | |||
246 | #if INPUT_SRC_CAPS != 0 | ||
247 | /* Select playback */ | ||
248 | rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); | ||
249 | rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); | ||
250 | #endif | ||
251 | |||
252 | #if SILENCE_TEST_TONE | ||
253 | /* Make the silence clip a square wave */ | ||
254 | const int16_t silence_amp = 32767 / 16; | ||
255 | unsigned i; | ||
256 | |||
257 | for (i = 0; i < ARRAYLEN(silence); i += 2) | ||
258 | { | ||
259 | if (i < ARRAYLEN(silence)/2) | ||
260 | { | ||
261 | silence[i] = silence_amp; | ||
262 | silence[i+1] = silence_amp; | ||
263 | } | ||
264 | else | ||
265 | { | ||
266 | silence[i] = -silence_amp; | ||
267 | silence[i+1] = -silence_amp; | ||
268 | } | ||
269 | } | ||
270 | #endif | ||
271 | |||
272 | return true; | ||
273 | } | ||
274 | |||
275 | void pcm_output_exit(void) | ||
276 | { | ||
277 | rb->pcm_set_frequency(HW_SAMPR_DEFAULT); | ||
278 | } | ||