/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2005 by Linus Nielsen Feltzing
 *
 * 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 "pcm_record.h"
#include "system.h"
#include "kernel.h"
#include "logf.h"
#include "thread.h"
#include "string-extra.h"
#include "storage.h"
#include "usb.h"
#include "buffer.h"
#include "general.h"
#include "audio.h"
#include "sound.h"
#include "metadata.h"
#include "appevents.h"
#ifdef HAVE_SPDIF_IN
#include "spdif.h"
#endif

/***************************************************************************/

extern unsigned int codec_thread_id;

/** General recording state **/
static bool is_recording;              /* We are recording                 */
static bool is_paused;                 /* We have paused                   */
static unsigned long errors;           /* An error has occured             */
static unsigned long warnings;         /* Warning                          */
static int flush_interrupts = 0;       /* Number of messages queued that
                                          should interrupt a flush in
                                          progress -
                                          for a safety net and a prompt
                                          response to stop, split and pause
                                          requests -
                                          only interrupts a flush initiated
                                          by pcmrec_flush(0) */

/* Utility functions for setting/clearing flushing interrupt flag */
static inline void flush_interrupt(void)
{
    flush_interrupts++;
    logf("flush int: %d", flush_interrupts);
}

static inline void clear_flush_interrupt(void)
{
    if (--flush_interrupts < 0)
        flush_interrupts = 0;
}

/** Stats on encoded data for current file **/
static size_t        num_rec_bytes;      /* Num bytes recorded             */
static unsigned long num_rec_samples;    /* Number of PCM samples recorded */

/** Stats on encoded data for all files from start to stop **/
#if 0
static unsigned long long accum_rec_bytes; /* total size written to chunks */
static unsigned long long accum_pcm_samples; /* total pcm count processed  */
#endif

/* Keeps data about current file and is sent as event data for codec */
static struct enc_file_event_data rec_fdata IDATA_ATTR =
{
    .chunk           =  NULL,
    .new_enc_size    =  0,
    .new_num_pcm     =  0,
    .rec_file        = -1,
    .num_pcm_samples =  0
};

/** These apply to current settings **/
static int           rec_source;         /* current rec_source setting     */
static int           rec_frequency;      /* current frequency setting      */
static unsigned long sample_rate;        /* Sample rate in HZ              */
static int           num_channels;       /* Current number of channels     */
static int           rec_mono_mode;      /* how mono is created            */
static struct encoder_config enc_config; /* Current encoder configuration  */
static unsigned long  pre_record_ticks;  /* pre-record time in ticks       */
  
/****************************************************************************
  use 2 circular buffers:
  pcm_buffer=DMA output buffer:    chunks (8192 Bytes) of raw pcm audio data
  enc_buffer=encoded audio buffer: storage for encoder output data

  Flow:
  1. when entering recording_screen DMA feeds the ringbuffer pcm_buffer
  2. if enough pcm data are available the encoder codec does encoding of pcm
      chunks (4-8192 Bytes) into ringbuffer enc_buffer in codec_thread
  3. pcmrec_callback detects enc_buffer 'near full' and writes data to disk

  Functions calls (basic encoder steps):
  1.main:    audio_load_encoder();     start the encoder
  2.encoder: enc_get_inputs();         get encoder recording settings
  3.encoder: enc_set_parameters();     set the encoder parameters
  4.encoder: enc_get_pcm_data();       get n bytes of unprocessed pcm data
  5.encoder: enc_unget_pcm_data();     put n bytes of data back (optional)
  6.encoder: enc_get_chunk();          get a ptr to next enc chunk
  7.encoder: <process enc chunk>       compress and store data to enc chunk
  8.encoder: enc_finish_chunk();       inform main about chunk processed and
                                       is available to be written to a file.
                                       Encoder can place any number of chunks
                                       of PCM data in a single output chunk
                                       but must stay within its output chunk
                                       size
  9.encoder: repeat 4. to 8.
  A.pcmrec:  enc_events_callback();   called for certain events

  (*) Optional step
****************************************************************************/

/** buffer parameters where incoming PCM data is placed **/
#if MEM <= 2
#define PCM_NUM_CHUNKS             16 /* Power of 2 */
#else
#define PCM_NUM_CHUNKS            256 /* Power of 2 */
#endif
#define PCM_CHUNK_SIZE           8192 /* Power of 2 */
#define PCM_CHUNK_MASK          (PCM_NUM_CHUNKS*PCM_CHUNK_SIZE - 1)

#define GET_PCM_CHUNK(offset)   ((long *)(pcm_buffer + (offset)))
#define GET_ENC_CHUNK(index)    ENC_CHUNK_HDR(enc_buffer + enc_chunk_size*(index))
#define INC_ENC_INDEX(index) \
    { if (++index >= enc_num_chunks) index = 0; }
#define DEC_ENC_INDEX(index) \
    { if (--index < 0) index = enc_num_chunks - 1; }

static size_t         rec_buffer_size; /* size of available buffer         */
static unsigned char *pcm_buffer;      /* circular recording buffer        */
static unsigned char *enc_buffer;      /* circular encoding buffer         */
#ifdef DEBUG
static unsigned long *wrap_id_p;       /* magic at wrap position - a debugging
                                          aid to check if the encoder data
                                          spilled out of its chunk         */
#endif /* DEBUG */
static volatile int   dma_wr_pos;      /* current DMA write pos            */
static int            pcm_rd_pos;      /* current PCM read pos             */
static int            pcm_enc_pos;     /* position encoder is processing   */
static volatile bool  dma_lock;        /* lock DMA write position          */
static int            enc_wr_index;    /* encoder chunk write index        */
static int            enc_rd_index;    /* encoder chunk read index         */
static int            enc_num_chunks;  /* number of chunks in ringbuffer   */
static size_t         enc_chunk_size;  /* maximum encoder chunk size       */
static unsigned long  enc_sample_rate; /* sample rate used by encoder      */
static bool           pcmrec_context = false;  /* called by pcmrec thread? */
static bool           pcm_buffer_empty; /* all pcm chunks processed?       */
 
/** file flushing **/
static int            low_watermark;   /* Low watermark to stop flush      */
static int            high_watermark;  /* max chunk limit for data flush   */
static unsigned long  spinup_time = 35*HZ/10;  /* Fudged spinup time       */
static int            last_storage_spinup_time = -1;/* previous spin time used */
#ifdef HAVE_PRIORITY_SCHEDULING
static int            flood_watermark; /* boost thread priority when here  */
#endif

/* Constants that control watermarks */
#define MINI_CHUNKS    10       /* chunk count for mini flush              */
#ifdef HAVE_PRIORITY_SCHEDULING
#define PRIO_SECONDS   10       /* max flush time before priority boost    */
#endif
#if MEM <= 2
/* fractions must be integer fractions of 4 because they are evaluated with
 * X*4*XXX_SECONDS, that way we avoid float calculation */
