Refactor audio thread to run both recording and playback.

Eliminates the pcmrec thread and keeps playback and recording engine
operation mutually-exclusive.

audio_thread.c contains the audio thread which branches to the
correct engine depending upon the request. It also handles the main
audio initialization.

Moves pcm_init into main.c just before dsp_init because I don't want
that one in audio_init in the new file.

(Also makes revision df6e1bc pointless ;)

Change-Id: Ifc1db24404e6d8dd9ac42d9f4dfbc207aa9a26e1
This commit is contained in:
Michael Sevakis 2013-05-31 02:41:02 -04:00
parent df6e1bcce5
commit 5857c44017
11 changed files with 553 additions and 564 deletions

View file

@ -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

162
apps/audio_thread.c Normal file
View file

@ -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();
}

102
apps/audio_thread.h Normal file
View file

@ -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 */

View file

@ -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"

View file

@ -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("<audio_init");
#if (CONFIG_CODEC == SWCODEC) && defined(HAVE_RECORDING)
pcm_rec_init();
#endif
/* runtime database has to be initialized after audio_init() */
cpu_boost(false);

View file

@ -27,7 +27,6 @@
#include "core_alloc.h"
#include "sound.h"
#include "ata.h"
#include "usb.h"
#include "codecs.h"
#include "codec_thread.h"
#include "voice_thread.h"
@ -38,6 +37,7 @@
#include "playlist.h"
#include "abrepeat.h"
#include "pcmbuf.h"
#include "audio_thread.h"
#include "playback.h"
#include "misc.h"
#include "settings.h"
@ -46,10 +46,6 @@
#include "tagcache.h"
#endif
#ifdef AUDIO_HAVE_RECORDING
#include "pcm_record.h"
#endif
#ifdef HAVE_LCD_BITMAP
#ifdef HAVE_ALBUMART
#include "albumart.h"
@ -104,8 +100,10 @@
*/
/** Miscellaneous **/
bool audio_is_initialized = false; /* (A,O-) */
extern struct codec_api ci; /* (A,C) */
extern unsigned int audio_thread_id; /* from audio_thread.c */
extern struct event_queue audio_queue; /* from audio_thread.c */
extern bool audio_is_initialized; /* from audio_thread.c */
extern struct codec_api ci; /* from codecs.c */
/** Possible arrangements of the main buffer **/
static enum audio_buffer_state
@ -190,7 +188,6 @@ static enum filling_state
STATE_FINISHED, /* all remaining tracks are fully buffered */
STATE_ENDING, /* audio playback is ending */
STATE_ENDED, /* audio playback is done */
STATE_USB, /* USB mode, ignore most messages */
} filling = STATE_IDLE;
/* Track info - holds information about each track in the buffer */
@ -330,15 +327,6 @@ static int codec_skip_status;
static bool codec_seeking = false; /* Codec seeking ack expected? */
static unsigned int position_key = 0;
/* Event queues */
static struct event_queue audio_queue SHAREDBSS_ATTR;
/* Audio thread */
static struct queue_sender_list audio_queue_sender_list SHAREDBSS_ATTR;
static long audio_stack[(DEFAULT_STACK_SIZE + 0x1000)/sizeof(long)];
static const char audio_thread_name[] = "audio";
static unsigned int audio_thread_id = 0;
/* Forward declarations */
enum audio_start_playback_flags
{
@ -2985,37 +2973,131 @@ static void audio_on_audio_flush(void)
}
}
#ifdef AUDIO_HAVE_RECORDING
/* Load the requested encoder type
(Q_AUDIO_LOAD_ENCODER) */
static void audio_on_load_encoder(int afmt)
/* Called by audio thread when playback is started */
void audio_playback_handler(struct queue_event *ev)
{
bool res = true;
if (play_status != PLAY_STOPPED)
audio_stop_playback(); /* Can't load both types at once */
else
codec_unload(); /* Encoder still loaded, stop and unload it */
if (afmt != AFMT_UNKNOWN)
{
res = codec_load(-1, afmt | CODEC_TYPE_ENCODER);
if (res)
codec_go(); /* These are run immediately */
}
queue_reply(&audio_queue, res);
}
#endif /* AUDIO_HAVE_RECORDING */
static void audio_thread(void)
{
struct queue_event ev;
pcm_postinit();
while (1)
{
switch (ev->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

View file

@ -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 */

View file

@ -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)

View file

@ -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);

View file

@ -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);

View file

@ -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