/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2005 Linus Nielsen Feltzing * Copyright (C) 2006 Antonius Hellmann * Copyright (C) 2006-2013 Michael Sevakis * * * 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 "config.h" #include "system.h" #include "kernel.h" #include "panic.h" #include "string-extra.h" #include "pcm_record.h" #include "codecs.h" #include "logf.h" #include "thread.h" #include "storage.h" #include "general.h" #include "codec_thread.h" #include "audio.h" #include "sound.h" #include "metadata.h" #include "appevents.h" #ifdef HAVE_SPDIF_IN #include "spdif.h" #endif #include "audio_thread.h" #include "core_alloc.h" #include "talk.h" /* Macros to enable logf for queues logging on SYS_TIMEOUT can be disabled */ #ifdef SIMULATOR /* Define this for logf output of all queuing except SYS_TIMEOUT */ #define PCMREC_LOGQUEUES /* Define this to logf SYS_TIMEOUT messages */ /*#define PCMREC_LOGQUEUES_SYS_TIMEOUT*/ #endif /* SIMULATOR */ #ifdef PCMREC_LOGQUEUES #define LOGFQUEUE logf #else #define LOGFQUEUE(...) #endif #ifdef PCMREC_LOGQUEUES_SYS_TIMEOUT #define LOGFQUEUE_SYS_TIMEOUT logf #else #define LOGFQUEUE_SYS_TIMEOUT(...) #endif /** Target-related configuration **/ /** * PCM_NUM_CHUNKS: Number of PCM chunks * PCM_CHUNK_SAMP: Number of samples in a PCM chunk * PCM_BOOST_SECONDS: PCM level at which to boost CPU * PANIC_SECONDS: Flood watermark time until full * FLUSH_SECONDS: Flush watermark time until full * STREAM_BUF_SIZE: Size of stream write buffer * PRIO_SECONDS: Max flush time before prio boost * * Total PCM buffer size should be mem aligned * * Fractions should be left without parentheses so the multiplier is * multiplied by the numerator first. */ #if MEMORYSIZE <= 2 #define PCM_NUM_CHUNKS 56 #define PCM_CHUNK_SAMP 1024 #define PCM_BOOST_SECONDS 1/2 #define PANIC_SECONDS 1/2 #define FLUSH_SECONDS 1 #define FLUSH_MON_INTERVAL 1/6 #define STREAM_BUF_SIZE 32768 #elif MEMORYSIZE <= 16 #define PANIC_SECONDS 5 #define FLUSH_SECONDS 7 #else /* MEMORYSIZE > 16 */ #define PANIC_SECONDS 8 #define FLUSH_SECONDS 10 #endif /* MEMORYSIZE */ /* Default values if not overridden above */ #ifndef PCM_NUM_CHUNKS #define PCM_NUM_CHUNKS 256 #endif #ifndef PCM_CHUNK_SAMP #define PCM_CHUNK_SAMP 2048 #endif #ifndef PCM_BOOST_SECONDS #define PCM_BOOST_SECONDS 1 #endif #ifndef FLUSH_MON_INTERVAL #define FLUSH_MON_INTERVAL 1/4 #endif #ifndef STREAM_BUF_SIZE #define STREAM_BUF_SIZE 65536 #endif #ifndef PRIO_SECONDS #define PRIO_SECONDS 10 #endif /* FAT limit for filesize. Recording will accept no further data from the * codec if this limit is reached in order to preserve its own data * integrity. A split should have made by the higher-ups long before this * point. * * Leave a generous 64k margin for metadata being added to file. */ #define MAX_NUM_REC_BYTES ((size_t)0x7fff0000u) /***************************************************************************/ extern struct codec_api ci; /* in codec_thread.c */ extern struct event_queue audio_queue; /* in audio_thread.c */ extern unsigned int audio_thread_id; /* in audio_thread.c */ /** General recording state **/ /* Recording action being performed */ static enum record_status { RECORD_STOPPED = 0, RECORD_PRERECORDING = AUDIO_STATUS_PRERECORD, RECORD_RECORDING = AUDIO_STATUS_RECORD, RECORD_PAUSED = (AUDIO_STATUS_RECORD | AUDIO_STATUS_PAUSE), } record_status = RECORD_STOPPED; /* State of engine operations */ static enum record_state { REC_STATE_IDLE, /* Stopped or prerecording */ REC_STATE_MONITOR, /* Monitoring buffer status */ REC_STATE_FLUSH, /* Flushing buffer */ } record_state = REC_STATE_IDLE; static uint32_t errors; /* An error has occured (bitmask) */ static uint32_t warnings; /* Non-fatal warnings (bitmask) */ static uint32_t rec_errors; /* Mirror of errors but private to * avoid race with controlling * thread. Engine uses this * internally. */ /** Stats on encoded data for current file **/ static int rec_fd = -1; /* Currently open file descriptor */ static size_t num_rec_bytes; /* Number of bytes recorded */ static uint64_t num_rec_samples; /* Number of PCM samples recorded */ static uint64_t encbuf_rec_count; /* Count of slots written to buffer for current file */ /** These apply to current settings **/ static int rec_source; /* Current rec_source setting */ static unsigned long sample_rate; /* Samplerate setting in HZ */ static int num_channels; /* Current number of channels */ static struct encoder_config enc_config; /* Current encoder configuration */ static unsigned int pre_record_seconds; /* Pre-record time in seconds */ /**************************************************************************** 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.audio: codec_load(); load the encoder 2.encoder: enc_init_parameters(); set the encoder parameters (at load) 3.audio: enc_callback(); configure encoder recording settings 4.audio: codec_go(); start encoding the new stream 5.encoder: enc_encbuf_get_buffer(); obtain an output buffer of size n 6.encoder: enc_pcmbuf_read(); read n bytes of unprocessed pcm data 7.encoder: enc_encbuf_finish_buffer(); add the obtained buffer to output 8.encoder: enc_pcmbuf_advance(); advance pcm by n samples 9.encoder: while more PCM available, repeat 5. to 9. 10.audio: codec_finish_stream(); finish the output for current stream Function calls (basic stream flushing steps through enc_callback()): 1.audio: flush_stream_start(); stream flush destination is opening 2.audio: flush_stream_data(); flush encoded audio to stream 3.audio: while encoded data available, repeat 2. 4.audio: flush_stream_end(); stream flush destination is closing ****************************************************************************/ /** Buffer parameters where incoming PCM data is placed **/ #define PCM_DEPTH_BYTES (sizeof (int16_t)) #define PCM_SAMP_SIZE (2*PCM_DEPTH_BYTES) #define PCM_CHUNK_SIZE (PCM_CHUNK_SAMP*PCM_SAMP_SIZE) #define PCM_BUF_SIZE (PCM_NUM_CHUNKS*PCM_CHUNK_SIZE) /* Convert byte sizes into buffer slot counts */ #define CHUNK_SIZE_COUNT(size) \ (((size) + ENC_HDR_SIZE - 1) / ENC_HDR_SIZE) #define CHUNK_FILE_COUNT(size) \ ({ typeof (size) __size = (size); \ CHUNK_SIZE_COUNT(MIN(__size, MAX_PATH) + ENC_HDR_SIZE); }) #define CHUNK_FILE_COUNT_PATH(path) \ CHUNK_FILE_COUNT(strlen(path) + 1) #define CHUNK_DATA_COUNT(size) \ CHUNK_SIZE_COUNT((size) + sizeof (struct enc_chunk_data)) /* Min margin to write stream split headers without overwrap risk */ #define ENCBUF_MIN_SPLIT_MARGIN \ (2*(1 + CHUNK_FILE_COUNT(MAX_PATH)) - 1) static void *rec_buffer; /* Root rec buffer pointer */ static size_t rec_buffer_size; /* Root rec buffer size */ static void *pcm_buffer; /* Circular buffer for PCM samples */ static volatile bool pcm_pause; /* Freeze DMA write position */ static volatile size_t pcm_widx; /* Current DMA write position */ static volatile size_t pcm_ridx; /* Current PCM read position */ static union enc_chunk_hdr *enc_buffer; /* Circular encoding buffer */ static size_t enc_widx; /* Encoder chunk write index */ static size_t enc_ridx; /* Encoder chunk read index */ static size_t enc_buflen; /* Length of buffer in slots */ static unsigned char *stream_buffer; /* Stream-to-disk write buffer */ static ssize_t stream_buf_used; /* Stream write buffer occupancy */ static struct enc_chunk_file *fname_buf;/* Buffer with next file to create */ static unsigned long enc_sample_rate; /* Samplerate used by encoder */ static bool pcm_buffer_empty; /* All PCM chunks processed? */ static typeof (memcpy) *pcm_copyfn; /* PCM memcpy or copy_buffer_mono */ static enc_callback_t enc_cb; /* Encoder's recording callback */ /** File flushing **/ static unsigned long encbuf_datarate; /* Rate of data per second */ #if (CONFIG_STORAGE & STORAGE_ATA) static int spinup_time; /* Last spinup time */ #endif static size_t high_watermark; /* Max limit for data flush */ #ifdef HAVE_PRIORITY_SCHEDULING static size_t flood_watermark; /* Max limit for thread prio boost */ static bool prio_boosted; #endif /** Stream marking **/ enum mark_stream_action { MARK_STREAM_END = 0x1, /* Mark end current stream */ MARK_STREAM_START = 0x2, /* Mark start of new stream */ MARK_STREAM_SPLIT = 0x3, /* Insert split; orr of above values */ MARK_STREAM_PRE = 0x4, /* Do prerecord data tally */ MARK_STREAM_START_PRE = MARK_STREAM_PRE | MARK_STREAM_START, }; /***************************************************************************/ /* Buffer pointer (p) to PCM sample memory address */ static inline void * pcmbuf_ptr(size_t p) { return pcm_buffer + p; } /* Buffer pointer (p) plus value (v), wrapped if necessary */ static size_t pcmbuf_add(size_t p, size_t v) { size_t res = p + v; if (res >= PCM_BUF_SIZE) res -= PCM_BUF_SIZE; return res; } /* Size of data in PCM buffer */ size_t pcmbuf_used(void) { size_t p1 = pcm_ridx; size_t p2 = pcm_widx; if (p1 > p2) p2 += PCM_BUF_SIZE; return p2 - p1; } /* Buffer pointer (p) to memory address of header */ static inline union enc_chunk_hdr * encbuf_ptr(size_t p) { return enc_buffer + p; } /* Buffer pointer (p) plus value (v), wrapped if necessary */ static size_t encbuf_add(size_t p, size_t v) { size_t res = p + v; if (res >= enc_buflen) res -= enc_buflen; return res; } /* Number of free buffer slots */ static size_t encbuf_free(void) { size_t p1 = enc_ridx; size_t p2 = enc_widx; if (p2 >= p1) p1 += enc_buflen; return p1 - p2; } /* Number of used buffer slots */ static size_t encbuf_used(void) { size_t p1 = enc_ridx; size_t p2 = enc_widx; if (p1 > p2) p2 += enc_buflen; return p2 - p1; } /* Is the encoder buffer empty? */ static bool encbuf_empty(void) { return enc_ridx == enc_widx; } /* Buffer pointer (p) plus size (v), written to enc_widx, new widx * zero-initialized */ static void encbuf_widx_advance(size_t widx, size_t v) { widx = encbuf_add(widx, v); encbuf_ptr(widx)->zero = 0; enc_widx = widx; } /* Buffer pointer (p) plus size of chunk at (p), wrapped to (0) if * necessary. * * pout points to variable to receive increment result * * Returns NULL if it was a wrap marker */ static void * encbuf_read_ptr_incr(size_t p, size_t *pout) { union enc_chunk_hdr *hdr = encbuf_ptr(p); size_t v; switch (hdr->type) { case CHUNK_T_DATA: v = CHUNK_DATA_COUNT(hdr->size); break; case CHUNK_T_STREAM_START: v = hdr->size; break; case CHUNK_T_STREAM_END: default: v = 1; break; case CHUNK_T_WRAP: /* Wrap markers are not returned but caller may have to know that the index was changed since it impacts available space */ *pout = 0; return NULL; } *pout = encbuf_add(p, v); return hdr; } /* Buffer pointer (p) of contiguous free space (v), wrapped to (0) if * necessary. * * pout points to variable to receive possible-adjusted p * * Returns header at (p) or wrapped header at (0) if wrap was * required in order to provide contiguous space. Header is zero- * initialized. * * Marks the wrap point if a wrap is required to make the allocation. */ static void * encbuf_get_write_ptr(size_t p, size_t v, size_t *pout) { union enc_chunk_hdr *hdr = encbuf_ptr(p); if (p + v > enc_buflen) { hdr->type = CHUNK_T_WRAP; /* All other fields ignored */ p = 0; hdr = encbuf_ptr(0); } *pout = p; hdr->zero = 0; return hdr; } /* Post a flush request to audio thread, if none is currently queued */ static void encbuf_request_flush(void) { if (!queue_peek_ex(&audio_queue, NULL, 0, &(const long [2]){ Q_AUDIO_RECORD_FLUSH, Q_AUDIO_RECORD_FLUSH })) queue_post(&audio_queue, Q_AUDIO_RECORD_FLUSH, 0); } /* Set the error bits in (e): no lock */ static inline void set_error_bits(uint32_t e) { errors |= e; rec_errors |= e; } /* Clear the error bits in (e): no lock */ static inline void clear_error_bits(uint32_t e) { errors &= ~e; } /* Set the error bits in (e) */ static void raise_error_status(uint32_t e) { pcm_rec_lock(); set_error_bits(e); pcm_rec_unlock(); } /* Clear the error bits in (e) */ static void clear_error_status(uint32_t e) { pcm_rec_lock(); clear_error_bits(e); pcm_rec_unlock(); } /* Set the warning bits in (w): no lock */ static inline void set_warning_bits(uint32_t w) { warnings |= w; } /* Clear the warning bits in (w): no lock */ static inline void clear_warning_bits(uint32_t w) { warnings &= ~w; } /* Set the warning bits in (w) */ static void raise_warning_status(uint32_t w) { pcm_rec_lock(); set_warning_bits(w); pcm_rec_unlock(); } /* Clear the warning bits in (w) */ static void clear_warning_status(uint32_t w) { pcm_rec_lock(); clear_warning_bits(w); pcm_rec_unlock(); } /* Callback for when more data is ready - called by DMA ISR */ static void pcm_rec_have_more(void **start, size_t *size) { size_t next_idx = pcm_widx; if (!pcm_pause) { /* One empty chunk must remain after widx is advanced */ if (pcmbuf_used() <= PCM_BUF_SIZE - 2*PCM_CHUNK_SIZE) next_idx = pcmbuf_add(next_idx, PCM_CHUNK_SIZE); else set_warning_bits(PCMREC_W_PCM_BUFFER_OVF); } *start = pcmbuf_ptr(next_idx); *size = PCM_CHUNK_SIZE; pcm_widx = next_idx; } static enum pcm_dma_status pcm_rec_status_callback(enum pcm_dma_status status) { if (status < PCM_DMAST_OK) { /* Some error condition */ if (status == PCM_DMAST_ERR_DMA) { set_error_bits(PCMREC_E_DMA); return status; } else { /* Try again next transmission - frame is invalid */ set_warning_bits(PCMREC_W_DMA); } } return PCM_DMAST_OK; } /* Start DMA transfer */ static void pcm_start_recording(void) { pcm_record_data(pcm_rec_have_more, pcm_rec_status_callback, pcmbuf_ptr(pcm_widx), PCM_CHUNK_SIZE); } /* Initialize the various recording buffers */ static void init_rec_buffers(void) { /* Layout of recording buffer: |PCMBUF|STREAMBUF|FILENAME|ENCBUF| */ void *buf = rec_buffer; size_t size = rec_buffer_size; /* PCMBUF */ pcm_buffer = CACHEALIGN_UP(buf); /* Line align */ size -= pcm_buffer + PCM_BUF_SIZE - buf; buf = pcm_buffer + PCM_BUF_SIZE; /* STREAMBUF */ stream_buffer = buf; /* Also line-aligned */ buf += STREAM_BUF_SIZE; size -= STREAM_BUF_SIZE; /* FILENAME */ fname_buf = buf; buf += CHUNK_FILE_COUNT(MAX_PATH)*ENC_HDR_SIZE; size -= CHUNK_FILE_COUNT(MAX_PATH)*ENC_HDR_SIZE; fname_buf->hdr.zero = 0; /* ENCBUF */ enc_buffer = buf; enc_buflen = size; ALIGN_BUFFER(enc_buffer, enc_buflen, ENC_HDR_SIZE); enc_buflen = CHUNK_SIZE_COUNT(enc_buflen); } /* Reset the circular buffers */ static void reset_fifos(bool hard) { /* PCM FIFO */ pcm_pause = true; if (hard) pcm_widx = 0; /* Don't just empty but reset it */ pcm_ridx = pcm_widx; /* Encoder FIFO */ encbuf_widx_advance(0, 0); enc_ridx = 0; /* No overflow-related warnings now */ clear_warning_status(PCMREC_W_PCM_BUFFER_OVF | PCMREC_W_ENC_BUFFER_OVF); } /* Initialize file statistics */ static void reset_rec_stats(void) { num_rec_bytes = 0; num_rec_samples = 0; encbuf_rec_count = 0; clear_warning_status(PCMREC_W_FILE_SIZE); } /* Boost or unboost recording threads' priorities */ static void do_prio_boost(bool boost) { #ifdef HAVE_PRIORITY_SCHEDULING prio_boosted = boost; int prio = PRIORITY_RECORDING; if (boost) prio -= 4; codec_thread_set_priority(prio); thread_set_priority(audio_thread_id, prio); #endif (void)boost; } /* Reset all relevant state */ static void init_state(void) { reset_fifos(true); reset_rec_stats(); do_prio_boost(false); cancel_cpu_boost(); record_state = REC_STATE_IDLE; record_status = RECORD_STOPPED; } /* Set hardware samplerate and save it */ static void update_samplerate_config(unsigned long sampr) { /* PCM samplerate is either the same as the setting or the nearest one hardware supports if using S/PDIF */ unsigned long pcm_sampr = sampr; #ifdef HAVE_SPDIF_IN if (rec_source == AUDIO_SRC_SPDIF) { int index = round_value_to_list32(sampr, hw_freq_sampr, HW_NUM_FREQ, false); pcm_sampr = hw_freq_sampr[index]; } #endif /* HAVE_SPDIF_IN */ pcm_set_frequency(pcm_sampr | SAMPR_TYPE_REC); sample_rate = sampr; } /* Calculate the average data rate */ static unsigned long get_encbuf_datarate(void) { /* If not yet calculable, start with uncompressed PCM byterate */ if (num_rec_samples && sample_rate && encbuf_rec_count) { return (encbuf_rec_count*sample_rate + num_rec_samples - 1) / num_rec_samples; } else { return CHUNK_SIZE_COUNT(sample_rate*num_channels*PCM_DEPTH_BYTES); } } /* Returns true if the watermarks should be updated due to data rate change */ static bool monitor_encbuf_datarate(void) { unsigned long rate = get_encbuf_datarate(); long diff = rate - encbuf_datarate; /* Off by more than 1/2 FLUSH_MON_INTERVAL? */ return 2*(unsigned long)abs(diff) > encbuf_datarate*FLUSH_MON_INTERVAL; } /* Get adjusted spinup time */ static int get_spinup_time(void) { int spin = storage_spinup_time(); #if (CONFIG_STORAGE & STORAGE_ATA) /* Write at FLUSH_SECONDS + st remaining in enc_buffer - range fs+2s to fs+10s total - default to 3.5s spinup. */ if (spin == 0) spin = 35*HZ/10; /* default - cozy */ else if (spin < 2*HZ) spin = 2*HZ; /* ludicrous - ramdisk? */ else if (spin > 10*HZ) spin = 10*HZ; /* do you have a functioning HD? */ #endif /* (CONFIG_STORAGE & STORAGE_ATA) */ return spin; } /* Returns true if the watermarks should be updated due to spinup time change */ static inline bool monitor_spinup_time(void) { #if (CONFIG_STORAGE & STORAGE_ATA) return get_spinup_time() != spinup_time; #else return false; #endif } /* Update buffer watermarks with spinup time compensation */ static void refresh_watermarks(void) { int spin = get_spinup_time(); #if (CONFIG_STORAGE & STORAGE_ATA) logf("ata spinup: %d", spin); spinup_time = spin; #endif unsigned long rate = get_encbuf_datarate(); logf("byterate: %lu", rate * ENC_HDR_SIZE); encbuf_datarate = rate; /* Try to start writing with FLUSH_SECONDS remaining after disk spinup */ high_watermark = (uint64_t)rate*(FLUSH_SECONDS*HZ + spin) / HZ; if (high_watermark > enc_buflen) high_watermark = enc_buflen; high_watermark = enc_buflen - high_watermark; logf("high wm: %lu", (unsigned long)high_watermark); #ifdef HAVE_PRIORITY_SCHEDULING /* Boost thread priority if enough ground is lost since flushing started or is taking an unreasonably long time */ flood_watermark = rate*PANIC_SECONDS; if (flood_watermark > enc_buflen) flood_watermark = enc_buflen; flood_watermark = enc_buflen - flood_watermark; logf("flood wm: %lu", (unsigned long)flood_watermark); #endif /* HAVE_PRIORITY_SCHEDULING */ } /* Tell encoder the stream parameters and get information back */ static bool configure_encoder_stream(void) { struct enc_inputs inputs; inputs.sample_rate = sample_rate; inputs.num_channels = num_channels; inputs.config = &enc_config; /* encoder can change these - init with defaults */ inputs.enc_sample_rate = sample_rate; if (enc_cb(ENC_CB_INPUTS, &inputs) < 0) { raise_error_status(PCMREC_E_ENC_SETUP); return false; } enc_sample_rate = inputs.enc_sample_rate; if (enc_sample_rate != sample_rate) { /* Codec doesn't want to/can't use the setting and has chosen a different sample rate */ raise_warning_status(PCMREC_W_SAMPR_MISMATCH); logf("enc sampr:%lu", enc_sample_rate); } else { clear_warning_status(PCMREC_W_SAMPR_MISMATCH); } refresh_watermarks(); return true; } #ifdef HAVE_SPDIF_IN /* Return the S/PDIF sample rate closest to a value in the master list */ static unsigned long get_spdif_samplerate(void) { unsigned long sr = spdif_measure_frequency(); int index = round_value_to_list32(sr, audio_master_sampr_list, SAMPR_NUM_FREQ, false); return audio_master_sampr_list[index]; } /* Check the S/PDIF rate and compare to current setting. Apply the new * rate if it changed. */ static void check_spdif_samplerate(void) { unsigned long sampr = get_spdif_samplerate(); if (sampr == sample_rate) return; codec_stop(); pcm_stop_recording(); reset_fifos(true); reset_rec_stats(); update_samplerate_config(sampr); pcm_apply_settings(); if (!configure_encoder_stream() || rec_errors) return; pcm_start_recording(); if (record_status == RECORD_PRERECORDING) { codec_go(); pcm_pause = false; } } #endif /* HAVE_SPDIF_IN */ /* Discard the stream buffer contents */ static inline void stream_discard_buf(void) { stream_buf_used = 0; } /* Flush stream buffer to disk */ static bool stream_flush_buf(void) { if (stream_buf_used == 0) return true; ssize_t rc = write(rec_fd, stream_buffer, stream_buf_used); if (LIKELY(rc == stream_buf_used)) { stream_discard_buf(); return true; } if (rc > 0) { /* Some was written; keep in sync */ stream_buf_used -= rc; memmove(stream_buffer, stream_buffer + rc, stream_buf_used); } return false; } /* Close the output file */ static void close_rec_file(void) { if (rec_fd < 0) return; bool ok = stream_flush_buf(); if (close(rec_fd) != 0 || !ok) raise_error_status(PCMREC_E_IO); rec_fd = -1; } /* Creates or opens the current path */ static bool open_rec_file(bool create) { if (rec_fd >= 0) { /* Any previous file should have been closed */ logf("open file: file already open"); close_rec_file(); } stream_discard_buf(); int oflags = create ? O_CREAT|O_TRUNC : 0; rec_fd = open(fname_buf->path, O_RDWR|oflags, 0666); if (rec_fd < 0) { raise_error_status(PCMREC_E_IO); return false; } return true; } /* Copy with mono conversion - output 1/2 size of input */ static void * ICODE_ATTR copy_buffer_mono_lr(void *dst, const void *src, size_t src_size) { int16_t *d = dst; int16_t const *s = src; /* mono = (L + R) / 2 */ do *d++ = ((int32_t){ *s++ } + *s++ + 1) >> 1; while (src_size -= PCM_SAMP_SIZE); return dst; } /* Copy with mono conversion - output 1/2 size of input */ static void * ICODE_ATTR copy_buffer_mono_l(void *dst, const void *src, size_t src_size) { int16_t *d = dst; int16_t const *s = (int16_t *)src - 2; /* mono = L */ do *d++ = *(s += 2); while (src_size -= PCM_SAMP_SIZE); return dst; } /* Copy with mono conversion - output 1/2 size of input */ static void * ICODE_ATTR copy_buffer_mono_r(void *dst, const void *src, size_t src_size) { int16_t *d = dst; int16_t const *s = (int16_t *)src - 1; /* mono = R */ do *d++ = *(s += 2); while (src_size -= PCM_SAMP_SIZE); return dst; } /** pcm_rec_* group **/ /* Clear all errors and warnings */ void pcm_rec_error_clear(void) { clear_error_status(PCMREC_E_ALL); clear_warning_status(PCMREC_W_ALL); } /* Check mode, errors and warnings */ unsigned int pcm_rec_status(void) { unsigned int ret = record_status; if (errors) ret |= AUDIO_STATUS_ERROR; if (warnings) ret |= AUDIO_STATUS_WARNING; return ret; } /* Return warnings that have occured since recording started */ uint32_t pcm_rec_get_warnings(void) { return warnings; } #ifdef HAVE_SPDIF_IN /* Return the currently-configured sample rate */ unsigned long pcm_rec_sample_rate(void) { return sample_rate; } #endif /** audio_* group **/ /* Initializes recording - call before calling any other recording function */ void audio_init_recording(void) { LOGFQUEUE("audio >| pcmrec Q_AUDIO_INIT_RECORDING"); audio_queue_send(Q_AUDIO_INIT_RECORDING, 1); } /* Closes recording - call audio_stop_recording first or risk data loss */ void audio_close_recording(void) { LOGFQUEUE("audio >| pcmrec Q_AUDIO_CLOSE_RECORDING"); audio_queue_send(Q_AUDIO_CLOSE_RECORDING, 0); } /* Sets recording parameters */ void audio_set_recording_options(struct audio_recording_options *options) { LOGFQUEUE("audio >| pcmrec Q_AUDIO_RECORDING_OPTIONS"); audio_queue_send(Q_AUDIO_RECORDING_OPTIONS, (intptr_t)options); } /* Start recording if not recording or else split */ void audio_record(const char *filename) { LOGFQUEUE("audio >| pcmrec Q_AUDIO_RECORD: %s", filename); audio_queue_send(Q_AUDIO_RECORD, (intptr_t)filename); } /* audio_record alias for API compatibility with HW codec */ void audio_new_file(const char *filename) __attribute__((alias("audio_record"))); /* Stop current recording if recording */ void audio_stop_recording(void) { LOGFQUEUE("audio > pcmrec Q_AUDIO_RECORD_STOP"); audio_queue_post(Q_AUDIO_RECORD_STOP, 0); } /* Pause current recording */ void audio_pause_recording(void) { LOGFQUEUE("audio > pcmrec Q_AUDIO_RECORD_PAUSE"); audio_queue_post(Q_AUDIO_RECORD_PAUSE, 0); } /* Resume current recording if paused */ void audio_resume_recording(void) { LOGFQUEUE("audio > pcmrec Q_AUDIO_RECORD_RESUME"); audio_queue_post(Q_AUDIO_RECORD_RESUME, 0); } /* Set the input source gain. For mono sources, only left gain is used */ void audio_set_recording_gain(int left, int right, int type) { #if 0 logf("pcmrec: t=%d l=%d r=%d", type, left, right); #endif audiohw_set_recvol(left, right, type); } /** Information about current state **/ /* Return sample clock in HZ */ static unsigned long get_samples_time(void) { if (enc_sample_rate == 0) return 0; return (unsigned long)(HZ*num_rec_samples / enc_sample_rate); } /* Return current prerecorded time in ticks (playback equivalent time) */ unsigned long audio_prerecorded_time(void) { if (record_status != RECORD_PRERECORDING) return 0; unsigned long t = get_samples_time(); return MIN(t, pre_record_seconds*HZ); } /* Return current recorded time in ticks (playback equivalent time) */ unsigned long audio_recorded_time(void) { if (record_state == REC_STATE_IDLE) return 0; return get_samples_time(); } /* Return number of bytes encoded to output */ unsigned long audio_num_recorded_bytes(void) { if (record_state == REC_STATE_IDLE) return 0; return num_rec_bytes; } /** Data Flushing **/ /* Stream start chunk with path was encountered */ static void flush_stream_start(struct enc_chunk_file *file) { /* Save filename; don't open file here which avoids creating files with no audio content. Splitting while paused can create those in large numbers. */ fname_buf->hdr = file->hdr; /* Correct size if this was wrap-padded */ fname_buf->hdr.size = CHUNK_FILE_COUNT( strlcpy(fname_buf->path, file->path, MAX_PATH) + 1); } /* Data chunk was encountered */ static bool flush_stream_data(struct enc_chunk_data *data) { if (fname_buf->hdr.zero) { /* First data chunk; create the file */ if (open_rec_file(true)) { /* Inherit some flags from initial data chunk */ fname_buf->hdr.err = data->hdr.err; fname_buf->hdr.pre = data->hdr.pre; fname_buf->hdr.aux0 = data->hdr.aux0; if (enc_cb(ENC_CB_STREAM, fname_buf) < 0) raise_error_status(PCMREC_E_ENCODER_STREAM); } fname_buf->hdr.zero = 0; if (rec_errors) return false; } if (rec_fd < 0) return true; /* Just keep discarding */ if (enc_cb(ENC_CB_STREAM, data) < 0) { raise_error_status(PCMREC_E_ENCODER_STREAM); return false; } return true; } /* Stream end chunk was encountered */ static bool flush_stream_end(union enc_chunk_hdr *hdr) { if (rec_fd < 0) return true; if (enc_cb(ENC_CB_STREAM, hdr) < 0) { raise_error_status(PCMREC_E_ENCODER_STREAM); return false; } close_rec_file(); return true; } /* Discard remainder of stream in encoder buffer */ static void discard_stream(void) { /* Discard everything up until the next non-data chunk */ while (!encbuf_empty()) { size_t ridx; union enc_chunk_hdr *hdr = encbuf_read_ptr_incr(enc_ridx, &ridx); if (hdr && hdr->type != CHUNK_T_DATA) { if (hdr->type != CHUNK_T_STREAM_START) enc_ridx = ridx; break; } enc_ridx = ridx; } /* Try to finish header by closing and reopening the file. A seek or other operation will likely fail because buffers will need to be flushed (here and in file code). That will likely fail but a close will just close the fd and discard everything. We reopen with what actually made it to disk. Modifying existing file contents will more than likely succeed even on a full disk. The result might not be entirely correct as far as the headers' sizes and counts unless the codec can correct that but the sample format information should be. */ if (rec_fd >= 0 && open_rec_file(false)) { /* Synthesize a special end chunk here */ union enc_chunk_hdr end; end.zero = 0; end.err = 1; /* Codec should try to correct anything that's off */ end.type = CHUNK_T_STREAM_END; if (!flush_stream_end(&end)) close_rec_file(); } } /* Flush a chunk to disk * * Transitions state from REC_STATE_MONITOR to REC_STATE_FLUSH when buffer * is filling. 'margin' is fullness threshold that transitions to flush state. * * Call with REC_STATE_IDLE to indicate a forced flush which flushes buffer * to less than 'margin'. */ static enum record_state flush_chunk(enum record_state state, size_t margin) { #ifdef HAVE_PRIORITY_SCHEDULING static unsigned long prio_tick; /* Timeout for auto boost */ #endif size_t used = encbuf_used(); switch (state) { case REC_STATE_MONITOR: if (monitor_encbuf_datarate() || monitor_spinup_time()) refresh_watermarks(); if (used < margin) return REC_STATE_MONITOR; state = REC_STATE_FLUSH; trigger_cpu_boost(); #ifdef HAVE_PRIORITY_SCHEDULING prio_tick = current_tick + PRIO_SECONDS*HZ; #if (CONFIG_STORAGE & STORAGE_ATA) prio_tick += spinup_time; #endif #endif /* HAVE_PRIORITY_SCHEDULING */ /* Fall-through */ case REC_STATE_IDLE: /* As a hint for "forced" */ if (used < margin) break; /* Fall-through */ case REC_STATE_FLUSH: #ifdef HAVE_PRIORITY_SCHEDULING if (!prio_boosted && state != REC_STATE_IDLE && (used >= flood_watermark || TIME_AFTER(current_tick, prio_tick))) do_prio_boost(true); #endif /* HAVE_PRIORITY_SCHEDULING */ while (used) { union enc_chunk_hdr *hdr = encbuf_ptr(enc_ridx); size_t count = 0; switch (hdr->type) { case CHUNK_T_DATA: if (flush_stream_data(ENC_DATA_HDR(hdr))) count = CHUNK_DATA_COUNT(hdr->size); break; case CHUNK_T_STREAM_START: /* Doesn't do stream writes */ flush_stream_start(ENC_FILE_HDR(hdr)); count = hdr->size; break; case CHUNK_T_STREAM_END: if (flush_stream_end(hdr)) count = 1; break; case CHUNK_T_WRAP: enc_ridx = 0; used = encbuf_used(); continue; } if (count) enc_ridx = encbuf_add(enc_ridx, count); else discard_stream(); break; } if (!encbuf_empty()) return state; break; } if (encbuf_empty()) { do_prio_boost(false); cancel_cpu_boost(); } return REC_STATE_MONITOR; } /* Monitor buffer and finish stream, freeing-up space at the same time */ static void finish_stream(bool stopping) { size_t threshold = stopping ? 1 : enc_buflen - ENCBUF_MIN_SPLIT_MARGIN; enum record_state state = REC_STATE_MONITOR; size_t need = 1; while (1) { switch (state) { case REC_STATE_IDLE: state = flush_chunk(state, threshold); continue; default: if (!need) break; if (!stopping || pcm_buffer_empty) { need = codec_finish_stream(); if (need) { need = 2*CHUNK_DATA_COUNT(need) - 1; if (need >= enc_buflen) { need = 0; codec_stop(); threshold = 1; } else if (threshold > enc_buflen - need) { threshold = enc_buflen - need; } } } if (!need || encbuf_used() >= threshold) state = REC_STATE_IDLE; /* Start flush */ else sleep(HZ/10); /* Don't flood with pings */ continue; } break; } } /* Start a new stream, transistion to a new one or end the current one */ static void mark_stream(const char *path, enum mark_stream_action action) { if (action & MARK_STREAM_END) { size_t widx; union enc_chunk_hdr *hdr = encbuf_get_write_ptr(enc_widx, 1, &widx); hdr->type = CHUNK_T_STREAM_END; encbuf_widx_advance(widx, 1); } if (action & MARK_STREAM_START) { size_t count = CHUNK_FILE_COUNT_PATH(path); struct enc_chunk_file *file; size_t widx; if (action & MARK_STREAM_PRE) { /* Prerecord: START marker goes first or before existing data */ if (enc_ridx < count) { /* Adjust to occupy end of buffer and pad accordingly */ count += enc_ridx; enc_ridx += enc_buflen; } enc_ridx -= count; /* Won't adjust p since enc_ridx is already set as non-wrapping */ file = encbuf_get_write_ptr(enc_ridx, count, &widx); } else { /* The usual: START marker goes first or after existing data */ file = encbuf_get_write_ptr(enc_widx, count, &widx); encbuf_widx_advance(widx, count); } file->hdr.type = CHUNK_T_STREAM_START; file->hdr.size = count; strlcpy(file->path, path, MAX_PATH); } } /* Tally-up and keep the required amount of prerecord data. * Updates record stats accordingly. */ static void tally_prerecord_data(void) { unsigned long count = 0; size_t bytes = 0; unsigned long samples = 0; /* Find out how much is there */ for (size_t idx = enc_ridx; idx != enc_widx;) { struct enc_chunk_data *data = encbuf_read_ptr_incr(idx, &idx); if (!data) continue; count += CHUNK_DATA_COUNT(data->hdr.size); bytes += data->hdr.size; samples += data->pcm_count; } /* Have too much? Discard oldest data. */ unsigned long pre_samples = enc_sample_rate*pre_record_seconds; while (samples > pre_samples) { struct enc_chunk_data *data = encbuf_read_ptr_incr(enc_ridx, &enc_ridx); if (!data) continue; count -= CHUNK_DATA_COUNT(data->hdr.size); bytes -= data->hdr.size; samples -= data->pcm_count; } encbuf_rec_count = count; num_rec_bytes = bytes; num_rec_samples = samples; } /** Event handlers for recording thread **/ static int pcmrec_handle; /* Q_AUDIO_INIT_RECORDING */ static void on_init_recording(void) { send_event(RECORDING_EVENT_START, NULL); /* dummy ops with no callbacks, needed because by * default buflib buffers can be moved around which must be avoided * FIXME: This buffer should play nicer and be shrinkable/movable */ static struct buflib_callbacks dummy_ops; talk_buffer_set_policy(TALK_BUFFER_LOOSE); pcmrec_handle = core_alloc_maximum("pcmrec", &rec_buffer_size, &dummy_ops); if (pcmrec_handle <= 0) /* someone is abusing core_alloc_maximum(). Fix this evil guy instead of * trying to handle OOM without hope */ panicf("%s(): OOM\n", __func__); rec_buffer = core_get_data(pcmrec_handle); init_rec_buffers(); init_state(); pcm_init_recording(); } /* Q_AUDIO_CLOSE_RECORDING */ static void on_close_recording(void) { /* Simply shut down the recording system. Whatever wasn't saved is lost. */ codec_unload(); pcm_close_recording(); close_rec_file(); init_state(); rec_errors = 0; pcm_rec_error_clear(); /* Reset PCM to defaults */ pcm_set_frequency(HW_SAMPR_RESET | SAMPR_TYPE_REC); audio_set_output_source(AUDIO_SRC_PLAYBACK); pcm_apply_settings(); if (pcmrec_handle > 0) pcmrec_handle = core_free(pcmrec_handle); talk_buffer_set_policy(TALK_BUFFER_DEFAULT); send_event(RECORDING_EVENT_STOP, NULL); } /* Q_AUDIO_RECORDING_OPTIONS */ static void on_recording_options(struct audio_recording_options *options) { if (!options) { logf("options: option NULL!"); return; } if (record_state != REC_STATE_IDLE) { /* This would ruin things */ logf("options: still recording!"); return; } /* Stop everything else that might be running */ pcm_stop_recording(); int afmt = rec_format_afmt[options->enc_config.rec_format]; bool enc_load = true; if (codec_loaded() != AFMT_UNKNOWN) { if (get_audio_base_codec_type(enc_config.afmt) != get_audio_base_codec_type(afmt)) { /* New format, new encoder; unload this one */ codec_unload(); } else { /* Keep current encoder */ codec_stop(); enc_load = false; } } init_state(); /* Read recording options, remember the ones used elsewhere */ unsigned frequency = options->rec_frequency; rec_source = options->rec_source; num_channels = options->rec_channels == 1 ? 1 : 2; unsigned mono_mode = options->rec_mono_mode; pre_record_seconds = options->rec_prerecord_time; enc_config = options->enc_config; enc_config.afmt = afmt; queue_reply(&audio_queue, 0); /* Let caller go */ /* Pick appropriate PCM copy routine */ pcm_copyfn = memcpy; if (num_channels == 1) { static typeof (memcpy) * const copy_buffer_mono[] = { copy_buffer_mono_lr, copy_buffer_mono_l, copy_buffer_mono_r }; if (mono_mode >= ARRAYLEN(copy_buffer_mono)) mono_mode = 0; pcm_copyfn = copy_buffer_mono[mono_mode]; } /* Get the hardware samplerate to be used */ unsigned long sampr; #ifdef HAVE_SPDIF_IN if (rec_source == AUDIO_SRC_SPDIF) sampr = get_spdif_samplerate(); /* Determined by source */ else #endif /* HAVE_SPDIF_IN */ sampr = rec_freq_sampr[frequency]; update_samplerate_config(sampr); /* Set monitoring */ audio_set_output_source(rec_source); /* Apply hardware setting to start monitoring now */ pcm_apply_settings(); if (!enc_load || codec_load(-1, afmt | CODEC_TYPE_ENCODER)) { enc_cb = codec_get_enc_callback(); if (!enc_cb || !configure_encoder_stream()) { codec_unload(); return; } if (pre_record_seconds != 0) { record_status = RECORD_PRERECORDING; codec_go(); pcm_pause = false; } pcm_start_recording(); } else { logf("set rec opt: enc load failed"); raise_error_status(PCMREC_E_LOAD_ENCODER); } } /* Q_AUDIO_RECORD - start recording (not gapless) or split stream (gapless) */ static void on_record(const char *filename) { if (rec_errors) { logf("on_record: errors not cleared"); return; } if (!filename) { logf("on_record: No filename"); return; } if (codec_loaded() == AFMT_UNKNOWN) { logf("on_record: Recording options not set"); return; } logf("on_record: new file '%s'", filename); /* Copy path and let caller go */ char path[MAX_PATH]; strlcpy(path, filename, MAX_PATH); queue_reply(&audio_queue, 0); enum mark_stream_action mark_action; if (record_state == REC_STATE_IDLE) { mark_action = MARK_STREAM_START; if (pre_record_seconds) { codec_pause(); tally_prerecord_data(); mark_action = MARK_STREAM_START_PRE; } clear_warning_status(PCMREC_W_ALL & ~(PCMREC_W_SAMPR_MISMATCH|PCMREC_W_DMA)); record_state = REC_STATE_MONITOR; record_status = RECORD_RECORDING; } else { /* Already recording, just split the stream */ logf("inserting split"); mark_action = MARK_STREAM_SPLIT; finish_stream(false); reset_rec_stats(); } if (rec_errors) { pcm_pause = true; codec_stop(); reset_fifos(false); return; } mark_stream(path, mark_action); codec_go(); pcm_pause = record_status != RECORD_RECORDING; } /* Q_AUDIO_RECORD_STOP */ static void on_record_stop(void) { if (record_state == REC_STATE_IDLE) return; trigger_cpu_boost(); /* Drain encoder and PCM buffers */ pcm_pause = true; finish_stream(true); /* End stream at last data and flush end marker */ mark_stream(NULL, MARK_STREAM_END); while (flush_chunk(REC_STATE_IDLE, 1) == REC_STATE_IDLE); reset_fifos(false); bool prerecord = pre_record_seconds != 0; if (rec_errors) { codec_stop(); prerecord = false; } close_rec_file(); rec_errors = 0; record_state = REC_STATE_IDLE; record_status = prerecord ? RECORD_PRERECORDING : RECORD_STOPPED; reset_rec_stats(); if (prerecord) { codec_go(); pcm_pause = false; } } /* Q_AUDIO_RECORD_PAUSE */ static void on_record_pause(void) { if (record_status != RECORD_RECORDING) return; pcm_pause = true; record_status = RECORD_PAUSED; } /* Q_AUDIO_RECORD_RESUME */ static void on_record_resume(void) { if (record_status != RECORD_PAUSED) return; record_status = RECORD_RECORDING; pcm_pause = !!rec_errors; } /* Called by audio thread when recording is initialized */ void audio_recording_handler(struct queue_event *ev) { #ifdef HAVE_PRIORITY_SCHEDULING /* Get current priorities since they get changed */ int old_prio = thread_get_priority(audio_thread_id); int old_cod_prio = codec_thread_get_priority(); #endif LOGFQUEUE("record < Q_AUDIO_INIT_RECORDING"); on_init_recording(); while (1) { int watermark = high_watermark; switch (ev->id) { case Q_AUDIO_CLOSE_RECORDING: LOGFQUEUE("record < Q_AUDIO_CLOSE_RECORDING"); goto recording_done; case Q_AUDIO_RECORDING_OPTIONS: LOGFQUEUE("record < Q_AUDIO_RECORDING_OPTIONS"); on_recording_options((struct audio_recording_options *)ev->data); break; case Q_AUDIO_RECORD: LOGFQUEUE("record < Q_AUDIO_RECORD: %s", (const char *)ev->data); on_record((const char *)ev->data); break; case Q_AUDIO_RECORD_STOP: LOGFQUEUE("record < Q_AUDIO_RECORD_STOP"); on_record_stop(); break; case Q_AUDIO_RECORD_PAUSE: LOGFQUEUE("record < Q_AUDIO_RECORD_PAUSE"); on_record_pause(); break; case Q_AUDIO_RECORD_RESUME: LOGFQUEUE("record < Q_AUDIO_RECORD_RESUME"); on_record_resume(); break; case Q_AUDIO_RECORD_FLUSH: watermark = 1; break; case SYS_USB_CONNECTED: LOGFQUEUE("record < SYS_USB_CONNECTED"); if (record_state != REC_STATE_IDLE) { LOGFQUEUE(" still recording"); break; } goto recording_done; } /* switch */ int timeout; switch (record_state) { case REC_STATE_FLUSH: case REC_STATE_MONITOR: do record_state = flush_chunk(record_state, watermark); while (record_state == REC_STATE_FLUSH && queue_empty(&audio_queue)); timeout = record_state == REC_STATE_FLUSH ? HZ*0 : HZ*FLUSH_MON_INTERVAL; break; case REC_STATE_IDLE: #ifdef HAVE_SPDIF_IN if (rec_source == AUDIO_SRC_SPDIF) { check_spdif_samplerate(); timeout = HZ/2; break; } #endif /* HAVE_SPDIF_IN */ default: timeout = TIMEOUT_BLOCK; break; } queue_wait_w_tmo(&audio_queue, ev, timeout); } /* while */ recording_done: on_close_recording(); #ifdef HAVE_PRIORITY_SCHEDULING /* Restore normal thread priorities */ thread_set_priority(audio_thread_id, old_prio); codec_thread_set_priority(old_cod_prio); #endif } /** Encoder callbacks **/ /* Read a block of unprocessed PCM data, with mono conversion if * num_channels == 1 * * NOTE: Request must be less than the PCM buffer length in samples in order * to progress. * (ie. count <= PCM_NUM_CHUNKS*PCM_CHUNK_SAMP) */ static int enc_pcmbuf_read(void *buffer, int count) { size_t avail = pcmbuf_used(); size_t size = count*PCM_SAMP_SIZE; if (count > 0 && avail >= size) { size_t endidx = pcm_ridx + size; if (endidx > PCM_BUF_SIZE) { size_t wrap = endidx - PCM_BUF_SIZE; size_t offset = size -= wrap; if (num_channels == 1) offset /= 2; /* src offset -> dst offset */ pcm_copyfn(buffer + offset, pcmbuf_ptr(0), wrap); } pcm_copyfn(buffer, pcmbuf_ptr(pcm_ridx), size); if (avail >= sample_rate*PCM_SAMP_SIZE*PCM_BOOST_SECONDS || avail >= PCM_BUF_SIZE*1/2) { /* Filling up - boost threshold data available or more or 1/2 full or more - boost codec */ trigger_cpu_boost(); } pcm_buffer_empty = false; return count; } /* Not enough data available - encoder should idle */ pcm_buffer_empty = true; cancel_cpu_boost(); /* Sleep a little bit */ sleep(0); return 0; } /* Advance PCM buffer by count samples */ static int enc_pcmbuf_advance(int count) { if (count <= 0) return 0; size_t avail = pcmbuf_used(); size_t size = count*PCM_SAMP_SIZE; if (avail < size) { size = avail; count = size / PCM_SAMP_SIZE; } pcm_ridx = pcmbuf_add(pcm_ridx, size); return count; } /* Return encoder chunk at current write position, wrapping to 0 if * requested size demands it. * * NOTE: No request should be more than 1/2 the buffer length, all elements * included, or progress will not be guaranteed. * (ie. CHUNK_DATA_COUNT(need) <= enc_buflen / 2) */ static struct enc_chunk_data * enc_encbuf_get_buffer(size_t need) { /* Convert to buffer slot count, including the header */ need = CHUNK_DATA_COUNT(need); enum record_state state = record_state; size_t avail = encbuf_free(); /* Must have the split margin as well but it does not have to be continuous with the request */ while (avail <= need + ENCBUF_MIN_SPLIT_MARGIN || (enc_widx + need > enc_buflen && enc_ridx <= need + ENCBUF_MIN_SPLIT_MARGIN)) { if (UNLIKELY(state == REC_STATE_IDLE)) { /* Prerecording - delete some old data */ size_t ridx; struct enc_chunk_data *data = encbuf_read_ptr_incr(enc_ridx, &ridx); if (data) { encbuf_rec_count -= CHUNK_DATA_COUNT(data->hdr.size); num_rec_bytes -= data->hdr.size; num_rec_samples -= data->pcm_count; } enc_ridx = ridx; avail = encbuf_free(); continue; } else if (avail == enc_buflen) { /* Empty but request larger than any possible space */ raise_warning_status(PCMREC_W_ENC_BUFFER_OVF); } else if (state != REC_STATE_FLUSH && encbuf_used() < high_watermark) { /* Not yet even at high watermark but what's needed won't fit */ encbuf_request_flush(); } sleep(0); return NULL; } struct enc_chunk_data *data = encbuf_get_write_ptr(enc_widx, need, &enc_widx); if (state == REC_STATE_IDLE) data->hdr.pre = 1; return data; } /* Releases the current buffer into the available chunks */ static void enc_encbuf_finish_buffer(void) { struct enc_chunk_data *data = ENC_DATA_HDR(encbuf_ptr(enc_widx)); if (data->hdr.err) { /* Encoder set error flag */ raise_error_status(PCMREC_E_ENCODER); return; } size_t data_size = data->hdr.size; if (data_size == 0) return; /* Claims nothing was written */ size_t count = CHUNK_DATA_COUNT(data_size); size_t avail = encbuf_free(); if (avail <= count || enc_widx + count > enc_buflen) { /* Claims it wrote too much? */ raise_warning_status(PCMREC_W_ENC_BUFFER_OVF); return; } if (num_rec_bytes + data_size > MAX_NUM_REC_BYTES) { /* Would exceed filesize limit; should have split sooner. This chunk will be dropped. :'( */ raise_warning_status(PCMREC_W_FILE_SIZE); return; } encbuf_widx_advance(enc_widx, count); encbuf_rec_count += count; num_rec_bytes += data_size; num_rec_samples += data->pcm_count; } /* Read from the output stream */ static ssize_t enc_stream_read(void *buf, size_t count) { if (!stream_flush_buf()) return -1; return read(rec_fd, buf, count); } /* Seek the output steam */ static off_t enc_stream_lseek(off_t offset, int whence) { if (!stream_flush_buf()) return -1; return lseek(rec_fd, offset, whence); } /* Write to the output stream */ static ssize_t enc_stream_write(const void *buf, size_t count) { if (UNLIKELY(count >= STREAM_BUF_SIZE)) { /* Too big to buffer */ if (stream_flush_buf()) return write(rec_fd, buf, count); } if (!count) return 0; if (stream_buf_used + count > STREAM_BUF_SIZE) { if (!stream_flush_buf() && stream_buf_used + count > STREAM_BUF_SIZE) count = STREAM_BUF_SIZE - stream_buf_used; } memcpy(stream_buffer + stream_buf_used, buf, count); stream_buf_used += count; return count; } /* One-time init at startup */ void INIT_ATTR recording_init(void) { /* Init API */ ci.enc_pcmbuf_read = enc_pcmbuf_read; ci.enc_pcmbuf_advance = enc_pcmbuf_advance; ci.enc_encbuf_get_buffer = enc_encbuf_get_buffer; ci.enc_encbuf_finish_buffer = enc_encbuf_finish_buffer; ci.enc_stream_read = enc_stream_read; ci.enc_stream_lseek = enc_stream_lseek; ci.enc_stream_write = enc_stream_write; }