#define LOW_SECONDS     1/4     /* low watermark time till empty           */
#define PANIC_SECONDS   1/2     /* flood watermark time until full         */
#define FLUSH_SECONDS   1       /* flush watermark time until full         */
#elif MEM <= 16
#define LOW_SECONDS     1       /* low watermark time till empty           */
#define PANIC_SECONDS   5       /* flood watermark time until full         */
#define FLUSH_SECONDS   7       /* flush watermark time until full         */
#else
#define LOW_SECONDS     1       /* low watermark time till empty           */
#define PANIC_SECONDS   8
#define FLUSH_SECONDS  10
#endif /* MEM */

/** encoder events **/
static void (*enc_events_callback)(enum enc_events event, void *data);

/** Path queue for files to write **/
#define FNQ_MIN_NUM_PATHS 16           /* minimum number of paths to hold  */
#define FNQ_MAX_NUM_PATHS 64           /* maximum number of paths to hold  */
static unsigned char *fn_queue;        /* pointer to first filename        */
static ssize_t        fnq_size;        /* capacity of queue in bytes       */
static int            fnq_rd_pos;      /* current read position            */
static int            fnq_wr_pos;      /* current write position           */
#define FNQ_NEXT(pos) \
    ({ int p = (pos) + MAX_PATH; \
       if (p >= fnq_size)        \
            p = 0;               \
       p; })
#define FNQ_PREV(pos) \
    ({ int p = (pos) - MAX_PATH;     \
       if (p < 0)                    \
            p = fnq_size - MAX_PATH; \
       p; })

enum
{
    PCMREC_FLUSH_INTERRUPTABLE  = 0x8000000, /* Flush can be interrupted by
                                                incoming messages - combine
                                                with other constants       */
    PCMREC_FLUSH_ALL            = 0x7ffffff, /* Flush all files            */
    PCMREC_FLUSH_MINI           = 0x7fffffe, /* Flush a small number of
                                                chunks                     */
    PCMREC_FLUSH_IF_HIGH        = 0x0000000, /* Flush if high watermark
                                                reached                    */
};

/***************************************************************************/

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,
    PCMREC_INIT,            /* enable recording                */
    PCMREC_CLOSE,           /* close recording                 */
    PCMREC_OPTIONS,         /* set recording options           */
    PCMREC_RECORD,          /* record a new file               */
    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     */
/*******************************************************************/
    
/* Callback for when more data is ready - called in interrupt context */
static void pcm_rec_have_more(int status, void **start, size_t *size)
{
    if (status < 0)
    {
        /* some error condition */
        if (status == DMA_REC_ERROR_DMA)
        {
            /* Flush recorded data to disk and stop recording */
            queue_post(&pcmrec_queue, PCMREC_STOP, 0);
            return;
        }
        /* else try again next transmission - frame is invalid */
    }
    else if (!dma_lock)
    {
        /* advance write position */
        int next_pos = (dma_wr_pos + PCM_CHUNK_SIZE) & PCM_CHUNK_MASK;

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

        dma_wr_pos = next_pos;
    }

    *start = GET_PCM_CHUNK(dma_wr_pos);
    *size = PCM_CHUNK_SIZE;
} /* pcm_rec_have_more */

static void reset_hardware(void)
{
    /* reset pcm to defaults */
    pcm_set_frequency(REC_SAMPR_DEFAULT | SAMPR_TYPE_REC);
    audio_set_output_source(AUDIO_SRC_PLAYBACK);
    pcm_apply_settings();
}

/** pcm_rec_* group **/

/**
 * Clear all errors and warnings
 */
void pcm_rec_error_clear(void)
{
    errors = warnings = 0;
} /* pcm_rec_error_clear */

/**
 * Check mode, errors and warnings
 */
unsigned long pcm_rec_status(void)
{
    unsigned long ret = 0;

    if (is_recording)
        ret |= AUDIO_STATUS_RECORD;
    else if (pre_record_ticks)
        ret |= AUDIO_STATUS_PRERECORD;

    if (is_paused)
        ret |= AUDIO_STATUS_PAUSE;

    if (errors)
        ret |= AUDIO_STATUS_ERROR;

    if (warnings)
        ret |= AUDIO_STATUS_WARNING;

    return ret;
} /* pcm_rec_status */

/**
 * Return warnings that have occured since recording started
 */
unsigned long pcm_rec_get_warnings(void)
{
    return warnings;
}

#if 0
int pcm_rec_current_bitrate(void)
{
    if (accum_pcm_samples == 0)
        return 0;

    return (int)(8*accum_rec_bytes*enc_sample_rate / (1000*accum_pcm_samples));
} /* pcm_rec_current_bitrate */
#endif

#if 0
int pcm_rec_encoder_afmt(void)
{
    return enc_config.afmt;
} /* pcm_rec_encoder_afmt */
#endif

#if 0
int pcm_rec_rec_format(void)
{
    return afmt_rec_format[enc_config.afmt];
} /* pcm_rec_rec_format */
#endif

#ifdef HAVE_SPDIF_IN
unsigned long pcm_rec_sample_rate(void)
{
    /* Which is better ?? */
#if 0
    return enc_sample_rate;
#endif
    return sample_rate;
} /* 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 **/

/**
 * Initializes recording - call before calling any other recording function
 */
void audio_init_recording(unsigned int buffer_offset)
{
    logf("audio_init_recording");
    queue_send(&pcmrec_queue, PCMREC_INIT, 0);
    logf("audio_init_recording done");
    (void)buffer_offset;
} /* audio_init_recording */

/**
 * Closes recording - call audio_stop_recording first
 */
void audio_close_recording(void)
{
    logf("audio_close_recording");
    queue_send(&pcmrec_queue, PCMREC_CLOSE, 0);
    logf("audio_close_recording done");
} /* audio_close_recording */

/**
 * Sets recording parameters
 */
void audio_set_recording_options(struct audio_recording_options *options)
{
    logf("audio_set_recording_options");
    queue_send(&pcmrec_queue, PCMREC_OPTIONS, (intptr_t)options);
    logf("audio_set_recording_options done");
} /* audio_set_recording_options */

/**
 * Start recording if not recording or else split
 */
void audio_record(const char *filename)
{
    logf("audio_record: %s", filename);
    flush_interrupt();
    queue_send(&pcmrec_queue, PCMREC_RECORD, (intptr_t)filename);
    logf("audio_record_done");
} /* audio_record */

/**
 * audio_record wrapper for API compatibility with HW codec
 */
void audio_new_file(const char *filename)
{
    audio_record(filename);
} /* audio_new_file */

/**
 * Stop current recording if recording
 */
void audio_stop_recording(void)
{
    logf("audio_stop_recording");
    flush_interrupt();
    queue_post(&pcmrec_queue, PCMREC_STOP, 0);
    logf("audio_stop_recording done");
} /* audio_stop_recording */

/**
 * Pause current recording
 */
