diff options
author | Michael Sevakis <jethead71@rockbox.org> | 2011-04-27 03:08:23 +0000 |
---|---|---|
committer | Michael Sevakis <jethead71@rockbox.org> | 2011-04-27 03:08:23 +0000 |
commit | c537d5958e8b421ac4f9bef6c8b9e7425a6cf167 (patch) | |
tree | 7ed36518fb6524da7bbd913ba7619b85b5d15d23 /apps/codec_thread.c | |
parent | dcf0f8de4a37ff1d2ea510aef75fa67977a8bdcc (diff) | |
download | rockbox-c537d5958e8b421ac4f9bef6c8b9e7425a6cf167.tar.gz rockbox-c537d5958e8b421ac4f9bef6c8b9e7425a6cf167.zip |
Commit FS#12069 - Playback rework - first stages. Gives as thorough as possible a treatment of codec management, track change and metadata logic as possible while maintaining fairly narrow focus and not rewriting everything all at once. Please see the rockbox-dev mail archive on 2011-04-25 (Playback engine rework) for a more thorough manifest of what was addressed. Plugins and codecs become incompatible.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29785 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/codec_thread.c')
-rw-r--r-- | apps/codec_thread.c | 679 |
1 files changed, 386 insertions, 293 deletions
diff --git a/apps/codec_thread.c b/apps/codec_thread.c index 65a7ebc7d5..7cf45c3490 100644 --- a/apps/codec_thread.c +++ b/apps/codec_thread.c | |||
@@ -9,6 +9,7 @@ | |||
9 | * | 9 | * |
10 | * Copyright (C) 2005-2007 Miika Pekkarinen | 10 | * Copyright (C) 2005-2007 Miika Pekkarinen |
11 | * Copyright (C) 2007-2008 Nicolas Pennequin | 11 | * Copyright (C) 2007-2008 Nicolas Pennequin |
12 | * Copyright (C) 2011 Michael Sevakis | ||
12 | * | 13 | * |
13 | * This program is free software; you can redistribute it and/or | 14 | * This program is free software; you can redistribute it and/or |
14 | * modify it under the terms of the GNU General Public License | 15 | * modify it under the terms of the GNU General Public License |
@@ -21,16 +22,14 @@ | |||
21 | ****************************************************************************/ | 22 | ****************************************************************************/ |
22 | #include "config.h" | 23 | #include "config.h" |
23 | #include "system.h" | 24 | #include "system.h" |
24 | #include "playback.h" | ||
25 | #include "codec_thread.h" | ||
26 | #include "kernel.h" | 25 | #include "kernel.h" |
27 | #include "codecs.h" | 26 | #include "codecs.h" |
28 | #include "buffering.h" | 27 | #include "codec_thread.h" |
29 | #include "pcmbuf.h" | 28 | #include "pcmbuf.h" |
29 | #include "playback.h" | ||
30 | #include "buffering.h" | ||
30 | #include "dsp.h" | 31 | #include "dsp.h" |
31 | #include "abrepeat.h" | ||
32 | #include "metadata.h" | 32 | #include "metadata.h" |
33 | #include "splash.h" | ||
34 | 33 | ||
35 | /* Define LOGF_ENABLE to enable logf output in this file */ | 34 | /* Define LOGF_ENABLE to enable logf output in this file */ |
36 | /*#define LOGF_ENABLE*/ | 35 | /*#define LOGF_ENABLE*/ |
@@ -57,38 +56,45 @@ | |||
57 | #define LOGFQUEUE_SYS_TIMEOUT(...) | 56 | #define LOGFQUEUE_SYS_TIMEOUT(...) |
58 | #endif | 57 | #endif |
59 | 58 | ||
60 | |||
61 | /* Variables are commented with the threads that use them: | 59 | /* Variables are commented with the threads that use them: |
62 | * A=audio, C=codec, V=voice. A suffix of - indicates that | 60 | * A=audio, C=codec |
63 | * the variable is read but not updated on that thread. | 61 | * - = reads only |
64 | 62 | * | |
65 | * Unless otherwise noted, the extern variables are located | 63 | * Unless otherwise noted, the extern variables are located |
66 | * in playback.c. | 64 | * in playback.c. |
67 | */ | 65 | */ |
68 | 66 | ||
69 | /* Main state control */ | 67 | /* Q_LOAD_CODEC parameter data */ |
70 | 68 | struct codec_load_info | |
71 | /* Type of codec loaded? (C/A) */ | 69 | { |
72 | static int current_codectype SHAREDBSS_ATTR = AFMT_UNKNOWN; | 70 | int hid; /* audio handle id (specify < 0 to use afmt) */ |
71 | int afmt; /* codec specification (AFMT_*) */ | ||
72 | }; | ||
73 | 73 | ||
74 | extern struct mp3entry *thistrack_id3, /* the currently playing track */ | ||
75 | *othertrack_id3; /* prev track during track-change-transition, or end of playlist, | ||
76 | * next track otherwise */ | ||
77 | 74 | ||
78 | /* Track change controls */ | 75 | /** --- Main state control --- **/ |
79 | extern struct event_queue audio_queue SHAREDBSS_ATTR; | ||
80 | 76 | ||
77 | static int codec_type = AFMT_UNKNOWN; /* Codec type (C,A-) */ | ||
81 | 78 | ||
79 | /* Private interfaces to main playback control */ | ||
80 | extern void audio_codec_update_elapsed(unsigned long value); | ||
81 | extern void audio_codec_update_offset(size_t value); | ||
82 | extern void audio_queue_post(long id, intptr_t data); | ||
82 | extern struct codec_api ci; /* from codecs.c */ | 83 | extern struct codec_api ci; /* from codecs.c */ |
83 | 84 | ||
84 | /* Codec thread */ | 85 | /* Codec thread */ |
85 | static unsigned int codec_thread_id; /* For modifying thread priority later */ | 86 | static unsigned int codec_thread_id; /* For modifying thread priority later */ |
86 | static struct event_queue codec_queue SHAREDBSS_ATTR; | 87 | static struct event_queue codec_queue SHAREDBSS_ATTR; |
87 | static struct queue_sender_list codec_queue_sender_list SHAREDBSS_ATTR; | 88 | static struct queue_sender_list codec_queue_sender_list SHAREDBSS_ATTR; |
88 | static long codec_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)] | 89 | static long codec_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)] IBSS_ATTR; |
89 | IBSS_ATTR; | ||
90 | static const char codec_thread_name[] = "codec"; | 90 | static const char codec_thread_name[] = "codec"; |
91 | 91 | ||
92 | static void unload_codec(void); | ||
93 | |||
94 | /* Messages are only ever sent one at a time to the codec from the audio | ||
95 | thread. This is important for correct operation unless playback is | ||
96 | stopped. */ | ||
97 | |||
92 | /* static routines */ | 98 | /* static routines */ |
93 | static void codec_queue_ack(intptr_t ackme) | 99 | static void codec_queue_ack(intptr_t ackme) |
94 | { | 100 | { |
@@ -100,52 +106,63 @@ static intptr_t codec_queue_send(long id, intptr_t data) | |||
100 | return queue_send(&codec_queue, id, data); | 106 | return queue_send(&codec_queue, id, data); |
101 | } | 107 | } |
102 | 108 | ||
103 | /**************************************/ | 109 | /* Poll the state of the codec queue. Returns < 0 if the message is urgent |
110 | and any state should exit, > 0 if it's a run message (and it was | ||
111 | scrubbed), 0 if message was ignored. */ | ||
112 | static int codec_check_queue__have_msg(void) | ||
113 | { | ||
114 | struct queue_event ev; | ||
104 | 115 | ||
105 | /** misc external functions */ | 116 | queue_peek(&codec_queue, &ev); |
106 | 117 | ||
107 | /* Used to check whether a new codec must be loaded. See array audio_formats[] | 118 | /* Seek, pause or stop? Just peek and return if so. Codec |
108 | * in metadata.c */ | 119 | must handle the command after returing. Inserts will not |
109 | int get_codec_base_type(int type) | 120 | be allowed until it complies. */ |
110 | { | 121 | switch (ev.id) |
111 | int base_type = type; | 122 | { |
112 | switch (type) { | 123 | case Q_CODEC_SEEK: |
113 | case AFMT_MPA_L1: | 124 | LOGFQUEUE("codec - Q_CODEC_SEEK", ev.id); |
114 | case AFMT_MPA_L2: | 125 | return -1; |
115 | case AFMT_MPA_L3: | 126 | case Q_CODEC_PAUSE: |
116 | base_type = AFMT_MPA_L3; | 127 | LOGFQUEUE("codec - Q_CODEC_PAUSE", ev.id); |
117 | break; | 128 | return -1; |
118 | case AFMT_MPC_SV7: | 129 | case Q_CODEC_STOP: |
119 | case AFMT_MPC_SV8: | 130 | LOGFQUEUE("codec - Q_CODEC_STOP", ev.id); |
120 | base_type = AFMT_MPC_SV7; | 131 | return -1; |
121 | break; | ||
122 | case AFMT_MP4_AAC: | ||
123 | case AFMT_MP4_AAC_HE: | ||
124 | base_type = AFMT_MP4_AAC; | ||
125 | break; | ||
126 | case AFMT_SAP: | ||
127 | case AFMT_CMC: | ||
128 | case AFMT_CM3: | ||
129 | case AFMT_CMR: | ||
130 | case AFMT_CMS: | ||
131 | case AFMT_DMC: | ||
132 | case AFMT_DLT: | ||
133 | case AFMT_MPT: | ||
134 | case AFMT_MPD: | ||
135 | case AFMT_RMT: | ||
136 | case AFMT_TMC: | ||
137 | case AFMT_TM8: | ||
138 | case AFMT_TM2: | ||
139 | base_type = AFMT_SAP; | ||
140 | break; | ||
141 | default: | ||
142 | break; | ||
143 | } | 132 | } |
144 | 133 | ||
145 | return base_type; | 134 | /* This is in error in this context unless it's "go, go, go!" */ |
135 | queue_wait(&codec_queue, &ev); | ||
136 | |||
137 | if (ev.id == Q_CODEC_RUN) | ||
138 | { | ||
139 | logf("codec < Q_CODEC_RUN: already running!"); | ||
140 | codec_queue_ack(Q_CODEC_RUN); | ||
141 | return 1; | ||
142 | } | ||
143 | |||
144 | /* Ignore it */ | ||
145 | logf("codec < bad req %ld (%s)", ev.id, __func__); | ||
146 | codec_queue_ack(Q_NULL); | ||
147 | return 0; | ||
148 | } | ||
149 | |||
150 | /* Does the audio format type equal CODEC_TYPE_ENCODER? */ | ||
151 | static inline bool type_is_encoder(int afmt) | ||
152 | { | ||
153 | #ifdef AUDIO_HAVE_RECORDING | ||
154 | return (afmt & CODEC_TYPE_MASK) == CODEC_TYPE_ENCODER; | ||
155 | #else | ||
156 | return false; | ||
157 | (void)afmt; | ||
158 | #endif | ||
146 | } | 159 | } |
147 | 160 | ||
148 | const char *get_codec_filename(int cod_spec) | 161 | /**************************************/ |
162 | |||
163 | |||
164 | /** --- Miscellaneous external functions --- **/ | ||
165 | const char * get_codec_filename(int cod_spec) | ||
149 | { | 166 | { |
150 | const char *fname; | 167 | const char *fname; |
151 | 168 | ||
@@ -173,7 +190,7 @@ const char *get_codec_filename(int cod_spec) | |||
173 | #endif /* HAVE_RECORDING */ | 190 | #endif /* HAVE_RECORDING */ |
174 | 191 | ||
175 | return fname; | 192 | return fname; |
176 | } /* get_codec_filename */ | 193 | } |
177 | 194 | ||
178 | /* Borrow the codec thread and return the ID */ | 195 | /* Borrow the codec thread and return the ID */ |
179 | void codec_thread_do_callback(void (*fn)(void), unsigned int *id) | 196 | void codec_thread_do_callback(void (*fn)(void), unsigned int *id) |
@@ -189,9 +206,9 @@ void codec_thread_do_callback(void (*fn)(void), unsigned int *id) | |||
189 | } | 206 | } |
190 | 207 | ||
191 | 208 | ||
192 | /** codec API callbacks */ | 209 | /** --- codec API callbacks --- **/ |
193 | 210 | ||
194 | static void* codec_get_buffer(size_t *size) | 211 | static void * codec_get_buffer(size_t *size) |
195 | { | 212 | { |
196 | ssize_t s = CODEC_SIZE - codec_size; | 213 | ssize_t s = CODEC_SIZE - codec_size; |
197 | void *buf = &codecbuf[codec_size]; | 214 | void *buf = &codecbuf[codec_size]; |
@@ -215,15 +232,19 @@ static void codec_pcmbuf_insert_callback( | |||
215 | int inp_count; | 232 | int inp_count; |
216 | char *dest; | 233 | char *dest; |
217 | 234 | ||
218 | /* Prevent audio from a previous track from playing */ | 235 | while (1) |
219 | if (ci.new_track || ci.stop_codec) | ||
220 | return; | ||
221 | |||
222 | while ((dest = pcmbuf_request_buffer(&out_count)) == NULL) | ||
223 | { | 236 | { |
237 | if ((dest = pcmbuf_request_buffer(&out_count)) != NULL) | ||
238 | break; | ||
239 | |||
224 | cancel_cpu_boost(); | 240 | cancel_cpu_boost(); |
225 | sleep(1); | 241 | |
226 | if (ci.seek_time || ci.new_track || ci.stop_codec) | 242 | /* It will be awhile before space is available but we want |
243 | "instant" response to any message */ | ||
244 | queue_wait_w_tmo(&codec_queue, NULL, HZ/20); | ||
245 | |||
246 | if (!queue_empty(&codec_queue) && | ||
247 | codec_check_queue__have_msg() < 0) | ||
227 | return; | 248 | return; |
228 | } | 249 | } |
229 | 250 | ||
@@ -247,62 +268,28 @@ static void codec_pcmbuf_insert_callback( | |||
247 | 268 | ||
248 | count -= inp_count; | 269 | count -= inp_count; |
249 | } | 270 | } |
250 | } /* codec_pcmbuf_insert_callback */ | 271 | } |
251 | 272 | ||
252 | static void codec_set_elapsed_callback(unsigned long value) | 273 | /* helper function, not a callback */ |
274 | static bool codec_advance_buffer_counters(size_t amount) | ||
253 | { | 275 | { |
254 | if (ci.seek_time) | 276 | if (bufadvance(ci.audio_hid, amount) < 0) |
255 | return; | ||
256 | |||
257 | #ifdef AB_REPEAT_ENABLE | ||
258 | ab_position_report(value); | ||
259 | #endif | ||
260 | |||
261 | unsigned long latency = pcmbuf_get_latency(); | ||
262 | if (value < latency) | ||
263 | thistrack_id3->elapsed = 0; | ||
264 | else | ||
265 | { | 277 | { |
266 | unsigned long elapsed = value - latency; | 278 | ci.curpos = ci.filesize; |
267 | if (elapsed > thistrack_id3->elapsed || | 279 | return false; |
268 | elapsed < thistrack_id3->elapsed - 2) | ||
269 | { | ||
270 | thistrack_id3->elapsed = elapsed; | ||
271 | } | ||
272 | } | 280 | } |
273 | } | ||
274 | |||
275 | static void codec_set_offset_callback(size_t value) | ||
276 | { | ||
277 | if (ci.seek_time) | ||
278 | return; | ||
279 | 281 | ||
280 | unsigned long latency = pcmbuf_get_latency() * thistrack_id3->bitrate / 8; | ||
281 | if (value < latency) | ||
282 | thistrack_id3->offset = 0; | ||
283 | else | ||
284 | thistrack_id3->offset = value - latency; | ||
285 | } | ||
286 | |||
287 | /* helper function, not a callback */ | ||
288 | static void codec_advance_buffer_counters(size_t amount) | ||
289 | { | ||
290 | bufadvance(get_audio_hid(), amount); | ||
291 | ci.curpos += amount; | 282 | ci.curpos += amount; |
283 | return true; | ||
292 | } | 284 | } |
293 | 285 | ||
294 | /* copy up-to size bytes into ptr and return the actual size copied */ | 286 | /* copy up-to size bytes into ptr and return the actual size copied */ |
295 | static size_t codec_filebuf_callback(void *ptr, size_t size) | 287 | static size_t codec_filebuf_callback(void *ptr, size_t size) |
296 | { | 288 | { |
297 | ssize_t copy_n; | 289 | ssize_t copy_n = bufread(ci.audio_hid, size, ptr); |
298 | |||
299 | if (ci.stop_codec) | ||
300 | return 0; | ||
301 | |||
302 | copy_n = bufread(get_audio_hid(), size, ptr); | ||
303 | 290 | ||
304 | /* Nothing requested OR nothing left */ | 291 | /* Nothing requested OR nothing left */ |
305 | if (copy_n == 0) | 292 | if (copy_n <= 0) |
306 | return 0; | 293 | return 0; |
307 | 294 | ||
308 | /* Update read and other position pointers */ | 295 | /* Update read and other position pointers */ |
@@ -310,15 +297,15 @@ static size_t codec_filebuf_callback(void *ptr, size_t size) | |||
310 | 297 | ||
311 | /* Return the actual amount of data copied to the buffer */ | 298 | /* Return the actual amount of data copied to the buffer */ |
312 | return copy_n; | 299 | return copy_n; |
313 | } /* codec_filebuf_callback */ | 300 | } |
314 | 301 | ||
315 | static void* codec_request_buffer_callback(size_t *realsize, size_t reqsize) | 302 | static void * codec_request_buffer_callback(size_t *realsize, size_t reqsize) |
316 | { | 303 | { |
317 | size_t copy_n = reqsize; | 304 | size_t copy_n = reqsize; |
318 | ssize_t ret; | 305 | ssize_t ret; |
319 | void *ptr; | 306 | void *ptr; |
320 | 307 | ||
321 | ret = bufgetdata(get_audio_hid(), reqsize, &ptr); | 308 | ret = bufgetdata(ci.audio_hid, reqsize, &ptr); |
322 | if (ret >= 0) | 309 | if (ret >= 0) |
323 | copy_n = MIN((size_t)ret, reqsize); | 310 | copy_n = MIN((size_t)ret, reqsize); |
324 | else | 311 | else |
@@ -329,101 +316,103 @@ static void* codec_request_buffer_callback(size_t *realsize, size_t reqsize) | |||
329 | 316 | ||
330 | *realsize = copy_n; | 317 | *realsize = copy_n; |
331 | return ptr; | 318 | return ptr; |
332 | } /* codec_request_buffer_callback */ | 319 | } |
333 | 320 | ||
334 | static void codec_advance_buffer_callback(size_t amount) | 321 | static void codec_advance_buffer_callback(size_t amount) |
335 | { | 322 | { |
336 | codec_advance_buffer_counters(amount); | 323 | if (!codec_advance_buffer_counters(amount)) |
337 | codec_set_offset_callback(ci.curpos); | 324 | return; |
325 | |||
326 | audio_codec_update_offset(ci.curpos); | ||
338 | } | 327 | } |
339 | 328 | ||
340 | static bool codec_seek_buffer_callback(size_t newpos) | 329 | static bool codec_seek_buffer_callback(size_t newpos) |
341 | { | 330 | { |
342 | logf("codec_seek_buffer_callback"); | 331 | logf("codec_seek_buffer_callback"); |
343 | 332 | ||
344 | int ret = bufseek(get_audio_hid(), newpos); | 333 | int ret = bufseek(ci.audio_hid, newpos); |
345 | if (ret == 0) { | 334 | if (ret == 0) |
335 | { | ||
346 | ci.curpos = newpos; | 336 | ci.curpos = newpos; |
347 | return true; | 337 | return true; |
348 | } | 338 | } |
349 | else { | 339 | |
350 | return false; | 340 | return false; |
351 | } | ||
352 | } | 341 | } |
353 | 342 | ||
354 | static void codec_seek_complete_callback(void) | 343 | static void codec_seek_complete_callback(void) |
355 | { | 344 | { |
356 | struct queue_event ev; | ||
357 | |||
358 | logf("seek_complete"); | 345 | logf("seek_complete"); |
359 | 346 | ||
360 | /* Clear DSP */ | 347 | /* Clear DSP */ |
361 | dsp_configure(ci.dsp, DSP_FLUSH, 0); | 348 | dsp_configure(ci.dsp, DSP_FLUSH, 0); |
362 | 349 | ||
363 | /* Post notification to audio thread */ | 350 | /* Post notification to audio thread */ |
364 | LOGFQUEUE("audio > Q_AUDIO_SEEK_COMPLETE"); | 351 | LOGFQUEUE("audio > Q_AUDIO_CODEC_SEEK_COMPLETE"); |
365 | queue_post(&audio_queue, Q_AUDIO_SEEK_COMPLETE, 0); | 352 | audio_queue_post(Q_AUDIO_CODEC_SEEK_COMPLETE, 0); |
366 | |||
367 | /* Wait for ACK */ | ||
368 | queue_wait(&codec_queue, &ev); | ||
369 | 353 | ||
370 | /* ACK back in context */ | 354 | /* Wait for urgent or go message */ |
371 | codec_queue_ack(Q_AUDIO_SEEK_COMPLETE); | 355 | do |
356 | { | ||
357 | queue_wait(&codec_queue, NULL); | ||
358 | } | ||
359 | while (codec_check_queue__have_msg() == 0); | ||
372 | } | 360 | } |
373 | 361 | ||
374 | static bool codec_request_next_track_callback(void) | 362 | static void codec_configure_callback(int setting, intptr_t value) |
375 | { | 363 | { |
376 | struct queue_event ev; | 364 | if (!dsp_configure(ci.dsp, setting, value)) |
377 | 365 | { | |
378 | logf("Request new track"); | 366 | logf("Illegal key: %d", setting); |
367 | } | ||
368 | } | ||
379 | 369 | ||
380 | audio_set_prev_elapsed(thistrack_id3->elapsed); | 370 | static enum codec_command_action |
371 | codec_get_command_callback(intptr_t *param) | ||
372 | { | ||
373 | yield(); | ||
381 | 374 | ||
382 | #ifdef AB_REPEAT_ENABLE | 375 | if (LIKELY(queue_empty(&codec_queue))) |
383 | ab_end_of_track_report(); | 376 | return CODEC_ACTION_NULL; /* As you were */ |
384 | #endif | ||
385 | 377 | ||
386 | if (ci.stop_codec) | 378 | /* Process the message - return requested action and data (if any should |
379 | be expected) */ | ||
380 | while (1) | ||
387 | { | 381 | { |
388 | /* Handle ACK in outer loop */ | 382 | enum codec_command_action action = CODEC_ACTION_NULL; |
389 | LOGFQUEUE("codec: already stopping"); | 383 | struct queue_event ev; |
390 | return false; | 384 | queue_wait(&codec_queue, &ev); |
391 | } | ||
392 | 385 | ||
393 | trigger_cpu_boost(); | 386 | switch (ev.id) |
387 | { | ||
388 | case Q_CODEC_RUN: /* Already running */ | ||
389 | LOGFQUEUE("codec < Q_CODEC_RUN"); | ||
390 | break; | ||
394 | 391 | ||
395 | /* Post request to audio thread */ | 392 | case Q_CODEC_PAUSE: /* Stay here and wait */ |
396 | LOGFQUEUE("codec > audio Q_AUDIO_CHECK_NEW_TRACK"); | 393 | LOGFQUEUE("codec < Q_CODEC_PAUSE"); |
397 | queue_post(&audio_queue, Q_AUDIO_CHECK_NEW_TRACK, 0); | 394 | codec_queue_ack(Q_CODEC_PAUSE); |
395 | continue; | ||
398 | 396 | ||
399 | /* Wait for ACK */ | 397 | case Q_CODEC_SEEK: /* Audio wants codec to seek */ |
400 | queue_wait(&codec_queue, &ev); | 398 | LOGFQUEUE("codec < Q_CODEC_SEEK %ld", ev.data); |
399 | *param = ev.data; | ||
400 | action = CODEC_ACTION_SEEK_TIME; | ||
401 | break; | ||
401 | 402 | ||
402 | if (ev.data == Q_CODEC_REQUEST_COMPLETE) | 403 | case Q_CODEC_STOP: /* Must only return 0 in main loop */ |
403 | { | 404 | LOGFQUEUE("codec < Q_CODEC_STOP"); |
404 | /* Seek to the beginning of the new track because if the struct | 405 | action = CODEC_ACTION_HALT; |
405 | mp3entry was buffered, "elapsed" might not be zero (if the track has | 406 | break; |
406 | been played already but not unbuffered) */ | ||
407 | codec_seek_buffer_callback(thistrack_id3->first_frame_offset); | ||
408 | } | ||
409 | 407 | ||
410 | /* ACK back in context */ | 408 | default: /* This is in error in this context. */ |
411 | codec_queue_ack(Q_AUDIO_CHECK_NEW_TRACK); | 409 | ev.id = Q_NULL; |
410 | logf("codec bad req %ld (%s)", ev.id, __func__); | ||
411 | } | ||
412 | 412 | ||
413 | if (ev.data != Q_CODEC_REQUEST_COMPLETE || ci.stop_codec) | 413 | codec_queue_ack(ev.id); |
414 | { | 414 | return action; |
415 | LOGFQUEUE("codec <= request failed (%d)", ev.data); | ||
416 | return false; | ||
417 | } | 415 | } |
418 | |||
419 | LOGFQUEUE("codec <= Q_CODEC_REQEST_COMPLETE"); | ||
420 | return true; | ||
421 | } | ||
422 | |||
423 | static void codec_configure_callback(int setting, intptr_t value) | ||
424 | { | ||
425 | if (!dsp_configure(ci.dsp, setting, value)) | ||
426 | { logf("Illegal key:%d", setting); } | ||
427 | } | 416 | } |
428 | 417 | ||
429 | /* Initialize codec API */ | 418 | /* Initialize codec API */ |
@@ -433,119 +422,215 @@ void codec_init_codec_api(void) | |||
433 | CODEC_IDX_AUDIO); | 422 | CODEC_IDX_AUDIO); |
434 | ci.codec_get_buffer = codec_get_buffer; | 423 | ci.codec_get_buffer = codec_get_buffer; |
435 | ci.pcmbuf_insert = codec_pcmbuf_insert_callback; | 424 | ci.pcmbuf_insert = codec_pcmbuf_insert_callback; |
436 | ci.set_elapsed = codec_set_elapsed_callback; | 425 | ci.set_elapsed = audio_codec_update_elapsed; |
437 | ci.read_filebuf = codec_filebuf_callback; | 426 | ci.read_filebuf = codec_filebuf_callback; |
438 | ci.request_buffer = codec_request_buffer_callback; | 427 | ci.request_buffer = codec_request_buffer_callback; |
439 | ci.advance_buffer = codec_advance_buffer_callback; | 428 | ci.advance_buffer = codec_advance_buffer_callback; |
440 | ci.seek_buffer = codec_seek_buffer_callback; | 429 | ci.seek_buffer = codec_seek_buffer_callback; |
441 | ci.seek_complete = codec_seek_complete_callback; | 430 | ci.seek_complete = codec_seek_complete_callback; |
442 | ci.request_next_track = codec_request_next_track_callback; | 431 | ci.set_offset = audio_codec_update_offset; |
443 | ci.set_offset = codec_set_offset_callback; | ||
444 | ci.configure = codec_configure_callback; | 432 | ci.configure = codec_configure_callback; |
433 | ci.get_command = codec_get_command_callback; | ||
445 | } | 434 | } |
446 | 435 | ||
447 | 436 | ||
448 | /* track change */ | 437 | /** --- CODEC THREAD --- **/ |
449 | 438 | ||
450 | /** CODEC THREAD */ | 439 | /* Handle Q_CODEC_LOAD */ |
451 | static void codec_thread(void) | 440 | static void load_codec(const struct codec_load_info *ev_data) |
452 | { | 441 | { |
453 | struct queue_event ev; | 442 | int status = CODEC_ERROR; |
443 | /* Save a local copy so we can let the audio thread go ASAP */ | ||
444 | struct codec_load_info data = *ev_data; | ||
445 | bool const encoder = type_is_encoder(data.afmt); | ||
454 | 446 | ||
447 | if (codec_type != AFMT_UNKNOWN) | ||
448 | { | ||
449 | /* Must have unloaded it first */ | ||
450 | logf("a codec is already loaded"); | ||
451 | if (data.hid >= 0) | ||
452 | bufclose(data.hid); | ||
453 | return; | ||
454 | } | ||
455 | 455 | ||
456 | while (1) | 456 | trigger_cpu_boost(); |
457 | |||
458 | if (!encoder) | ||
457 | { | 459 | { |
458 | int status = CODEC_OK; | 460 | /* Do this now because codec may set some things up at load time */ |
459 | void *handle = NULL; | 461 | dsp_configure(ci.dsp, DSP_RESET, 0); |
460 | int hid; | 462 | } |
461 | const char *codec_fn; | 463 | |
462 | 464 | if (data.hid >= 0) | |
463 | #ifdef HAVE_CROSSFADE | 465 | { |
464 | if (!pcmbuf_is_crossfade_active()) | 466 | /* First try buffer load */ |
465 | #endif | 467 | status = codec_load_buf(data.hid, &ci); |
468 | bufclose(data.hid); | ||
469 | } | ||
470 | |||
471 | if (status < 0) | ||
472 | { | ||
473 | /* Either not a valid handle or the buffer method failed */ | ||
474 | const char *codec_fn = get_codec_filename(data.afmt); | ||
475 | if (codec_fn) | ||
466 | { | 476 | { |
467 | cancel_cpu_boost(); | 477 | #ifdef HAVE_IO_PRIORITY |
478 | buf_back_off_storage(true); | ||
479 | #endif | ||
480 | status = codec_load_file(codec_fn, &ci); | ||
481 | #ifdef HAVE_IO_PRIORITY | ||
482 | buf_back_off_storage(false); | ||
483 | #endif | ||
468 | } | 484 | } |
485 | } | ||
486 | |||
487 | if (status >= 0) | ||
488 | { | ||
489 | codec_type = data.afmt; | ||
490 | codec_queue_ack(Q_CODEC_LOAD); | ||
491 | return; | ||
492 | } | ||
493 | |||
494 | /* Failed - get rid of it */ | ||
495 | unload_codec(); | ||
496 | } | ||
497 | |||
498 | /* Handle Q_CODEC_RUN */ | ||
499 | static void run_codec(void) | ||
500 | { | ||
501 | bool const encoder = type_is_encoder(codec_type); | ||
502 | int status; | ||
503 | |||
504 | if (codec_type == AFMT_UNKNOWN) | ||
505 | { | ||
506 | logf("no codec to run"); | ||
507 | return; | ||
508 | } | ||
509 | |||
510 | codec_queue_ack(Q_CODEC_RUN); | ||
511 | |||
512 | trigger_cpu_boost(); | ||
513 | |||
514 | if (!encoder) | ||
515 | { | ||
516 | /* This will be either the initial buffered offset or where it left off | ||
517 | if it remained buffered and we're skipping back to it and it is best | ||
518 | to have ci.curpos in sync with the handle's read position - it's the | ||
519 | codec's responsibility to ensure it has the correct positions - | ||
520 | playback is sorta dumb and only has a vague idea about what to | ||
521 | buffer based upon what metadata has to say */ | ||
522 | ci.curpos = bufftell(ci.audio_hid); | ||
523 | |||
524 | /* Pin the codec's audio data in place */ | ||
525 | buf_pin_handle(ci.audio_hid, true); | ||
526 | } | ||
527 | |||
528 | status = codec_run_proc(); | ||
529 | |||
530 | if (!encoder) | ||
531 | { | ||
532 | /* Codec is done with it - let it move */ | ||
533 | buf_pin_handle(ci.audio_hid, false); | ||
534 | |||
535 | /* Notify audio that we're done for better or worse - advise of the | ||
536 | status */ | ||
537 | LOGFQUEUE("codec > audio Q_AUDIO_CODEC_COMPLETE: %d", status); | ||
538 | audio_queue_post(Q_AUDIO_CODEC_COMPLETE, status); | ||
539 | } | ||
540 | } | ||
541 | |||
542 | /* Handle Q_CODEC_SEEK */ | ||
543 | static void seek_codec(unsigned long time) | ||
544 | { | ||
545 | if (codec_type == AFMT_UNKNOWN) | ||
546 | { | ||
547 | logf("no codec to seek"); | ||
548 | codec_queue_ack(Q_CODEC_SEEK); | ||
549 | codec_seek_complete_callback(); | ||
550 | return; | ||
551 | } | ||
552 | |||
553 | /* Post it up one level */ | ||
554 | queue_post(&codec_queue, Q_CODEC_SEEK, time); | ||
555 | codec_queue_ack(Q_CODEC_SEEK); | ||
556 | |||
557 | /* Have to run it again */ | ||
558 | run_codec(); | ||
559 | } | ||
560 | |||
561 | /* Handle Q_CODEC_UNLOAD */ | ||
562 | static void unload_codec(void) | ||
563 | { | ||
564 | /* Tell codec to clean up */ | ||
565 | codec_type = AFMT_UNKNOWN; | ||
566 | codec_close(); | ||
567 | } | ||
568 | |||
569 | /* Handle Q_CODEC_DO_CALLBACK */ | ||
570 | static void do_callback(void (* callback)(void)) | ||
571 | { | ||
572 | codec_queue_ack(Q_CODEC_DO_CALLBACK); | ||
573 | |||
574 | if (callback) | ||
575 | { | ||
576 | cpucache_commit_discard(); | ||
577 | callback(); | ||
578 | cpucache_commit(); | ||
579 | } | ||
580 | } | ||
581 | |||
582 | /* Codec thread function */ | ||
583 | static void NORETURN_ATTR codec_thread(void) | ||
584 | { | ||
585 | struct queue_event ev; | ||
586 | |||
587 | while (1) | ||
588 | { | ||
589 | cancel_cpu_boost(); | ||
469 | 590 | ||
470 | queue_wait(&codec_queue, &ev); | 591 | queue_wait(&codec_queue, &ev); |
471 | 592 | ||
472 | switch (ev.id) | 593 | switch (ev.id) |
473 | { | 594 | { |
474 | case Q_CODEC_LOAD_DISK: | 595 | case Q_CODEC_LOAD: |
475 | LOGFQUEUE("codec < Q_CODEC_LOAD_DISK"); | 596 | LOGFQUEUE("codec < Q_CODEC_LOAD"); |
476 | codec_fn = get_codec_filename(ev.data); | 597 | load_codec((const struct codec_load_info *)ev.data); |
477 | if (!codec_fn) | 598 | break; |
478 | break; | ||
479 | #ifdef AUDIO_HAVE_RECORDING | ||
480 | if (ev.data & CODEC_TYPE_ENCODER) | ||
481 | { | ||
482 | ev.id = Q_ENCODER_LOAD_DISK; | ||
483 | handle = codec_load_file(codec_fn, &ci); | ||
484 | if (handle) | ||
485 | codec_queue_ack(Q_ENCODER_LOAD_DISK); | ||
486 | } | ||
487 | else | ||
488 | #endif | ||
489 | { | ||
490 | codec_queue_ack(Q_CODEC_LOAD_DISK); | ||
491 | handle = codec_load_file(codec_fn, &ci); | ||
492 | } | ||
493 | break; | ||
494 | 599 | ||
495 | case Q_CODEC_LOAD: | 600 | case Q_CODEC_RUN: |
496 | LOGFQUEUE("codec < Q_CODEC_LOAD"); | 601 | LOGFQUEUE("codec < Q_CODEC_RUN"); |
497 | codec_queue_ack(Q_CODEC_LOAD); | 602 | run_codec(); |
498 | hid = (int)ev.data; | 603 | break; |
499 | handle = codec_load_buf(hid, &ci); | ||
500 | bufclose(hid); | ||
501 | break; | ||
502 | 604 | ||
503 | case Q_CODEC_DO_CALLBACK: | 605 | case Q_CODEC_PAUSE: |
504 | LOGFQUEUE("codec < Q_CODEC_DO_CALLBACK"); | 606 | LOGFQUEUE("codec < Q_CODEC_PAUSE"); |
505 | codec_queue_ack(Q_CODEC_DO_CALLBACK); | 607 | break; |
506 | if ((void*)ev.data != NULL) | ||
507 | { | ||
508 | cpucache_commit_discard(); | ||
509 | ((void (*)(void))ev.data)(); | ||
510 | cpucache_commit(); | ||
511 | } | ||
512 | break; | ||
513 | 608 | ||
514 | default: | 609 | case Q_CODEC_SEEK: |
515 | LOGFQUEUE("codec < default : %ld", ev.id); | 610 | LOGFQUEUE("codec < Q_CODEC_SEEK: %lu", (unsigned long)ev.data); |
516 | } | 611 | seek_codec(ev.data); |
612 | break; | ||
517 | 613 | ||
518 | if (handle) | 614 | case Q_CODEC_UNLOAD: |
519 | { | 615 | LOGFQUEUE("codec < Q_CODEC_UNLOAD"); |
520 | /* Codec loaded - call the entrypoint */ | 616 | unload_codec(); |
521 | yield(); | 617 | break; |
522 | logf("codec running"); | ||
523 | status = codec_begin(handle); | ||
524 | logf("codec stopped"); | ||
525 | codec_close(handle); | ||
526 | current_codectype = AFMT_UNKNOWN; | ||
527 | |||
528 | if (ci.stop_codec) | ||
529 | status = CODEC_OK; | ||
530 | } | ||
531 | 618 | ||
532 | switch (ev.id) | 619 | case Q_CODEC_DO_CALLBACK: |
533 | { | 620 | LOGFQUEUE("codec < Q_CODEC_DO_CALLBACK"); |
534 | #ifdef AUDIO_HAVE_RECORDING | 621 | do_callback((void (*)(void))ev.data); |
535 | case Q_ENCODER_LOAD_DISK: | 622 | break; |
536 | #endif | 623 | |
537 | case Q_CODEC_LOAD_DISK: | 624 | default: |
538 | case Q_CODEC_LOAD: | 625 | LOGFQUEUE("codec < default : %ld", ev.id); |
539 | /* Notify about the status */ | ||
540 | if (!handle) | ||
541 | status = CODEC_ERROR; | ||
542 | LOGFQUEUE("codec > audio notify status: %d", status); | ||
543 | queue_post(&audio_queue, ev.id, status); | ||
544 | break; | ||
545 | } | 626 | } |
546 | } | 627 | } |
547 | } | 628 | } |
548 | 629 | ||
630 | |||
631 | /** --- Miscellaneous external interfaces -- **/ | ||
632 | |||
633 | /* Create the codec thread and init kernel objects */ | ||
549 | void make_codec_thread(void) | 634 | void make_codec_thread(void) |
550 | { | 635 | { |
551 | queue_init(&codec_queue, false); | 636 | queue_init(&codec_queue, false); |
@@ -558,78 +643,86 @@ void make_codec_thread(void) | |||
558 | codec_thread_id); | 643 | codec_thread_id); |
559 | } | 644 | } |
560 | 645 | ||
646 | /* Unfreeze the codec thread */ | ||
561 | void codec_thread_resume(void) | 647 | void codec_thread_resume(void) |
562 | { | 648 | { |
563 | thread_thaw(codec_thread_id); | 649 | thread_thaw(codec_thread_id); |
564 | } | 650 | } |
565 | 651 | ||
652 | /* Is the current thread the codec thread? */ | ||
566 | bool is_codec_thread(void) | 653 | bool is_codec_thread(void) |
567 | { | 654 | { |
568 | return thread_self() == codec_thread_id; | 655 | return thread_self() == codec_thread_id; |
569 | } | 656 | } |
570 | 657 | ||
571 | #ifdef HAVE_PRIORITY_SCHEDULING | 658 | #ifdef HAVE_PRIORITY_SCHEDULING |
659 | /* Obtain codec thread's current priority */ | ||
572 | int codec_thread_get_priority(void) | 660 | int codec_thread_get_priority(void) |
573 | { | 661 | { |
574 | return thread_get_priority(codec_thread_id); | 662 | return thread_get_priority(codec_thread_id); |
575 | } | 663 | } |
576 | 664 | ||
665 | /* Set the codec thread's priority and return the old value */ | ||
577 | int codec_thread_set_priority(int priority) | 666 | int codec_thread_set_priority(int priority) |
578 | { | 667 | { |
579 | return thread_set_priority(codec_thread_id, priority); | 668 | return thread_set_priority(codec_thread_id, priority); |
580 | } | 669 | } |
581 | #endif /* HAVE_PRIORITY_SCHEDULING */ | 670 | #endif /* HAVE_PRIORITY_SCHEDULING */ |
582 | 671 | ||
583 | /* functions for audio thread use */ | ||
584 | intptr_t codec_ack_msg(intptr_t data, bool stop_codec) | ||
585 | { | ||
586 | intptr_t resp; | ||
587 | LOGFQUEUE("codec >| Q_CODEC_ACK: %d", data); | ||
588 | if (stop_codec) | ||
589 | ci.stop_codec = true; | ||
590 | resp = codec_queue_send(Q_CODEC_ACK, data); | ||
591 | if (stop_codec) | ||
592 | codec_stop(); | ||
593 | LOGFQUEUE(" ack: %ld", resp); | ||
594 | return resp; | ||
595 | } | ||
596 | 672 | ||
673 | /** --- Functions for audio thread use --- **/ | ||
674 | |||
675 | /* Load a decoder or encoder and set the format type */ | ||
597 | bool codec_load(int hid, int cod_spec) | 676 | bool codec_load(int hid, int cod_spec) |
598 | { | 677 | { |
599 | bool retval = false; | 678 | struct codec_load_info parm = { hid, cod_spec }; |
600 | 679 | ||
601 | ci.stop_codec = false; | 680 | LOGFQUEUE("audio >| codec Q_CODEC_LOAD: %d, %d", hid, cod_spec); |
602 | current_codectype = cod_spec; | 681 | return codec_queue_send(Q_CODEC_LOAD, (intptr_t)&parm) != 0; |
682 | } | ||
603 | 683 | ||
604 | if (hid >= 0) | 684 | /* Begin decoding the current file */ |
605 | { | 685 | void codec_go(void) |
606 | LOGFQUEUE("audio >| codec Q_CODEC_LOAD: %d", hid); | 686 | { |
607 | retval = codec_queue_send(Q_CODEC_LOAD, hid) != Q_NULL; | 687 | LOGFQUEUE("audio >| codec Q_CODEC_RUN"); |
608 | } | 688 | codec_queue_send(Q_CODEC_RUN, 0); |
609 | else | 689 | } |
610 | { | ||
611 | LOGFQUEUE("audio >| codec Q_CODEC_LOAD_DISK: %d", cod_spec); | ||
612 | retval = codec_queue_send(Q_CODEC_LOAD_DISK, cod_spec) != Q_NULL; | ||
613 | } | ||
614 | 690 | ||
615 | if (!retval) | 691 | /* Instruct the codec to seek to the specified time (should be properly |
616 | { | 692 | paused or stopped first to avoid possible buffering deadlock) */ |
617 | ci.stop_codec = true; | 693 | void codec_seek(long time) |
618 | current_codectype = AFMT_UNKNOWN; | 694 | { |
619 | } | 695 | LOGFQUEUE("audio > codec Q_CODEC_SEEK: %ld", time); |
696 | codec_queue_send(Q_CODEC_SEEK, time); | ||
697 | } | ||
620 | 698 | ||
621 | return retval; | 699 | /* Pause the codec and make it wait for further instructions inside the |
700 | command callback */ | ||
701 | bool codec_pause(void) | ||
702 | { | ||
703 | LOGFQUEUE("audio >| codec Q_CODEC_PAUSE"); | ||
704 | return codec_queue_send(Q_CODEC_PAUSE, 0) != Q_NULL; | ||
622 | } | 705 | } |
623 | 706 | ||
707 | /* Stop codec if running - codec stays resident if loaded */ | ||
624 | void codec_stop(void) | 708 | void codec_stop(void) |
625 | { | 709 | { |
626 | ci.stop_codec = true; | ||
627 | /* Wait until it's in the main loop */ | 710 | /* Wait until it's in the main loop */ |
628 | while (codec_ack_msg(0, false) != Q_NULL); | 711 | LOGFQUEUE("audio >| codec Q_CODEC_STOP"); |
629 | current_codectype = AFMT_UNKNOWN; | 712 | while (codec_queue_send(Q_CODEC_STOP, 0) != Q_NULL); |
713 | } | ||
714 | |||
715 | /* Call the codec's exit routine and close all references */ | ||
716 | void codec_unload(void) | ||
717 | { | ||
718 | codec_stop(); | ||
719 | LOGFQUEUE("audio >| codec Q_CODEC_UNLOAD"); | ||
720 | codec_queue_send(Q_CODEC_UNLOAD, 0); | ||
630 | } | 721 | } |
631 | 722 | ||
723 | /* Return the afmt type of the loaded codec - sticks until calling | ||
724 | codec_unload unless initial load failed */ | ||
632 | int codec_loaded(void) | 725 | int codec_loaded(void) |
633 | { | 726 | { |
634 | return current_codectype; | 727 | return codec_type; |
635 | } | 728 | } |