diff options
Diffstat (limited to 'firmware/pcm_record.c')
| -rw-r--r-- | firmware/pcm_record.c | 2002 |
1 files changed, 1263 insertions, 739 deletions
diff --git a/firmware/pcm_record.c b/firmware/pcm_record.c index 2785d4b..25f1f1e 100644 --- a/firmware/pcm_record.c +++ b/firmware/pcm_record.c @@ -16,199 +16,238 @@ * KIND, either express or implied. * ****************************************************************************/ - -#include "config.h" -#include "debug.h" +#include "system.h" +#include "kernel.h" +#include "logf.h" #include "panic.h" #include "thread.h" - -#include <kernel.h> -#include <stdio.h> -#include <string.h> -#include <stdarg.h> #include <string.h> - -#include "cpu.h" -#include "i2c.h" -#include "power.h" -#ifdef HAVE_UDA1380 +#include "ata.h" +#include "usb.h" +#if defined(HAVE_UDA1380) #include "uda1380.h" -#endif -#ifdef HAVE_TLV320 +#include "general.h" +#elif defined(HAVE_TLV320) #include "tlv320.h" #endif -#include "system.h" -#include "usb.h" - #include "buffer.h" #include "audio.h" -#include "button.h" -#include "file.h" -#include "sprintf.h" -#include "logf.h" -#include "button.h" -#include "lcd.h" -#include "lcd-remote.h" -#include "pcm_playback.h" #include "sound.h" #include "id3.h" -#include "pcm_record.h" - -extern int boost_counter; /* used for boost check */ /***************************************************************************/ +/** + * APIs implemented in the target tree portion: + * Public - + * pcm_init_recording + * pcm_close_recording + * pcm_rec_mux + * Semi-private - + * pcm_rec_dma_start + * pcm_rec_dma_stop + */ + +/** These items may be implemented target specifically or need to + be shared semi-privately **/ + +/* the registered callback function for when more data is available */ +pcm_more_callback_type pcm_callback_more_ready = NULL; +/* DMA transfer in is currently active */ +bool pcm_recording = false; + +/* APIs implemented in the target-specific portion */ +void pcm_rec_dma_start(const void *addr, size_t size); +void pcm_rec_dma_stop(void); + +/** General recording state **/ static bool is_recording; /* We are recording */ static bool is_paused; /* We have paused */ +static bool is_stopping; /* We are currently stopping */ static bool is_error; /* An error has occured */ -static unsigned long num_rec_bytes; /* Num bytes recorded */ -static unsigned long num_file_bytes; /* Num bytes written to current file */ -static int error_count; /* Number of DMA errors */ -static unsigned long num_pcm_samples; /* Num pcm samples written to current file */ - -static long record_start_time; /* current_tick when recording was started */ -static long pause_start_time; /* current_tick when pause was started */ -static unsigned int sample_rate; /* Sample rate at time of recording start */ -static int rec_source; /* Current recording source */ +/** 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 */ -static int wav_file; -static char recording_filename[MAX_PATH]; +/** Stats on encoded data for all files from start to stop **/ +static unsigned long long accum_rec_bytes; /* total size written to chunks */ +static unsigned long long accum_pcm_samples; /* total pcm count processed */ -static volatile bool init_done, close_done, record_done; -static volatile bool stop_done, pause_done, resume_done, new_file_done; - -static int peak_left, peak_right; +/* 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 +}; -#ifdef IAUDIO_X5 -#define SET_IIS_PLAY(x) IIS1CONFIG = (x); -#define SET_IIS_REC(x) IIS1CONFIG = (x); -#else -#define SET_IIS_PLAY(x) IIS2CONFIG = (x); -#define SET_IIS_REC(x) IIS1CONFIG = (x); -#endif +/** 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 struct encoder_config enc_config; /* Current encoder configuration */ /**************************************************************************** - use 2 circular buffers of same size: - rec_buffer=DMA output buffer: chunks (8192 Bytes) of raw pcm audio data + 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 rec_buffer + 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: - 1.main: codec_load_encoder(); start the encoder - 2.encoder: enc_get_inputs(); get encoder buffsize, mono/stereo, quality - 3.encoder: enc_set_parameters(); set the encoder parameters (max.chunksize) - 4.encoder: enc_get_wav_data(); get n bytes of unprocessed pcm data - 5.encoder: enc_wavbuf_near_empty();if true: reduce cpu_boost + 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_pcm_buf_near_empty(); if 1: reduce cpu_boost 6.encoder: enc_alloc_chunk(); get a ptr to next enc chunk 7.encoder: <process enc chunk> compress and store data to enc chunk 8.encoder: enc_free_chunk(); inform main about chunk process finished 9.encoder: repeat 4. to 8. - A.main: enc_set_header_callback(); create the current format header (file) + A.pcmrec: enc_events_callback(); called for certain events ****************************************************************************/ -#define NUM_CHUNKS 256 /* Power of 2 */ -#define CHUNK_SIZE 8192 /* Power of 2 */ -#define MAX_FEED_SIZE 20000 /* max pcm size passed to encoder */ -#define CHUNK_MASK (NUM_CHUNKS * CHUNK_SIZE - 1) -#define WRITE_THRESHOLD (44100 * 5 / enc_samp_per_chunk) /* 5sec */ -#define GET_CHUNK(x) (long*)(&rec_buffer[x]) -#define GET_ENC_CHUNK(x) (long*)(&enc_buffer[enc_chunk_size*(x)]) - -static int audio_enc_id; /* current encoder id */ -static unsigned char *rec_buffer; /* Circular recording buffer */ -static unsigned char *enc_buffer; /* Circular encoding buffer */ -static unsigned char *enc_head_buffer; /* encoder header buffer */ -static int enc_head_size; /* used size in header buffer */ -static int write_pos; /* Current chunk pos for DMA writing */ -static int read_pos; /* Current chunk pos for encoding */ -static long pre_record_ticks;/* pre-record time expressed in ticks */ -static int enc_wr_index; /* Current encoding chunk write index */ -static int enc_rd_index; /* Current encoding chunk read index */ -static int enc_chunk_size; /* maximum encoder chunk size */ + +/** 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 */ +static volatile int dma_wr_pos; /* current DMA write pos */ +static int pcm_rd_pos; /* current PCM read pos */ +static volatile bool dma_lock; /* lock DMA write position */ +static unsigned long pre_record_ticks;/* pre-record time in ticks */ +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 int enc_buffer_size; /* encode buffer size */ -static int enc_channels; /* 1=mono 2=stereo */ -static int enc_quality; /* mp3: 64,96,128,160,192,320 kBit */ -static int enc_samp_per_chunk;/* pcm samples per encoder chunk */ +static size_t enc_chunk_size; /* maximum encoder chunk size */ +static size_t enc_data_size; /* maximum data size for encoder */ +static unsigned long enc_sample_rate; /* sample rate used by encoder */ static bool wav_queue_empty; /* all wav chunks processed? */ -static unsigned long avrg_bit_rate; /* average bit rates from chunks */ -static unsigned long curr_bit_rate; /* cumulated bit rates from chunks */ -static unsigned long curr_chunk_cnt; /* number of processed chunks */ -void (*enc_set_header_callback)(void *head_buffer, int head_size, - int num_pcm_samples, bool is_file_header); +/** file flushing **/ +static int write_threshold; /* max chunk limit for data flush */ +static int panic_threshold; /* boost thread prio when here */ +static int spinup_time = -1;/* last ata_spinup_time */ + +/** 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 */ +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 */ /***************************************************************************/ static struct event_queue pcmrec_queue; -static long pcmrec_stack[2*DEFAULT_STACK_SIZE/sizeof(long)]; +static long pcmrec_stack[3*DEFAULT_STACK_SIZE/sizeof(long)]; static const char pcmrec_thread_name[] = "pcmrec"; static void pcmrec_thread(void); -static void pcmrec_dma_start(void); -static void pcmrec_dma_stop(void); -static void close_wave(void); -/* Event IDs */ -#define PCMREC_INIT 1 /* Enable recording */ -#define PCMREC_CLOSE 2 +/* Event values which are also single-bit flags */ +#define PCMREC_INIT 0x00000001 /* enable recording */ +#define PCMREC_CLOSE 0x00000002 -#define PCMREC_START 3 /* Start a new recording */ -#define PCMREC_STOP 4 /* Stop the current recording */ -#define PCMREC_PAUSE 10 -#define PCMREC_RESUME 11 -#define PCMREC_NEW_FILE 12 -#define PCMREC_SET_GAIN 13 +#define PCMREC_START 0x00000004 /* start recording (when stopped) */ +#define PCMREC_STOP 0x00000008 /* stop the current recording */ +#define PCMREC_PAUSE 0x00000010 /* pause the current recording */ +#define PCMREC_RESUME 0x00000020 /* resume the current recording */ +#define PCMREC_NEW_FILE 0x00000040 /* start new file (when recording) */ +#define PCMREC_SET_GAIN 0x00000080 +#define PCMREC_FLUSH_NUM 0x00000100 /* flush a number of files out */ +#define PCMREC_FINISH_STOP 0x00000200 /* finish the stopping recording */ -/*******************************************************************/ -/* Functions that are not executing in the pcmrec_thread first */ -/*******************************************************************/ +/* mask for signaling events */ +static volatile long pcm_thread_event_mask; -/* Creates pcmrec_thread */ -void pcm_rec_init(void) +static void pcm_thread_sync_post(long event, void *data) { - queue_init(&pcmrec_queue, true); - create_thread(pcmrec_thread, pcmrec_stack, sizeof(pcmrec_stack), - pcmrec_thread_name IF_PRIO(, PRIORITY_RECORDING)); -} + pcm_thread_event_mask &= ~event; + queue_post(&pcmrec_queue, event, data); + while(!(event & pcm_thread_event_mask)) + yield(); +} /* pcm_thread_sync_post */ +static inline void pcm_thread_signal_event(long event) +{ + pcm_thread_event_mask |= event; +} /* pcm_thread_signal_event */ -int audio_get_encoder_id(void) +static inline void pcm_thread_unsignal_event(long event) { - return audio_enc_id; -} + pcm_thread_event_mask &= ~event; +} /* pcm_thread_unsignal_event */ -/* Initializes recording: - * - Set up the UDA1380/TLV320 for recording - * - Prepare for DMA transfers - */ +static inline bool pcm_thread_event_state(long signaled, long unsignaled) +{ + return ((signaled | unsignaled) & pcm_thread_event_mask) == signaled; +} /* pcm_thread_event_state */ -void audio_init_recording(unsigned int buffer_offset) +static void pcm_thread_wait_for_stop(void) { - (void)buffer_offset; + if (is_stopping) + { + logf("waiting for stop to finish"); + while (is_stopping) + yield(); + } +} /* pcm_thread_wait_for_stop */ - init_done = false; - queue_post(&pcmrec_queue, PCMREC_INIT, 0); +/*******************************************************************/ +/* Functions that are not executing in the pcmrec_thread first */ +/*******************************************************************/ - while(!init_done) - sleep_thread(1); -} - -void audio_close_recording(void) +/* Callback for when more data is ready */ +static void pcm_rec_have_more(unsigned char **data, size_t *size) { - close_done = false; - queue_post(&pcmrec_queue, PCMREC_CLOSE, 0); + if (*size != 0) + { + /* some error condition */ + if (*size == DMA_REC_ERROR_DMA) + { + /* Flush recorded data to disk and stop recording */ + queue_post(&pcmrec_queue, PCMREC_STOP, NULL); + return; + } + /* else try again next transmission */ + } + else if (!dma_lock) + { + /* advance write position */ + dma_wr_pos = (dma_wr_pos + PCM_CHUNK_SIZE) & PCM_CHUNK_MASK; + } - while(!close_done) - sleep_thread(1); + *data = (unsigned char *)GET_PCM_CHUNK(dma_wr_pos); + *size = PCM_CHUNK_SIZE; +} /* pcm_rec_have_more */ - audio_remove_encoder(); -} +/** pcm_rec_* group **/ +void pcm_rec_error_clear(void) +{ + is_error = false; +} /* pcm_rec_error_clear */ unsigned long pcm_rec_status(void) { @@ -216,165 +255,223 @@ unsigned long pcm_rec_status(void) if (is_recording) ret |= AUDIO_STATUS_RECORD; + if (is_paused) ret |= AUDIO_STATUS_PAUSE; + if (is_error) ret |= AUDIO_STATUS_ERROR; - if (!is_recording && pre_record_ticks && init_done && !close_done) + + if (!is_recording && pre_record_ticks && + pcm_thread_event_state(PCMREC_INIT, PCMREC_CLOSE)) ret |= AUDIO_STATUS_PRERECORD; return ret; -} +} /* pcm_rec_status */ int pcm_rec_current_bitrate(void) { - return avrg_bit_rate; -} + if (accum_pcm_samples == 0) + return 0; -unsigned long audio_recorded_time(void) + return (int)(8*accum_rec_bytes*enc_sample_rate / (1000*accum_pcm_samples)); +} /* pcm_rec_current_bitrate */ + +int pcm_rec_encoder_afmt(void) { - if (is_recording) + return enc_config.afmt; +} /* pcm_rec_encoder_afmt */ + +int pcm_rec_rec_format(void) { - if (is_paused) - return pause_start_time - record_start_time; - else - return current_tick - record_start_time; - } + return afmt_rec_format[enc_config.afmt]; +} /* pcm_rec_rec_format */ - return 0; -} +unsigned long pcm_rec_sample_rate(void) +{ + /* Which is better ?? */ +#if 0 + return enc_sample_rate; +#endif + return sample_rate; +} /* audio_get_sample_rate */ -unsigned long audio_num_recorded_bytes(void) +/** + * Creates pcmrec_thread + */ +void pcm_rec_init(void) { - if (is_recording) - return num_rec_bytes; + queue_init(&pcmrec_queue, true); + create_thread(pcmrec_thread, pcmrec_stack, sizeof(pcmrec_stack), + pcmrec_thread_name, PRIORITY_RECORDING); +} /* pcm_rec_init */ +/** audio_* group **/ + +void audio_init_recording(unsigned int buffer_offset) +{ + (void)buffer_offset; + pcm_thread_wait_for_stop(); + pcm_thread_sync_post(PCMREC_INIT, NULL); +} /* audio_init_recording */ + +void audio_close_recording(void) +{ + pcm_thread_wait_for_stop(); + pcm_thread_sync_post(PCMREC_CLOSE, NULL); + /* reset pcm to defaults (playback only) */ + pcm_set_frequency(-1); + pcm_set_monitor(-1); + pcm_set_rec_source(-1); +#ifdef HAVE_TLV320 + /* tlv320 screeches if left at 88.2 with no inputs */ + pcm_apply_settings(true); +#endif + audio_remove_encoder(); +} /* audio_close_recording */ + +unsigned long audio_recorded_time(void) +{ + if (!is_recording || enc_sample_rate == 0) return 0; -} -#ifdef HAVE_SPDIF_IN -/* Only the last six of these are standard rates, but all sample rates are - * possible, so we support some other common ones as well. - */ -static unsigned long spdif_sample_rates[] = { - 8000, 11025, 12000, 16000, 22050, 24000, - 32000, 44100, 48000, 64000, 88200, 96000 -}; + /* 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 SPDIF sample rate. Since we base our reading on the actual SPDIF - * sample rate (which might be a bit inaccurate), we round off to the closest - * sample rate that is supported by SPDIF. - */ -unsigned long audio_get_spdif_sample_rate(void) +unsigned long audio_num_recorded_bytes(void) { - int i = 0; - unsigned long measured_rate; - const int upper_bound = sizeof(spdif_sample_rates)/sizeof(long) - 1; + if (!is_recording) + return 0; + + return num_rec_bytes; +} /* audio_num_recorded_bytes */ +#ifdef HAVE_SPDIF_IN +/* Return current SPDIF sample rate */ +static unsigned long measure_spdif_sample_rate(void) +{ /* The following formula is specified in MCF5249 user's manual section - * 17.6.1. The 3*(1 << 13) part will need changing if the setup of the - * PHASECONFIG register is ever changed. The 128 divide is because of the - * fact that the SPDIF clock is the sample rate times 128. + * 17.6.1. The 128 divide is because of the fact that the SPDIF clock is + * the sample rate times 128. Keep "3*(1 << 13)" part in sync with + * PHASECONFIG setup in pcm_init_recording in pcm-coldfire.c. */ - measured_rate = (unsigned long)((unsigned long long)FREQMEAS*CPU_FREQ/ + return (unsigned long)((unsigned long long)FREQMEAS*CPU_FREQ / ((1 << 15)*3*(1 << 13))/128); - /* Find which SPDIF sample rate we're closest to. */ - while (spdif_sample_rates[i] < measured_rate && i < upper_bound) ++i; - if (i > 0 && i < upper_bound) - { - long diff1 = measured_rate - spdif_sample_rates[i - 1]; - long diff2 = spdif_sample_rates[i] - measured_rate; +} /* measure_spdif_sample_rate */ - if (diff2 > diff1) --i; - } - return i; -} -#endif +/** + * Return SPDIF sample rate index in audio_master_sampr_list. Since we base + * our reading on the actual SPDIF sample rate (which might be a bit + * inaccurate), we round off to the closest sample rate that is supported by + * SPDIF. + */ +int audio_get_spdif_sample_rate(void) +{ + unsigned long measured_rate = measure_spdif_sample_rate(); + /* Find which SPDIF sample rate we're closest to. */ + return round_value_to_list32(measured_rate, audio_master_sampr_list, + SAMPR_NUM_FREQ, false); +} /* audio_get_spdif_sample_rate */ -#if 0 -/* not needed atm */ #ifdef HAVE_SPDIF_POWER static bool spdif_power_setting; void audio_set_spdif_power_setting(bool on) { spdif_power_setting = on; -} +} /* audio_set_spdif_power_setting */ + +bool audio_get_spdif_power_setting(void) +{ + return spdif_power_setting; +} /* audio_get_spdif_power_setting */ #endif + +void audio_spdif_set_monitor(int monitor_spdif) +{ + EBU1CONFIG = 0x800; /* Reset before reprogram */ + + if (monitor_spdif > 0) + { +#ifdef HAVE_SPDIF_POWER + EBU1CONFIG = spdif_power_setting ? (1 << 2) : 0; + /* Input source is EBUin1, Feed-through monitoring if desired */ +#else + EBU1CONFIG = (1 << 2); + /* Input source is EBUin1, Feed-through monitoring */ #endif + } + else if (monitor_spdif == 0) + { + /* SCLK2, TXSRC = IIS1recv, validity, normal operation */ + EBU1CONFIG = (7 << 12) | (4 << 8) | (1 << 5) | (5 << 2); + } +} /* audio_spdif_set_monitor */ + +#endif /* HAVE_SPDIF_IN */ /** * Sets recording parameters - * - * This functions starts feeding the CPU with audio data over the I2S bus */ -void audio_set_recording_options(int frequency, int quality, - int source, int channel_mode, - bool editable, int prerecord_time) +void audio_set_recording_options(struct audio_recording_options *options) { - /* TODO: */ - (void)editable; + pcm_thread_wait_for_stop(); - /* NOTE: Coldfire UDA based recording does not yet support anything other - * than 44.1kHz sampling rate, so we limit it to that case here now. SPDIF - * based recording will overwrite this value with the proper sample rate in - * audio_record(), and will not be affected by this. - */ - frequency = 44100; - enc_quality = quality; - rec_source = source; - enc_channels = channel_mode == CHN_MODE_MONO ? 1 : 2; - pre_record_ticks = prerecord_time * HZ; + /* stop DMA transfer */ + dma_lock = true; + pcm_stop_recording(); - switch (source) - { - case AUDIO_SRC_MIC: - case AUDIO_SRC_LINEIN: -#ifdef HAVE_FMRADIO_IN - case AUDIO_SRC_FMRADIO: -#endif - /* Generate int. when 6 samples in FIFO, PDIR2 src = IIS1recv */ - DATAINCONTROL = 0xc020; - break; + rec_frequency = options->rec_frequency; + rec_source = options->rec_source; + num_channels = options->rec_channels == 1 ? 1 : 2; + 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 - case AUDIO_SRC_SPDIF: - /* Int. when 6 samples in FIFO. PDIR2 source = ebu1RcvData */ - DATAINCONTROL = 0xc038; - break; -#endif /* HAVE_SPDIF_IN */ + if (rec_source == AUDIO_SRC_SPDIF) + { + /* must measure SPDIF sample rate before configuring codecs */ + unsigned long sr = measure_spdif_sample_rate(); + /* 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); } - sample_rate = frequency; - - /* Monitoring: route the signals through the coldfire audio interface. */ + pcm_set_monitor(rec_source); + pcm_set_rec_source(rec_source); - SET_IIS_PLAY(0x800); /* Reset before reprogram */ + /* apply pcm settings to hardware */ + pcm_apply_settings(true); -#ifdef HAVE_SPDIF_IN - if (source == AUDIO_SRC_SPDIF) + if (audio_load_encoder(enc_config.afmt)) { - /* SCLK2 = Audioclk/4 (can't use EBUin clock), TXSRC = EBU1rcv, 64 bclk/wclk */ - IIS2CONFIG = (6 << 12) | (7 << 8) | (4 << 2); - /* S/PDIF feed-through already configured */ + /* start DMA transfer */ + pcm_record_data(pcm_rec_have_more, NULL, 0); + /* do unlock after starting to prevent preincrement of dma_wr_pos */ + dma_lock = pre_record_ticks == 0; } else { - /* SCLK2 follow IIS1 (UDA clock), TXSRC = IIS1rcv, 64 bclk/wclk */ - IIS2CONFIG = (8 << 12) | (4 << 8) | (4 << 2); - - EBU1CONFIG = 0x800; /* Reset before reprogram */ - /* SCLK2, TXSRC = IIS1recv, validity, normal operation */ - EBU1CONFIG = (7 << 12) | (4 << 8) | (1 << 5) | (5 << 2); - } -#else - /* SCLK2 follow IIS1 (UDA clock), TXSRC = IIS1rcv, 64 bclk/wclk */ - SET_IIS_PLAY( (8 << 12) | (4 << 8) | (4 << 2) ); -#endif - - audio_load_encoder(rec_quality_info_afmt[quality]); + logf("set rec opt: enc load failed"); + is_error = true; } - +} /* audio_set_recording_options */ /** * Note that microphone is mono, only left value is used @@ -391,8 +488,7 @@ void audio_set_recording_gain(int left, int right, int type) #elif defined (HAVE_TLV320) tlv320_set_recvol(left, right, type); #endif -} - +} /* audio_set_recording_gain */ /** * Start recording @@ -401,585 +497,882 @@ void audio_set_recording_gain(int left, int right, int type) */ void audio_record(const char *filename) { + logf("audio_record: %s", filename); + + pcm_thread_wait_for_stop(); + pcm_thread_sync_post(PCMREC_START, (void *)filename); + + logf("audio_record_done"); +} /* audio_record */ + +void audio_new_file(const char *filename) +{ + logf("audio_new_file: %s", filename); + + pcm_thread_wait_for_stop(); + pcm_thread_sync_post(PCMREC_NEW_FILE, (void *)filename); + + logf("audio_new_file done"); +} /* audio_new_file */ + +void audio_stop_recording(void) +{ + logf("audio_stop_recording"); + + pcm_thread_wait_for_stop(); + if (is_recording) - { - logf("record while recording"); - return; - } + dma_lock = true; /* fix DMA write ptr at current position */ + + pcm_thread_sync_post(PCMREC_STOP, NULL); + + logf("audio_stop_recording done"); +} /* audio_stop_recording */ + +void audio_pause_recording(void) +{ + logf("audio_pause_recording"); - strncpy(recording_filename, filename, MAX_PATH - 1); - recording_filename[MAX_PATH - 1] = 0; + pcm_thread_wait_for_stop(); -#ifdef HAVE_SPDIF_IN - if (rec_source == AUDIO_SRC_SPDIF) - sample_rate = audio_get_spdif_sample_rate(); -#endif + if (is_recording) + dma_lock = true; /* fix DMA write ptr at current position */ - record_done = false; - queue_post(&pcmrec_queue, PCMREC_START, 0); + pcm_thread_sync_post(PCMREC_PAUSE, NULL); + logf("audio_pause_recording done"); +} /* audio_pause_recording */ - while(!record_done) - sleep_thread(1); -} +void audio_resume_recording(void) +{ + logf("audio_resume_recording"); + pcm_thread_wait_for_stop(); + pcm_thread_sync_post(PCMREC_RESUME, NULL); -void audio_new_file(const char *filename) + logf("audio_resume_recording done"); +} /* audio_resume_recording */ + +/***************************************************************************/ +/* */ +/* 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) { - logf("pcm_new_file"); + 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 */ - new_file_done = false; +/* 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; - strncpy(recording_filename, filename, MAX_PATH - 1); - recording_filename[MAX_PATH - 1] = 0; + return size >= fnq_size - MAX_PATH; +} /* pcmrec_fnq_is_full */ - queue_post(&pcmrec_queue, PCMREC_NEW_FILE, 0); +/* 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); - while(!new_file_done) - sleep_thread(1); + if ((fnq_wr_pos += MAX_PATH) >= fnq_size) + fnq_wr_pos = 0; - logf("pcm_new_file done"); -} + if (fnq_rd_pos != fnq_wr_pos) + return true; -/** - * - */ -void audio_stop_recording(void) + /* queue full */ + if ((fnq_rd_pos += MAX_PATH) >= fnq_size) + fnq_rd_pos = 0; + + return true; +} /* pcmrec_fnq_add_filename */ + +/* replace the last filename added */ +static bool pcmrec_fnq_replace_tail(const char *filename) { - if (!is_recording) - return; + int pos; + + if (pcmrec_fnq_is_empty()) + return false; + + pos = fnq_wr_pos - MAX_PATH; + if (pos < 0) + pos = fnq_size - MAX_PATH; + + strncpy(fn_queue + pos, filename, MAX_PATH); + + return true; +} /* pcmrec_fnq_replace_tail */ - logf("pcm_stop"); +/* 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); - is_paused = true; /* fix pcm write ptr at current position */ - stop_done = false; - queue_post(&pcmrec_queue, PCMREC_STOP, 0); + if ((fnq_rd_pos += MAX_PATH) >= fnq_size) + fnq_rd_pos = 0; - while(!stop_done) - sleep_thread(1); + return true; +} /* pcmrec_fnq_get_filename */ - logf("pcm_stop done"); -} +/* close the file number pointed to by fd_p */ +static void pcmrec_close_file(int *fd_p) +{ + if (*fd_p < 0) + return; /* preserve error */ -void audio_pause_recording(void) + close(*fd_p); + *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 (!is_recording) + if (rec_fdata.new_enc_size != prev_enc_size) { - logf("pause when not recording"); - return; + ssize_t size_diff = rec_fdata.new_enc_size - prev_enc_size; + num_rec_bytes += size_diff; + accum_rec_bytes += size_diff; } - if (is_paused) + + if (rec_fdata.new_num_pcm != prev_num_pcm) { - logf("pause when paused"); - return; + unsigned long pcm_diff = rec_fdata.new_num_pcm - prev_num_pcm; + num_rec_samples += pcm_diff; + accum_pcm_samples += pcm_diff; } - - pause_done = false; - queue_post(&pcmrec_queue, PCMREC_PAUSE, 0); +} /* pcmrec_update_sizes_inl */ - while(!pause_done) - sleep_thread(1); -} +/* 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 */ -void audio_resume_recording(void) +static void pcmrec_start_file(void) { - if (!is_paused) + 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("resume when not paused"); - return; + logf("start file: fnq empty"); + *filename = '\0'; + is_error = true; + } + else if (is_error) + { + logf("start file: is_error already"); + } + else if (curr_rec_file >= 0) + { + /* Any previous file should have been closed */ + logf("start file: file already open"); + is_error = true; } - resume_done = false; - queue_post(&pcmrec_queue, PCMREC_RESUME, 0); + if (is_error) + rec_fdata.chunk->flags |= CHUNKF_ERROR; - while(!resume_done) - sleep_thread(1); -} + /* 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 (!is_error && (rec_fdata.chunk->flags & CHUNKF_ERROR)) + { + logf("start file: enc error"); + is_error = true; + } -/* return peaks as int, so convert from short first - note that peak values are always positive */ -void pcm_rec_get_peaks(int *left, int *right) + if (is_error) + { + 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) { - if (left) - *left = peak_left; - if (right) - *right = peak_right; - peak_left = 0; - peak_right = 0; -} + size_t enc_size = rec_fdata.new_enc_size; + unsigned long num_pcm = rec_fdata.new_num_pcm; -/***************************************************************************/ -/* Functions that executes in the context of pcmrec_thread */ -/***************************************************************************/ + if (is_error) + 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 (!is_error) + { + logf("wr chk enc error %d %d", + rec_fdata.chunk->enc_size, rec_fdata.chunk->num_pcm); + is_error = true; + } +} /* 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 (!is_error) + { + if (rec_fdata.chunk->flags & CHUNKF_ERROR) + { + logf("end file: enc error"); + is_error = true; + } + else + { + pcmrec_update_sizes(enc_size, num_pcm); + } + } + + /* Force file close if error */ + if (is_error) + pcmrec_close_file(&rec_fdata.rec_file); + + rec_fdata.chunk->flags &= ~CHUNKF_END_FILE; +} /* pcmrec_end_file */ /** * Process the chunks * * This function is called when queue_get_w_tmo times out. * - * Other functions can also call this function with flush = true when - * they want to save everything in the buffers to disk. + * 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. * */ -static void pcmrec_callback(bool flush) +static void pcmrec_flush(unsigned flush_num) { - int i, num_ready, size_yield; - long *enc_chunk, chunk_size; - - if (!is_recording && !flush) - return; + static unsigned long last_flush_tick = 0; + unsigned long start_tick; + int num_ready, num; + int prio; + int i; num_ready = enc_wr_index - enc_rd_index; if (num_ready < 0) num_ready += enc_num_chunks; - /* calculate an estimate of recorded bytes */ - num_rec_bytes = num_file_bytes + num_ready * /* enc_chunk_size */ - ((avrg_bit_rate * 1000 / 8 * enc_samp_per_chunk + 22050) / 44100); + num = num_ready; - /* near full state reached: less than 5sec remaining space */ - if (enc_num_chunks - num_ready < WRITE_THRESHOLD || flush) + if (flush_num == 0) { - logf("writing: %d (%d)", num_ready, flush); - - cpu_boost_id(true, CPUBOOSTID_PCMRECORD); + if (!is_recording) + return; - size_yield = 0; - for (i=0; i<num_ready; i++) + if (ata_spinup_time != spinup_time) { - enc_chunk = GET_ENC_CHUNK(enc_rd_index); - chunk_size = *enc_chunk++; - - /* safety net: if size entry got corrupted => limit */ - if (chunk_size > (long)(enc_chunk_size - sizeof(long))) - chunk_size = enc_chunk_size - sizeof(long); + /* 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; + else if (write_threshold > panic_threshold) + write_threshold = panic_threshold; + + logf("new wr thresh: %d", write_threshold); + } - if (enc_set_header_callback != NULL) - enc_set_header_callback(enc_chunk, enc_chunk_size, - num_pcm_samples, false); + if (num_ready < write_threshold) + return; - if (write(wav_file, enc_chunk, chunk_size) != chunk_size) - { - close_wave(); - logf("pcmrec: write err"); - is_error = true; - break; - } + /* 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; + } - num_file_bytes += chunk_size; - num_pcm_samples += enc_samp_per_chunk; - size_yield += chunk_size; + start_tick = current_tick; + prio = -1; - if (size_yield >= 32768) - { /* yield when 32kB written */ - size_yield = 0; - yield(); - } + logf("writing: %d (%d)", num_ready, flush_num); + + cpu_boost_id(true, CPUBOOSTID_PCMRECORD); - enc_rd_index = (enc_rd_index + 1) % enc_num_chunks; + for (i=0; i<num_ready; i++) + { + if (prio == -1 && (num >= panic_threshold || + current_tick - start_tick > 10*HZ)) + { + /* losing ground - boost priority until finished */ + logf("pcmrec: boost priority"); + prio = thread_set_priority(NULL, thread_get_priority(NULL)-1); } - /* sync file */ - fsync(wav_file); + 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; - cpu_boost_id(false, CPUBOOSTID_PCMRECORD); + 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 + chunk if it has data */ + } - logf("done"); - } -} + pcmrec_write_chunk(); -/* Abort dma transfer */ -static void pcmrec_dma_stop(void) -{ - DCR1 = 0; + if (rec_fdata.chunk->flags & CHUNKF_END_FILE) + pcmrec_end_file(); - error_count++; + INC_ENC_INDEX(enc_rd_index); - DSR1 = 1; /* Clear interrupt */ - IPR |= (1<<15); /* Clear pending interrupt request */ + if (is_error) + break; - logf("dma1 stopped"); -} + if (prio == -1) + { + num = enc_wr_index - enc_rd_index; + if (num < 0) + num += enc_num_chunks; + } -static void pcmrec_dma_start(void) -{ - DAR1 = (unsigned long)GET_CHUNK(write_pos); /* Destination address */ - SAR1 = (unsigned long)&PDIR2; /* Source address */ - BCR1 = CHUNK_SIZE; /* Bytes to transfer */ + /* no yielding, the file apis called in the codecs do that */ + } /* end for */ - /* Start the DMA transfer.. */ -#ifdef HAVE_SPDIF_IN - INTERRUPTCLEAR = 0x03c00000; -#endif + /* sync file */ + if (rec_fdata.rec_file >= 0) + fsync(rec_fdata.rec_file); + + cpu_boost_id(false, CPUBOOSTID_PCMRECORD); - /* 16Byte transfers prevents from sporadic errors during cpu_boost() */ - DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_DINC | DMA_DSIZE(3) | DMA_START; + if (prio != -1) + { + /* return to original priority */ + logf("pcmrec: unboost priority"); + thread_set_priority(NULL, prio); + } - logf("dma1 started"); -} + last_flush_tick = current_tick; /* save tick when we left */ + logf("done"); +} /* pcmrec_flush */ -/* DMA1 Interrupt is called when the DMA has finished transfering a chunk */ -void DMA1(void) __attribute__ ((interrupt_handler, section(".icode"))); -void DMA1(void) +/** + * 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 void pcmrec_new_stream(const char *filename, /* next file name */ + unsigned long flags, /* CHUNKF_* flags */ + int pre_index) /* index for prerecorded data */ { - int res = DSR1; + logf("pcmrec_new_stream"); - DSR1 = 1; /* Clear interrupt */ + struct enc_buffer_event_data data; + bool (*fnq_add_fn)(const char *) = NULL; + struct enc_chunk_hdr *start = NULL; - if (res & 0x70) + int get_chunk_index(struct enc_chunk_hdr *chunk) { - DCR1 = 0; /* Stop DMA transfer */ - error_count++; - - logf("dma1 err: 0x%x", res); - - DAR1 = (unsigned long)GET_CHUNK(write_pos); /* Destination address */ - BCR1 = CHUNK_SIZE; - DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_DINC | DMA_START; + return ((char *)chunk - (char *)enc_buffer) / enc_chunk_size; + } - /* Flush recorded data to disk and stop recording */ - queue_post(&pcmrec_queue, PCMREC_STOP, NULL); - } -#ifdef HAVE_SPDIF_IN - else if ((rec_source == AUDIO_SRC_SPDIF) && - (INTERRUPTSTAT & 0x01c00000)) /* valnogood, symbolerr, parityerr */ + struct enc_chunk_hdr * get_prev_chunk(int index) { - INTERRUPTCLEAR = 0x03c00000; - error_count++; + DEC_ENC_INDEX(index); + return GET_ENC_CHUNK(index); + } - logf("spdif err"); + data.pre_chunk = NULL; + data.chunk = GET_ENC_CHUNK(enc_wr_index); - DAR1 = (unsigned long)GET_CHUNK(write_pos); /* Destination address */ - BCR1 = CHUNK_SIZE; - } -#endif - else + /* end chunk */ + if (flags & CHUNKF_END_FILE) { - long peak_l, peak_r; - long *ptr, j; - - ptr = GET_CHUNK(write_pos); + data.chunk->flags &= CHUNKF_START_FILE | CHUNKF_END_FILE; - if (!is_paused) /* advance write position */ - write_pos = (write_pos + CHUNK_SIZE) & CHUNK_MASK; + 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 = get_prev_chunk(enc_wr_index); - DAR1 = (unsigned long)GET_CHUNK(write_pos); /* Destination address */ - BCR1 = CHUNK_SIZE; + if (last->flags & CHUNKF_END_FILE) + { + /* end already processed and marked - can't end twice */ + logf("file end again"); + flags &= ~CHUNKF_END_FILE; + } + } + } - peak_l = peak_r = 0; + /* start chunk */ + if (flags & CHUNKF_START_FILE) + { + bool pre = flags & CHUNKF_PRERECORD; - /* only peak every 4th sample */ - for (j=0; j<CHUNK_SIZE/4; j+=4) + if (pre) { - long value = ptr[j]; -#ifdef ROCKBOX_BIG_ENDIAN - if (value > peak_l) peak_l = value; - else if (-value > peak_l) peak_l = -value; - - value <<= 16; - if (value > peak_r) peak_r = value; - else if (-value > peak_r) peak_r = -value; -#else - if (value > peak_r) peak_r = value; - else if (-value > peak_r) peak_r = -value; - - value <<= 16; - if (value > peak_l) peak_l = value; - else if (-value > peak_l) peak_l = -value; -#endif + 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; } - peak_left = (int)(peak_l >> 16); - peak_right = (int)(peak_r >> 16); + /* 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; + } } - IPR |= (1<<15); /* Clear pending interrupt request */ -} - -/* Create WAVE file and write header */ -/* Sets returns 0 if success, -1 on failure */ -static int start_wave(void) -{ - wav_file = open(recording_filename, O_RDWR|O_CREAT|O_TRUNC); + data.flags = flags; + enc_events_callback(ENC_REC_NEW_STREAM, &data); - if (wav_file < 0) + if (flags & CHUNKF_END_FILE) { - wav_file = -1; - logf("rec: create failed: %d", wav_file); - is_error = true; - return -1; + int i = get_chunk_index(data.chunk); + get_prev_chunk(i)->flags |= CHUNKF_END_FILE; } - - /* add main file header (enc_head_size=0 for encoders without) */ - if (enc_head_size != write(wav_file, enc_head_buffer, enc_head_size)) + + if (start) { - close(wav_file); - wav_file = -1; - logf("rec: write failed"); - is_error = true; - return -1; - } + if (!(flags & CHUNKF_PRERECORD)) + { + /* get stats on data added to start - sort of a prerecord operation */ + int i = get_chunk_index(data.chunk); + struct enc_chunk_hdr *chunk = data.chunk; - return 0; -} + logf("start data: %d %d", i, enc_wr_index); -/* Update header and set correct length values */ -static void close_wave(void) -{ - unsigned char head[100]; /* assume maximum 100 bytes for file header */ - int size_read; + num_rec_bytes = 0; + num_rec_samples = 0; - if (wav_file != -1) - { - /* update header before closing the file (wav+wv encoder will do) */ - if (enc_set_header_callback != NULL) - { - lseek(wav_file, 0, SEEK_SET); - /* try to read the head size (but we'll accept less) */ - size_read = read(wav_file, head, sizeof(head)); + 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; - enc_set_header_callback(head, size_read, num_pcm_samples, true); - lseek(wav_file, 0, SEEK_SET); - write(wav_file, head, size_read); + /* flush one file out if full and adding */ + if (fnq_add_fn == pcmrec_fnq_add_filename && pcmrec_fnq_is_full()) + { + logf("fnq full: flushing 1"); + pcmrec_flush(1); } - close(wav_file); - wav_file = -1; + + fnq_add_fn(filename); } -} +} /* pcmrec_new_stream */ -static void pcmrec_start(void) +/** event handlers for pcmrec thread */ + +/* PCMREC_INIT */ +static void pcmrec_init(void) { - long max_pre_chunks, pre_ticks, max_pre_ticks; + rec_fdata.rec_file = -1; + + /* pcm FIFO */ + dma_lock = true; + pcm_rd_pos = 0; + dma_wr_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; + accum_rec_bytes = 0; + accum_pcm_samples = 0; + + pcm_thread_unsignal_event(PCMREC_CLOSE); + is_recording = false; + is_paused = false; + is_stopping = false; + is_error = false; + + pcm_buffer = audio_get_recording_buffer(&rec_buffer_size); + /* Line align pcm_buffer 2^4=16 bytes */ + pcm_buffer = (unsigned char *)ALIGN_UP_P2((unsigned)pcm_buffer, 4); + enc_buffer = pcm_buffer + ALIGN_UP_P2(PCM_NUM_CHUNKS*PCM_CHUNK_SIZE + + PCM_MAX_FEED_SIZE, 2); + + pcm_init_recording(); + pcm_thread_signal_event(PCMREC_INIT); +} /* pcmrec_init */ + +/* PCMREC_CLOSE */ +static void pcmrec_close(void) +{ + dma_lock = true; + pcm_close_recording(); + pcm_thread_unsignal_event(PCMREC_INIT); + pcm_thread_signal_event(PCMREC_CLOSE); +} /* pcmrec_close */ + +/* PCMREC_START */ +static void pcmrec_start(const char *filename) +{ + unsigned long pre_sample_ticks; + int rd_start; - logf("pcmrec_start"); + logf("pcmrec_start: %s", filename); if (is_recording) { logf("already recording"); - record_done = true; - return; + goto already_recording; } - if (wav_file != -1) - close_wave(); + /* reset stats */ + num_rec_bytes = 0; + num_rec_samples = 0; + accum_rec_bytes = 0; + accum_pcm_samples = 0; + spinup_time = -1; + + rd_start = enc_wr_index; + pre_sample_ticks = 0; - if (start_wave() != 0) + if (pre_record_ticks) { - /* failed to create the file */ - record_done = true; - return; - } + int i; - /* calculate maximum available chunks & resulting ticks */ - max_pre_chunks = (enc_wr_index - enc_rd_index + - enc_num_chunks) % enc_num_chunks; - if (max_pre_chunks > enc_num_chunks - WRITE_THRESHOLD) - max_pre_chunks = enc_num_chunks - WRITE_THRESHOLD; - max_pre_ticks = max_pre_chunks * HZ * enc_samp_per_chunk / 44100; - - /* limit prerecord if not enough data available */ - pre_ticks = pre_record_ticks > max_pre_ticks ? - max_pre_ticks : pre_record_ticks; - max_pre_chunks = 44100 * pre_ticks / HZ / enc_samp_per_chunk; - enc_rd_index = (enc_wr_index - max_pre_chunks + + /* 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 (i = rd_start; 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; - record_start_time = current_tick - pre_ticks; + chunk_sample_ticks = chunk->num_pcm*HZ; - num_rec_bytes = enc_num_chunks * CHUNK_SIZE; - num_file_bytes = 0; - num_pcm_samples = 0; - pause_start_time = 0; + rd_start = i; + pre_sample_ticks += chunk_sample_ticks; + num_rec_bytes += chunk->enc_size; + num_rec_samples += chunk->num_pcm; + + /* stop here if enough already */ + if (pre_sample_ticks >= pre_record_sample_ticks) + break; + } + + accum_rec_bytes = num_rec_bytes; + accum_pcm_samples = num_rec_samples; + } + + enc_rd_index = rd_start; + + /* filename queue should be empty */ + if (!pcmrec_fnq_is_empty()) + { + logf("fnq: not empty!"); + pcmrec_fnq_set_empty(); + } + dma_lock = false; is_paused = false; is_recording = true; - record_done = true; -} + pcmrec_new_stream(filename, + CHUNKF_START_FILE | + (pre_sample_ticks > 0 ? CHUNKF_PRERECORD : 0), + enc_rd_index); + +already_recording: + pcm_thread_signal_event(PCMREC_START); + logf("pcmrec_start done"); +} /* pcmrec_start */ + +/* PCMREC_STOP */ static void pcmrec_stop(void) { logf("pcmrec_stop"); - if (is_recording) + if (!is_recording) { - /* wait for encoding finish */ - is_paused = true; - while(!wav_queue_empty) - sleep_thread(1); - - is_recording = false; - - /* Flush buffers to file */ - pcmrec_callback(true); - close_wave(); + logf("not recording"); + goto not_recording_or_stopping; + } + else if (is_stopping) + { + logf("already stopping"); + goto not_recording_or_stopping; } - is_paused = false; - stop_done = true; + is_stopping = true; + dma_lock = true; /* lock dma write position */ + queue_post(&pcmrec_queue, PCMREC_FINISH_STOP, NULL); +not_recording_or_stopping: + pcm_thread_signal_event(PCMREC_STOP); logf("pcmrec_stop done"); -} +} /* pcmrec_stop */ -static void pcmrec_new_file(void) +/* PCMREC_FINISH_STOP */ +static void pcmrec_finish_stop(void) { - logf("pcmrec_new_file"); + logf("pcmrec_finish_stop"); - if (!is_recording) + if (!is_stopping) { - logf("not recording"); - new_file_done = true; - return; + logf("not stopping"); + goto not_stopping; } - /* Since pcmrec_callback() blocks until the data has been written, - here is a good approximation when recording to the new file starts - */ - record_start_time = current_tick; + /* flush all available data first to avoid overflow while waiting + for encoding to finish */ + pcmrec_flush(-1); - if (is_paused) - pause_start_time = record_start_time; + /* wait for encoder to finish remaining data */ + if (!is_error) + { + while (!wav_queue_empty) + yield(); + } - /* Flush what we got in buffers to file */ - pcmrec_callback(true); + /* end stream at last data */ + pcmrec_new_stream(NULL, CHUNKF_END_FILE, 0); - close_wave(); - - num_rec_bytes = 0; - num_file_bytes = 0; - num_pcm_samples = 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; - /* start the new file */ - if (start_wave() != 0) + /* filename queue should be empty */ + if (!pcmrec_fnq_is_empty()) { - logf("new_file failed"); - pcmrec_stop(); + logf("fnq: not empty!"); + pcmrec_fnq_set_empty(); } - new_file_done = true; - logf("pcmrec_new_file done"); -} + /* be absolutely sure the file is closed */ + if (is_error) + pcmrec_close_file(&rec_fdata.rec_file); + rec_fdata.rec_file = -1; + + is_recording = false; + is_paused = false; + is_stopping = false; + dma_lock = pre_record_ticks == 0; + +not_stopping: + logf("pcmrec_finish_stop done"); +} /* pcmrec_finish_stop */ +/* PCMREC_PAUSE */ static void pcmrec_pause(void) { logf("pcmrec_pause"); if (!is_recording) { - logf("pause: not recording"); - pause_done = true; - return; + logf("not recording"); + goto not_recording_or_paused; + } + else if (is_paused) + { + logf("already paused"); + goto not_recording_or_paused; } - pause_start_time = current_tick; + dma_lock = true; /* fix DMA write pointer at current position */ is_paused = true; - pause_done = true; +not_recording_or_paused: + pcm_thread_signal_event(PCMREC_PAUSE); logf("pcmrec_pause done"); -} - +} /* pcmrec_pause */ +/* PCMREC_RESUME */ static void pcmrec_resume(void) { logf("pcmrec_resume"); - if (!is_paused) + if (!is_recording) { - logf("resume: not paused"); - resume_done = true; - return; + 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; - /* Compensate for the time we have been paused */ - if (pause_start_time) - { - record_start_time += current_tick - pause_start_time; - pause_start_time = 0; - } - - resume_done = true; +not_recording_or_not_paused: + pcm_thread_signal_event(PCMREC_RESUME); logf("pcmrec_resume done"); -} +} /* pcmrec_resume */ -/** - * audio_init_recording calls this function using PCMREC_INIT - * - */ -static void pcmrec_init(void) +/* PCMREC_NEW_FILE */ +static void pcmrec_new_file(const char *filename) { - wav_file = -1; - read_pos = 0; - write_pos = 0; - enc_wr_index = 0; - enc_rd_index = 0; + logf("pcmrec_new_file: %s", filename); - avrg_bit_rate = 0; - curr_bit_rate = 0; - curr_chunk_cnt = 0; - - peak_left = 0; - peak_right = 0; + if (!is_recording) + { + logf("not recording"); + goto not_recording; + } num_rec_bytes = 0; - num_file_bytes = 0; - num_pcm_samples = 0; - record_start_time = 0; - pause_start_time = 0; - - close_done = false; - is_recording = false; - is_paused = false; - is_error = false; - - rec_buffer = (unsigned char*)(((long)audiobuf + 15) & ~15); - enc_buffer = rec_buffer + NUM_CHUNKS * CHUNK_SIZE + MAX_FEED_SIZE; - /* 8000Bytes at audiobufend */ - enc_buffer_size = audiobufend - enc_buffer - 8000; - - SET_IIS_PLAY(0x800); /* Stop any playback */ - AUDIOGLOB |= 0x180; /* IIS1 fifo auto sync = on, PDIR2 auto sync = on */ - DATAINCONTROL = 0xc000; /* Generate Interrupt when 6 samples in fifo */ - - DIVR1 = 55; /* DMA1 is mapped into vector 55 in system.c */ - DMACONFIG = 1; /* DMA0Req = PDOR3, DMA1Req = PDIR2 */ - DMAROUTE = (DMAROUTE & 0xffff00ff) | DMA1_REQ_AUDIO_2; - ICR7 = 0x1c; /* Enable interrupt at level 7, priority 0 */ - IMR &= ~(1<<15); /* bit 15 is DMA1 */ - -#ifdef HAVE_SPDIF_IN - PHASECONFIG = 0x34; /* Gain = 3*2^13, source = EBUIN */ -#endif - pcmrec_dma_start(); - - init_done = 1; -} + num_rec_samples = 0; -static void pcmrec_close(void) -{ - DMAROUTE = (DMAROUTE & 0xffff00ff); - ICR7 = 0x00; /* Disable interrupt */ - IMR |= (1<<15); /* bit 15 is DMA1 */ + pcmrec_new_stream(filename, + CHUNKF_START_FILE | CHUNKF_END_FILE, + 0); - pcmrec_dma_stop(); - - /* Reset PDIR2 data flow */ - DATAINCONTROL = 0x200; - close_done = true; - init_done = false; -} +not_recording: + pcm_thread_signal_event(PCMREC_NEW_FILE); + logf("pcmrec_new_file done"); +} /* pcmrec_new_file */ +static void pcmrec_thread(void) __attribute__((noreturn)); static void pcmrec_thread(void) { struct event ev; logf("thread pcmrec start"); - error_count = 0; - while(1) { - queue_wait_w_tmo(&pcmrec_queue, &ev, HZ / 4); + if (is_recording) + { + /* Poll periodically to flush data */ + queue_wait_w_tmo(&pcmrec_queue, &ev, HZ/5); + + if (ev.id == SYS_TIMEOUT) + { + pcmrec_flush(0); /* flush if getting full */ + continue; + } + } + else + { + /* Not doing anything - sit and wait for commands */ + queue_wait(&pcmrec_queue, &ev); + } switch (ev.id) { @@ -992,13 +1385,17 @@ static void pcmrec_thread(void) break; case PCMREC_START: - pcmrec_start(); + pcmrec_start((const char *)ev.data); break; case PCMREC_STOP: pcmrec_stop(); break; + case PCMREC_FINISH_STOP: + pcmrec_finish_stop(); + break; + case PCMREC_PAUSE: pcmrec_pause(); break; @@ -1008,11 +1405,11 @@ static void pcmrec_thread(void) break; case PCMREC_NEW_FILE: - pcmrec_new_file(); + pcmrec_new_file((const char *)ev.data); break; - case SYS_TIMEOUT: - pcmrec_callback(false); + case PCMREC_FLUSH_NUM: + pcmrec_flush((unsigned)ev.data); break; case SYS_USB_CONNECTED: @@ -1023,140 +1420,267 @@ static void pcmrec_thread(void) usb_wait_for_disconnect(&pcmrec_queue); } break; - } - } + } /* end switch */ + } /* end while */ +} /* pcmrec_thread */ - logf("thread pcmrec done"); -} +/****************************************************************************/ +/* */ +/* following functions will be called by the encoder codec */ +/* */ +/****************************************************************************/ -/* Select VINL & VINR source: 0=Line-in, 1=FM Radio */ -void pcm_rec_mux(int source) +/* pass the encoder settings to the encoder */ +void enc_get_inputs(struct enc_inputs *inputs) { -#ifdef IRIVER_H300_SERIES - if(source == 0) - and_l(~0x40000000, &GPIO_OUT); /* Line In */ - else - or_l(0x40000000, &GPIO_OUT); /* FM radio */ + inputs->sample_rate = sample_rate; + inputs->num_channels = num_channels; + inputs->config = &enc_config; +} /* enc_get_inputs */ - or_l(0x40000000, &GPIO_ENABLE); - or_l(0x40000000, &GPIO_FUNCTION); -#elif defined(IRIVER_H100_SERIES) - if(source == 0) - and_l(~0x00800000, &GPIO_OUT); /* Line In */ - else - or_l(0x00800000, &GPIO_OUT); /* FM radio */ +/* set the encoder dimensions (called by encoder codec at initialization and + termination) */ +void enc_set_parameters(struct enc_parameters *params) +{ + size_t bufsize, resbytes; - or_l(0x00800000, &GPIO_ENABLE); - or_l(0x00800000, &GPIO_FUNCTION); + logf("enc_set_parameters"); -#elif defined(IAUDIO_X5) - if(source == 0) - or_l((1<<29), &GPIO_OUT); /* Line In */ - else - and_l(~(1<<29), &GPIO_OUT); /* FM radio */ + if (!params) + { + logf("reset"); + /* Encoder is terminating */ + memset(&enc_config, 0, sizeof (enc_config)); + enc_sample_rate = 0; + return; + } + + enc_sample_rate = params->enc_sample_rate; + logf("enc sampr:%d", enc_sample_rate); + + pcm_rd_pos = dma_wr_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_data_size = enc_chunk_size - ENC_CHUNK_HDR_SIZE; + enc_events_callback = params->events_callback; + + logf("chunk size:%d", 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:%d", resbytes); + + bufsize = rec_buffer_size - (enc_buffer - pcm_buffer) - + resbytes - FNQ_MIN_NUM_PATHS*MAX_PATH; + + enc_num_chunks = bufsize / enc_chunk_size; + logf("num chunks:%d", enc_num_chunks); - or_l((1<<29), &GPIO_ENABLE); - or_l((1<<29), &GPIO_FUNCTION); + /* get real amount used by encoder chunks */ + bufsize = enc_num_chunks*enc_chunk_size; + logf("enc size:%d", bufsize); + + /* 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); + + /** 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; + } - /* iAudio x5 */ + /* 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 = ALIGN_DOWN(fnq_size, MAX_PATH); + logf("fnq files: %d", fnq_size / MAX_PATH); + +#if 0 + logf("ab :%08X", (unsigned long)audiobuf); + logf("pcm:%08X", (unsigned long)pcm_buffer); + logf("enc:%08X", (unsigned long)enc_buffer); + logf("res:%08X", (unsigned long)params->reserve_buffer); + logf("fnq:%08X", (unsigned long)fn_queue); + logf("end:%08X", (unsigned long)fn_queue + fnq_size); + logf("abe:%08X", (unsigned long)audiobufend); #endif -} + /* init all chunk headers and reset indexes */ + enc_rd_index = 0; + for (enc_wr_index = enc_num_chunks; enc_wr_index > 0; ) + GET_ENC_CHUNK(--enc_wr_index)->flags = 0; -/****************************************************************************/ -/* */ -/* following functions will be called by the encoder codec */ -/* */ -/****************************************************************************/ + logf("enc_set_parameters done"); +} /* enc_set_parameters */ -/* pass the encoder buffer pointer/size, mono/stereo, quality to the encoder */ -void enc_get_inputs(int *buffer_size, int *channels, int *quality) +/* return encoder chunk at current write position */ +struct enc_chunk_hdr * enc_get_chunk(void) { - *buffer_size = enc_buffer_size; - *channels = enc_channels; - *quality = enc_quality; -} + struct enc_chunk_hdr *chunk = GET_ENC_CHUNK(enc_wr_index); + chunk->flags &= CHUNKF_START_FILE; -/* set the encoder dimensions (called by encoder codec at initialization) */ -void enc_set_parameters(int chunk_size, int num_chunks, int samp_per_chunk, - char *head_ptr, int head_size, int enc_id) -{ - /* set read_pos just in front of current write_pos */ - read_pos = (write_pos - CHUNK_SIZE) & CHUNK_MASK; - - enc_rd_index = 0; /* reset */ - enc_wr_index = 0; /* reset */ - enc_chunk_size = chunk_size; /* max chunk size */ - enc_num_chunks = num_chunks; /* total number of chunks */ - enc_samp_per_chunk = samp_per_chunk; /* pcm samples / encoderchunk */ - enc_head_buffer = head_ptr; /* optional file header data (wav) */ - enc_head_size = head_size; /* optional file header data (wav) */ - audio_enc_id = enc_id; /* AFMT_* id */ -} + if (!is_recording) + chunk->flags |= CHUNKF_PRERECORD; -/* allocate encoder chunk */ -unsigned int *enc_alloc_chunk(void) -{ - return (unsigned int*)(enc_buffer + enc_wr_index * enc_chunk_size); -} + return chunk; +} /* enc_get_chunk */ -/* free previously allocated encoder chunk */ -void enc_free_chunk(void) +/* releases the current chunk into the available chunks */ +void enc_finish_chunk(void) { - unsigned long *enc_chunk; + struct enc_chunk_hdr *chunk = GET_ENC_CHUNK(enc_wr_index); - enc_chunk = GET_ENC_CHUNK(enc_wr_index); - curr_chunk_cnt++; -/* curr_bit_rate += *enc_chunk * 44100 * 8 / (enc_samp_per_chunk * 1000); */ - curr_bit_rate += *enc_chunk * 441 * 8 / (enc_samp_per_chunk * 10 ); - avrg_bit_rate = (curr_bit_rate + curr_chunk_cnt / 2) / curr_chunk_cnt; + /* encoder may have set error flag or written too much data */ + if ((long)chunk->flags < 0 || chunk->enc_size > enc_data_size) + { + is_error = true; - /* advance enc_wr_index to the next chunk */ - enc_wr_index = (enc_wr_index + 1) % enc_num_chunks; +#ifdef ROCKBOX_HAS_LOGF + if (chunk->enc_size > enc_data_size) + { + /* illegal to scribble over next chunk */ + logf("finish chk ovf: %d>%d", chunk->enc_size, enc_data_size); + } + else + { + /* encoder set error flag */ + logf("finish chk enc error"); + } +#endif + } + + /* advance enc_wr_index to the next encoder chunk */ + INC_ENC_INDEX(enc_wr_index); - /* buffer full: advance enc_rd_index (for prerecording purpose) */ - if (enc_rd_index == enc_wr_index) + if (enc_rd_index != enc_wr_index) { - enc_rd_index = (enc_rd_index + 1) % enc_num_chunks; + num_rec_bytes += chunk->enc_size; + accum_rec_bytes += chunk->enc_size; + num_rec_samples += chunk->num_pcm; + accum_pcm_samples += chunk->num_pcm; } -} + else if (is_recording) /* buffer full */ + { + /* keep current position */ + logf("enc_buffer ovf"); + DEC_ENC_INDEX(enc_wr_index); + } + else + { + /* advance enc_rd_index for prerecording */ + INC_ENC_INDEX(enc_rd_index); + } +} /* enc_finish_chunk */ -/* checks near empty state on wav input buffer */ -int enc_wavbuf_near_empty(void) +/* checks near empty state on pcm input buffer */ +int enc_pcm_buf_near_empty(void) { /* less than 1sec raw data? => unboost encoder */ - if (((write_pos - read_pos) & CHUNK_MASK) < 44100*4) - return 1; - else - return 0; -} + size_t avail = (dma_wr_pos - pcm_rd_pos) & PCM_CHUNK_MASK; + return avail < (sample_rate << 2) ? 1 : 0; +} /* enc_pcm_buf_near_empty */ /* passes a pointer to next chunk of unprocessed wav data */ -char *enc_get_wav_data(int size) +/* TODO: this really should give the actual size returned */ +unsigned char * enc_get_pcm_data(size_t size) { - char *ptr; - int avail; + size_t avail = (dma_wr_pos - pcm_rd_pos) & PCM_CHUNK_MASK; /* limit the requested pcm data size */ - if(size > MAX_FEED_SIZE) - size = MAX_FEED_SIZE; - - avail = (write_pos - read_pos) & CHUNK_MASK; + if (size > PCM_MAX_FEED_SIZE) + size = PCM_MAX_FEED_SIZE; if (avail >= size) { - ptr = rec_buffer + read_pos; - read_pos = (read_pos + size) & CHUNK_MASK; + unsigned char *ptr = pcm_buffer + pcm_rd_pos; + pcm_rd_pos = (pcm_rd_pos + size) & PCM_CHUNK_MASK; /* ptr must point to continous data at wraparound position */ - if (read_pos < size) - memcpy(rec_buffer + NUM_CHUNKS * CHUNK_SIZE, - rec_buffer, read_pos); + 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; return ptr; } + /* not enough data available - encoder should idle */ wav_queue_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); + + if (pcm_rd_pos != dma_wr_pos) + { + /* disallow backing up into current DMA write chunk */ + size_t old_avail = (pcm_rd_pos - dma_wr_pos - PCM_CHUNK_SIZE) + & PCM_CHUNK_MASK; + + /* limit size to amount of old data remaining */ + if (size > old_avail) + size = old_avail; + + pcm_rd_pos = (pcm_rd_pos - size) & PCM_CHUNK_MASK; + } + + set_irq_level(level); + + return size; +} /* enc_unget_pcm_data */ + +/** Low level pcm recording apis **/ + +/**************************************************************************** + * Functions that do not require targeted implementation but only a targeted + * interface + */ +void pcm_record_data(pcm_more_callback_type more_ready, + unsigned char *start, size_t size) +{ + pcm_callback_more_ready = more_ready; + + if (!(start && size)) + { + size = 0; + if (more_ready) + more_ready(&start, &size); + } + + if (start && size) + pcm_rec_dma_start(start, size); +} /* pcm_record_data */ + +void pcm_stop_recording(void) +{ + if (pcm_recording) + pcm_rec_dma_stop(); +} /* pcm_stop_recording */ |