void audio_pause_recording(void)
{
    logf("audio_pause_recording");
    flush_interrupt();
    queue_post(&pcmrec_queue, PCMREC_PAUSE, 0);
    logf("audio_pause_recording done");
} /* audio_pause_recording */

/**
 * Resume current recording if paused
 */    
void audio_resume_recording(void)
{
    logf("audio_resume_recording");
    queue_post(&pcmrec_queue, PCMREC_RESUME, 0);
    logf("audio_resume_recording done");
} /* audio_resume_recording */

/**
 * Note that microphone is mono, only left value is used 
 * See audiohw_set_recvol() for exact ranges.
 *
 * @param type   AUDIO_GAIN_MIC, AUDIO_GAIN_LINEIN
 * 
 */
void audio_set_recording_gain(int left, int right, int type)
{
    //logf("rcmrec: t=%d l=%d r=%d", type, left, right);
    audiohw_set_recvol(left, right, type);
} /* audio_set_recording_gain */

/** Information about current state **/

/**
 * Return current recorded time in ticks (playback eqivalent time)
 */
unsigned long audio_recorded_time(void)
{
    if (!is_recording || enc_sample_rate == 0)
        return 0;

    /* return actual recorded time a la encoded data even if encoder rate
       doesn't match the pcm rate */
    return (long)(HZ*(unsigned long long)num_rec_samples / enc_sample_rate);
} /* audio_recorded_time */

/**
 * Return number of bytes encoded to output
 */
unsigned long audio_num_recorded_bytes(void)
{
    if (!is_recording)
        return 0;

    return num_rec_bytes;
} /* audio_num_recorded_bytes */
    
/***************************************************************************/
/*                                                                         */
/*         Functions that execute in the context of pcmrec_thread          */
/*                                                                         */
/***************************************************************************/

/** Filename Queue **/

/* returns true if the queue is empty */
static inline bool pcmrec_fnq_is_empty(void)
{
    return  fnq_rd_pos == fnq_wr_pos;
} /* pcmrec_fnq_is_empty */

/* empties the filename queue */
static inline void pcmrec_fnq_set_empty(void)
{
    fnq_rd_pos = fnq_wr_pos;
} /* pcmrec_fnq_set_empty */
        
/* returns true if the queue is full */
static bool pcmrec_fnq_is_full(void)
{
    ssize_t size = fnq_wr_pos - fnq_rd_pos;
    if (size < 0)
        size += fnq_size;
    
    return size >= fnq_size - MAX_PATH;
} /* pcmrec_fnq_is_full */

/* queue another filename - will overwrite oldest one if full */
static bool pcmrec_fnq_add_filename(const char *filename)
{
    strlcpy(fn_queue + fnq_wr_pos, filename, MAX_PATH);
    fnq_wr_pos = FNQ_NEXT(fnq_wr_pos);
    
    if (fnq_rd_pos != fnq_wr_pos)
        return true;

    /* queue full */
    fnq_rd_pos = FNQ_NEXT(fnq_rd_pos);
    return true;
} /* pcmrec_fnq_add_filename */

/* replace the last filename added */
static bool pcmrec_fnq_replace_tail(const char *filename)
{
    int pos;

    if (pcmrec_fnq_is_empty())
        return false;

    pos = FNQ_PREV(fnq_wr_pos);

    strlcpy(fn_queue + pos, filename, MAX_PATH);

    return true;
} /* pcmrec_fnq_replace_tail */

/* pulls the next filename from the queue */
static bool pcmrec_fnq_get_filename(char *filename)
{
    if (pcmrec_fnq_is_empty())
        return false;

    if (filename)
        strlcpy(filename, fn_queue + fnq_rd_pos, MAX_PATH);
    
    fnq_rd_pos = FNQ_NEXT(fnq_rd_pos);
    return true;
} /* pcmrec_fnq_get_filename */

/* close the file number pointed to by fd_p */
static void pcmrec_close_file(int *fd_p)
{
    if (*fd_p < 0)
        return; /* preserve error */

    if (close(*fd_p) != 0)
        errors |= PCMREC_E_IO;

    *fd_p = -1;
} /* pcmrec_close_file */

/** Data Flushing **/

/**
 * called after callback to update sizes if codec changed the amount of data
 * a chunk represents
 */
static inline void pcmrec_update_sizes_inl(size_t prev_enc_size,
                                           unsigned long prev_num_pcm)
{
    if (rec_fdata.new_enc_size != prev_enc_size)
    {
        ssize_t size_diff = rec_fdata.new_enc_size - prev_enc_size;
        num_rec_bytes   += size_diff;
#if 0
        accum_rec_bytes += size_diff;
#endif
    }

    if (rec_fdata.new_num_pcm != prev_num_pcm)
    {
        unsigned long pcm_diff = rec_fdata.new_num_pcm - prev_num_pcm;
        num_rec_samples   += pcm_diff;
#if 0
        accum_pcm_samples += pcm_diff;
#endif
    }
} /* pcmrec_update_sizes_inl */

/* don't need to inline every instance */
static void pcmrec_update_sizes(size_t prev_enc_size,
                                unsigned long prev_num_pcm)
{
    pcmrec_update_sizes_inl(prev_enc_size, prev_num_pcm);
} /* pcmrec_update_sizes */

static void pcmrec_start_file(void)
{
    size_t        enc_size = rec_fdata.new_enc_size;
    unsigned long num_pcm  = rec_fdata.new_num_pcm;
    int curr_rec_file      = rec_fdata.rec_file;
    char filename[MAX_PATH];

    /* must always pull the filename that matches with this queue */
    if (!pcmrec_fnq_get_filename(filename))
    {
        logf("start file: fnq empty");
        *filename = '\0';
        errors |= PCMREC_E_FNQ_DESYNC;
    }
    else if (errors != 0)
    {
        logf("start file: error already");
    }
    else if (curr_rec_file >= 0)
    {
        /* Any previous file should have been closed */
        logf("start file: file already open");
        errors |= PCMREC_E_FNQ_DESYNC;
    }
    
    if (errors != 0)
        rec_fdata.chunk->flags |= CHUNKF_ERROR;

    /* encoder can set error flag here and should increase
       enc_new_size and pcm_new_size to reflect additional
       data written if any */
    rec_fdata.filename = filename;
    enc_events_callback(ENC_START_FILE, &rec_fdata);

    if (errors == 0 && (rec_fdata.chunk->flags & CHUNKF_ERROR))
    {
        logf("start file: enc error");
        errors |= PCMREC_E_ENCODER;
    }

    if (errors != 0)
    {
        pcmrec_close_file(&curr_rec_file);
        /* Write no more to this file */
        rec_fdata.chunk->flags |= CHUNKF_END_FILE;
    }
    else
    {
        pcmrec_update_sizes(enc_size, num_pcm);
    }
    
    rec_fdata.chunk->flags &= ~CHUNKF_START_FILE;
} /* pcmrec_start_file */

