d56999890f
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@26253 a1c6a512-1295-4272-9138-f99709370657
1807 lines
55 KiB
C
1807 lines
55 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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.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 (playback only) */
|
|
pcm_set_frequency(HW_SAMPR_DEFAULT);
|
|
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]);
|
|
/* 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);
|
|
}
|
|
|
|
/* 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 */
|