2009-10-31 19:17:36 +00:00
|
|
|
/***************************************************************************
|
|
|
|
* __________ __ ___.
|
|
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
|
|
* \/ \/ \/ \/ \/
|
|
|
|
* $Id$
|
|
|
|
*
|
|
|
|
* Copyright (C) 2005-2007 Miika Pekkarinen
|
|
|
|
* Copyright (C) 2007-2008 Nicolas Pennequin
|
2011-04-27 03:08:23 +00:00
|
|
|
* Copyright (C) 2011 Michael Sevakis
|
2009-10-31 19:17:36 +00:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
****************************************************************************/
|
2014-01-05 00:22:19 +00:00
|
|
|
|
2011-02-23 14:31:13 +00:00
|
|
|
#include "config.h"
|
|
|
|
#include "system.h"
|
2009-10-31 19:17:36 +00:00
|
|
|
#include "kernel.h"
|
|
|
|
#include "codecs.h"
|
2011-04-27 03:08:23 +00:00
|
|
|
#include "codec_thread.h"
|
2009-10-31 19:17:36 +00:00
|
|
|
#include "pcmbuf.h"
|
2013-05-31 06:41:02 +00:00
|
|
|
#include "audio_thread.h"
|
2011-04-27 03:08:23 +00:00
|
|
|
#include "playback.h"
|
|
|
|
#include "buffering.h"
|
2012-04-29 21:31:30 +00:00
|
|
|
#include "dsp_core.h"
|
2009-10-31 19:17:36 +00:00
|
|
|
#include "metadata.h"
|
2011-08-30 19:40:09 +00:00
|
|
|
#include "settings.h"
|
2009-10-31 19:17:36 +00:00
|
|
|
|
|
|
|
/* Define LOGF_ENABLE to enable logf output in this file */
|
|
|
|
/*#define LOGF_ENABLE*/
|
|
|
|
#include "logf.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 PLAYBACK_LOGQUEUES
|
|
|
|
/* Define this to logf SYS_TIMEOUT messages */
|
|
|
|
/*#define PLAYBACK_LOGQUEUES_SYS_TIMEOUT*/
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef PLAYBACK_LOGQUEUES
|
|
|
|
#define LOGFQUEUE logf
|
|
|
|
#else
|
|
|
|
#define LOGFQUEUE(...)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef PLAYBACK_LOGQUEUES_SYS_TIMEOUT
|
|
|
|
#define LOGFQUEUE_SYS_TIMEOUT logf
|
|
|
|
#else
|
|
|
|
#define LOGFQUEUE_SYS_TIMEOUT(...)
|
|
|
|
#endif
|
|
|
|
|
2009-11-01 19:39:23 +00:00
|
|
|
/* Variables are commented with the threads that use them:
|
2011-04-27 03:08:23 +00:00
|
|
|
* A=audio, C=codec
|
|
|
|
* - = reads only
|
|
|
|
*
|
2009-11-01 19:39:23 +00:00
|
|
|
* Unless otherwise noted, the extern variables are located
|
|
|
|
* in playback.c.
|
|
|
|
*/
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Q_LOAD_CODEC parameter data */
|
|
|
|
struct codec_load_info
|
|
|
|
{
|
|
|
|
int hid; /* audio handle id (specify < 0 to use afmt) */
|
|
|
|
int afmt; /* codec specification (AFMT_*) */
|
|
|
|
};
|
2009-10-31 19:17:36 +00:00
|
|
|
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/** --- Main state control --- **/
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
static int codec_type = AFMT_UNKNOWN; /* Codec type (C,A-) */
|
2009-11-01 19:39:23 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Private interfaces to main playback control */
|
2011-08-28 07:45:35 +00:00
|
|
|
extern void audio_codec_update_elapsed(unsigned long elapsed);
|
|
|
|
extern void audio_codec_update_offset(size_t offset);
|
|
|
|
extern void audio_codec_complete(int status);
|
|
|
|
extern void audio_codec_seek_complete(void);
|
2009-11-01 19:39:23 +00:00
|
|
|
extern struct codec_api ci; /* from codecs.c */
|
2009-10-31 19:17:36 +00:00
|
|
|
|
|
|
|
/* Codec thread */
|
2011-02-23 14:31:13 +00:00
|
|
|
static unsigned int codec_thread_id; /* For modifying thread priority later */
|
|
|
|
static struct event_queue codec_queue SHAREDBSS_ATTR;
|
2011-02-14 11:27:45 +00:00
|
|
|
static struct queue_sender_list codec_queue_sender_list SHAREDBSS_ATTR;
|
2018-10-09 18:24:34 +00:00
|
|
|
|
2018-08-19 08:42:04 +00:00
|
|
|
/* Workaround stack overflow in opus codec on highmem devices (see FS#13060). */
|
2018-10-09 18:24:34 +00:00
|
|
|
#if !defined(CPU_COLDFIRE) && (MEMORYSIZE >= 8) && defined(IRAMSIZE) && IRAMSIZE > (32 * 1024)
|
|
|
|
#define WORKAROUND_FS13060 0x800
|
|
|
|
#else
|
|
|
|
#define WORKAROUND_FS13060 0
|
|
|
|
#endif
|
|
|
|
|
2018-08-19 08:42:04 +00:00
|
|
|
static long codec_stack[(DEFAULT_STACK_SIZE + 0x2000 + WORKAROUND_FS13060)/sizeof(long)] IBSS_ATTR;
|
2009-10-31 19:17:36 +00:00
|
|
|
static const char codec_thread_name[] = "codec";
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
static void unload_codec(void);
|
|
|
|
|
|
|
|
/* Messages are only ever sent one at a time to the codec from the audio
|
|
|
|
thread. This is important for correct operation unless playback is
|
|
|
|
stopped. */
|
|
|
|
|
2011-02-23 14:31:13 +00:00
|
|
|
/* static routines */
|
|
|
|
static void codec_queue_ack(intptr_t ackme)
|
|
|
|
{
|
|
|
|
queue_reply(&codec_queue, ackme);
|
|
|
|
}
|
2009-11-01 19:39:23 +00:00
|
|
|
|
2011-02-23 14:31:13 +00:00
|
|
|
static intptr_t codec_queue_send(long id, intptr_t data)
|
|
|
|
{
|
|
|
|
return queue_send(&codec_queue, id, data);
|
|
|
|
}
|
2009-11-01 19:39:23 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Poll the state of the codec queue. Returns < 0 if the message is urgent
|
|
|
|
and any state should exit, > 0 if it's a run message (and it was
|
|
|
|
scrubbed), 0 if message was ignored. */
|
|
|
|
static int codec_check_queue__have_msg(void)
|
|
|
|
{
|
|
|
|
struct queue_event ev;
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
queue_peek(&codec_queue, &ev);
|
2009-11-01 19:39:23 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Seek, pause or stop? Just peek and return if so. Codec
|
|
|
|
must handle the command after returing. Inserts will not
|
|
|
|
be allowed until it complies. */
|
|
|
|
switch (ev.id)
|
|
|
|
{
|
|
|
|
case Q_CODEC_SEEK:
|
2018-09-21 18:27:12 +00:00
|
|
|
LOGFQUEUE("codec - Q_CODEC_SEEK %ld", ev.id);
|
2011-04-27 03:08:23 +00:00
|
|
|
return -1;
|
|
|
|
case Q_CODEC_PAUSE:
|
2018-09-21 18:27:12 +00:00
|
|
|
LOGFQUEUE("codec - Q_CODEC_PAUSE %ld", ev.id);
|
2011-04-27 03:08:23 +00:00
|
|
|
return -1;
|
|
|
|
case Q_CODEC_STOP:
|
2018-09-21 18:27:12 +00:00
|
|
|
LOGFQUEUE("codec - Q_CODEC_STOP %ld", ev.id);
|
2011-04-27 03:08:23 +00:00
|
|
|
return -1;
|
2009-11-01 19:39:23 +00:00
|
|
|
}
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* This is in error in this context unless it's "go, go, go!" */
|
|
|
|
queue_wait(&codec_queue, &ev);
|
|
|
|
|
|
|
|
if (ev.id == Q_CODEC_RUN)
|
|
|
|
{
|
|
|
|
logf("codec < Q_CODEC_RUN: already running!");
|
|
|
|
codec_queue_ack(Q_CODEC_RUN);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Ignore it */
|
|
|
|
logf("codec < bad req %ld (%s)", ev.id, __func__);
|
|
|
|
codec_queue_ack(Q_NULL);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Does the audio format type equal CODEC_TYPE_ENCODER? */
|
|
|
|
static inline bool type_is_encoder(int afmt)
|
|
|
|
{
|
|
|
|
#ifdef AUDIO_HAVE_RECORDING
|
|
|
|
return (afmt & CODEC_TYPE_MASK) == CODEC_TYPE_ENCODER;
|
|
|
|
#else
|
|
|
|
return false;
|
|
|
|
(void)afmt;
|
|
|
|
#endif
|
2009-10-31 19:17:36 +00:00
|
|
|
}
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/**************************************/
|
|
|
|
|
|
|
|
|
|
|
|
/** --- Miscellaneous external functions --- **/
|
|
|
|
const char * get_codec_filename(int cod_spec)
|
2009-10-31 19:17:36 +00:00
|
|
|
{
|
|
|
|
const char *fname;
|
|
|
|
|
|
|
|
#ifdef HAVE_RECORDING
|
|
|
|
/* Can choose decoder or encoder if one available */
|
|
|
|
int type = cod_spec & CODEC_TYPE_MASK;
|
|
|
|
int afmt = cod_spec & CODEC_AFMT_MASK;
|
|
|
|
|
|
|
|
if ((unsigned)afmt >= AFMT_NUM_CODECS)
|
|
|
|
type = AFMT_UNKNOWN | (type & CODEC_TYPE_MASK);
|
|
|
|
|
|
|
|
fname = (type == CODEC_TYPE_ENCODER) ?
|
|
|
|
audio_formats[afmt].codec_enc_root_fn :
|
|
|
|
audio_formats[afmt].codec_root_fn;
|
|
|
|
|
|
|
|
logf("%s: %d - %s",
|
|
|
|
(type == CODEC_TYPE_ENCODER) ? "Encoder" : "Decoder",
|
|
|
|
afmt, fname ? fname : "<unknown>");
|
|
|
|
#else /* !HAVE_RECORDING */
|
|
|
|
/* Always decoder */
|
|
|
|
if ((unsigned)cod_spec >= AFMT_NUM_CODECS)
|
|
|
|
cod_spec = AFMT_UNKNOWN;
|
|
|
|
fname = audio_formats[cod_spec].codec_root_fn;
|
|
|
|
logf("Codec: %d - %s", cod_spec, fname ? fname : "<unknown>");
|
|
|
|
#endif /* HAVE_RECORDING */
|
|
|
|
|
|
|
|
return fname;
|
2011-04-27 03:08:23 +00:00
|
|
|
}
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2009-11-01 19:39:23 +00:00
|
|
|
/* Borrow the codec thread and return the ID */
|
|
|
|
void codec_thread_do_callback(void (*fn)(void), unsigned int *id)
|
|
|
|
{
|
|
|
|
/* Set id before telling thread to call something; it may be
|
|
|
|
* needed before this function returns. */
|
|
|
|
if (id != NULL)
|
|
|
|
*id = codec_thread_id;
|
|
|
|
|
|
|
|
/* Codec thread will signal just before entering callback */
|
|
|
|
LOGFQUEUE("codec >| Q_CODEC_DO_CALLBACK");
|
2011-02-23 14:31:13 +00:00
|
|
|
codec_queue_send(Q_CODEC_DO_CALLBACK, (intptr_t)fn);
|
2009-11-01 19:39:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/** --- codec API callbacks --- **/
|
2009-11-01 19:39:23 +00:00
|
|
|
|
2009-11-11 07:02:18 +00:00
|
|
|
static void codec_pcmbuf_insert_callback(
|
2009-10-31 19:17:36 +00:00
|
|
|
const void *ch1, const void *ch2, int count)
|
|
|
|
{
|
2012-03-27 23:52:15 +00:00
|
|
|
struct dsp_buffer src;
|
|
|
|
src.remcount = count;
|
|
|
|
src.pin[0] = ch1;
|
|
|
|
src.pin[1] = ch2;
|
|
|
|
src.proc_mask = 0;
|
|
|
|
|
2012-05-13 07:25:55 +00:00
|
|
|
while (LIKELY(queue_empty(&codec_queue)) ||
|
|
|
|
codec_check_queue__have_msg() >= 0)
|
2009-10-31 19:17:36 +00:00
|
|
|
{
|
2012-03-27 23:52:15 +00:00
|
|
|
struct dsp_buffer dst;
|
|
|
|
dst.remcount = 0;
|
|
|
|
dst.bufcount = MAX(src.remcount, 1024); /* Arbitrary min request */
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2012-05-13 07:25:55 +00:00
|
|
|
if ((dst.p16out = pcmbuf_request_buffer(&dst.bufcount)) == NULL)
|
2009-10-31 19:17:36 +00:00
|
|
|
{
|
|
|
|
cancel_cpu_boost();
|
2011-04-27 03:08:23 +00:00
|
|
|
|
2012-03-27 23:52:15 +00:00
|
|
|
/* It may be awhile before space is available but we want
|
2011-04-27 03:08:23 +00:00
|
|
|
"instant" response to any message */
|
|
|
|
queue_wait_w_tmo(&codec_queue, NULL, HZ/20);
|
2012-05-13 07:25:55 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dsp_process(ci.dsp, &src, &dst);
|
2011-04-27 03:08:23 +00:00
|
|
|
|
2012-05-13 07:25:55 +00:00
|
|
|
if (dst.remcount > 0)
|
|
|
|
{
|
|
|
|
pcmbuf_write_complete(dst.remcount, ci.id3->elapsed,
|
|
|
|
ci.id3->offset);
|
|
|
|
}
|
|
|
|
else if (src.remcount <= 0)
|
2012-03-27 23:52:15 +00:00
|
|
|
{
|
2012-05-13 07:25:55 +00:00
|
|
|
return; /* No input remains and DSP purged */
|
2012-03-27 23:52:15 +00:00
|
|
|
}
|
2009-10-31 19:17:36 +00:00
|
|
|
}
|
2013-06-22 20:41:16 +00:00
|
|
|
}
|
2011-04-27 03:08:23 +00:00
|
|
|
}
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* helper function, not a callback */
|
|
|
|
static bool codec_advance_buffer_counters(size_t amount)
|
2009-10-31 19:17:36 +00:00
|
|
|
{
|
2011-04-27 03:08:23 +00:00
|
|
|
if (bufadvance(ci.audio_hid, amount) < 0)
|
2009-10-31 19:17:36 +00:00
|
|
|
{
|
2011-04-27 03:08:23 +00:00
|
|
|
ci.curpos = ci.filesize;
|
|
|
|
return false;
|
2009-10-31 19:17:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ci.curpos += amount;
|
2011-04-27 03:08:23 +00:00
|
|
|
return true;
|
2009-10-31 19:17:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* copy up-to size bytes into ptr and return the actual size copied */
|
|
|
|
static size_t codec_filebuf_callback(void *ptr, size_t size)
|
|
|
|
{
|
2011-04-27 03:08:23 +00:00
|
|
|
ssize_t copy_n = bufread(ci.audio_hid, size, ptr);
|
2009-10-31 19:17:36 +00:00
|
|
|
|
|
|
|
/* Nothing requested OR nothing left */
|
2011-04-27 03:08:23 +00:00
|
|
|
if (copy_n <= 0)
|
2009-10-31 19:17:36 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Update read and other position pointers */
|
|
|
|
codec_advance_buffer_counters(copy_n);
|
|
|
|
|
|
|
|
/* Return the actual amount of data copied to the buffer */
|
|
|
|
return copy_n;
|
2011-04-27 03:08:23 +00:00
|
|
|
}
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
static void * codec_request_buffer_callback(size_t *realsize, size_t reqsize)
|
2009-10-31 19:17:36 +00:00
|
|
|
{
|
|
|
|
size_t copy_n = reqsize;
|
|
|
|
ssize_t ret;
|
|
|
|
void *ptr;
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
ret = bufgetdata(ci.audio_hid, reqsize, &ptr);
|
2009-10-31 19:17:36 +00:00
|
|
|
if (ret >= 0)
|
|
|
|
copy_n = MIN((size_t)ret, reqsize);
|
2011-02-23 14:31:13 +00:00
|
|
|
else
|
|
|
|
copy_n = 0;
|
2009-10-31 19:17:36 +00:00
|
|
|
|
|
|
|
if (copy_n == 0)
|
2011-02-23 14:31:13 +00:00
|
|
|
ptr = NULL;
|
2009-10-31 19:17:36 +00:00
|
|
|
|
|
|
|
*realsize = copy_n;
|
|
|
|
return ptr;
|
2011-04-27 03:08:23 +00:00
|
|
|
}
|
2009-10-31 19:17:36 +00:00
|
|
|
|
|
|
|
static void codec_advance_buffer_callback(size_t amount)
|
|
|
|
{
|
2011-04-27 03:08:23 +00:00
|
|
|
if (!codec_advance_buffer_counters(amount))
|
|
|
|
return;
|
|
|
|
|
|
|
|
audio_codec_update_offset(ci.curpos);
|
2009-10-31 19:17:36 +00:00
|
|
|
}
|
|
|
|
|
2009-11-01 19:39:23 +00:00
|
|
|
static bool codec_seek_buffer_callback(size_t newpos)
|
|
|
|
{
|
|
|
|
logf("codec_seek_buffer_callback");
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
int ret = bufseek(ci.audio_hid, newpos);
|
|
|
|
if (ret == 0)
|
|
|
|
{
|
2009-11-01 19:39:23 +00:00
|
|
|
ci.curpos = newpos;
|
|
|
|
return true;
|
|
|
|
}
|
2011-04-27 03:08:23 +00:00
|
|
|
|
|
|
|
return false;
|
2009-11-01 19:39:23 +00:00
|
|
|
}
|
|
|
|
|
2009-10-31 19:17:36 +00:00
|
|
|
static void codec_seek_complete_callback(void)
|
|
|
|
{
|
|
|
|
logf("seek_complete");
|
|
|
|
|
2011-02-23 14:31:13 +00:00
|
|
|
/* Clear DSP */
|
|
|
|
dsp_configure(ci.dsp, DSP_FLUSH, 0);
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-08-28 07:45:35 +00:00
|
|
|
/* Sync position */
|
|
|
|
audio_codec_update_offset(ci.curpos);
|
|
|
|
|
2011-02-23 14:31:13 +00:00
|
|
|
/* Post notification to audio thread */
|
2011-08-28 07:45:35 +00:00
|
|
|
audio_codec_seek_complete();
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Wait for urgent or go message */
|
|
|
|
do
|
|
|
|
{
|
|
|
|
queue_wait(&codec_queue, NULL);
|
|
|
|
}
|
|
|
|
while (codec_check_queue__have_msg() == 0);
|
2009-11-01 19:39:23 +00:00
|
|
|
}
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
static void codec_configure_callback(int setting, intptr_t value)
|
2009-11-01 19:39:23 +00:00
|
|
|
{
|
2012-03-27 23:52:15 +00:00
|
|
|
dsp_configure(ci.dsp, setting, value);
|
2011-04-27 03:08:23 +00:00
|
|
|
}
|
2009-11-01 19:39:23 +00:00
|
|
|
|
2017-12-07 18:21:57 +00:00
|
|
|
static long codec_get_command_callback(intptr_t *param)
|
2011-04-27 03:08:23 +00:00
|
|
|
{
|
|
|
|
yield();
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
if (LIKELY(queue_empty(&codec_queue)))
|
|
|
|
return CODEC_ACTION_NULL; /* As you were */
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Process the message - return requested action and data (if any should
|
|
|
|
be expected) */
|
|
|
|
while (1)
|
2011-02-23 14:31:13 +00:00
|
|
|
{
|
2017-12-07 18:21:57 +00:00
|
|
|
long action = CODEC_ACTION_NULL;
|
2011-04-27 03:08:23 +00:00
|
|
|
struct queue_event ev;
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2012-05-13 07:25:55 +00:00
|
|
|
queue_peek(&codec_queue, &ev); /* Find out what it is */
|
|
|
|
|
2013-06-22 20:41:16 +00:00
|
|
|
intptr_t id = ev.id;
|
2012-05-13 07:25:55 +00:00
|
|
|
|
|
|
|
switch (id)
|
2011-04-27 03:08:23 +00:00
|
|
|
{
|
2013-06-22 20:41:16 +00:00
|
|
|
case Q_NULL:
|
|
|
|
LOGFQUEUE("codec < Q_NULL");
|
|
|
|
break;
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
case Q_CODEC_RUN: /* Already running */
|
|
|
|
LOGFQUEUE("codec < Q_CODEC_RUN");
|
|
|
|
break;
|
2009-11-01 19:39:23 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
case Q_CODEC_PAUSE: /* Stay here and wait */
|
|
|
|
LOGFQUEUE("codec < Q_CODEC_PAUSE");
|
2012-05-13 07:25:55 +00:00
|
|
|
queue_wait(&codec_queue, &ev); /* Remove message */
|
2011-04-27 03:08:23 +00:00
|
|
|
codec_queue_ack(Q_CODEC_PAUSE);
|
2012-05-13 07:25:55 +00:00
|
|
|
queue_wait(&codec_queue, NULL); /* Wait for next (no remove) */
|
2011-04-27 03:08:23 +00:00
|
|
|
continue;
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
case Q_CODEC_SEEK: /* Audio wants codec to seek */
|
|
|
|
LOGFQUEUE("codec < Q_CODEC_SEEK %ld", ev.data);
|
|
|
|
*param = ev.data;
|
|
|
|
action = CODEC_ACTION_SEEK_TIME;
|
2012-05-13 07:25:55 +00:00
|
|
|
trigger_cpu_boost();
|
2011-04-27 03:08:23 +00:00
|
|
|
break;
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
case Q_CODEC_STOP: /* Must only return 0 in main loop */
|
2013-06-22 20:41:16 +00:00
|
|
|
LOGFQUEUE("codec < Q_CODEC_STOP: %ld", ev.data);
|
|
|
|
#ifdef HAVE_RECORDING
|
|
|
|
if (type_is_encoder(codec_type))
|
|
|
|
{
|
|
|
|
/* Stream finish request (soft stop)? */
|
|
|
|
if (ev.data && param)
|
|
|
|
{
|
|
|
|
/* ev.data is pointer to size */
|
|
|
|
*param = ev.data;
|
|
|
|
action = CODEC_ACTION_STREAM_FINISH;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif /* HAVE_RECORDING */
|
|
|
|
{
|
|
|
|
dsp_configure(ci.dsp, DSP_FLUSH, 0); /* Discontinuity */
|
|
|
|
}
|
|
|
|
|
2012-05-13 07:25:55 +00:00
|
|
|
return CODEC_ACTION_HALT; /* Leave in queue */
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
default: /* This is in error in this context. */
|
|
|
|
logf("codec bad req %ld (%s)", ev.id, __func__);
|
2012-05-13 07:25:55 +00:00
|
|
|
id = Q_NULL;
|
2011-04-27 03:08:23 +00:00
|
|
|
}
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2012-05-13 07:25:55 +00:00
|
|
|
queue_wait(&codec_queue, &ev); /* Actually remove it */
|
|
|
|
codec_queue_ack(id);
|
2011-04-27 03:08:23 +00:00
|
|
|
return action;
|
2009-10-31 19:17:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-08-30 19:40:09 +00:00
|
|
|
static bool codec_loop_track_callback(void)
|
|
|
|
{
|
|
|
|
return global_settings.repeat_mode == REPEAT_ONE;
|
|
|
|
}
|
|
|
|
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/** --- CODEC THREAD --- **/
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Handle Q_CODEC_LOAD */
|
|
|
|
static void load_codec(const struct codec_load_info *ev_data)
|
2009-10-31 19:17:36 +00:00
|
|
|
{
|
2011-04-27 03:08:23 +00:00
|
|
|
int status = CODEC_ERROR;
|
|
|
|
/* Save a local copy so we can let the audio thread go ASAP */
|
|
|
|
struct codec_load_info data = *ev_data;
|
|
|
|
bool const encoder = type_is_encoder(data.afmt);
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
if (codec_type != AFMT_UNKNOWN)
|
|
|
|
{
|
|
|
|
/* Must have unloaded it first */
|
|
|
|
logf("a codec is already loaded");
|
|
|
|
if (data.hid >= 0)
|
|
|
|
bufclose(data.hid);
|
|
|
|
return;
|
|
|
|
}
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
trigger_cpu_boost();
|
|
|
|
|
|
|
|
if (!encoder)
|
2011-02-23 14:31:13 +00:00
|
|
|
{
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Do this now because codec may set some things up at load time */
|
|
|
|
dsp_configure(ci.dsp, DSP_RESET, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data.hid >= 0)
|
|
|
|
{
|
|
|
|
/* First try buffer load */
|
|
|
|
status = codec_load_buf(data.hid, &ci);
|
|
|
|
bufclose(data.hid);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status < 0)
|
|
|
|
{
|
|
|
|
/* Either not a valid handle or the buffer method failed */
|
|
|
|
const char *codec_fn = get_codec_filename(data.afmt);
|
|
|
|
if (codec_fn)
|
|
|
|
status = codec_load_file(codec_fn, &ci);
|
|
|
|
}
|
|
|
|
|
2013-06-22 20:41:16 +00:00
|
|
|
/* Types must agree */
|
|
|
|
if (status >= 0 && encoder == !!codec_get_enc_callback())
|
2011-04-27 03:08:23 +00:00
|
|
|
{
|
|
|
|
codec_type = data.afmt;
|
|
|
|
codec_queue_ack(Q_CODEC_LOAD);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Failed - get rid of it */
|
|
|
|
unload_codec();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Handle Q_CODEC_RUN */
|
|
|
|
static void run_codec(void)
|
|
|
|
{
|
|
|
|
bool const encoder = type_is_encoder(codec_type);
|
|
|
|
int status;
|
|
|
|
|
|
|
|
if (codec_type == AFMT_UNKNOWN)
|
|
|
|
{
|
|
|
|
logf("no codec to run");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
codec_queue_ack(Q_CODEC_RUN);
|
|
|
|
|
|
|
|
trigger_cpu_boost();
|
2013-05-23 17:58:51 +00:00
|
|
|
dsp_configure(ci.dsp, DSP_SET_OUT_FREQUENCY, pcmbuf_get_frequency());
|
2011-04-27 03:08:23 +00:00
|
|
|
|
|
|
|
if (!encoder)
|
|
|
|
{
|
|
|
|
/* This will be either the initial buffered offset or where it left off
|
|
|
|
if it remained buffered and we're skipping back to it and it is best
|
|
|
|
to have ci.curpos in sync with the handle's read position - it's the
|
|
|
|
codec's responsibility to ensure it has the correct positions -
|
|
|
|
playback is sorta dumb and only has a vague idea about what to
|
|
|
|
buffer based upon what metadata has to say */
|
|
|
|
ci.curpos = bufftell(ci.audio_hid);
|
|
|
|
|
|
|
|
/* Pin the codec's audio data in place */
|
|
|
|
buf_pin_handle(ci.audio_hid, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
status = codec_run_proc();
|
|
|
|
|
|
|
|
if (!encoder)
|
|
|
|
{
|
|
|
|
/* Codec is done with it - let it move */
|
|
|
|
buf_pin_handle(ci.audio_hid, false);
|
|
|
|
|
|
|
|
/* Notify audio that we're done for better or worse - advise of the
|
|
|
|
status */
|
2011-08-28 07:45:35 +00:00
|
|
|
audio_codec_complete(status);
|
2011-04-27 03:08:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Handle Q_CODEC_SEEK */
|
|
|
|
static void seek_codec(unsigned long time)
|
|
|
|
{
|
|
|
|
if (codec_type == AFMT_UNKNOWN)
|
|
|
|
{
|
|
|
|
logf("no codec to seek");
|
|
|
|
codec_queue_ack(Q_CODEC_SEEK);
|
|
|
|
codec_seek_complete_callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Post it up one level */
|
|
|
|
queue_post(&codec_queue, Q_CODEC_SEEK, time);
|
|
|
|
codec_queue_ack(Q_CODEC_SEEK);
|
|
|
|
|
|
|
|
/* Have to run it again */
|
|
|
|
run_codec();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Handle Q_CODEC_UNLOAD */
|
|
|
|
static void unload_codec(void)
|
|
|
|
{
|
|
|
|
/* Tell codec to clean up */
|
|
|
|
codec_type = AFMT_UNKNOWN;
|
|
|
|
codec_close();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Handle Q_CODEC_DO_CALLBACK */
|
|
|
|
static void do_callback(void (* callback)(void))
|
|
|
|
{
|
|
|
|
codec_queue_ack(Q_CODEC_DO_CALLBACK);
|
|
|
|
|
|
|
|
if (callback)
|
|
|
|
{
|
2011-12-17 07:27:24 +00:00
|
|
|
commit_discard_idcache();
|
2011-04-27 03:08:23 +00:00
|
|
|
callback();
|
2011-12-17 07:27:24 +00:00
|
|
|
commit_dcache();
|
2011-04-27 03:08:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Codec thread function */
|
|
|
|
static void NORETURN_ATTR codec_thread(void)
|
|
|
|
{
|
|
|
|
struct queue_event ev;
|
|
|
|
|
2013-06-22 20:41:16 +00:00
|
|
|
while (1)
|
2011-04-27 03:08:23 +00:00
|
|
|
{
|
|
|
|
cancel_cpu_boost();
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2009-10-31 19:17:36 +00:00
|
|
|
queue_wait(&codec_queue, &ev);
|
|
|
|
|
2011-02-23 14:31:13 +00:00
|
|
|
switch (ev.id)
|
|
|
|
{
|
2011-04-27 03:08:23 +00:00
|
|
|
case Q_CODEC_LOAD:
|
|
|
|
LOGFQUEUE("codec < Q_CODEC_LOAD");
|
|
|
|
load_codec((const struct codec_load_info *)ev.data);
|
|
|
|
break;
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
case Q_CODEC_RUN:
|
|
|
|
LOGFQUEUE("codec < Q_CODEC_RUN");
|
|
|
|
run_codec();
|
|
|
|
break;
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
case Q_CODEC_PAUSE:
|
|
|
|
LOGFQUEUE("codec < Q_CODEC_PAUSE");
|
|
|
|
break;
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
case Q_CODEC_SEEK:
|
|
|
|
LOGFQUEUE("codec < Q_CODEC_SEEK: %lu", (unsigned long)ev.data);
|
|
|
|
seek_codec(ev.data);
|
|
|
|
break;
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
case Q_CODEC_UNLOAD:
|
|
|
|
LOGFQUEUE("codec < Q_CODEC_UNLOAD");
|
|
|
|
unload_codec();
|
|
|
|
break;
|
2009-10-31 19:17:36 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
case Q_CODEC_DO_CALLBACK:
|
|
|
|
LOGFQUEUE("codec < Q_CODEC_DO_CALLBACK");
|
|
|
|
do_callback((void (*)(void))ev.data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
LOGFQUEUE("codec < default : %ld", ev.id);
|
2011-02-23 14:31:13 +00:00
|
|
|
}
|
2009-10-31 19:17:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
|
|
|
|
/** --- Miscellaneous external interfaces -- **/
|
|
|
|
|
2011-09-01 07:32:07 +00:00
|
|
|
/* Initialize playback's codec interface */
|
2013-06-30 02:19:59 +00:00
|
|
|
void INIT_ATTR codec_thread_init(void)
|
2009-10-31 19:17:36 +00:00
|
|
|
{
|
2011-09-01 07:32:07 +00:00
|
|
|
/* Init API */
|
2012-03-27 23:52:15 +00:00
|
|
|
ci.dsp = dsp_get_config(CODEC_IDX_AUDIO);
|
2011-09-01 07:32:07 +00:00
|
|
|
ci.codec_get_buffer = codec_get_buffer_callback;
|
|
|
|
ci.pcmbuf_insert = codec_pcmbuf_insert_callback;
|
|
|
|
ci.set_elapsed = audio_codec_update_elapsed;
|
|
|
|
ci.read_filebuf = codec_filebuf_callback;
|
|
|
|
ci.request_buffer = codec_request_buffer_callback;
|
|
|
|
ci.advance_buffer = codec_advance_buffer_callback;
|
|
|
|
ci.seek_buffer = codec_seek_buffer_callback;
|
|
|
|
ci.seek_complete = codec_seek_complete_callback;
|
|
|
|
ci.set_offset = audio_codec_update_offset;
|
|
|
|
ci.configure = codec_configure_callback;
|
|
|
|
ci.get_command = codec_get_command_callback;
|
|
|
|
ci.loop_track = codec_loop_track_callback;
|
|
|
|
|
|
|
|
/* Init threading */
|
2011-02-23 14:31:13 +00:00
|
|
|
queue_init(&codec_queue, false);
|
2009-10-31 19:17:36 +00:00
|
|
|
codec_thread_id = create_thread(
|
2011-09-01 07:32:07 +00:00
|
|
|
codec_thread, codec_stack, sizeof(codec_stack), 0,
|
2009-10-31 19:17:36 +00:00
|
|
|
codec_thread_name IF_PRIO(, PRIORITY_PLAYBACK)
|
|
|
|
IF_COP(, CPU));
|
|
|
|
queue_enable_queue_send(&codec_queue, &codec_queue_sender_list,
|
|
|
|
codec_thread_id);
|
|
|
|
}
|
2011-02-23 14:31:13 +00:00
|
|
|
|
|
|
|
#ifdef HAVE_PRIORITY_SCHEDULING
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Obtain codec thread's current priority */
|
2011-02-23 14:31:13 +00:00
|
|
|
int codec_thread_get_priority(void)
|
|
|
|
{
|
|
|
|
return thread_get_priority(codec_thread_id);
|
|
|
|
}
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Set the codec thread's priority and return the old value */
|
2011-02-23 14:31:13 +00:00
|
|
|
int codec_thread_set_priority(int priority)
|
|
|
|
{
|
|
|
|
return thread_set_priority(codec_thread_id, priority);
|
|
|
|
}
|
|
|
|
#endif /* HAVE_PRIORITY_SCHEDULING */
|
|
|
|
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/** --- Functions for audio thread use --- **/
|
|
|
|
|
|
|
|
/* Load a decoder or encoder and set the format type */
|
2011-02-23 14:31:13 +00:00
|
|
|
bool codec_load(int hid, int cod_spec)
|
|
|
|
{
|
2011-04-27 03:08:23 +00:00
|
|
|
struct codec_load_info parm = { hid, cod_spec };
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
LOGFQUEUE("audio >| codec Q_CODEC_LOAD: %d, %d", hid, cod_spec);
|
|
|
|
return codec_queue_send(Q_CODEC_LOAD, (intptr_t)&parm) != 0;
|
|
|
|
}
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Begin decoding the current file */
|
|
|
|
void codec_go(void)
|
|
|
|
{
|
|
|
|
LOGFQUEUE("audio >| codec Q_CODEC_RUN");
|
|
|
|
codec_queue_send(Q_CODEC_RUN, 0);
|
|
|
|
}
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Instruct the codec to seek to the specified time (should be properly
|
|
|
|
paused or stopped first to avoid possible buffering deadlock) */
|
|
|
|
void codec_seek(long time)
|
|
|
|
{
|
|
|
|
LOGFQUEUE("audio > codec Q_CODEC_SEEK: %ld", time);
|
|
|
|
codec_queue_send(Q_CODEC_SEEK, time);
|
|
|
|
}
|
2011-02-23 14:31:13 +00:00
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Pause the codec and make it wait for further instructions inside the
|
|
|
|
command callback */
|
|
|
|
bool codec_pause(void)
|
|
|
|
{
|
|
|
|
LOGFQUEUE("audio >| codec Q_CODEC_PAUSE");
|
|
|
|
return codec_queue_send(Q_CODEC_PAUSE, 0) != Q_NULL;
|
2011-02-23 14:31:13 +00:00
|
|
|
}
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Stop codec if running - codec stays resident if loaded */
|
2011-02-23 14:31:13 +00:00
|
|
|
void codec_stop(void)
|
|
|
|
{
|
|
|
|
/* Wait until it's in the main loop */
|
2013-06-22 20:41:16 +00:00
|
|
|
LOGFQUEUE("audio >| codec Q_CODEC_STOP: 0");
|
2011-04-27 03:08:23 +00:00
|
|
|
while (codec_queue_send(Q_CODEC_STOP, 0) != Q_NULL);
|
|
|
|
}
|
|
|
|
|
2013-06-22 20:41:16 +00:00
|
|
|
#ifdef HAVE_RECORDING
|
|
|
|
/* Tells codec to take final encoding step and then exit -
|
|
|
|
Returns minimum buffer size required or 0 if complete */
|
|
|
|
size_t codec_finish_stream(void)
|
|
|
|
{
|
|
|
|
size_t size = 0;
|
|
|
|
|
|
|
|
LOGFQUEUE("audio >| codec Q_CODEC_STOP: &size");
|
|
|
|
if (codec_queue_send(Q_CODEC_STOP, (intptr_t)&size) != Q_NULL)
|
|
|
|
{
|
|
|
|
/* Sync to keep size in scope and get response */
|
|
|
|
LOGFQUEUE("audio >| codec Q_NULL");
|
|
|
|
codec_queue_send(Q_NULL, 0);
|
|
|
|
|
|
|
|
if (size == 0)
|
|
|
|
codec_stop(); /* Replied with 0 size */
|
|
|
|
}
|
|
|
|
/* else thread running in the main loop */
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
#endif /* HAVE_RECORDING */
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Call the codec's exit routine and close all references */
|
|
|
|
void codec_unload(void)
|
|
|
|
{
|
|
|
|
codec_stop();
|
|
|
|
LOGFQUEUE("audio >| codec Q_CODEC_UNLOAD");
|
|
|
|
codec_queue_send(Q_CODEC_UNLOAD, 0);
|
2011-02-23 14:31:13 +00:00
|
|
|
}
|
|
|
|
|
2011-04-27 03:08:23 +00:00
|
|
|
/* Return the afmt type of the loaded codec - sticks until calling
|
|
|
|
codec_unload unless initial load failed */
|
2011-02-23 14:31:13 +00:00
|
|
|
int codec_loaded(void)
|
|
|
|
{
|
2011-04-27 03:08:23 +00:00
|
|
|
return codec_type;
|
2011-02-23 14:31:13 +00:00
|
|
|
}
|