static inline void pcmrec_write_chunk(void)
{
    size_t        enc_size = rec_fdata.new_enc_size;
    unsigned long num_pcm  = rec_fdata.new_num_pcm;

    if (errors != 0)
        rec_fdata.chunk->flags |= CHUNKF_ERROR;

    enc_events_callback(ENC_WRITE_CHUNK, &rec_fdata);

    if ((long)rec_fdata.chunk->flags >= 0)
    {
        pcmrec_update_sizes_inl(enc_size, num_pcm);
    }
    else if (errors == 0)
    {
        logf("wr chk enc error %lu %lu",
             rec_fdata.chunk->enc_size, rec_fdata.chunk->num_pcm);
        errors |= PCMREC_E_ENCODER;
    }
} /* pcmrec_write_chunk */

static void pcmrec_end_file(void)
{
    /* all data in output buffer for current file will have been
       written and encoder can now do any nescessary steps to
       finalize the written file */
    size_t        enc_size = rec_fdata.new_enc_size;
    unsigned long num_pcm  = rec_fdata.new_num_pcm;

    enc_events_callback(ENC_END_FILE, &rec_fdata);

    if (errors == 0)
    {
        if (rec_fdata.chunk->flags & CHUNKF_ERROR)
        {
            logf("end file: enc error");
            errors |= PCMREC_E_ENCODER;
        }
        else
        {
            pcmrec_update_sizes(enc_size, num_pcm);
        }
    }

    /* Force file close if error */
    if (errors != 0)
        pcmrec_close_file(&rec_fdata.rec_file);

    rec_fdata.chunk->flags &= ~CHUNKF_END_FILE;
} /* pcmrec_end_file */

/**
 * Update buffer watermarks with spinup time compensation
 *
 * All this assumes reasonable data rates, chunk sizes and sufficient
 * memory for the most part. Some dumb checks are included but perhaps
 * are pointless since this all will break down at extreme limits that
 * are currently not applicable to any supported device.
 */
static void pcmrec_refresh_watermarks(void)
{
    logf("ata spinup: %d", storage_spinup_time());

    /* set the low mark for when flushing stops if automatic */
    /* don't change the order in this expression, LOW_SECONDS can be an
     * integer fraction of 4 */
    low_watermark = (sample_rate*4*LOW_SECONDS + (enc_chunk_size-1))
                        / enc_chunk_size;
    logf("low wmk: %d", low_watermark);

#ifdef HAVE_PRIORITY_SCHEDULING
    /* panic boost thread priority if 2 seconds of ground is lost -
       this allows encoder to boost with just under a second of
       pcm data (if not yet full enough to boost itself)
       and not falsely trip the alarm. */
    /* don't change the order in this expression, PANIC_SECONDS can be an
     * integer fraction of 4 */
    flood_watermark = enc_num_chunks -
        (sample_rate*4*PANIC_SECONDS + (enc_chunk_size-1))
             / enc_chunk_size;

    if (flood_watermark < low_watermark)
    {
        logf("warning: panic < low");
        flood_watermark = low_watermark;
    }

    logf("flood at: %d", flood_watermark);
#endif
    spinup_time = last_storage_spinup_time = storage_spinup_time();
#if (CONFIG_STORAGE & STORAGE_ATA)
    /* write at 8s + st remaining in enc_buffer - range 12s to
       20s total - default to 3.5s spinup. */
    if (spinup_time == 0)
        spinup_time = 35*HZ/10;  /* default - cozy                */
    else if (spinup_time < 2*HZ)
        spinup_time = 2*HZ;      /* ludicrous - ramdisk?          */
    else if (spinup_time > 10*HZ)
        spinup_time = 10*HZ;     /* do you have a functioning HD? */
#endif

    /* try to start writing with 10s remaining after disk spinup */
    high_watermark = enc_num_chunks -
        ((FLUSH_SECONDS*HZ + spinup_time)*4*sample_rate +
              (enc_chunk_size-1)*HZ) / (enc_chunk_size*HZ);

    if (high_watermark < low_watermark)
    {
        logf("warning: low 'write at' (%d)", high_watermark);
        high_watermark = low_watermark;
        low_watermark /= 2;
    }

    logf("write at: %d", high_watermark);
} /* pcmrec_refresh_watermarks */

/**
 * Process the chunks
 *
 * This function is called when queue_get_w_tmo times out.
 *
 * Set flush_num to the number of files to flush to disk or to
 * a PCMREC_FLUSH_* constant.
 */
