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