Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1 | /*************************************************************************** |
| 2 | * __________ __ ___. |
| 3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| 4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| 5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| 6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| 7 | * \/ \/ \/ \/ \/ |
| 8 | * $Id$ |
| 9 | * |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 10 | * Copyright (C) 2005 Linus Nielsen Feltzing |
| 11 | * Copyright (C) 2006 Antonius Hellmann |
| 12 | * Copyright (C) 2006-2013 Michael Sevakis |
| 13 | * |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 14 | * |
Daniel Stenberg | 2acc0ac | 2008-06-28 18:10:04 +0000 | [diff] [blame] | 15 | * This program is free software; you can redistribute it and/or |
| 16 | * modify it under the terms of the GNU General Public License |
| 17 | * as published by the Free Software Foundation; either version 2 |
| 18 | * of the License, or (at your option) any later version. |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 19 | * |
| 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| 21 | * KIND, either express or implied. |
| 22 | * |
| 23 | ****************************************************************************/ |
Thomas Martitz | 0e2286f | 2010-08-12 13:38:25 +0000 | [diff] [blame] | 24 | #include "config.h" |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 25 | #include "system.h" |
| 26 | #include "kernel.h" |
Thomas Martitz | 22e802e | 2013-05-30 11:24:16 +0200 | [diff] [blame] | 27 | #include "panic.h" |
Michael Sevakis | 59abdb5 | 2013-06-29 19:27:21 -0400 | [diff] [blame] | 28 | #include "string-extra.h" |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 29 | #include "pcm_record.h" |
| 30 | #include "codecs.h" |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 31 | #include "logf.h" |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 32 | #include "thread.h" |
Frank Gevaerts | 2f8a008 | 2008-11-01 16:14:28 +0000 | [diff] [blame] | 33 | #include "storage.h" |
Michael Sevakis | 36c9405 | 2006-12-17 11:03:19 +0000 | [diff] [blame] | 34 | #include "general.h" |
Michael Sevakis | 6510973 | 2011-02-23 14:31:13 +0000 | [diff] [blame] | 35 | #include "codec_thread.h" |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 36 | #include "audio.h" |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 37 | #include "sound.h" |
Björn Stenberg | 51b45d5 | 2008-10-15 06:38:51 +0000 | [diff] [blame] | 38 | #include "metadata.h" |
Jonathan Gordon | 7d5e0d7 | 2010-05-16 11:13:42 +0000 | [diff] [blame] | 39 | #include "appevents.h" |
Michael Sevakis | cc50c14 | 2006-11-13 23:21:54 +0000 | [diff] [blame] | 40 | #ifdef HAVE_SPDIF_IN |
| 41 | #include "spdif.h" |
| 42 | #endif |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 43 | #include "audio_thread.h" |
Thomas Martitz | 22e802e | 2013-05-30 11:24:16 +0200 | [diff] [blame] | 44 | #include "core_alloc.h" |
| 45 | #include "talk.h" |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 46 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 47 | /* Macros to enable logf for queues |
| 48 | logging on SYS_TIMEOUT can be disabled */ |
| 49 | #ifdef SIMULATOR |
| 50 | /* Define this for logf output of all queuing except SYS_TIMEOUT */ |
| 51 | #define PCMREC_LOGQUEUES |
| 52 | /* Define this to logf SYS_TIMEOUT messages */ |
| 53 | /*#define PCMREC_LOGQUEUES_SYS_TIMEOUT*/ |
| 54 | #endif /* SIMULATOR */ |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 55 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 56 | #ifdef PCMREC_LOGQUEUES |
| 57 | #define LOGFQUEUE logf |
| 58 | #else |
| 59 | #define LOGFQUEUE(...) |
Michael Sevakis | 7d00f97 | 2006-11-29 14:23:24 +0000 | [diff] [blame] | 60 | #endif |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 61 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 62 | #ifdef PCMREC_LOGQUEUES_SYS_TIMEOUT |
| 63 | #define LOGFQUEUE_SYS_TIMEOUT logf |
| 64 | #else |
| 65 | #define LOGFQUEUE_SYS_TIMEOUT(...) |
| 66 | #endif |
| 67 | |
| 68 | /** Target-related configuration **/ |
| 69 | |
| 70 | /** |
| 71 | * PCM_NUM_CHUNKS: Number of PCM chunks |
| 72 | * PCM_CHUNK_SAMP: Number of samples in a PCM chunk |
| 73 | * PCM_BOOST_SECONDS: PCM level at which to boost CPU |
| 74 | * PANIC_SECONDS: Flood watermark time until full |
| 75 | * FLUSH_SECONDS: Flush watermark time until full |
| 76 | * STREAM_BUF_SIZE: Size of stream write buffer |
| 77 | * PRIO_SECONDS: Max flush time before prio boost |
| 78 | * |
| 79 | * Total PCM buffer size should be mem aligned |
| 80 | * |
| 81 | * Fractions should be left without parentheses so the multiplier is |
| 82 | * multiplied by the numerator first. |
| 83 | */ |
| 84 | #if MEMORYSIZE <= 2 |
| 85 | #define PCM_NUM_CHUNKS 56 |
| 86 | #define PCM_CHUNK_SAMP 1024 |
| 87 | #define PCM_BOOST_SECONDS 1/2 |
| 88 | #define PANIC_SECONDS 1/2 |
| 89 | #define FLUSH_SECONDS 1 |
| 90 | #define FLUSH_MON_INTERVAL 1/6 |
| 91 | #define STREAM_BUF_SIZE 32768 |
| 92 | #elif MEMORYSIZE <= 16 |
| 93 | #define PANIC_SECONDS 5 |
| 94 | #define FLUSH_SECONDS 7 |
| 95 | #else /* MEMORYSIZE > 16 */ |
| 96 | #define PANIC_SECONDS 8 |
| 97 | #define FLUSH_SECONDS 10 |
| 98 | #endif /* MEMORYSIZE */ |
| 99 | |
| 100 | /* Default values if not overridden above */ |
| 101 | #ifndef PCM_NUM_CHUNKS |
| 102 | #define PCM_NUM_CHUNKS 256 |
| 103 | #endif |
| 104 | #ifndef PCM_CHUNK_SAMP |
| 105 | #define PCM_CHUNK_SAMP 2048 |
| 106 | #endif |
| 107 | #ifndef PCM_BOOST_SECONDS |
| 108 | #define PCM_BOOST_SECONDS 1 |
| 109 | #endif |
| 110 | #ifndef FLUSH_MON_INTERVAL |
| 111 | #define FLUSH_MON_INTERVAL 1/4 |
| 112 | #endif |
| 113 | #ifndef STREAM_BUF_SIZE |
| 114 | #define STREAM_BUF_SIZE 65536 |
| 115 | #endif |
| 116 | #ifndef PRIO_SECONDS |
| 117 | #define PRIO_SECONDS 10 |
| 118 | #endif |
| 119 | |
| 120 | /* FAT limit for filesize. Recording will accept no further data from the |
| 121 | * codec if this limit is reached in order to preserve its own data |
| 122 | * integrity. A split should have made by the higher-ups long before this |
| 123 | * point. |
| 124 | * |
| 125 | * Leave a generous 64k margin for metadata being added to file. */ |
| 126 | #define MAX_NUM_REC_BYTES ((size_t)0x7fff0000u) |
| 127 | |
| 128 | /***************************************************************************/ |
| 129 | extern struct codec_api ci; /* in codec_thread.c */ |
| 130 | extern struct event_queue audio_queue; /* in audio_thread.c */ |
| 131 | extern unsigned int audio_thread_id; /* in audio_thread.c */ |
| 132 | |
| 133 | /** General recording state **/ |
| 134 | |
| 135 | /* Recording action being performed */ |
| 136 | static enum record_status |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 137 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 138 | RECORD_STOPPED = 0, |
| 139 | RECORD_PRERECORDING = AUDIO_STATUS_PRERECORD, |
| 140 | RECORD_RECORDING = AUDIO_STATUS_RECORD, |
| 141 | RECORD_PAUSED = (AUDIO_STATUS_RECORD | AUDIO_STATUS_PAUSE), |
| 142 | } record_status = RECORD_STOPPED; |
| 143 | |
| 144 | /* State of engine operations */ |
| 145 | static enum record_state |
| 146 | { |
| 147 | REC_STATE_IDLE, /* Stopped or prerecording */ |
| 148 | REC_STATE_MONITOR, /* Monitoring buffer status */ |
| 149 | REC_STATE_FLUSH, /* Flushing buffer */ |
| 150 | } record_state = REC_STATE_IDLE; |
| 151 | |
| 152 | static uint32_t errors; /* An error has occured (bitmask) */ |
| 153 | static uint32_t warnings; /* Non-fatal warnings (bitmask) */ |
| 154 | |
| 155 | static uint32_t rec_errors; /* Mirror of errors but private to |
| 156 | * avoid race with controlling |
| 157 | * thread. Engine uses this |
| 158 | * internally. */ |
| 159 | |
| 160 | /** Stats on encoded data for current file **/ |
| 161 | static int rec_fd = -1; /* Currently open file descriptor */ |
| 162 | static size_t num_rec_bytes; /* Number of bytes recorded */ |
| 163 | static uint64_t num_rec_samples; /* Number of PCM samples recorded */ |
| 164 | static uint64_t encbuf_rec_count; /* Count of slots written to buffer |
| 165 | for current file */ |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 166 | |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 167 | /** These apply to current settings **/ |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 168 | static int rec_source; /* Current rec_source setting */ |
| 169 | static unsigned long sample_rate; /* Samplerate setting in HZ */ |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 170 | static int num_channels; /* Current number of channels */ |
| 171 | static struct encoder_config enc_config; /* Current encoder configuration */ |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 172 | static unsigned int pre_record_seconds; /* Pre-record time in seconds */ |
Michael Sevakis | a9ea1a4 | 2013-06-22 16:39:40 -0400 | [diff] [blame] | 173 | |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 174 | /**************************************************************************** |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 175 | Use 2 circular buffers: |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 176 | pcm_buffer=DMA output buffer: chunks (8192 Bytes) of raw pcm audio data |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 177 | enc_buffer=encoded audio buffer: storage for encoder output data |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 178 | |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 179 | Flow: |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 180 | 1. When entering recording_screen DMA feeds the ringbuffer pcm_buffer |
| 181 | 2. If enough pcm data are available the encoder codec does encoding of pcm |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 182 | chunks (4-8192 Bytes) into ringbuffer enc_buffer in codec_thread |
| 183 | 3. pcmrec_callback detects enc_buffer 'near full' and writes data to disk |
| 184 | |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 185 | Functions calls (basic encoder steps): |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 186 | 1.audio: codec_load(); load the encoder |
| 187 | 2.encoder: enc_init_parameters(); set the encoder parameters (at load) |
| 188 | 3.audio: enc_callback(); configure encoder recording settings |
| 189 | 4.audio: codec_go(); start encoding the new stream |
| 190 | 5.encoder: enc_encbuf_get_buffer(); obtain an output buffer of size n |
| 191 | 6.encoder: enc_pcmbuf_read(); read n bytes of unprocessed pcm data |
| 192 | 7.encoder: enc_encbuf_finish_buffer(); add the obtained buffer to output |
| 193 | 8.encoder: enc_pcmbuf_advance(); advance pcm by n samples |
| 194 | 9.encoder: while more PCM available, repeat 5. to 9. |
| 195 | 10.audio: codec_finish_stream(); finish the output for current stream |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 196 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 197 | Function calls (basic stream flushing steps through enc_callback()): |
| 198 | 1.audio: flush_stream_start(); stream flush destination is opening |
| 199 | 2.audio: flush_stream_data(); flush encoded audio to stream |
| 200 | 3.audio: while encoded data available, repeat 2. |
| 201 | 4.audio: flush_stream_end(); stream flush destination is closing |
| 202 | |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 203 | ****************************************************************************/ |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 204 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 205 | /** Buffer parameters where incoming PCM data is placed **/ |
| 206 | #define PCM_DEPTH_BYTES (sizeof (int16_t)) |
| 207 | #define PCM_SAMP_SIZE (2*PCM_DEPTH_BYTES) |
| 208 | #define PCM_CHUNK_SIZE (PCM_CHUNK_SAMP*PCM_SAMP_SIZE) |
| 209 | #define PCM_BUF_SIZE (PCM_NUM_CHUNKS*PCM_CHUNK_SIZE) |
| 210 | |
| 211 | /* Convert byte sizes into buffer slot counts */ |
| 212 | #define CHUNK_SIZE_COUNT(size) \ |
| 213 | (((size) + ENC_HDR_SIZE - 1) / ENC_HDR_SIZE) |
| 214 | #define CHUNK_FILE_COUNT(size) \ |
| 215 | ({ typeof (size) __size = (size); \ |
| 216 | CHUNK_SIZE_COUNT(MIN(__size, MAX_PATH) + ENC_HDR_SIZE); }) |
| 217 | #define CHUNK_FILE_COUNT_PATH(path) \ |
| 218 | CHUNK_FILE_COUNT(strlen(path) + 1) |
| 219 | #define CHUNK_DATA_COUNT(size) \ |
| 220 | CHUNK_SIZE_COUNT((size) + sizeof (struct enc_chunk_data)) |
| 221 | |
| 222 | /* Min margin to write stream split headers without overwrap risk */ |
| 223 | #define ENCBUF_MIN_SPLIT_MARGIN \ |
| 224 | (2*(1 + CHUNK_FILE_COUNT(MAX_PATH)) - 1) |
| 225 | |
| 226 | static void *rec_buffer; /* Root rec buffer pointer */ |
| 227 | static size_t rec_buffer_size; /* Root rec buffer size */ |
| 228 | |
| 229 | static void *pcm_buffer; /* Circular buffer for PCM samples */ |
| 230 | static volatile bool pcm_pause; /* Freeze DMA write position */ |
| 231 | static volatile size_t pcm_widx; /* Current DMA write position */ |
| 232 | static volatile size_t pcm_ridx; /* Current PCM read position */ |
| 233 | |
| 234 | static union enc_chunk_hdr *enc_buffer; /* Circular encoding buffer */ |
| 235 | static size_t enc_widx; /* Encoder chunk write index */ |
| 236 | static size_t enc_ridx; /* Encoder chunk read index */ |
| 237 | static size_t enc_buflen; /* Length of buffer in slots */ |
| 238 | |
| 239 | static unsigned char *stream_buffer; /* Stream-to-disk write buffer */ |
| 240 | static ssize_t stream_buf_used; /* Stream write buffer occupancy */ |
| 241 | |
| 242 | static struct enc_chunk_file *fname_buf;/* Buffer with next file to create */ |
| 243 | |
| 244 | static unsigned long enc_sample_rate; /* Samplerate used by encoder */ |
| 245 | static bool pcm_buffer_empty; /* All PCM chunks processed? */ |
| 246 | |
| 247 | static typeof (memcpy) *pcm_copyfn; /* PCM memcpy or copy_buffer_mono */ |
| 248 | static enc_callback_t enc_cb; /* Encoder's recording callback */ |
| 249 | |
| 250 | /** File flushing **/ |
| 251 | static unsigned long encbuf_datarate; /* Rate of data per second */ |
| 252 | #if (CONFIG_STORAGE & STORAGE_ATA) |
| 253 | static int spinup_time; /* Last spinup time */ |
Rafaël Carré | 020dddf | 2010-05-14 11:08:15 +0000 | [diff] [blame] | 254 | #endif |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 255 | static size_t high_watermark; /* Max limit for data flush */ |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 256 | |
Michael Sevakis | 0d094bd | 2007-01-31 06:43:31 +0000 | [diff] [blame] | 257 | #ifdef HAVE_PRIORITY_SCHEDULING |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 258 | static size_t flood_watermark; /* Max limit for thread prio boost */ |
| 259 | static bool prio_boosted; |
Michael Sevakis | 0d094bd | 2007-01-31 06:43:31 +0000 | [diff] [blame] | 260 | #endif |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 261 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 262 | /** Stream marking **/ |
| 263 | enum mark_stream_action |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 264 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 265 | MARK_STREAM_END = 0x1, /* Mark end current stream */ |
| 266 | MARK_STREAM_START = 0x2, /* Mark start of new stream */ |
| 267 | MARK_STREAM_SPLIT = 0x3, /* Insert split; orr of above values */ |
| 268 | MARK_STREAM_PRE = 0x4, /* Do prerecord data tally */ |
| 269 | MARK_STREAM_START_PRE = MARK_STREAM_PRE | MARK_STREAM_START, |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 270 | }; |
| 271 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 272 | |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 273 | /***************************************************************************/ |
| 274 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 275 | /* Buffer pointer (p) to PCM sample memory address */ |
| 276 | static inline void * pcmbuf_ptr(size_t p) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 277 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 278 | return pcm_buffer + p; |
| 279 | } |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 280 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 281 | /* Buffer pointer (p) plus value (v), wrapped if necessary */ |
| 282 | static size_t pcmbuf_add(size_t p, size_t v) |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 283 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 284 | size_t res = p + v; |
| 285 | |
| 286 | if (res >= PCM_BUF_SIZE) |
| 287 | res -= PCM_BUF_SIZE; |
| 288 | |
| 289 | return res; |
| 290 | } |
| 291 | |
| 292 | /* Size of data in PCM buffer */ |
| 293 | size_t pcmbuf_used(void) |
| 294 | { |
| 295 | size_t p1 = pcm_ridx; |
| 296 | size_t p2 = pcm_widx; |
| 297 | |
| 298 | if (p1 > p2) |
| 299 | p2 += PCM_BUF_SIZE; |
| 300 | |
| 301 | return p2 - p1; |
| 302 | } |
| 303 | |
| 304 | /* Buffer pointer (p) to memory address of header */ |
| 305 | static inline union enc_chunk_hdr * encbuf_ptr(size_t p) |
| 306 | { |
| 307 | return enc_buffer + p; |
| 308 | } |
| 309 | |
| 310 | /* Buffer pointer (p) plus value (v), wrapped if necessary */ |
| 311 | static size_t encbuf_add(size_t p, size_t v) |
| 312 | { |
| 313 | size_t res = p + v; |
| 314 | |
| 315 | if (res >= enc_buflen) |
| 316 | res -= enc_buflen; |
| 317 | |
| 318 | return res; |
| 319 | } |
| 320 | |
| 321 | /* Number of free buffer slots */ |
| 322 | static size_t encbuf_free(void) |
| 323 | { |
| 324 | size_t p1 = enc_ridx; |
| 325 | size_t p2 = enc_widx; |
| 326 | |
| 327 | if (p2 >= p1) |
| 328 | p1 += enc_buflen; |
| 329 | |
| 330 | return p1 - p2; |
| 331 | } |
| 332 | |
| 333 | /* Number of used buffer slots */ |
| 334 | static size_t encbuf_used(void) |
| 335 | { |
| 336 | size_t p1 = enc_ridx; |
| 337 | size_t p2 = enc_widx; |
| 338 | |
| 339 | if (p1 > p2) |
| 340 | p2 += enc_buflen; |
| 341 | |
| 342 | return p2 - p1; |
| 343 | } |
| 344 | |
| 345 | /* Is the encoder buffer empty? */ |
| 346 | static bool encbuf_empty(void) |
| 347 | { |
| 348 | return enc_ridx == enc_widx; |
| 349 | } |
| 350 | |
| 351 | /* Buffer pointer (p) plus size (v), written to enc_widx, new widx |
| 352 | * zero-initialized */ |
| 353 | static void encbuf_widx_advance(size_t widx, size_t v) |
| 354 | { |
| 355 | widx = encbuf_add(widx, v); |
| 356 | encbuf_ptr(widx)->zero = 0; |
| 357 | enc_widx = widx; |
| 358 | } |
| 359 | |
| 360 | /* Buffer pointer (p) plus size of chunk at (p), wrapped to (0) if |
| 361 | * necessary. |
| 362 | * |
| 363 | * pout points to variable to receive increment result |
| 364 | * |
| 365 | * Returns NULL if it was a wrap marker */ |
| 366 | static void * encbuf_read_ptr_incr(size_t p, size_t *pout) |
| 367 | { |
| 368 | union enc_chunk_hdr *hdr = encbuf_ptr(p); |
| 369 | size_t v; |
| 370 | |
| 371 | switch (hdr->type) |
| 372 | { |
| 373 | case CHUNK_T_DATA: |
| 374 | v = CHUNK_DATA_COUNT(hdr->size); |
| 375 | break; |
| 376 | case CHUNK_T_STREAM_START: |
| 377 | v = hdr->size; |
| 378 | break; |
| 379 | case CHUNK_T_STREAM_END: |
| 380 | default: |
| 381 | v = 1; |
| 382 | break; |
| 383 | case CHUNK_T_WRAP: |
| 384 | /* Wrap markers are not returned but caller may have to know that |
| 385 | the index was changed since it impacts available space */ |
| 386 | *pout = 0; |
| 387 | return NULL; |
| 388 | } |
| 389 | |
| 390 | *pout = encbuf_add(p, v); |
| 391 | return hdr; |
| 392 | } |
| 393 | |
| 394 | /* Buffer pointer (p) of contiguous free space (v), wrapped to (0) if |
| 395 | * necessary. |
| 396 | * |
| 397 | * pout points to variable to receive possible-adjusted p |
| 398 | * |
| 399 | * Returns header at (p) or wrapped header at (0) if wrap was |
| 400 | * required in order to provide contiguous space. Header is zero- |
| 401 | * initialized. |
| 402 | * |
| 403 | * Marks the wrap point if a wrap is required to make the allocation. */ |
| 404 | static void * encbuf_get_write_ptr(size_t p, size_t v, size_t *pout) |
| 405 | { |
| 406 | union enc_chunk_hdr *hdr = encbuf_ptr(p); |
| 407 | |
| 408 | if (p + v > enc_buflen) |
| 409 | { |
| 410 | hdr->type = CHUNK_T_WRAP; /* All other fields ignored */ |
| 411 | p = 0; |
| 412 | hdr = encbuf_ptr(0); |
| 413 | } |
| 414 | |
| 415 | *pout = p; |
| 416 | hdr->zero = 0; |
| 417 | return hdr; |
| 418 | } |
| 419 | |
| 420 | /* Post a flush request to audio thread, if none is currently queued */ |
| 421 | static void encbuf_request_flush(void) |
| 422 | { |
| 423 | if (!queue_peek_ex(&audio_queue, NULL, 0, |
| 424 | &(const long [2]){ Q_AUDIO_RECORD_FLUSH, |
| 425 | Q_AUDIO_RECORD_FLUSH })) |
| 426 | queue_post(&audio_queue, Q_AUDIO_RECORD_FLUSH, 0); |
| 427 | } |
| 428 | |
| 429 | /* Set the error bits in (e): no lock */ |
| 430 | static inline void set_error_bits(uint32_t e) |
| 431 | { |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 432 | errors |= e; |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 433 | rec_errors |= e; |
| 434 | } |
| 435 | |
| 436 | /* Clear the error bits in (e): no lock */ |
| 437 | static inline void clear_error_bits(uint32_t e) |
| 438 | { |
| 439 | errors &= ~e; |
| 440 | } |
| 441 | |
| 442 | /* Set the error bits in (e) */ |
| 443 | static void raise_error_status(uint32_t e) |
| 444 | { |
| 445 | pcm_rec_lock(); |
| 446 | set_error_bits(e); |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 447 | pcm_rec_unlock(); |
| 448 | } |
| 449 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 450 | /* Clear the error bits in (e) */ |
| 451 | static void clear_error_status(uint32_t e) |
| 452 | { |
| 453 | pcm_rec_lock(); |
| 454 | clear_error_bits(e); |
| 455 | pcm_rec_unlock(); |
| 456 | } |
| 457 | |
| 458 | /* Set the warning bits in (w): no lock */ |
| 459 | static inline void set_warning_bits(uint32_t w) |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 460 | { |
| 461 | warnings |= w; |
| 462 | } |
Michael Sevakis | a9ea1a4 | 2013-06-22 16:39:40 -0400 | [diff] [blame] | 463 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 464 | /* Clear the warning bits in (w): no lock */ |
| 465 | static inline void clear_warning_bits(uint32_t w) |
| 466 | { |
| 467 | warnings &= ~w; |
| 468 | } |
| 469 | |
| 470 | /* Set the warning bits in (w) */ |
| 471 | static void raise_warning_status(uint32_t w) |
| 472 | { |
| 473 | pcm_rec_lock(); |
| 474 | set_warning_bits(w); |
| 475 | pcm_rec_unlock(); |
| 476 | } |
| 477 | |
| 478 | /* Clear the warning bits in (w) */ |
| 479 | static void clear_warning_status(uint32_t w) |
| 480 | { |
| 481 | pcm_rec_lock(); |
| 482 | clear_warning_bits(w); |
| 483 | pcm_rec_unlock(); |
| 484 | } |
| 485 | |
| 486 | /* Callback for when more data is ready - called by DMA ISR */ |
Michael Sevakis | 286a4c5 | 2012-02-23 08:14:46 -0500 | [diff] [blame] | 487 | static void pcm_rec_have_more(void **start, size_t *size) |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 488 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 489 | size_t next_idx = pcm_widx; |
| 490 | |
| 491 | if (!pcm_pause) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 492 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 493 | /* One empty chunk must remain after widx is advanced */ |
| 494 | if (pcmbuf_used() <= PCM_BUF_SIZE - 2*PCM_CHUNK_SIZE) |
| 495 | next_idx = pcmbuf_add(next_idx, PCM_CHUNK_SIZE); |
| 496 | else |
| 497 | set_warning_bits(PCMREC_W_PCM_BUFFER_OVF); |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 498 | } |
Michael Sevakis | 51189b4 | 2006-12-06 08:34:55 +0000 | [diff] [blame] | 499 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 500 | *start = pcmbuf_ptr(next_idx); |
Michael Sevakis | d569998 | 2010-05-24 16:42:32 +0000 | [diff] [blame] | 501 | *size = PCM_CHUNK_SIZE; |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 502 | |
| 503 | pcm_widx = next_idx; |
| 504 | } |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 505 | |
Michael Sevakis | 286a4c5 | 2012-02-23 08:14:46 -0500 | [diff] [blame] | 506 | static enum pcm_dma_status pcm_rec_status_callback(enum pcm_dma_status status) |
| 507 | { |
| 508 | if (status < PCM_DMAST_OK) |
| 509 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 510 | /* Some error condition */ |
Michael Sevakis | 286a4c5 | 2012-02-23 08:14:46 -0500 | [diff] [blame] | 511 | if (status == PCM_DMAST_ERR_DMA) |
| 512 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 513 | set_error_bits(PCMREC_E_DMA); |
Michael Sevakis | 286a4c5 | 2012-02-23 08:14:46 -0500 | [diff] [blame] | 514 | return status; |
| 515 | } |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 516 | else |
| 517 | { |
| 518 | /* Try again next transmission - frame is invalid */ |
| 519 | set_warning_bits(PCMREC_W_DMA); |
| 520 | } |
Michael Sevakis | 286a4c5 | 2012-02-23 08:14:46 -0500 | [diff] [blame] | 521 | } |
| 522 | |
| 523 | return PCM_DMAST_OK; |
Michael Sevakis | 0d768a3 | 2006-12-07 19:34:26 +0000 | [diff] [blame] | 524 | } |
| 525 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 526 | /* Start DMA transfer */ |
| 527 | static void pcm_start_recording(void) |
| 528 | { |
| 529 | pcm_record_data(pcm_rec_have_more, pcm_rec_status_callback, |
| 530 | pcmbuf_ptr(pcm_widx), PCM_CHUNK_SIZE); |
| 531 | } |
| 532 | |
| 533 | /* Initialize the various recording buffers */ |
| 534 | static void init_rec_buffers(void) |
| 535 | { |
| 536 | /* Layout of recording buffer: |PCMBUF|STREAMBUF|FILENAME|ENCBUF| */ |
| 537 | void *buf = rec_buffer; |
| 538 | size_t size = rec_buffer_size; |
| 539 | |
| 540 | /* PCMBUF */ |
| 541 | pcm_buffer = CACHEALIGN_UP(buf); /* Line align */ |
| 542 | size -= pcm_buffer + PCM_BUF_SIZE - buf; |
| 543 | buf = pcm_buffer + PCM_BUF_SIZE; |
| 544 | |
| 545 | /* STREAMBUF */ |
| 546 | stream_buffer = buf; /* Also line-aligned */ |
| 547 | buf += STREAM_BUF_SIZE; |
| 548 | size -= STREAM_BUF_SIZE; |
| 549 | |
| 550 | /* FILENAME */ |
| 551 | fname_buf = buf; |
| 552 | buf += CHUNK_FILE_COUNT(MAX_PATH)*ENC_HDR_SIZE; |
| 553 | size -= CHUNK_FILE_COUNT(MAX_PATH)*ENC_HDR_SIZE; |
| 554 | fname_buf->hdr.zero = 0; |
| 555 | |
| 556 | /* ENCBUF */ |
| 557 | enc_buffer = buf; |
| 558 | enc_buflen = size; |
| 559 | ALIGN_BUFFER(enc_buffer, enc_buflen, ENC_HDR_SIZE); |
| 560 | enc_buflen = CHUNK_SIZE_COUNT(enc_buflen); |
| 561 | } |
| 562 | |
| 563 | /* Reset the circular buffers */ |
| 564 | static void reset_fifos(bool hard) |
| 565 | { |
| 566 | /* PCM FIFO */ |
| 567 | pcm_pause = true; |
| 568 | |
| 569 | if (hard) |
| 570 | pcm_widx = 0; /* Don't just empty but reset it */ |
| 571 | |
| 572 | pcm_ridx = pcm_widx; |
| 573 | |
| 574 | /* Encoder FIFO */ |
| 575 | encbuf_widx_advance(0, 0); |
| 576 | enc_ridx = 0; |
| 577 | |
| 578 | /* No overflow-related warnings now */ |
| 579 | clear_warning_status(PCMREC_W_PCM_BUFFER_OVF | PCMREC_W_ENC_BUFFER_OVF); |
| 580 | } |
| 581 | |
| 582 | /* Initialize file statistics */ |
| 583 | static void reset_rec_stats(void) |
| 584 | { |
| 585 | num_rec_bytes = 0; |
| 586 | num_rec_samples = 0; |
| 587 | encbuf_rec_count = 0; |
| 588 | clear_warning_status(PCMREC_W_FILE_SIZE); |
| 589 | } |
| 590 | |
| 591 | /* Boost or unboost recording threads' priorities */ |
| 592 | static void do_prio_boost(bool boost) |
| 593 | { |
| 594 | #ifdef HAVE_PRIORITY_SCHEDULING |
| 595 | prio_boosted = boost; |
| 596 | |
| 597 | int prio = PRIORITY_RECORDING; |
| 598 | |
| 599 | if (boost) |
| 600 | prio -= 4; |
| 601 | |
| 602 | codec_thread_set_priority(prio); |
| 603 | thread_set_priority(audio_thread_id, prio); |
| 604 | #endif |
| 605 | (void)boost; |
| 606 | } |
| 607 | |
| 608 | /* Reset all relevant state */ |
| 609 | static void init_state(void) |
| 610 | { |
| 611 | reset_fifos(true); |
| 612 | reset_rec_stats(); |
| 613 | do_prio_boost(false); |
| 614 | cancel_cpu_boost(); |
| 615 | record_state = REC_STATE_IDLE; |
| 616 | record_status = RECORD_STOPPED; |
| 617 | } |
| 618 | |
| 619 | /* Set hardware samplerate and save it */ |
| 620 | static void update_samplerate_config(unsigned long sampr) |
| 621 | { |
| 622 | /* PCM samplerate is either the same as the setting or the nearest |
| 623 | one hardware supports if using S/PDIF */ |
| 624 | unsigned long pcm_sampr = sampr; |
| 625 | |
| 626 | #ifdef HAVE_SPDIF_IN |
| 627 | if (rec_source == AUDIO_SRC_SPDIF) |
| 628 | { |
| 629 | int index = round_value_to_list32(sampr, hw_freq_sampr, |
| 630 | HW_NUM_FREQ, false); |
| 631 | pcm_sampr = hw_freq_sampr[index]; |
| 632 | } |
| 633 | #endif /* HAVE_SPDIF_IN */ |
| 634 | |
| 635 | pcm_set_frequency(pcm_sampr | SAMPR_TYPE_REC); |
| 636 | sample_rate = sampr; |
| 637 | } |
| 638 | |
| 639 | /* Calculate the average data rate */ |
| 640 | static unsigned long get_encbuf_datarate(void) |
| 641 | { |
| 642 | /* If not yet calculable, start with uncompressed PCM byterate */ |
| 643 | if (num_rec_samples && sample_rate && encbuf_rec_count) |
| 644 | { |
| 645 | return (encbuf_rec_count*sample_rate + num_rec_samples - 1) |
| 646 | / num_rec_samples; |
| 647 | } |
| 648 | else |
| 649 | { |
| 650 | return CHUNK_SIZE_COUNT(sample_rate*num_channels*PCM_DEPTH_BYTES); |
| 651 | } |
| 652 | } |
| 653 | |
| 654 | /* Returns true if the watermarks should be updated due to data rate |
| 655 | change */ |
| 656 | static bool monitor_encbuf_datarate(void) |
| 657 | { |
| 658 | unsigned long rate = get_encbuf_datarate(); |
| 659 | long diff = rate - encbuf_datarate; |
| 660 | /* Off by more than 1/2 FLUSH_MON_INTERVAL? */ |
| 661 | return 2*(unsigned long)abs(diff) > encbuf_datarate*FLUSH_MON_INTERVAL; |
| 662 | } |
| 663 | |
| 664 | /* Get adjusted spinup time */ |
| 665 | static int get_spinup_time(void) |
| 666 | { |
| 667 | int spin = storage_spinup_time(); |
| 668 | |
| 669 | #if (CONFIG_STORAGE & STORAGE_ATA) |
| 670 | /* Write at FLUSH_SECONDS + st remaining in enc_buffer - range fs+2s to |
| 671 | fs+10s total - default to 3.5s spinup. */ |
| 672 | if (spin == 0) |
| 673 | spin = 35*HZ/10; /* default - cozy */ |
| 674 | else if (spin < 2*HZ) |
| 675 | spin = 2*HZ; /* ludicrous - ramdisk? */ |
| 676 | else if (spin > 10*HZ) |
| 677 | spin = 10*HZ; /* do you have a functioning HD? */ |
| 678 | #endif /* (CONFIG_STORAGE & STORAGE_ATA) */ |
| 679 | |
| 680 | return spin; |
| 681 | } |
| 682 | |
| 683 | /* Returns true if the watermarks should be updated due to spinup time |
| 684 | change */ |
| 685 | static inline bool monitor_spinup_time(void) |
| 686 | { |
| 687 | #if (CONFIG_STORAGE & STORAGE_ATA) |
| 688 | return get_spinup_time() != spinup_time; |
| 689 | #else |
| 690 | return false; |
| 691 | #endif |
| 692 | } |
| 693 | |
| 694 | /* Update buffer watermarks with spinup time compensation */ |
| 695 | static void refresh_watermarks(void) |
| 696 | { |
| 697 | int spin = get_spinup_time(); |
| 698 | #if (CONFIG_STORAGE & STORAGE_ATA) |
| 699 | logf("ata spinup: %d", spin); |
| 700 | spinup_time = spin; |
| 701 | #endif |
| 702 | |
| 703 | unsigned long rate = get_encbuf_datarate(); |
| 704 | logf("byterate: %lu", rate * ENC_HDR_SIZE); |
| 705 | encbuf_datarate = rate; |
| 706 | |
| 707 | /* Try to start writing with FLUSH_SECONDS remaining after disk spinup */ |
| 708 | high_watermark = (uint64_t)rate*(FLUSH_SECONDS*HZ + spin) / HZ; |
| 709 | |
| 710 | if (high_watermark > enc_buflen) |
| 711 | high_watermark = enc_buflen; |
| 712 | |
| 713 | high_watermark = enc_buflen - high_watermark; |
| 714 | |
| 715 | logf("high wm: %lu", (unsigned long)high_watermark); |
| 716 | |
| 717 | #ifdef HAVE_PRIORITY_SCHEDULING |
| 718 | /* Boost thread priority if enough ground is lost since flushing started |
| 719 | or is taking an unreasonably long time */ |
| 720 | flood_watermark = rate*PANIC_SECONDS; |
| 721 | |
| 722 | if (flood_watermark > enc_buflen) |
| 723 | flood_watermark = enc_buflen; |
| 724 | |
| 725 | flood_watermark = enc_buflen - flood_watermark; |
| 726 | |
| 727 | logf("flood wm: %lu", (unsigned long)flood_watermark); |
| 728 | #endif /* HAVE_PRIORITY_SCHEDULING */ |
| 729 | } |
| 730 | |
| 731 | /* Tell encoder the stream parameters and get information back */ |
| 732 | static bool configure_encoder_stream(void) |
| 733 | { |
| 734 | struct enc_inputs inputs; |
| 735 | inputs.sample_rate = sample_rate; |
| 736 | inputs.num_channels = num_channels; |
| 737 | inputs.config = &enc_config; |
| 738 | |
| 739 | /* encoder can change these - init with defaults */ |
| 740 | inputs.enc_sample_rate = sample_rate; |
| 741 | |
| 742 | if (enc_cb(ENC_CB_INPUTS, &inputs) < 0) |
| 743 | { |
| 744 | raise_error_status(PCMREC_E_ENC_SETUP); |
| 745 | return false; |
| 746 | } |
| 747 | |
| 748 | enc_sample_rate = inputs.enc_sample_rate; |
| 749 | |
| 750 | if (enc_sample_rate != sample_rate) |
| 751 | { |
| 752 | /* Codec doesn't want to/can't use the setting and has chosen a |
| 753 | different sample rate */ |
| 754 | raise_warning_status(PCMREC_W_SAMPR_MISMATCH); |
| 755 | logf("enc sampr:%lu", enc_sample_rate); |
| 756 | } |
| 757 | else |
| 758 | { |
| 759 | clear_warning_status(PCMREC_W_SAMPR_MISMATCH); |
| 760 | } |
| 761 | |
| 762 | refresh_watermarks(); |
| 763 | return true; |
| 764 | } |
| 765 | |
| 766 | #ifdef HAVE_SPDIF_IN |
| 767 | /* Return the S/PDIF sample rate closest to a value in the master list */ |
| 768 | static unsigned long get_spdif_samplerate(void) |
| 769 | { |
| 770 | unsigned long sr = spdif_measure_frequency(); |
| 771 | int index = round_value_to_list32(sr, audio_master_sampr_list, |
| 772 | SAMPR_NUM_FREQ, false); |
| 773 | return audio_master_sampr_list[index]; |
| 774 | } |
| 775 | |
| 776 | /* Check the S/PDIF rate and compare to current setting. Apply the new |
| 777 | * rate if it changed. */ |
| 778 | static void check_spdif_samplerate(void) |
| 779 | { |
| 780 | unsigned long sampr = get_spdif_samplerate(); |
| 781 | |
| 782 | if (sampr == sample_rate) |
| 783 | return; |
| 784 | |
| 785 | codec_stop(); |
| 786 | pcm_stop_recording(); |
| 787 | reset_fifos(true); |
| 788 | reset_rec_stats(); |
| 789 | update_samplerate_config(sampr); |
| 790 | pcm_apply_settings(); |
| 791 | |
| 792 | if (!configure_encoder_stream() || rec_errors) |
| 793 | return; |
| 794 | |
| 795 | pcm_start_recording(); |
| 796 | |
| 797 | if (record_status == RECORD_PRERECORDING) |
| 798 | { |
| 799 | codec_go(); |
| 800 | pcm_pause = false; |
| 801 | } |
| 802 | } |
| 803 | #endif /* HAVE_SPDIF_IN */ |
| 804 | |
| 805 | /* Discard the stream buffer contents */ |
| 806 | static inline void stream_discard_buf(void) |
| 807 | { |
| 808 | stream_buf_used = 0; |
| 809 | } |
| 810 | |
| 811 | /* Flush stream buffer to disk */ |
| 812 | static bool stream_flush_buf(void) |
| 813 | { |
| 814 | if (stream_buf_used == 0) |
| 815 | return true; |
| 816 | |
| 817 | ssize_t rc = write(rec_fd, stream_buffer, stream_buf_used); |
| 818 | |
| 819 | if (LIKELY(rc == stream_buf_used)) |
| 820 | { |
| 821 | stream_discard_buf(); |
| 822 | return true; |
| 823 | } |
| 824 | |
| 825 | if (rc > 0) |
| 826 | { |
| 827 | /* Some was written; keep in sync */ |
| 828 | stream_buf_used -= rc; |
| 829 | memmove(stream_buffer, stream_buffer + rc, stream_buf_used); |
| 830 | } |
| 831 | |
| 832 | return false; |
| 833 | } |
| 834 | |
| 835 | /* Close the output file */ |
| 836 | static void close_rec_file(void) |
| 837 | { |
| 838 | if (rec_fd < 0) |
| 839 | return; |
| 840 | |
| 841 | bool ok = stream_flush_buf(); |
| 842 | |
| 843 | if (close(rec_fd) != 0 || !ok) |
| 844 | raise_error_status(PCMREC_E_IO); |
| 845 | |
| 846 | rec_fd = -1; |
| 847 | } |
| 848 | |
| 849 | /* Creates or opens the current path */ |
| 850 | static bool open_rec_file(bool create) |
| 851 | { |
| 852 | if (rec_fd >= 0) |
| 853 | { |
| 854 | /* Any previous file should have been closed */ |
| 855 | logf("open file: file already open"); |
| 856 | close_rec_file(); |
| 857 | } |
| 858 | |
| 859 | stream_discard_buf(); |
| 860 | int oflags = create ? O_CREAT|O_TRUNC : 0; |
| 861 | rec_fd = open(fname_buf->path, O_RDWR|oflags, 0666); |
| 862 | |
| 863 | if (rec_fd < 0) |
| 864 | { |
| 865 | raise_error_status(PCMREC_E_IO); |
| 866 | return false; |
| 867 | } |
| 868 | |
| 869 | return true; |
| 870 | } |
| 871 | |
| 872 | /* Copy with mono conversion - output 1/2 size of input */ |
| 873 | static void * ICODE_ATTR |
| 874 | copy_buffer_mono_lr(void *dst, const void *src, size_t src_size) |
| 875 | { |
| 876 | int16_t *d = dst; |
| 877 | int16_t const *s = src; |
| 878 | |
| 879 | /* mono = (L + R) / 2 */ |
| 880 | do |
| 881 | *d++ = ((int32_t){ *s++ } + *s++ + 1) >> 1; |
| 882 | while (src_size -= PCM_SAMP_SIZE); |
| 883 | |
| 884 | return dst; |
| 885 | } |
| 886 | |
| 887 | /* Copy with mono conversion - output 1/2 size of input */ |
| 888 | static void * ICODE_ATTR |
| 889 | copy_buffer_mono_l(void *dst, const void *src, size_t src_size) |
| 890 | { |
| 891 | int16_t *d = dst; |
| 892 | int16_t const *s = (int16_t *)src - 2; |
| 893 | |
| 894 | /* mono = L */ |
| 895 | do |
| 896 | *d++ = *(s += 2); |
| 897 | while (src_size -= PCM_SAMP_SIZE); |
| 898 | |
| 899 | return dst; |
| 900 | } |
| 901 | |
| 902 | /* Copy with mono conversion - output 1/2 size of input */ |
| 903 | static void * ICODE_ATTR |
| 904 | copy_buffer_mono_r(void *dst, const void *src, size_t src_size) |
| 905 | { |
| 906 | int16_t *d = dst; |
| 907 | int16_t const *s = (int16_t *)src - 1; |
| 908 | |
| 909 | /* mono = R */ |
| 910 | do |
| 911 | *d++ = *(s += 2); |
| 912 | while (src_size -= PCM_SAMP_SIZE); |
| 913 | |
| 914 | return dst; |
| 915 | } |
| 916 | |
| 917 | |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 918 | /** pcm_rec_* group **/ |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 919 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 920 | /* Clear all errors and warnings */ |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 921 | void pcm_rec_error_clear(void) |
| 922 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 923 | clear_error_status(PCMREC_E_ALL); |
| 924 | clear_warning_status(PCMREC_W_ALL); |
| 925 | } |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 926 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 927 | /* Check mode, errors and warnings */ |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 928 | unsigned int pcm_rec_status(void) |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 929 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 930 | unsigned int ret = record_status; |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 931 | |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 932 | if (errors) |
Andy | e6e5496 | 2005-11-12 04:00:56 +0000 | [diff] [blame] | 933 | ret |= AUDIO_STATUS_ERROR; |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 934 | |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 935 | if (warnings) |
| 936 | ret |= AUDIO_STATUS_WARNING; |
Christian Gmeiner | 14e8067 | 2005-08-06 10:12:19 +0000 | [diff] [blame] | 937 | |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 938 | return ret; |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 939 | } |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 940 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 941 | /* Return warnings that have occured since recording started */ |
| 942 | uint32_t pcm_rec_get_warnings(void) |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 943 | { |
| 944 | return warnings; |
| 945 | } |
| 946 | |
Michael Sevakis | 7d00f97 | 2006-11-29 14:23:24 +0000 | [diff] [blame] | 947 | #ifdef HAVE_SPDIF_IN |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 948 | /* Return the currently-configured sample rate */ |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 949 | unsigned long pcm_rec_sample_rate(void) |
| 950 | { |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 951 | return sample_rate; |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 952 | } |
Michael Sevakis | 7d00f97 | 2006-11-29 14:23:24 +0000 | [diff] [blame] | 953 | #endif |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 954 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 955 | |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 956 | /** audio_* group **/ |
| 957 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 958 | /* Initializes recording - call before calling any other recording function */ |
Thomas Martitz | 8a701e5 | 2011-08-14 15:37:05 +0000 | [diff] [blame] | 959 | void audio_init_recording(void) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 960 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 961 | LOGFQUEUE("audio >| pcmrec Q_AUDIO_INIT_RECORDING"); |
| 962 | audio_queue_send(Q_AUDIO_INIT_RECORDING, 1); |
| 963 | } |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 964 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 965 | /* Closes recording - call audio_stop_recording first or risk data loss */ |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 966 | void audio_close_recording(void) |
| 967 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 968 | LOGFQUEUE("audio >| pcmrec Q_AUDIO_CLOSE_RECORDING"); |
| 969 | audio_queue_send(Q_AUDIO_CLOSE_RECORDING, 0); |
| 970 | } |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 971 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 972 | /* Sets recording parameters */ |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 973 | void audio_set_recording_options(struct audio_recording_options *options) |
| 974 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 975 | LOGFQUEUE("audio >| pcmrec Q_AUDIO_RECORDING_OPTIONS"); |
| 976 | audio_queue_send(Q_AUDIO_RECORDING_OPTIONS, (intptr_t)options); |
| 977 | } |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 978 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 979 | /* Start recording if not recording or else split */ |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 980 | void audio_record(const char *filename) |
| 981 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 982 | LOGFQUEUE("audio >| pcmrec Q_AUDIO_RECORD: %s", filename); |
| 983 | audio_queue_send(Q_AUDIO_RECORD, (intptr_t)filename); |
| 984 | } |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 985 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 986 | /* audio_record alias for API compatibility with HW codec */ |
Michael Sevakis | 2d48d0f | 2007-06-08 23:42:04 +0000 | [diff] [blame] | 987 | void audio_new_file(const char *filename) |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 988 | __attribute__((alias("audio_record"))); |
Michael Sevakis | 2d48d0f | 2007-06-08 23:42:04 +0000 | [diff] [blame] | 989 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 990 | /* Stop current recording if recording */ |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 991 | void audio_stop_recording(void) |
| 992 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 993 | LOGFQUEUE("audio > pcmrec Q_AUDIO_RECORD_STOP"); |
| 994 | audio_queue_post(Q_AUDIO_RECORD_STOP, 0); |
| 995 | } |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 996 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 997 | /* Pause current recording */ |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 998 | void audio_pause_recording(void) |
| 999 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1000 | LOGFQUEUE("audio > pcmrec Q_AUDIO_RECORD_PAUSE"); |
| 1001 | audio_queue_post(Q_AUDIO_RECORD_PAUSE, 0); |
| 1002 | } |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1003 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1004 | /* Resume current recording if paused */ |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1005 | void audio_resume_recording(void) |
| 1006 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1007 | LOGFQUEUE("audio > pcmrec Q_AUDIO_RECORD_RESUME"); |
| 1008 | audio_queue_post(Q_AUDIO_RECORD_RESUME, 0); |
| 1009 | } |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1010 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1011 | /* Set the input source gain. For mono sources, only left gain is used */ |
Michael Sevakis | 36c9405 | 2006-12-17 11:03:19 +0000 | [diff] [blame] | 1012 | void audio_set_recording_gain(int left, int right, int type) |
| 1013 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1014 | #if 0 |
| 1015 | logf("pcmrec: t=%d l=%d r=%d", type, left, right); |
| 1016 | #endif |
Michael Sevakis | 36c9405 | 2006-12-17 11:03:19 +0000 | [diff] [blame] | 1017 | audiohw_set_recvol(left, right, type); |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1018 | } |
| 1019 | |
Michael Sevakis | 36c9405 | 2006-12-17 11:03:19 +0000 | [diff] [blame] | 1020 | |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1021 | /** Information about current state **/ |
| 1022 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1023 | /* Return sample clock in HZ */ |
| 1024 | static unsigned long get_samples_time(void) |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1025 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1026 | if (enc_sample_rate == 0) |
Michael Sevakis | 7d00f97 | 2006-11-29 14:23:24 +0000 | [diff] [blame] | 1027 | return 0; |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1028 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1029 | return (unsigned long)(HZ*num_rec_samples / enc_sample_rate); |
| 1030 | } |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1031 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1032 | /* Return current prerecorded time in ticks (playback equivalent time) */ |
| 1033 | unsigned long audio_prerecorded_time(void) |
| 1034 | { |
| 1035 | if (record_status != RECORD_PRERECORDING) |
| 1036 | return 0; |
| 1037 | |
| 1038 | unsigned long t = get_samples_time(); |
| 1039 | return MIN(t, pre_record_seconds*HZ); |
| 1040 | } |
| 1041 | |
| 1042 | /* Return current recorded time in ticks (playback equivalent time) */ |
| 1043 | unsigned long audio_recorded_time(void) |
| 1044 | { |
| 1045 | if (record_state == REC_STATE_IDLE) |
| 1046 | return 0; |
| 1047 | |
| 1048 | return get_samples_time(); |
| 1049 | } |
| 1050 | |
| 1051 | /* Return number of bytes encoded to output */ |
Andy | e6e5496 | 2005-11-12 04:00:56 +0000 | [diff] [blame] | 1052 | unsigned long audio_num_recorded_bytes(void) |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1053 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1054 | if (record_state == REC_STATE_IDLE) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1055 | return 0; |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1056 | |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1057 | return num_rec_bytes; |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1058 | } |
Michael Sevakis | a9ea1a4 | 2013-06-22 16:39:40 -0400 | [diff] [blame] | 1059 | |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1060 | |
| 1061 | /** Data Flushing **/ |
| 1062 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1063 | /* Stream start chunk with path was encountered */ |
| 1064 | static void flush_stream_start(struct enc_chunk_file *file) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1065 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1066 | /* Save filename; don't open file here which avoids creating files |
| 1067 | with no audio content. Splitting while paused can create those |
| 1068 | in large numbers. */ |
| 1069 | fname_buf->hdr = file->hdr; |
| 1070 | /* Correct size if this was wrap-padded */ |
| 1071 | fname_buf->hdr.size = CHUNK_FILE_COUNT( |
| 1072 | strlcpy(fname_buf->path, file->path, MAX_PATH) + 1); |
| 1073 | } |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1074 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1075 | /* Data chunk was encountered */ |
| 1076 | static bool flush_stream_data(struct enc_chunk_data *data) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1077 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1078 | if (fname_buf->hdr.zero) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1079 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1080 | /* First data chunk; create the file */ |
| 1081 | if (open_rec_file(true)) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1082 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1083 | /* Inherit some flags from initial data chunk */ |
| 1084 | fname_buf->hdr.err = data->hdr.err; |
| 1085 | fname_buf->hdr.pre = data->hdr.pre; |
| 1086 | fname_buf->hdr.aux0 = data->hdr.aux0; |
| 1087 | |
| 1088 | if (enc_cb(ENC_CB_STREAM, fname_buf) < 0) |
| 1089 | raise_error_status(PCMREC_E_ENCODER_STREAM); |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1090 | } |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1091 | |
| 1092 | fname_buf->hdr.zero = 0; |
| 1093 | |
| 1094 | if (rec_errors) |
| 1095 | return false; |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1096 | } |
| 1097 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1098 | if (rec_fd < 0) |
| 1099 | return true; /* Just keep discarding */ |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1100 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1101 | if (enc_cb(ENC_CB_STREAM, data) < 0) |
| 1102 | { |
| 1103 | raise_error_status(PCMREC_E_ENCODER_STREAM); |
| 1104 | return false; |
| 1105 | } |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1106 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1107 | return true; |
| 1108 | } |
| 1109 | |
| 1110 | /* Stream end chunk was encountered */ |
| 1111 | static bool flush_stream_end(union enc_chunk_hdr *hdr) |
| 1112 | { |
| 1113 | if (rec_fd < 0) |
| 1114 | return true; |
| 1115 | |
| 1116 | if (enc_cb(ENC_CB_STREAM, hdr) < 0) |
| 1117 | { |
| 1118 | raise_error_status(PCMREC_E_ENCODER_STREAM); |
| 1119 | return false; |
| 1120 | } |
| 1121 | |
| 1122 | close_rec_file(); |
| 1123 | return true; |
| 1124 | } |
| 1125 | |
| 1126 | /* Discard remainder of stream in encoder buffer */ |
| 1127 | static void discard_stream(void) |
| 1128 | { |
| 1129 | /* Discard everything up until the next non-data chunk */ |
| 1130 | while (!encbuf_empty()) |
| 1131 | { |
| 1132 | size_t ridx; |
| 1133 | union enc_chunk_hdr *hdr = encbuf_read_ptr_incr(enc_ridx, &ridx); |
| 1134 | |
| 1135 | if (hdr && hdr->type != CHUNK_T_DATA) |
| 1136 | { |
| 1137 | if (hdr->type != CHUNK_T_STREAM_START) |
| 1138 | enc_ridx = ridx; |
| 1139 | break; |
| 1140 | } |
| 1141 | |
| 1142 | enc_ridx = ridx; |
| 1143 | } |
| 1144 | |
| 1145 | /* Try to finish header by closing and reopening the file. A seek or |
| 1146 | other operation will likely fail because buffers will need to be |
| 1147 | flushed (here and in file code). That will likely fail but a close |
| 1148 | will just close the fd and discard everything. We reopen with what |
| 1149 | actually made it to disk. Modifying existing file contents will |
| 1150 | more than likely succeed even on a full disk. The result might not |
| 1151 | be entirely correct as far as the headers' sizes and counts unless |
| 1152 | the codec can correct that but the sample format information |
| 1153 | should be. */ |
| 1154 | if (rec_fd >= 0 && open_rec_file(false)) |
| 1155 | { |
| 1156 | /* Synthesize a special end chunk here */ |
| 1157 | union enc_chunk_hdr end; |
| 1158 | end.zero = 0; |
| 1159 | end.err = 1; /* Codec should try to correct anything that's off */ |
| 1160 | end.type = CHUNK_T_STREAM_END; |
| 1161 | if (!flush_stream_end(&end)) |
| 1162 | close_rec_file(); |
| 1163 | } |
| 1164 | } |
| 1165 | |
| 1166 | /* Flush a chunk to disk |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1167 | * |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1168 | * Transitions state from REC_STATE_MONITOR to REC_STATE_FLUSH when buffer |
| 1169 | * is filling. 'margin' is fullness threshold that transitions to flush state. |
| 1170 | * |
| 1171 | * Call with REC_STATE_IDLE to indicate a forced flush which flushes buffer |
| 1172 | * to less than 'margin'. |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1173 | */ |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1174 | static enum record_state flush_chunk(enum record_state state, size_t margin) |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1175 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1176 | #ifdef HAVE_PRIORITY_SCHEDULING |
| 1177 | static unsigned long prio_tick; /* Timeout for auto boost */ |
| 1178 | #endif |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1179 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1180 | size_t used = encbuf_used(); |
| 1181 | |
| 1182 | switch (state) |
| 1183 | { |
| 1184 | case REC_STATE_MONITOR: |
| 1185 | if (monitor_encbuf_datarate() || monitor_spinup_time()) |
| 1186 | refresh_watermarks(); |
| 1187 | |
| 1188 | if (used < margin) |
| 1189 | return REC_STATE_MONITOR; |
| 1190 | |
| 1191 | state = REC_STATE_FLUSH; |
| 1192 | trigger_cpu_boost(); |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1193 | |
| 1194 | #ifdef HAVE_PRIORITY_SCHEDULING |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1195 | prio_tick = current_tick + PRIO_SECONDS*HZ; |
Rafaël Carré | 020dddf | 2010-05-14 11:08:15 +0000 | [diff] [blame] | 1196 | #if (CONFIG_STORAGE & STORAGE_ATA) |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1197 | prio_tick += spinup_time; |
Rafaël Carré | 020dddf | 2010-05-14 11:08:15 +0000 | [diff] [blame] | 1198 | #endif |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1199 | #endif /* HAVE_PRIORITY_SCHEDULING */ |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1200 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1201 | /* Fall-through */ |
| 1202 | case REC_STATE_IDLE: /* As a hint for "forced" */ |
| 1203 | if (used < margin) |
| 1204 | break; |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1205 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1206 | /* Fall-through */ |
| 1207 | case REC_STATE_FLUSH: |
Michael Sevakis | 0d094bd | 2007-01-31 06:43:31 +0000 | [diff] [blame] | 1208 | #ifdef HAVE_PRIORITY_SCHEDULING |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1209 | if (!prio_boosted && state != REC_STATE_IDLE && |
| 1210 | (used >= flood_watermark || TIME_AFTER(current_tick, prio_tick))) |
| 1211 | do_prio_boost(true); |
| 1212 | #endif /* HAVE_PRIORITY_SCHEDULING */ |
Peter D'Hoye | 9ace16f | 2006-02-24 22:28:57 +0000 | [diff] [blame] | 1213 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1214 | while (used) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1215 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1216 | union enc_chunk_hdr *hdr = encbuf_ptr(enc_ridx); |
| 1217 | size_t count = 0; |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1218 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1219 | switch (hdr->type) |
| 1220 | { |
| 1221 | case CHUNK_T_DATA: |
| 1222 | if (flush_stream_data(ENC_DATA_HDR(hdr))) |
| 1223 | count = CHUNK_DATA_COUNT(hdr->size); |
| 1224 | break; |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1225 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1226 | case CHUNK_T_STREAM_START: |
| 1227 | /* Doesn't do stream writes */ |
| 1228 | flush_stream_start(ENC_FILE_HDR(hdr)); |
| 1229 | count = hdr->size; |
| 1230 | break; |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1231 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1232 | case CHUNK_T_STREAM_END: |
| 1233 | if (flush_stream_end(hdr)) |
| 1234 | count = 1; |
| 1235 | break; |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1236 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1237 | case CHUNK_T_WRAP: |
| 1238 | enc_ridx = 0; |
| 1239 | used = encbuf_used(); |
| 1240 | continue; |
| 1241 | } |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1242 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1243 | if (count) |
| 1244 | enc_ridx = encbuf_add(enc_ridx, count); |
| 1245 | else |
| 1246 | discard_stream(); |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1247 | |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1248 | break; |
Peter D'Hoye | 85058f5 | 2007-10-10 23:26:17 +0000 | [diff] [blame] | 1249 | } |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1250 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1251 | if (!encbuf_empty()) |
| 1252 | return state; |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1253 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1254 | break; |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1255 | } |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1256 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1257 | if (encbuf_empty()) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1258 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1259 | do_prio_boost(false); |
| 1260 | cancel_cpu_boost(); |
| 1261 | } |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 1262 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1263 | return REC_STATE_MONITOR; |
| 1264 | } |
Christian Gmeiner | 14e8067 | 2005-08-06 10:12:19 +0000 | [diff] [blame] | 1265 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1266 | /* Monitor buffer and finish stream, freeing-up space at the same time */ |
| 1267 | static void finish_stream(bool stopping) |
| 1268 | { |
| 1269 | size_t threshold = stopping ? 1 : enc_buflen - ENCBUF_MIN_SPLIT_MARGIN; |
| 1270 | enum record_state state = REC_STATE_MONITOR; |
| 1271 | size_t need = 1; |
| 1272 | |
| 1273 | while (1) |
| 1274 | { |
| 1275 | switch (state) |
| 1276 | { |
| 1277 | case REC_STATE_IDLE: |
| 1278 | state = flush_chunk(state, threshold); |
| 1279 | continue; |
| 1280 | |
| 1281 | default: |
| 1282 | if (!need) |
| 1283 | break; |
| 1284 | |
| 1285 | if (!stopping || pcm_buffer_empty) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1286 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1287 | need = codec_finish_stream(); |
Christian Gmeiner | 14e8067 | 2005-08-06 10:12:19 +0000 | [diff] [blame] | 1288 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1289 | if (need) |
| 1290 | { |
| 1291 | need = 2*CHUNK_DATA_COUNT(need) - 1; |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1292 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1293 | if (need >= enc_buflen) |
| 1294 | { |
| 1295 | need = 0; |
| 1296 | codec_stop(); |
| 1297 | threshold = 1; |
| 1298 | } |
| 1299 | else if (threshold > enc_buflen - need) |
| 1300 | { |
| 1301 | threshold = enc_buflen - need; |
| 1302 | } |
| 1303 | } |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1304 | } |
| 1305 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1306 | if (!need || encbuf_used() >= threshold) |
| 1307 | state = REC_STATE_IDLE; /* Start flush */ |
| 1308 | else |
| 1309 | sleep(HZ/10); /* Don't flood with pings */ |
| 1310 | |
| 1311 | continue; |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1312 | } |
| 1313 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1314 | break; |
| 1315 | } |
| 1316 | } |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1317 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1318 | /* Start a new stream, transistion to a new one or end the current one */ |
| 1319 | static void mark_stream(const char *path, enum mark_stream_action action) |
| 1320 | { |
| 1321 | if (action & MARK_STREAM_END) |
| 1322 | { |
| 1323 | size_t widx; |
| 1324 | union enc_chunk_hdr *hdr = encbuf_get_write_ptr(enc_widx, 1, &widx); |
| 1325 | hdr->type = CHUNK_T_STREAM_END; |
| 1326 | encbuf_widx_advance(widx, 1); |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1327 | } |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1328 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1329 | if (action & MARK_STREAM_START) |
| 1330 | { |
| 1331 | size_t count = CHUNK_FILE_COUNT_PATH(path); |
| 1332 | struct enc_chunk_file *file; |
| 1333 | size_t widx; |
| 1334 | |
| 1335 | if (action & MARK_STREAM_PRE) |
| 1336 | { |
| 1337 | /* Prerecord: START marker goes first or before existing data */ |
| 1338 | if (enc_ridx < count) |
| 1339 | { |
| 1340 | /* Adjust to occupy end of buffer and pad accordingly */ |
| 1341 | count += enc_ridx; |
| 1342 | enc_ridx += enc_buflen; |
| 1343 | } |
| 1344 | |
| 1345 | enc_ridx -= count; |
| 1346 | |
| 1347 | /* Won't adjust p since enc_ridx is already set as non-wrapping */ |
| 1348 | file = encbuf_get_write_ptr(enc_ridx, count, &widx); |
| 1349 | } |
| 1350 | else |
| 1351 | { |
| 1352 | /* The usual: START marker goes first or after existing data */ |
| 1353 | file = encbuf_get_write_ptr(enc_widx, count, &widx); |
| 1354 | encbuf_widx_advance(widx, count); |
| 1355 | } |
| 1356 | |
| 1357 | file->hdr.type = CHUNK_T_STREAM_START; |
| 1358 | file->hdr.size = count; |
| 1359 | strlcpy(file->path, path, MAX_PATH); |
| 1360 | } |
| 1361 | } |
| 1362 | |
| 1363 | /* Tally-up and keep the required amount of prerecord data. |
| 1364 | * Updates record stats accordingly. */ |
| 1365 | static void tally_prerecord_data(void) |
| 1366 | { |
| 1367 | unsigned long count = 0; |
| 1368 | size_t bytes = 0; |
| 1369 | unsigned long samples = 0; |
| 1370 | |
| 1371 | /* Find out how much is there */ |
| 1372 | for (size_t idx = enc_ridx; idx != enc_widx;) |
| 1373 | { |
| 1374 | struct enc_chunk_data *data = encbuf_read_ptr_incr(idx, &idx); |
| 1375 | |
| 1376 | if (!data) |
| 1377 | continue; |
| 1378 | |
| 1379 | count += CHUNK_DATA_COUNT(data->hdr.size); |
| 1380 | bytes += data->hdr.size; |
| 1381 | samples += data->pcm_count; |
| 1382 | } |
| 1383 | |
| 1384 | /* Have too much? Discard oldest data. */ |
| 1385 | unsigned long pre_samples = enc_sample_rate*pre_record_seconds; |
| 1386 | |
| 1387 | while (samples > pre_samples) |
| 1388 | { |
| 1389 | struct enc_chunk_data *data = |
| 1390 | encbuf_read_ptr_incr(enc_ridx, &enc_ridx); |
| 1391 | |
| 1392 | if (!data) |
| 1393 | continue; |
| 1394 | |
| 1395 | count -= CHUNK_DATA_COUNT(data->hdr.size); |
| 1396 | bytes -= data->hdr.size; |
| 1397 | samples -= data->pcm_count; |
| 1398 | } |
| 1399 | |
| 1400 | encbuf_rec_count = count; |
| 1401 | num_rec_bytes = bytes; |
| 1402 | num_rec_samples = samples; |
| 1403 | } |
Christian Gmeiner | 14e8067 | 2005-08-06 10:12:19 +0000 | [diff] [blame] | 1404 | |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1405 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1406 | /** Event handlers for recording thread **/ |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1407 | |
Thomas Martitz | 22e802e | 2013-05-30 11:24:16 +0200 | [diff] [blame] | 1408 | static int pcmrec_handle; |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1409 | /* Q_AUDIO_INIT_RECORDING */ |
| 1410 | static void on_init_recording(void) |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1411 | { |
Jonathan Gordon | 7d5e0d7 | 2010-05-16 11:13:42 +0000 | [diff] [blame] | 1412 | send_event(RECORDING_EVENT_START, NULL); |
Thomas Martitz | 22e802e | 2013-05-30 11:24:16 +0200 | [diff] [blame] | 1413 | /* dummy ops with no callbacks, needed because by |
| 1414 | * default buflib buffers can be moved around which must be avoided |
| 1415 | * FIXME: This buffer should play nicer and be shrinkable/movable */ |
| 1416 | static struct buflib_callbacks dummy_ops; |
| 1417 | talk_buffer_set_policy(TALK_BUFFER_LOOSE); |
| 1418 | pcmrec_handle = core_alloc_maximum("pcmrec", &rec_buffer_size, &dummy_ops); |
Thomas Martitz | a56837b | 2013-12-24 10:20:13 +0100 | [diff] [blame] | 1419 | if (pcmrec_handle <= 0) |
Thomas Martitz | 22e802e | 2013-05-30 11:24:16 +0200 | [diff] [blame] | 1420 | /* someone is abusing core_alloc_maximum(). Fix this evil guy instead of |
| 1421 | * trying to handle OOM without hope */ |
| 1422 | panicf("%s(): OOM\n", __func__); |
| 1423 | rec_buffer = core_get_data(pcmrec_handle); |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1424 | init_rec_buffers(); |
| 1425 | init_state(); |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1426 | pcm_init_recording(); |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1427 | } |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1428 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1429 | /* Q_AUDIO_CLOSE_RECORDING */ |
| 1430 | static void on_close_recording(void) |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1431 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1432 | /* Simply shut down the recording system. Whatever wasn't saved is |
| 1433 | lost. */ |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1434 | codec_unload(); |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1435 | pcm_close_recording(); |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1436 | close_rec_file(); |
| 1437 | init_state(); |
| 1438 | |
| 1439 | rec_errors = 0; |
| 1440 | pcm_rec_error_clear(); |
| 1441 | |
| 1442 | /* Reset PCM to defaults */ |
| 1443 | pcm_set_frequency(HW_SAMPR_RESET | SAMPR_TYPE_REC); |
| 1444 | audio_set_output_source(AUDIO_SRC_PLAYBACK); |
| 1445 | pcm_apply_settings(); |
| 1446 | |
Thomas Martitz | 22e802e | 2013-05-30 11:24:16 +0200 | [diff] [blame] | 1447 | if (pcmrec_handle > 0) |
| 1448 | pcmrec_handle = core_free(pcmrec_handle); |
| 1449 | talk_buffer_set_policy(TALK_BUFFER_DEFAULT); |
| 1450 | |
Jonathan Gordon | 7d5e0d7 | 2010-05-16 11:13:42 +0000 | [diff] [blame] | 1451 | send_event(RECORDING_EVENT_STOP, NULL); |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1452 | } |
Miika Pekkarinen | 482a242 | 2005-12-23 21:43:05 +0000 | [diff] [blame] | 1453 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1454 | /* Q_AUDIO_RECORDING_OPTIONS */ |
| 1455 | static void on_recording_options(struct audio_recording_options *options) |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1456 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1457 | if (!options) |
| 1458 | { |
| 1459 | logf("options: option NULL!"); |
| 1460 | return; |
| 1461 | } |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1462 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1463 | if (record_state != REC_STATE_IDLE) |
| 1464 | { |
| 1465 | /* This would ruin things */ |
| 1466 | logf("options: still recording!"); |
| 1467 | return; |
| 1468 | } |
| 1469 | |
| 1470 | /* Stop everything else that might be running */ |
| 1471 | pcm_stop_recording(); |
| 1472 | |
| 1473 | int afmt = rec_format_afmt[options->enc_config.rec_format]; |
| 1474 | bool enc_load = true; |
| 1475 | |
| 1476 | if (codec_loaded() != AFMT_UNKNOWN) |
| 1477 | { |
| 1478 | if (get_audio_base_codec_type(enc_config.afmt) != |
| 1479 | get_audio_base_codec_type(afmt)) |
| 1480 | { |
| 1481 | /* New format, new encoder; unload this one */ |
| 1482 | codec_unload(); |
| 1483 | } |
| 1484 | else |
| 1485 | { |
| 1486 | /* Keep current encoder */ |
| 1487 | codec_stop(); |
| 1488 | enc_load = false; |
| 1489 | } |
| 1490 | } |
| 1491 | |
| 1492 | init_state(); |
| 1493 | |
| 1494 | /* Read recording options, remember the ones used elsewhere */ |
| 1495 | unsigned frequency = options->rec_frequency; |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1496 | rec_source = options->rec_source; |
| 1497 | num_channels = options->rec_channels == 1 ? 1 : 2; |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1498 | unsigned mono_mode = options->rec_mono_mode; |
| 1499 | pre_record_seconds = options->rec_prerecord_time; |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1500 | enc_config = options->enc_config; |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1501 | enc_config.afmt = afmt; |
| 1502 | |
| 1503 | queue_reply(&audio_queue, 0); /* Let caller go */ |
| 1504 | |
| 1505 | /* Pick appropriate PCM copy routine */ |
| 1506 | pcm_copyfn = memcpy; |
| 1507 | |
| 1508 | if (num_channels == 1) |
| 1509 | { |
| 1510 | static typeof (memcpy) * const copy_buffer_mono[] = |
| 1511 | { |
| 1512 | copy_buffer_mono_lr, |
| 1513 | copy_buffer_mono_l, |
| 1514 | copy_buffer_mono_r |
| 1515 | }; |
| 1516 | |
| 1517 | if (mono_mode >= ARRAYLEN(copy_buffer_mono)) |
| 1518 | mono_mode = 0; |
| 1519 | |
| 1520 | pcm_copyfn = copy_buffer_mono[mono_mode]; |
| 1521 | } |
| 1522 | |
| 1523 | /* Get the hardware samplerate to be used */ |
| 1524 | unsigned long sampr; |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1525 | |
| 1526 | #ifdef HAVE_SPDIF_IN |
| 1527 | if (rec_source == AUDIO_SRC_SPDIF) |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1528 | sampr = get_spdif_samplerate(); /* Determined by source */ |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1529 | else |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1530 | #endif /* HAVE_SPDIF_IN */ |
| 1531 | sampr = rec_freq_sampr[frequency]; |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1532 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1533 | update_samplerate_config(sampr); |
| 1534 | |
| 1535 | /* Set monitoring */ |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1536 | audio_set_output_source(rec_source); |
| 1537 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1538 | /* Apply hardware setting to start monitoring now */ |
Michael Sevakis | 8867d00 | 2007-03-05 08:14:27 +0000 | [diff] [blame] | 1539 | pcm_apply_settings(); |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1540 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1541 | if (!enc_load || codec_load(-1, afmt | CODEC_TYPE_ENCODER)) |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1542 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1543 | enc_cb = codec_get_enc_callback(); |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1544 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1545 | if (!enc_cb || !configure_encoder_stream()) |
| 1546 | { |
| 1547 | codec_unload(); |
| 1548 | return; |
| 1549 | } |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1550 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1551 | if (pre_record_seconds != 0) |
| 1552 | { |
| 1553 | record_status = RECORD_PRERECORDING; |
| 1554 | codec_go(); |
| 1555 | pcm_pause = false; |
| 1556 | } |
| 1557 | |
| 1558 | pcm_start_recording(); |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1559 | } |
| 1560 | else |
| 1561 | { |
| 1562 | logf("set rec opt: enc load failed"); |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1563 | raise_error_status(PCMREC_E_LOAD_ENCODER); |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1564 | } |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1565 | } |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1566 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1567 | /* Q_AUDIO_RECORD - start recording (not gapless) |
| 1568 | or split stream (gapless) */ |
| 1569 | static void on_record(const char *filename) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1570 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1571 | if (rec_errors) |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1572 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1573 | logf("on_record: errors not cleared"); |
| 1574 | return; |
| 1575 | } |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1576 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1577 | if (!filename) |
| 1578 | { |
| 1579 | logf("on_record: No filename"); |
| 1580 | return; |
| 1581 | } |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1582 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1583 | if (codec_loaded() == AFMT_UNKNOWN) |
| 1584 | { |
| 1585 | logf("on_record: Recording options not set"); |
| 1586 | return; |
| 1587 | } |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1588 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1589 | logf("on_record: new file '%s'", filename); |
| 1590 | |
| 1591 | /* Copy path and let caller go */ |
| 1592 | char path[MAX_PATH]; |
| 1593 | strlcpy(path, filename, MAX_PATH); |
| 1594 | |
| 1595 | queue_reply(&audio_queue, 0); |
| 1596 | |
| 1597 | enum mark_stream_action mark_action; |
| 1598 | |
| 1599 | if (record_state == REC_STATE_IDLE) |
| 1600 | { |
| 1601 | mark_action = MARK_STREAM_START; |
| 1602 | |
| 1603 | if (pre_record_seconds) |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1604 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1605 | codec_pause(); |
| 1606 | tally_prerecord_data(); |
| 1607 | mark_action = MARK_STREAM_START_PRE; |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1608 | } |
| 1609 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1610 | clear_warning_status(PCMREC_W_ALL & |
| 1611 | ~(PCMREC_W_SAMPR_MISMATCH|PCMREC_W_DMA)); |
| 1612 | record_state = REC_STATE_MONITOR; |
| 1613 | record_status = RECORD_RECORDING; |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1614 | } |
| 1615 | else |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1616 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1617 | /* Already recording, just split the stream */ |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1618 | logf("inserting split"); |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1619 | mark_action = MARK_STREAM_SPLIT; |
| 1620 | finish_stream(false); |
| 1621 | reset_rec_stats(); |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1622 | } |
| 1623 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1624 | if (rec_errors) |
| 1625 | { |
| 1626 | pcm_pause = true; |
| 1627 | codec_stop(); |
| 1628 | reset_fifos(false); |
| 1629 | return; |
| 1630 | } |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1631 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1632 | mark_stream(path, mark_action); |
| 1633 | |
| 1634 | codec_go(); |
| 1635 | pcm_pause = record_status != RECORD_RECORDING; |
| 1636 | } |
| 1637 | |
| 1638 | /* Q_AUDIO_RECORD_STOP */ |
| 1639 | static void on_record_stop(void) |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1640 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1641 | if (record_state == REC_STATE_IDLE) |
| 1642 | return; |
Michael Sevakis | a9ea1a4 | 2013-06-22 16:39:40 -0400 | [diff] [blame] | 1643 | |
Michael Sevakis | 5f0692b | 2013-07-09 07:36:22 -0400 | [diff] [blame] | 1644 | trigger_cpu_boost(); |
| 1645 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1646 | /* Drain encoder and PCM buffers */ |
| 1647 | pcm_pause = true; |
| 1648 | finish_stream(true); |
| 1649 | |
| 1650 | /* End stream at last data and flush end marker */ |
| 1651 | mark_stream(NULL, MARK_STREAM_END); |
| 1652 | while (flush_chunk(REC_STATE_IDLE, 1) == REC_STATE_IDLE); |
| 1653 | |
| 1654 | reset_fifos(false); |
| 1655 | |
| 1656 | bool prerecord = pre_record_seconds != 0; |
| 1657 | |
| 1658 | if (rec_errors) |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1659 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1660 | codec_stop(); |
| 1661 | prerecord = false; |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1662 | } |
| 1663 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1664 | close_rec_file(); |
| 1665 | rec_errors = 0; |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1666 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1667 | record_state = REC_STATE_IDLE; |
| 1668 | record_status = prerecord ? RECORD_PRERECORDING : RECORD_STOPPED; |
| 1669 | reset_rec_stats(); |
| 1670 | |
| 1671 | if (prerecord) |
| 1672 | { |
| 1673 | codec_go(); |
| 1674 | pcm_pause = false; |
| 1675 | } |
| 1676 | } |
| 1677 | |
| 1678 | /* Q_AUDIO_RECORD_PAUSE */ |
| 1679 | static void on_record_pause(void) |
Andy | e6e5496 | 2005-11-12 04:00:56 +0000 | [diff] [blame] | 1680 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1681 | if (record_status != RECORD_RECORDING) |
| 1682 | return; |
Andy | e6e5496 | 2005-11-12 04:00:56 +0000 | [diff] [blame] | 1683 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1684 | pcm_pause = true; |
| 1685 | record_status = RECORD_PAUSED; |
| 1686 | } |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1687 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1688 | /* Q_AUDIO_RECORD_RESUME */ |
| 1689 | static void on_record_resume(void) |
Andy | e6e5496 | 2005-11-12 04:00:56 +0000 | [diff] [blame] | 1690 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1691 | if (record_status != RECORD_PAUSED) |
| 1692 | return; |
Michael Sevakis | a9ea1a4 | 2013-06-22 16:39:40 -0400 | [diff] [blame] | 1693 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1694 | record_status = RECORD_RECORDING; |
| 1695 | pcm_pause = !!rec_errors; |
| 1696 | } |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1697 | |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1698 | /* Called by audio thread when recording is initialized */ |
| 1699 | void audio_recording_handler(struct queue_event *ev) |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1700 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1701 | #ifdef HAVE_PRIORITY_SCHEDULING |
| 1702 | /* Get current priorities since they get changed */ |
| 1703 | int old_prio = thread_get_priority(audio_thread_id); |
| 1704 | int old_cod_prio = codec_thread_get_priority(); |
| 1705 | #endif |
| 1706 | |
| 1707 | LOGFQUEUE("record < Q_AUDIO_INIT_RECORDING"); |
| 1708 | on_init_recording(); |
Christian Gmeiner | 14e8067 | 2005-08-06 10:12:19 +0000 | [diff] [blame] | 1709 | |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1710 | while (1) |
Andy | e5d0872 | 2005-06-19 03:05:53 +0000 | [diff] [blame] | 1711 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1712 | int watermark = high_watermark; |
| 1713 | |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1714 | switch (ev->id) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1715 | { |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1716 | case Q_AUDIO_CLOSE_RECORDING: |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1717 | LOGFQUEUE("record < Q_AUDIO_CLOSE_RECORDING"); |
| 1718 | goto recording_done; |
Christian Gmeiner | 14e8067 | 2005-08-06 10:12:19 +0000 | [diff] [blame] | 1719 | |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1720 | case Q_AUDIO_RECORDING_OPTIONS: |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1721 | LOGFQUEUE("record < Q_AUDIO_RECORDING_OPTIONS"); |
| 1722 | on_recording_options((struct audio_recording_options *)ev->data); |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1723 | break; |
Christian Gmeiner | 14e8067 | 2005-08-06 10:12:19 +0000 | [diff] [blame] | 1724 | |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1725 | case Q_AUDIO_RECORD: |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1726 | LOGFQUEUE("record < Q_AUDIO_RECORD: %s", (const char *)ev->data); |
| 1727 | on_record((const char *)ev->data); |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1728 | break; |
Christian Gmeiner | 14e8067 | 2005-08-06 10:12:19 +0000 | [diff] [blame] | 1729 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1730 | case Q_AUDIO_RECORD_STOP: |
| 1731 | LOGFQUEUE("record < Q_AUDIO_RECORD_STOP"); |
| 1732 | on_record_stop(); |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1733 | break; |
Michael Sevakis | eca9f7f | 2006-12-10 14:21:31 +0000 | [diff] [blame] | 1734 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1735 | case Q_AUDIO_RECORD_PAUSE: |
| 1736 | LOGFQUEUE("record < Q_AUDIO_RECORD_PAUSE"); |
| 1737 | on_record_pause(); |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1738 | break; |
Christian Gmeiner | 14e8067 | 2005-08-06 10:12:19 +0000 | [diff] [blame] | 1739 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1740 | case Q_AUDIO_RECORD_RESUME: |
| 1741 | LOGFQUEUE("record < Q_AUDIO_RECORD_RESUME"); |
| 1742 | on_record_resume(); |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1743 | break; |
Christian Gmeiner | 14e8067 | 2005-08-06 10:12:19 +0000 | [diff] [blame] | 1744 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1745 | case Q_AUDIO_RECORD_FLUSH: |
| 1746 | watermark = 1; |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1747 | break; |
Michael Sevakis | 5857c44 | 2013-05-31 02:41:02 -0400 | [diff] [blame] | 1748 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1749 | case SYS_USB_CONNECTED: |
| 1750 | LOGFQUEUE("record < SYS_USB_CONNECTED"); |
| 1751 | if (record_state != REC_STATE_IDLE) |
| 1752 | { |
| 1753 | LOGFQUEUE(" still recording"); |
| 1754 | break; |
| 1755 | } |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 1756 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1757 | goto recording_done; |
| 1758 | } /* switch */ |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 1759 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1760 | int timeout; |
Peter D'Hoye | 528fe44 | 2008-10-08 22:18:16 +0000 | [diff] [blame] | 1761 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1762 | switch (record_state) |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1763 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1764 | case REC_STATE_FLUSH: |
| 1765 | case REC_STATE_MONITOR: |
| 1766 | do |
| 1767 | record_state = flush_chunk(record_state, watermark); |
| 1768 | while (record_state == REC_STATE_FLUSH && |
| 1769 | queue_empty(&audio_queue)); |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1770 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1771 | timeout = record_state == REC_STATE_FLUSH ? |
| 1772 | HZ*0 : HZ*FLUSH_MON_INTERVAL; |
| 1773 | break; |
| 1774 | case REC_STATE_IDLE: |
| 1775 | #ifdef HAVE_SPDIF_IN |
| 1776 | if (rec_source == AUDIO_SRC_SPDIF) |
| 1777 | { |
| 1778 | check_spdif_samplerate(); |
| 1779 | timeout = HZ/2; |
| 1780 | break; |
| 1781 | } |
| 1782 | #endif /* HAVE_SPDIF_IN */ |
| 1783 | default: |
| 1784 | timeout = TIMEOUT_BLOCK; |
| 1785 | break; |
Michael Sevakis | 5be5674 | 2007-09-30 15:55:34 +0000 | [diff] [blame] | 1786 | } |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 1787 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1788 | queue_wait_w_tmo(&audio_queue, ev, timeout); |
| 1789 | } /* while */ |
| 1790 | |
| 1791 | recording_done: |
| 1792 | on_close_recording(); |
| 1793 | #ifdef HAVE_PRIORITY_SCHEDULING |
| 1794 | /* Restore normal thread priorities */ |
| 1795 | thread_set_priority(audio_thread_id, old_prio); |
| 1796 | codec_thread_set_priority(old_cod_prio); |
| 1797 | #endif |
| 1798 | } |
| 1799 | |
| 1800 | |
| 1801 | /** Encoder callbacks **/ |
| 1802 | |
| 1803 | /* Read a block of unprocessed PCM data, with mono conversion if |
| 1804 | * num_channels == 1 |
| 1805 | * |
| 1806 | * NOTE: Request must be less than the PCM buffer length in samples in order |
| 1807 | * to progress. |
| 1808 | * (ie. count <= PCM_NUM_CHUNKS*PCM_CHUNK_SAMP) |
| 1809 | */ |
| 1810 | static int enc_pcmbuf_read(void *buffer, int count) |
| 1811 | { |
| 1812 | size_t avail = pcmbuf_used(); |
| 1813 | size_t size = count*PCM_SAMP_SIZE; |
| 1814 | |
| 1815 | if (count > 0 && avail >= size) |
| 1816 | { |
| 1817 | size_t endidx = pcm_ridx + size; |
| 1818 | |
| 1819 | if (endidx > PCM_BUF_SIZE) |
Michael Sevakis | 5323fe9 | 2007-11-30 05:16:56 +0000 | [diff] [blame] | 1820 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1821 | size_t wrap = endidx - PCM_BUF_SIZE; |
| 1822 | size_t offset = size -= wrap; |
| 1823 | |
| 1824 | if (num_channels == 1) |
| 1825 | offset /= 2; /* src offset -> dst offset */ |
| 1826 | |
| 1827 | pcm_copyfn(buffer + offset, pcmbuf_ptr(0), wrap); |
| 1828 | } |
| 1829 | |
| 1830 | pcm_copyfn(buffer, pcmbuf_ptr(pcm_ridx), size); |
| 1831 | |
| 1832 | if (avail >= sample_rate*PCM_SAMP_SIZE*PCM_BOOST_SECONDS || |
| 1833 | avail >= PCM_BUF_SIZE*1/2) |
| 1834 | { |
| 1835 | /* Filling up - boost threshold data available or more or 1/2 full |
| 1836 | or more - boost codec */ |
Michael Sevakis | 5323fe9 | 2007-11-30 05:16:56 +0000 | [diff] [blame] | 1837 | trigger_cpu_boost(); |
| 1838 | } |
| 1839 | |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1840 | pcm_buffer_empty = false; |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1841 | |
| 1842 | return count; |
Michael Sevakis | 4fc717a | 2006-08-28 22:38:41 +0000 | [diff] [blame] | 1843 | } |
| 1844 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1845 | /* Not enough data available - encoder should idle */ |
Michael Sevakis | ec1f482 | 2007-02-16 08:52:06 +0000 | [diff] [blame] | 1846 | pcm_buffer_empty = true; |
Michael Sevakis | 5323fe9 | 2007-11-30 05:16:56 +0000 | [diff] [blame] | 1847 | |
| 1848 | cancel_cpu_boost(); |
| 1849 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1850 | /* Sleep a little bit */ |
Michael Sevakis | 5323fe9 | 2007-11-30 05:16:56 +0000 | [diff] [blame] | 1851 | sleep(0); |
| 1852 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1853 | return 0; |
| 1854 | } |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1855 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1856 | /* Advance PCM buffer by count samples */ |
| 1857 | static int enc_pcmbuf_advance(int count) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1858 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1859 | if (count <= 0) |
| 1860 | return 0; |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1861 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1862 | size_t avail = pcmbuf_used(); |
| 1863 | size_t size = count*PCM_SAMP_SIZE; |
| 1864 | |
| 1865 | if (avail < size) |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1866 | { |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1867 | size = avail; |
| 1868 | count = size / PCM_SAMP_SIZE; |
Michael Sevakis | 0f5cb94 | 2006-11-06 18:07:30 +0000 | [diff] [blame] | 1869 | } |
| 1870 | |
Michael Sevakis | 4888131 | 2013-06-22 16:41:16 -0400 | [diff] [blame] | 1871 | pcm_ridx = pcmbuf_add(pcm_ridx, size); |
| 1872 | |
| 1873 | return count; |
| 1874 | } |
| 1875 | |
| 1876 | /* Return encoder chunk at current write position, wrapping to 0 if |
| 1877 | * requested size demands it. |
| 1878 | * |
| 1879 | * NOTE: No request should be more than 1/2 the buffer length, all elements |
| 1880 | * included, or progress will not be guaranteed. |
| 1881 | * (ie. CHUNK_DATA_COUNT(need) <= enc_buflen / 2) |
| 1882 | */ |
| 1883 | static struct enc_chunk_data * enc_encbuf_get_buffer(size_t need) |
| 1884 | { |
| 1885 | /* Convert to buffer slot count, including the header */ |
| 1886 | need = CHUNK_DATA_COUNT(need); |
| 1887 | |
| 1888 | enum record_state state = record_state; |
| 1889 | size_t avail = encbuf_free(); |
| 1890 | |
| 1891 | /* Must have the split margin as well but it does not have to be |
| 1892 | continuous with the request */ |
| 1893 | while (avail <= need + ENCBUF_MIN_SPLIT_MARGIN || |
| 1894 | (enc_widx + need > enc_buflen && |
| 1895 | enc_ridx <= need + ENCBUF_MIN_SPLIT_MARGIN)) |
| 1896 | { |
| 1897 | if (UNLIKELY(state == REC_STATE_IDLE)) |
| 1898 | { |
| 1899 | /* Prerecording - delete some old data */ |
| 1900 | size_t ridx; |
| 1901 | struct enc_chunk_data *data = |
| 1902 | encbuf_read_ptr_incr(enc_ridx, &ridx); |
| 1903 | |
| 1904 | if (data) |
| 1905 | { |
| 1906 | encbuf_rec_count -= CHUNK_DATA_COUNT(data->hdr.size); |
| 1907 | num_rec_bytes -= data->hdr.size; |
| 1908 | num_rec_samples -= data->pcm_count; |
| 1909 | } |
| 1910 | |
| 1911 | enc_ridx = ridx; |
| 1912 | avail = encbuf_free(); |
| 1913 | continue; |
| 1914 | } |
| 1915 | else if (avail == enc_buflen) |
| 1916 | { |
| 1917 | /* Empty but request larger than any possible space */ |
| 1918 | raise_warning_status(PCMREC_W_ENC_BUFFER_OVF); |
| 1919 | } |
| 1920 | else if (state != REC_STATE_FLUSH && encbuf_used() < high_watermark) |
| 1921 | { |
| 1922 | /* Not yet even at high watermark but what's needed won't fit */ |
| 1923 | encbuf_request_flush(); |
| 1924 | } |
| 1925 | |
| 1926 | sleep(0); |
| 1927 | return NULL; |
| 1928 | } |
| 1929 | |
| 1930 | struct enc_chunk_data *data = |
| 1931 | encbuf_get_write_ptr(enc_widx, need, &enc_widx); |
| 1932 | |
| 1933 | if (state == REC_STATE_IDLE) |
| 1934 | data->hdr.pre = 1; |
| 1935 | |
| 1936 | return data; |
| 1937 | } |
| 1938 | |
| 1939 | /* Releases the current buffer into the available chunks */ |
| 1940 | static void enc_encbuf_finish_buffer(void) |
| 1941 | { |
| 1942 | struct enc_chunk_data *data = ENC_DATA_HDR(encbuf_ptr(enc_widx)); |
| 1943 | |
| 1944 | if (data->hdr.err) |
| 1945 | { |
| 1946 | /* Encoder set error flag */ |
| 1947 | raise_error_status(PCMREC_E_ENCODER); |
| 1948 | return; |
| 1949 | } |
| 1950 | |
| 1951 | size_t data_size = data->hdr.size; |
| 1952 | |
| 1953 | if (data_size == 0) |
| 1954 | return; /* Claims nothing was written */ |
| 1955 | |
| 1956 | size_t count = CHUNK_DATA_COUNT(data_size); |
| 1957 | size_t avail = encbuf_free(); |
| 1958 | |
| 1959 | if (avail <= count || enc_widx + count > enc_buflen) |
| 1960 | { |
| 1961 | /* Claims it wrote too much? */ |
| 1962 | raise_warning_status(PCMREC_W_ENC_BUFFER_OVF); |
| 1963 | return; |
| 1964 | } |
| 1965 | |
| 1966 | if (num_rec_bytes + data_size > MAX_NUM_REC_BYTES) |
| 1967 | { |
| 1968 | /* Would exceed filesize limit; should have split sooner. |
| 1969 | This chunk will be dropped. :'( */ |
| 1970 | raise_warning_status(PCMREC_W_FILE_SIZE); |
| 1971 | return; |
| 1972 | } |
| 1973 | |
| 1974 | encbuf_widx_advance(enc_widx, count); |
| 1975 | |
| 1976 | encbuf_rec_count += count; |
| 1977 | num_rec_bytes += data_size; |
| 1978 | num_rec_samples += data->pcm_count; |
| 1979 | } |
| 1980 | |
| 1981 | /* Read from the output stream */ |
| 1982 | static ssize_t enc_stream_read(void *buf, size_t count) |
| 1983 | { |
| 1984 | if (!stream_flush_buf()) |
| 1985 | return -1; |
| 1986 | |
| 1987 | return read(rec_fd, buf, count); |
| 1988 | } |
| 1989 | |
| 1990 | /* Seek the output steam */ |
| 1991 | static off_t enc_stream_lseek(off_t offset, int whence) |
| 1992 | { |
| 1993 | if (!stream_flush_buf()) |
| 1994 | return -1; |
| 1995 | |
| 1996 | return lseek(rec_fd, offset, whence); |
| 1997 | } |
| 1998 | |
| 1999 | /* Write to the output stream */ |
| 2000 | static ssize_t enc_stream_write(const void *buf, size_t count) |
| 2001 | { |
| 2002 | if (UNLIKELY(count >= STREAM_BUF_SIZE)) |
| 2003 | { |
| 2004 | /* Too big to buffer */ |
| 2005 | if (stream_flush_buf()) |
| 2006 | return write(rec_fd, buf, count); |
| 2007 | } |
| 2008 | |
| 2009 | if (!count) |
| 2010 | return 0; |
| 2011 | |
| 2012 | if (stream_buf_used + count > STREAM_BUF_SIZE) |
| 2013 | { |
| 2014 | if (!stream_flush_buf() && stream_buf_used + count > STREAM_BUF_SIZE) |
| 2015 | count = STREAM_BUF_SIZE - stream_buf_used; |
| 2016 | } |
| 2017 | |
| 2018 | memcpy(stream_buffer + stream_buf_used, buf, count); |
| 2019 | stream_buf_used += count; |
| 2020 | |
| 2021 | return count; |
| 2022 | } |
| 2023 | |
| 2024 | /* One-time init at startup */ |
| 2025 | void INIT_ATTR recording_init(void) |
| 2026 | { |
| 2027 | /* Init API */ |
| 2028 | ci.enc_pcmbuf_read = enc_pcmbuf_read; |
| 2029 | ci.enc_pcmbuf_advance = enc_pcmbuf_advance; |
| 2030 | ci.enc_encbuf_get_buffer = enc_encbuf_get_buffer; |
| 2031 | ci.enc_encbuf_finish_buffer = enc_encbuf_finish_buffer; |
| 2032 | ci.enc_stream_read = enc_stream_read; |
| 2033 | ci.enc_stream_lseek = enc_stream_lseek; |
| 2034 | ci.enc_stream_write = enc_stream_write; |
| 2035 | } |