static void pcmrec_flush(unsigned flush_num)
{
#ifdef HAVE_PRIORITY_SCHEDULING
    static unsigned long last_flush_tick; /* tick when function returned   */
    unsigned long start_tick;      /* When flush started                   */
    unsigned long prio_tick;       /* Timeout for auto boost               */
    int           prio_pcmrec;     /* Current thread priority for pcmrec   */
    int           prio_codec;      /* Current thread priority for codec    */
#endif
    int           num_ready;       /* Number of chunks ready at start      */
    unsigned      remaining;       /* Number of file starts remaining      */
    unsigned      chunks_flushed;  /* Chunks flushed (for mini flush only) */
    bool          interruptable;   /* Flush can be interupted              */

    num_ready = enc_wr_index - enc_rd_index;
    if (num_ready < 0)
        num_ready += enc_num_chunks;

    /* save interruptable flag and remove it to get the actual count */
    interruptable = (flush_num & PCMREC_FLUSH_INTERRUPTABLE) != 0;
    flush_num    &= ~PCMREC_FLUSH_INTERRUPTABLE;

    if (flush_num == 0)
    {
        if (!is_recording)
            return;

        if (storage_spinup_time() != last_storage_spinup_time)
            pcmrec_refresh_watermarks();

        /* enough available? no? then leave */
        if (num_ready < high_watermark)
            return;
    } /* endif (flush_num == 0) */

#ifdef HAVE_PRIORITY_SCHEDULING
    start_tick = current_tick;
    prio_tick  = start_tick + PRIO_SECONDS*HZ + spinup_time;

    if (flush_num == 0 && TIME_BEFORE(current_tick, last_flush_tick + HZ/2))
    {
        /* if we're getting called too much and this isn't forced,
           boost stat by expiring timeout in advance */
        logf("too frequent flush");
        prio_tick = current_tick - 1;
    }

    prio_pcmrec = -1;
    prio_codec  = -1; /* GCC is too stoopid to figure out it doesn't
                         need init */
#endif

    logf("writing:%d(%d):%s%s", num_ready, flush_num,
         interruptable ? "i" : "",
         flush_num == PCMREC_FLUSH_MINI ? "m" : "");
        
    cpu_boost(true);

    remaining      = flush_num;
    chunks_flushed = 0;

    while (num_ready > 0)
    {
        /* check current number of encoder chunks */
        int num = enc_wr_index - enc_rd_index;
        if (num < 0)
            num += enc_num_chunks;

        if (num <= low_watermark &&
            (flush_num == PCMREC_FLUSH_IF_HIGH || num <= 0))
        {
            logf("low data: %d", num);
            break; /* data remaining is below threshold */
        }

        if (interruptable && flush_interrupts > 0)
        {
            logf("int at: %d", num);
            break; /* interrupted */
        }

#ifdef HAVE_PRIORITY_SCHEDULING
        if (prio_pcmrec == -1 && (num >= flood_watermark ||
                                  TIME_AFTER(current_tick, prio_tick)))
        {
            /* losing ground or holding without progress - boost
               priority until finished */
            logf("pcmrec: boost (%s)",
                 num >= flood_watermark ? "num" : "time");
            prio_pcmrec = thread_set_priority(THREAD_ID_CURRENT,
                                thread_get_priority(THREAD_ID_CURRENT) - 4);
            prio_codec  = thread_set_priority(codec_thread_id,
                                thread_get_priority(codec_thread_id) - 4);
        }
#endif

        rec_fdata.chunk        = GET_ENC_CHUNK(enc_rd_index);
        rec_fdata.new_enc_size = rec_fdata.chunk->enc_size;
        rec_fdata.new_num_pcm  = rec_fdata.chunk->num_pcm;

        if (rec_fdata.chunk->flags & CHUNKF_START_FILE)
        {
            pcmrec_start_file();
            if (--remaining == 0)
                num_ready = 0; /* stop on next loop - must write this
                                  chunk if it has data */
        }

        pcmrec_write_chunk();

        if (rec_fdata.chunk->flags & CHUNKF_END_FILE)
            pcmrec_end_file();

        INC_ENC_INDEX(enc_rd_index);

        if (errors != 0)
        {
            pcmrec_end_file();
            break;
        }

        if (flush_num == PCMREC_FLUSH_MINI &&
                ++chunks_flushed >= MINI_CHUNKS)
        {
            logf("mini flush break");
            break;
        }
        /* no yielding; the file apis called in the codecs do that
           sufficiently */
    } /* end while */

    /* sync file */
    if (rec_fdata.rec_file >= 0 && fsync(rec_fdata.rec_file) != 0)
        errors |= PCMREC_E_IO;

    cpu_boost(false);

#ifdef HAVE_PRIORITY_SCHEDULING
    if (prio_pcmrec != -1)
    {
        /* return to original priorities */
        logf("pcmrec: unboost priority");
        thread_set_priority(THREAD_ID_CURRENT, prio_pcmrec);
        thread_set_priority(codec_thread_id, prio_codec);
    }

    last_flush_tick = current_tick; /* save tick when we left */
#endif

    logf("done");
} /* pcmrec_flush */

/**
 * Marks a new stream in the buffer and gives the encoder a chance for special
 * handling of transition from one to the next. The encoder may change the
 * chunk that ends the old stream by requesting more chunks and similiarly for
 * the new but must always advance the position though the interface. It can
 * later reject any data it cares to when writing the file but should mark the
 * chunk so it can recognize this. ENC_WRITE_CHUNK event must be able to accept
 * a NULL data pointer without error as well.
 */
static int pcmrec_get_chunk_index(struct enc_chunk_hdr *chunk)
{
    return ((char *)chunk - (char *)enc_buffer) / enc_chunk_size;
} /* pcmrec_get_chunk_index */

static struct enc_chunk_hdr * pcmrec_get_prev_chunk(int index)
{
    DEC_ENC_INDEX(index);
    return GET_ENC_CHUNK(index);
} /* pcmrec_get_prev_chunk */

static void pcmrec_new_stream(const char *filename, /* next file name */
                              unsigned long flags,  /* CHUNKF_* flags */
                              int pre_index) /* index for prerecorded data */
{
    logf("pcmrec_new_stream");
    char path[MAX_PATH]; /* place to copy filename so sender can be released */

    struct enc_buffer_event_data data;
    bool (*fnq_add_fn)(const char *) = NULL; /* function to use to add
                                                new filename */
    struct enc_chunk_hdr *start = NULL;      /* pointer to starting chunk of
                                                stream */
    bool did_flush = false;                  /* did a flush occurr? */

    if (filename)
        strlcpy(path, filename, MAX_PATH);
    queue_reply(&pcmrec_queue, 0); /* We have all we need */

    data.pre_chunk = NULL;
    data.chunk = GET_ENC_CHUNK(enc_wr_index);

    /* end chunk */
    if (flags & CHUNKF_END_FILE)
    {
        data.chunk->flags &= CHUNKF_START_FILE | CHUNKF_END_FILE;

        if (data.chunk->flags & CHUNKF_START_FILE)
        {
            /* cannot start and end on same unprocessed chunk */
            logf("file end on start");
            flags &= ~CHUNKF_END_FILE;
        }
        else if (enc_rd_index == enc_wr_index)
        {
            /* all data flushed but file not ended - chunk will be left
               empty */
            logf("end on dead end");
            data.chunk->flags    = 0;
            data.chunk->enc_size = 0;
            data.chunk->num_pcm  = 0;
            data.chunk->enc_data = NULL;
            INC_ENC_INDEX(enc_wr_index);
            data.chunk = GET_ENC_CHUNK(enc_wr_index);
        }
        else
        {
            struct enc_chunk_hdr *last = pcmrec_get_prev_chunk(enc_wr_index);

            if (last->flags & CHUNKF_END_FILE)
            {
                /* end already processed and marked - can't end twice */
                logf("file end again");
                flags &= ~CHUNKF_END_FILE;
            }
        }
    }

    /* start chunk */
    if (flags & CHUNKF_START_FILE)
    {
        bool pre = flags & CHUNKF_PRERECORD;

        if (pre)
        {
            logf("stream prerecord start");
            start = data.pre_chunk = GET_ENC_CHUNK(pre_index);
            start->flags &= CHUNKF_START_FILE | CHUNKF_PRERECORD;
        } 
        else
        {
            logf("stream normal start");
            start = data.chunk;
            start->flags &= CHUNKF_START_FILE;
        }

        /* if encoder hasn't yet processed the last start - abort the start
           of the previous file queued or else it will be empty and invalid */
        if (start->flags & CHUNKF_START_FILE)
        {
            logf("replacing fnq tail: %s", filename);
            fnq_add_fn = pcmrec_fnq_replace_tail;
        }
        else
        {
            logf("adding filename: %s", filename);
            fnq_add_fn = pcmrec_fnq_add_filename;
        }
    }

    data.flags = flags;
    pcmrec_context = true; /* switch encoder context */
    enc_events_callback(ENC_REC_NEW_STREAM, &data);
    pcmrec_context = false; /* switch back */

    if (flags & CHUNKF_END_FILE)
    {
        int i = pcmrec_get_chunk_index(data.chunk);
        pcmrec_get_prev_chunk(i)->flags |= CHUNKF_END_FILE;
    }

    if (start)
    {
        if (!(flags & CHUNKF_PRERECORD))
        {
            /* get stats on data added to start - sort of a prerecord
               operation */
            int i = pcmrec_get_chunk_index(data.chunk);
            struct enc_chunk_hdr *chunk = data.chunk;

            logf("start data: %d %d", i, enc_wr_index);

            num_rec_bytes   = 0;
            num_rec_samples = 0;

            while (i != enc_wr_index)
            {
                num_rec_bytes   += chunk->enc_size;
                num_rec_samples += chunk->num_pcm;
                INC_ENC_INDEX(i);
                chunk = GET_ENC_CHUNK(i);
            }

            start->flags &= ~CHUNKF_START_FILE;
            start = data.chunk;
        }

        start->flags |= CHUNKF_START_FILE;

        /* flush all pending files out if full and adding */
        if (fnq_add_fn == pcmrec_fnq_add_filename && pcmrec_fnq_is_full())
        {
            logf("fnq full");
            pcmrec_flush(PCMREC_FLUSH_ALL);
            did_flush = true;
        }
   
        fnq_add_fn(path);
    }

    /* Make sure to complete any interrupted high watermark */
    if (!did_flush)
        pcmrec_flush(PCMREC_FLUSH_IF_HIGH);
} /* pcmrec_new_stream */

