summaryrefslogtreecommitdiff
path: root/lib/rbcodec/codecs/wav_enc.c
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2013-06-22 16:41:16 -0400
committerMichael Sevakis <jethead71@rockbox.org>2013-06-30 00:40:27 +0200
commit488813197292bd1db8d533d7b42c38852971c2e8 (patch)
tree07ea7247799b1b6b487c5ca73311380fc947700e /lib/rbcodec/codecs/wav_enc.c
parenta9ea1a42695401334717f2e497a7f5576d87691d (diff)
downloadrockbox-488813197292bd1db8d533d7b42c38852971c2e8.tar.gz
rockbox-488813197292bd1db8d533d7b42c38852971c2e8.zip
Update software recording engine to latest codec interface.
Basically, just give it a good rewrite. Software codec recording can be implemented in a more straightforward and simple manner and made more robust through the better codec control now available. Encoded audio buffer uses a packed format instead of fixed-size chunks and uses smaller data headers leading to more efficient usage. The greatest benefit is with a VBR format like wavpack which needs to request a maximum size but only actually ends up committing part of that request. No guard buffers are used for either PCM or encoded audio. PCM is read into the codec's provided buffer and mono conversion done at that time in the core if required. Any highly-specialized sample conversion is still done within the codec itself, such as 32-bit (wavpack) or interleaved mono (mp3). There is no longer a separate filename array. All metadata goes onto the main encoded audio buffer, eliminating any predermined file limit on the buffer as well as not wasting the space for unused path queue slots. The core and codec interface is less awkward and a bit more sensible. Some less useful interface features were removed. Threads are kept on narrow code paths ie. the audio thread never calls encoding functions and the codec thread never calls file functions as before. Codecs no longer call file functions directly. Writes are buffered in the core and data written to storage in larger chunks to speed up flushing of data. In fact, codecs are no longer aware of the stream being a file at all and have no access to the fd. SPDIF frequency detection no longer requires a restart of recording or plugging the source before entering the screen. It will poll for changes and update when stopped or prerecording (which does discard now-invalid prerecorded data). I've seen to it that writing a proper header on full disk works when the format makes it reasonably practical to do so. Other cases may have incorrect data sizes but sample info will be in tact. File left that way may play anyway. mp3_enc.codec acquires the ability to write 'Info' headers with LAME tags to make it gapless (bonus). Change-Id: I670685166d5eb32ef58ef317f50b8af766ceb653 Reviewed-on: http://gerrit.rockbox.org/493 Reviewed-by: Michael Sevakis <jethead71@rockbox.org> Tested-by: Michael Sevakis <jethead71@rockbox.org>
Diffstat (limited to 'lib/rbcodec/codecs/wav_enc.c')
-rw-r--r--lib/rbcodec/codecs/wav_enc.c376
1 files changed, 117 insertions, 259 deletions
diff --git a/lib/rbcodec/codecs/wav_enc.c b/lib/rbcodec/codecs/wav_enc.c
index 01d0f79bcf..71bb652374 100644
--- a/lib/rbcodec/codecs/wav_enc.c
+++ b/lib/rbcodec/codecs/wav_enc.c
@@ -8,6 +8,7 @@
8 * $Id$ 8 * $Id$
9 * 9 *
10 * Copyright (C) 2006 Antonius Hellmann 10 * Copyright (C) 2006 Antonius Hellmann
11 * Copyright (C) 2006-2013 Michael Sevakis
11 * 12 *
12 * This program is free software; you can redistribute it and/or 13 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License 14 * modify it under the terms of the GNU General Public License
@@ -40,12 +41,12 @@ struct riff_header
40 uint16_t block_align; /* 20h - num_channels*bits_per_samples/8 */ 41 uint16_t block_align; /* 20h - num_channels*bits_per_samples/8 */
41 uint16_t bits_per_sample; /* 22h - 8=8 bits, 16=16 bits, etc. */ 42 uint16_t bits_per_sample; /* 22h - 8=8 bits, 16=16 bits, etc. */
42 /* Not for audio_format=1 (PCM) */ 43 /* Not for audio_format=1 (PCM) */
43/* unsigned short extra_param_size; 24h - size of extra data */ 44/* uint16_t extra_param_size; 24h - size of extra data */
44/* unsigned char *extra_params; */ 45/* uint8_t extra_params[extra_param_size]; */
45 /* data header */ 46 /* data header */
46 uint8_t data_id[4]; /* 24h - "data" */ 47 uint8_t data_id[4]; /* 24h - "data" */
47 uint32_t data_size; /* 28h - num_samples*num_channels*bits_per_sample/8 */ 48 uint32_t data_size; /* 28h - num_samples*num_channels*bits_per_sample/8 */
48/* unsigned char *data; 2ch - actual sound data */ 49/* uint8_t data[data_size]; 2Ch - actual sound data */
49} __attribute__((packed)); 50} __attribute__((packed));
50 51
51#define RIFF_FMT_HEADER_SIZE 12 /* format -> format_size */ 52#define RIFF_FMT_HEADER_SIZE 12 /* format -> format_size */
@@ -55,19 +56,17 @@ struct riff_header
55#define PCM_DEPTH_BYTES 2 56#define PCM_DEPTH_BYTES 2
56#define PCM_DEPTH_BITS 16 57#define PCM_DEPTH_BITS 16
57#define PCM_SAMP_PER_CHUNK 2048 58#define PCM_SAMP_PER_CHUNK 2048
58#define PCM_CHUNK_SIZE (PCM_SAMP_PER_CHUNK*4)
59 59
60static int num_channels IBSS_ATTR; 60static int num_channels;
61static int rec_mono_mode IBSS_ATTR;
62static uint32_t sample_rate; 61static uint32_t sample_rate;
63static uint32_t enc_size; 62static size_t frame_size;
64static int32_t err IBSS_ATTR; 63static size_t data_size;
65 64
66static const struct riff_header riff_header = 65static const struct riff_header riff_template_header =
67{ 66{
68 /* "RIFF" header */ 67 /* "RIFF" header */
69 { 'R', 'I', 'F', 'F' }, /* riff_id */ 68 { 'R', 'I', 'F', 'F' }, /* riff_id */
70 0, /* riff_size (*) */ 69 0, /* riff_size (*) */
71 /* format header */ 70 /* format header */
72 { 'W', 'A', 'V', 'E' }, /* format */ 71 { 'W', 'A', 'V', 'E' }, /* format */
73 { 'f', 'm', 't', ' ' }, /* format_id */ 72 { 'f', 'm', 't', ' ' }, /* format_id */
@@ -82,305 +81,164 @@ static const struct riff_header riff_header =
82 /* data header */ 81 /* data header */
83 { 'd', 'a', 't', 'a' }, /* data_id */ 82 { 'd', 'a', 't', 'a' }, /* data_id */
84 0 /* data_size (*) */ 83 0 /* data_size (*) */
85 /* (*) updated during ENC_END_FILE event */ 84 /* (*) updated when finalizing stream */
86}; 85};
87 86
88/* called version often - inline */ 87static inline void frame_htole(uint32_t *p, size_t size)
89static inline bool is_file_data_ok(struct enc_file_event_data *data) ICODE_ATTR;
90static inline bool is_file_data_ok(struct enc_file_event_data *data)
91{ 88{
92 return data->rec_file >= 0 && (long)data->chunk->flags >= 0; 89#ifdef ROCKBOX_BIG_ENDIAN
93} /* is_file_data_ok */ 90 /* Byte-swap samples, stereo or mono */
94 91 do
95/* called version often - inline */
96static inline bool on_write_chunk(struct enc_file_event_data *data) ICODE_ATTR;
97static inline bool on_write_chunk(struct enc_file_event_data *data)
98{
99 if (!is_file_data_ok(data))
100 return false;
101
102 if (data->chunk->enc_data == NULL)
103 { 92 {
104#ifdef ROCKBOX_HAS_LOGF 93 uint32_t t;
105 ci->logf("wav enc: NULL data"); 94 t = swap_odd_even32(*p); *p++ = t;
106#endif 95 t = swap_odd_even32(*p); *p++ = t;
107 return true; 96 t = swap_odd_even32(*p); *p++ = t;
97 t = swap_odd_even32(*p); *p++ = t;
98 t = swap_odd_even32(*p); *p++ = t;
99 t = swap_odd_even32(*p); *p++ = t;
100 t = swap_odd_even32(*p); *p++ = t;
101 t = swap_odd_even32(*p); *p++ = t;
108 } 102 }
103 while (size -= 8 * 2 * PCM_DEPTH_BYTES);
104#endif /* ROCKBOX_BIG_ENDIAN */
105 (void)p; (void)size;
106}
109 107
110 if (ci->write(data->rec_file, data->chunk->enc_data, 108static int on_stream_data(struct enc_chunk_data *data)
111 data->chunk->enc_size) != (ssize_t)data->chunk->enc_size)
112 return false;
113
114 data->num_pcm_samples += data->chunk->num_pcm;
115 return true;
116} /* on_write_chunk */
117
118static bool on_start_file(struct enc_file_event_data *data)
119{ 109{
120 if ((data->chunk->flags & CHUNKF_ERROR) || *data->filename == '\0') 110 size_t size = data->hdr.size;
121 return false; 111
112 if (ci->enc_stream_write(data->data, size) != (ssize_t)size)
113 return -1;
122 114
123 data->rec_file = ci->open(data->filename, O_RDWR|O_CREAT|O_TRUNC, 0666); 115 data_size += size;
124 116
125 if (data->rec_file < 0) 117 return 0;
126 return false; 118}
127 119
120static int on_stream_start(void)
121{
128 /* reset sample count */ 122 /* reset sample count */
129 data->num_pcm_samples = 0; 123 data_size = 0;
130 124
131 /* write template header */ 125 /* write template header */
132 if (ci->write(data->rec_file, &riff_header, sizeof (riff_header)) 126 if (ci->enc_stream_write(&riff_template_header, sizeof (struct riff_header))
133 != sizeof (riff_header)) 127 != sizeof (struct riff_header))
134 { 128 return -1;
135 return false;
136 }
137 129
138 data->new_enc_size += sizeof (riff_header); 130 return 0;
139 return true; 131}
140} /* on_start_file */
141 132
142static bool on_end_file(struct enc_file_event_data *data) 133static int on_stream_end(union enc_chunk_hdr *hdr)
143{ 134{
144 /* update template header */ 135 /* update template header */
145 struct riff_header hdr; 136 struct riff_header riff;
146 uint32_t data_size;
147
148 if (data->rec_file < 0)
149 return false; /* file already closed, nothing more we can do */
150 137
151 /* always _try_ to write the file header, even on error */ 138 if (hdr->err)
152 if ((ci->lseek(data->rec_file, 0, SEEK_SET)) ||
153 (ci->read(data->rec_file, &hdr, sizeof (hdr)) != sizeof (hdr)))
154 { 139 {
155 return false; 140 /* Called for stream error; get correct data size */
141 ssize_t size = ci->enc_stream_lseek(0, SEEK_END);
142
143 if (size > (ssize_t)sizeof (riff))
144 data_size = size - sizeof (riff);
156 } 145 }
157 146
158 data_size = data->num_pcm_samples*num_channels*PCM_DEPTH_BYTES; 147 if (ci->enc_stream_lseek(0, SEEK_SET) ||
148 ci->enc_stream_read(&riff, sizeof (riff)) != sizeof (riff))
149 return -1;
159 150
160 /* "RIFF" header */ 151 /* "RIFF" header */
161 hdr.riff_size = htole32(RIFF_FMT_HEADER_SIZE + RIFF_FMT_DATA_SIZE 152 riff.riff_size = htole32(RIFF_FMT_HEADER_SIZE + RIFF_FMT_DATA_SIZE
162 + RIFF_DATA_HEADER_SIZE + data_size); 153 + RIFF_DATA_HEADER_SIZE + data_size);
163 154
164 /* format data */ 155 /* format data */
165 hdr.num_channels = htole16(num_channels); 156 riff.num_channels = htole16(num_channels);
166 hdr.sample_rate = htole32(sample_rate); 157 riff.sample_rate = htole32(sample_rate);
167 hdr.byte_rate = htole32(sample_rate*num_channels* PCM_DEPTH_BYTES); 158 riff.byte_rate = htole32(sample_rate*num_channels*PCM_DEPTH_BYTES);
168 hdr.block_align = htole16(num_channels*PCM_DEPTH_BYTES); 159 riff.block_align = htole16(num_channels*PCM_DEPTH_BYTES);
169 160
170 /* data header */ 161 /* data header */
171 hdr.data_size = htole32(data_size); 162 riff.data_size = htole32(data_size);
172 163
173 if (ci->lseek(data->rec_file, 0, SEEK_SET) != 0 || 164 if (ci->enc_stream_lseek(0, SEEK_SET) != 0)
174 ci->write(data->rec_file, &hdr, sizeof (hdr)) != sizeof (hdr) || 165 return -2;
175 ci->close(data->rec_file) != 0)
176 {
177 return false;
178 }
179 166
180 data->rec_file = -1; 167 if (ci->enc_stream_write(&riff, sizeof (riff)) != sizeof (riff))
168 return -3;
181 169
182 return true; 170 return 0;
183} /* on_end_file */ 171}
184 172
185static void enc_events_callback(enum enc_events event, void *data) 173/* this is the codec entry point */
186 ICODE_ATTR; 174enum codec_status codec_main(enum codec_entry_call_reason reason)
187static void enc_events_callback(enum enc_events event, void *data)
188{ 175{
189 switch (event) 176 return CODEC_OK;
190 { 177 (void)reason;
191 case ENC_WRITE_CHUNK: 178}
192 if (on_write_chunk((struct enc_file_event_data *)data))
193 return;
194
195 break;
196
197 case ENC_START_FILE:
198 if (on_start_file((struct enc_file_event_data *)data))
199 return;
200
201 break;
202
203 case ENC_END_FILE:
204 if (on_end_file((struct enc_file_event_data *)data))
205 return;
206
207 break;
208
209 default:
210 return;
211 }
212
213 /* Something failed above. Signal error back to core. */
214 ((struct enc_file_event_data *)data)->chunk->flags |= CHUNKF_ERROR;
215} /* enc_events_callback */
216 179
217/* convert native pcm samples to wav format samples */ 180/* this is called for each file to process */
218static inline void sample_to_mono(uint32_t **src, uint32_t **dst) 181enum codec_status ICODE_ATTR codec_run(void)
219{ 182{
220 int32_t lr1, lr2; 183 enum { GETBUF_ENC, GETBUF_PCM } getbuf = GETBUF_ENC;
184 struct enc_chunk_data *data = NULL;
221 185
222 switch(rec_mono_mode) 186 /* main encoding loop */
187 while (1)
223 { 188 {
224 case 1: 189 enum codec_command_action action = ci->get_command(NULL);
225 /* mono = L */ 190
226 lr1 = *(*src)++; 191 if (action != CODEC_ACTION_NULL)
227 lr1 = lr1 >> 16;
228 lr2 = *(*src)++;
229 lr2 = lr2 >> 16;
230 break;
231 case 2:
232 /* mono = R */
233 lr1 = *(*src)++;
234 lr1 = (uint16_t)lr1;
235 lr2 = *(*src)++;
236 lr2 = (uint16_t)lr2;
237 break;
238 case 0:
239 default:
240 /* mono = (L+R)/2 */
241 lr1 = *(*src)++;
242 lr1 = (int16_t)lr1 + (lr1 >> 16) + err;
243 err = lr1 & 1;
244 lr1 >>= 1;
245
246 lr2 = *(*src)++;
247 lr2 = (int16_t)lr2 + (lr2 >> 16) + err;
248 err = lr2 & 1;
249 lr2 >>= 1;
250 break; 192 break;
251 }
252 *(*dst)++ = htole32((lr2 << 16) | (uint16_t)lr1);
253} /* sample_to_mono */
254 193
255static void chunk_to_wav_format(uint32_t *src, uint32_t *dst) ICODE_ATTR; 194 /* First obtain output buffer; when available, get PCM data */
256static void chunk_to_wav_format(uint32_t *src, uint32_t *dst) 195 switch (getbuf)
257{
258 if (num_channels == 1)
259 {
260 /* On big endian:
261 * |LLLLLLLLllllllll|RRRRRRRRrrrrrrrr|
262 * |LLLLLLLLllllllll|RRRRRRRRrrrrrrrr| =>
263 * |mmmmmmmmMMMMMMMM|mmmmmmmmMMMMMMMM|
264 *
265 * On little endian:
266 * |llllllllLLLLLLLL|rrrrrrrrRRRRRRRR|
267 * |llllllllLLLLLLLL|rrrrrrrrRRRRRRRR| =>
268 * |mmmmmmmmMMMMMMMM|mmmmmmmmMMMMMMMM|
269 */
270 uint32_t *src_end = src + PCM_SAMP_PER_CHUNK;
271
272 do
273 { 196 {
274 sample_to_mono(&src, &dst); 197 case GETBUF_ENC:
275 sample_to_mono(&src, &dst); 198 if (!(data = ci->enc_encbuf_get_buffer(frame_size)))
276 sample_to_mono(&src, &dst); 199 continue;
277 sample_to_mono(&src, &dst); 200 getbuf = GETBUF_PCM;
278 sample_to_mono(&src, &dst); 201 case GETBUF_PCM:
279 sample_to_mono(&src, &dst); 202 if (!ci->enc_pcmbuf_read(data->data, PCM_SAMP_PER_CHUNK))
280 sample_to_mono(&src, &dst); 203 continue;
281 sample_to_mono(&src, &dst); 204 getbuf = GETBUF_ENC;
282 } 205 }
283 while (src < src_end);
284 }
285 else
286 {
287#ifdef ROCKBOX_BIG_ENDIAN
288 /* |LLLLLLLLllllllll|RRRRRRRRrrrrrrrr| =>
289 * |llllllllLLLLLLLL|rrrrrrrrRRRRRRRR|
290 */
291 uint32_t *src_end = src + PCM_SAMP_PER_CHUNK;
292 206
293 do 207 data->hdr.size = frame_size;
294 { 208 data->pcm_count = PCM_SAMP_PER_CHUNK;
295 *dst++ = swap_odd_even32(*src++);
296 *dst++ = swap_odd_even32(*src++);
297 *dst++ = swap_odd_even32(*src++);
298 *dst++ = swap_odd_even32(*src++);
299 *dst++ = swap_odd_even32(*src++);
300 *dst++ = swap_odd_even32(*src++);
301 *dst++ = swap_odd_even32(*src++);
302 *dst++ = swap_odd_even32(*src++);
303 }
304 while (src < src_end);
305#else
306 /* |llllllllLLLLLLLL|rrrrrrrrRRRRRRRR| =>
307 * |llllllllLLLLLLLL|rrrrrrrrRRRRRRRR|
308 */
309 ci->memcpy(dst, src, PCM_CHUNK_SIZE);
310#endif
311 }
312} /* chunk_to_wav_format */
313 209
314static bool init_encoder(void) 210 frame_htole((uint32_t *)data->data, frame_size);
315{
316 struct enc_inputs inputs;
317 struct enc_parameters params;
318
319 if (ci->enc_get_inputs == NULL ||
320 ci->enc_set_parameters == NULL ||
321 ci->enc_get_chunk == NULL ||
322 ci->enc_finish_chunk == NULL ||
323 ci->enc_get_pcm_data == NULL )
324 return false;
325
326 ci->enc_get_inputs(&inputs);
327
328 if (inputs.config->afmt != AFMT_PCM_WAV)
329 return false;
330
331 sample_rate = inputs.sample_rate;
332 num_channels = inputs.num_channels;
333 rec_mono_mode = inputs.rec_mono_mode;
334 err = 0;
335
336 /* configure the buffer system */
337 params.afmt = AFMT_PCM_WAV;
338 enc_size = PCM_CHUNK_SIZE*inputs.num_channels / 2;
339 params.chunk_size = enc_size;
340 params.enc_sample_rate = sample_rate;
341 params.reserve_bytes = 0;
342 params.events_callback = enc_events_callback;
343 ci->enc_set_parameters(&params);
344
345 return true;
346} /* init_encoder */
347 211
348/* this is the codec entry point */ 212 ci->enc_pcmbuf_advance(PCM_SAMP_PER_CHUNK);
349enum codec_status codec_main(enum codec_entry_call_reason reason) 213 ci->enc_encbuf_finish_buffer();
350{
351 if (reason == CODEC_LOAD) {
352 if (!init_encoder())
353 return CODEC_ERROR;
354 }
355 else if (reason == CODEC_UNLOAD) {
356 /* reset parameters to initial state */
357 ci->enc_set_parameters(NULL);
358 } 214 }
359 215
360 return CODEC_OK; 216 return CODEC_OK;
361} 217}
362 218
363/* this is called for each file to process */ 219/* this is called by recording system */
364enum codec_status codec_run(void) 220int ICODE_ATTR enc_callback(enum enc_callback_reason reason,
221 void *params)
365{ 222{
366 /* main encoding loop */ 223 if (LIKELY(reason == ENC_CB_STREAM))
367 while(ci->get_command(NULL) != CODEC_ACTION_HALT)
368 { 224 {
369 uint32_t *src = (uint32_t *)ci->enc_get_pcm_data(PCM_CHUNK_SIZE); 225 switch (((union enc_chunk_hdr *)params)->type)
370 struct enc_chunk_hdr *chunk; 226 {
371 227 case CHUNK_T_DATA:
372 if(src == NULL) 228 return on_stream_data(params);
373 continue; 229 case CHUNK_T_STREAM_START:
374 230 return on_stream_start();
375 chunk = ci->enc_get_chunk(); 231 case CHUNK_T_STREAM_END:
376 chunk->enc_size = enc_size; 232 return on_stream_end(params);
377 chunk->num_pcm = PCM_SAMP_PER_CHUNK; 233 }
378 chunk->enc_data = ENC_CHUNK_SKIP_HDR(chunk->enc_data, chunk); 234 }
379 235 else if (reason == ENC_CB_INPUTS)
380 chunk_to_wav_format(src, (uint32_t *)chunk->enc_data); 236 {
381 237 struct enc_inputs *inputs = params;
382 ci->enc_finish_chunk(); 238 sample_rate = inputs->sample_rate;
239 num_channels = inputs->num_channels;
240 frame_size = PCM_SAMP_PER_CHUNK*PCM_DEPTH_BYTES*num_channels;
383 } 241 }
384 242
385 return CODEC_OK; 243 return 0;
386} 244}