| /*************************************************************************** |
| * __________ __ ___. |
| * 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 "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 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; |
| 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->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 */ |