/** event handlers for pcmrec thread */

/* PCMREC_INIT */
static void pcmrec_init(void)
{
    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;

    /* 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);

    /* Line align pcm_buffer 2^5=32 bytes */
    pcm_buffer = (unsigned char *)ALIGN_UP_P2((uintptr_t)buffer, 5);
    enc_buffer = pcm_buffer + ALIGN_UP_P2(PCM_NUM_CHUNKS*PCM_CHUNK_SIZE +
                                          PCM_MAX_FEED_SIZE, 2);
    /* Adjust available buffer for possible align advancement */
    rec_buffer_size -= pcm_buffer - buffer;

    pcm_init_recording();
} /* pcmrec_init */

/* PCMREC_CLOSE */
static void pcmrec_close(void)
{
    dma_lock = true;
    pre_record_ticks = 0; /* Can't be prerecording any more */
    warnings         = 0;
    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 audio_recording_options *options)
{
    /* stop DMA transfer */
    dma_lock = true;
    pcm_stop_recording();

    rec_frequency      = options->rec_frequency;
    rec_source         = options->rec_source;
    num_channels       = options->rec_channels == 1 ? 1 : 2;
    rec_mono_mode      = options->rec_mono_mode;
    pre_record_ticks   = options->rec_prerecord_time * HZ;
    enc_config         = options->enc_config;
    enc_config.afmt    = rec_format_afmt[enc_config.rec_format];

#ifdef HAVE_SPDIF_IN
    if (rec_source == AUDIO_SRC_SPDIF)
    {
        /* must measure SPDIF sample rate before configuring codecs */
        unsigned long sr = spdif_measure_frequency();
        /* round to master list for SPDIF rate */
        int index = round_value_to_list32(sr, audio_master_sampr_list,
                                          SAMPR_NUM_FREQ, false);
        sample_rate = audio_master_sampr_list[index];
        /* round to HW playback rates for monitoring */
        index = round_value_to_list32(sr, hw_freq_sampr,
                                      HW_NUM_FREQ, false);
        pcm_set_frequency(hw_freq_sampr[index] | SAMPR_TYPE_REC);
        /* encoders with a limited number of rates do their own rounding */
    }
    else
#endif
    {
        /* set sample rate from frequency selection */
        sample_rate = rec_freq_sampr[rec_frequency];
        pcm_set_frequency(sample_rate | SAMPR_TYPE_REC);
    }

    /* set monitoring */
    audio_set_output_source(rec_source);

    /* apply hardware setting to start monitoring now */
    pcm_apply_settings();

    queue_reply(&pcmrec_queue, 0); /* Release sender */

    if (audio_load_encoder(enc_config.afmt))
    {
        /* start DMA transfer */
        dma_lock = pre_record_ticks == 0;
        pcm_record_data(pcm_rec_have_more, GET_PCM_CHUNK(dma_wr_pos),
                        PCM_CHUNK_SIZE);
    }
    else
    {
        logf("set rec opt: enc load failed");
        errors |= PCMREC_E_LOAD_ENCODER;
    }
} /* pcmrec_set_recording_options */

/* PCMREC_RECORD - start recording (not gapless)
                   or split stream (gapless) */
static void pcmrec_record(const char *filename)
{
    unsigned long pre_sample_ticks;
    int           rd_start;
    unsigned long flags;
    int           pre_index;

    logf("pcmrec_record: %s", filename);

    /* reset stats */
    num_rec_bytes   = 0;
    num_rec_samples = 0;

    if (!is_recording) 
    {
#if 0
        accum_rec_bytes   = 0;
        accum_pcm_samples = 0;
#endif
        warnings          = 0;  /* reset warnings */

        rd_start          = enc_wr_index;
        pre_sample_ticks  = 0;

        pcmrec_refresh_watermarks();

        if (pre_record_ticks)
        {
            int i = rd_start;
            /* calculate number of available chunks */
            unsigned long avail_pre_chunks = (enc_wr_index - enc_rd_index +
                            enc_num_chunks) % enc_num_chunks;
            /* overflow at 974 seconds of prerecording at 44.1kHz */
            unsigned long pre_record_sample_ticks =
                                enc_sample_rate*pre_record_ticks;
            int pre_chunks = 0; /* Counter to limit prerecorded time to
                                   prevent flood state at outset */

            logf("pre-st: %ld", pre_record_sample_ticks);

            /* Get exact measure of recorded data as number of samples aren't
               nescessarily going to be the max for each chunk */
            for (; avail_pre_chunks-- > 0;)
            {
                struct enc_chunk_hdr *chunk;
                unsigned long chunk_sample_ticks;

                DEC_ENC_INDEX(i);

                chunk = GET_ENC_CHUNK(i);

                /* must have data to be counted */
                if (chunk->enc_data == NULL)
                    continue;

                chunk_sample_ticks = chunk->num_pcm*HZ;

                rd_start           = i;
                pre_sample_ticks  += chunk_sample_ticks;
                num_rec_bytes     += chunk->enc_size;
                num_rec_samples   += chunk->num_pcm;
                pre_chunks++;

                /* stop here if enough already */
                if (pre_chunks >= high_watermark ||
                    pre_sample_ticks >= pre_record_sample_ticks)
                {
                    logf("pre-chks: %d", pre_chunks);
                    break;
                }
            }

#if 0
            accum_rec_bytes   = num_rec_bytes;
            accum_pcm_samples = num_rec_samples;
#endif
        }

        enc_rd_index = rd_start;

        /* filename queue should be empty */
        if (!pcmrec_fnq_is_empty())
        {
            logf("fnq: not empty!");
            pcmrec_fnq_set_empty();
        }

        flags = CHUNKF_START_FILE;
        if (pre_sample_ticks > 0)
            flags |= CHUNKF_PRERECORD;

        pre_index    = enc_rd_index;

        dma_lock     = false;
        is_paused    = false;
        is_recording = true;
    }
    else
    {
        /* already recording, just split the stream */
        logf("inserting split");
        flags     = CHUNKF_START_FILE | CHUNKF_END_FILE;
        pre_index = 0;
    }

    pcmrec_new_stream(filename, flags, pre_index);
    logf("pcmrec_record done");
} /* pcmrec_record */

