diff --git a/apps/SOURCES b/apps/SOURCES index 4b28f8888b..b22f597b4a 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -163,6 +163,7 @@ radio/radioart.c #if INPUT_SRC_CAPS != 0 audio_path.c #endif /* INPUT_SRC_CAPS != 0 */ +audio_thread.c pcmbuf.c codec_thread.c playback.c diff --git a/apps/audio_thread.c b/apps/audio_thread.c new file mode 100644 index 0000000000..2f01f7a8c2 --- /dev/null +++ b/apps/audio_thread.c @@ -0,0 +1,162 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005-2007 Miika Pekkarinen + * Copyright (C) 2007-2008 Nicolas Pennequin + * Copyright (C) 2011-2013 Michael Sevakis + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "config.h" +#include "system.h" +#include "kernel.h" +#include "logf.h" +#include "usb.h" +#include "pcm.h" +#include "sound.h" +#include "audio_thread.h" +#ifdef AUDIO_HAVE_RECORDING +#include "pcm_record.h" +#endif +#include "codec_thread.h" +#include "voice_thread.h" +#include "talk.h" +#include "settings.h" + +/* Macros to enable logf for queues + logging on SYS_TIMEOUT can be disabled */ +#ifdef SIMULATOR +/* Define this for logf output of all queuing except SYS_TIMEOUT */ +#define AUDIO_LOGQUEUES +/* Define this to logf SYS_TIMEOUT messages */ +/*#define AUDIO_LOGQUEUES_SYS_TIMEOUT*/ +#endif + +#ifdef AUDIO_LOGQUEUES +#define LOGFQUEUE logf +#else +#define LOGFQUEUE(...) +#endif + +bool audio_is_initialized = false; + +/* Event queues */ +struct event_queue audio_queue SHAREDBSS_ATTR; +static struct queue_sender_list audio_queue_sender_list SHAREDBSS_ATTR; + +/* Audio thread */ +static long audio_stack[(DEFAULT_STACK_SIZE + 0x1000)/sizeof(long)]; +static const char audio_thread_name[] = "audio"; +unsigned int audio_thread_id = 0; + +static void NORETURN_ATTR audio_thread(void) +{ + struct queue_event ev; + ev.id = SYS_TIMEOUT; /* something not in switch below */ + + pcm_postinit(); + + while (1) + { + switch (ev.id) + { + /* Starts the playback engine branch */ + case Q_AUDIO_PLAY: + LOGFQUEUE("audio < Q_AUDIO_PLAY"); + audio_playback_handler(&ev); + continue; + +#ifdef AUDIO_HAVE_RECORDING + /* Starts the recording engine branch */ + case Q_AUDIO_INIT_RECORDING: + LOGFQUEUE("audio < Q_AUDIO_INIT_RECORDING"); + audio_recording_handler(&ev); + continue; +#endif + + /* All return upon USB */ + case SYS_USB_CONNECTED: + LOGFQUEUE("audio < SYS_USB_CONNECTED"); + voice_stop(); + usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_wait_for_disconnect(&audio_queue); + break; + } + + queue_wait(&audio_queue, &ev); + } +} + +/* Return the playback and recording status */ +int audio_status(void) +{ + return playback_status() +#ifdef AUDIO_HAVE_RECORDING + | pcm_rec_status() +#endif + ; +} + +/* Clear all accumulated audio errors for playback and recording */ +void audio_error_clear(void) +{ +#ifdef AUDIO_HAVE_RECORDING + pcm_rec_error_clear(); +#endif +} + +/** -- Startup -- **/ + +/* Initialize the audio system - called from init() in main.c */ +void audio_init(void) +{ + /* Can never do this twice */ + if (audio_is_initialized) + { + logf("audio: already initialized"); + return; + } + + logf("audio: initializing"); + + playback_init(); + + /* Recording doesn't need init call */ + + /* Initialize queues before giving control elsewhere in case it likes + to send messages. Thread creation will be delayed however so nothing + starts running until ready if something yields such as talk_init. */ + queue_init(&audio_queue, true); + codec_thread_init(); + + /* This thread does buffer, so match its priority */ + audio_thread_id = create_thread(audio_thread, audio_stack, + sizeof(audio_stack), 0, audio_thread_name + IF_PRIO(, MIN(PRIORITY_BUFFERING, PRIORITY_USER_INTERFACE)) + IF_COP(, CPU)); + + queue_enable_queue_send(&audio_queue, &audio_queue_sender_list, + audio_thread_id); + + /* ...now...audio_reset_buffer must know the size of voicefile buffer so + init talk first which will init the buffers */ + talk_init(); + + /* Probably safe to say */ + audio_is_initialized = true; + + sound_settings_apply(); +} diff --git a/apps/audio_thread.h b/apps/audio_thread.h new file mode 100644 index 0000000000..7e0ad28849 --- /dev/null +++ b/apps/audio_thread.h @@ -0,0 +1,102 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005-2007 Miika Pekkarinen + * Copyright (C) 2007-2008 Nicolas Pennequin + * Copyright (C) 2011-2013 Michael Sevakis + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef AUDIO_THREAD_H +#define AUDIO_THREAD_H + +/* Define one constant that includes recording related functionality */ +#if defined(HAVE_RECORDING) && !defined(SIMULATOR) +#define AUDIO_HAVE_RECORDING +#endif + +enum +{ + Q_NULL = 0, /* reserved */ + + /* -> audio */ + Q_AUDIO_PLAY, + Q_AUDIO_STOP, + Q_AUDIO_PAUSE, + Q_AUDIO_SKIP, + Q_AUDIO_PRE_FF_REWIND, + Q_AUDIO_FF_REWIND, + Q_AUDIO_FLUSH, + Q_AUDIO_DIR_SKIP, + + /* pcmbuf -> audio */ + Q_AUDIO_TRACK_CHANGED, + + /* audio -> audio */ + Q_AUDIO_FILL_BUFFER, /* continue buffering next track */ + + /* buffering -> audio */ + Q_AUDIO_BUFFERING, /* some buffer event */ + Q_AUDIO_FINISH_LOAD_TRACK, /* metadata is buffered */ + Q_AUDIO_HANDLE_FINISHED, /* some other type is buffered */ + + /* codec -> audio (*) */ + Q_AUDIO_CODEC_SEEK_COMPLETE, + Q_AUDIO_CODEC_COMPLETE, + + /* audio -> codec */ + Q_CODEC_LOAD, + Q_CODEC_RUN, + Q_CODEC_PAUSE, + Q_CODEC_SEEK, + Q_CODEC_STOP, + Q_CODEC_UNLOAD, + + /* -> codec */ + Q_CODEC_DO_CALLBACK, + + /* -> recording */ +#ifdef HAVE_RECORDING + Q_AUDIO_INIT_RECORDING, + Q_AUDIO_CLOSE_RECORDING, + Q_AUDIO_RECORDING_OPTIONS, + Q_AUDIO_RECORD, + Q_AUDIO_RESUME, +#endif + + /*- settings -*/ + +#ifdef HAVE_DISK_STORAGE + /* -> audio */ + Q_AUDIO_UPDATE_WATERMARK, /* buffering watermark needs updating */ +#endif + /* -> audio */ + Q_AUDIO_REMAKE_AUDIO_BUFFER, /* buffer needs to be reinitialized */ +}; + +/* (*) If you change these, you must check audio_clear_track_notifications + in playback.c for correctness */ + +void audio_init(void); +void playback_init(void); +unsigned int playback_status(void); + +void audio_playback_handler(struct queue_event *ev); +#ifdef AUDIO_HAVE_RECORDING +void audio_recording_handler(struct queue_event *ev); +#endif + +#endif /* AUDIO_THREAD_H */ diff --git a/apps/codec_thread.c b/apps/codec_thread.c index 308b2ff982..54bc28e19a 100644 --- a/apps/codec_thread.c +++ b/apps/codec_thread.c @@ -26,6 +26,7 @@ #include "codecs.h" #include "codec_thread.h" #include "pcmbuf.h" +#include "audio_thread.h" #include "playback.h" #include "buffering.h" #include "dsp_core.h" diff --git a/apps/main.c b/apps/main.c index 6b6566c7ca..57257aff66 100644 --- a/apps/main.c +++ b/apps/main.c @@ -87,6 +87,7 @@ #endif #if (CONFIG_CODEC == SWCODEC) +#include "audio_thread.h" #include "playback.h" #include "tdspeed.h" #endif @@ -386,6 +387,7 @@ static void init(void) storage_init(); #if CONFIG_CODEC == SWCODEC + pcm_init(); dsp_init(); #endif settings_reset(); @@ -422,10 +424,6 @@ static void init(void) audio_init(); -#if (CONFIG_CODEC == SWCODEC) && defined(HAVE_RECORDING) - pcm_rec_init(); -#endif - settings_apply_skins(); } @@ -641,6 +639,7 @@ static void init(void) } #if CONFIG_CODEC == SWCODEC + pcm_init(); dsp_init(); #endif @@ -727,10 +726,6 @@ static void init(void) audio_init(); CHART("id) + { + /** Codec and track change messages **/ + case Q_AUDIO_CODEC_COMPLETE: + /* Codec is done processing track and has gone idle */ + LOGFQUEUE("playback < Q_AUDIO_CODEC_COMPLETE: %ld", + (long)ev->data); + audio_on_codec_complete(ev->data); + break; + + case Q_AUDIO_CODEC_SEEK_COMPLETE: + /* Codec is done seeking */ + LOGFQUEUE("playback < Q_AUDIO_SEEK_COMPLETE"); + audio_on_codec_seek_complete(); + break; + + case Q_AUDIO_TRACK_CHANGED: + /* PCM track change done */ + LOGFQUEUE("playback < Q_AUDIO_TRACK_CHANGED"); + audio_on_track_changed(); + break; + + /** Control messages **/ + case Q_AUDIO_PLAY: + LOGFQUEUE("playback < Q_AUDIO_PLAY"); + audio_start_playback(ev->data, 0); + break; + +#ifdef AUDIO_HAVE_RECORDING + /* So we can go straight from playback to recording */ + case Q_AUDIO_INIT_RECORDING: +#endif + case SYS_USB_CONNECTED: + case Q_AUDIO_STOP: + LOGFQUEUE("playback < Q_AUDIO_STOP"); + audio_stop_playback(); + if (ev->data != 0) + queue_clear(&audio_queue); + return; /* no more playback */ + + case Q_AUDIO_PAUSE: + LOGFQUEUE("playback < Q_AUDIO_PAUSE"); + audio_on_pause(ev->data); + break; + + case Q_AUDIO_SKIP: + LOGFQUEUE("playback < Q_AUDIO_SKIP"); + audio_on_skip(); + break; + + case Q_AUDIO_DIR_SKIP: + LOGFQUEUE("playback < Q_AUDIO_DIR_SKIP"); + audio_on_dir_skip(ev->data); + break; + + case Q_AUDIO_PRE_FF_REWIND: + LOGFQUEUE("playback < Q_AUDIO_PRE_FF_REWIND"); + audio_on_pre_ff_rewind(); + break; + + case Q_AUDIO_FF_REWIND: + LOGFQUEUE("playback < Q_AUDIO_FF_REWIND"); + audio_on_ff_rewind(ev->data); + break; + + case Q_AUDIO_FLUSH: + LOGFQUEUE("playback < Q_AUDIO_FLUSH: %d", (int)ev->data); + audio_on_audio_flush(); + break; + + /** Buffering messages **/ + case Q_AUDIO_BUFFERING: + /* some buffering event */ + LOGFQUEUE("playback < Q_AUDIO_BUFFERING: %d", (int)ev->data); + audio_on_buffering(ev->data); + break; + + case Q_AUDIO_FILL_BUFFER: + /* continue buffering next track */ + LOGFQUEUE("playback < Q_AUDIO_FILL_BUFFER"); + audio_on_fill_buffer(); + break; + + case Q_AUDIO_FINISH_LOAD_TRACK: + /* metadata is buffered */ + LOGFQUEUE("playback < Q_AUDIO_FINISH_LOAD_TRACK"); + audio_on_finish_load_track(ev->data); + break; + + case Q_AUDIO_HANDLE_FINISHED: + /* some other type is buffered */ + LOGFQUEUE("playback < Q_AUDIO_HANDLE_FINISHED"); + audio_on_handle_finished(ev->data); + break; + + /** Miscellaneous messages **/ + case Q_AUDIO_REMAKE_AUDIO_BUFFER: + /* buffer needs to be reinitialized */ + LOGFQUEUE("playback < Q_AUDIO_REMAKE_AUDIO_BUFFER"); + audio_start_playback(0, AUDIO_START_RESTART | AUDIO_START_NEWBUF); + break; + +#ifdef HAVE_DISK_STORAGE + case Q_AUDIO_UPDATE_WATERMARK: + /* buffering watermark needs updating */ + LOGFQUEUE("playback < Q_AUDIO_UPDATE_WATERMARK: %d", + (int)ev->data); + audio_update_filebuf_watermark(ev->data); + break; +#endif /* HAVE_DISK_STORAGE */ + + case SYS_TIMEOUT: + LOGFQUEUE_SYS_TIMEOUT("playback < SYS_TIMEOUT"); + break; + + default: + /* LOGFQUEUE("audio < default : %08lX", ev->id); */ + break; + } /* end switch */ + switch (filling) { /* Active states */ @@ -3039,174 +3121,22 @@ static void audio_thread(void) if (audio_pcmbuf_track_change_scan()) { /* Transfer notification to audio queue event */ - ev.id = Q_AUDIO_TRACK_CHANGED; - ev.data = 1; + ev->id = Q_AUDIO_TRACK_CHANGED; + ev->data = 1; } else { /* If doing auto skip, poll pcmbuf track notifications a bit faster to promply detect the transition */ - queue_wait_w_tmo(&audio_queue, &ev, - skip_pending == TRACK_SKIP_NONE ? - HZ/2 : HZ/10); + queue_wait_w_tmo(&audio_queue, ev, + skip_pending == TRACK_SKIP_NONE ? HZ/2 : HZ/10); } break; /* Idle states */ default: - queue_wait(&audio_queue, &ev); - -#if (CONFIG_PLATFORM & PLATFORM_NATIVE) - switch (ev.id) - { -#ifdef AUDIO_HAVE_RECORDING - /* Must monitor the encoder message for recording so it can remove - it if we process the insertion before it does. It cannot simply - be removed from under recording however. */ - case Q_AUDIO_LOAD_ENCODER: - break; -#endif - case SYS_USB_DISCONNECTED: - filling = STATE_IDLE; - break; - - default: - if (filling == STATE_USB) - continue; - } -#endif /* CONFIG_PLATFORM */ + queue_wait(&audio_queue, ev); } - - switch (ev.id) - { - /** Codec and track change messages **/ - case Q_AUDIO_CODEC_COMPLETE: - /* Codec is done processing track and has gone idle */ - LOGFQUEUE("audio < Q_AUDIO_CODEC_COMPLETE: %ld", (long)ev.data); - audio_on_codec_complete(ev.data); - break; - - case Q_AUDIO_CODEC_SEEK_COMPLETE: - /* Codec is done seeking */ - LOGFQUEUE("audio < Q_AUDIO_SEEK_COMPLETE"); - audio_on_codec_seek_complete(); - break; - - case Q_AUDIO_TRACK_CHANGED: - /* PCM track change done */ - LOGFQUEUE("audio < Q_AUDIO_TRACK_CHANGED"); - audio_on_track_changed(); - break; - - /** Control messages **/ - case Q_AUDIO_PLAY: - LOGFQUEUE("audio < Q_AUDIO_PLAY"); - audio_start_playback(ev.data, 0); - break; - - case Q_AUDIO_STOP: - LOGFQUEUE("audio < Q_AUDIO_STOP"); - audio_stop_playback(); - if (ev.data != 0) - queue_clear(&audio_queue); - break; - - case Q_AUDIO_PAUSE: - LOGFQUEUE("audio < Q_AUDIO_PAUSE"); - audio_on_pause(ev.data); - break; - - case Q_AUDIO_SKIP: - LOGFQUEUE("audio < Q_AUDIO_SKIP"); - audio_on_skip(); - break; - - case Q_AUDIO_DIR_SKIP: - LOGFQUEUE("audio < Q_AUDIO_DIR_SKIP"); - audio_on_dir_skip(ev.data); - break; - - case Q_AUDIO_PRE_FF_REWIND: - LOGFQUEUE("audio < Q_AUDIO_PRE_FF_REWIND"); - audio_on_pre_ff_rewind(); - break; - - case Q_AUDIO_FF_REWIND: - LOGFQUEUE("audio < Q_AUDIO_FF_REWIND"); - audio_on_ff_rewind(ev.data); - break; - - case Q_AUDIO_FLUSH: - LOGFQUEUE("audio < Q_AUDIO_FLUSH: %d", (int)ev.data); - audio_on_audio_flush(); - break; - - /** Buffering messages **/ - case Q_AUDIO_BUFFERING: - /* some buffering event */ - LOGFQUEUE("audio < Q_AUDIO_BUFFERING: %d", (int)ev.data); - audio_on_buffering(ev.data); - break; - - case Q_AUDIO_FILL_BUFFER: - /* continue buffering next track */ - LOGFQUEUE("audio < Q_AUDIO_FILL_BUFFER"); - audio_on_fill_buffer(); - break; - - case Q_AUDIO_FINISH_LOAD_TRACK: - /* metadata is buffered */ - LOGFQUEUE("audio < Q_AUDIO_FINISH_LOAD_TRACK"); - audio_on_finish_load_track(ev.data); - break; - - case Q_AUDIO_HANDLE_FINISHED: - /* some other type is buffered */ - LOGFQUEUE("audio < Q_AUDIO_HANDLE_FINISHED"); - audio_on_handle_finished(ev.data); - break; - - /** Miscellaneous messages **/ - case Q_AUDIO_REMAKE_AUDIO_BUFFER: - /* buffer needs to be reinitialized */ - LOGFQUEUE("audio < Q_AUDIO_REMAKE_AUDIO_BUFFER"); - audio_start_playback(0, AUDIO_START_RESTART | AUDIO_START_NEWBUF); - break; - -#ifdef HAVE_DISK_STORAGE - case Q_AUDIO_UPDATE_WATERMARK: - /* buffering watermark needs updating */ - LOGFQUEUE("audio < Q_AUDIO_UPDATE_WATERMARK: %d", (int)ev.data); - audio_update_filebuf_watermark(ev.data); - break; -#endif /* HAVE_DISK_STORAGE */ - -#ifdef AUDIO_HAVE_RECORDING - case Q_AUDIO_LOAD_ENCODER: - /* load an encoder for recording */ - LOGFQUEUE("audio < Q_AUDIO_LOAD_ENCODER: %d", (int)ev.data); - audio_on_load_encoder(ev.data); - break; -#endif /* AUDIO_HAVE_RECORDING */ - - case SYS_USB_CONNECTED: - LOGFQUEUE("audio < SYS_USB_CONNECTED"); - audio_stop_playback(); -#ifdef PLAYBACK_VOICE - voice_stop(); -#endif - filling = STATE_USB; - usb_acknowledge(SYS_USB_CONNECTED_ACK); - break; - - case SYS_TIMEOUT: - LOGFQUEUE_SYS_TIMEOUT("audio < SYS_TIMEOUT"); - break; - - default: - /* LOGFQUEUE("audio < default : %08lX", ev.id); */ - break; - } /* end switch */ } /* end while */ } @@ -3356,27 +3286,6 @@ bool audio_pcmbuf_may_play(void) /** -- External interfaces -- **/ -/* Return the playback and recording status */ -int audio_status(void) -{ - unsigned int ret = play_status; - -#ifdef AUDIO_HAVE_RECORDING - /* Do this here for constitency with mpeg.c version */ - ret |= pcm_rec_status(); -#endif - - return (int)ret; -} - -/* Clear all accumulated audio errors for playback and recording */ -void audio_error_clear(void) -{ -#ifdef AUDIO_HAVE_RECORDING - pcm_rec_error_clear(); -#endif -} - /* Get a copy of the id3 data for the for current track + offset + skip delta */ bool audio_peek_track(struct mp3entry *id3, int offset) { @@ -3599,7 +3508,7 @@ unsigned char * audio_get_buffer(bool talk_buf, size_t *buffer_size) { unsigned char *buf; - if (audio_is_initialized) + if (audio_is_initialized && thread_self() != audio_thread_id) { audio_hard_stop(); } @@ -3656,15 +3565,6 @@ unsigned char * audio_get_buffer(bool talk_buf, size_t *buffer_size) return buf; } -#ifdef HAVE_RECORDING -/* Stop audio, voice and obtain all available buffer space */ -unsigned char * audio_get_recording_buffer(size_t *buffer_size) -{ - audio_hard_stop(); - return audio_get_buffer(true, buffer_size); -} -#endif /* HAVE_RECORDING */ - /* Restore audio buffer to a particular state (promoting status) */ bool audio_restore_playback(int type) { @@ -3755,30 +3655,6 @@ void playback_release_aa_slot(int slot) } #endif /* HAVE_ALBUMART */ - -#ifdef HAVE_RECORDING -/* Load an encoder and run it */ -bool audio_load_encoder(int afmt) -{ -#if (CONFIG_PLATFORM & PLATFORM_NATIVE) - LOGFQUEUE("audio >| Q_AUDIO_LOAD_ENCODER: %d", afmt); - return audio_queue_send(Q_AUDIO_LOAD_ENCODER, afmt) != 0; -#else - (void)afmt; - return true; -#endif -} - -/* Stop an encoder and unload it */ -void audio_remove_encoder(void) -{ -#if (CONFIG_PLATFORM & PLATFORM_NATIVE) - LOGFQUEUE("audio >| Q_AUDIO_LOAD_ENCODER: NULL"); - audio_queue_send(Q_AUDIO_LOAD_ENCODER, AFMT_UNKNOWN); -#endif -} -#endif /* HAVE_RECORDING */ - /* Is an automatic skip in progress? If called outside transition callbacks, indicates the last skip type at the time it was processed and isn't very meaningful. */ @@ -3866,58 +3742,24 @@ void audio_set_crossfade(int enable) } #endif /* HAVE_CROSSFADE */ +unsigned int playback_status(void) +{ + return play_status; +} /** -- Startup -- **/ - -/* Initialize the audio system - called from init() in main.c */ -void audio_init(void) +void playback_init(void) { - /* Can never do this twice */ - if (audio_is_initialized) - { - logf("audio: already initialized"); - return; - } - - logf("audio: initializing"); - - /* Initialize queues before giving control elsewhere in case it likes - to send messages. Thread creation will be delayed however so nothing - starts running until ready if something yields such as talk_init. */ - queue_init(&audio_queue, true); - - mutex_init(&id3_mutex); - - pcm_init(); - - codec_thread_init(); - - /* This thread does buffer, so match its priority */ - audio_thread_id = create_thread(audio_thread, audio_stack, - sizeof(audio_stack), 0, audio_thread_name - IF_PRIO(, MIN(PRIORITY_BUFFERING, PRIORITY_USER_INTERFACE)) - IF_COP(, CPU)); - - queue_enable_queue_send(&audio_queue, &audio_queue_sender_list, - audio_thread_id); + logf("playback: initializing"); /* Initialize the track buffering system */ + mutex_init(&id3_mutex); track_list_init(); buffering_init(); - #ifdef HAVE_CROSSFADE /* Set crossfade setting for next buffer init which should be about... */ pcmbuf_request_crossfade_enable(global_settings.crossfade); #endif - - /* ...now...audio_reset_buffer must know the size of voicefile buffer so - init talk first which will init the buffers */ - talk_init(); - - /* Probably safe to say */ - audio_is_initialized = true; - - sound_settings_apply(); #ifdef HAVE_DISK_STORAGE audio_set_buffer_margin(global_settings.buffer_margin); #endif diff --git a/apps/playback.h b/apps/playback.h index 5135c988cb..865e9a313b 100644 --- a/apps/playback.h +++ b/apps/playback.h @@ -92,68 +92,6 @@ size_t audio_get_filebuflen(void); otherwise the result is undefined. */ bool audio_automatic_skip(void); -/* Define one constant that includes recording related functionality */ -#if defined(HAVE_RECORDING) && !defined(SIMULATOR) -#define AUDIO_HAVE_RECORDING -#endif - -enum { - Q_NULL = 0, /* reserved */ - - /* -> audio */ - Q_AUDIO_PLAY = 1, - Q_AUDIO_STOP, - Q_AUDIO_PAUSE, - Q_AUDIO_SKIP, - Q_AUDIO_PRE_FF_REWIND, - Q_AUDIO_FF_REWIND, - Q_AUDIO_FLUSH, - Q_AUDIO_DIR_SKIP, - - /* pcmbuf -> audio */ - Q_AUDIO_TRACK_CHANGED, - - /* audio -> audio */ - Q_AUDIO_FILL_BUFFER, /* continue buffering next track */ - - /* buffering -> audio */ - Q_AUDIO_BUFFERING, /* some buffer event */ - Q_AUDIO_FINISH_LOAD_TRACK, /* metadata is buffered */ - Q_AUDIO_HANDLE_FINISHED, /* some other type is buffered */ - - /* codec -> audio (*) */ - Q_AUDIO_CODEC_SEEK_COMPLETE, - Q_AUDIO_CODEC_COMPLETE, - - /* audio -> codec */ - Q_CODEC_LOAD, - Q_CODEC_RUN, - Q_CODEC_PAUSE, - Q_CODEC_SEEK, - Q_CODEC_STOP, - Q_CODEC_UNLOAD, - - - /*- miscellanous -*/ -#ifdef AUDIO_HAVE_RECORDING - /* -> codec */ - Q_AUDIO_LOAD_ENCODER, /* load an encoder for recording */ -#endif - /* -> codec */ - Q_CODEC_DO_CALLBACK, - - - /*- settings -*/ - -#ifdef HAVE_DISK_STORAGE - /* -> audio */ - Q_AUDIO_UPDATE_WATERMARK, /* buffering watermark needs updating */ -#endif - /* -> audio */ - Q_AUDIO_REMAKE_AUDIO_BUFFER, /* buffer needs to be reinitialized */ -}; - -/* (*) If you change these, you must check audio_clear_track_notifications - in playback.c for correctness */ +unsigned int playback_status(void); #endif /* _PLAYBACK_H */ diff --git a/apps/recorder/pcm_record.c b/apps/recorder/pcm_record.c index fe7a54a565..a45dcc2d11 100644 --- a/apps/recorder/pcm_record.c +++ b/apps/recorder/pcm_record.c @@ -38,11 +38,13 @@ #ifdef HAVE_SPDIF_IN #include "spdif.h" #endif +#include "audio_thread.h" /***************************************************************************/ +extern struct event_queue audio_queue; + /** General recording state **/ -static bool is_initialized = false; /* Subsystem ready? */ static bool is_recording; /* We are recording */ static bool is_paused; /* We have paused */ static unsigned long errors; /* An error has occured */ @@ -230,14 +232,6 @@ enum /***************************************************************************/ -static struct event_queue pcmrec_queue SHAREDBSS_ATTR; -static struct queue_sender_list pcmrec_queue_send SHAREDBSS_ATTR; -static long pcmrec_stack[3*DEFAULT_STACK_SIZE/sizeof(long)]; -static const char pcmrec_thread_name[] = "pcmrec"; -static unsigned int pcmrec_thread_id = 0; - -static void pcmrec_thread(void); - enum { PCMREC_NULL = 0, @@ -248,14 +242,23 @@ enum PCMREC_STOP, /* stop the current recording */ PCMREC_PAUSE, /* pause the current recording */ PCMREC_RESUME, /* resume the current recording */ -#if 0 - PCMREC_FLUSH_NUM, /* flush a number of files out */ -#endif }; /*******************************************************************/ -/* Functions that are not executing in the pcmrec_thread first */ +/* Functions that are not executing in the audio thread first */ /*******************************************************************/ + +static void pcmrec_raise_error_status(unsigned long e) +{ + pcm_rec_lock(); /* DMA sets this too */ + errors |= e; + pcm_rec_unlock(); +} + +static void pcmrec_raise_warning_status(unsigned long w) +{ + warnings |= w; +} /* Callback for when more data is ready - called in interrupt context */ static void pcm_rec_have_more(void **start, size_t *size) @@ -268,7 +271,7 @@ static void pcm_rec_have_more(void **start, size_t *size) /* set pcm ovf if processing start position is inside current write chunk */ if ((unsigned)(pcm_enc_pos - next_pos) < PCM_CHUNK_SIZE) - warnings |= PCMREC_W_PCM_BUFFER_OVF; + pcmrec_raise_warning_status(PCMREC_W_PCM_BUFFER_OVF); dma_wr_pos = next_pos; } @@ -285,7 +288,7 @@ static enum pcm_dma_status pcm_rec_status_callback(enum pcm_dma_status status) if (status == PCM_DMAST_ERR_DMA) { /* Flush recorded data to disk and stop recording */ - queue_post(&pcmrec_queue, PCMREC_STOP, 0); + errors |= PCMREC_E_DMA; return status; } /* else try again next transmission - frame is invalid */ @@ -315,9 +318,9 @@ void pcm_rec_error_clear(void) /** * Check mode, errors and warnings */ -unsigned long pcm_rec_status(void) +unsigned int pcm_rec_status(void) { - unsigned long ret = 0; + unsigned int ret = 0; if (is_recording) ret |= AUDIO_STATUS_RECORD; @@ -379,20 +382,6 @@ unsigned long pcm_rec_sample_rate(void) } /* audio_get_sample_rate */ #endif -/** - * Creates pcmrec_thread - */ -void pcm_rec_init(void) -{ - queue_init(&pcmrec_queue, true); - pcmrec_thread_id = - create_thread(pcmrec_thread, pcmrec_stack, sizeof(pcmrec_stack), - 0, pcmrec_thread_name IF_PRIO(, PRIORITY_RECORDING) - IF_COP(, CPU)); - queue_enable_queue_send(&pcmrec_queue, &pcmrec_queue_send, - pcmrec_thread_id); -} /* pcm_rec_init */ - /** audio_* group **/ /** @@ -401,7 +390,7 @@ void pcm_rec_init(void) void audio_init_recording(void) { logf("audio_init_recording"); - queue_send(&pcmrec_queue, PCMREC_INIT, 0); + queue_send(&audio_queue, Q_AUDIO_INIT_RECORDING, 1); logf("audio_init_recording done"); } /* audio_init_recording */ @@ -411,7 +400,7 @@ void audio_init_recording(void) void audio_close_recording(void) { logf("audio_close_recording"); - queue_send(&pcmrec_queue, PCMREC_CLOSE, 0); + queue_send(&audio_queue, Q_AUDIO_CLOSE_RECORDING, 0); logf("audio_close_recording done"); } /* audio_close_recording */ @@ -421,7 +410,7 @@ void audio_close_recording(void) void audio_set_recording_options(struct audio_recording_options *options) { logf("audio_set_recording_options"); - queue_send(&pcmrec_queue, PCMREC_OPTIONS, (intptr_t)options); + queue_send(&audio_queue, Q_AUDIO_RECORDING_OPTIONS, (intptr_t)options); logf("audio_set_recording_options done"); } /* audio_set_recording_options */ @@ -432,7 +421,7 @@ void audio_record(const char *filename) { logf("audio_record: %s", filename); flush_interrupt(); - queue_send(&pcmrec_queue, PCMREC_RECORD, (intptr_t)filename); + queue_send(&audio_queue, Q_AUDIO_RECORD, (intptr_t)filename); logf("audio_record_done"); } /* audio_record */ @@ -451,7 +440,7 @@ void audio_stop_recording(void) { logf("audio_stop_recording"); flush_interrupt(); - queue_post(&pcmrec_queue, PCMREC_STOP, 0); + queue_post(&audio_queue, Q_AUDIO_STOP, 0); logf("audio_stop_recording done"); } /* audio_stop_recording */ @@ -462,7 +451,7 @@ void audio_pause_recording(void) { logf("audio_pause_recording"); flush_interrupt(); - queue_post(&pcmrec_queue, PCMREC_PAUSE, 0); + queue_post(&audio_queue, Q_AUDIO_PAUSE, 0); logf("audio_pause_recording done"); } /* audio_pause_recording */ @@ -472,7 +461,7 @@ void audio_pause_recording(void) void audio_resume_recording(void) { logf("audio_resume_recording"); - queue_post(&pcmrec_queue, PCMREC_RESUME, 0); + queue_post(&audio_queue, Q_AUDIO_RESUME, 0); logf("audio_resume_recording done"); } /* audio_resume_recording */ @@ -517,10 +506,46 @@ unsigned long audio_num_recorded_bytes(void) /***************************************************************************/ /* */ -/* Functions that execute in the context of pcmrec_thread */ +/* Functions that execute in the context of audio thread */ /* */ /***************************************************************************/ +static void pcmrec_init_state(void) +{ + flush_interrupts = 0; + + /* warings and errors */ + warnings = + errors = 0; + + /* pcm FIFO */ + dma_lock = true; + pcm_rd_pos = 0; + dma_wr_pos = 0; + pcm_enc_pos = 0; + + /* encoder FIFO */ + enc_wr_index = 0; + enc_rd_index = 0; + + /* filename queue */ + fnq_rd_pos = 0; + fnq_wr_pos = 0; + + /* stats */ + num_rec_bytes = 0; + num_rec_samples = 0; +#if 0 + accum_rec_bytes = 0; + accum_pcm_samples = 0; +#endif + + pre_record_ticks = 0; + + is_recording = false; + is_paused = false; +} /* pcmrec_init_state */ + /** Filename Queue **/ /* returns true if the queue is empty */ @@ -594,7 +619,7 @@ static void pcmrec_close_file(int *fd_p) return; /* preserve error */ if (close(*fd_p) != 0) - errors |= PCMREC_E_IO; + pcmrec_raise_error_status(PCMREC_E_IO); *fd_p = -1; } /* pcmrec_close_file */ @@ -646,7 +671,7 @@ static void pcmrec_start_file(void) { logf("start file: fnq empty"); *filename = '\0'; - errors |= PCMREC_E_FNQ_DESYNC; + pcmrec_raise_error_status(PCMREC_E_FNQ_DESYNC); } else if (errors != 0) { @@ -656,7 +681,7 @@ static void pcmrec_start_file(void) { /* Any previous file should have been closed */ logf("start file: file already open"); - errors |= PCMREC_E_FNQ_DESYNC; + pcmrec_raise_error_status(PCMREC_E_FNQ_DESYNC); } if (errors != 0) @@ -671,7 +696,7 @@ static void pcmrec_start_file(void) if (errors == 0 && (rec_fdata.chunk->flags & CHUNKF_ERROR)) { logf("start file: enc error"); - errors |= PCMREC_E_ENCODER; + pcmrec_raise_error_status(PCMREC_E_ENCODER); } if (errors != 0) @@ -706,7 +731,7 @@ static inline void pcmrec_write_chunk(void) { logf("wr chk enc error %lu %lu", rec_fdata.chunk->enc_size, rec_fdata.chunk->num_pcm); - errors |= PCMREC_E_ENCODER; + pcmrec_raise_error_status(PCMREC_E_ENCODER); } } /* pcmrec_write_chunk */ @@ -725,7 +750,7 @@ static void pcmrec_end_file(void) if (rec_fdata.chunk->flags & CHUNKF_ERROR) { logf("end file: enc error"); - errors |= PCMREC_E_ENCODER; + pcmrec_raise_error_status(PCMREC_E_ENCODER); } else { @@ -946,7 +971,7 @@ static void pcmrec_flush(unsigned flush_num) /* sync file */ if (rec_fdata.rec_file >= 0 && fsync(rec_fdata.rec_file) != 0) - errors |= PCMREC_E_IO; + pcmrec_raise_error_status(PCMREC_E_IO); cpu_boost(false); @@ -1001,7 +1026,7 @@ static void pcmrec_new_stream(const char *filename, /* next file name */ if (filename) strlcpy(path, filename, MAX_PATH); - queue_reply(&pcmrec_queue, 0); /* We have all we need */ + queue_reply(&audio_queue, 0); /* We have all we need */ data.pre_chunk = NULL; data.chunk = GET_ENC_CHUNK(enc_wr_index); @@ -1129,51 +1154,18 @@ static void pcmrec_new_stream(const char *filename, /* next file name */ pcmrec_flush(PCMREC_FLUSH_IF_HIGH); } /* pcmrec_new_stream */ + /** event handlers for pcmrec thread */ /* PCMREC_INIT */ static void pcmrec_init(void) { - is_initialized = true; - - unsigned char *buffer; send_event(RECORDING_EVENT_START, NULL); - - /* warings and errors */ - warnings = - errors = 0; - pcmrec_close_file(&rec_fdata.rec_file); - rec_fdata.rec_file = -1; - /* pcm FIFO */ - dma_lock = true; - pcm_rd_pos = 0; - dma_wr_pos = 0; - pcm_enc_pos = 0; + pcmrec_init_state(); - /* encoder FIFO */ - enc_wr_index = 0; - enc_rd_index = 0; - - /* filename queue */ - fnq_rd_pos = 0; - fnq_wr_pos = 0; - - /* stats */ - num_rec_bytes = 0; - num_rec_samples = 0; -#if 0 - accum_rec_bytes = 0; - accum_pcm_samples = 0; -#endif - - pre_record_ticks = 0; - - is_recording = false; - is_paused = false; - - buffer = audio_get_recording_buffer(&rec_buffer_size); + unsigned char *buffer = audio_get_buffer(true, &rec_buffer_size); /* Line align pcm_buffer 2^5=32 bytes */ pcm_buffer = (unsigned char *)ALIGN_UP_P2((uintptr_t)buffer, 5); @@ -1188,23 +1180,25 @@ static void pcmrec_init(void) /* PCMREC_CLOSE */ static void pcmrec_close(void) { - is_initialized = false; dma_lock = true; pre_record_ticks = 0; /* Can't be prerecording any more */ warnings = 0; + codec_unload(); pcm_close_recording(); reset_hardware(); - audio_remove_encoder(); send_event(RECORDING_EVENT_STOP, NULL); } /* pcmrec_close */ /* PCMREC_OPTIONS */ static void pcmrec_set_recording_options( + struct event_queue *q, struct audio_recording_options *options) { - /* stop DMA transfer */ + /* stop everything */ dma_lock = true; + codec_unload(); pcm_stop_recording(); + pcmrec_init_state(); rec_frequency = options->rec_frequency; rec_source = options->rec_source; @@ -1243,10 +1237,13 @@ static void pcmrec_set_recording_options( /* apply hardware setting to start monitoring now */ pcm_apply_settings(); - queue_reply(&pcmrec_queue, 0); /* Release sender */ - - if (audio_load_encoder(enc_config.afmt)) + if (codec_load(-1, enc_config.afmt | CODEC_TYPE_ENCODER)) { + queue_reply(q, true); + + /* run immediately */ + codec_go(); + /* start DMA transfer */ dma_lock = pre_record_ticks == 0; pcm_record_data(pcm_rec_have_more, pcm_rec_status_callback, @@ -1255,7 +1252,7 @@ static void pcmrec_set_recording_options( else { logf("set rec opt: enc load failed"); - errors |= PCMREC_E_LOAD_ENCODER; + pcmrec_raise_error_status(PCMREC_E_LOAD_ENCODER); } } /* pcmrec_set_recording_options */ @@ -1468,97 +1465,65 @@ static void pcmrec_resume(void) logf("pcmrec_resume done"); } /* pcmrec_resume */ -static void pcmrec_thread(void) NORETURN_ATTR; -static void pcmrec_thread(void) +/* Called by audio thread when recording is initialized */ +void audio_recording_handler(struct queue_event *ev) { - struct queue_event ev; + logf("audio recording start"); - logf("thread pcmrec start"); - - while(1) + while (1) { - if (is_recording) + switch (ev->id) { - /* Poll periodically to flush data */ - queue_wait_w_tmo(&pcmrec_queue, &ev, HZ/5); + case Q_AUDIO_INIT_RECORDING: + pcmrec_init(); + break; - if (ev.id == SYS_TIMEOUT) - { - /* Messages that interrupt this will complete it */ - pcmrec_flush(PCMREC_FLUSH_IF_HIGH | - PCMREC_FLUSH_INTERRUPTABLE); - continue; - } - } - else - { - /* Not doing anything - sit and wait for commands */ - queue_wait(&pcmrec_queue, &ev); - - /* Some messages must be handled even if not initialized */ - switch (ev.id) - { - case PCMREC_INIT: - case SYS_USB_CONNECTED: + case SYS_USB_CONNECTED: + if (is_recording) break; - default: - if (!is_initialized) - continue; - } - } + /* Fall-through */ + case Q_AUDIO_CLOSE_RECORDING: + pcmrec_close(); + return; /* no more recording */ - switch (ev.id) - { - case PCMREC_INIT: - pcmrec_init(); - break; + case Q_AUDIO_RECORDING_OPTIONS: + pcmrec_set_recording_options(&audio_queue, + (struct audio_recording_options *)ev->data); + break; - case PCMREC_CLOSE: - pcmrec_close(); - break; + case Q_AUDIO_RECORD: + clear_flush_interrupt(); + pcmrec_record((const char *)ev->data); + break; - case PCMREC_OPTIONS: - pcmrec_set_recording_options( - (struct audio_recording_options *)ev.data); - break; + case Q_AUDIO_STOP: + clear_flush_interrupt(); + pcmrec_stop(); + break; - case PCMREC_RECORD: - clear_flush_interrupt(); - pcmrec_record((const char *)ev.data); - break; + case Q_AUDIO_PAUSE: + clear_flush_interrupt(); + pcmrec_pause(); + break; - case PCMREC_STOP: - clear_flush_interrupt(); - pcmrec_stop(); - break; + case Q_AUDIO_RESUME: + pcmrec_resume(); + break; - case PCMREC_PAUSE: - clear_flush_interrupt(); - pcmrec_pause(); - break; + case SYS_TIMEOUT: + /* Messages that interrupt this will complete it */ + pcmrec_flush(PCMREC_FLUSH_IF_HIGH | + PCMREC_FLUSH_INTERRUPTABLE); - case PCMREC_RESUME: - pcmrec_resume(); - break; -#if 0 - case PCMREC_FLUSH_NUM: - pcmrec_flush((unsigned)ev.data); - break; -#endif - case SYS_USB_CONNECTED: - if (is_recording) - break; - - if (is_initialized) - pcmrec_close(); - - usb_acknowledge(SYS_USB_CONNECTED_ACK); - usb_wait_for_disconnect(&pcmrec_queue); - flush_interrupts = 0; - break; + if (errors & PCMREC_E_DMA) + queue_post(&audio_queue, Q_AUDIO_STOP, 0); + break; } /* end switch */ + + queue_wait_w_tmo(&audio_queue, ev, + is_recording ? HZ/5 : TIMEOUT_BLOCK); } /* end while */ -} /* pcmrec_thread */ +} /* audio_recording_handler */ /****************************************************************************/ /* */ @@ -1696,7 +1661,7 @@ struct enc_chunk_hdr * enc_get_chunk(void) #ifdef DEBUG if (chunk->id != ENC_CHUNK_MAGIC || *wrap_id_p != ENC_CHUNK_MAGIC) { - errors |= PCMREC_E_CHUNK_OVF; + pcmrec_raise_error_status(PCMREC_E_CHUNK_OVF); logf("finish chk ovf: %d", enc_wr_index); } #endif @@ -1718,7 +1683,7 @@ void enc_finish_chunk(void) if ((long)chunk->flags < 0) { /* encoder set error flag */ - errors |= PCMREC_E_ENCODER; + pcmrec_raise_error_status(PCMREC_E_ENCODER); logf("finish chk enc error"); } @@ -1737,7 +1702,7 @@ void enc_finish_chunk(void) else if (is_recording) /* buffer full */ { /* keep current position and put up warning flag */ - warnings |= PCMREC_W_ENC_BUFFER_OVF; + pcmrec_raise_warning_status(PCMREC_W_ENC_BUFFER_OVF); logf("enc_buffer ovf"); DEC_ENC_INDEX(enc_wr_index); if (pcmrec_context) diff --git a/apps/recorder/pcm_record.h b/apps/recorder/pcm_record.h index 1ac6faefb2..bff7881605 100644 --- a/apps/recorder/pcm_record.h +++ b/apps/recorder/pcm_record.h @@ -42,6 +42,8 @@ /* encoder has written past end of allocated space */ #define PCMREC_E_CHUNK_OVF 0x80010000 #endif /* DEBUG */ +/* DMA callback has reported an error */ +#define PCMREC_E_DMA 0x80020000 /** General functions for high level codec recording **/ /* pcm_rec_error_clear is deprecated for general use. audio_error_clear @@ -49,7 +51,7 @@ void pcm_rec_error_clear(void); /* pcm_rec_status is deprecated for general use. audio_status merges the results for consistency with the hardware codec version */ -unsigned long pcm_rec_status(void); +unsigned int pcm_rec_status(void); unsigned long pcm_rec_get_warnings(void); void pcm_rec_init(void) INIT_ATTR; int pcm_rec_current_bitrate(void); diff --git a/apps/recorder/recording.c b/apps/recorder/recording.c index 203c7101b7..53dc5cbf95 100644 --- a/apps/recorder/recording.c +++ b/apps/recorder/recording.c @@ -1074,10 +1074,6 @@ bool recording_screen(bool no_source) #endif #if CONFIG_CODEC == SWCODEC - /* This should be done before touching audio settings */ - while (!pcm_is_initialized()) - sleep(0); - /* recording_menu gets messed up: so prevent manus talking */ talk_disable(true); /* audio_init_recording stops anything playing when it takes the audio @@ -1209,11 +1205,6 @@ bool recording_screen(bool no_source) trig_width[i] = vp_top[i].width - pm_x[i]; } -#if CONFIG_CODEC == SWCODEC - audio_close_recording(); - audio_init_recording(); -#endif - rec_init_recording_options(&rec_options); rec_set_recording_options(&rec_options); diff --git a/firmware/export/thread.h b/firmware/export/thread.h index 577cdc1e61..da395b8ffa 100644 --- a/firmware/export/thread.h +++ b/firmware/export/thread.h @@ -64,23 +64,13 @@ #define IO_PRIORITY_BACKGROUND 32 #if CONFIG_CODEC == SWCODEC - -#ifdef HAVE_RECORDING -#ifdef HAVE_HARDWARE_CLICK -#define BASETHREADS 18 +# ifdef HAVE_HARDWARE_CLICK +# define BASETHREADS 17 +# else +# define BASETHREADS 16 +# endif #else -#define BASETHREADS 17 -#endif -#else -#ifdef HAVE_HARDWARE_CLICK -#define BASETHREADS 17 -#else -#define BASETHREADS 16 -#endif -#endif - -#else -#define BASETHREADS 11 +# define BASETHREADS 11 #endif /* CONFIG_CODE == * */ #ifndef TARGET_EXTRA_THREADS