summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorBjörn Stenberg <bjorn@haxx.se>2008-10-14 11:12:20 +0000
committerBjörn Stenberg <bjorn@haxx.se>2008-10-14 11:12:20 +0000
commit9558c4956d3d603c4d132af88633767810f3ba62 (patch)
treeb0d739f6246a7175de106562a2c755724f977cbe /apps
parent91b9c6139b3447d7c0f4f2924ef73a9dc323703b (diff)
downloadrockbox-9558c4956d3d603c4d132af88633767810f3ba62.tar.gz
rockbox-9558c4956d3d603c4d132af88633767810f3ba62.zip
Moved pcm_record from firmware to apps. Cleaned up some. Now all code using struct mp3entry is in apps.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@18807 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps')
-rw-r--r--apps/SOURCES3
-rw-r--r--apps/id3.h246
-rw-r--r--apps/mpeg.c25
-rw-r--r--apps/mpeg.h66
-rw-r--r--apps/recorder/pcm_record.c1788
-rw-r--r--apps/recorder/pcm_record.h66
-rw-r--r--apps/recorder/recording.c1
-rw-r--r--apps/replaygain.h33
8 files changed, 2212 insertions, 16 deletions
diff --git a/apps/SOURCES b/apps/SOURCES
index 7622b416f7..3fce356092 100644
--- a/apps/SOURCES
+++ b/apps/SOURCES
@@ -116,6 +116,9 @@ codecs.c
dsp.c
#ifdef HAVE_RECORDING
enc_config.c
+#ifndef SIMULATOR
+recorder/pcm_record.c
+#endif /* SIMULATOR */
#endif
eq.c
#if defined(CPU_COLDFIRE)
diff --git a/apps/id3.h b/apps/id3.h
new file mode 100644
index 0000000000..da2faf1b12
--- /dev/null
+++ b/apps/id3.h
@@ -0,0 +1,246 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Daniel Stenberg
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef ID3_H
+#define ID3_H
+
+#include <stdbool.h>
+#include "config.h"
+#include "file.h"
+
+#define ID3V2_BUF_SIZE 300
+
+/* Audio file types. */
+/* NOTE: The values of the AFMT_* items are used for the %fc tag in the WPS
+ - so new entries MUST be added to the end to maintain compatibility.
+ */
+enum
+{
+ AFMT_UNKNOWN = 0, /* Unknown file format */
+
+ /* start formats */
+
+ AFMT_MPA_L1, /* MPEG Audio layer 1 */
+ AFMT_MPA_L2, /* MPEG Audio layer 2 */
+ AFMT_MPA_L3, /* MPEG Audio layer 3 */
+
+#if CONFIG_CODEC == SWCODEC
+ AFMT_AIFF, /* Audio Interchange File Format */
+ AFMT_PCM_WAV, /* Uncompressed PCM in a WAV file */
+ AFMT_OGG_VORBIS, /* Ogg Vorbis */
+ AFMT_FLAC, /* FLAC */
+ AFMT_MPC, /* Musepack */
+ AFMT_A52, /* A/52 (aka AC3) audio */
+ AFMT_WAVPACK, /* WavPack */
+ AFMT_ALAC, /* Apple Lossless Audio Codec */
+ AFMT_AAC, /* Advanced Audio Coding (AAC) in M4A container */
+ AFMT_SHN, /* Shorten */
+ AFMT_SID, /* SID File Format */
+ AFMT_ADX, /* ADX File Format */
+ AFMT_NSF, /* NESM (NES Sound Format) */
+ AFMT_SPEEX, /* Ogg Speex speech */
+ AFMT_SPC, /* SPC700 save state */
+ AFMT_APE, /* Monkey's Audio (APE) */
+ AFMT_WMA, /* WMAV1/V2 in ASF */
+ AFMT_MOD, /* Amiga MOD File Format */
+ AFMT_SAP, /* Amiga 8Bit SAP Format */
+#endif
+
+ /* add new formats at any index above this line to have a sensible order -
+ specified array index inits are used */
+ /* format arrays defined in id3.c */
+
+ AFMT_NUM_CODECS,
+
+#if CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING)
+ /* masks to decompose parts */
+ CODEC_AFMT_MASK = 0x0fff,
+ CODEC_TYPE_MASK = 0x7000,
+
+ /* switch for specifying codec type when requesting a filename */
+ CODEC_TYPE_DECODER = (0 << 12), /* default */
+ CODEC_TYPE_ENCODER = (1 << 12),
+#endif /* CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING) */
+};
+
+#if CONFIG_CODEC == SWCODEC
+#define CODEC_EXTENSION "codec"
+
+#ifdef HAVE_RECORDING
+#define ENCODER_SUFFIX "_enc"
+enum rec_format_indexes
+{
+ __REC_FORMAT_START_INDEX = -1,
+
+ /* start formats */
+
+ REC_FORMAT_PCM_WAV,
+ REC_FORMAT_AIFF,
+ REC_FORMAT_WAVPACK,
+ REC_FORMAT_MPA_L3,
+
+ /* add new formats at any index above this line to have a sensible order -
+ specified array index inits are used
+ REC_FORMAT_CFG_NUM_BITS should allocate enough bits to hold the range
+ REC_FORMAT_CFG_VALUE_LIST should be in same order as indexes
+ */
+
+ REC_NUM_FORMATS,
+
+ REC_FORMAT_DEFAULT = REC_FORMAT_PCM_WAV,
+ REC_FORMAT_CFG_NUM_BITS = 2
+};
+
+#define REC_FORMAT_CFG_VAL_LIST "wave,aiff,wvpk,mpa3"
+
+/* get REC_FORMAT_* corresponding AFMT_* */
+extern const int rec_format_afmt[REC_NUM_FORMATS];
+/* get AFMT_* corresponding REC_FORMAT_* */
+extern const int afmt_rec_format[AFMT_NUM_CODECS];
+
+#define AFMT_ENTRY(label, root_fname, enc_root_fname, ext_list) \
+ { label, root_fname, enc_root_fname, ext_list }
+#else /* !HAVE_RECORDING */
+#define AFMT_ENTRY(label, root_fname, enc_root_fname, ext_list) \
+ { label, root_fname, ext_list }
+#endif /* HAVE_RECORDING */
+#else /* !SWCODEC */
+
+#define AFMT_ENTRY(label, root_fname, enc_root_fname, ext_list) \
+ { label, ext_list }
+#endif /* CONFIG_CODEC == SWCODEC */
+
+/* record describing the audio format */
+struct afmt_entry
+{
+ char label[8]; /* format label */
+#if CONFIG_CODEC == SWCODEC
+ char *codec_root_fn; /* root codec filename (sans _enc and .codec) */
+#ifdef HAVE_RECORDING
+ char *codec_enc_root_fn; /* filename of encoder codec */
+#endif
+#endif
+ char *ext_list; /* double NULL terminated extension
+ list for type with the first as
+ the default for recording */
+};
+
+/* database of labels and codecs. add formats per above enum */
+extern const struct afmt_entry audio_formats[AFMT_NUM_CODECS];
+
+struct mp3entry {
+ char path[MAX_PATH];
+ char* title;
+ char* artist;
+ char* album;
+ char* genre_string;
+ char* disc_string;
+ char* track_string;
+ char* year_string;
+ char* composer;
+ char* comment;
+ char* albumartist;
+ char* grouping;
+ int discnum;
+ int tracknum;
+ int version;
+ int layer;
+ int year;
+ unsigned char id3version;
+ unsigned int codectype;
+ unsigned int bitrate;
+ unsigned long frequency;
+ unsigned long id3v2len;
+ unsigned long id3v1len;
+ unsigned long first_frame_offset; /* Byte offset to first real MP3 frame.
+ Used for skipping leading garbage to
+ avoid gaps between tracks. */
+ unsigned long vbr_header_pos;
+ unsigned long filesize; /* without headers; in bytes */
+ unsigned long length; /* song length in ms */
+ unsigned long elapsed; /* ms played */
+
+ int lead_trim; /* Number of samples to skip at the beginning */
+ int tail_trim; /* Number of samples to remove from the end */
+
+ /* Added for Vorbis */
+ unsigned long samples; /* number of samples in track */
+
+ /* MP3 stream specific info */
+ unsigned long frame_count; /* number of frames in the file (if VBR) */
+
+ /* Used for A52/AC3 */
+ unsigned long bytesperframe; /* number of bytes per frame (if CBR) */
+
+ /* Xing VBR fields */
+ bool vbr;
+ bool has_toc; /* True if there is a VBR header in the file */
+ unsigned char toc[100]; /* table of contents */
+
+ /* these following two fields are used for local buffering */
+ char id3v2buf[ID3V2_BUF_SIZE];
+ char id3v1buf[4][92];
+
+ /* resume related */
+ unsigned long offset; /* bytes played */
+ int index; /* playlist index */
+
+ /* runtime database fields */
+ long tagcache_idx; /* 0=invalid, otherwise idx+1 */
+ int rating;
+ int score;
+ long playcount;
+ long lastplayed;
+ long playtime;
+
+ /* replaygain support */
+
+#if CONFIG_CODEC == SWCODEC
+ char* track_gain_string;
+ char* album_gain_string;
+ long track_gain; /* 7.24 signed fixed point. 0 for no gain. */
+ long album_gain;
+ long track_peak; /* 7.24 signed fixed point. 0 for no peak. */
+ long album_peak;
+#endif
+
+ /* Cuesheet support */
+ int cuesheet_type; /* 0: none, 1: external, 2: embedded */
+
+ /* Musicbrainz Track ID */
+ char* mb_track_id;
+};
+
+enum {
+ ID3_VER_1_0 = 1,
+ ID3_VER_1_1,
+ ID3_VER_2_2,
+ ID3_VER_2_3,
+ ID3_VER_2_4
+};
+
+bool get_mp3_metadata(int fd, struct mp3entry *entry, const char *filename);
+bool mp3info(struct mp3entry *entry, const char *filename);
+char* id3_get_num_genre(unsigned int genre_num);
+int getid3v2len(int fd);
+void adjust_mp3entry(struct mp3entry *entry, void *dest, const void *orig);
+void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig);
+
+#endif
diff --git a/apps/mpeg.c b/apps/mpeg.c
index 2c65e46060..b570f41b28 100644
--- a/apps/mpeg.c
+++ b/apps/mpeg.c
@@ -2432,6 +2432,15 @@ void audio_set_recording_options(struct audio_recording_options *options)
DEBUGF("mas_writemem(MAS_BANK_D0, ENCODER_CONTROL, %x)\n", shadow_encoder_control);
+#if CONFIG_TUNER & S1A0903X01
+ /* Store the (unpitched) MAS PLL frequency. Used for avoiding FM
+ interference with the Samsung tuner. */
+ if (rec_frequency_index)
+ mas_store_pllfreq(24576000);
+ else
+ mas_store_pllfreq(22579000);
+#endif
+
shadow_soft_mute = options->rec_editable?4:0;
mas_writemem(MAS_BANK_D0, MAS_D0_SOFT_MUTE, &shadow_soft_mute,1);
@@ -2484,22 +2493,6 @@ void audio_set_recording_gain(int left, int right, int type)
mas_codec_writereg(0x0, shadow_codec_reg0);
}
-#if CONFIG_TUNER & S1A0903X01
-/* Get the (unpitched) MAS PLL frequency, for avoiding FM interference with the
- * Samsung tuner. Zero means unknown. Currently handles recording from analog
- * input only. */
-int mpeg_get_mas_pllfreq(void)
-{
- if (mpeg_mode != MPEG_ENCODER)
- return 0;
-
- if (rec_frequency_index == 0) /* 44.1 kHz / 22.05 kHz */
- return 22579000;
- else
- return 24576000;
-}
-#endif /* CONFIG_TUNER & S1A0903X01 */
-
/* try to make some kind of beep, also in recording mode */
void audio_beep(int duration)
{
diff --git a/apps/mpeg.h b/apps/mpeg.h
new file mode 100644
index 0000000000..ce2cff0069
--- /dev/null
+++ b/apps/mpeg.h
@@ -0,0 +1,66 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 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.
+ *
+ ****************************************************************************/
+#ifndef _MPEG_H_
+#define _MPEG_H_
+
+#include <stdbool.h>
+#include "id3.h"
+
+#define MPEG_SWAP_CHUNKSIZE 0x2000
+#define MPEG_HIGH_WATER 2 /* We leave 2 bytes empty because otherwise we
+ wouldn't be able to see the difference between
+ an empty buffer and a full one. */
+#define MPEG_LOW_WATER 0x60000
+#define MPEG_RECORDING_LOW_WATER 0x80000
+#define MPEG_LOW_WATER_CHUNKSIZE 0x40000
+#define MPEG_LOW_WATER_SWAP_CHUNKSIZE 0x10000
+#ifdef HAVE_MMC
+#define MPEG_PLAY_PENDING_THRESHOLD 0x20000
+#define MPEG_PLAY_PENDING_SWAPSIZE 0x20000
+#else
+#define MPEG_PLAY_PENDING_THRESHOLD 0x10000
+#define MPEG_PLAY_PENDING_SWAPSIZE 0x10000
+#endif
+
+#define MPEG_MAX_PRERECORD_SECONDS 30
+
+/* For ID3 info and VBR header */
+#define MPEG_RESERVED_HEADER_SPACE (4096 + 576)
+
+#if (CONFIG_CODEC == MAS3587F) || defined(SIMULATOR)
+
+#if CONFIG_TUNER & S1A0903X01
+int mpeg_get_mas_pllfreq(void);
+#endif
+
+#endif
+unsigned long mpeg_get_last_header(void);
+
+/* in order to keep the recording here, I have to expose this */
+void rec_tick(void);
+void playback_tick(void); /* FixMe: get rid of this, use mp3_get_playtime() */
+
+void audio_set_track_changed_event(void (*handler)(struct mp3entry *id3));
+void audio_set_track_buffer_event(void (*handler)(struct mp3entry *id3));
+void audio_set_track_unbuffer_event(void (*handler)(struct mp3entry *id3));
+void audio_set_cuesheet_callback(bool (*handler)(const char *filename));
+
+#endif
diff --git a/apps/recorder/pcm_record.c b/apps/recorder/pcm_record.c
new file mode 100644
index 0000000000..b1ea535470
--- /dev/null
+++ b/apps/recorder/pcm_record.c
@@ -0,0 +1,1788 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 "ata.h"
+#include "usb.h"
+#include "buffer.h"
+#include "general.h"
+#include "audio.h"
+#include "sound.h"
+#include "id3.h"
+#ifdef HAVE_SPDIF_IN
+#include "spdif.h"
+#endif
+
+/***************************************************************************/
+
+extern struct thread_entry *codec_thread_p;
+
+/** 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 **/
+#define PCM_NUM_CHUNKS 256 /* Power of 2 */
+#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_ata_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 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 */
+#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 struct thread_entry *pcmrec_thread_p;
+
+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 int pcm_rec_have_more(int status)
+{
+ 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 -1;
+ }
+ /* else try again next transmission */
+ }
+ 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;
+ }
+
+ pcm_record_more(GET_PCM_CHUNK(dma_wr_pos), PCM_CHUNK_SIZE);
+ return 0;
+} /* 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_p =
+ 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_p);
+} /* 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)
+{
+ strncpy(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);
+
+ strncpy(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)
+ strncpy(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", 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 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 (ata_spinup_time != last_ata_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(NULL,
+ thread_get_priority(NULL) - 4);
+ prio_codec = thread_set_priority(codec_thread_p,
+ thread_get_priority(codec_thread_p) - 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(NULL, prio_pcmrec);
+ thread_set_priority(codec_thread_p, 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)
+ 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);
+
+ /* 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;
+
+ /* 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^4=16 bytes */
+ 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 */
+ 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();
+} /* 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 */
diff --git a/apps/recorder/pcm_record.h b/apps/recorder/pcm_record.h
new file mode 100644
index 0000000000..f805313fe5
--- /dev/null
+++ b/apps/recorder/pcm_record.h
@@ -0,0 +1,66 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef PCM_RECORD_H
+#define PCM_RECORD_H
+
+#define DMA_REC_ERROR_DMA (-1)
+#ifdef HAVE_SPDIF_REC
+#define DMA_REC_ERROR_SPDIF (-2)
+#endif
+
+/** Warnings **/
+/* pcm (dma) buffer has overflowed */
+#define PCMREC_W_PCM_BUFFER_OVF 0x00000001
+/* encoder output buffer has overflowed */
+#define PCMREC_W_ENC_BUFFER_OVF 0x00000002
+/** Errors **/
+/* failed to load encoder */
+#define PCMREC_E_LOAD_ENCODER 0x80001000
+/* error originating in encoder */
+#define PCMREC_E_ENCODER 0x80002000
+/* filename queue has desynced from stream markers */
+#define PCMREC_E_FNQ_DESYNC 0x80004000
+/* I/O error has occurred */
+#define PCMREC_E_IO 0x80008000
+#ifdef DEBUG
+/* encoder has written past end of allotted space */
+#define PCMREC_E_CHUNK_OVF 0x80010000
+#endif /* DEBUG */
+
+/** General functions for high level codec recording **/
+/* pcm_rec_error_clear is deprecated for general use. audio_error_clear
+ should be used */
+void pcm_rec_error_clear(void);
+/* pcm_rec_status is deprecated for general use. audio_status merges the
+ results for consistency with the hardware codec version */
+unsigned long pcm_rec_status(void);
+unsigned long pcm_rec_get_warnings(void);
+void pcm_rec_init(void);
+int pcm_rec_current_bitrate(void);
+int pcm_rec_encoder_afmt(void); /* AFMT_* value, AFMT_UNKNOWN if none */
+int pcm_rec_rec_format(void); /* Format index or -1 otherwise */
+unsigned long pcm_rec_sample_rate(void);
+int pcm_get_num_unprocessed(void);
+
+/* audio.h contains audio_* recording functions */
+
+#endif /* PCM_RECORD_H */
diff --git a/apps/recorder/recording.c b/apps/recorder/recording.c
index 6f5c7df09a..d29db390c7 100644
--- a/apps/recorder/recording.c
+++ b/apps/recorder/recording.c
@@ -40,6 +40,7 @@
#include "spdif.h"
#endif
#endif /* CONFIG_CODEC == SWCODEC */
+#include "pcm_record.h"
#include "recording.h"
#include "mp3_playback.h"
#include "mas.h"
diff --git a/apps/replaygain.h b/apps/replaygain.h
new file mode 100644
index 0000000000..dbc079b1d3
--- /dev/null
+++ b/apps/replaygain.h
@@ -0,0 +1,33 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Magnus Holmgren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#ifndef _REPLAYGAIN_H
+#define _REPLAYGAIN_H
+
+#include "id3.h"
+
+long get_replaygain_int(long int_gain);
+long parse_replaygain(const char* key, const char* value,
+ struct mp3entry* entry, char* buffer, int length);
+long parse_replaygain_int(bool album, long gain, long peak,
+ struct mp3entry* entry, char* buffer, int length);
+
+#endif