/* PCMREC_STOP */
static void pcmrec_stop(void)
{
    logf("pcmrec_stop");
   
    if (is_recording)
    {
        dma_lock = true;    /* lock dma write position */

        /* flush all available data first to avoid overflow while waiting
           for encoding to finish */
        pcmrec_flush(PCMREC_FLUSH_ALL);

        /* wait for encoder to finish remaining data */
        while (errors == 0 && !pcm_buffer_empty)
            yield();

        /* end stream at last data */
        pcmrec_new_stream(NULL, CHUNKF_END_FILE, 0);

        /* flush anything else encoder added */
        pcmrec_flush(PCMREC_FLUSH_ALL);

        /* remove any pending file start not yet processed - should be at
           most one at enc_wr_index */
        pcmrec_fnq_get_filename(NULL);
        /* encoder should abort any chunk it was in midst of processing */
        GET_ENC_CHUNK(enc_wr_index)->flags = CHUNKF_ABORT;

        /* filename queue should be empty */
        if (!pcmrec_fnq_is_empty())
        {
            logf("fnq: not empty!");
            pcmrec_fnq_set_empty();
        }   

        /* be absolutely sure the file is closed */
        if (errors != 0)
            pcmrec_close_file(&rec_fdata.rec_file);
        rec_fdata.rec_file = -1;

        is_recording = false;
        is_paused    = false;
        dma_lock     = pre_record_ticks == 0;
    }
    else
    {
        logf("not recording");
    }

    logf("pcmrec_stop done");
} /* pcmrec_stop */

/* PCMREC_PAUSE */
static void pcmrec_pause(void)
{
    logf("pcmrec_pause");

    if (!is_recording)
    {
        logf("not recording");
    }
    else if (is_paused)
    {
        logf("already paused");
    }
    else
    {
        dma_lock  = true;
        is_paused = true;
    }

    logf("pcmrec_pause done");
} /* pcmrec_pause */

/* PCMREC_RESUME */
static void pcmrec_resume(void)
{
    logf("pcmrec_resume");
    
    if (!is_recording)
    {
        logf("not recording");
    }
    else if (!is_paused)
    {
        logf("not paused");
    }
    else
    {
        is_paused    = false;
        is_recording = true;
        dma_lock     = false;
    }

    logf("pcmrec_resume done");
} /* pcmrec_resume */

