diff options
-rw-r--r-- | apps/audio_thread.c | 14 | ||||
-rw-r--r-- | apps/audio_thread.h | 9 | ||||
-rw-r--r-- | apps/codec_thread.c | 59 | ||||
-rw-r--r-- | apps/codec_thread.h | 3 | ||||
-rw-r--r-- | apps/codecs.c | 43 | ||||
-rw-r--r-- | apps/playback.c | 12 | ||||
-rw-r--r-- | apps/recorder/pcm_record.c | 2924 | ||||
-rw-r--r-- | apps/recorder/pcm_record.h | 74 | ||||
-rw-r--r-- | apps/recorder/recording.c | 13 | ||||
-rw-r--r-- | firmware/export/audio.h | 5 | ||||
-rw-r--r-- | firmware/export/enc_base.h | 204 | ||||
-rw-r--r-- | lib/rbcodec/codecs/aiff_enc.c | 399 | ||||
-rw-r--r-- | lib/rbcodec/codecs/codecs.h | 45 | ||||
-rw-r--r-- | lib/rbcodec/codecs/mp3_enc.c | 813 | ||||
-rw-r--r-- | lib/rbcodec/codecs/wav_enc.c | 376 | ||||
-rw-r--r-- | lib/rbcodec/codecs/wavpack_enc.c | 453 |
16 files changed, 2659 insertions, 2787 deletions
diff --git a/apps/audio_thread.c b/apps/audio_thread.c index 397d8b0946..bbf73470d2 100644 --- a/apps/audio_thread.c +++ b/apps/audio_thread.c | |||
@@ -108,6 +108,16 @@ static void NORETURN_ATTR audio_thread(void) | |||
108 | } | 108 | } |
109 | } | 109 | } |
110 | 110 | ||
111 | void audio_queue_post(long id, intptr_t data) | ||
112 | { | ||
113 | queue_post(&audio_queue, id, data); | ||
114 | } | ||
115 | |||
116 | intptr_t audio_queue_send(long id, intptr_t data) | ||
117 | { | ||
118 | return queue_send(&audio_queue, id, data); | ||
119 | } | ||
120 | |||
111 | /* Return the playback and recording status */ | 121 | /* Return the playback and recording status */ |
112 | int audio_status(void) | 122 | int audio_status(void) |
113 | { | 123 | { |
@@ -156,7 +166,9 @@ void audio_init(void) | |||
156 | audio_thread_id); | 166 | audio_thread_id); |
157 | 167 | ||
158 | playback_init(); | 168 | playback_init(); |
159 | /* Recording doesn't need init call */ | 169 | #ifdef AUDIO_HAVE_RECORDING |
170 | recording_init(); | ||
171 | #endif | ||
160 | 172 | ||
161 | /* ...now...audio_reset_buffer must know the size of voicefile buffer so | 173 | /* ...now...audio_reset_buffer must know the size of voicefile buffer so |
162 | init talk first which will init the buffers */ | 174 | init talk first which will init the buffers */ |
diff --git a/apps/audio_thread.h b/apps/audio_thread.h index 7e0ad28849..57edf2b5ec 100644 --- a/apps/audio_thread.h +++ b/apps/audio_thread.h | |||
@@ -74,7 +74,10 @@ enum | |||
74 | Q_AUDIO_CLOSE_RECORDING, | 74 | Q_AUDIO_CLOSE_RECORDING, |
75 | Q_AUDIO_RECORDING_OPTIONS, | 75 | Q_AUDIO_RECORDING_OPTIONS, |
76 | Q_AUDIO_RECORD, | 76 | Q_AUDIO_RECORD, |
77 | Q_AUDIO_RESUME, | 77 | Q_AUDIO_RECORD_STOP, |
78 | Q_AUDIO_RECORD_PAUSE, | ||
79 | Q_AUDIO_RECORD_RESUME, | ||
80 | Q_AUDIO_RECORD_FLUSH, | ||
78 | #endif | 81 | #endif |
79 | 82 | ||
80 | /*- settings -*/ | 83 | /*- settings -*/ |
@@ -99,4 +102,8 @@ void audio_playback_handler(struct queue_event *ev); | |||
99 | void audio_recording_handler(struct queue_event *ev); | 102 | void audio_recording_handler(struct queue_event *ev); |
100 | #endif | 103 | #endif |
101 | 104 | ||
105 | /** --- audio_queue helpers --- **/ | ||
106 | void audio_queue_post(long id, intptr_t data); | ||
107 | intptr_t audio_queue_send(long id, intptr_t data); | ||
108 | |||
102 | #endif /* AUDIO_THREAD_H */ | 109 | #endif /* AUDIO_THREAD_H */ |
diff --git a/apps/codec_thread.c b/apps/codec_thread.c index 54bc28e19a..1a0b0b255c 100644 --- a/apps/codec_thread.c +++ b/apps/codec_thread.c | |||
@@ -249,7 +249,7 @@ static void codec_pcmbuf_insert_callback( | |||
249 | return; /* No input remains and DSP purged */ | 249 | return; /* No input remains and DSP purged */ |
250 | } | 250 | } |
251 | } | 251 | } |
252 | } | 252 | } |
253 | } | 253 | } |
254 | 254 | ||
255 | /* helper function, not a callback */ | 255 | /* helper function, not a callback */ |
@@ -365,10 +365,14 @@ static enum codec_command_action | |||
365 | 365 | ||
366 | queue_peek(&codec_queue, &ev); /* Find out what it is */ | 366 | queue_peek(&codec_queue, &ev); /* Find out what it is */ |
367 | 367 | ||
368 | long id = ev.id; | 368 | intptr_t id = ev.id; |
369 | 369 | ||
370 | switch (id) | 370 | switch (id) |
371 | { | 371 | { |
372 | case Q_NULL: | ||
373 | LOGFQUEUE("codec < Q_NULL"); | ||
374 | break; | ||
375 | |||
372 | case Q_CODEC_RUN: /* Already running */ | 376 | case Q_CODEC_RUN: /* Already running */ |
373 | LOGFQUEUE("codec < Q_CODEC_RUN"); | 377 | LOGFQUEUE("codec < Q_CODEC_RUN"); |
374 | break; | 378 | break; |
@@ -388,8 +392,25 @@ static enum codec_command_action | |||
388 | break; | 392 | break; |
389 | 393 | ||
390 | case Q_CODEC_STOP: /* Must only return 0 in main loop */ | 394 | case Q_CODEC_STOP: /* Must only return 0 in main loop */ |
391 | LOGFQUEUE("codec < Q_CODEC_STOP"); | 395 | LOGFQUEUE("codec < Q_CODEC_STOP: %ld", ev.data); |
392 | dsp_configure(ci.dsp, DSP_FLUSH, 0); /* Discontinuity */ | 396 | #ifdef HAVE_RECORDING |
397 | if (type_is_encoder(codec_type)) | ||
398 | { | ||
399 | /* Stream finish request (soft stop)? */ | ||
400 | if (ev.data && param) | ||
401 | { | ||
402 | /* ev.data is pointer to size */ | ||
403 | *param = ev.data; | ||
404 | action = CODEC_ACTION_STREAM_FINISH; | ||
405 | break; | ||
406 | } | ||
407 | } | ||
408 | else | ||
409 | #endif /* HAVE_RECORDING */ | ||
410 | { | ||
411 | dsp_configure(ci.dsp, DSP_FLUSH, 0); /* Discontinuity */ | ||
412 | } | ||
413 | |||
393 | return CODEC_ACTION_HALT; /* Leave in queue */ | 414 | return CODEC_ACTION_HALT; /* Leave in queue */ |
394 | 415 | ||
395 | default: /* This is in error in this context. */ | 416 | default: /* This is in error in this context. */ |
@@ -459,7 +480,8 @@ static void load_codec(const struct codec_load_info *ev_data) | |||
459 | } | 480 | } |
460 | } | 481 | } |
461 | 482 | ||
462 | if (status >= 0) | 483 | /* Types must agree */ |
484 | if (status >= 0 && encoder == !!codec_get_enc_callback()) | ||
463 | { | 485 | { |
464 | codec_type = data.afmt; | 486 | codec_type = data.afmt; |
465 | codec_queue_ack(Q_CODEC_LOAD); | 487 | codec_queue_ack(Q_CODEC_LOAD); |
@@ -558,7 +580,7 @@ static void NORETURN_ATTR codec_thread(void) | |||
558 | { | 580 | { |
559 | struct queue_event ev; | 581 | struct queue_event ev; |
560 | 582 | ||
561 | while (1) | 583 | while (1) |
562 | { | 584 | { |
563 | cancel_cpu_boost(); | 585 | cancel_cpu_boost(); |
564 | 586 | ||
@@ -685,10 +707,33 @@ bool codec_pause(void) | |||
685 | void codec_stop(void) | 707 | void codec_stop(void) |
686 | { | 708 | { |
687 | /* Wait until it's in the main loop */ | 709 | /* Wait until it's in the main loop */ |
688 | LOGFQUEUE("audio >| codec Q_CODEC_STOP"); | 710 | LOGFQUEUE("audio >| codec Q_CODEC_STOP: 0"); |
689 | while (codec_queue_send(Q_CODEC_STOP, 0) != Q_NULL); | 711 | while (codec_queue_send(Q_CODEC_STOP, 0) != Q_NULL); |
690 | } | 712 | } |
691 | 713 | ||
714 | #ifdef HAVE_RECORDING | ||
715 | /* Tells codec to take final encoding step and then exit - | ||
716 | Returns minimum buffer size required or 0 if complete */ | ||
717 | size_t codec_finish_stream(void) | ||
718 | { | ||
719 | size_t size = 0; | ||
720 | |||
721 | LOGFQUEUE("audio >| codec Q_CODEC_STOP: &size"); | ||
722 | if (codec_queue_send(Q_CODEC_STOP, (intptr_t)&size) != Q_NULL) | ||
723 | { | ||
724 | /* Sync to keep size in scope and get response */ | ||
725 | LOGFQUEUE("audio >| codec Q_NULL"); | ||
726 | codec_queue_send(Q_NULL, 0); | ||
727 | |||
728 | if (size == 0) | ||
729 | codec_stop(); /* Replied with 0 size */ | ||
730 | } | ||
731 | /* else thread running in the main loop */ | ||
732 | |||
733 | return size; | ||
734 | } | ||
735 | #endif /* HAVE_RECORDING */ | ||
736 | |||
692 | /* Call the codec's exit routine and close all references */ | 737 | /* Call the codec's exit routine and close all references */ |
693 | void codec_unload(void) | 738 | void codec_unload(void) |
694 | { | 739 | { |
diff --git a/apps/codec_thread.h b/apps/codec_thread.h index c33284e141..6aa340bffe 100644 --- a/apps/codec_thread.h +++ b/apps/codec_thread.h | |||
@@ -46,6 +46,9 @@ void codec_go(void); | |||
46 | bool codec_pause(void); | 46 | bool codec_pause(void); |
47 | void codec_seek(long time); | 47 | void codec_seek(long time); |
48 | void codec_stop(void); | 48 | void codec_stop(void); |
49 | #ifdef HAVE_RECORDING | ||
50 | size_t codec_finish_stream(void); | ||
51 | #endif | ||
49 | void codec_unload(void); | 52 | void codec_unload(void); |
50 | int codec_loaded(void); | 53 | int codec_loaded(void); |
51 | 54 | ||
diff --git a/apps/codecs.c b/apps/codecs.c index 69204b7c4f..18022d9df3 100644 --- a/apps/codecs.c +++ b/apps/codecs.c | |||
@@ -73,13 +73,6 @@ static size_t codec_size; | |||
73 | 73 | ||
74 | extern void* plugin_get_audio_buffer(size_t *buffer_size); | 74 | extern void* plugin_get_audio_buffer(size_t *buffer_size); |
75 | 75 | ||
76 | #if (CONFIG_PLATFORM & PLATFORM_NATIVE) && defined(HAVE_RECORDING) | ||
77 | #undef open | ||
78 | static int open(const char* pathname, int flags, ...) | ||
79 | { | ||
80 | return file_open(pathname, flags); | ||
81 | } | ||
82 | #endif | ||
83 | struct codec_api ci = { | 76 | struct codec_api ci = { |
84 | 77 | ||
85 | 0, /* filesize */ | 78 | 0, /* filesize */ |
@@ -99,7 +92,7 @@ struct codec_api ci = { | |||
99 | NULL, /* configure */ | 92 | NULL, /* configure */ |
100 | NULL, /* get_command */ | 93 | NULL, /* get_command */ |
101 | NULL, /* loop_track */ | 94 | NULL, /* loop_track */ |
102 | 95 | ||
103 | /* kernel/ system */ | 96 | /* kernel/ system */ |
104 | #if defined(CPU_ARM) && CONFIG_PLATFORM & PLATFORM_NATIVE | 97 | #if defined(CPU_ARM) && CONFIG_PLATFORM & PLATFORM_NATIVE |
105 | __div0, | 98 | __div0, |
@@ -147,21 +140,14 @@ struct codec_api ci = { | |||
147 | #endif | 140 | #endif |
148 | 141 | ||
149 | #ifdef HAVE_RECORDING | 142 | #ifdef HAVE_RECORDING |
150 | enc_get_inputs, | 143 | NULL, /* enc_pcmbuf_read */ |
151 | enc_set_parameters, | 144 | NULL, /* enc_pcmbuf_advance */ |
152 | enc_get_chunk, | 145 | NULL, /* enc_encbuf_get_buffer */ |
153 | enc_finish_chunk, | 146 | NULL, /* enc_encbuf_finish_buffer */ |
154 | enc_get_pcm_data, | 147 | NULL, /* enc_stream_read */ |
155 | enc_unget_pcm_data, | 148 | NULL, /* enc_stream_lseek */ |
156 | 149 | NULL, /* enc_stream_write */ | |
157 | /* file */ | ||
158 | (open_func)PREFIX(open), | ||
159 | PREFIX(close), | ||
160 | (read_func)PREFIX(read), | ||
161 | PREFIX(lseek), | ||
162 | (write_func)PREFIX(write), | ||
163 | round_value_to_list32, | 150 | round_value_to_list32, |
164 | |||
165 | #endif /* HAVE_RECORDING */ | 151 | #endif /* HAVE_RECORDING */ |
166 | 152 | ||
167 | /* new stuff at the end, sort into place next time | 153 | /* new stuff at the end, sort into place next time |
@@ -299,3 +285,16 @@ int codec_close(void) | |||
299 | 285 | ||
300 | return status; | 286 | return status; |
301 | } | 287 | } |
288 | |||
289 | #ifdef HAVE_RECORDING | ||
290 | enc_callback_t codec_get_enc_callback(void) | ||
291 | { | ||
292 | if (curr_handle == NULL || | ||
293 | c_hdr->lc_hdr.magic != CODEC_ENC_MAGIC) { | ||
294 | logf("Codec: not an encoder"); | ||
295 | return NULL; | ||
296 | } | ||
297 | |||
298 | return c_hdr->rec_extension[0]; | ||
299 | } | ||
300 | #endif /* HAVE_RECORDING */ | ||
diff --git a/apps/playback.c b/apps/playback.c index f676c6d31f..87922bfb82 100644 --- a/apps/playback.c +++ b/apps/playback.c | |||
@@ -351,18 +351,6 @@ void playback_voice_event(void *data) | |||
351 | pcmbuf_soft_mode(*(bool *)data); | 351 | pcmbuf_soft_mode(*(bool *)data); |
352 | } | 352 | } |
353 | 353 | ||
354 | /** --- audio_queue helpers --- **/ | ||
355 | static void audio_queue_post(long id, intptr_t data) | ||
356 | { | ||
357 | queue_post(&audio_queue, id, data); | ||
358 | } | ||
359 | |||
360 | static intptr_t audio_queue_send(long id, intptr_t data) | ||
361 | { | ||
362 | return queue_send(&audio_queue, id, data); | ||
363 | } | ||
364 | |||
365 | |||
366 | /** --- MP3Entry --- **/ | 354 | /** --- MP3Entry --- **/ |
367 | 355 | ||
368 | /* Does the mp3entry have enough info for us to use it? */ | 356 | /* Does the mp3entry have enough info for us to use it? */ |
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 | } | ||
diff --git a/apps/recorder/pcm_record.h b/apps/recorder/pcm_record.h index bff7881605..cf7197946a 100644 --- a/apps/recorder/pcm_record.h +++ b/apps/recorder/pcm_record.h | |||
@@ -8,6 +8,7 @@ | |||
8 | * $Id$ | 8 | * $Id$ |
9 | * | 9 | * |
10 | * Copyright (C) 2005 by Linus Nielsen Feltzing | 10 | * Copyright (C) 2005 by Linus Nielsen Feltzing |
11 | * Copyright (C) 2006-2013 by 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 |
@@ -18,47 +19,64 @@ | |||
18 | * KIND, either express or implied. | 19 | * KIND, either express or implied. |
19 | * | 20 | * |
20 | ****************************************************************************/ | 21 | ****************************************************************************/ |
21 | |||
22 | #ifndef PCM_RECORD_H | 22 | #ifndef PCM_RECORD_H |
23 | #define PCM_RECORD_H | 23 | #define PCM_RECORD_H |
24 | 24 | ||
25 | #include "config.h" | 25 | /** Warnings (recording may continue with possible data loss) |
26 | ** Reset of recording clears, otherwise see notes below | ||
27 | */ | ||
26 | 28 | ||
27 | /** Warnings **/ | 29 | /* PCM buffer has overflowed; PCM samples were dropped */ |
28 | /* pcm (dma) buffer has overflowed */ | 30 | /* persists until: stop, new file, clear */ |
29 | #define PCMREC_W_PCM_BUFFER_OVF 0x00000001 | 31 | #define PCMREC_W_PCM_BUFFER_OVF 0x00000001 |
30 | /* encoder output buffer has overflowed */ | 32 | /* encoder output buffer has overflowed; encoded data was dropped */ |
33 | /* persists until: stop, new file, clear */ | ||
31 | #define PCMREC_W_ENC_BUFFER_OVF 0x00000002 | 34 | #define PCMREC_W_ENC_BUFFER_OVF 0x00000002 |
32 | /** Errors **/ | 35 | /* encoder and set/detected sample rates do not match */ |
36 | /* persists until: rates match, clear */ | ||
37 | #define PCMREC_W_SAMPR_MISMATCH 0x00000004 | ||
38 | /* PCM frame skipped because of recoverable DMA error */ | ||
39 | /* persists until: clear */ | ||
40 | #define PCMREC_W_DMA 0x00000008 | ||
41 | /* internal file size limit was reached; encoded data was dropped */ | ||
42 | /* persists until: stop, new file, clear */ | ||
43 | #define PCMREC_W_FILE_SIZE 0x00000010 | ||
44 | |||
45 | /* all warning flags */ | ||
46 | #define PCMREC_W_ALL 0x0000001f | ||
47 | |||
48 | /** Errors (recording should be reset) | ||
49 | ** | ||
50 | ** Stopping recording if recording clears internally and externally visible | ||
51 | ** status must be cleared with audio_error_clear() | ||
52 | ** reset of recording clears | ||
53 | */ | ||
54 | |||
55 | /* DMA callback has reported an error */ | ||
56 | #define PCMREC_E_DMA 0x80010000 | ||
33 | /* failed to load encoder */ | 57 | /* failed to load encoder */ |
34 | #define PCMREC_E_LOAD_ENCODER 0x80001000 | 58 | #define PCMREC_E_LOAD_ENCODER 0x80020000 |
35 | /* error originating in encoder */ | 59 | /* error originating in encoder */ |
36 | #define PCMREC_E_ENCODER 0x80002000 | 60 | #define PCMREC_E_ENCODER 0x80040000 |
37 | /* filename queue has desynced from stream markers */ | 61 | /* error from encoder setup of stream parameters */ |
38 | #define PCMREC_E_FNQ_DESYNC 0x80004000 | 62 | #define PCMREC_E_ENC_SETUP 0x80080000 |
63 | /* error writing to output stream */ | ||
64 | #define PCMREC_E_ENCODER_STREAM 0x80100000 | ||
39 | /* I/O error has occurred */ | 65 | /* I/O error has occurred */ |
40 | #define PCMREC_E_IO 0x80008000 | 66 | #define PCMREC_E_IO 0x80200000 |
41 | #ifdef DEBUG | ||
42 | /* encoder has written past end of allocated space */ | ||
43 | #define PCMREC_E_CHUNK_OVF 0x80010000 | ||
44 | #endif /* DEBUG */ | ||
45 | /* DMA callback has reported an error */ | ||
46 | #define PCMREC_E_DMA 0x80020000 | ||
47 | 67 | ||
48 | /** General functions for high level codec recording **/ | 68 | /* all error flags */ |
49 | /* pcm_rec_error_clear is deprecated for general use. audio_error_clear | 69 | #define PCMREC_E_ALL 0x803f0000 |
50 | should be used */ | 70 | |
71 | /* Functions called by audio_thread.c */ | ||
51 | void pcm_rec_error_clear(void); | 72 | void pcm_rec_error_clear(void); |
52 | /* pcm_rec_status is deprecated for general use. audio_status merges the | ||
53 | results for consistency with the hardware codec version */ | ||
54 | unsigned int pcm_rec_status(void); | 73 | unsigned int pcm_rec_status(void); |
55 | unsigned long pcm_rec_get_warnings(void); | 74 | uint32_t pcm_rec_get_warnings(void); |
56 | void pcm_rec_init(void) INIT_ATTR; | 75 | #ifdef HAVE_SPDIF_IN |
57 | int pcm_rec_current_bitrate(void); | ||
58 | int pcm_rec_encoder_afmt(void); /* AFMT_* value, AFMT_UNKNOWN if none */ | ||
59 | int pcm_rec_rec_format(void); /* Format index or -1 otherwise */ | ||
60 | unsigned long pcm_rec_sample_rate(void); | 76 | unsigned long pcm_rec_sample_rate(void); |
61 | int pcm_get_num_unprocessed(void); | 77 | #endif |
78 | |||
79 | void recording_init(void); | ||
62 | 80 | ||
63 | /* audio.h contains audio_* recording functions */ | 81 | /* audio.h contains audio_* recording functions */ |
64 | 82 | ||
diff --git a/apps/recorder/recording.c b/apps/recorder/recording.c index 4893f589f1..e246825443 100644 --- a/apps/recorder/recording.c +++ b/apps/recorder/recording.c | |||
@@ -1730,7 +1730,7 @@ bool recording_screen(bool no_source) | |||
1730 | /* Don't use language string unless agreed upon to make this | 1730 | /* Don't use language string unless agreed upon to make this |
1731 | method permanent - could do something in the statusbar */ | 1731 | method permanent - could do something in the statusbar */ |
1732 | snprintf(buf, sizeof(buf), "Warning: %08lX", | 1732 | snprintf(buf, sizeof(buf), "Warning: %08lX", |
1733 | pcm_rec_get_warnings()); | 1733 | (unsigned long)pcm_rec_get_warnings()); |
1734 | } | 1734 | } |
1735 | else | 1735 | else |
1736 | #endif /* CONFIG_CODEC == SWCODEC */ | 1736 | #endif /* CONFIG_CODEC == SWCODEC */ |
@@ -1755,8 +1755,16 @@ bool recording_screen(bool no_source) | |||
1755 | 1755 | ||
1756 | if(audio_stat & AUDIO_STATUS_PRERECORD) | 1756 | if(audio_stat & AUDIO_STATUS_PRERECORD) |
1757 | { | 1757 | { |
1758 | #if CONFIG_CODEC == SWCODEC | ||
1759 | /* Tracks amount of prerecorded data in buffer */ | ||
1760 | snprintf(buf, sizeof(buf), "%s (%lu/%ds)...", | ||
1761 | str(LANG_RECORD_PRERECORD), | ||
1762 | audio_prerecorded_time() / HZ, | ||
1763 | global_settings.rec_prerecord_time); | ||
1764 | #else /* !SWCODEC */ | ||
1758 | snprintf(buf, sizeof(buf), "%s...", | 1765 | snprintf(buf, sizeof(buf), "%s...", |
1759 | str(LANG_RECORD_PRERECORD)); | 1766 | str(LANG_RECORD_PRERECORD)); |
1767 | #endif /* CONFIG_CODEC == SWCODEC */ | ||
1760 | } | 1768 | } |
1761 | else | 1769 | else |
1762 | { | 1770 | { |
@@ -1915,8 +1923,7 @@ bool recording_screen(bool no_source) | |||
1915 | screens[i].update(); | 1923 | screens[i].update(); |
1916 | 1924 | ||
1917 | #if CONFIG_CODEC == SWCODEC | 1925 | #if CONFIG_CODEC == SWCODEC |
1918 | /* stop recording - some players like H10 freeze otherwise | 1926 | /* stop recording first and try to finish saving whatever it can */ |
1919 | TO DO: find out why it freezes and fix properly */ | ||
1920 | rec_command(RECORDING_CMD_STOP); | 1927 | rec_command(RECORDING_CMD_STOP); |
1921 | audio_close_recording(); | 1928 | audio_close_recording(); |
1922 | #endif | 1929 | #endif |
diff --git a/firmware/export/audio.h b/firmware/export/audio.h index 293956cb37..24e8e9a0e7 100644 --- a/firmware/export/audio.h +++ b/firmware/export/audio.h | |||
@@ -212,10 +212,7 @@ unsigned long audio_num_recorded_bytes(void); | |||
212 | 212 | ||
213 | #if CONFIG_CODEC == SWCODEC | 213 | #if CONFIG_CODEC == SWCODEC |
214 | /* SWCODEC recording functions */ | 214 | /* SWCODEC recording functions */ |
215 | /* playback.c */ | 215 | unsigned long audio_prerecorded_time(void); |
216 | bool audio_load_encoder(int afmt); | ||
217 | void audio_remove_encoder(void); | ||
218 | unsigned char *audio_get_recording_buffer(size_t *buffer_size); | ||
219 | #endif /* CONFIG_CODEC == SWCODEC */ | 216 | #endif /* CONFIG_CODEC == SWCODEC */ |
220 | 217 | ||
221 | #endif /* HAVE_RECORDING */ | 218 | #endif /* HAVE_RECORDING */ |
diff --git a/firmware/export/enc_base.h b/firmware/export/enc_base.h index f5dfb65f2a..7228dc4c83 100644 --- a/firmware/export/enc_base.h +++ b/firmware/export/enc_base.h | |||
@@ -9,7 +9,7 @@ | |||
9 | * | 9 | * |
10 | * Base declarations for working with software encoders | 10 | * Base declarations for working with software encoders |
11 | * | 11 | * |
12 | * Copyright (C) 2006 Michael Sevakis | 12 | * Copyright (C) 2006-2013 Michael Sevakis |
13 | * | 13 | * |
14 | * This program is free software; you can redistribute it and/or | 14 | * This program is free software; you can redistribute it and/or |
15 | * modify it under the terms of the GNU General Public License | 15 | * modify it under the terms of the GNU General Public License |
@@ -24,7 +24,9 @@ | |||
24 | #ifndef ENC_BASE_H | 24 | #ifndef ENC_BASE_H |
25 | #define ENC_BASE_H | 25 | #define ENC_BASE_H |
26 | 26 | ||
27 | /** encoder config structures **/ | 27 | #include <sys/types.h> |
28 | |||
29 | /** Encoder config structures **/ | ||
28 | 30 | ||
29 | /** aiff_enc.codec **/ | 31 | /** aiff_enc.codec **/ |
30 | struct aiff_enc_config | 32 | struct aiff_enc_config |
@@ -57,18 +59,22 @@ struct aiff_enc_config | |||
57 | 59 | ||
58 | /* MPEG 1 */ | 60 | /* MPEG 1 */ |
59 | #define MPEG1_SAMPR_CAPS (SAMPR_CAP_32 | SAMPR_CAP_48 | SAMPR_CAP_44) | 61 | #define MPEG1_SAMPR_CAPS (SAMPR_CAP_32 | SAMPR_CAP_48 | SAMPR_CAP_44) |
60 | #define MPEG1_BITR_CAPS (MP3_BITR_CAP_32 | MP3_BITR_CAP_40 | MP3_BITR_CAP_48 | \ | 62 | #define MPEG1_BITR_CAPS (MP3_BITR_CAP_32 | MP3_BITR_CAP_40 | \ |
61 | MP3_BITR_CAP_56 | MP3_BITR_CAP_64 | MP3_BITR_CAP_80 | \ | 63 | MP3_BITR_CAP_48 | MP3_BITR_CAP_56 | \ |
62 | MP3_BITR_CAP_96 | MP3_BITR_CAP_112 | MP3_BITR_CAP_128 | \ | 64 | MP3_BITR_CAP_64 | MP3_BITR_CAP_80 | \ |
63 | MP3_BITR_CAP_160 | MP3_BITR_CAP_192 | MP3_BITR_CAP_224 | \ | 65 | MP3_BITR_CAP_96 | MP3_BITR_CAP_112 | \ |
66 | MP3_BITR_CAP_128 | MP3_BITR_CAP_160 | \ | ||
67 | MP3_BITR_CAP_192 | MP3_BITR_CAP_224 | \ | ||
64 | MP3_BITR_CAP_256 | MP3_BITR_CAP_320) | 68 | MP3_BITR_CAP_256 | MP3_BITR_CAP_320) |
65 | 69 | ||
66 | /* MPEG 2 */ | 70 | /* MPEG 2 */ |
67 | #define MPEG2_SAMPR_CAPS (SAMPR_CAP_22 | SAMPR_CAP_24 | SAMPR_CAP_16) | 71 | #define MPEG2_SAMPR_CAPS (SAMPR_CAP_22 | SAMPR_CAP_24 | SAMPR_CAP_16) |
68 | #define MPEG2_BITR_CAPS (MP3_BITR_CAP_8 | MP3_BITR_CAP_16 | MP3_BITR_CAP_24 | \ | 72 | #define MPEG2_BITR_CAPS (MP3_BITR_CAP_8 | MP3_BITR_CAP_16 | \ |
69 | MP3_BITR_CAP_32 | MP3_BITR_CAP_40 | MP3_BITR_CAP_48 | \ | 73 | MP3_BITR_CAP_24 | MP3_BITR_CAP_32 | \ |
70 | MP3_BITR_CAP_56 | MP3_BITR_CAP_64 | MP3_BITR_CAP_80 | \ | 74 | MP3_BITR_CAP_40 | MP3_BITR_CAP_48 | \ |
71 | MP3_BITR_CAP_96 | MP3_BITR_CAP_112 | MP3_BITR_CAP_128 | \ | 75 | MP3_BITR_CAP_56 | MP3_BITR_CAP_64 | \ |
76 | MP3_BITR_CAP_80 | MP3_BITR_CAP_96 | \ | ||
77 | MP3_BITR_CAP_112 | MP3_BITR_CAP_128 | \ | ||
72 | MP3_BITR_CAP_144 | MP3_BITR_CAP_160) | 78 | MP3_BITR_CAP_144 | MP3_BITR_CAP_160) |
73 | 79 | ||
74 | #if 0 | 80 | #if 0 |
@@ -131,6 +137,7 @@ struct wavpack_enc_config | |||
131 | #endif | 137 | #endif |
132 | }; | 138 | }; |
133 | 139 | ||
140 | /* General config information about any encoder */ | ||
134 | struct encoder_config | 141 | struct encoder_config |
135 | { | 142 | { |
136 | union | 143 | union |
@@ -149,144 +156,77 @@ struct encoder_config | |||
149 | }; | 156 | }; |
150 | 157 | ||
151 | /** Encoder chunk macros and definitions **/ | 158 | /** Encoder chunk macros and definitions **/ |
152 | #define CHUNKF_START_FILE 0x0001ul /* This chunk starts a new file */ | ||
153 | #define CHUNKF_END_FILE 0x0002ul /* This chunk ends the current file */ | ||
154 | #define CHUNKF_PRERECORD 0x0010ul /* This chunk is prerecord data, | ||
155 | a new file could start anytime */ | ||
156 | #define CHUNKF_ABORT 0x0020ul /* Encoder should not finish this | ||
157 | chunk */ | ||
158 | #define CHUNKF_ERROR (~0ul ^ (~0ul >> 1)) /* An error has occured | ||
159 | (passed to/from encoder). Use the | ||
160 | sign bit to check (long)flags < 0. */ | ||
161 | #define CHUNKF_ALLFLAGS (0x0033ul | CHUNKF_ERROR) | ||
162 | |||
163 | /* Header at the beginning of every encoder chunk */ | ||
164 | #ifdef DEBUG | ||
165 | #define H_TO_BE32 htobe32 | ||
166 | #define ENC_CHUNK_MAGIC H_TO_BE32(('P' << 24) | ('T' << 16) | ('Y' << 8) | 'R') | ||
167 | #endif | ||
168 | struct enc_chunk_hdr | ||
169 | { | ||
170 | #ifdef DEBUG | ||
171 | unsigned long id; /* overflow detection - 'PTYR' - acronym for | ||
172 | "PTYR Tells You Right" ;) */ | ||
173 | #endif | ||
174 | unsigned long flags; /* in/out: flags used by encoder and file | ||
175 | writing */ | ||
176 | size_t enc_size; /* out: amount of encoder data written to | ||
177 | chunk */ | ||
178 | unsigned long num_pcm; /* out: number of PCM samples eaten during | ||
179 | processing | ||
180 | (<= size of allocated buffer) */ | ||
181 | unsigned char *enc_data; /* out: pointer to enc_size_written bytes | ||
182 | of encoded audio data in chunk */ | ||
183 | /* Encoder defined data follows header. Can be audio data + any other | ||
184 | stuff the encoder needs to handle on a per chunk basis */ | ||
185 | }; | ||
186 | 159 | ||
187 | /* Paranoia: be sure header size is 4-byte aligned */ | 160 | /* What sort of data does the header describe? */ |
188 | #define ENC_CHUNK_HDR_SIZE \ | 161 | enum CHUNK_T |
189 | ALIGN_UP_P2(sizeof (struct enc_chunk_hdr), 2) | ||
190 | /* Skip the chunk header and return data */ | ||
191 | #define ENC_CHUNK_SKIP_HDR(t, hdr) \ | ||
192 | ((typeof (t))((char *)hdr + ENC_CHUNK_HDR_SIZE)) | ||
193 | /* Cast p to struct enc_chunk_hdr * */ | ||
194 | #define ENC_CHUNK_HDR(p) \ | ||
195 | ((struct enc_chunk_hdr *)(p)) | ||
196 | |||
197 | enum enc_events | ||
198 | { | 162 | { |
199 | /* File writing events - data points to enc_file_event_data */ | 163 | CHUNK_T_DATA = 0x0, /* Encoded audio data */ |
200 | ENC_START_FILE = 0, /* a new file has been opened and no data has yet | 164 | CHUNK_T_STREAM_START = 0x1, /* Stream start marker */ |
201 | been written */ | 165 | CHUNK_T_STREAM_END = 0x2, /* Stream end marker */ |
202 | ENC_WRITE_CHUNK, /* write the current chunk to disk */ | 166 | CHUNK_T_WRAP = 0x3 /* Buffer early wrap marker */ |
203 | ENC_END_FILE, /* current file about to be closed and all valid | ||
204 | data has been written */ | ||
205 | /* Encoder buffer events - data points to enc_buffer_event_data */ | ||
206 | ENC_REC_NEW_STREAM, /* Take steps to finish current stream and start | ||
207 | new */ | ||
208 | }; | 167 | }; |
209 | 168 | ||
210 | /** | 169 | /* Header for every buffer slot and chunk */ |
211 | * encoder can write extra data to the file such as headers or more encoded | 170 | union enc_chunk_hdr |
212 | * samples and must update sizes and samples accordingly. | ||
213 | */ | ||
214 | struct enc_file_event_data | ||
215 | { | 171 | { |
216 | struct enc_chunk_hdr *chunk; /* Current chunk */ | 172 | struct |
217 | size_t new_enc_size; /* New size of chunk */ | 173 | { |
218 | unsigned long new_num_pcm; /* New number of pcm in chunk */ | 174 | uint32_t type : 2; /* Chunk type (CHUNK_T_*) */ |
219 | const char *filename; /* filename to open if ENC_START_FILE */ | 175 | uint32_t err : 1; /* Encoder error */ |
220 | int rec_file; /* Current file or < 0 if none */ | 176 | uint32_t pre : 1; /* Chunk is prerecorded data */ |
221 | unsigned long num_pcm_samples; /* Current pcm sample count written to | 177 | uint32_t aux0 : 1; /* Aux flag 0 - for encoder */ |
222 | file so far. */ | 178 | uint32_t unused : 3; /* */ |
223 | }; | 179 | uint32_t size : 24; /* size of data */ |
180 | }; | ||
181 | uint32_t zero; /* Zero-out struct access */ | ||
182 | intptr_t reserved1; /* Want it at least pointer-sized */ | ||
183 | } __attribute__((__may_alias__)); | ||
184 | |||
185 | #define ENC_HDR_SIZE (sizeof (union enc_chunk_hdr)) | ||
224 | 186 | ||
225 | /** | 187 | /* When hdr.type is CHUNK_T_STREAM_START */ |
226 | * encoder may add some data to the end of the last and start of the next | 188 | struct enc_chunk_file |
227 | * but must never yield when called so any encoding done should be absolutely | ||
228 | * minimal. | ||
229 | */ | ||
230 | struct enc_buffer_event_data | ||
231 | { | 189 | { |
232 | unsigned long flags; /* in: One or more of: | 190 | union enc_chunk_hdr hdr; /* This chunk's header */ |
233 | * CHUNKF_PRERECORD | 191 | /* hdr.size = slot count of chunk */ |
234 | * CHUNKF_END_FILE | 192 | char path[]; /* NULL-terminated path of file */ |
235 | * CHUNKF_START_FILE | 193 | } __attribute__((__may_alias__)); |
236 | */ | ||
237 | struct enc_chunk_hdr *pre_chunk; /* in: pointer to first prerecord | ||
238 | * chunk | ||
239 | */ | ||
240 | struct enc_chunk_hdr *chunk; /* in,out: chunk were split occurs - | ||
241 | * first chunk of start | ||
242 | */ | ||
243 | }; | ||
244 | 194 | ||
245 | /** Callbacks called by encoder codec **/ | 195 | /* If flags = CHUNK_T_STREAM_END, just the header exists */ |
246 | 196 | ||
247 | /* parameters passed to encoder by enc_get_inputs */ | 197 | /* When hdr.type is CHUNK_T_DATA */ |
248 | struct enc_inputs | 198 | struct enc_chunk_data |
249 | { | 199 | { |
250 | unsigned long sample_rate; /* out - pcm frequency */ | 200 | union enc_chunk_hdr hdr; /* IN,OUT: This chunk's header */ |
251 | int num_channels; /* out - number of audio channels */ | 201 | /* hdr.size = total size of data[] */ |
252 | int rec_mono_mode; /* out - how to create mono */ | 202 | uint32_t pcm_count; /* OUT: number of PCM samples encoded */ |
253 | struct encoder_config *config; /* out - encoder settings */ | 203 | uint8_t data[]; /* OUT: encoded audio data */ |
254 | }; | 204 | } __attribute__((__may_alias__)); |
255 | 205 | ||
256 | void enc_get_inputs(struct enc_inputs *inputs); | 206 | /* CHUNK_T_STREAM_END and CHUNK_T_WRAP consist of only the header */ |
257 | 207 | ||
258 | /* parameters pass from encoder to enc_set_parameters */ | 208 | #define ENC_FILE_HDR(hdr) ((struct enc_chunk_file *)(hdr)) |
259 | struct enc_parameters | 209 | #define ENC_DATA_HDR(hdr) ((struct enc_chunk_data *)(hdr)) |
210 | |||
211 | /* Audio and encoder stream parameters */ | ||
212 | struct enc_inputs | ||
260 | { | 213 | { |
261 | /* IN parameters */ | 214 | /* IN parameters */ |
262 | int afmt; /* AFMT_* id - sanity checker */ | 215 | unsigned long sample_rate; /* PCM samplerate setting */ |
263 | size_t chunk_size; /* max chunk size required */ | 216 | int num_channels; /* Number of audio channels */ |
264 | unsigned long enc_sample_rate; /* actual sample rate used by encoder | 217 | struct encoder_config *config; /* Encoder settings */ |
265 | (for recorded time calculation) */ | ||
266 | size_t reserve_bytes; /* number of bytes to reserve immediately | ||
267 | following chunks */ | ||
268 | void (*events_callback)(enum enc_events event, | ||
269 | void *data); /* pointer to events callback */ | ||
270 | /* OUT parameters */ | ||
271 | unsigned char *enc_buffer; /* pointer to enc_buffer */ | ||
272 | size_t buf_chunk_size; /* size of chunks in enc_buffer */ | ||
273 | int num_chunks; /* number of chunks allotted to encoder */ | ||
274 | unsigned char *reserve_buffer; /* pointer to reserve_bytes bytes */ | ||
275 | }; | ||
276 | 218 | ||
277 | /* set the encoder dimensions - called by encoder codec at initialization | 219 | /* IN,OUT parameters */ |
278 | and termination */ | 220 | unsigned long enc_sample_rate; /* Actual sample rate accepted by encoder |
279 | void enc_set_parameters(struct enc_parameters *params); | 221 | (for recorded time calculation) */ |
280 | /* returns pointer to next write chunk in circular buffer */ | 222 | }; |
281 | struct enc_chunk_hdr * enc_get_chunk(void); | ||
282 | /* releases the current chunk into the available chunks */ | ||
283 | void enc_finish_chunk(void); | ||
284 | 223 | ||
285 | #define PCM_MAX_FEED_SIZE 20000 /* max pcm size passed to encoder */ | 224 | enum enc_callback_reason |
225 | { | ||
226 | ENC_CB_INPUTS, /* 'params' is struct enc_inputs * */ | ||
227 | ENC_CB_STREAM, /* 'params' is union enc_chunk_hdr * */ | ||
228 | }; | ||
286 | 229 | ||
287 | /* passes a pointer to next chunk of unprocessed wav data */ | 230 | typedef int (* enc_callback_t)(enum enc_callback_reason reason, void *params); |
288 | unsigned char * enc_get_pcm_data(size_t size); | ||
289 | /* puts some pcm data back in the queue */ | ||
290 | size_t enc_unget_pcm_data(size_t size); | ||
291 | 231 | ||
292 | #endif /* ENC_BASE_H */ | 232 | #endif /* ENC_BASE_H */ |
diff --git a/lib/rbcodec/codecs/aiff_enc.c b/lib/rbcodec/codecs/aiff_enc.c index 8e9246d2bb..fb8db384a3 100644 --- a/lib/rbcodec/codecs/aiff_enc.c +++ b/lib/rbcodec/codecs/aiff_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 |
@@ -47,10 +48,15 @@ struct aiff_header | |||
47 | #define PCM_DEPTH_BYTES 2 | 48 | #define PCM_DEPTH_BYTES 2 |
48 | #define PCM_DEPTH_BITS 16 | 49 | #define PCM_DEPTH_BITS 16 |
49 | #define PCM_SAMP_PER_CHUNK 2048 | 50 | #define PCM_SAMP_PER_CHUNK 2048 |
50 | #define PCM_CHUNK_SIZE (PCM_SAMP_PER_CHUNK*4) | 51 | |
52 | static int num_channels; | ||
53 | static uint32_t sample_rate; | ||
54 | static size_t frame_size; | ||
55 | static size_t pcm_size; | ||
56 | static uint32_t num_sample_frames; | ||
51 | 57 | ||
52 | /* Template headers */ | 58 | /* Template headers */ |
53 | struct aiff_header aiff_header = | 59 | static const struct aiff_header aiff_template_header = |
54 | { | 60 | { |
55 | { 'F', 'O', 'R', 'M' }, /* form_id */ | 61 | { 'F', 'O', 'R', 'M' }, /* form_id */ |
56 | 0, /* form_size (*) */ | 62 | 0, /* form_size (*) */ |
@@ -65,336 +71,193 @@ struct aiff_header aiff_header = | |||
65 | 0, /* ssnd_size (*) */ | 71 | 0, /* ssnd_size (*) */ |
66 | htobe32(0), /* offset */ | 72 | htobe32(0), /* offset */ |
67 | htobe32(0), /* block_size */ | 73 | htobe32(0), /* block_size */ |
74 | /* (*) updated when finalizing stream */ | ||
68 | }; | 75 | }; |
69 | 76 | ||
70 | /* (*) updated when finalizing file */ | 77 | static inline void frame_htobe(uint32_t *p, size_t size) |
71 | 78 | { | |
72 | static int num_channels IBSS_ATTR; | 79 | #ifdef ROCKBOX_LITTLE_ENDIAN |
73 | static int rec_mono_mode IBSS_ATTR; | 80 | /* Byte-swap samples, stereo or mono */ |
74 | static uint32_t sample_rate; | 81 | do |
75 | static uint32_t enc_size; | 82 | { |
76 | static int32_t err IBSS_ATTR; | 83 | uint32_t t; |
84 | t = swap_odd_even32(*p); *p++ = t; | ||
85 | t = swap_odd_even32(*p); *p++ = t; | ||
86 | t = swap_odd_even32(*p); *p++ = t; | ||
87 | t = swap_odd_even32(*p); *p++ = t; | ||
88 | t = swap_odd_even32(*p); *p++ = t; | ||
89 | t = swap_odd_even32(*p); *p++ = t; | ||
90 | t = swap_odd_even32(*p); *p++ = t; | ||
91 | t = swap_odd_even32(*p); *p++ = t; | ||
92 | } | ||
93 | while (size -= 8 * 2 * PCM_DEPTH_BYTES); | ||
94 | #endif /* ROCKBOX_LITTLE_ENDIAN */ | ||
95 | (void)p; (void)size; | ||
96 | } | ||
77 | 97 | ||
78 | /* convert unsigned 32 bit value to 80-bit floating point number */ | 98 | /* convert unsigned 32 bit value to 80-bit floating point number */ |
79 | static void uint32_h_to_ieee754_extended_be(uint8_t f[10], uint32_t l) | 99 | static void uint32_h_to_ieee754_extended_be(uint8_t f[10], uint32_t l) |
80 | ICODE_ATTR; | ||
81 | static void uint32_h_to_ieee754_extended_be(uint8_t f[10], uint32_t l) | ||
82 | { | 100 | { |
83 | int32_t exp; | ||
84 | |||
85 | ci->memset(f, 0, 10); | 101 | ci->memset(f, 0, 10); |
86 | 102 | ||
87 | if (l == 0) | 103 | if (l == 0) |
88 | return; | 104 | return; |
89 | 105 | ||
90 | for (exp = 30; (l & (1ul << 31)) == 0; exp--) | 106 | int shift = __builtin_clz(l); |
91 | l <<= 1; | ||
92 | 107 | ||
93 | /* sign always zero - bit 79 */ | 108 | /* sign always zero - bit 79 */ |
94 | /* exponent is 0-31 (normalized: 31 - shift + 16383) - bits 64-78 */ | 109 | /* exponent is 0-31 (normalized: 30 - shift + 16383) - bits 64-78 */ |
95 | f[0] = 0x40; | 110 | f[0] = 0x40; |
96 | f[1] = (uint8_t)exp; | 111 | f[1] = (uint8_t)(30 - shift); |
97 | /* mantissa is value left justified with most significant non-zero | 112 | /* mantissa is value left justified with most significant non-zero |
98 | bit stored in bit 63 - bits 0-63 */ | 113 | bit stored in bit 63 - bits 0-63 */ |
114 | l <<= shift; | ||
99 | f[2] = (uint8_t)(l >> 24); | 115 | f[2] = (uint8_t)(l >> 24); |
100 | f[3] = (uint8_t)(l >> 16); | 116 | f[3] = (uint8_t)(l >> 16); |
101 | f[4] = (uint8_t)(l >> 8); | 117 | f[4] = (uint8_t)(l >> 8); |
102 | f[5] = (uint8_t)(l >> 0); | 118 | f[5] = (uint8_t)(l >> 0); |
103 | } /* uint32_h_to_ieee754_extended_be */ | 119 | } |
104 | |||
105 | /* called version often - inline */ | ||
106 | static inline bool is_file_data_ok(struct enc_file_event_data *data) ICODE_ATTR; | ||
107 | static inline bool is_file_data_ok(struct enc_file_event_data *data) | ||
108 | { | ||
109 | return data->rec_file >= 0 && (long)data->chunk->flags >= 0; | ||
110 | } /* is_file_data_ok */ | ||
111 | 120 | ||
112 | /* called version often - inline */ | 121 | static int on_stream_data(struct enc_chunk_data *data) |
113 | static inline bool on_write_chunk(struct enc_file_event_data *data) ICODE_ATTR; | ||
114 | static inline bool on_write_chunk(struct enc_file_event_data *data) | ||
115 | { | 122 | { |
116 | if (!is_file_data_ok(data)) | 123 | size_t size = data->hdr.size; |
117 | return false; | ||
118 | 124 | ||
119 | if (data->chunk->enc_data == NULL) | 125 | if (ci->enc_stream_write(data->data, size) != (ssize_t)size) |
120 | { | 126 | return -1; |
121 | #ifdef ROCKBOX_HAS_LOGF | ||
122 | ci->logf("aiff enc: NULL data"); | ||
123 | #endif | ||
124 | return true; | ||
125 | } | ||
126 | 127 | ||
127 | if (ci->write(data->rec_file, data->chunk->enc_data, | 128 | pcm_size += size; |
128 | data->chunk->enc_size) != (ssize_t)data->chunk->enc_size) | 129 | num_sample_frames += data->pcm_count; |
129 | return false; | ||
130 | 130 | ||
131 | data->num_pcm_samples += data->chunk->num_pcm; | 131 | return 0; |
132 | return true; | 132 | } |
133 | } /* on_write_chunk */ | ||
134 | 133 | ||
135 | static bool on_start_file(struct enc_file_event_data *data) | 134 | static int on_stream_start(void) |
136 | { | 135 | { |
137 | if ((data->chunk->flags & CHUNKF_ERROR) || *data->filename == '\0') | ||
138 | return false; | ||
139 | |||
140 | data->rec_file = ci->open(data->filename, O_RDWR|O_CREAT|O_TRUNC, 0666); | ||
141 | |||
142 | if (data->rec_file < 0) | ||
143 | return false; | ||
144 | |||
145 | /* reset sample count */ | 136 | /* reset sample count */ |
146 | data->num_pcm_samples = 0; | 137 | pcm_size = 0; |
138 | num_sample_frames = 0; | ||
147 | 139 | ||
148 | /* write template headers */ | 140 | /* write template header */ |
149 | if (ci->write(data->rec_file, &aiff_header, sizeof (aiff_header)) | 141 | if (ci->enc_stream_write(&aiff_template_header, |
150 | != sizeof (aiff_header)) | 142 | sizeof (struct aiff_header)) |
151 | { | 143 | != sizeof (struct aiff_header)) |
152 | return false; | 144 | return -1; |
153 | } | ||
154 | 145 | ||
155 | data->new_enc_size += sizeof(aiff_header); | 146 | return 0; |
156 | return true; | 147 | } |
157 | } /* on_start_file */ | ||
158 | 148 | ||
159 | static bool on_end_file(struct enc_file_event_data *data) | 149 | static int on_stream_end(union enc_chunk_hdr *hdr) |
160 | { | 150 | { |
161 | /* update template headers */ | 151 | /* update template header */ |
162 | struct aiff_header hdr; | 152 | struct aiff_header aiff; |
163 | uint32_t data_size; | ||
164 | 153 | ||
165 | if (!is_file_data_ok(data)) | 154 | if (hdr->err) |
166 | return false; | ||
167 | |||
168 | if (ci->lseek(data->rec_file, 0, SEEK_SET) != 0 || | ||
169 | ci->read(data->rec_file, &hdr, sizeof (hdr)) != sizeof (hdr)) | ||
170 | { | 155 | { |
171 | return false; | 156 | /* Called for stream error; get correct data size */ |
157 | ssize_t size = ci->enc_stream_lseek(0, SEEK_END); | ||
158 | |||
159 | if (size > (ssize_t)sizeof (aiff)) | ||
160 | { | ||
161 | pcm_size = size - sizeof (aiff); | ||
162 | num_sample_frames = pcm_size / (PCM_DEPTH_BYTES*num_channels); | ||
163 | } | ||
172 | } | 164 | } |
173 | 165 | ||
174 | data_size = data->num_pcm_samples*num_channels*PCM_DEPTH_BYTES; | 166 | if (ci->enc_stream_lseek(0, SEEK_SET) != 0) |
167 | return -1; | ||
168 | |||
169 | if (ci->enc_stream_read(&aiff, sizeof (aiff)) != sizeof (aiff)) | ||
170 | return -2; | ||
175 | 171 | ||
176 | /* 'FORM' chunk */ | 172 | /* 'FORM' chunk */ |
177 | hdr.form_size = htobe32(data_size + sizeof (hdr) - 8); | 173 | aiff.form_size = htobe32(pcm_size + sizeof (aiff) - 8); |
178 | 174 | ||
179 | /* 'COMM' chunk */ | 175 | /* 'COMM' chunk */ |
180 | hdr.num_channels = htobe16(num_channels); | 176 | aiff.num_channels = htobe16(num_channels); |
181 | hdr.num_sample_frames = htobe32(data->num_pcm_samples); | 177 | aiff.num_sample_frames = htobe32(num_sample_frames); |
182 | uint32_h_to_ieee754_extended_be(hdr.sample_rate, sample_rate); | 178 | uint32_h_to_ieee754_extended_be(aiff.sample_rate, sample_rate); |
183 | 179 | ||
184 | /* 'SSND' chunk */ | 180 | /* 'SSND' chunk */ |
185 | hdr.ssnd_size = htobe32(data_size + 8); | 181 | aiff.ssnd_size = htobe32(pcm_size + 8); |
186 | 182 | ||
187 | if (ci->lseek(data->rec_file, 0, SEEK_SET) != 0 || | 183 | if (ci->enc_stream_lseek(0, SEEK_SET) != 0) |
188 | ci->write(data->rec_file, &hdr, sizeof (hdr)) != sizeof (hdr) || | 184 | return -3; |
189 | ci->close(data->rec_file) != 0) | ||
190 | { | ||
191 | return false; | ||
192 | } | ||
193 | 185 | ||
194 | data->rec_file = -1; | 186 | if (ci->enc_stream_write(&aiff, sizeof (aiff)) != sizeof (aiff)) |
187 | return -4; | ||
195 | 188 | ||
196 | return true; | 189 | return 0; |
197 | } /* on_end_file */ | 190 | } |
198 | 191 | ||
199 | static void enc_events_callback(enum enc_events event, void *data) | 192 | /* this is the codec entry point */ |
200 | ICODE_ATTR; | 193 | enum codec_status codec_main(enum codec_entry_call_reason reason) |
201 | static void enc_events_callback(enum enc_events event, void *data) | ||
202 | { | 194 | { |
203 | switch (event) | 195 | return CODEC_OK; |
204 | { | 196 | (void)reason; |
205 | case ENC_WRITE_CHUNK: | 197 | } |
206 | if (on_write_chunk((struct enc_file_event_data *)data)) | ||
207 | return; | ||
208 | |||
209 | break; | ||
210 | |||
211 | case ENC_START_FILE: | ||
212 | if (on_start_file((struct enc_file_event_data *)data)) | ||
213 | return; | ||
214 | |||
215 | break; | ||
216 | |||
217 | case ENC_END_FILE: | ||
218 | if (on_end_file((struct enc_file_event_data *)data)) | ||
219 | return; | ||
220 | |||
221 | break; | ||
222 | |||
223 | default: | ||
224 | return; | ||
225 | } | ||
226 | |||
227 | /* Something failed above. Signal error back to core. */ | ||
228 | ((struct enc_file_event_data *)data)->chunk->flags |= CHUNKF_ERROR; | ||
229 | } /* enc_events_callback */ | ||
230 | 198 | ||
231 | /* convert native pcm samples to aiff format samples */ | 199 | /* this is called for each file to process */ |
232 | static inline void sample_to_mono(uint32_t **src, uint32_t **dst) | 200 | enum codec_status ICODE_ATTR codec_run(void) |
233 | { | 201 | { |
234 | int32_t lr1, lr2; | 202 | enum { GETBUF_ENC, GETBUF_PCM } getbuf = GETBUF_ENC; |
203 | struct enc_chunk_data *data = NULL; | ||
235 | 204 | ||
236 | switch(rec_mono_mode) | 205 | /* main encoding loop */ |
206 | while (1) | ||
237 | { | 207 | { |
238 | case 1: | 208 | enum codec_command_action action = ci->get_command(NULL); |
239 | /* mono = L */ | 209 | |
240 | lr1 = *(*src)++; | 210 | if (action != CODEC_ACTION_NULL) |
241 | lr1 = lr1 >> 16; | ||
242 | lr2 = *(*src)++; | ||
243 | lr2 = lr2 >> 16; | ||
244 | break; | ||
245 | case 2: | ||
246 | /* mono = R */ | ||
247 | lr1 = *(*src)++; | ||
248 | lr1 = (int16_t)lr1; | ||
249 | lr2 = *(*src)++; | ||
250 | lr2 = (int16_t)lr2; | ||
251 | break; | ||
252 | case 0: | ||
253 | default: | ||
254 | /* mono = (L+R)/2 */ | ||
255 | lr1 = *(*src)++; | ||
256 | lr1 = (int16_t)lr1 + (lr1 >> 16) + err; | ||
257 | err = lr1 & 1; | ||
258 | lr1 >>= 1; | ||
259 | |||
260 | lr2 = *(*src)++; | ||
261 | lr2 = (int16_t)lr2 + (lr2 >> 16) + err; | ||
262 | err = lr2 & 1; | ||
263 | lr2 >>= 1; | ||
264 | break; | 211 | break; |
265 | } | ||
266 | *(*dst)++ = htobe32((lr1 << 16) | (uint16_t)lr2); | ||
267 | } /* sample_to_mono */ | ||
268 | 212 | ||
269 | static void chunk_to_aiff_format(uint32_t *src, uint32_t *dst) ICODE_ATTR; | 213 | /* First obtain output buffer; when available, get PCM data */ |
270 | static void chunk_to_aiff_format(uint32_t *src, uint32_t *dst) | 214 | switch (getbuf) |
271 | { | ||
272 | if (num_channels == 1) | ||
273 | { | ||
274 | /* On big endian: | ||
275 | * |LLLLLLLLllllllll|RRRRRRRRrrrrrrrr| | ||
276 | * |LLLLLLLLllllllll|RRRRRRRRrrrrrrrr| => | ||
277 | * |MMMMMMMMmmmmmmmm|MMMMMMMMmmmmmmmm| | ||
278 | * | ||
279 | * On little endian: | ||
280 | * |llllllllLLLLLLLL|rrrrrrrrRRRRRRRR| | ||
281 | * |llllllllLLLLLLLL|rrrrrrrrRRRRRRRR| => | ||
282 | * |MMMMMMMMmmmmmmmm|MMMMMMMMmmmmmmmm| | ||
283 | */ | ||
284 | uint32_t *src_end = src + PCM_SAMP_PER_CHUNK; | ||
285 | |||
286 | do | ||
287 | { | ||
288 | sample_to_mono(&src, &dst); | ||
289 | sample_to_mono(&src, &dst); | ||
290 | sample_to_mono(&src, &dst); | ||
291 | sample_to_mono(&src, &dst); | ||
292 | sample_to_mono(&src, &dst); | ||
293 | sample_to_mono(&src, &dst); | ||
294 | sample_to_mono(&src, &dst); | ||
295 | sample_to_mono(&src, &dst); | ||
296 | } | ||
297 | while (src < src_end); | ||
298 | } | ||
299 | else | ||
300 | { | ||
301 | #ifdef ROCKBOX_BIG_ENDIAN | ||
302 | /* |LLLLLLLLllllllll|RRRRRRRRrrrrrrrr| => | ||
303 | * |LLLLLLLLllllllll|RRRRRRRRrrrrrrrr| | ||
304 | */ | ||
305 | ci->memcpy(dst, src, PCM_CHUNK_SIZE); | ||
306 | #else | ||
307 | /* |llllllllLLLLLLLL|rrrrrrrrRRRRRRRR| => | ||
308 | * |LLLLLLLLllllllll|RRRRRRRRrrrrrrrr| | ||
309 | */ | ||
310 | uint32_t *src_end = src + PCM_SAMP_PER_CHUNK; | ||
311 | |||
312 | do | ||
313 | { | 215 | { |
314 | *dst++ = swap_odd_even32(*src++); | 216 | case GETBUF_ENC: |
315 | *dst++ = swap_odd_even32(*src++); | 217 | if (!(data = ci->enc_encbuf_get_buffer(frame_size))) |
316 | *dst++ = swap_odd_even32(*src++); | 218 | continue; |
317 | *dst++ = swap_odd_even32(*src++); | 219 | getbuf = GETBUF_PCM; |
318 | *dst++ = swap_odd_even32(*src++); | 220 | case GETBUF_PCM: |
319 | *dst++ = swap_odd_even32(*src++); | 221 | if (!ci->enc_pcmbuf_read(data->data, PCM_SAMP_PER_CHUNK)) |
320 | *dst++ = swap_odd_even32(*src++); | 222 | continue; |
321 | *dst++ = swap_odd_even32(*src++); | 223 | getbuf = GETBUF_ENC; |
322 | } | 224 | } |
323 | while (src < src_end); | ||
324 | #endif | ||
325 | } | ||
326 | } /* chunk_to_aiff_format */ | ||
327 | 225 | ||
328 | static bool init_encoder(void) | 226 | data->hdr.size = frame_size; |
329 | { | 227 | data->pcm_count = PCM_SAMP_PER_CHUNK; |
330 | struct enc_inputs inputs; | ||
331 | struct enc_parameters params; | ||
332 | |||
333 | if (ci->enc_get_inputs == NULL || | ||
334 | ci->enc_set_parameters == NULL || | ||
335 | ci->enc_get_chunk == NULL || | ||
336 | ci->enc_finish_chunk == NULL || | ||
337 | ci->enc_get_pcm_data == NULL ) | ||
338 | return false; | ||
339 | |||
340 | ci->enc_get_inputs(&inputs); | ||
341 | |||
342 | if (inputs.config->afmt != AFMT_AIFF) | ||
343 | return false; | ||
344 | |||
345 | sample_rate = inputs.sample_rate; | ||
346 | num_channels = inputs.num_channels; | ||
347 | rec_mono_mode = inputs.rec_mono_mode; | ||
348 | err = 0; | ||
349 | |||
350 | /* configure the buffer system */ | ||
351 | params.afmt = AFMT_AIFF; | ||
352 | enc_size = PCM_CHUNK_SIZE*inputs.num_channels / 2; | ||
353 | params.chunk_size = enc_size; | ||
354 | params.enc_sample_rate = sample_rate; | ||
355 | params.reserve_bytes = 0; | ||
356 | params.events_callback = enc_events_callback; | ||
357 | ci->enc_set_parameters(¶ms); | ||
358 | |||
359 | return true; | ||
360 | } /* init_encoder */ | ||
361 | 228 | ||
362 | /* this is the codec entry point */ | 229 | frame_htobe((uint32_t *)data->data, frame_size); |
363 | enum codec_status codec_main(enum codec_entry_call_reason reason) | 230 | |
364 | { | 231 | ci->enc_pcmbuf_advance(PCM_SAMP_PER_CHUNK); |
365 | if (reason == CODEC_LOAD) { | 232 | ci->enc_encbuf_finish_buffer(); |
366 | if (!init_encoder()) | ||
367 | return CODEC_ERROR; | ||
368 | } | ||
369 | else if (reason == CODEC_UNLOAD) { | ||
370 | /* reset parameters to initial state */ | ||
371 | ci->enc_set_parameters(NULL); | ||
372 | } | 233 | } |
373 | 234 | ||
374 | return CODEC_OK; | 235 | return CODEC_OK; |
375 | } | 236 | } |
376 | 237 | ||
377 | /* this is called for each file to process */ | 238 | /* this is called by recording system */ |
378 | enum codec_status codec_run(void) | 239 | int ICODE_ATTR enc_callback(enum enc_callback_reason reason, |
240 | void *params) | ||
379 | { | 241 | { |
380 | /* main encoding loop */ | 242 | if (LIKELY(reason == ENC_CB_STREAM)) |
381 | while (ci->get_command(NULL) != CODEC_ACTION_HALT) | ||
382 | { | 243 | { |
383 | uint32_t *src = (uint32_t *)ci->enc_get_pcm_data(PCM_CHUNK_SIZE); | 244 | switch (((union enc_chunk_hdr *)params)->type) |
384 | struct enc_chunk_hdr *chunk; | 245 | { |
385 | 246 | case CHUNK_T_DATA: | |
386 | if (src == NULL) | 247 | return on_stream_data(params); |
387 | continue; | 248 | case CHUNK_T_STREAM_START: |
388 | 249 | return on_stream_start(); | |
389 | chunk = ci->enc_get_chunk(); | 250 | case CHUNK_T_STREAM_END: |
390 | chunk->enc_size = enc_size; | 251 | return on_stream_end(params); |
391 | chunk->num_pcm = PCM_SAMP_PER_CHUNK; | 252 | } |
392 | chunk->enc_data = ENC_CHUNK_SKIP_HDR(chunk->enc_data, chunk); | 253 | } |
393 | 254 | else if (reason == ENC_CB_INPUTS) | |
394 | chunk_to_aiff_format(src, (uint32_t *)chunk->enc_data); | 255 | { |
395 | 256 | struct enc_inputs *inputs = params; | |
396 | ci->enc_finish_chunk(); | 257 | sample_rate = inputs->sample_rate; |
258 | num_channels = inputs->num_channels; | ||
259 | frame_size = PCM_SAMP_PER_CHUNK*PCM_DEPTH_BYTES*num_channels; | ||
397 | } | 260 | } |
398 | 261 | ||
399 | return CODEC_OK; | 262 | return 0; |
400 | } | 263 | } |
diff --git a/lib/rbcodec/codecs/codecs.h b/lib/rbcodec/codecs/codecs.h index ae4233b7a6..672b1ded53 100644 --- a/lib/rbcodec/codecs/codecs.h +++ b/lib/rbcodec/codecs/codecs.h | |||
@@ -36,7 +36,7 @@ | |||
36 | #endif | 36 | #endif |
37 | #if (CONFIG_CODEC == SWCODEC) | 37 | #if (CONFIG_CODEC == SWCODEC) |
38 | #ifdef HAVE_RECORDING | 38 | #ifdef HAVE_RECORDING |
39 | #include "pcm_record.h" | 39 | #include "enc_base.h" |
40 | #endif | 40 | #endif |
41 | #include "dsp_core.h" | 41 | #include "dsp_core.h" |
42 | #include "dsp_misc.h" | 42 | #include "dsp_misc.h" |
@@ -72,12 +72,12 @@ | |||
72 | #define CODEC_ENC_MAGIC 0x52454E43 /* RENC */ | 72 | #define CODEC_ENC_MAGIC 0x52454E43 /* RENC */ |
73 | 73 | ||
74 | /* increase this every time the api struct changes */ | 74 | /* increase this every time the api struct changes */ |
75 | #define CODEC_API_VERSION 45 | 75 | #define CODEC_API_VERSION 46 |
76 | 76 | ||
77 | /* update this to latest version if a change to the api struct breaks | 77 | /* update this to latest version if a change to the api struct breaks |
78 | backwards compatibility (and please take the opportunity to sort in any | 78 | backwards compatibility (and please take the opportunity to sort in any |
79 | new function which are "waiting" at the end of the function table) */ | 79 | new function which are "waiting" at the end of the function table) */ |
80 | #define CODEC_MIN_API_VERSION 45 | 80 | #define CODEC_MIN_API_VERSION 46 |
81 | 81 | ||
82 | /* reasons for calling codec main entrypoint */ | 82 | /* reasons for calling codec main entrypoint */ |
83 | enum codec_entry_call_reason { | 83 | enum codec_entry_call_reason { |
@@ -96,6 +96,9 @@ enum codec_command_action { | |||
96 | CODEC_ACTION_HALT = -1, | 96 | CODEC_ACTION_HALT = -1, |
97 | CODEC_ACTION_NULL = 0, | 97 | CODEC_ACTION_NULL = 0, |
98 | CODEC_ACTION_SEEK_TIME = 1, | 98 | CODEC_ACTION_SEEK_TIME = 1, |
99 | #ifdef HAVE_RECORDING | ||
100 | CODEC_ACTION_STREAM_FINISH = 2, | ||
101 | #endif | ||
99 | }; | 102 | }; |
100 | 103 | ||
101 | /* NOTE: To support backwards compatibility, only add new functions at | 104 | /* NOTE: To support backwards compatibility, only add new functions at |
@@ -200,24 +203,18 @@ struct codec_api { | |||
200 | #endif | 203 | #endif |
201 | 204 | ||
202 | #ifdef HAVE_RECORDING | 205 | #ifdef HAVE_RECORDING |
203 | void (*enc_get_inputs)(struct enc_inputs *inputs); | 206 | int (*enc_pcmbuf_read)(void *buf, int count); |
204 | void (*enc_set_parameters)(struct enc_parameters *params); | 207 | int (*enc_pcmbuf_advance)(int count); |
205 | struct enc_chunk_hdr * (*enc_get_chunk)(void); | 208 | struct enc_chunk_data * (*enc_encbuf_get_buffer)(size_t need); |
206 | void (*enc_finish_chunk)(void); | 209 | void (*enc_encbuf_finish_buffer)(void); |
207 | unsigned char * (*enc_get_pcm_data)(size_t size); | 210 | ssize_t (*enc_stream_read)(void *buf, size_t count); |
208 | size_t (*enc_unget_pcm_data)(size_t size); | 211 | off_t (*enc_stream_lseek)(off_t offset, int whence); |
209 | 212 | ssize_t (*enc_stream_write)(const void *buf, size_t count); | |
210 | /* file */ | ||
211 | int (*open)(const char* pathname, int flags, ...); | ||
212 | int (*close)(int fd); | ||
213 | ssize_t (*read)(int fd, void* buf, size_t count); | ||
214 | off_t (*lseek)(int fd, off_t offset, int whence); | ||
215 | ssize_t (*write)(int fd, const void* buf, size_t count); | ||
216 | int (*round_value_to_list32)(unsigned long value, | 213 | int (*round_value_to_list32)(unsigned long value, |
217 | const unsigned long list[], | 214 | const unsigned long list[], |
218 | int count, | 215 | int count, |
219 | bool signd); | 216 | bool signd); |
220 | #endif | 217 | #endif /* HAVE_RECORDING */ |
221 | 218 | ||
222 | /* new stuff at the end, sort into place next time | 219 | /* new stuff at the end, sort into place next time |
223 | the API gets incompatible */ | 220 | the API gets incompatible */ |
@@ -229,6 +226,7 @@ struct codec_header { | |||
229 | enum codec_status(*entry_point)(enum codec_entry_call_reason reason); | 226 | enum codec_status(*entry_point)(enum codec_entry_call_reason reason); |
230 | enum codec_status(*run_proc)(void); | 227 | enum codec_status(*run_proc)(void); |
231 | struct codec_api **api; | 228 | struct codec_api **api; |
229 | void * rec_extension[]; /* extension for encoders */ | ||
232 | }; | 230 | }; |
233 | 231 | ||
234 | #ifdef CODEC | 232 | #ifdef CODEC |
@@ -249,7 +247,7 @@ extern unsigned char plugin_end_addr[]; | |||
249 | __attribute__ ((section (".header")))= { \ | 247 | __attribute__ ((section (".header")))= { \ |
250 | { CODEC_ENC_MAGIC, TARGET_ID, CODEC_API_VERSION, \ | 248 | { CODEC_ENC_MAGIC, TARGET_ID, CODEC_API_VERSION, \ |
251 | plugin_start_addr, plugin_end_addr }, codec_start, \ | 249 | plugin_start_addr, plugin_end_addr }, codec_start, \ |
252 | codec_run, &ci }; | 250 | codec_run, &ci, { enc_callback } }; |
253 | 251 | ||
254 | #else /* def SIMULATOR */ | 252 | #else /* def SIMULATOR */ |
255 | /* decoders */ | 253 | /* decoders */ |
@@ -262,7 +260,7 @@ extern unsigned char plugin_end_addr[]; | |||
262 | #define CODEC_ENC_HEADER \ | 260 | #define CODEC_ENC_HEADER \ |
263 | const struct codec_header __header = { \ | 261 | const struct codec_header __header = { \ |
264 | { CODEC_ENC_MAGIC, TARGET_ID, CODEC_API_VERSION, NULL, NULL }, \ | 262 | { CODEC_ENC_MAGIC, TARGET_ID, CODEC_API_VERSION, NULL, NULL }, \ |
265 | codec_start, codec_run, &ci }; | 263 | codec_start, codec_run, &ci, { enc_callback } }; |
266 | #endif /* SIMULATOR */ | 264 | #endif /* SIMULATOR */ |
267 | #endif /* CODEC */ | 265 | #endif /* CODEC */ |
268 | 266 | ||
@@ -277,12 +275,19 @@ void *codec_get_buffer_callback(size_t *size); | |||
277 | int codec_load_buf(int hid, struct codec_api *api); | 275 | int codec_load_buf(int hid, struct codec_api *api); |
278 | int codec_load_file(const char* codec, struct codec_api *api); | 276 | int codec_load_file(const char* codec, struct codec_api *api); |
279 | int codec_run_proc(void); | 277 | int codec_run_proc(void); |
280 | int codec_halt(void); | ||
281 | int codec_close(void); | 278 | int codec_close(void); |
279 | #if CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING) | ||
280 | enc_callback_t codec_get_enc_callback(void); | ||
281 | #else | ||
282 | #define codec_get_enc_callback() NULL | ||
283 | #endif | ||
282 | 284 | ||
283 | /* defined by the codec */ | 285 | /* defined by the codec */ |
284 | enum codec_status codec_start(enum codec_entry_call_reason reason); | 286 | enum codec_status codec_start(enum codec_entry_call_reason reason); |
285 | enum codec_status codec_main(enum codec_entry_call_reason reason); | 287 | enum codec_status codec_main(enum codec_entry_call_reason reason); |
286 | enum codec_status codec_run(void); | 288 | enum codec_status codec_run(void); |
289 | #if CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING) | ||
290 | int enc_callback(enum enc_callback_reason reason, void *params); | ||
291 | #endif | ||
287 | 292 | ||
288 | #endif /* _CODECS_H_ */ | 293 | #endif /* _CODECS_H_ */ |
diff --git a/lib/rbcodec/codecs/mp3_enc.c b/lib/rbcodec/codecs/mp3_enc.c index 000eedd849..a349f99f25 100644 --- a/lib/rbcodec/codecs/mp3_enc.c +++ b/lib/rbcodec/codecs/mp3_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 |
@@ -37,17 +38,9 @@ | |||
37 | 38 | ||
38 | CODEC_ENC_HEADER | 39 | CODEC_ENC_HEADER |
39 | 40 | ||
40 | #define ENC_PADDING_FRAMES1 2 | 41 | #define SAMPL2 576 |
41 | #define ENC_PADDING_FRAMES2 4 | 42 | #define SBLIMIT 32 |
42 | #define ENC_DELAY_SAMP 576 | 43 | #define HTN 16 |
43 | #define ENC_DELAY_SIZE (ENC_DELAY_SAMP*4) | ||
44 | #define SAMP_PER_FRAME1 1152 | ||
45 | #define SAMP_PER_FRAME2 576 | ||
46 | #define PCM_CHUNK_SIZE1 (SAMP_PER_FRAME1*4) | ||
47 | #define PCM_CHUNK_SIZE2 (SAMP_PER_FRAME2*4) | ||
48 | #define SAMPL2 576 | ||
49 | #define SBLIMIT 32 | ||
50 | #define HTN 16 | ||
51 | #define memcpy ci->memcpy | 44 | #define memcpy ci->memcpy |
52 | #define memset ci->memset | 45 | #define memset ci->memset |
53 | #define putlong(c, s) if(s+sz <= 32) { cc = (cc << s) | c; sz+= s; } \ | 46 | #define putlong(c, s) if(s+sz <= 32) { cc = (cc << s) | c; sz+= s; } \ |
@@ -79,18 +72,24 @@ typedef struct { | |||
79 | } side_info_t; | 72 | } side_info_t; |
80 | 73 | ||
81 | typedef struct { | 74 | typedef struct { |
82 | side_info_t cod_info[2][2]; | 75 | side_info_t cod_info[2][2]; |
83 | mpeg_t mpg; | 76 | mpeg_t mpg; |
84 | long frac_per_frame; | 77 | long frac_per_frame; |
85 | long byte_per_frame; | 78 | long byte_per_frame; |
86 | long slot_lag; | 79 | long req_byte_per_frame; |
87 | int sideinfo_len; | 80 | long slot_lag; |
88 | int mean_bits; | 81 | int sideinfo_len; |
89 | int ResvSize; | 82 | int mean_bits; |
90 | int channels; | 83 | int ResvSize; |
91 | int rec_mono_mode; | 84 | int channels; |
92 | int granules; | 85 | int granules; |
93 | long samplerate; | 86 | long src_samplerate; |
87 | long samplerate; | ||
88 | short *samp_buffer; | ||
89 | unsigned samp_per_frame; | ||
90 | int flush_frames; | ||
91 | int delay; | ||
92 | int padding; | ||
94 | } config_t; | 93 | } config_t; |
95 | 94 | ||
96 | typedef struct { | 95 | typedef struct { |
@@ -118,7 +117,8 @@ struct huffcodebig { | |||
118 | #define shft_n(x,n) ((x) >> n) | 117 | #define shft_n(x,n) ((x) >> n) |
119 | #define SQRT 724 /* sqrt(2) * 512 */ | 118 | #define SQRT 724 /* sqrt(2) * 512 */ |
120 | 119 | ||
121 | static short mfbuf [2*(1152+512)] IBSS_ATTR; /* 3328 Bytes */ | 120 | static short mfbuf [2*(1152+512)] IBSS_ATTR |
121 | /* for memcpy and 32-bit access */ MEM_ALIGN_ATTR; /* 3328 Bytes */ | ||
122 | static int sb_data [2][2][18][SBLIMIT] IBSS_ATTR; /* 13824 Bytes */ | 122 | static int sb_data [2][2][18][SBLIMIT] IBSS_ATTR; /* 13824 Bytes */ |
123 | static int mdct_freq [SAMPL2] IBSS_ATTR; /* 2304 Bytes */ | 123 | static int mdct_freq [SAMPL2] IBSS_ATTR; /* 2304 Bytes */ |
124 | static char mdct_sign [SAMPL2] IBSS_ATTR; /* 576 Bytes */ | 124 | static char mdct_sign [SAMPL2] IBSS_ATTR; /* 576 Bytes */ |
@@ -171,12 +171,7 @@ static uint8_t t16l [256] IBSS_ATTR; | |||
171 | static uint8_t t24l [256] IBSS_ATTR; | 171 | static uint8_t t24l [256] IBSS_ATTR; |
172 | static struct huffcodetab ht [HTN] IBSS_ATTR; | 172 | static struct huffcodetab ht [HTN] IBSS_ATTR; |
173 | 173 | ||
174 | static unsigned pcm_chunk_size IBSS_ATTR; | ||
175 | static unsigned samp_per_frame IBSS_ATTR; | ||
176 | |||
177 | static config_t cfg IBSS_ATTR; | 174 | static config_t cfg IBSS_ATTR; |
178 | static char *res_buffer; | ||
179 | static int32_t err IBSS_ATTR; | ||
180 | static uint8_t band_scale_f[22]; | 175 | static uint8_t band_scale_f[22]; |
181 | 176 | ||
182 | static const uint8_t ht_count_const[2][2][16] = | 177 | static const uint8_t ht_count_const[2][2][16] = |
@@ -848,42 +843,56 @@ static int count_bit1 ( short *ix, uint32_t start, uint32_t end, int *bits ); | |||
848 | static int count_bigv ( short *ix, uint32_t start, uint32_t end, int table0, int table1, | 843 | static int count_bigv ( short *ix, uint32_t start, uint32_t end, int table0, int table1, |
849 | int *bits); | 844 | int *bits); |
850 | 845 | ||
846 | static inline uint32_t encodeHeader( int padding, long bitr_id ) | ||
847 | { | ||
848 | /* | ||
849 | * MPEG header layout: | ||
850 | * AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM | ||
851 | * A (31-21) = frame sync | ||
852 | * B (20-19) = MPEG type | ||
853 | * C (18-17) = MPEG layer | ||
854 | * D (16) = protection bit | ||
855 | * E (15-12) = bitrate index | ||
856 | * F (11-10) = samplerate index | ||
857 | * G (9) = padding bit | ||
858 | * H (8) = private bit | ||
859 | * I (7-6) = channel mode | ||
860 | * J (5-4) = mode extension (jstereo only) | ||
861 | * K (3) = copyright bit | ||
862 | * L (2) = original | ||
863 | * M (1-0) = emphasis | ||
864 | */ | ||
865 | return (0xffe00000 ) /* frame sync (AAAAAAAAA AAA) */ | ||
866 | | (0x2 << 19) /* mp3 type (upper): 1 (BB) */ | ||
867 | | (cfg.mpg.type << 19) | ||
868 | | (0x1 << 17) /* mp3 layer: 01 (CC) */ | ||
869 | | (0x1 << 16) /* mp3 crc: 1 (D) */ | ||
870 | | (bitr_id << 12) | ||
871 | | (cfg.mpg.smpl_id << 10) | ||
872 | | (padding << 9) | ||
873 | | (cfg.mpg.mode << 6) | ||
874 | | (0x1 << 2); /* mp3 org: 1 (L) */ | ||
875 | /* no emphasis (bits 0-1) */ | ||
876 | } | ||
877 | |||
878 | static long calcFrameSize(int bitr_id, long *frac) | ||
879 | { | ||
880 | unsigned long v = bitr_index[cfg.mpg.type][bitr_id]; | ||
881 | v = SAMPL2 * 16000 * v / (2 - cfg.mpg.type); | ||
882 | v /= cfg.samplerate; | ||
883 | |||
884 | if (frac) | ||
885 | *frac = v % 64; | ||
886 | |||
887 | return v / 64; | ||
851 | 888 | ||
889 | } | ||
852 | static void encodeSideInfo( side_info_t si[2][2] ) | 890 | static void encodeSideInfo( side_info_t si[2][2] ) |
853 | { | 891 | { |
854 | int gr, ch, header; | 892 | int gr, ch; |
855 | uint32_t cc=0, sz=0; | 893 | uint32_t cc=0, sz=0; |
856 | 894 | ||
857 | /* | 895 | putbits( encodeHeader( cfg.mpg.padding, cfg.mpg.bitr_id ), 32 ); |
858 | * MPEG header layout: | ||
859 | * AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM | ||
860 | * A (31-21) = frame sync | ||
861 | * B (20-19) = MPEG type | ||
862 | * C (18-17) = MPEG layer | ||
863 | * D (16) = protection bit | ||
864 | * E (15-12) = bitrate index | ||
865 | * F (11-10) = samplerate index | ||
866 | * G (9) = padding bit | ||
867 | * H (8) = private bit | ||
868 | * I (7-6) = channel mode | ||
869 | * J (5-4) = mode extension (jstereo only) | ||
870 | * K (3) = copyright bit | ||
871 | * L (2) = original | ||
872 | * M (1-0) = emphasis | ||
873 | */ | ||
874 | |||
875 | header = (0xfff00000) | /* frame sync (AAAAAAAAA AAA) | ||
876 | mp3 type (upper): 1 (B) */ | ||
877 | (0x01 << 17) | /* mp3 layer: 01 (CC) */ | ||
878 | ( 0x1 << 16) | /* mp3 crc: 1 (D) */ | ||
879 | ( 0x1 << 2); /* mp3 org: 1 (L) */ | ||
880 | header |= cfg.mpg.type << 19; | ||
881 | header |= cfg.mpg.bitr_id << 12; | ||
882 | header |= cfg.mpg.smpl_id << 10; | ||
883 | header |= cfg.mpg.padding << 9; | ||
884 | header |= cfg.mpg.mode << 6; | ||
885 | /* no emphasis (bits 0-1) */ | ||
886 | putbits( header, 32 ); | ||
887 | 896 | ||
888 | if(cfg.mpg.type == 1) | 897 | if(cfg.mpg.type == 1) |
889 | { /* MPEG1 */ | 898 | { /* MPEG1 */ |
@@ -1501,8 +1510,8 @@ static void iteration_loop(int *xr, side_info_t *si, int gr_cnt) | |||
1501 | 1510 | ||
1502 | 1511 | ||
1503 | /* returns sum_j=0^31 a[j]*cos(PI*j*(k+1/2)/32), 0<=k<32 */ | 1512 | /* returns sum_j=0^31 a[j]*cos(PI*j*(k+1/2)/32), 0<=k<32 */ |
1504 | void window_subband1(short *wk, int sb0[SBLIMIT], int sb1[SBLIMIT]) ICODE_ATTR; | 1513 | static void ICODE_ATTR window_subband1(short *wk, int sb0[SBLIMIT], |
1505 | void window_subband1(short *wk, int sb0[SBLIMIT], int sb1[SBLIMIT]) | 1514 | int sb1[SBLIMIT]) |
1506 | { | 1515 | { |
1507 | int k, i, u, v; | 1516 | int k, i, u, v; |
1508 | short *wp, *x1, *x2; | 1517 | short *wp, *x1, *x2; |
@@ -1761,8 +1770,7 @@ void window_subband1(short *wk, int sb0[SBLIMIT], int sb1[SBLIMIT]) | |||
1761 | #endif | 1770 | #endif |
1762 | } | 1771 | } |
1763 | 1772 | ||
1764 | void window_subband2(short *x1, int a[SBLIMIT]) ICODE_ATTR; | 1773 | static void ICODE_ATTR window_subband2(short *x1, int a[SBLIMIT]) |
1765 | void window_subband2(short *x1, int a[SBLIMIT]) | ||
1766 | { | 1774 | { |
1767 | int xr; | 1775 | int xr; |
1768 | short *wp = enwindow; | 1776 | short *wp = enwindow; |
@@ -1879,8 +1887,7 @@ void window_subband2(short *x1, int a[SBLIMIT]) | |||
1879 | xr = a[29]; a[29] += a[ 2]; a[ 2] -= xr; | 1887 | xr = a[29]; a[29] += a[ 2]; a[ 2] -= xr; |
1880 | } | 1888 | } |
1881 | 1889 | ||
1882 | void mdct_long(int *out, int *in) ICODE_ATTR; | 1890 | static void ICODE_ATTR mdct_long(int *out, int *in) |
1883 | void mdct_long(int *out, int *in) | ||
1884 | { | 1891 | { |
1885 | int ct,st; | 1892 | int ct,st; |
1886 | int tc1, tc2, tc3, tc4, ts5, ts6, ts7, ts8; | 1893 | int tc1, tc2, tc3, tc4, ts5, ts6, ts7, ts8; |
@@ -1969,44 +1976,51 @@ static int find_samplerate_index(long freq, int *mp3_type) | |||
1969 | return i; | 1976 | return i; |
1970 | } | 1977 | } |
1971 | 1978 | ||
1972 | static bool init_mp3_encoder_engine(int sample_rate, | 1979 | static void mp3_encoder_reset(void) |
1973 | int num_channels, | 1980 | { |
1974 | int rec_mono_mode, | 1981 | memset(&cfg.cod_info, 0, sizeof(cfg.cod_info)); |
1975 | struct encoder_config *enc_cfg) | 1982 | memset(mfbuf , 0, sizeof(mfbuf )); |
1983 | memset(mdct_freq , 0, sizeof(mdct_freq )); | ||
1984 | memset(enc_data , 0, sizeof(enc_data )); | ||
1985 | memset(sb_data , 0, sizeof(sb_data )); | ||
1986 | memset(&CodedData , 0, sizeof(CodedData )); | ||
1987 | cfg.slot_lag = 0; | ||
1988 | } | ||
1989 | |||
1990 | static void mp3_encoder_init(unsigned long sample_rate, int num_channels, | ||
1991 | unsigned long bitrate) | ||
1976 | { | 1992 | { |
1977 | const bool stereo = num_channels > 1; | 1993 | mp3_encoder_reset(); |
1978 | uint32_t avg_byte_per_frame; | 1994 | |
1979 | 1995 | const bool stereo = num_channels > 1; | |
1980 | cfg.channels = stereo ? 2 : 1; | 1996 | cfg.channels = stereo ? 2 : 1; |
1981 | cfg.rec_mono_mode = rec_mono_mode; | 1997 | cfg.mpg.mode = stereo ? 0 : 3; /* 0=stereo, 3=mono */ |
1982 | cfg.mpg.mode = stereo ? 0 : 3; /* 0=stereo, 3=mono */ | 1998 | cfg.mpg.smpl_id = find_samplerate_index(sample_rate, &cfg.mpg.type); |
1983 | cfg.mpg.smpl_id = find_samplerate_index(sample_rate, &cfg.mpg.type); | 1999 | cfg.samplerate = sampr_index[cfg.mpg.type][cfg.mpg.smpl_id]; |
1984 | cfg.samplerate = sampr_index[cfg.mpg.type][cfg.mpg.smpl_id]; | 2000 | cfg.src_samplerate = sample_rate; |
1985 | cfg.mpg.bitr_id = find_bitrate_index(cfg.mpg.type, | 2001 | cfg.mpg.bitr_id = find_bitrate_index(cfg.mpg.type, bitrate, stereo); |
1986 | enc_cfg->mp3_enc.bitrate, | 2002 | cfg.mpg.bitrate = bitr_index[cfg.mpg.type][cfg.mpg.bitr_id]; |
1987 | stereo); | 2003 | cfg.mpg.num_bands = num_bands[stereo ? cfg.mpg.type : 2][cfg.mpg.bitr_id]; |
1988 | cfg.mpg.bitrate = bitr_index[cfg.mpg.type][cfg.mpg.bitr_id]; | ||
1989 | cfg.mpg.num_bands = num_bands[stereo ? cfg.mpg.type : 2][cfg.mpg.bitr_id]; | ||
1990 | 2004 | ||
1991 | if (cfg.mpg.type == 1) | 2005 | if (cfg.mpg.type == 1) |
1992 | { | 2006 | { |
1993 | cfg.granules = 2; | 2007 | cfg.granules = 2; |
1994 | pcm_chunk_size = PCM_CHUNK_SIZE1; | 2008 | cfg.samp_per_frame = 1152; |
1995 | samp_per_frame = SAMP_PER_FRAME1; | 2009 | cfg.flush_frames = 2; |
1996 | } | 2010 | } |
1997 | else | 2011 | else |
1998 | { | 2012 | { |
1999 | cfg.granules = 1; | 2013 | cfg.granules = 1; |
2000 | pcm_chunk_size = PCM_CHUNK_SIZE2; | 2014 | cfg.samp_per_frame = 576; |
2001 | samp_per_frame = SAMP_PER_FRAME2; | 2015 | cfg.flush_frames = 3; |
2002 | } | 2016 | } |
2003 | 2017 | ||
2018 | cfg.delay = 576-16; | ||
2019 | cfg.padding = 3*576+16; | ||
2020 | |||
2021 | cfg.samp_buffer = mfbuf + 2*512; | ||
2022 | |||
2004 | memcpy(scalefac, sfBand[cfg.mpg.smpl_id + 3*cfg.mpg.type], sizeof(scalefac)); | 2023 | memcpy(scalefac, sfBand[cfg.mpg.smpl_id + 3*cfg.mpg.type], sizeof(scalefac)); |
2005 | memset(mfbuf , 0 , sizeof(mfbuf )); | ||
2006 | memset(mdct_freq , 0 , sizeof(mdct_freq )); | ||
2007 | memset(enc_data , 0 , sizeof(enc_data )); | ||
2008 | memset(sb_data , 0 , sizeof(sb_data )); | ||
2009 | memset(&CodedData, 0 , sizeof(CodedData )); | ||
2010 | memcpy(ca , ca_const , sizeof(ca )); | 2024 | memcpy(ca , ca_const , sizeof(ca )); |
2011 | memcpy(cs , cs_const , sizeof(cs )); | 2025 | memcpy(cs , cs_const , sizeof(cs )); |
2012 | memcpy(cx , cx_const , sizeof(cx )); | 2026 | memcpy(cx , cx_const , sizeof(cx )); |
@@ -2052,6 +2066,7 @@ static bool init_mp3_encoder_engine(int sample_rate, | |||
2052 | memcpy(t16l , t16l_const , sizeof(t16l )); | 2066 | memcpy(t16l , t16l_const , sizeof(t16l )); |
2053 | memcpy(t24l , t24l_const , sizeof(t24l )); | 2067 | memcpy(t24l , t24l_const , sizeof(t24l )); |
2054 | memcpy(ht , ht_const , sizeof(ht )); | 2068 | memcpy(ht , ht_const , sizeof(ht )); |
2069 | memset(band_scale_f, 0 , sizeof(band_scale_f)); | ||
2055 | 2070 | ||
2056 | ht[ 0].table = NULL; ht[ 0].hlen = NULL; /* Apparently not used */ | 2071 | ht[ 0].table = NULL; ht[ 0].hlen = NULL; /* Apparently not used */ |
2057 | ht[ 1].table = t1HB; ht[ 1].hlen = t1l; | 2072 | ht[ 1].table = t1HB; ht[ 1].hlen = t1l; |
@@ -2071,90 +2086,14 @@ static bool init_mp3_encoder_engine(int sample_rate, | |||
2071 | ht[15].table = t15HB; ht[15].hlen = t15l; | 2086 | ht[15].table = t15HB; ht[15].hlen = t15l; |
2072 | 2087 | ||
2073 | /* Figure average number of 'bytes' per frame */ | 2088 | /* Figure average number of 'bytes' per frame */ |
2074 | avg_byte_per_frame = SAMPL2 * 16000 * cfg.mpg.bitrate / (2 - cfg.mpg.type); | 2089 | cfg.byte_per_frame = calcFrameSize(cfg.mpg.bitr_id, &cfg.frac_per_frame); |
2075 | avg_byte_per_frame = avg_byte_per_frame / cfg.samplerate; | ||
2076 | cfg.byte_per_frame = avg_byte_per_frame / 64; | ||
2077 | cfg.frac_per_frame = avg_byte_per_frame & 63; | ||
2078 | cfg.slot_lag = 0; | ||
2079 | cfg.sideinfo_len = 32 + (cfg.mpg.type ? (cfg.channels == 1 ? 136 : 256) | 2090 | cfg.sideinfo_len = 32 + (cfg.mpg.type ? (cfg.channels == 1 ? 136 : 256) |
2080 | : (cfg.channels == 1 ? 72 : 136)); | 2091 | : (cfg.channels == 1 ? 72 : 136)); |
2081 | 2092 | ||
2082 | return true; | 2093 | cfg.req_byte_per_frame = ALIGN_UP(cfg.byte_per_frame + 1, |
2094 | sizeof (uint32_t)); | ||
2083 | } | 2095 | } |
2084 | 2096 | ||
2085 | static inline void to_mono(uint16_t **samp) | ||
2086 | { | ||
2087 | int16_t l = **samp; | ||
2088 | int16_t r = *(*samp+1); | ||
2089 | int32_t m; | ||
2090 | |||
2091 | switch(cfg.rec_mono_mode) | ||
2092 | { | ||
2093 | case 1: | ||
2094 | /* mono = L */ | ||
2095 | m = l; | ||
2096 | break; | ||
2097 | case 2: | ||
2098 | /* mono = R */ | ||
2099 | m = r; | ||
2100 | break; | ||
2101 | case 0: | ||
2102 | default: | ||
2103 | /* mono = (L+R)/2 */ | ||
2104 | m = l + r + err; | ||
2105 | err = m & 1; | ||
2106 | m >>= 1; | ||
2107 | break; | ||
2108 | } | ||
2109 | *(*samp)++ = (uint16_t)m; | ||
2110 | *(*samp)++ = (uint16_t)m; | ||
2111 | } /* to_mono */ | ||
2112 | |||
2113 | static void to_mono_mm(void) ICODE_ATTR; | ||
2114 | static void to_mono_mm(void) | ||
2115 | { | ||
2116 | /* |llllllllllllllll|rrrrrrrrrrrrrrrr| => | ||
2117 | * |mmmmmmmmmmmmmmmm|mmmmmmmmmmmmmmmm| | ||
2118 | */ | ||
2119 | uint16_t *samp = &mfbuf[2*512]; | ||
2120 | uint16_t *samp_end = samp + 2*samp_per_frame; | ||
2121 | |||
2122 | do | ||
2123 | { | ||
2124 | to_mono(&samp); | ||
2125 | to_mono(&samp); | ||
2126 | to_mono(&samp); | ||
2127 | to_mono(&samp); | ||
2128 | to_mono(&samp); | ||
2129 | to_mono(&samp); | ||
2130 | to_mono(&samp); | ||
2131 | to_mono(&samp); | ||
2132 | } | ||
2133 | while (samp < samp_end); | ||
2134 | } /* to_mono_mm */ | ||
2135 | |||
2136 | #ifdef ROCKBOX_LITTLE_ENDIAN | ||
2137 | /* Swaps a frame to big endian */ | ||
2138 | static inline void byte_swap_frame32(uint32_t *dst, uint32_t *src, | ||
2139 | size_t size) | ||
2140 | { | ||
2141 | uint32_t *src_end = SKIPBYTES(src, size); | ||
2142 | |||
2143 | do | ||
2144 | { | ||
2145 | *dst++ = swap32(*src++); | ||
2146 | *dst++ = swap32(*src++); | ||
2147 | *dst++ = swap32(*src++); | ||
2148 | *dst++ = swap32(*src++); | ||
2149 | *dst++ = swap32(*src++); | ||
2150 | *dst++ = swap32(*src++); | ||
2151 | *dst++ = swap32(*src++); | ||
2152 | *dst++ = swap32(*src++); | ||
2153 | } | ||
2154 | while(src < src_end); | ||
2155 | } /* byte_swap_frame32 */ | ||
2156 | #endif /* ROCKBOX_LITTLE_ENDIAN */ | ||
2157 | |||
2158 | static void set_scale_facs(int *mdct_freq) | 2097 | static void set_scale_facs(int *mdct_freq) |
2159 | { | 2098 | { |
2160 | unsigned int i, is, ie, k, s; | 2099 | unsigned int i, is, ie, k, s; |
@@ -2188,12 +2127,10 @@ static void set_scale_facs(int *mdct_freq) | |||
2188 | } | 2127 | } |
2189 | } | 2128 | } |
2190 | 2129 | ||
2191 | static void encode_frame(char *buffer, struct enc_chunk_hdr *chunk) | 2130 | static size_t ICODE_ATTR mp3_encoder_encode_frame(uint8_t *outbuf) |
2192 | ICODE_ATTR; | ||
2193 | static void encode_frame(char *buffer, struct enc_chunk_hdr *chunk) | ||
2194 | { | 2131 | { |
2195 | int gr, gr_cnt; | 2132 | int gr, gr_cnt; |
2196 | uint32_t max; | 2133 | uint32_t max; |
2197 | 2134 | ||
2198 | /* encode one mp3 frame in this loop */ | 2135 | /* encode one mp3 frame in this loop */ |
2199 | CodedData.bitpos = 0; | 2136 | CodedData.bitpos = 0; |
@@ -2211,28 +2148,6 @@ static void encode_frame(char *buffer, struct enc_chunk_hdr *chunk) | |||
2211 | - cfg.sideinfo_len) / cfg.granules / cfg.channels | 2148 | - cfg.sideinfo_len) / cfg.granules / cfg.channels |
2212 | - 42; // reserved for scale_facs | 2149 | - 42; // reserved for scale_facs |
2213 | 2150 | ||
2214 | /* shift out old samples */ | ||
2215 | memcpy(mfbuf, mfbuf + 2*cfg.granules*576, 4*512); | ||
2216 | |||
2217 | if (chunk->flags & CHUNKF_START_FILE) | ||
2218 | { | ||
2219 | /* prefix silent samples for encoder delay */ | ||
2220 | memset(mfbuf + 2*512, 0, ENC_DELAY_SIZE); | ||
2221 | /* read new samples to iram for further processing */ | ||
2222 | memcpy(mfbuf + 2*512 + ENC_DELAY_SIZE/2, | ||
2223 | buffer, pcm_chunk_size - ENC_DELAY_SIZE); | ||
2224 | chunk->num_pcm = samp_per_frame - ENC_DELAY_SAMP; | ||
2225 | } | ||
2226 | else | ||
2227 | { | ||
2228 | /* read new samples to iram for further processing */ | ||
2229 | memcpy(mfbuf + 2*512, buffer, pcm_chunk_size); | ||
2230 | chunk->num_pcm = samp_per_frame; | ||
2231 | } | ||
2232 | |||
2233 | if (cfg.channels == 1) | ||
2234 | to_mono_mm(); | ||
2235 | |||
2236 | cfg.ResvSize = 0; | 2151 | cfg.ResvSize = 0; |
2237 | gr_cnt = cfg.granules * cfg.channels; | 2152 | gr_cnt = cfg.granules * cfg.channels; |
2238 | CodedData.bitpos = cfg.sideinfo_len; /* leave space for mp3 header */ | 2153 | CodedData.bitpos = cfg.sideinfo_len; /* leave space for mp3 header */ |
@@ -2366,264 +2281,398 @@ static void encode_frame(char *buffer, struct enc_chunk_hdr *chunk) | |||
2366 | } | 2281 | } |
2367 | } | 2282 | } |
2368 | 2283 | ||
2369 | chunk->enc_size = cfg.byte_per_frame + cfg.mpg.padding; | 2284 | /* shift out old samples */ |
2285 | memmove(mfbuf, mfbuf + 2*cfg.granules*576, 4*512); | ||
2370 | 2286 | ||
2371 | /* finish this chunk by adding sideinfo header data */ | 2287 | /* finish this chunk by adding sideinfo header data */ |
2372 | CodedData.bitpos = 0; | 2288 | CodedData.bitpos = 0; |
2373 | encodeSideInfo( cfg.cod_info ); | 2289 | encodeSideInfo( cfg.cod_info ); |
2374 | 2290 | ||
2375 | #ifdef ROCKBOX_BIG_ENDIAN | 2291 | long size = cfg.byte_per_frame + cfg.mpg.padding; |
2376 | /* copy chunk to enc_buffer */ | 2292 | |
2377 | memcpy(chunk->enc_data, CodedData.bbuf, chunk->enc_size); | 2293 | #ifdef ROCKBOX_LITTLE_ENDIAN |
2294 | /* convert frame to big endian */ | ||
2295 | const uint32_t *src = CodedData.bbuf; | ||
2296 | uint32_t *dst = (uint32_t *)outbuf; | ||
2297 | |||
2298 | for(long i = 0; i < size; i += sizeof(uint32_t)) | ||
2299 | *dst++ = swap32(*src++); | ||
2378 | #else | 2300 | #else |
2379 | /* swap frame to big endian */ | 2301 | memcpy(outbuf, CodedData.bbuf, size); |
2380 | byte_swap_frame32((uint32_t *)chunk->enc_data, CodedData.bbuf, chunk->enc_size); | 2302 | #endif /* ROCKBOX_LITTLE_ENDIAN */ |
2381 | #endif | ||
2382 | } /* encode_frame */ | ||
2383 | 2303 | ||
2384 | /* called very often - inline */ | 2304 | return size; |
2385 | static inline bool is_file_data_ok(struct enc_file_event_data *filed) | 2305 | } |
2386 | { | ||
2387 | return filed->rec_file >= 0 && (long)filed->chunk->flags >= 0; | ||
2388 | } /* is_event_ok */ | ||
2389 | 2306 | ||
2390 | static unsigned char mp3_data[16384] __attribute__((aligned(4))); | ||
2391 | static unsigned int mp3_data_len; /* current data size in buffer */ | ||
2392 | 2307 | ||
2393 | /* called very often - inline */ | 2308 | /*======== Codec section ========*/ |
2394 | static inline bool on_write_chunk(struct enc_file_event_data *data) | ||
2395 | { | ||
2396 | if (!is_file_data_ok(data)) | ||
2397 | return false; | ||
2398 | 2309 | ||
2399 | if (data->chunk->enc_data == NULL) | 2310 | /* CRC code lovingly ripped from: |
2400 | { | 2311 | * github.com/CFR-maniac/lame/blob/master/libmp3lame/VbrTag.c */ |
2401 | #ifdef ROCKBOX_HAS_LOGF | ||
2402 | ci->logf("mp3 enc: NULL data"); | ||
2403 | #endif | ||
2404 | return true; | ||
2405 | } | ||
2406 | 2312 | ||
2407 | /* if current chunk doesn't fit => write collected data */ | 2313 | /* Lookup table for fast CRC computation |
2408 | if (mp3_data_len + data->chunk->enc_size > sizeof(mp3_data)) | 2314 | * See 'crc_update_lookup' |
2409 | { | 2315 | * Uses the polynomial x^16+x^15+x^2+1 */ |
2410 | if (ci->write(data->rec_file, mp3_data, | 2316 | static const uint16_t crc16_lookup[256] ICONST_ATTR = |
2411 | mp3_data_len) != (ssize_t)mp3_data_len) | 2317 | { |
2412 | return false; | 2318 | 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, |
2319 | 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, | ||
2320 | 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, | ||
2321 | 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, | ||
2322 | 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, | ||
2323 | 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, | ||
2324 | 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, | ||
2325 | 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, | ||
2326 | 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, | ||
2327 | 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, | ||
2328 | 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, | ||
2329 | 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, | ||
2330 | 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, | ||
2331 | 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, | ||
2332 | 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, | ||
2333 | 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, | ||
2334 | 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, | ||
2335 | 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, | ||
2336 | 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, | ||
2337 | 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, | ||
2338 | 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, | ||
2339 | 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, | ||
2340 | 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, | ||
2341 | 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, | ||
2342 | 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, | ||
2343 | 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, | ||
2344 | 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, | ||
2345 | 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, | ||
2346 | 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, | ||
2347 | 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, | ||
2348 | 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, | ||
2349 | 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 | ||
2350 | }; | ||
2413 | 2351 | ||
2414 | mp3_data_len = 0; | 2352 | static ssize_t header_size; |
2415 | } | 2353 | static unsigned int mp3_crc16; |
2416 | 2354 | ||
2417 | memcpy(mp3_data+mp3_data_len, data->chunk->enc_data, | 2355 | /* fast CRC-16 computation - uses table crc16_lookup 8*/ |
2418 | data->chunk->enc_size); | 2356 | static inline unsigned int crc_update_lookup(unsigned int value, |
2357 | unsigned int crc) | ||
2358 | { | ||
2359 | unsigned int tmp = crc ^ value; | ||
2360 | crc = (crc >> 8) ^ crc16_lookup[tmp & 0xff]; | ||
2361 | return crc & 0xffff; | ||
2362 | } | ||
2419 | 2363 | ||
2420 | mp3_data_len += data->chunk->enc_size; | 2364 | /* Calculate position of 'Info' header */ |
2365 | static int get_info_offset(uint32_t header) | ||
2366 | { | ||
2367 | uint32_t type = (header & (0x3 << 19)) >> 19; | ||
2368 | uint32_t mode = (header & (0x3 << 6)) >> 6; | ||
2421 | 2369 | ||
2422 | data->num_pcm_samples += data->chunk->num_pcm; | 2370 | return type == 3 ? (mode == 3 ? 21 : 36) : (mode == 3 ? 13 : 21); |
2423 | return true; | 2371 | } |
2424 | } /* on_write_chunk */ | ||
2425 | 2372 | ||
2426 | static bool on_start_file(struct enc_file_event_data *data) | 2373 | /* Write very basic 'Info' header with delay, padding and a bit of |
2374 | * miscellaneous info. */ | ||
2375 | static bool write_info_header(bool first_encode) | ||
2427 | { | 2376 | { |
2428 | if ((data->chunk->flags & CHUNKF_ERROR) || *data->filename == '\0') | 2377 | ssize_t size = cfg.byte_per_frame; |
2429 | return false; | ||
2430 | 2378 | ||
2431 | data->rec_file = ci->open(data->filename, O_RDWR|O_CREAT|O_TRUNC, 0666); | 2379 | /* By default the MP3 frame header for the info frame is the same as |
2380 | unpadded audio frames */ | ||
2381 | uint32_t header = encodeHeader(0, cfg.mpg.bitr_id); | ||
2432 | 2382 | ||
2433 | if (data->rec_file < 0) | 2383 | int i = get_info_offset(header); |
2434 | return false; | ||
2435 | 2384 | ||
2436 | /* reset sample count */ | 2385 | if (i + 8 + 36 > size) |
2437 | data->num_pcm_samples = 0; | 2386 | { |
2387 | /* The default frame size too small so find the smallest one that | ||
2388 | may accomodate it by increasing the bit rate for this empty | ||
2389 | MP3 frame */ | ||
2390 | int j; | ||
2391 | for (j = cfg.mpg.bitr_id + 1; j < 15; j++) | ||
2392 | { | ||
2393 | size = calcFrameSize(j, NULL); | ||
2438 | 2394 | ||
2439 | /* reset buffer write position */ | 2395 | if (size >= i + 8 + 36) |
2440 | mp3_data_len = 0; | 2396 | break; |
2397 | } | ||
2441 | 2398 | ||
2442 | return true; | 2399 | if (j >= 15) |
2443 | } /* on_start_file */ | 2400 | { |
2401 | /* Shouldn't really happen but... */ | ||
2402 | header_size = -1; | ||
2403 | return true; | ||
2404 | } | ||
2444 | 2405 | ||
2445 | static bool on_end_file(struct enc_file_event_data *data) | 2406 | header = encodeHeader(0, j); |
2446 | { | 2407 | /* Info offset won't change */ |
2447 | if (data->rec_file < 0) | 2408 | } |
2448 | return false; /* file already closed, nothing more we can do */ | ||
2449 | 2409 | ||
2450 | /* write the remaining mp3_data */ | 2410 | uint8_t frame[size]; |
2451 | if (ci->write(data->rec_file, mp3_data, mp3_data_len) | 2411 | memset(frame, 0, size); |
2452 | != (ssize_t)mp3_data_len) | ||
2453 | return false; | ||
2454 | 2412 | ||
2455 | /* reset buffer write position */ | 2413 | frame[0] = header >> 24; |
2456 | mp3_data_len = 0; | 2414 | frame[1] = header >> 16; |
2415 | frame[2] = header >> 8; | ||
2416 | frame[3] = header >> 0; | ||
2457 | 2417 | ||
2458 | /* always _try_ to write the file header, even on error */ | 2418 | /* 'Info' header (CBR 'Xing') */ |
2459 | if (ci->close(data->rec_file) != 0) | 2419 | memcpy(&frame[i], "Info", 4); |
2460 | return false; | ||
2461 | 2420 | ||
2462 | data->rec_file = -1; | 2421 | /* flags = 0; Info contains no other sections and is 8 bytes */ |
2463 | 2422 | ||
2464 | return true; | 2423 | /* Just mark the LAMEness to indicate header presence; we're not |
2465 | } /* on_end_file */ | 2424 | actually _the_ LAME so 'rbshn' is the version we give */ |
2425 | memcpy(&frame[i + 8], "LAMErbshn", 9); | ||
2466 | 2426 | ||
2467 | static void on_rec_new_stream(struct enc_buffer_event_data *data) | 2427 | /* Fill-in some info about us |
2468 | { | 2428 | * reference: http://gabriel.mp3-tech.org/mp3infotag.html |
2469 | int num_frames = cfg.mpg.type == 1 ? | 2429 | */ |
2470 | ENC_PADDING_FRAMES1 : ENC_PADDING_FRAMES2; | ||
2471 | 2430 | ||
2472 | if (data->flags & CHUNKF_END_FILE) | 2431 | /* Revision + VBR method: |
2473 | { | 2432 | * [7:4] = Revision (0 ??) |
2474 | /* add silent frames to end - encoder will also be flushed for start | 2433 | * [3:0] = VBR method (CBR) |
2475 | of next file if any */ | 2434 | */ |
2476 | memset(res_buffer, 0, pcm_chunk_size); | 2435 | frame[i + 17] = (0 << 4) | (1 << 0); |
2477 | 2436 | ||
2478 | /* the initial chunk given for the end is at enc_wr_index */ | 2437 | /* If first frame since encoder reset is long gone (not unlikely in |
2479 | while (num_frames-- > 0) | 2438 | prerecording), then the delay is long passed and no trimming done |
2480 | { | 2439 | at the start */ |
2481 | data->chunk->enc_data = ENC_CHUNK_SKIP_HDR(data->chunk->enc_data, | 2440 | unsigned int delay = first_encode ? cfg.delay : 0; |
2482 | data->chunk); | 2441 | unsigned int padding = cfg.padding; |
2483 | 2442 | ||
2484 | encode_frame(res_buffer, data->chunk); | 2443 | /* Delay and padding: |
2485 | data->chunk->num_pcm = samp_per_frame; | 2444 | * [23:12] = delay |
2445 | * [11: 0] = padding | ||
2446 | */ | ||
2447 | frame[i + 29] = delay >> 4; | ||
2448 | frame[i + 30] = (delay << 4) | (padding >> 8); | ||
2449 | frame[i + 31] = padding; | ||
2450 | |||
2451 | /* Misc: | ||
2452 | * [7:6] = source frequency | ||
2453 | * [ 5] = unwise settings (of course not :) | ||
2454 | * [4:2] = stereo mode (mono or stereo) | ||
2455 | * [1:0] = noise shaping (who knows, 0) | ||
2456 | */ | ||
2457 | uint8_t misc; | ||
2486 | 2458 | ||
2487 | ci->enc_finish_chunk(); | 2459 | if (cfg.src_samplerate <= 32000) |
2488 | data->chunk = ci->enc_get_chunk(); | 2460 | misc = (0 << 6); |
2489 | } | 2461 | else if (cfg.src_samplerate <= 44100) |
2490 | } | 2462 | misc = (1 << 6); |
2491 | else if (data->flags & CHUNKF_PRERECORD) | 2463 | else if (cfg.src_samplerate <= 48000) |
2492 | { | 2464 | misc = (2 << 6); |
2493 | /* nothing to add and we cannot change prerecorded data */ | 2465 | else /* > 48000 */ |
2494 | } | 2466 | misc = (3 << 6); |
2495 | else if (data->flags & CHUNKF_START_FILE) | ||
2496 | { | ||
2497 | /* starting fresh ... be sure to flush encoder first */ | ||
2498 | struct enc_chunk_hdr *chunk = ENC_CHUNK_HDR(res_buffer); | ||
2499 | 2467 | ||
2500 | chunk->flags = 0; | 2468 | if (cfg.channels > 1) |
2501 | chunk->enc_data = ENC_CHUNK_SKIP_HDR(chunk->enc_data, chunk); | 2469 | misc |= (1 << 2); /* Stereo */ |
2502 | 2470 | ||
2503 | while (num_frames-- > 0) | 2471 | frame[i + 32] = misc; |
2504 | { | 2472 | |
2505 | memset(chunk->enc_data, 0, pcm_chunk_size); | 2473 | if (ci->enc_stream_write(frame, size) != size) |
2506 | encode_frame(chunk->enc_data, chunk); | 2474 | { |
2507 | } | 2475 | ci->enc_stream_lseek(0, SEEK_SET); |
2476 | header_size = -1; | ||
2477 | return false; | ||
2508 | } | 2478 | } |
2509 | } /* on_rec_new_stream */ | ||
2510 | 2479 | ||
2511 | static void enc_events_callback(enum enc_events event, void *data) | 2480 | header_size = size; |
2481 | return true; | ||
2482 | } | ||
2483 | |||
2484 | static inline int on_stream_data(struct enc_chunk_data *data) | ||
2512 | { | 2485 | { |
2513 | switch (event) | 2486 | ssize_t size = data->hdr.size; |
2487 | |||
2488 | if (header_size > 0) | ||
2514 | { | 2489 | { |
2515 | case ENC_WRITE_CHUNK: | 2490 | /* Header is layed-down; keep running CRC of audio data */ |
2516 | if (on_write_chunk((struct enc_file_event_data *)data)) | 2491 | uint8_t *p = data->data; |
2517 | return; | 2492 | uint8_t *p_end = p + size; |
2518 | 2493 | ||
2519 | break; | 2494 | while (p < p_end) |
2495 | mp3_crc16 = crc_update_lookup(*p++, mp3_crc16); | ||
2496 | } | ||
2520 | 2497 | ||
2521 | case ENC_START_FILE: | 2498 | if (ci->enc_stream_write(data->data, size) != size) |
2522 | if (on_start_file((struct enc_file_event_data *)data)) | 2499 | return -1; |
2523 | return; | ||
2524 | 2500 | ||
2525 | break; | 2501 | return 0; |
2502 | } | ||
2526 | 2503 | ||
2527 | case ENC_END_FILE: | 2504 | static int on_stream_start(struct enc_chunk_file *file) |
2528 | if (on_end_file((struct enc_file_event_data *)data)) | 2505 | { |
2529 | return; | 2506 | mp3_crc16 = 0x0000; |
2530 | 2507 | ||
2531 | break; | 2508 | if (!write_info_header(file->hdr.aux0)) |
2509 | return -1; | ||
2532 | 2510 | ||
2533 | case ENC_REC_NEW_STREAM: | 2511 | return 0; |
2534 | on_rec_new_stream((struct enc_buffer_event_data *)data); | 2512 | } |
2535 | return; | ||
2536 | 2513 | ||
2537 | default: | 2514 | static int on_stream_end(union enc_chunk_hdr *hdr) |
2538 | return; | 2515 | { |
2539 | } | 2516 | ssize_t size = header_size; |
2540 | 2517 | ||
2541 | /* Something failed above. Signal error back to core. */ | 2518 | if (size <= 0) |
2542 | ((struct enc_file_event_data *)data)->chunk->flags |= CHUNKF_ERROR; | 2519 | return 0; /* No header possible/none yet written */ |
2543 | } /* enc_events_callback */ | ||
2544 | 2520 | ||
2545 | static bool enc_init(void) | 2521 | /* Update audio CRC and header CRC */ |
2546 | { | 2522 | uint8_t frame[size]; |
2547 | struct enc_inputs inputs; | ||
2548 | struct enc_parameters params; | ||
2549 | |||
2550 | if (ci->enc_get_inputs == NULL || | ||
2551 | ci->enc_set_parameters == NULL || | ||
2552 | ci->enc_get_chunk == NULL || | ||
2553 | ci->enc_finish_chunk == NULL || | ||
2554 | ci->enc_get_pcm_data == NULL || | ||
2555 | ci->enc_unget_pcm_data == NULL ) | ||
2556 | return false; | ||
2557 | 2523 | ||
2558 | ci->enc_get_inputs(&inputs); | 2524 | /* Won't fail this since it could still be useable if some decoder |
2525 | plays loose with the CRC info (like Rockbox :) */ | ||
2526 | if (ci->enc_stream_lseek(0, SEEK_SET) != 0 || | ||
2527 | ci->enc_stream_read(frame, size) != size) | ||
2528 | return 0; | ||
2559 | 2529 | ||
2560 | if (inputs.config->afmt != AFMT_MPA_L3) | 2530 | uint32_t header = (frame[0] << 24) | (frame[1] << 16) | |
2561 | return false; | 2531 | (frame[2] << 8) | (frame[3] << 0); |
2532 | int i = get_info_offset(header); /* Get 'Info' header */ | ||
2562 | 2533 | ||
2563 | init_mp3_encoder_engine(inputs.sample_rate, inputs.num_channels, | 2534 | /* 'Info' header = 8 bytes */ |
2564 | inputs.rec_mono_mode, inputs.config); | ||
2565 | 2535 | ||
2566 | err = 0; | 2536 | /* Fill-in audio data CRC16 */ |
2567 | 2537 | ||
2568 | /* configure the buffer system */ | 2538 | /* On error, fixing data CRC would require scanning file since it |
2569 | params.afmt = AFMT_MPA_L3; | 2539 | has probably dropped something we tried to write and the likely |
2570 | params.chunk_size = cfg.byte_per_frame + 1; | 2540 | reason is that the disk filled; just leave it 0 in that case. */ |
2571 | params.enc_sample_rate = cfg.samplerate; | 2541 | if (!hdr->err) |
2572 | /* need enough reserved bytes to hold one frame of pcm samples + hdr | 2542 | { |
2573 | for padding and flushing */ | 2543 | frame[i + 40] = mp3_crc16 >> 8; |
2574 | params.reserve_bytes = ENC_CHUNK_HDR_SIZE + pcm_chunk_size; | 2544 | frame[i + 41] = mp3_crc16; |
2575 | params.events_callback = enc_events_callback; | 2545 | } |
2576 | ci->enc_set_parameters(¶ms); | ||
2577 | 2546 | ||
2578 | res_buffer = params.reserve_buffer; | 2547 | /* Fill-in header CRC16 */ |
2548 | unsigned int hdr_crc16 = 0x0000; | ||
2549 | for (int j = 0; j < i + 42; j++) | ||
2550 | hdr_crc16 = crc_update_lookup(frame[j], hdr_crc16); | ||
2579 | 2551 | ||
2580 | #ifdef CPU_COLDFIRE | 2552 | frame[i + 42] = hdr_crc16 >> 8; |
2581 | asm volatile ("move.l #0, %macsr"); /* integer mode */ | 2553 | frame[i + 43] = hdr_crc16; |
2582 | #endif | ||
2583 | 2554 | ||
2584 | return true; | 2555 | /* Update file */ |
2585 | } /* enc_init */ | 2556 | if (ci->enc_stream_lseek(0, SEEK_SET) == 0) |
2557 | ci->enc_stream_write(frame, size); | ||
2558 | |||
2559 | return 0; | ||
2560 | } | ||
2586 | 2561 | ||
2587 | /* this is the codec entry point */ | 2562 | /* this is the codec entry point */ |
2588 | enum codec_status codec_main(enum codec_entry_call_reason reason) | 2563 | enum codec_status codec_main(enum codec_entry_call_reason reason) |
2589 | { | 2564 | { |
2590 | if (reason == CODEC_LOAD) { | 2565 | #ifdef CPU_COLDFIRE |
2591 | if (!enc_init()) | 2566 | if (reason == CODEC_LOAD) |
2592 | return CODEC_ERROR; | 2567 | asm volatile ("move.l #0, %macsr"); /* integer mode */ |
2593 | } | 2568 | #endif |
2594 | else if (reason == CODEC_UNLOAD) { | ||
2595 | /* reset parameters to initial state */ | ||
2596 | ci->enc_set_parameters(NULL); | ||
2597 | } | ||
2598 | |||
2599 | return CODEC_OK; | 2569 | return CODEC_OK; |
2570 | (void)reason; | ||
2600 | } | 2571 | } |
2601 | 2572 | ||
2602 | /* this is called for each file to process */ | 2573 | /* this is called for each file to process */ |
2603 | enum codec_status codec_run(void) | 2574 | enum codec_status codec_run(void) |
2604 | { | 2575 | { |
2576 | mp3_encoder_reset(); | ||
2577 | uint32_t first = 1; | ||
2578 | |||
2579 | /* Needs to do stream finishing steps to flush-out all samples */ | ||
2580 | int frames_rem = -1; /* -1 = indeterminate */ | ||
2581 | |||
2582 | enum { GETBUF_ENC, GETBUF_PCM } getbuf = GETBUF_ENC; | ||
2583 | struct enc_chunk_data *data = NULL; | ||
2584 | |||
2605 | /* main encoding loop */ | 2585 | /* main encoding loop */ |
2606 | while(ci->get_command(NULL) != CODEC_ACTION_HALT) | 2586 | while (frames_rem) |
2607 | { | 2587 | { |
2608 | char *buffer = buffer = ci->enc_get_pcm_data(pcm_chunk_size); | 2588 | intptr_t param; |
2609 | struct enc_chunk_hdr *chunk; | 2589 | enum codec_command_action action = ci->get_command(¶m); |
2610 | 2590 | ||
2611 | if(buffer == NULL) | 2591 | if (action != CODEC_ACTION_NULL) |
2612 | continue; | 2592 | { |
2593 | if (action != CODEC_ACTION_STREAM_FINISH) | ||
2594 | break; | ||
2613 | 2595 | ||
2614 | chunk = ci->enc_get_chunk(); | 2596 | if (frames_rem < 0) |
2615 | chunk->enc_data = ENC_CHUNK_SKIP_HDR(chunk->enc_data, chunk); | 2597 | frames_rem = cfg.flush_frames; |
2616 | 2598 | ||
2617 | encode_frame(buffer, chunk); | 2599 | /* Reply with required space */ |
2600 | *(size_t *)param = cfg.req_byte_per_frame*frames_rem; | ||
2601 | } | ||
2618 | 2602 | ||
2619 | if (chunk->num_pcm < samp_per_frame) | 2603 | /* First obtain output buffer; when available, get PCM data */ |
2604 | switch (getbuf) | ||
2620 | { | 2605 | { |
2621 | ci->enc_unget_pcm_data(pcm_chunk_size - chunk->num_pcm*4); | 2606 | case GETBUF_ENC: |
2622 | chunk->num_pcm = samp_per_frame; | 2607 | if (!(data = ci->enc_encbuf_get_buffer(cfg.req_byte_per_frame))) |
2608 | continue; | ||
2609 | getbuf = GETBUF_PCM; | ||
2610 | case GETBUF_PCM: | ||
2611 | if (LIKELY(frames_rem < 0)) | ||
2612 | { | ||
2613 | /* Encoding audio */ | ||
2614 | int count = cfg.samp_per_frame; | ||
2615 | if (!ci->enc_pcmbuf_read(cfg.samp_buffer, count)) | ||
2616 | continue; | ||
2617 | |||
2618 | ci->enc_pcmbuf_advance(cfg.samp_per_frame); | ||
2619 | |||
2620 | if (cfg.channels == 1) | ||
2621 | { | ||
2622 | /* Interleave the mono samples to stereo as required by | ||
2623 | encoder */ | ||
2624 | uint16_t *src = cfg.samp_buffer + count; | ||
2625 | uint32_t *dst = (uint32_t *)(src + count); | ||
2626 | |||
2627 | for (int i = count; i > 0; i--) | ||
2628 | { uint32_t s = *--src; *--dst = s | (s << 16); } | ||
2629 | } | ||
2630 | } | ||
2631 | else | ||
2632 | { | ||
2633 | /* Flushing encoder */ | ||
2634 | memset(cfg.samp_buffer, 0, cfg.samp_per_frame*4); | ||
2635 | frames_rem--; | ||
2636 | } | ||
2637 | getbuf = GETBUF_ENC; | ||
2623 | } | 2638 | } |
2624 | 2639 | ||
2625 | ci->enc_finish_chunk(); | 2640 | data->hdr.aux0 = first; |
2641 | first = 0; | ||
2642 | data->hdr.size = mp3_encoder_encode_frame(data->data); | ||
2643 | data->pcm_count = cfg.samp_per_frame; | ||
2644 | ci->enc_encbuf_finish_buffer(); | ||
2626 | } | 2645 | } |
2627 | 2646 | ||
2628 | return CODEC_OK; | 2647 | return CODEC_OK; |
2629 | } | 2648 | } |
2649 | |||
2650 | /* this is called by recording system */ | ||
2651 | int ICODE_ATTR enc_callback(enum enc_callback_reason reason, | ||
2652 | void *params) | ||
2653 | { | ||
2654 | if (LIKELY(reason == ENC_CB_STREAM)) | ||
2655 | { | ||
2656 | switch (((union enc_chunk_hdr *)params)->type) | ||
2657 | { | ||
2658 | case CHUNK_T_DATA: | ||
2659 | return on_stream_data(params); | ||
2660 | case CHUNK_T_STREAM_START: | ||
2661 | return on_stream_start(params); | ||
2662 | case CHUNK_T_STREAM_END: | ||
2663 | return on_stream_end(params); | ||
2664 | } | ||
2665 | } | ||
2666 | else if (reason == ENC_CB_INPUTS) | ||
2667 | { | ||
2668 | struct enc_inputs *inputs = params; | ||
2669 | |||
2670 | mp3_encoder_init(inputs->sample_rate, inputs->num_channels, | ||
2671 | inputs->config->mp3_enc.bitrate); | ||
2672 | |||
2673 | /* Return the actual configuration */ | ||
2674 | inputs->enc_sample_rate = cfg.samplerate; | ||
2675 | } | ||
2676 | |||
2677 | return 0; | ||
2678 | } | ||
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 | ||
60 | static int num_channels IBSS_ATTR; | 60 | static int num_channels; |
61 | static int rec_mono_mode IBSS_ATTR; | ||
62 | static uint32_t sample_rate; | 61 | static uint32_t sample_rate; |
63 | static uint32_t enc_size; | 62 | static size_t frame_size; |
64 | static int32_t err IBSS_ATTR; | 63 | static size_t data_size; |
65 | 64 | ||
66 | static const struct riff_header riff_header = | 65 | static 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 */ | 87 | static inline void frame_htole(uint32_t *p, size_t size) |
89 | static inline bool is_file_data_ok(struct enc_file_event_data *data) ICODE_ATTR; | ||
90 | static 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 */ | ||
96 | static inline bool on_write_chunk(struct enc_file_event_data *data) ICODE_ATTR; | ||
97 | static 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, | 108 | static 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 | |||
118 | static 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 | ||
120 | static 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 | ||
142 | static bool on_end_file(struct enc_file_event_data *data) | 133 | static 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 | ||
185 | static void enc_events_callback(enum enc_events event, void *data) | 173 | /* this is the codec entry point */ |
186 | ICODE_ATTR; | 174 | enum codec_status codec_main(enum codec_entry_call_reason reason) |
187 | static 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 */ |
218 | static inline void sample_to_mono(uint32_t **src, uint32_t **dst) | 181 | enum 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 | ||
255 | static void chunk_to_wav_format(uint32_t *src, uint32_t *dst) ICODE_ATTR; | 194 | /* First obtain output buffer; when available, get PCM data */ |
256 | static 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 | ||
314 | static 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(¶ms); | ||
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); |
349 | enum 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 */ |
364 | enum codec_status codec_run(void) | 220 | int 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 | } |
diff --git a/lib/rbcodec/codecs/wavpack_enc.c b/lib/rbcodec/codecs/wavpack_enc.c index 1fae2d46a7..864012b4cd 100644 --- a/lib/rbcodec/codecs/wavpack_enc.c +++ b/lib/rbcodec/codecs/wavpack_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 |
@@ -47,29 +48,39 @@ struct riff_header | |||
47 | uint16_t block_align; /* 20h - num_channels*bits_per_samples/8 */ | 48 | uint16_t block_align; /* 20h - num_channels*bits_per_samples/8 */ |
48 | uint16_t bits_per_sample; /* 22h - 8=8 bits, 16=16 bits, etc. */ | 49 | uint16_t bits_per_sample; /* 22h - 8=8 bits, 16=16 bits, etc. */ |
49 | /* Not for audio_format=1 (PCM) */ | 50 | /* Not for audio_format=1 (PCM) */ |
50 | /* unsigned short extra_param_size; 24h - size of extra data */ | 51 | /* uint16_t extra_param_size; 24h - size of extra data */ |
51 | /* unsigned char *extra_params; */ | 52 | /* uint8_t extra_params[extra_param_size]; */ |
52 | /* data header */ | 53 | /* data header */ |
53 | uint8_t data_id[4]; /* 24h - "data" */ | 54 | uint8_t data_id[4]; /* 24h - "data" */ |
54 | uint32_t data_size; /* 28h - num_samples*num_channels*bits_per_sample/8 */ | 55 | uint32_t data_size; /* 28h - num_samples*num_channels*bits_per_sample/8 */ |
55 | /* unsigned char *data; 2ch - actual sound data */ | 56 | /* uint8_t data[data_size]; 2Ch - actual sound data */ |
56 | } __attribute__((packed)); | 57 | } __attribute__((packed)); |
57 | 58 | ||
58 | #define RIFF_FMT_HEADER_SIZE 12 /* format -> format_size */ | 59 | #define RIFF_FMT_HEADER_SIZE 12 /* format -> format_size */ |
59 | #define RIFF_FMT_DATA_SIZE 16 /* audio_format -> bits_per_sample */ | 60 | #define RIFF_FMT_DATA_SIZE 16 /* audio_format -> bits_per_sample */ |
60 | #define RIFF_DATA_HEADER_SIZE 8 /* data_id -> data_size */ | 61 | #define RIFF_DATA_HEADER_SIZE 8 /* data_id -> data_size */ |
61 | 62 | ||
63 | struct wvpk_chunk_data | ||
64 | { | ||
65 | struct enc_chunk_data ckhdr; /* The base data chunk header */ | ||
66 | WavpackHeader wphdr; /* The block wavpack info */ | ||
67 | uint8_t data[]; /* Encoded audio data */ | ||
68 | }; | ||
69 | |||
62 | #define PCM_DEPTH_BITS 16 | 70 | #define PCM_DEPTH_BITS 16 |
63 | #define PCM_DEPTH_BYTES 2 | 71 | #define PCM_DEPTH_BYTES 2 |
64 | #define PCM_SAMP_PER_CHUNK 5000 | 72 | #define PCM_SAMP_PER_CHUNK 5000 |
65 | #define PCM_CHUNK_SIZE (4*PCM_SAMP_PER_CHUNK) | ||
66 | 73 | ||
67 | /** Data **/ | 74 | /** Data **/ |
68 | static int8_t input_buffer[PCM_CHUNK_SIZE*2] IBSS_ATTR; | 75 | static int32_t input_buffer[PCM_SAMP_PER_CHUNK*2] IBSS_ATTR; |
69 | static WavpackConfig config IBSS_ATTR; | 76 | |
77 | static WavpackConfig config IBSS_ATTR; | ||
70 | static WavpackContext *wpc; | 78 | static WavpackContext *wpc; |
71 | static int32_t data_size, input_size, input_step IBSS_ATTR; | 79 | static uint32_t sample_rate; |
72 | static int32_t err IBSS_ATTR; | 80 | static int num_channels; |
81 | static uint32_t total_samples; | ||
82 | static size_t out_reqsize; | ||
83 | static size_t frame_size; | ||
73 | 84 | ||
74 | static const WavpackMetadataHeader wvpk_mdh = | 85 | static const WavpackMetadataHeader wvpk_mdh = |
75 | { | 86 | { |
@@ -77,7 +88,7 @@ static const WavpackMetadataHeader wvpk_mdh = | |||
77 | sizeof (struct riff_header) / sizeof (uint16_t), | 88 | sizeof (struct riff_header) / sizeof (uint16_t), |
78 | }; | 89 | }; |
79 | 90 | ||
80 | static const struct riff_header riff_header = | 91 | static const struct riff_header riff_template_header = |
81 | { | 92 | { |
82 | /* "RIFF" header */ | 93 | /* "RIFF" header */ |
83 | { 'R', 'I', 'F', 'F' }, /* riff_id */ | 94 | { 'R', 'I', 'F', 'F' }, /* riff_id */ |
@@ -96,157 +107,75 @@ static const struct riff_header riff_header = | |||
96 | /* data header */ | 107 | /* data header */ |
97 | { 'd', 'a', 't', 'a' }, /* data_id */ | 108 | { 'd', 'a', 't', 'a' }, /* data_id */ |
98 | 0 /* data_size (*) */ | 109 | 0 /* data_size (*) */ |
99 | /* (*) updated during ENC_END_FILE event */ | 110 | /* (*) updated when finalizing stream */ |
100 | }; | 111 | }; |
101 | 112 | ||
102 | static inline void sample_to_int32_mono(int32_t **src, int32_t **dst) | 113 | static inline void sample_to_int32(int32_t **dst, int32_t **src) |
103 | { | ||
104 | int32_t t = *(*src)++; | ||
105 | /* endianness irrelevant */ | ||
106 | t = (int16_t)t + (t >> 16) + err; | ||
107 | err = t & 1; | ||
108 | *(*dst)++ = t >> 1; | ||
109 | } /* sample_to_int32_mono */ | ||
110 | |||
111 | static inline void sample_to_int32_stereo(int32_t **src, int32_t **dst) | ||
112 | { | 114 | { |
113 | int32_t t = *(*src)++; | 115 | uint32_t t = *(*src)++; |
114 | #ifdef ROCKBOX_BIG_ENDIAN | 116 | #ifdef ROCKBOX_BIG_ENDIAN |
115 | *(*dst)++ = t >> 16, *(*dst)++ = (int16_t)t; | 117 | *(*dst)++ = (int32_t)t >> 16; |
118 | *(*dst)++ = (int16_t)t; | ||
116 | #else | 119 | #else |
117 | *(*dst)++ = (int16_t)t, *(*dst)++ = t >> 16; | 120 | *(*dst)++ = (int16_t)t; |
121 | *(*dst)++ = (int32_t)t >> 16; | ||
118 | #endif | 122 | #endif |
119 | } /* sample_to_int32_stereo */ | 123 | } |
120 | 124 | ||
121 | static void chunk_to_int32(int32_t *src) ICODE_ATTR; | 125 | static void ICODE_ATTR input_buffer_to_int32(size_t size) |
122 | static void chunk_to_int32(int32_t *src) | ||
123 | { | 126 | { |
124 | int32_t *src_end, *dst; | 127 | int32_t *dst = input_buffer; |
125 | #ifdef USE_IRAM | 128 | int32_t *src = input_buffer + PCM_SAMP_PER_CHUNK; |
126 | /* copy to IRAM before converting data */ | ||
127 | dst = (int32_t *)input_buffer + PCM_SAMP_PER_CHUNK; | ||
128 | src_end = dst + PCM_SAMP_PER_CHUNK; | ||
129 | |||
130 | memcpy(dst, src, PCM_CHUNK_SIZE); | ||
131 | |||
132 | src = dst; | ||
133 | #else | ||
134 | src_end = src + PCM_SAMP_PER_CHUNK; | ||
135 | #endif | ||
136 | |||
137 | dst = (int32_t *)input_buffer; | ||
138 | 129 | ||
139 | if (config.num_channels == 1) | 130 | do |
140 | { | 131 | { |
141 | /* | 132 | sample_to_int32(&dst, &src); |
142 | * |llllllllllllllll|rrrrrrrrrrrrrrrr| => | 133 | sample_to_int32(&dst, &src); |
143 | * |mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm| | 134 | sample_to_int32(&dst, &src); |
144 | */ | 135 | sample_to_int32(&dst, &src); |
145 | do | 136 | sample_to_int32(&dst, &src); |
146 | { | 137 | sample_to_int32(&dst, &src); |
147 | /* read 10 longs and write 10 longs */ | 138 | sample_to_int32(&dst, &src); |
148 | sample_to_int32_mono(&src, &dst); | 139 | sample_to_int32(&dst, &src); |
149 | sample_to_int32_mono(&src, &dst); | 140 | sample_to_int32(&dst, &src); |
150 | sample_to_int32_mono(&src, &dst); | 141 | sample_to_int32(&dst, &src); |
151 | sample_to_int32_mono(&src, &dst); | ||
152 | sample_to_int32_mono(&src, &dst); | ||
153 | sample_to_int32_mono(&src, &dst); | ||
154 | sample_to_int32_mono(&src, &dst); | ||
155 | sample_to_int32_mono(&src, &dst); | ||
156 | sample_to_int32_mono(&src, &dst); | ||
157 | sample_to_int32_mono(&src, &dst); | ||
158 | } | ||
159 | while(src < src_end); | ||
160 | |||
161 | return; | ||
162 | } | 142 | } |
163 | else | 143 | while (size -= 10 * 2 * PCM_DEPTH_BYTES); |
164 | { | 144 | } |
165 | /* | ||
166 | * |llllllllllllllll|rrrrrrrrrrrrrrrr| => | ||
167 | * |llllllllllllllllllllllllllllllll|rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr| | ||
168 | */ | ||
169 | do | ||
170 | { | ||
171 | /* read 10 longs and write 20 longs */ | ||
172 | sample_to_int32_stereo(&src, &dst); | ||
173 | sample_to_int32_stereo(&src, &dst); | ||
174 | sample_to_int32_stereo(&src, &dst); | ||
175 | sample_to_int32_stereo(&src, &dst); | ||
176 | sample_to_int32_stereo(&src, &dst); | ||
177 | sample_to_int32_stereo(&src, &dst); | ||
178 | sample_to_int32_stereo(&src, &dst); | ||
179 | sample_to_int32_stereo(&src, &dst); | ||
180 | sample_to_int32_stereo(&src, &dst); | ||
181 | sample_to_int32_stereo(&src, &dst); | ||
182 | } | ||
183 | while (src < src_end); | ||
184 | |||
185 | return; | ||
186 | } | ||
187 | } /* chunk_to_int32 */ | ||
188 | |||
189 | /* called very often - inline */ | ||
190 | static inline bool is_file_data_ok(struct enc_file_event_data *data) ICODE_ATTR; | ||
191 | static inline bool is_file_data_ok(struct enc_file_event_data *data) | ||
192 | { | ||
193 | return data->rec_file >= 0 && (long)data->chunk->flags >= 0; | ||
194 | } /* is_file_data_ok */ | ||
195 | 145 | ||
196 | /* called very often - inline */ | 146 | static int on_stream_data(struct wvpk_chunk_data *wpdata) |
197 | static inline bool on_write_chunk(struct enc_file_event_data *data) ICODE_ATTR; | ||
198 | static inline bool on_write_chunk(struct enc_file_event_data *data) | ||
199 | { | 147 | { |
200 | if (!is_file_data_ok(data)) | ||
201 | return false; | ||
202 | |||
203 | if (data->chunk->enc_data == NULL) | ||
204 | { | ||
205 | #ifdef ROCKBOX_HAS_LOGF | ||
206 | ci->logf("wvpk enc: NULL data"); | ||
207 | #endif | ||
208 | return true; | ||
209 | } | ||
210 | |||
211 | /* update timestamp (block_index) */ | 148 | /* update timestamp (block_index) */ |
212 | ((WavpackHeader *)data->chunk->enc_data)->block_index = | 149 | wpdata->wphdr.block_index = htole32(total_samples); |
213 | htole32(data->num_pcm_samples); | ||
214 | 150 | ||
215 | if (ci->write(data->rec_file, data->chunk->enc_data, | 151 | size_t size = wpdata->ckhdr.hdr.size; |
216 | data->chunk->enc_size) != (ssize_t)data->chunk->enc_size) | 152 | if (ci->enc_stream_write(wpdata->ckhdr.data, size) != (ssize_t)size) |
217 | return false; | 153 | return -1; |
218 | 154 | ||
219 | data->num_pcm_samples += data->chunk->num_pcm; | 155 | total_samples += wpdata->ckhdr.pcm_count; |
220 | return true; | ||
221 | } /* on_write_chunk */ | ||
222 | 156 | ||
223 | static bool on_start_file(struct enc_file_event_data *data) | 157 | return 0; |
224 | { | 158 | } |
225 | if ((data->chunk->flags & CHUNKF_ERROR) || *data->filename == '\0') | ||
226 | return false; | ||
227 | |||
228 | data->rec_file = ci->open(data->filename, O_RDWR|O_CREAT|O_TRUNC, 0666); | ||
229 | |||
230 | if (data->rec_file < 0) | ||
231 | return false; | ||
232 | 159 | ||
160 | static int on_stream_start(void) | ||
161 | { | ||
233 | /* reset sample count */ | 162 | /* reset sample count */ |
234 | data->num_pcm_samples = 0; | 163 | total_samples = 0; |
235 | 164 | ||
236 | /* write template headers */ | 165 | /* write template headers */ |
237 | if (ci->write(data->rec_file, &wvpk_mdh, sizeof (wvpk_mdh)) | 166 | if (ci->enc_stream_write(&wvpk_mdh, sizeof (wvpk_mdh)) |
238 | != sizeof (wvpk_mdh) || | 167 | != sizeof (wvpk_mdh)) |
239 | ci->write(data->rec_file, &riff_header, sizeof (riff_header)) | 168 | return -1; |
240 | != sizeof (riff_header)) | 169 | |
241 | { | 170 | if (ci->enc_stream_write(&riff_template_header, |
242 | return false; | 171 | sizeof (riff_template_header)) |
243 | } | 172 | != sizeof (riff_template_header)) |
173 | return -2; | ||
244 | 174 | ||
245 | data->new_enc_size += sizeof(wvpk_mdh) + sizeof(riff_header); | 175 | return 0; |
246 | return true; | 176 | } |
247 | } /* on_start_file */ | ||
248 | 177 | ||
249 | static bool on_end_file(struct enc_file_event_data *data) | 178 | static int on_stream_end(void) |
250 | { | 179 | { |
251 | struct | 180 | struct |
252 | { | 181 | { |
@@ -255,19 +184,16 @@ static bool on_end_file(struct enc_file_event_data *data) | |||
255 | WavpackHeader wph; | 184 | WavpackHeader wph; |
256 | } __attribute__ ((packed)) h; | 185 | } __attribute__ ((packed)) h; |
257 | 186 | ||
258 | uint32_t data_size; | 187 | /* Correcting sizes on error is a bit of a pain */ |
259 | |||
260 | if (data->rec_file < 0) | ||
261 | return false; /* file already closed, nothing more we can do */ | ||
262 | |||
263 | /* always _try_ to write the file header, even on error */ | ||
264 | 188 | ||
265 | /* read template headers at start */ | 189 | /* read template headers at start */ |
266 | if (ci->lseek(data->rec_file, 0, SEEK_SET) != 0 || | 190 | if (ci->enc_stream_lseek(0, SEEK_SET) != 0) |
267 | ci->read(data->rec_file, &h, sizeof (h)) != sizeof (h)) | 191 | return -1; |
268 | return false; | ||
269 | 192 | ||
270 | data_size = data->num_pcm_samples*config.num_channels*PCM_DEPTH_BYTES; | 193 | if (ci->enc_stream_read(&h, sizeof (h)) != sizeof (h)) |
194 | return -2; | ||
195 | |||
196 | size_t data_size = total_samples*config.num_channels*PCM_DEPTH_BYTES; | ||
271 | 197 | ||
272 | /** "RIFF" header **/ | 198 | /** "RIFF" header **/ |
273 | h.rhdr.riff_size = htole32(RIFF_FMT_HEADER_SIZE + | 199 | h.rhdr.riff_size = htole32(RIFF_FMT_HEADER_SIZE + |
@@ -286,121 +212,29 @@ static bool on_end_file(struct enc_file_event_data *data) | |||
286 | /** Wavpack header **/ | 212 | /** Wavpack header **/ |
287 | h.wph.ckSize = htole32(letoh32(h.wph.ckSize) + sizeof (h.wpmdh) | 213 | h.wph.ckSize = htole32(letoh32(h.wph.ckSize) + sizeof (h.wpmdh) |
288 | + sizeof (h.rhdr)); | 214 | + sizeof (h.rhdr)); |
289 | h.wph.total_samples = htole32(data->num_pcm_samples); | 215 | h.wph.total_samples = htole32(total_samples); |
290 | 216 | ||
291 | /* MDH|RIFF|WVPK => WVPK|MDH|RIFF */ | 217 | /* MDH|RIFF|WVPK => WVPK|MDH|RIFF */ |
292 | if (ci->lseek(data->rec_file, 0, SEEK_SET) | 218 | if (ci->enc_stream_lseek(0, SEEK_SET) != 0) |
293 | != 0 || | 219 | return -3; |
294 | ci->write(data->rec_file, &h.wph, sizeof (h.wph)) | ||
295 | != sizeof (h.wph) || | ||
296 | ci->write(data->rec_file, &h.wpmdh, sizeof (h.wpmdh)) | ||
297 | != sizeof (h.wpmdh) || | ||
298 | ci->write(data->rec_file, &h.rhdr, sizeof (h.rhdr)) | ||
299 | != sizeof (h.rhdr) || | ||
300 | ci->close(data->rec_file) != 0 ) | ||
301 | { | ||
302 | return false; | ||
303 | } | ||
304 | |||
305 | data->rec_file = -1; | ||
306 | |||
307 | return true; | ||
308 | } /* on_end_file */ | ||
309 | 220 | ||
310 | static void enc_events_callback(enum enc_events event, void *data) | 221 | if (ci->enc_stream_write(&h.wph, sizeof (h.wph)) != sizeof (h.wph)) |
311 | ICODE_ATTR; | 222 | return -4; |
312 | static void enc_events_callback(enum enc_events event, void *data) | ||
313 | { | ||
314 | switch (event) | ||
315 | { | ||
316 | case ENC_WRITE_CHUNK: | ||
317 | if (on_write_chunk((struct enc_file_event_data *)data)) | ||
318 | return; | ||
319 | |||
320 | break; | ||
321 | |||
322 | case ENC_START_FILE: | ||
323 | /* write metadata header and RIFF header */ | ||
324 | if (on_start_file((struct enc_file_event_data *)data)) | ||
325 | return; | ||
326 | |||
327 | break; | ||
328 | |||
329 | case ENC_END_FILE: | ||
330 | if (on_end_file((struct enc_file_event_data *)data)) | ||
331 | return; | ||
332 | |||
333 | break; | ||
334 | |||
335 | default: | ||
336 | return; | ||
337 | } | ||
338 | |||
339 | /* Something failed above. Signal error back to core. */ | ||
340 | ((struct enc_file_event_data *)data)->chunk->flags |= CHUNKF_ERROR; | ||
341 | } /* enc_events_callback */ | ||
342 | |||
343 | static bool init_encoder(void) | ||
344 | { | ||
345 | struct enc_inputs inputs; | ||
346 | struct enc_parameters params; | ||
347 | |||
348 | codec_init(); | ||
349 | |||
350 | if (ci->enc_get_inputs == NULL || | ||
351 | ci->enc_set_parameters == NULL || | ||
352 | ci->enc_get_chunk == NULL || | ||
353 | ci->enc_finish_chunk == NULL || | ||
354 | ci->enc_get_pcm_data == NULL || | ||
355 | ci->enc_unget_pcm_data == NULL ) | ||
356 | return false; | ||
357 | |||
358 | ci->enc_get_inputs(&inputs); | ||
359 | |||
360 | if (inputs.config->afmt != AFMT_WAVPACK) | ||
361 | return false; | ||
362 | |||
363 | memset(&config, 0, sizeof (config)); | ||
364 | config.bits_per_sample = PCM_DEPTH_BITS; | ||
365 | config.bytes_per_sample = PCM_DEPTH_BYTES; | ||
366 | config.sample_rate = inputs.sample_rate; | ||
367 | config.num_channels = inputs.num_channels; | ||
368 | 223 | ||
369 | wpc = WavpackOpenFileOutput (); | 224 | if (ci->enc_stream_write(&h.wpmdh, sizeof (h.wpmdh)) != sizeof (h.wpmdh)) |
225 | return -5; | ||
370 | 226 | ||
371 | if (!WavpackSetConfiguration(wpc, &config, -1)) | 227 | if (ci->enc_stream_write(&h.rhdr, sizeof (h.rhdr)) != sizeof (h.rhdr)) |
372 | return false; | 228 | return -6; |
373 | 229 | ||
374 | err = 0; | 230 | return 0; |
375 | 231 | } | |
376 | /* configure the buffer system */ | ||
377 | params.afmt = AFMT_WAVPACK; | ||
378 | input_size = PCM_CHUNK_SIZE*inputs.num_channels / 2; | ||
379 | data_size = 105*input_size / 100; | ||
380 | input_size *= 2; | ||
381 | input_step = input_size / 4; | ||
382 | params.chunk_size = data_size; | ||
383 | params.enc_sample_rate = inputs.sample_rate; | ||
384 | params.reserve_bytes = 0; | ||
385 | params.events_callback = enc_events_callback; | ||
386 | |||
387 | ci->enc_set_parameters(¶ms); | ||
388 | |||
389 | return true; | ||
390 | } /* init_encoder */ | ||
391 | 232 | ||
392 | /* this is the codec entry point */ | 233 | /* this is the codec entry point */ |
393 | enum codec_status codec_main(enum codec_entry_call_reason reason) | 234 | enum codec_status codec_main(enum codec_entry_call_reason reason) |
394 | { | 235 | { |
395 | if (reason == CODEC_LOAD) { | 236 | if (reason == CODEC_LOAD) |
396 | /* initialize params and config */ | 237 | codec_init(); |
397 | if (!init_encoder()) | ||
398 | return CODEC_ERROR; | ||
399 | } | ||
400 | else if (reason == CODEC_UNLOAD) { | ||
401 | /* reset parameters to initial state */ | ||
402 | ci->enc_set_parameters(NULL); | ||
403 | } | ||
404 | 238 | ||
405 | return CODEC_OK; | 239 | return CODEC_OK; |
406 | } | 240 | } |
@@ -408,60 +242,89 @@ enum codec_status codec_main(enum codec_entry_call_reason reason) | |||
408 | /* this is called for each file to process */ | 242 | /* this is called for each file to process */ |
409 | enum codec_status codec_run(void) | 243 | enum codec_status codec_run(void) |
410 | { | 244 | { |
245 | enum { GETBUF_ENC, GETBUF_PCM } getbuf = GETBUF_ENC; | ||
246 | struct enc_chunk_data *data = NULL; | ||
247 | |||
411 | /* main encoding loop */ | 248 | /* main encoding loop */ |
412 | while(ci->get_command(NULL) != CODEC_ACTION_HALT) | 249 | while (1) |
413 | { | 250 | { |
414 | uint8_t *src = (uint8_t *)ci->enc_get_pcm_data(PCM_CHUNK_SIZE); | 251 | enum codec_command_action action = ci->get_command(NULL); |
415 | struct enc_chunk_hdr *chunk; | ||
416 | bool abort_chunk; | ||
417 | uint8_t *dst; | ||
418 | uint8_t *src_end; | ||
419 | |||
420 | if(src == NULL) | ||
421 | continue; | ||
422 | |||
423 | chunk = ci->enc_get_chunk(); | ||
424 | |||
425 | /* reset counts and pointer */ | ||
426 | chunk->enc_size = 0; | ||
427 | chunk->num_pcm = 0; | ||
428 | chunk->enc_data = NULL; | ||
429 | |||
430 | dst = ENC_CHUNK_SKIP_HDR(dst, chunk); | ||
431 | 252 | ||
432 | WavpackStartBlock(wpc, dst, dst + data_size); | 253 | if (action != CODEC_ACTION_NULL) |
254 | break; | ||
433 | 255 | ||
434 | chunk_to_int32((uint32_t*)src); | 256 | /* First obtain output buffer; when available, get PCM data */ |
435 | src = input_buffer; | 257 | switch (getbuf) |
436 | src_end = src + input_size; | ||
437 | |||
438 | /* encode chunk in four steps yielding between each */ | ||
439 | do | ||
440 | { | 258 | { |
441 | abort_chunk = true; | 259 | case GETBUF_ENC: |
442 | if (WavpackPackSamples(wpc, (int32_t *)src, | 260 | if (!(data = ci->enc_encbuf_get_buffer(out_reqsize))) |
443 | PCM_SAMP_PER_CHUNK/4)) | 261 | continue; |
444 | { | 262 | getbuf = GETBUF_PCM; |
445 | chunk->num_pcm += PCM_SAMP_PER_CHUNK/4; | 263 | case GETBUF_PCM: |
446 | ci->yield(); | 264 | if (!ci->enc_pcmbuf_read(input_buffer + PCM_SAMP_PER_CHUNK, |
447 | /* could've been stopped in some way */ | 265 | PCM_SAMP_PER_CHUNK)) |
448 | abort_chunk = chunk->flags & CHUNKF_ABORT; | 266 | continue; |
449 | } | 267 | getbuf = GETBUF_ENC; |
450 | |||
451 | src += input_step; | ||
452 | } | 268 | } |
453 | while (!abort_chunk && src < src_end); | ||
454 | 269 | ||
455 | if (!abort_chunk) | 270 | input_buffer_to_int32(frame_size); |
271 | |||
272 | if (WavpackStartBlock(wpc, data->data, data->data + out_reqsize) && | ||
273 | WavpackPackSamples(wpc, input_buffer, PCM_SAMP_PER_CHUNK)) | ||
456 | { | 274 | { |
457 | chunk->enc_data = dst; | ||
458 | if (chunk->num_pcm < PCM_SAMP_PER_CHUNK) | ||
459 | ci->enc_unget_pcm_data(PCM_CHUNK_SIZE - chunk->num_pcm*4); | ||
460 | /* finish the chunk and store chunk size info */ | 275 | /* finish the chunk and store chunk size info */ |
461 | chunk->enc_size = WavpackFinishBlock(wpc); | 276 | data->hdr.size = WavpackFinishBlock(wpc); |
462 | ci->enc_finish_chunk(); | 277 | data->pcm_count = PCM_SAMP_PER_CHUNK; |
463 | } | 278 | } |
279 | else | ||
280 | { | ||
281 | data->hdr.err = 1; | ||
282 | } | ||
283 | |||
284 | ci->enc_pcmbuf_advance(PCM_SAMP_PER_CHUNK); | ||
285 | ci->enc_encbuf_finish_buffer(); | ||
464 | } | 286 | } |
465 | 287 | ||
466 | return CODEC_OK; | 288 | return CODEC_OK; |
467 | } | 289 | } |
290 | |||
291 | /* this is called by recording system */ | ||
292 | int ICODE_ATTR enc_callback(enum enc_callback_reason reason, | ||
293 | void *params) | ||
294 | { | ||
295 | if (LIKELY(reason == ENC_CB_STREAM)) | ||
296 | { | ||
297 | switch (((union enc_chunk_hdr *)params)->type) | ||
298 | { | ||
299 | case CHUNK_T_DATA: | ||
300 | return on_stream_data(params); | ||
301 | case CHUNK_T_STREAM_START: | ||
302 | return on_stream_start(); | ||
303 | case CHUNK_T_STREAM_END: | ||
304 | return on_stream_end(); | ||
305 | } | ||
306 | } | ||
307 | else if (reason == ENC_CB_INPUTS) | ||
308 | { | ||
309 | /* Save parameters */ | ||
310 | struct enc_inputs *inputs = params; | ||
311 | sample_rate = inputs->sample_rate; | ||
312 | num_channels = inputs->num_channels; | ||
313 | frame_size = PCM_SAMP_PER_CHUNK*PCM_DEPTH_BYTES*num_channels; | ||
314 | out_reqsize = frame_size*110 / 100; /* Add 10% */ | ||
315 | |||
316 | /* Setup Wavpack encoder */ | ||
317 | memset(&config, 0, sizeof (config)); | ||
318 | config.bits_per_sample = PCM_DEPTH_BITS; | ||
319 | config.bytes_per_sample = PCM_DEPTH_BYTES; | ||
320 | config.sample_rate = sample_rate; | ||
321 | config.num_channels = num_channels; | ||
322 | |||
323 | wpc = WavpackOpenFileOutput(); | ||
324 | |||
325 | if (!WavpackSetConfiguration(wpc, &config, -1)) | ||
326 | return -1; | ||
327 | } | ||
328 | |||
329 | return 0; | ||
330 | } | ||