diff options
Diffstat (limited to 'apps/plugins/mpegplayer/pcm_output.c')
-rw-r--r-- | apps/plugins/mpegplayer/pcm_output.c | 150 |
1 files changed, 111 insertions, 39 deletions
diff --git a/apps/plugins/mpegplayer/pcm_output.c b/apps/plugins/mpegplayer/pcm_output.c index a5d8f86e5b..0b8ad7701a 100644 --- a/apps/plugins/mpegplayer/pcm_output.c +++ b/apps/plugins/mpegplayer/pcm_output.c | |||
@@ -36,21 +36,30 @@ static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_tail IBSS_ATTR; | |||
36 | 36 | ||
37 | /* Bytes */ | 37 | /* Bytes */ |
38 | static ssize_t pcmbuf_curr_size IBSS_ATTR; /* Size of currently playing frame */ | 38 | static ssize_t pcmbuf_curr_size IBSS_ATTR; /* Size of currently playing frame */ |
39 | static uint64_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */ | 39 | static ssize_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */ |
40 | static uint64_t pcmbuf_written IBSS_ATTR; /* Number of bytes written by source */ | 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 */ | 41 | static ssize_t pcmbuf_threshold IBSS_ATTR; /* Non-silence threshold */ |
42 | 42 | ||
43 | /* Clock */ | 43 | /* Clock */ |
44 | static uint32_t clock_base IBSS_ATTR; /* Our base clock */ | ||
45 | static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */ | 44 | static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */ |
46 | static int32_t clock_adjust IBSS_ATTR; /* Clock drift adjustment */ | 45 | static uint32_t volatile clock_tick IBSS_ATTR; /* Our base clock */ |
46 | static uint32_t volatile clock_time IBSS_ATTR; /* Timestamp adjusted */ | ||
47 | 47 | ||
48 | int pcm_skipped = 0; | 48 | static int pcm_skipped = 0; |
49 | int pcm_underruns = 0; | 49 | static int pcm_underruns = 0; |
50 | 50 | ||
51 | /* Small silence clip. ~5.80ms @ 44.1kHz */ | 51 | /* Small silence clip. ~5.80ms @ 44.1kHz */ |
52 | static int16_t silence[256*2] ALIGNED_ATTR(4) = { 0 }; | 52 | static int16_t silence[256*2] ALIGNED_ATTR(4) = { 0 }; |
53 | 53 | ||
54 | /* Delete all buffer contents */ | ||
55 | static void pcm_reset_buffer(void) | ||
56 | { | ||
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; | ||
61 | } | ||
62 | |||
54 | /* Advance a PCM buffer pointer by size bytes circularly */ | 63 | /* Advance a PCM buffer pointer by size bytes circularly */ |
55 | static inline void pcm_advance_buffer(struct pcm_frame_header **p, | 64 | static inline void pcm_advance_buffer(struct pcm_frame_header **p, |
56 | size_t size) | 65 | size_t size) |
@@ -60,15 +69,16 @@ static inline void pcm_advance_buffer(struct pcm_frame_header **p, | |||
60 | *p = SKIPBYTES(*p, -PCMOUT_BUFSIZE); | 69 | *p = SKIPBYTES(*p, -PCMOUT_BUFSIZE); |
61 | } | 70 | } |
62 | 71 | ||
63 | /* Inline internally but not externally */ | 72 | /* Return physical space used */ |
64 | inline ssize_t pcm_output_used(void) | 73 | static inline ssize_t pcm_output_bytes_used(void) |
65 | { | 74 | { |
66 | return (ssize_t)(pcmbuf_written - pcmbuf_read); | 75 | return pcmbuf_written - pcmbuf_read; /* wrap-safe */ |
67 | } | 76 | } |
68 | 77 | ||
69 | inline ssize_t pcm_output_free(void) | 78 | /* Return physical space free */ |
79 | static inline ssize_t pcm_output_bytes_free(void) | ||
70 | { | 80 | { |
71 | return (ssize_t)(PCMOUT_BUFSIZE - pcmbuf_written + pcmbuf_read); | 81 | return PCMOUT_BUFSIZE - pcm_output_bytes_used(); |
72 | } | 82 | } |
73 | 83 | ||
74 | /* Audio DMA handler */ | 84 | /* Audio DMA handler */ |
@@ -80,7 +90,7 @@ static void get_more(unsigned char **start, size_t *size) | |||
80 | pcmbuf_read += pcmbuf_curr_size; | 90 | pcmbuf_read += pcmbuf_curr_size; |
81 | pcmbuf_curr_size = 0; | 91 | pcmbuf_curr_size = 0; |
82 | 92 | ||
83 | sz = pcm_output_used(); | 93 | sz = pcm_output_bytes_used(); |
84 | 94 | ||
85 | if (sz > pcmbuf_threshold) | 95 | if (sz > pcmbuf_threshold) |
86 | { | 96 | { |
@@ -89,16 +99,15 @@ static void get_more(unsigned char **start, size_t *size) | |||
89 | while (1) | 99 | while (1) |
90 | { | 100 | { |
91 | uint32_t time = pcmbuf_head->time; | 101 | uint32_t time = pcmbuf_head->time; |
92 | int32_t offset = time - (clock_base + clock_adjust); | 102 | int32_t offset = time - clock_time; |
93 | 103 | ||
94 | sz = pcmbuf_head->size; | 104 | sz = pcmbuf_head->size; |
95 | 105 | ||
96 | if (sz < (ssize_t)(sizeof(pcmbuf_head) + 4) || | 106 | if (sz < (ssize_t)(PCM_HDR_SIZE + 4) || |
97 | (sz & 3) != 0) | 107 | (sz & 3) != 0) |
98 | { | 108 | { |
99 | /* Just show a warning about this - will never happen | 109 | /* Just show a warning about this - will never happen |
100 | * without a bug in the audio thread code or a clobbered | 110 | * without a corrupted buffer */ |
101 | * buffer */ | ||
102 | DEBUGF("get_more: invalid size (%ld)\n", (long)sz); | 111 | DEBUGF("get_more: invalid size (%ld)\n", (long)sz); |
103 | } | 112 | } |
104 | 113 | ||
@@ -108,11 +117,12 @@ static void get_more(unsigned char **start, size_t *size) | |||
108 | pcm_advance_buffer(&pcmbuf_head, sz); | 117 | pcm_advance_buffer(&pcmbuf_head, sz); |
109 | pcmbuf_read += sz; | 118 | pcmbuf_read += sz; |
110 | pcm_skipped++; | 119 | pcm_skipped++; |
111 | if (pcmbuf_read < pcmbuf_written) | 120 | if (pcm_output_bytes_used() > 0) |
112 | continue; | 121 | continue; |
113 | 122 | ||
114 | /* Ran out so revert to default watermark */ | 123 | /* Ran out so revert to default watermark */ |
115 | pcmbuf_threshold = PCMOUT_PLAY_WM; | 124 | pcmbuf_threshold = PCMOUT_PLAY_WM; |
125 | pcm_underruns++; | ||
116 | } | 126 | } |
117 | else if (offset < 100*CLOCK_RATE/1000) | 127 | else if (offset < 100*CLOCK_RATE/1000) |
118 | { | 128 | { |
@@ -122,15 +132,15 @@ static void get_more(unsigned char **start, size_t *size) | |||
122 | pcm_advance_buffer(&pcmbuf_head, sz); | 132 | pcm_advance_buffer(&pcmbuf_head, sz); |
123 | pcmbuf_curr_size = sz; | 133 | pcmbuf_curr_size = sz; |
124 | 134 | ||
125 | sz -= sizeof (struct pcm_frame_header); | 135 | sz -= PCM_HDR_SIZE; |
126 | 136 | ||
127 | *size = sz; | 137 | *size = sz; |
128 | 138 | ||
129 | /* Audio is time master - keep clock synchronized */ | 139 | /* Audio is time master - keep clock synchronized */ |
130 | clock_adjust = time - clock_base; | 140 | clock_time = time + (sz >> 2); |
131 | 141 | ||
132 | /* Update base clock */ | 142 | /* Update base clock */ |
133 | clock_base += sz >> 2; | 143 | clock_tick += sz >> 2; |
134 | return; | 144 | return; |
135 | } | 145 | } |
136 | /* Frame will be dropped - play silence clip */ | 146 | /* Frame will be dropped - play silence clip */ |
@@ -150,22 +160,57 @@ static void get_more(unsigned char **start, size_t *size) | |||
150 | *start = (unsigned char *)silence; | 160 | *start = (unsigned char *)silence; |
151 | *size = sizeof (silence); | 161 | *size = sizeof (silence); |
152 | 162 | ||
153 | clock_base += sizeof (silence) / 4; | 163 | clock_tick += sizeof (silence) / 4; |
164 | clock_time += sizeof (silence) / 4; | ||
154 | 165 | ||
155 | if (pcmbuf_read > pcmbuf_written) | 166 | if (sz < 0) |
156 | pcmbuf_read = pcmbuf_written; | 167 | pcmbuf_read = pcmbuf_written; |
157 | } | 168 | } |
158 | 169 | ||
159 | struct pcm_frame_header * pcm_output_get_buffer(void) | 170 | /** Public interface **/ |
171 | |||
172 | /* Return a buffer pointer if at least size bytes are available and if so, | ||
173 | * give the actual free space */ | ||
174 | unsigned char * pcm_output_get_buffer(ssize_t *size) | ||
160 | { | 175 | { |
161 | return pcmbuf_tail; | 176 | ssize_t sz = *size; |
177 | ssize_t free = pcm_output_bytes_free() - PCM_HDR_SIZE; | ||
178 | |||
179 | if (sz >= 0 && free >= sz) | ||
180 | { | ||
181 | *size = free; /* return actual free space (- header) */ | ||
182 | return pcmbuf_tail->data; | ||
183 | } | ||
184 | |||
185 | /* Leave *size alone so caller doesn't have to reinit */ | ||
186 | return NULL; | ||
162 | } | 187 | } |
163 | 188 | ||
164 | void pcm_output_add_data(void) | 189 | /* Commit the buffer returned by pcm_ouput_get_buffer; timestamp is PCM |
190 | * clock time units, not video format time units */ | ||
191 | bool pcm_output_commit_data(ssize_t size, uint32_t timestamp) | ||
165 | { | 192 | { |
166 | size_t size = pcmbuf_tail->size; | 193 | if (size <= 0 || (size & 3)) |
194 | return false; /* invalid */ | ||
195 | |||
196 | size += PCM_HDR_SIZE; | ||
197 | |||
198 | if (size > pcm_output_bytes_free()) | ||
199 | return false; /* too big */ | ||
200 | |||
201 | pcmbuf_tail->size = size; | ||
202 | pcmbuf_tail->time = timestamp; | ||
203 | |||
167 | pcm_advance_buffer(&pcmbuf_tail, size); | 204 | pcm_advance_buffer(&pcmbuf_tail, size); |
168 | pcmbuf_written += size; | 205 | pcmbuf_written += size; |
206 | |||
207 | return true; | ||
208 | } | ||
209 | |||
210 | /* Returns 'true' if the buffer is completely empty */ | ||
211 | bool pcm_output_empty(void) | ||
212 | { | ||
213 | return pcm_output_bytes_used() <= 0; | ||
169 | } | 214 | } |
170 | 215 | ||
171 | /* Flushes the buffer - clock keeps counting */ | 216 | /* Flushes the buffer - clock keeps counting */ |
@@ -182,10 +227,7 @@ void pcm_output_flush(void) | |||
182 | if (playing) | 227 | if (playing) |
183 | rb->pcm_play_stop(); | 228 | rb->pcm_play_stop(); |
184 | 229 | ||
185 | pcmbuf_threshold = PCMOUT_PLAY_WM; | 230 | pcm_reset_buffer(); |
186 | pcmbuf_curr_size = pcmbuf_read = pcmbuf_written = 0; | ||
187 | pcmbuf_head = pcmbuf_tail = pcm_buffer; | ||
188 | pcm_skipped = pcm_underruns = 0; | ||
189 | 231 | ||
190 | /* Restart if playing state was current */ | 232 | /* Restart if playing state was current */ |
191 | if (playing && !paused) | 233 | if (playing && !paused) |
@@ -201,25 +243,54 @@ void pcm_output_set_clock(uint32_t time) | |||
201 | { | 243 | { |
202 | rb->pcm_play_lock(); | 244 | rb->pcm_play_lock(); |
203 | 245 | ||
204 | clock_base = time; | ||
205 | clock_start = time; | 246 | clock_start = time; |
206 | clock_adjust = 0; | 247 | clock_tick = time; |
248 | clock_time = time; | ||
207 | 249 | ||
208 | rb->pcm_play_unlock(); | 250 | rb->pcm_play_unlock(); |
209 | } | 251 | } |
210 | 252 | ||
253 | /* Return the clock as synchronized by audio frame timestamps */ | ||
211 | uint32_t pcm_output_get_clock(void) | 254 | uint32_t pcm_output_get_clock(void) |
212 | { | 255 | { |
213 | return clock_base + clock_adjust | 256 | uint32_t time, rem; |
214 | - (rb->pcm_get_bytes_waiting() >> 2); | 257 | |
258 | /* Reread if data race detected - rem will be 0 if driver hasn't yet | ||
259 | * updated to the new buffer size. Also be sure pcm state doesn't | ||
260 | * cause indefinite loop. | ||
261 | * | ||
262 | * FYI: NOT scrutinized for rd/wr reordering on different cores. */ | ||
263 | do | ||
264 | { | ||
265 | time = clock_time; | ||
266 | rem = rb->pcm_get_bytes_waiting() >> 2; | ||
267 | } | ||
268 | while (UNLIKELY(time != clock_time || | ||
269 | (rem == 0 && rb->pcm_is_playing() && !rb->pcm_is_paused()))); | ||
270 | |||
271 | return time - rem; | ||
272 | |||
215 | } | 273 | } |
216 | 274 | ||
275 | /* Return the raw clock as counted from the last pcm_output_set_clock | ||
276 | * call */ | ||
217 | uint32_t pcm_output_get_ticks(uint32_t *start) | 277 | uint32_t pcm_output_get_ticks(uint32_t *start) |
218 | { | 278 | { |
279 | uint32_t tick, rem; | ||
280 | |||
281 | /* Same procedure as pcm_output_get_clock */ | ||
282 | do | ||
283 | { | ||
284 | tick = clock_tick; | ||
285 | rem = rb->pcm_get_bytes_waiting() >> 2; | ||
286 | } | ||
287 | while (UNLIKELY(tick != clock_tick || | ||
288 | (rem == 0 && rb->pcm_is_playing() && !rb->pcm_is_paused()))); | ||
289 | |||
219 | if (start) | 290 | if (start) |
220 | *start = clock_start; | 291 | *start = clock_start; |
221 | 292 | ||
222 | return clock_base - (rb->pcm_get_bytes_waiting() >> 2); | 293 | return tick - rem; |
223 | } | 294 | } |
224 | 295 | ||
225 | /* Pauses/Starts pcm playback - and the clock */ | 296 | /* Pauses/Starts pcm playback - and the clock */ |
@@ -267,12 +338,13 @@ bool pcm_output_init(void) | |||
267 | if (pcm_buffer == NULL) | 338 | if (pcm_buffer == NULL) |
268 | return false; | 339 | return false; |
269 | 340 | ||
270 | pcmbuf_threshold = PCMOUT_PLAY_WM; | ||
271 | pcmbuf_head = pcm_buffer; | ||
272 | pcmbuf_tail = pcm_buffer; | ||
273 | pcmbuf_end = SKIPBYTES(pcm_buffer, PCMOUT_BUFSIZE); | 341 | pcmbuf_end = SKIPBYTES(pcm_buffer, PCMOUT_BUFSIZE); |
274 | pcmbuf_curr_size = pcmbuf_read = pcmbuf_written = 0; | ||
275 | 342 | ||
343 | pcm_reset_buffer(); | ||
344 | |||
345 | /* Some targets could play at the movie frequency without resampling but | ||
346 | * as of now DSP assumes a certain frequency (always 44100Hz) so | ||
347 | * resampling will be needed for other movie audio rates. */ | ||
276 | rb->pcm_set_frequency(NATIVE_FREQUENCY); | 348 | rb->pcm_set_frequency(NATIVE_FREQUENCY); |
277 | 349 | ||
278 | #if INPUT_SRC_CAPS != 0 | 350 | #if INPUT_SRC_CAPS != 0 |