static void pcmrec_thread(void) __attribute__((noreturn));
static void pcmrec_thread(void)
{
    struct queue_event ev;

    logf("thread pcmrec start");

    while(1)
    {
        if (is_recording)
        {
            /* Poll periodically to flush data */
            queue_wait_w_tmo(&pcmrec_queue, &ev, HZ/5);

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

        switch (ev.id)
        {
            case PCMREC_INIT:
                pcmrec_init();
                break;

            case PCMREC_CLOSE:
                pcmrec_close();
                break;

            case PCMREC_OPTIONS:
                pcmrec_set_recording_options(
                    (struct audio_recording_options *)ev.data);
                break;

            case PCMREC_RECORD:
                clear_flush_interrupt();
                pcmrec_record((const char *)ev.data);
                break;

            case PCMREC_STOP:
                clear_flush_interrupt();
                pcmrec_stop();
                break;

            case PCMREC_PAUSE:
                clear_flush_interrupt();
                pcmrec_pause();
                break;

            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;
                pcmrec_close();
                usb_acknowledge(SYS_USB_CONNECTED_ACK);
                usb_wait_for_disconnect(&pcmrec_queue);
                flush_interrupts = 0;
                break;
        } /* end switch */
    } /* end while */
} /* pcmrec_thread */

/****************************************************************************/
/*                                                                          */
/*         following functions will be called by the encoder codec          */
/*         in a free-threaded manner                                        */
/*                                                                          */
/****************************************************************************/

/* pass the encoder settings to the encoder */
void enc_get_inputs(struct enc_inputs *inputs)
{
    inputs->sample_rate  = sample_rate;
    inputs->num_channels = num_channels;
    inputs->rec_mono_mode = rec_mono_mode;
    inputs->config       = &enc_config;
} /* enc_get_inputs */

/* set the encoder dimensions (called by encoder codec at initialization and
   termination) */
void enc_set_parameters(struct enc_parameters *params)
{
    size_t bufsize, resbytes;

    logf("enc_set_parameters");

    if (!params)
    {
        logf("reset");
        /* Encoder is terminating */
        memset(&enc_config, 0, sizeof (enc_config));
        enc_sample_rate = 0;
        cancel_cpu_boost(); /* Make sure no boost remains */
        return;
    }

    enc_sample_rate = params->enc_sample_rate;
    logf("enc sampr:%lu", enc_sample_rate);

    pcm_rd_pos = dma_wr_pos;
    pcm_enc_pos = pcm_rd_pos;

    enc_config.afmt     = params->afmt;
    /* addition of the header is always implied - chunk size 4-byte aligned */
    enc_chunk_size      =
                ALIGN_UP_P2(ENC_CHUNK_HDR_SIZE + params->chunk_size, 2);
    enc_events_callback = params->events_callback;

    logf("chunk size:%lu", enc_chunk_size);

    /*** Configure the buffers ***/

    /* Layout of recording buffer:
     * [ax] = possible alignment x multiple
     * [sx] = possible size alignment of x multiple
     * |[a16]|[s4]:PCM Buffer+PCM Guard|[s4 each]:Encoder Chunks|->
     * |[[s4]:Reserved Bytes]|Filename Queue->|[space]|
     */
    resbytes = ALIGN_UP_P2(params->reserve_bytes, 2);
    logf("resbytes:%lu", resbytes);

    bufsize   = rec_buffer_size - (enc_buffer - pcm_buffer) -
                resbytes - FNQ_MIN_NUM_PATHS*MAX_PATH
#ifdef DEBUG
                - sizeof (*wrap_id_p)
#endif
                ;

    enc_num_chunks = bufsize / enc_chunk_size;
    logf("num chunks:%d", enc_num_chunks);
        
    /* get real amount used by encoder chunks */
    bufsize = enc_num_chunks*enc_chunk_size;
    logf("enc size:%lu", bufsize);

#ifdef DEBUG
    /* add magic at wraparound for spillover checks */
    wrap_id_p  = SKIPBYTES((unsigned long *)enc_buffer, bufsize);
    bufsize   += sizeof (*wrap_id_p);
    *wrap_id_p = ENC_CHUNK_MAGIC;
#endif

    /** set OUT parameters **/
    params->enc_buffer     = enc_buffer;
    params->buf_chunk_size = enc_chunk_size;
    params->num_chunks     = enc_num_chunks;

    /* calculate reserve buffer start and return pointer to encoder */
    params->reserve_buffer = NULL;
    if (resbytes > 0)
    {
        params->reserve_buffer = enc_buffer + bufsize;
        bufsize               += resbytes;
    }

    /* place filename queue at end of buffer using up whatever remains */
    fnq_rd_pos = 0; /* reset */
    fnq_wr_pos = 0; /* reset */
    fn_queue   = enc_buffer + bufsize;
    fnq_size   = pcm_buffer + rec_buffer_size - fn_queue;
    fnq_size  /= MAX_PATH;
    if (fnq_size > FNQ_MAX_NUM_PATHS)
        fnq_size = FNQ_MAX_NUM_PATHS;
    fnq_size  *= MAX_PATH;
    logf("fnq files:%ld", fnq_size / MAX_PATH);

#if defined(DEBUG)
    logf("ab :%08lX", (uintptr_t)audiobuf);
    logf("pcm:%08lX", (uintptr_t)pcm_buffer);
    logf("enc:%08lX", (uintptr_t)enc_buffer);
    logf("res:%08lX", (uintptr_t)params->reserve_buffer);
    logf("wip:%08lX", (uintptr_t)wrap_id_p);
    logf("fnq:%08lX", (uintptr_t)fn_queue);
    logf("end:%08lX", (uintptr_t)fn_queue + fnq_size);
    logf("abe:%08lX", (uintptr_t)audiobufend);
#endif

    /* init all chunk headers and reset indexes */
    enc_rd_index = 0;
    for (enc_wr_index = enc_num_chunks; enc_wr_index > 0; )
    {
        struct enc_chunk_hdr *chunk = GET_ENC_CHUNK(--enc_wr_index);
#ifdef DEBUG
        chunk->id = ENC_CHUNK_MAGIC;
#endif
        chunk->flags = 0;
    }

    logf("enc_set_parameters done");
} /* enc_set_parameters */

/* return encoder chunk at current write position  -
   NOTE: can be called by pcmrec thread when splitting streams */
struct enc_chunk_hdr * enc_get_chunk(void)
{
    struct enc_chunk_hdr *chunk = GET_ENC_CHUNK(enc_wr_index);

#ifdef DEBUG
    if (chunk->id != ENC_CHUNK_MAGIC || *wrap_id_p != ENC_CHUNK_MAGIC)
    {
        errors |= PCMREC_E_CHUNK_OVF;
        logf("finish chk ovf: %d", enc_wr_index);
    }
#endif

    chunk->flags &= CHUNKF_START_FILE;

    if (!is_recording)
        chunk->flags |= CHUNKF_PRERECORD;

    return chunk;
} /* enc_get_chunk */

/* releases the current chunk into the available chunks - 
   NOTE: can be called by pcmrec thread when splitting streams */
void enc_finish_chunk(void)
{
    struct enc_chunk_hdr *chunk = GET_ENC_CHUNK(enc_wr_index);

    if ((long)chunk->flags < 0)
    {
        /* encoder set error flag */
        errors |= PCMREC_E_ENCODER;
        logf("finish chk enc error");
    }

    /* advance enc_wr_index to the next encoder chunk */
    INC_ENC_INDEX(enc_wr_index);

    if (enc_rd_index != enc_wr_index)
    {
        num_rec_bytes      += chunk->enc_size;
        num_rec_samples    += chunk->num_pcm;
#if 0
        accum_rec_bytes    += chunk->enc_size;
        accum_pcm_samples  += chunk->num_pcm;
#endif
    }
    else if (is_recording)        /* buffer full */
    {
        /* keep current position and put up warning flag */
        warnings |= PCMREC_W_ENC_BUFFER_OVF;
        logf("enc_buffer ovf");
        DEC_ENC_INDEX(enc_wr_index);
        if (pcmrec_context)
        {
            /* if stream splitting, keep this out of circulation and
               flush a small number, then readd - cannot risk losing
               stream markers */
            logf("mini flush");
            pcmrec_flush(PCMREC_FLUSH_MINI);
            INC_ENC_INDEX(enc_wr_index);
        }
    }
    else
    {
        /* advance enc_rd_index for prerecording */
        INC_ENC_INDEX(enc_rd_index);
    }
} /* enc_finish_chunk */

/* passes a pointer to next chunk of unprocessed wav data */
/* TODO: this really should give the actual size returned */
unsigned char * enc_get_pcm_data(size_t size)
{
    int    wp    = dma_wr_pos;
    size_t avail = (wp - pcm_rd_pos) & PCM_CHUNK_MASK;

    /* limit the requested pcm data size */
    if (size > PCM_MAX_FEED_SIZE)
        size = PCM_MAX_FEED_SIZE;

    if (avail >= size)
    {
        unsigned char *ptr = pcm_buffer + pcm_rd_pos;
        int next_pos = (pcm_rd_pos + size) & PCM_CHUNK_MASK;

        pcm_enc_pos = pcm_rd_pos;
        pcm_rd_pos = next_pos;

        /* ptr must point to continous data at wraparound position */
        if ((size_t)pcm_rd_pos < size)
        {
            memcpy(pcm_buffer + PCM_NUM_CHUNKS*PCM_CHUNK_SIZE,
                   pcm_buffer, pcm_rd_pos);
        }

        if (avail >= (sample_rate << 2))
        {
            /* Filling up - boost codec */
            trigger_cpu_boost();
        }

        pcm_buffer_empty = false;
        return ptr;
    }

    /* not enough data available - encoder should idle */
    pcm_buffer_empty = true;

    cancel_cpu_boost();

    /* Sleep long enough to allow one frame on average */
    sleep(0);

    return NULL;
} /* enc_get_pcm_data */

/* puts some pcm data back in the queue */
size_t enc_unget_pcm_data(size_t size)
{
    int    wp        = dma_wr_pos;
    size_t old_avail = ((pcm_rd_pos - wp) & PCM_CHUNK_MASK) -
                            2*PCM_CHUNK_SIZE;

    /* allow one interrupt to occur during this call and not have the
       new read position inside the DMA destination chunk */
    if ((ssize_t)old_avail > 0)
    {
        /* limit size to amount of old data remaining */
        if (size > old_avail)
            size = old_avail;

        pcm_enc_pos = (pcm_rd_pos - size) & PCM_CHUNK_MASK;
        pcm_rd_pos = pcm_enc_pos;

        return size;
    }

    return 0;
} /* enc_unget_pcm_data */
