e8faf2f2ad
There are various allocations that can't be moved or shrunk. Provide a global callback struct for this use case instead of making each caller declare its own dummy struct. Also fixed ROLO and x1000 installer code which incorrectly used movable allocations. Change-Id: I00088396b9826e02e69a4a33477fe1a7816374f1
2028 lines
54 KiB
C
2028 lines
54 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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 = (int16_t*) dst;
|
|
int16_t const *s = (int16_t const*) src;
|
|
ssize_t copy_size = src_size;
|
|
|
|
/* mono = (L + R) / 2 */
|
|
while(copy_size > 0) {
|
|
*d++ = ((int32_t)s[0] + (int32_t)s[1] + 1) >> 1;
|
|
s += 2;
|
|
copy_size -= PCM_SAMP_SIZE;
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
static void * ICODE_ATTR
|
|
copy_buffer_mono_l(void *dst, const void *src, size_t src_size)
|
|
{
|
|
int16_t *d = (int16_t*) dst;
|
|
int16_t const *s = (int16_t const*) src;
|
|
ssize_t copy_size = src_size;
|
|
|
|
/* mono = L */
|
|
while(copy_size > 0) {
|
|
*d++ = *s;
|
|
s += 2;
|
|
copy_size -= PCM_SAMP_SIZE;
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
static void * ICODE_ATTR
|
|
copy_buffer_mono_r(void *dst, const void *src, size_t src_size)
|
|
{
|
|
return copy_buffer_mono_l(dst, src + 2, src_size);
|
|
}
|
|
|
|
|
|
/** 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);
|
|
/* FIXME: This buffer should play nicer and be shrinkable/movable */
|
|
talk_buffer_set_policy(TALK_BUFFER_LOOSE);
|
|
pcmrec_handle = core_alloc_maximum("pcmrec", &rec_buffer_size, &buflib_ops_locked);
|
|
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;
|
|
}
|