Some formatting fixes, reported by Alexander Levin
[kugel-rb.git] / apps / codec_thread.c
blob21d55a77797bb54d2ab19d824423825f73e9e8ba
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2005-2007 Miika Pekkarinen
11 * Copyright (C) 2007-2008 Nicolas Pennequin
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ****************************************************************************/
23 #include "playback.h"
24 #include "codec_thread.h"
25 #include "system.h"
26 #include "kernel.h"
27 #include "codecs.h"
28 #include "buffering.h"
29 #include "pcmbuf.h"
30 #include "dsp.h"
31 #include "abrepeat.h"
32 #include "metadata.h"
33 #include "splash.h"
35 /* Define LOGF_ENABLE to enable logf output in this file */
36 /*#define LOGF_ENABLE*/
37 #include "logf.h"
39 /* macros to enable logf for queues
40 logging on SYS_TIMEOUT can be disabled */
41 #ifdef SIMULATOR
42 /* Define this for logf output of all queuing except SYS_TIMEOUT */
43 #define PLAYBACK_LOGQUEUES
44 /* Define this to logf SYS_TIMEOUT messages */
45 /*#define PLAYBACK_LOGQUEUES_SYS_TIMEOUT*/
46 #endif
48 #ifdef PLAYBACK_LOGQUEUES
49 #define LOGFQUEUE logf
50 #else
51 #define LOGFQUEUE(...)
52 #endif
54 #ifdef PLAYBACK_LOGQUEUES_SYS_TIMEOUT
55 #define LOGFQUEUE_SYS_TIMEOUT logf
56 #else
57 #define LOGFQUEUE_SYS_TIMEOUT(...)
58 #endif
61 /* Variables are commented with the threads that use them:
62 * A=audio, C=codec, V=voice. A suffix of - indicates that
63 * the variable is read but not updated on that thread.
65 * Unless otherwise noted, the extern variables are located
66 * in playback.c.
69 /* Main state control */
70 volatile bool audio_codec_loaded SHAREDBSS_ATTR = false; /* Codec loaded? (C/A-) */
72 extern struct mp3entry *thistrack_id3, /* the currently playing track */
73 *othertrack_id3; /* prev track during track-change-transition, or end of playlist,
74 * next track otherwise */
76 /* Track change controls */
77 extern bool automatic_skip; /* Who initiated in-progress skip? (C/A-) */
79 /* Set to true if the codec thread should send an audio stop request
80 * (typically because the end of the playlist has been reached).
82 static bool codec_requested_stop = false;
84 extern struct event_queue audio_queue;
85 extern struct event_queue codec_queue;
87 extern struct codec_api ci; /* from codecs.c */
89 /* Codec thread */
90 unsigned int codec_thread_id; /* For modifying thread priority later.
91 Used by playback.c and pcmbuf.c */
92 static struct queue_sender_list codec_queue_sender_list;
93 static long codec_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)]
94 IBSS_ATTR;
95 static const char codec_thread_name[] = "codec";
97 /* function prototypes */
98 static bool codec_load_next_track(void);
101 /**************************************/
103 /** misc external functions */
105 int get_codec_base_type(int type)
107 switch (type) {
108 case AFMT_MPA_L1:
109 case AFMT_MPA_L2:
110 case AFMT_MPA_L3:
111 return AFMT_MPA_L3;
114 return type;
117 const char *get_codec_filename(int cod_spec)
119 const char *fname;
121 #ifdef HAVE_RECORDING
122 /* Can choose decoder or encoder if one available */
123 int type = cod_spec & CODEC_TYPE_MASK;
124 int afmt = cod_spec & CODEC_AFMT_MASK;
126 if ((unsigned)afmt >= AFMT_NUM_CODECS)
127 type = AFMT_UNKNOWN | (type & CODEC_TYPE_MASK);
129 fname = (type == CODEC_TYPE_ENCODER) ?
130 audio_formats[afmt].codec_enc_root_fn :
131 audio_formats[afmt].codec_root_fn;
133 logf("%s: %d - %s",
134 (type == CODEC_TYPE_ENCODER) ? "Encoder" : "Decoder",
135 afmt, fname ? fname : "<unknown>");
136 #else /* !HAVE_RECORDING */
137 /* Always decoder */
138 if ((unsigned)cod_spec >= AFMT_NUM_CODECS)
139 cod_spec = AFMT_UNKNOWN;
140 fname = audio_formats[cod_spec].codec_root_fn;
141 logf("Codec: %d - %s", cod_spec, fname ? fname : "<unknown>");
142 #endif /* HAVE_RECORDING */
144 return fname;
145 } /* get_codec_filename */
147 /* Borrow the codec thread and return the ID */
148 void codec_thread_do_callback(void (*fn)(void), unsigned int *id)
150 /* Set id before telling thread to call something; it may be
151 * needed before this function returns. */
152 if (id != NULL)
153 *id = codec_thread_id;
155 /* Codec thread will signal just before entering callback */
156 LOGFQUEUE("codec >| Q_CODEC_DO_CALLBACK");
157 queue_send(&codec_queue, Q_CODEC_DO_CALLBACK, (intptr_t)fn);
161 /** codec API callbacks */
163 static void* codec_get_buffer(size_t *size)
165 if (codec_size >= CODEC_SIZE)
166 return NULL;
167 *size = CODEC_SIZE - codec_size;
168 return &codecbuf[codec_size];
171 static void codec_pcmbuf_insert_callback(
172 const void *ch1, const void *ch2, int count)
174 const char *src[2] = { ch1, ch2 };
176 while (count > 0)
178 int out_count = dsp_output_count(ci.dsp, count);
179 int inp_count;
180 char *dest;
182 /* Prevent audio from a previous track from playing */
183 if (ci.new_track || ci.stop_codec)
184 return;
186 while ((dest = pcmbuf_request_buffer(&out_count)) == NULL)
188 cancel_cpu_boost();
189 sleep(1);
190 if (ci.seek_time || ci.new_track || ci.stop_codec)
191 return;
194 /* Get the real input_size for output_size bytes, guarding
195 * against resampling buffer overflows. */
196 inp_count = dsp_input_count(ci.dsp, out_count);
198 if (inp_count <= 0)
199 return;
201 /* Input size has grown, no error, just don't write more than length */
202 if (inp_count > count)
203 inp_count = count;
205 out_count = dsp_process(ci.dsp, dest, src, inp_count);
207 if (out_count <= 0)
208 return;
210 pcmbuf_write_complete(out_count);
212 count -= inp_count;
214 } /* codec_pcmbuf_insert_callback */
216 static void codec_set_elapsed_callback(unsigned long value)
218 if (ci.seek_time)
219 return;
221 #ifdef AB_REPEAT_ENABLE
222 ab_position_report(value);
223 #endif
225 unsigned long latency = pcmbuf_get_latency();
226 if (value < latency)
227 thistrack_id3->elapsed = 0;
228 else
230 unsigned long elapsed = value - latency;
231 if (elapsed > thistrack_id3->elapsed ||
232 elapsed < thistrack_id3->elapsed - 2)
234 thistrack_id3->elapsed = elapsed;
239 static void codec_set_offset_callback(size_t value)
241 if (ci.seek_time)
242 return;
244 unsigned long latency = pcmbuf_get_latency() * thistrack_id3->bitrate / 8;
245 if (value < latency)
246 thistrack_id3->offset = 0;
247 else
248 thistrack_id3->offset = value - latency;
251 /* helper function, not a callback */
252 static void codec_advance_buffer_counters(size_t amount)
254 bufadvance(get_audio_hid(), amount);
255 ci.curpos += amount;
258 /* copy up-to size bytes into ptr and return the actual size copied */
259 static size_t codec_filebuf_callback(void *ptr, size_t size)
261 ssize_t copy_n;
263 if (ci.stop_codec || !(audio_status() & AUDIO_STATUS_PLAY))
264 return 0;
266 copy_n = bufread(get_audio_hid(), size, ptr);
268 /* Nothing requested OR nothing left */
269 if (copy_n == 0)
270 return 0;
272 /* Update read and other position pointers */
273 codec_advance_buffer_counters(copy_n);
275 /* Return the actual amount of data copied to the buffer */
276 return copy_n;
277 } /* codec_filebuf_callback */
279 static void* codec_request_buffer_callback(size_t *realsize, size_t reqsize)
281 size_t copy_n = reqsize;
282 ssize_t ret;
283 void *ptr;
285 if (!(audio_status() & AUDIO_STATUS_PLAY))
287 *realsize = 0;
288 return NULL;
291 ret = bufgetdata(get_audio_hid(), reqsize, &ptr);
292 if (ret >= 0)
293 copy_n = MIN((size_t)ret, reqsize);
295 if (copy_n == 0)
297 *realsize = 0;
298 return NULL;
301 *realsize = copy_n;
303 return ptr;
304 } /* codec_request_buffer_callback */
306 static void codec_advance_buffer_callback(size_t amount)
308 codec_advance_buffer_counters(amount);
309 codec_set_offset_callback(ci.curpos);
312 static void codec_advance_buffer_loc_callback(void *ptr)
314 size_t amount = buf_get_offset(get_audio_hid(), ptr);
315 codec_advance_buffer_callback(amount);
318 static bool codec_seek_buffer_callback(size_t newpos)
320 logf("codec_seek_buffer_callback");
322 int ret = bufseek(get_audio_hid(), newpos);
323 if (ret == 0) {
324 ci.curpos = newpos;
325 return true;
327 else {
328 return false;
332 static void codec_seek_complete_callback(void)
334 logf("seek_complete");
335 /* If seeking-while-playing, pcm_is_paused() is true.
336 * If seeking-while-paused, audio_status PAUSE is true.
337 * A seamless seek skips this section. */
338 bool audio_paused = audio_status() & AUDIO_STATUS_PAUSE;
339 if (pcm_is_paused() || audio_paused)
341 /* Clear the buffer */
342 pcmbuf_play_stop();
343 dsp_configure(ci.dsp, DSP_FLUSH, 0);
345 /* If seeking-while-playing, resume pcm playback */
346 if (!audio_paused)
347 pcmbuf_pause(false);
349 ci.seek_time = 0;
352 static void codec_discard_codec_callback(void)
354 int *codec_hid = get_codec_hid();
355 if (*codec_hid >= 0)
357 bufclose(*codec_hid);
358 *codec_hid = -1;
362 static bool codec_request_next_track_callback(void)
364 int prev_codectype;
366 if (ci.stop_codec || !(audio_status() & AUDIO_STATUS_PLAY))
367 return false;
369 prev_codectype = get_codec_base_type(thistrack_id3->codectype);
370 if (!codec_load_next_track())
371 return false;
373 /* Seek to the beginning of the new track because if the struct
374 mp3entry was buffered, "elapsed" might not be zero (if the track has
375 been played already but not unbuffered) */
376 codec_seek_buffer_callback(thistrack_id3->first_frame_offset);
377 /* Check if the next codec is the same file. */
378 if (prev_codectype == get_codec_base_type(thistrack_id3->codectype))
380 logf("New track loaded");
381 codec_discard_codec_callback();
382 return true;
384 else
386 logf("New codec:%d/%d", thistrack_id3->codectype, prev_codectype);
387 return false;
391 static void codec_configure_callback(int setting, intptr_t value)
393 if (!dsp_configure(ci.dsp, setting, value))
394 { logf("Illegal key:%d", setting); }
397 /* Initialize codec API */
398 void codec_init_codec_api(void)
400 ci.dsp = (struct dsp_config *)dsp_configure(NULL, DSP_MYDSP,
401 CODEC_IDX_AUDIO);
402 ci.codec_get_buffer = codec_get_buffer;
403 ci.pcmbuf_insert = codec_pcmbuf_insert_callback;
404 ci.set_elapsed = codec_set_elapsed_callback;
405 ci.read_filebuf = codec_filebuf_callback;
406 ci.request_buffer = codec_request_buffer_callback;
407 ci.advance_buffer = codec_advance_buffer_callback;
408 ci.advance_buffer_loc = codec_advance_buffer_loc_callback;
409 ci.seek_buffer = codec_seek_buffer_callback;
410 ci.seek_complete = codec_seek_complete_callback;
411 ci.request_next_track = codec_request_next_track_callback;
412 ci.discard_codec = codec_discard_codec_callback;
413 ci.set_offset = codec_set_offset_callback;
414 ci.configure = codec_configure_callback;
418 /* track change */
420 static bool codec_load_next_track(void)
422 intptr_t result = Q_CODEC_REQUEST_FAILED;
424 audio_set_prev_elapsed(thistrack_id3->elapsed);
426 #ifdef AB_REPEAT_ENABLE
427 ab_end_of_track_report();
428 #endif
430 logf("Request new track");
432 if (ci.new_track == 0)
434 ci.new_track++;
435 automatic_skip = true;
438 if (!ci.stop_codec)
440 trigger_cpu_boost();
441 LOGFQUEUE("codec >| audio Q_AUDIO_CHECK_NEW_TRACK");
442 result = queue_send(&audio_queue, Q_AUDIO_CHECK_NEW_TRACK, 0);
445 switch (result)
447 case Q_CODEC_REQUEST_COMPLETE:
448 LOGFQUEUE("codec |< Q_CODEC_REQUEST_COMPLETE");
449 pcmbuf_start_track_change(automatic_skip);
450 return true;
452 case Q_CODEC_REQUEST_FAILED:
453 LOGFQUEUE("codec |< Q_CODEC_REQUEST_FAILED");
454 ci.new_track = 0;
455 ci.stop_codec = true;
456 codec_requested_stop = true;
457 return false;
459 default:
460 LOGFQUEUE("codec |< default");
461 ci.stop_codec = true;
462 codec_requested_stop = true;
463 return false;
467 /** CODEC THREAD */
468 static void codec_thread(void)
470 struct queue_event ev;
471 int status;
473 while (1) {
474 status = 0;
476 #ifdef HAVE_CROSSFADE
477 if (!pcmbuf_is_crossfade_active())
478 #endif
480 cancel_cpu_boost();
483 queue_wait(&codec_queue, &ev);
484 codec_requested_stop = false;
486 switch (ev.id) {
487 case Q_CODEC_LOAD_DISK:
488 LOGFQUEUE("codec < Q_CODEC_LOAD_DISK");
489 queue_reply(&codec_queue, 1);
490 audio_codec_loaded = true;
491 ci.stop_codec = false;
492 status = codec_load_file((const char *)ev.data, &ci);
493 LOGFQUEUE("codec_load_file %s %d\n", (const char *)ev.data, status);
494 break;
496 case Q_CODEC_LOAD:
497 LOGFQUEUE("codec < Q_CODEC_LOAD");
498 if (*get_codec_hid() < 0) {
499 logf("Codec slot is empty!");
500 /* Wait for the pcm buffer to go empty */
501 while (pcm_is_playing())
502 yield();
503 /* This must be set to prevent an infinite loop */
504 ci.stop_codec = true;
505 LOGFQUEUE("codec > codec Q_AUDIO_PLAY");
506 queue_post(&codec_queue, Q_AUDIO_PLAY, 0);
507 break;
510 audio_codec_loaded = true;
511 ci.stop_codec = false;
512 status = codec_load_buf(*get_codec_hid(), &ci);
513 LOGFQUEUE("codec_load_buf %d\n", status);
514 break;
516 case Q_CODEC_DO_CALLBACK:
517 LOGFQUEUE("codec < Q_CODEC_DO_CALLBACK");
518 queue_reply(&codec_queue, 1);
519 if ((void*)ev.data != NULL)
521 cpucache_invalidate();
522 ((void (*)(void))ev.data)();
523 cpucache_flush();
525 break;
527 #ifdef AUDIO_HAVE_RECORDING
528 case Q_ENCODER_LOAD_DISK:
529 LOGFQUEUE("codec < Q_ENCODER_LOAD_DISK");
530 audio_codec_loaded = false; /* Not audio codec! */
531 logf("loading encoder");
532 ci.stop_encoder = false;
533 status = codec_load_file((const char *)ev.data, &ci);
534 logf("encoder stopped");
535 break;
536 #endif /* AUDIO_HAVE_RECORDING */
538 default:
539 LOGFQUEUE("codec < default");
542 if (audio_codec_loaded)
544 if (ci.stop_codec)
546 status = CODEC_OK;
547 if (!(audio_status() & AUDIO_STATUS_PLAY))
548 pcmbuf_play_stop();
551 audio_codec_loaded = false;
554 switch (ev.id) {
555 case Q_CODEC_LOAD_DISK:
556 case Q_CODEC_LOAD:
557 LOGFQUEUE("codec < Q_CODEC_LOAD");
558 if (audio_status() & AUDIO_STATUS_PLAY)
560 if (ci.new_track || status != CODEC_OK)
562 if (!ci.new_track)
564 logf("Codec failure, %d %d", ci.new_track, status);
565 splash(HZ*2, "Codec failure");
568 if (!codec_load_next_track())
570 LOGFQUEUE("codec > audio Q_AUDIO_STOP");
571 /* End of playlist */
572 queue_post(&audio_queue, Q_AUDIO_STOP, 0);
573 break;
576 else
578 logf("Codec finished");
579 if (ci.stop_codec)
581 /* Wait for the audio to stop playing before
582 * triggering the WPS exit */
583 while(pcm_is_playing())
585 /* There has been one too many struct pointer swaps by now
586 * so even though it says othertrack_id3, its the correct one! */
587 othertrack_id3->elapsed =
588 othertrack_id3->length - pcmbuf_get_latency();
589 sleep(1);
592 if (codec_requested_stop)
594 LOGFQUEUE("codec > audio Q_AUDIO_STOP");
595 queue_post(&audio_queue, Q_AUDIO_STOP, 0);
597 break;
601 if (*get_codec_hid() >= 0)
603 LOGFQUEUE("codec > codec Q_CODEC_LOAD");
604 queue_post(&codec_queue, Q_CODEC_LOAD, 0);
606 else
608 const char *codec_fn =
609 get_codec_filename(thistrack_id3->codectype);
610 if (codec_fn)
612 LOGFQUEUE("codec > codec Q_CODEC_LOAD_DISK");
613 queue_post(&codec_queue, Q_CODEC_LOAD_DISK,
614 (intptr_t)codec_fn);
618 break;
620 #ifdef AUDIO_HAVE_RECORDING
621 case Q_ENCODER_LOAD_DISK:
622 LOGFQUEUE("codec < Q_ENCODER_LOAD_DISK");
624 if (status == CODEC_OK)
625 break;
627 logf("Encoder failure");
628 splash(HZ*2, "Encoder failure");
630 if (ci.enc_codec_loaded < 0)
631 break;
633 logf("Encoder failed to load");
634 ci.enc_codec_loaded = -1;
635 break;
636 #endif /* AUDIO_HAVE_RECORDING */
638 default:
639 LOGFQUEUE("codec < default");
641 } /* end switch */
645 void make_codec_thread(void)
647 codec_thread_id = create_thread(
648 codec_thread, codec_stack, sizeof(codec_stack),
649 CREATE_THREAD_FROZEN,
650 codec_thread_name IF_PRIO(, PRIORITY_PLAYBACK)
651 IF_COP(, CPU));
652 queue_enable_queue_send(&codec_queue, &codec_queue_sender_list,
653 codec_thread_id);