summaryrefslogtreecommitdiff
path: root/firmware/pcm_record.c
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2007-02-16 08:52:06 +0000
committerMichael Sevakis <jethead71@rockbox.org>2007-02-16 08:52:06 +0000
commitec1f4829cd4276137d3877e36a95c7671ea778d8 (patch)
treec8382071c39def196d2d7730304caf40809278c3 /firmware/pcm_record.c
parentfce01de0f0eeb72d67eebc66304ec1e7af04a375 (diff)
SWCODEC Recording: Improve buffer handling by 1) Lowering watermark for flushing 2) Flushing down to a low watermark instead of a fixed number of chunks 3) Allowing in-process automatic flushes to be interrupted so file splits and stops can be marked in a timely manner (especially important when approaching 2GB limit) 4) Flush small amounts if buffer is filled during a split to protect integrity of streams 5) Boost both pcmrec and codec threads then thread priority boost is needed so that encoder doesn't fail to run and keep up with incoming data
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@12332 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'firmware/pcm_record.c')
-rw-r--r--firmware/pcm_record.c651
1 files changed, 405 insertions, 246 deletions
diff --git a/firmware/pcm_record.c b/firmware/pcm_record.c
index 98473a3870..85ab7caafc 100644
--- a/firmware/pcm_record.c
+++ b/firmware/pcm_record.c
@@ -46,6 +46,7 @@
/** These items may be implemented target specifically or need to
be shared semi-privately **/
+extern struct thread_entry *codec_thread_p;
/* the registered callback function for when more data is available */
volatile pcm_more_callback_type2 pcm_callback_more_ready = NULL;
@@ -57,6 +58,14 @@ 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) */
/** Stats on encoded data for current file **/
static size_t num_rec_bytes; /* Num bytes recorded */
@@ -179,31 +188,62 @@ static unsigned char *pcm_buffer; /* circular recording buffer */
static unsigned char *enc_buffer; /* circular encoding buffer */
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 wav_queue_empty; /* all wav chunks processed? */
+static bool pcmrec_context = false; /* called by pcmrec thread? */
+static bool pcm_buffer_empty; /* all pcm chunks processed? */
/** file flushing **/
-static int write_threshold; /* max chunk limit for data flush */
-static int spinup_time = -1;/* last ata_spinup_time */
+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_ata_spinup_time = -1;/* previous spin time used */
#ifdef HAVE_PRIORITY_SCHEDULING
-static int panic_threshold; /* boost thread prio when here */
+static int flood_watermark; /* boost thread priority when here */
#endif
+/* Constants that control watermarks */
+#define LOW_SECONDS 1 /* low watermark time till empty */
+#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 <= 16
+#define PANIC_SECONDS 5 /* flood watermark time until full */
+#define FLUSH_SECONDS 7 /* flush watermark time until full */
+#else
+#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 */
+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 */
+};
+
/** extra debugging info positioned away from other vars **/
#ifdef PCMREC_PARANOID
static unsigned long *wrap_id_p; /* magic at end of encoding buffer */
@@ -220,6 +260,7 @@ static struct event_queue pcmrec_queue;
static struct queue_sender_list pcmrec_queue_send;
static long pcmrec_stack[3*DEFAULT_STACK_SIZE/sizeof(long)];
static const char pcmrec_thread_name[] = "pcmrec";
+static struct thread_entry *pcmrec_thread_p;
static void pcmrec_thread(void);
@@ -261,8 +302,9 @@ static int pcm_rec_have_more(int status)
/* advance write position */
int next_pos = (dma_wr_pos + PCM_CHUNK_SIZE) & PCM_CHUNK_MASK;
- /* set pcm ovf if read position is inside current write chunk */
- if ((unsigned)(pcm_rd_pos - next_pos) < PCM_CHUNK_SIZE)
+ /* set pcm ovf if processing start position is inside current
+ write chunk */
+ if ((unsigned)(pcm_enc_pos - next_pos) < PCM_CHUNK_SIZE)
warnings |= PCMREC_W_PCM_BUFFER_OVF;
#ifdef PCMREC_PARANOID
@@ -374,16 +416,13 @@ void pcm_rec_init(void)
{
queue_init(&pcmrec_queue, true);
queue_enable_queue_send(&pcmrec_queue, &pcmrec_queue_send);
- create_thread(pcmrec_thread, pcmrec_stack, sizeof(pcmrec_stack),
- pcmrec_thread_name IF_PRIO(, PRIORITY_RECORDING));
+ pcmrec_thread_p =
+ create_thread(pcmrec_thread, pcmrec_stack, sizeof(pcmrec_stack),
+ pcmrec_thread_name IF_PRIO(, PRIORITY_RECORDING));
} /* pcm_rec_init */
/** audio_* group **/
-/* NOTE: The following posting functions are really only single-thread safe
- at the moment since a response to a particular message at a particular
- position in the queue can't be distinguished */
-
/**
* Initializes recording - call before calling any other recording function
*/
@@ -421,6 +460,8 @@ void audio_set_recording_options(struct audio_recording_options *options)
void audio_record(const char *filename)
{
logf("audio_record: %s", filename);
+ flush_interrupts++;
+ logf("flush int: %d", flush_interrupts);
queue_send(&pcmrec_queue, PCMREC_RECORD, (intptr_t)filename);
logf("audio_record_done");
} /* audio_record */
@@ -431,6 +472,8 @@ void audio_record(const char *filename)
void audio_stop_recording(void)
{
logf("audio_stop_recording");
+ flush_interrupts++;
+ logf("flush int: %d", flush_interrupts);
queue_send(&pcmrec_queue, PCMREC_STOP, 0);
logf("audio_stop_recording done");
} /* audio_stop_recording */
@@ -441,6 +484,8 @@ void audio_stop_recording(void)
void audio_pause_recording(void)
{
logf("audio_pause_recording");
+ flush_interrupts++;
+ logf("flush int: %d", flush_interrupts);
queue_send(&pcmrec_queue, PCMREC_PAUSE, 0);
logf("audio_pause_recording done");
} /* audio_pause_recording */
@@ -787,95 +832,166 @@ static void pcmrec_end_file(void)
} /* 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", ata_spinup_time);
+
+ /* set the low mark for when flushing stops if automatic */
+ low_watermark = (LOW_SECONDS*4*sample_rate + (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. */
+ flood_watermark = enc_num_chunks -
+ (PANIC_SECONDS*4*sample_rate + (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_ata_spinup_time = ata_spinup_time;
+
+ /* 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? */
+
+ /* 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)
+ {
+ high_watermark = low_watermark;
+ low_watermark /= 2;
+ logf("warning: low 'write at'");
+ }
+
+ 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.
- * flush_num = -1 to flush all available chunks to disk.
- * flush_num = 0 normal write thresholding
- * flush_num = 1 or greater - all available chunks of current file plus
- * flush_num file starts if first chunk has been processed.
- *
+ * 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 = 0;
- unsigned long start_tick;
- int num;
- int prio;
+ 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;
- int i;
+ 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;
-#ifdef HAVE_PRIORITY_SCHEDULING
- num = num_ready;
-#endif
+ /* 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 (ata_spinup_time != spinup_time)
- {
- /* spinup time has changed, calculate new write threshold */
- logf("new t spinup : %d", ata_spinup_time);
- unsigned long st = spinup_time = ata_spinup_time;
-
- /* write at 5s + st remaining in enc_buffer */
- if (st < 2*HZ)
- st = 2*HZ; /* my drive is usually < 250 ticks :) */
- else if (st > 10*HZ)
- st = 10*HZ;
-
- write_threshold = enc_num_chunks -
- (int)(((5ull*HZ + st)*4ull*sample_rate + (enc_chunk_size-1)) /
- (enc_chunk_size*HZ));
-
- if (write_threshold < 0)
- write_threshold = 0;
-#ifdef HAVE_PRIORITY_SCHEDULING
- else if (write_threshold > panic_threshold)
- write_threshold = panic_threshold;
-#endif
- logf("new wr thresh: %d", write_threshold);
- }
+ if (ata_spinup_time != last_ata_spinup_time)
+ pcmrec_refresh_watermarks();
- if (num_ready < write_threshold)
+ /* 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 */
- if (current_tick - last_flush_tick < HZ/2)
- num = panic_threshold;
-#endif
+ boost stat by expiring timeout in advance */
+ logf("too frequent flush");
+ prio_tick = current_tick - 1;
}
-#ifdef HAVE_PRIORITY_SCHEDULING
- start_tick = current_tick;
- prio = -1;
+ prio_pcmrec = -1;
+ prio_codec = -1; /* GCC is too stoopid to figure out it doesn't
+ need init */
#endif
- logf("writing: %d (%d)", num_ready, flush_num);
+ logf("writing:%d(%d):%s%s", num_ready, flush_num,
+ interruptable ? "i" : "",
+ flush_num == PCMREC_FLUSH_MINI ? "m" : "");
cpu_boost(true);
- for (i = 0; i < num_ready; i++)
+ 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 == -1 && (num >= panic_threshold ||
- current_tick - start_tick > 10*HZ))
+ if (prio_pcmrec == -1 && (num >= flood_watermark ||
+ TIME_AFTER(current_tick, prio_tick)))
{
- /* losing ground - boost priority until finished */
- logf("pcmrec: boost priority");
- prio = thread_set_priority(NULL, thread_get_priority(NULL)-1);
+ /* losing ground or holding without progress - boost
+ priority until finished */
+ logf("pcmrec: boost (%s)",
+ num >= flood_watermark ? "num" : "time");
+ prio_pcmrec = thread_set_priority(NULL,
+ thread_get_priority(NULL) - 1);
+ prio_codec = thread_set_priority(codec_thread_p,
+ thread_get_priority(codec_thread_p) - 1);
}
#endif
@@ -888,8 +1004,8 @@ static void pcmrec_flush(unsigned flush_num)
if (rec_fdata.chunk->flags & CHUNKF_START_FILE)
{
pcmrec_start_file();
- if (--flush_num == 0)
- i = num_ready; /* stop on next loop - must write this
+ if (--remaining == 0)
+ num_ready = 0; /* stop on next loop - must write this
chunk if it has data */
}
@@ -903,16 +1019,15 @@ static void pcmrec_flush(unsigned flush_num)
if (errors != 0)
break;
-#ifdef HAVE_PRIORITY_SCHEDULING
- if (prio == -1)
+ if (flush_num == PCMREC_FLUSH_MINI &&
+ ++chunks_flushed >= MINI_CHUNKS)
{
- num = enc_wr_index - enc_rd_index;
- if (num < 0)
- num += enc_num_chunks;
+ logf("mini flush break");
+ break;
}
-#endif
- /* no yielding, the file apis called in the codecs do that */
- } /* end for */
+ /* no yielding; the file apis called in the codecs do that
+ sufficiently */
+ } /* end while */
/* sync file */
if (rec_fdata.rec_file >= 0)
@@ -921,11 +1036,12 @@ static void pcmrec_flush(unsigned flush_num)
cpu_boost(false);
#ifdef HAVE_PRIORITY_SCHEDULING
- if (prio != -1)
+ if (prio_pcmrec != -1)
{
- /* return to original priority */
+ /* return to original priorities */
logf("pcmrec: unboost priority");
- thread_set_priority(NULL, prio);
+ thread_set_priority(NULL, prio_pcmrec);
+ thread_set_priority(codec_thread_p, prio_codec);
}
last_flush_tick = current_tick; /* save tick when we left */
@@ -948,10 +1064,14 @@ static void pcmrec_new_stream(const char *filename, /* next file name */
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;
- struct enc_chunk_hdr *start = NULL;
+ 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? */
int get_chunk_index(struct enc_chunk_hdr *chunk)
{
@@ -967,6 +1087,10 @@ static void pcmrec_new_stream(const char *filename, /* next file name */
return GET_ENC_CHUNK(index);
}
+ if (filename)
+ strncpy(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);
@@ -1039,7 +1163,9 @@ static void pcmrec_new_stream(const char *filename, /* next file name */
}
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)
{
@@ -1049,11 +1175,10 @@ static void pcmrec_new_stream(const char *filename, /* next file name */
if (start)
{
- char buf[MAX_PATH]; /* place to copy in case we're full */
-
if (!(flags & CHUNKF_PRERECORD))
{
- /* get stats on data added to start - sort of a prerecord operation */
+ /* get stats on data added to start - sort of a prerecord
+ operation */
int i = get_chunk_index(data.chunk);
#ifdef PCMREC_PARANOID
int i_last = i;
@@ -1083,16 +1208,16 @@ static void pcmrec_new_stream(const char *filename, /* next file name */
if (fnq_add_fn == pcmrec_fnq_add_filename && pcmrec_fnq_is_full())
{
logf("fnq full");
- /* make a local copy of filename and let sender go as this
- flush will hang the screen for a bit otherwise */
- strncpy(buf, filename, MAX_PATH);
- filename = buf;
- queue_reply(&pcmrec_queue, 0);
- pcmrec_flush(-1);
+ pcmrec_flush(PCMREC_FLUSH_ALL);
+ did_flush = true;
}
- fnq_add_fn(filename);
+ 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 */
@@ -1102,6 +1227,7 @@ static void pcmrec_init(void)
{
unsigned char *buffer;
+ pcmrec_close_file(&rec_fdata.rec_file);
rec_fdata.rec_file = -1;
/* warings and errors */
@@ -1112,6 +1238,7 @@ static void pcmrec_init(void)
dma_lock = true;
SET_PCM_POS(pcm_rd_pos, 0);
SET_PCM_POS(dma_wr_pos, 0);
+ pcm_enc_pos = 0;
/* encoder FIFO */
SET_ENC_INDEX(enc_wr_index, 0);
@@ -1137,7 +1264,7 @@ static void pcmrec_init(void)
buffer = audio_get_recording_buffer(&rec_buffer_size);
/* Line align pcm_buffer 2^4=16 bytes */
- pcm_buffer = (unsigned char *)ALIGN_UP_P2((unsigned long)buffer, 4);
+ pcm_buffer = (unsigned char *)ALIGN_UP_P2((uintptr_t)buffer, 4);
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 */
@@ -1158,7 +1285,8 @@ static void pcmrec_close(void)
} /* pcmrec_close */
/* PCMREC_OPTIONS */
-static void pcmrec_set_recording_options(struct audio_recording_options *options)
+static void pcmrec_set_recording_options(
+ struct audio_recording_options *options)
{
/* stop DMA transfer */
dma_lock = true;
@@ -1222,97 +1350,111 @@ 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;
+ num_rec_bytes = 0;
+ num_rec_samples = 0;
- if (is_recording)
+ if (!is_recording)
{
- /* already recording, just split the stream */
- logf("inserting split");
- pcmrec_new_stream(filename,
- CHUNKF_START_FILE | CHUNKF_END_FILE,
- 0);
- goto record_done;
- }
-
#if 0
- accum_rec_bytes = 0;
- accum_pcm_samples = 0;
+ accum_rec_bytes = 0;
+ accum_pcm_samples = 0;
#endif
- spinup_time = -1;
- warnings = 0; /* reset warnings */
+ warnings = 0; /* reset warnings */
- rd_start = enc_wr_index;
- pre_sample_ticks = 0;
+ rd_start = enc_wr_index;
+ pre_sample_ticks = 0;
- if (pre_record_ticks)
- {
- int i = rd_start;
+ pcmrec_refresh_watermarks();
+
+ if (pre_record_ticks)
+ {
+ int i = rd_start;
#ifdef PCMREC_PARANOID
- int i_last = i;
+ int i_last = i;
#endif
- /* 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;
-
- /* 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;
+ /* 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);
+ DEC_ENC_INDEX(i);
- chunk = GET_ENC_CHUNK(i);
+ chunk = GET_ENC_CHUNK(i);
- /* must have data to be counted */
- if (chunk->enc_data == NULL)
- continue;
+ /* must have data to be counted */
+ if (chunk->enc_data == NULL)
+ continue;
- chunk_sample_ticks = chunk->num_pcm*HZ;
+ 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;
+ 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_sample_ticks >= pre_record_sample_ticks)
- break;
- }
+ /* 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;
+ accum_rec_bytes = num_rec_bytes;
+ accum_pcm_samples = num_rec_samples;
#endif
- }
+ }
+
+ SET_ENC_INDEX(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;
- SET_ENC_INDEX(enc_rd_index, rd_start);
+ pre_index = enc_rd_index;
- /* filename queue should be empty */
- if (!pcmrec_fnq_is_empty())
+ dma_lock = false;
+ is_paused = false;
+ is_recording = true;
+ }
+ else
{
- logf("fnq: not empty!");
- pcmrec_fnq_set_empty();
+ /* already recording, just split the stream */
+ logf("inserting split");
+ flags = CHUNKF_START_FILE | CHUNKF_END_FILE;
+ pre_index = 0;
}
-
- dma_lock = false;
- is_paused = false;
- is_recording = true;
- pcmrec_new_stream(filename,
- CHUNKF_START_FILE |
- (pre_sample_ticks > 0 ? CHUNKF_PRERECORD : 0),
- enc_rd_index);
-
-record_done:
+ pcmrec_new_stream(filename, flags, pre_index);
logf("pcmrec_record done");
} /* pcmrec_record */
@@ -1321,52 +1463,52 @@ static void pcmrec_stop(void)
{
logf("pcmrec_stop");
- if (!is_recording)
+ if (is_recording)
{
- logf("not recording");
- goto not_recording;
- }
+ dma_lock = true; /* lock dma write position */
+ queue_reply(&pcmrec_queue, 0);
- dma_lock = true; /* lock dma write position */
- queue_reply(&pcmrec_queue, 0);
+ /* flush all available data first to avoid overflow while waiting
+ for encoding to finish */
+ pcmrec_flush(PCMREC_FLUSH_ALL);
- /* flush all available data first to avoid overflow while waiting
- for encoding to finish */
- pcmrec_flush(-1);
-
- /* wait for encoder to finish remaining data */
- while (errors == 0 && !wav_queue_empty)
- yield();
-
- /* end stream at last data */
- pcmrec_new_stream(NULL, CHUNKF_END_FILE, 0);
-
- /* flush anything else encoder added */
- pcmrec_flush(-1);
-
- /* 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();
- }
+ /* wait for encoder to finish remaining data */
+ while (errors == 0 && !pcm_buffer_empty)
+ yield();
- /* be absolutely sure the file is closed */
- if (errors != 0)
- pcmrec_close_file(&rec_fdata.rec_file);
- rec_fdata.rec_file = -1;
+ /* end stream at last data */
+ pcmrec_new_stream(NULL, CHUNKF_END_FILE, 0);
+
+ /* flush anything else encoder added */
+ pcmrec_flush(PCMREC_FLUSH_ALL);
- is_recording = false;
- is_paused = false;
- dma_lock = pre_record_ticks == 0;
+ /* 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");
+ }
-not_recording:
logf("pcmrec_stop done");
} /* pcmrec_stop */
@@ -1378,18 +1520,17 @@ static void pcmrec_pause(void)
if (!is_recording)
{
logf("not recording");
- goto not_recording_or_paused;
}
else if (is_paused)
{
logf("already paused");
- goto not_recording_or_paused;
}
-
- dma_lock = true; /* fix DMA write pointer at current position */
- is_paused = true;
-
-not_recording_or_paused:
+ else
+ {
+ dma_lock = true;
+ is_paused = true;
+ }
+
logf("pcmrec_pause done");
} /* pcmrec_pause */
@@ -1401,19 +1542,18 @@ static void pcmrec_resume(void)
if (!is_recording)
{
logf("not recording");
- goto not_recording_or_not_paused;
}
else if (!is_paused)
{
logf("not paused");
- goto not_recording_or_not_paused;
}
-
- is_paused = false;
- is_recording = true;
- dma_lock = false;
-
-not_recording_or_not_paused:
+ else
+ {
+ is_paused = false;
+ is_recording = true;
+ dma_lock = false;
+ }
+
logf("pcmrec_resume done");
} /* pcmrec_resume */
@@ -1424,6 +1564,12 @@ static void pcmrec_thread(void)
logf("thread pcmrec start");
+ static void clear_flush_interrupt(void)
+ {
+ if (--flush_interrupts < 0)
+ flush_interrupts = 0;
+ }
+
while(1)
{
if (is_recording)
@@ -1433,7 +1579,9 @@ static void pcmrec_thread(void)
if (ev.id == SYS_TIMEOUT)
{
- pcmrec_flush(0); /* flush if getting full */
+ /* Messages that interrupt this will complete it */
+ pcmrec_flush(PCMREC_FLUSH_IF_HIGH |
+ PCMREC_FLUSH_INTERRUPTABLE);
continue;
}
}
@@ -1459,14 +1607,17 @@ static void pcmrec_thread(void)
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;
@@ -1483,6 +1634,9 @@ static void pcmrec_thread(void)
break;
pcmrec_close();
reset_hardware();
+ /* Be sure other threads are released if waiting */
+ queue_clear(&pcmrec_queue);
+ flush_interrupts = 0;
usb_acknowledge(SYS_USB_CONNECTED_ACK);
usb_wait_for_disconnect(&pcmrec_queue);
break;
@@ -1495,6 +1649,7 @@ static void pcmrec_thread(void)
/****************************************************************************/
/* */
/* following functions will be called by the encoder codec */
+/* in a free-threaded manner */
/* */
/****************************************************************************/
@@ -1527,6 +1682,7 @@ void enc_set_parameters(struct enc_parameters *params)
logf("enc sampr:%d", enc_sample_rate);
SET_PCM_POS(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 */
@@ -1568,16 +1724,6 @@ void enc_set_parameters(struct enc_parameters *params)
*wrap_id_p = ENC_CHUNK_MAGIC;
#endif /* PCMREC_PARANOID */
-#ifdef HAVE_PRIORITY_SCHEDULING
- /* panic boost thread priority at 1 second remaining */
- panic_threshold = enc_num_chunks -
- (4*sample_rate + (enc_chunk_size-1)) / enc_chunk_size;
- if (panic_threshold < 0)
- panic_threshold = 0;
-
- logf("panic thr:%d", panic_threshold);
-#endif
-
/** set OUT parameters **/
params->enc_buffer = enc_buffer;
params->buf_chunk_size = enc_chunk_size;
@@ -1596,7 +1742,10 @@ void enc_set_parameters(struct enc_parameters *params)
fnq_wr_pos = 0; /* reset */
fn_queue = enc_buffer + bufsize;
fnq_size = pcm_buffer + rec_buffer_size - fn_queue;
- fnq_size = ALIGN_DOWN(fnq_size, MAX_PATH);
+ fnq_size /= MAX_PATH;
+ if (fnq_size > FNQ_MAX_NUM_PATHS)
+ fnq_size = FNQ_MAX_NUM_PATHS;
+ fnq_size *= MAX_PATH;
logf("fnq files: %d", fnq_size / MAX_PATH);
#if 0
@@ -1626,7 +1775,8 @@ void enc_set_parameters(struct enc_parameters *params)
logf("enc_set_parameters done");
} /* enc_set_parameters */
-/* return encoder chunk at current write position */
+/* 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);
@@ -1647,7 +1797,8 @@ struct enc_chunk_hdr * enc_get_chunk(void)
return chunk;
} /* enc_get_chunk */
-/* releases the current chunk into the available chunks */
+/* 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);
@@ -1675,10 +1826,19 @@ void enc_finish_chunk(void)
}
else if (is_recording) /* buffer full */
{
- /* keep current position - but put up warning flag */
+ /* 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
{
@@ -1691,7 +1851,7 @@ void enc_finish_chunk(void)
int enc_pcm_buf_near_empty(void)
{
/* less than 1sec raw data? => unboost encoder */
- int wp = dma_wr_pos;
+ int wp = dma_wr_pos;
size_t avail = (wp - pcm_rd_pos) & PCM_CHUNK_MASK;
return avail < (sample_rate << 2) ? 1 : 0;
} /* enc_pcm_buf_near_empty */
@@ -1700,7 +1860,7 @@ int enc_pcm_buf_near_empty(void)
/* TODO: this really should give the actual size returned */
unsigned char * enc_get_pcm_data(size_t size)
{
- int wp = dma_wr_pos;
+ int wp = dma_wr_pos;
size_t avail = (wp - pcm_rd_pos) & PCM_CHUNK_MASK;
/* limit the requested pcm data size */
@@ -1712,47 +1872,46 @@ unsigned char * enc_get_pcm_data(size_t 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;
+
SET_PCM_POS(pcm_rd_pos, next_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);
- wav_queue_empty = false;
+ pcm_buffer_empty = false;
return ptr;
}
/* not enough data available - encoder should idle */
- wav_queue_empty = true;
+ pcm_buffer_empty = true;
return NULL;
} /* enc_get_pcm_data */
/* puts some pcm data back in the queue */
size_t enc_unget_pcm_data(size_t size)
{
- /* can't let DMA advance write position when doing this */
- int level = set_irq_level(HIGHEST_IRQ_LEVEL);
+ int wp = dma_wr_pos;
+ size_t old_avail = ((pcm_rd_pos - wp) & PCM_CHUNK_MASK) -
+ 2*PCM_CHUNK_SIZE;
- if (pcm_rd_pos != dma_wr_pos)
+ /* 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)
{
- /* disallow backing up into current DMA write chunk */
- size_t old_avail = (pcm_rd_pos - dma_wr_pos - PCM_CHUNK_SIZE)
- & PCM_CHUNK_MASK;
- int next_pos;
-
/* limit size to amount of old data remaining */
if (size > old_avail)
size = old_avail;
- next_pos = (pcm_rd_pos - size) & PCM_CHUNK_MASK;
- SET_PCM_POS(pcm_rd_pos, next_pos);
- }
+ pcm_enc_pos = (pcm_rd_pos - size) & PCM_CHUNK_MASK;
+ SET_PCM_POS(pcm_rd_pos, pcm_enc_pos);
- set_irq_level(level);
+ return size;
+ }
- return size;
+ return 0;
} /* enc_unget_pcm_data */
/** Low level pcm recording apis **/