summaryrefslogtreecommitdiff
path: root/apps/plugins/mpegplayer/pcm_output.c
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2007-12-29 19:46:35 +0000
committerMichael Sevakis <jethead71@rockbox.org>2007-12-29 19:46:35 +0000
commita222f27c4a17ed8f9809cda7861fe5f23d4cc0c1 (patch)
treed393a23d83549f99772bb156e59ffb88725148b6 /apps/plugins/mpegplayer/pcm_output.c
parent1d0f6b90ff43776e55b4b9f062c9bea3f99aa768 (diff)
downloadrockbox-a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1.tar.gz
rockbox-a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1.zip
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
Diffstat (limited to 'apps/plugins/mpegplayer/pcm_output.c')
-rw-r--r--apps/plugins/mpegplayer/pcm_output.c278
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 */
27static struct pcm_frame_header * ALIGNED_ATTR(4) pcm_buffer;
28/* End of buffer (not guard) */
29static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_end;
30/* Read pointer */
31static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_head IBSS_ATTR;
32/* Write pointer */
33static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_tail IBSS_ATTR;
34
35/* Bytes */
36static uint64_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */
37static uint64_t pcmbuf_written IBSS_ATTR; /* Number of bytes written by source */
38static ssize_t pcmbuf_threshold IBSS_ATTR; /* Non-silence threshold */
39
40/* Clock */
41static uint32_t clock_base IBSS_ATTR; /* Our base clock */
42static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */
43static int32_t clock_adjust IBSS_ATTR; /* Clock drift adjustment */
44
45/* Small silence clip. ~5.80ms @ 44.1kHz */
46static int16_t silence[256*2] ALIGNED_ATTR(4) = { 0 };
47
48/* Advance a PCM buffer pointer by size bytes circularly */
49static 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 */
58inline ssize_t pcm_output_used(void)
59{
60 return (ssize_t)(pcmbuf_written - pcmbuf_read);
61}
62
63inline ssize_t pcm_output_free(void)
64{
65 return (ssize_t)(PCMOUT_BUFSIZE - pcmbuf_written + pcmbuf_read);
66}
67
68/* Audio DMA handler */
69static 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
140struct pcm_frame_header * pcm_output_get_buffer(void)
141{
142 return pcmbuf_tail;
143}
144
145void 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 */
153void 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 */
167void 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
178uint32_t pcm_output_get_clock(void)
179{
180 return clock_base + clock_adjust
181 - (rb->pcm_get_bytes_waiting() >> 2);
182}
183
184uint32_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 */
193void 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 */
210void 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 */
224void pcm_output_drain(void)
225{
226 rb->pcm_play_lock();
227 pcmbuf_threshold = PCMOUT_LOW_WM;
228 rb->pcm_play_unlock();
229}
230
231bool 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
275void pcm_output_exit(void)
276{
277 rb->pcm_set_frequency(HW_SAMPR_DEFAULT);
278}