diff options
author | Björn Stenberg <bjorn@haxx.se> | 2008-10-14 11:12:20 +0000 |
---|---|---|
committer | Björn Stenberg <bjorn@haxx.se> | 2008-10-14 11:12:20 +0000 |
commit | 9558c4956d3d603c4d132af88633767810f3ba62 (patch) | |
tree | b0d739f6246a7175de106562a2c755724f977cbe /apps | |
parent | 91b9c6139b3447d7c0f4f2924ef73a9dc323703b (diff) | |
download | rockbox-9558c4956d3d603c4d132af88633767810f3ba62.tar.gz rockbox-9558c4956d3d603c4d132af88633767810f3ba62.zip |
Moved pcm_record from firmware to apps. Cleaned up some. Now all code using struct mp3entry is in apps.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@18807 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps')
-rw-r--r-- | apps/SOURCES | 3 | ||||
-rw-r--r-- | apps/id3.h | 246 | ||||
-rw-r--r-- | apps/mpeg.c | 25 | ||||
-rw-r--r-- | apps/mpeg.h | 66 | ||||
-rw-r--r-- | apps/recorder/pcm_record.c | 1788 | ||||
-rw-r--r-- | apps/recorder/pcm_record.h | 66 | ||||
-rw-r--r-- | apps/recorder/recording.c | 1 | ||||
-rw-r--r-- | apps/replaygain.h | 33 |
8 files changed, 2212 insertions, 16 deletions
diff --git a/apps/SOURCES b/apps/SOURCES index 7622b416f7..3fce356092 100644 --- a/apps/SOURCES +++ b/apps/SOURCES | |||
@@ -116,6 +116,9 @@ codecs.c | |||
116 | dsp.c | 116 | dsp.c |
117 | #ifdef HAVE_RECORDING | 117 | #ifdef HAVE_RECORDING |
118 | enc_config.c | 118 | enc_config.c |
119 | #ifndef SIMULATOR | ||
120 | recorder/pcm_record.c | ||
121 | #endif /* SIMULATOR */ | ||
119 | #endif | 122 | #endif |
120 | eq.c | 123 | eq.c |
121 | #if defined(CPU_COLDFIRE) | 124 | #if defined(CPU_COLDFIRE) |
diff --git a/apps/id3.h b/apps/id3.h new file mode 100644 index 0000000000..da2faf1b12 --- /dev/null +++ b/apps/id3.h | |||
@@ -0,0 +1,246 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Daniel Stenberg | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | #ifndef ID3_H | ||
22 | #define ID3_H | ||
23 | |||
24 | #include <stdbool.h> | ||
25 | #include "config.h" | ||
26 | #include "file.h" | ||
27 | |||
28 | #define ID3V2_BUF_SIZE 300 | ||
29 | |||
30 | /* Audio file types. */ | ||
31 | /* NOTE: The values of the AFMT_* items are used for the %fc tag in the WPS | ||
32 | - so new entries MUST be added to the end to maintain compatibility. | ||
33 | */ | ||
34 | enum | ||
35 | { | ||
36 | AFMT_UNKNOWN = 0, /* Unknown file format */ | ||
37 | |||
38 | /* start formats */ | ||
39 | |||
40 | AFMT_MPA_L1, /* MPEG Audio layer 1 */ | ||
41 | AFMT_MPA_L2, /* MPEG Audio layer 2 */ | ||
42 | AFMT_MPA_L3, /* MPEG Audio layer 3 */ | ||
43 | |||
44 | #if CONFIG_CODEC == SWCODEC | ||
45 | AFMT_AIFF, /* Audio Interchange File Format */ | ||
46 | AFMT_PCM_WAV, /* Uncompressed PCM in a WAV file */ | ||
47 | AFMT_OGG_VORBIS, /* Ogg Vorbis */ | ||
48 | AFMT_FLAC, /* FLAC */ | ||
49 | AFMT_MPC, /* Musepack */ | ||
50 | AFMT_A52, /* A/52 (aka AC3) audio */ | ||
51 | AFMT_WAVPACK, /* WavPack */ | ||
52 | AFMT_ALAC, /* Apple Lossless Audio Codec */ | ||
53 | AFMT_AAC, /* Advanced Audio Coding (AAC) in M4A container */ | ||
54 | AFMT_SHN, /* Shorten */ | ||
55 | AFMT_SID, /* SID File Format */ | ||
56 | AFMT_ADX, /* ADX File Format */ | ||
57 | AFMT_NSF, /* NESM (NES Sound Format) */ | ||
58 | AFMT_SPEEX, /* Ogg Speex speech */ | ||
59 | AFMT_SPC, /* SPC700 save state */ | ||
60 | AFMT_APE, /* Monkey's Audio (APE) */ | ||
61 | AFMT_WMA, /* WMAV1/V2 in ASF */ | ||
62 | AFMT_MOD, /* Amiga MOD File Format */ | ||
63 | AFMT_SAP, /* Amiga 8Bit SAP Format */ | ||
64 | #endif | ||
65 | |||
66 | /* add new formats at any index above this line to have a sensible order - | ||
67 | specified array index inits are used */ | ||
68 | /* format arrays defined in id3.c */ | ||
69 | |||
70 | AFMT_NUM_CODECS, | ||
71 | |||
72 | #if CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING) | ||
73 | /* masks to decompose parts */ | ||
74 | CODEC_AFMT_MASK = 0x0fff, | ||
75 | CODEC_TYPE_MASK = 0x7000, | ||
76 | |||
77 | /* switch for specifying codec type when requesting a filename */ | ||
78 | CODEC_TYPE_DECODER = (0 << 12), /* default */ | ||
79 | CODEC_TYPE_ENCODER = (1 << 12), | ||
80 | #endif /* CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING) */ | ||
81 | }; | ||
82 | |||
83 | #if CONFIG_CODEC == SWCODEC | ||
84 | #define CODEC_EXTENSION "codec" | ||
85 | |||
86 | #ifdef HAVE_RECORDING | ||
87 | #define ENCODER_SUFFIX "_enc" | ||
88 | enum rec_format_indexes | ||
89 | { | ||
90 | __REC_FORMAT_START_INDEX = -1, | ||
91 | |||
92 | /* start formats */ | ||
93 | |||
94 | REC_FORMAT_PCM_WAV, | ||
95 | REC_FORMAT_AIFF, | ||
96 | REC_FORMAT_WAVPACK, | ||
97 | REC_FORMAT_MPA_L3, | ||
98 | |||
99 | /* add new formats at any index above this line to have a sensible order - | ||
100 | specified array index inits are used | ||
101 | REC_FORMAT_CFG_NUM_BITS should allocate enough bits to hold the range | ||
102 | REC_FORMAT_CFG_VALUE_LIST should be in same order as indexes | ||
103 | */ | ||
104 | |||
105 | REC_NUM_FORMATS, | ||
106 | |||
107 | REC_FORMAT_DEFAULT = REC_FORMAT_PCM_WAV, | ||
108 | REC_FORMAT_CFG_NUM_BITS = 2 | ||
109 | }; | ||
110 | |||
111 | #define REC_FORMAT_CFG_VAL_LIST "wave,aiff,wvpk,mpa3" | ||
112 | |||
113 | /* get REC_FORMAT_* corresponding AFMT_* */ | ||
114 | extern const int rec_format_afmt[REC_NUM_FORMATS]; | ||
115 | /* get AFMT_* corresponding REC_FORMAT_* */ | ||
116 | extern const int afmt_rec_format[AFMT_NUM_CODECS]; | ||
117 | |||
118 | #define AFMT_ENTRY(label, root_fname, enc_root_fname, ext_list) \ | ||
119 | { label, root_fname, enc_root_fname, ext_list } | ||
120 | #else /* !HAVE_RECORDING */ | ||
121 | #define AFMT_ENTRY(label, root_fname, enc_root_fname, ext_list) \ | ||
122 | { label, root_fname, ext_list } | ||
123 | #endif /* HAVE_RECORDING */ | ||
124 | #else /* !SWCODEC */ | ||
125 | |||
126 | #define AFMT_ENTRY(label, root_fname, enc_root_fname, ext_list) \ | ||
127 | { label, ext_list } | ||
128 | #endif /* CONFIG_CODEC == SWCODEC */ | ||
129 | |||
130 | /* record describing the audio format */ | ||
131 | struct afmt_entry | ||
132 | { | ||
133 | char label[8]; /* format label */ | ||
134 | #if CONFIG_CODEC == SWCODEC | ||
135 | char *codec_root_fn; /* root codec filename (sans _enc and .codec) */ | ||
136 | #ifdef HAVE_RECORDING | ||
137 | char *codec_enc_root_fn; /* filename of encoder codec */ | ||
138 | #endif | ||
139 | #endif | ||
140 | char *ext_list; /* double NULL terminated extension | ||
141 | list for type with the first as | ||
142 | the default for recording */ | ||
143 | }; | ||
144 | |||
145 | /* database of labels and codecs. add formats per above enum */ | ||
146 | extern const struct afmt_entry audio_formats[AFMT_NUM_CODECS]; | ||
147 | |||
148 | struct mp3entry { | ||
149 | char path[MAX_PATH]; | ||
150 | char* title; | ||
151 | char* artist; | ||
152 | char* album; | ||
153 | char* genre_string; | ||
154 | char* disc_string; | ||
155 | char* track_string; | ||
156 | char* year_string; | ||
157 | char* composer; | ||
158 | char* comment; | ||
159 | char* albumartist; | ||
160 | char* grouping; | ||
161 | int discnum; | ||
162 | int tracknum; | ||
163 | int version; | ||
164 | int layer; | ||
165 | int year; | ||
166 | unsigned char id3version; | ||
167 | unsigned int codectype; | ||
168 | unsigned int bitrate; | ||
169 | unsigned long frequency; | ||
170 | unsigned long id3v2len; | ||
171 | unsigned long id3v1len; | ||
172 | unsigned long first_frame_offset; /* Byte offset to first real MP3 frame. | ||
173 | Used for skipping leading garbage to | ||
174 | avoid gaps between tracks. */ | ||
175 | unsigned long vbr_header_pos; | ||
176 | unsigned long filesize; /* without headers; in bytes */ | ||
177 | unsigned long length; /* song length in ms */ | ||
178 | unsigned long elapsed; /* ms played */ | ||
179 | |||
180 | int lead_trim; /* Number of samples to skip at the beginning */ | ||
181 | int tail_trim; /* Number of samples to remove from the end */ | ||
182 | |||
183 | /* Added for Vorbis */ | ||
184 | unsigned long samples; /* number of samples in track */ | ||
185 | |||
186 | /* MP3 stream specific info */ | ||
187 | unsigned long frame_count; /* number of frames in the file (if VBR) */ | ||
188 | |||
189 | /* Used for A52/AC3 */ | ||
190 | unsigned long bytesperframe; /* number of bytes per frame (if CBR) */ | ||
191 | |||
192 | /* Xing VBR fields */ | ||
193 | bool vbr; | ||
194 | bool has_toc; /* True if there is a VBR header in the file */ | ||
195 | unsigned char toc[100]; /* table of contents */ | ||
196 | |||
197 | /* these following two fields are used for local buffering */ | ||
198 | char id3v2buf[ID3V2_BUF_SIZE]; | ||
199 | char id3v1buf[4][92]; | ||
200 | |||
201 | /* resume related */ | ||
202 | unsigned long offset; /* bytes played */ | ||
203 | int index; /* playlist index */ | ||
204 | |||
205 | /* runtime database fields */ | ||
206 | long tagcache_idx; /* 0=invalid, otherwise idx+1 */ | ||
207 | int rating; | ||
208 | int score; | ||
209 | long playcount; | ||
210 | long lastplayed; | ||
211 | long playtime; | ||
212 | |||
213 | /* replaygain support */ | ||
214 | |||
215 | #if CONFIG_CODEC == SWCODEC | ||
216 | char* track_gain_string; | ||
217 | char* album_gain_string; | ||
218 | long track_gain; /* 7.24 signed fixed point. 0 for no gain. */ | ||
219 | long album_gain; | ||
220 | long track_peak; /* 7.24 signed fixed point. 0 for no peak. */ | ||
221 | long album_peak; | ||
222 | #endif | ||
223 | |||
224 | /* Cuesheet support */ | ||
225 | int cuesheet_type; /* 0: none, 1: external, 2: embedded */ | ||
226 | |||
227 | /* Musicbrainz Track ID */ | ||
228 | char* mb_track_id; | ||
229 | }; | ||
230 | |||
231 | enum { | ||
232 | ID3_VER_1_0 = 1, | ||
233 | ID3_VER_1_1, | ||
234 | ID3_VER_2_2, | ||
235 | ID3_VER_2_3, | ||
236 | ID3_VER_2_4 | ||
237 | }; | ||
238 | |||
239 | bool get_mp3_metadata(int fd, struct mp3entry *entry, const char *filename); | ||
240 | bool mp3info(struct mp3entry *entry, const char *filename); | ||
241 | char* id3_get_num_genre(unsigned int genre_num); | ||
242 | int getid3v2len(int fd); | ||
243 | void adjust_mp3entry(struct mp3entry *entry, void *dest, const void *orig); | ||
244 | void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig); | ||
245 | |||
246 | #endif | ||
diff --git a/apps/mpeg.c b/apps/mpeg.c index 2c65e46060..b570f41b28 100644 --- a/apps/mpeg.c +++ b/apps/mpeg.c | |||
@@ -2432,6 +2432,15 @@ void audio_set_recording_options(struct audio_recording_options *options) | |||
2432 | 2432 | ||
2433 | DEBUGF("mas_writemem(MAS_BANK_D0, ENCODER_CONTROL, %x)\n", shadow_encoder_control); | 2433 | DEBUGF("mas_writemem(MAS_BANK_D0, ENCODER_CONTROL, %x)\n", shadow_encoder_control); |
2434 | 2434 | ||
2435 | #if CONFIG_TUNER & S1A0903X01 | ||
2436 | /* Store the (unpitched) MAS PLL frequency. Used for avoiding FM | ||
2437 | interference with the Samsung tuner. */ | ||
2438 | if (rec_frequency_index) | ||
2439 | mas_store_pllfreq(24576000); | ||
2440 | else | ||
2441 | mas_store_pllfreq(22579000); | ||
2442 | #endif | ||
2443 | |||
2435 | shadow_soft_mute = options->rec_editable?4:0; | 2444 | shadow_soft_mute = options->rec_editable?4:0; |
2436 | mas_writemem(MAS_BANK_D0, MAS_D0_SOFT_MUTE, &shadow_soft_mute,1); | 2445 | mas_writemem(MAS_BANK_D0, MAS_D0_SOFT_MUTE, &shadow_soft_mute,1); |
2437 | 2446 | ||
@@ -2484,22 +2493,6 @@ void audio_set_recording_gain(int left, int right, int type) | |||
2484 | mas_codec_writereg(0x0, shadow_codec_reg0); | 2493 | mas_codec_writereg(0x0, shadow_codec_reg0); |
2485 | } | 2494 | } |
2486 | 2495 | ||
2487 | #if CONFIG_TUNER & S1A0903X01 | ||
2488 | /* Get the (unpitched) MAS PLL frequency, for avoiding FM interference with the | ||
2489 | * Samsung tuner. Zero means unknown. Currently handles recording from analog | ||
2490 | * input only. */ | ||
2491 | int mpeg_get_mas_pllfreq(void) | ||
2492 | { | ||
2493 | if (mpeg_mode != MPEG_ENCODER) | ||
2494 | return 0; | ||
2495 | |||
2496 | if (rec_frequency_index == 0) /* 44.1 kHz / 22.05 kHz */ | ||
2497 | return 22579000; | ||
2498 | else | ||
2499 | return 24576000; | ||
2500 | } | ||
2501 | #endif /* CONFIG_TUNER & S1A0903X01 */ | ||
2502 | |||
2503 | /* try to make some kind of beep, also in recording mode */ | 2496 | /* try to make some kind of beep, also in recording mode */ |
2504 | void audio_beep(int duration) | 2497 | void audio_beep(int duration) |
2505 | { | 2498 | { |
diff --git a/apps/mpeg.h b/apps/mpeg.h new file mode 100644 index 0000000000..ce2cff0069 --- /dev/null +++ b/apps/mpeg.h | |||
@@ -0,0 +1,66 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Linus Nielsen Feltzing | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | #ifndef _MPEG_H_ | ||
22 | #define _MPEG_H_ | ||
23 | |||
24 | #include <stdbool.h> | ||
25 | #include "id3.h" | ||
26 | |||
27 | #define MPEG_SWAP_CHUNKSIZE 0x2000 | ||
28 | #define MPEG_HIGH_WATER 2 /* We leave 2 bytes empty because otherwise we | ||
29 | wouldn't be able to see the difference between | ||
30 | an empty buffer and a full one. */ | ||
31 | #define MPEG_LOW_WATER 0x60000 | ||
32 | #define MPEG_RECORDING_LOW_WATER 0x80000 | ||
33 | #define MPEG_LOW_WATER_CHUNKSIZE 0x40000 | ||
34 | #define MPEG_LOW_WATER_SWAP_CHUNKSIZE 0x10000 | ||
35 | #ifdef HAVE_MMC | ||
36 | #define MPEG_PLAY_PENDING_THRESHOLD 0x20000 | ||
37 | #define MPEG_PLAY_PENDING_SWAPSIZE 0x20000 | ||
38 | #else | ||
39 | #define MPEG_PLAY_PENDING_THRESHOLD 0x10000 | ||
40 | #define MPEG_PLAY_PENDING_SWAPSIZE 0x10000 | ||
41 | #endif | ||
42 | |||
43 | #define MPEG_MAX_PRERECORD_SECONDS 30 | ||
44 | |||
45 | /* For ID3 info and VBR header */ | ||
46 | #define MPEG_RESERVED_HEADER_SPACE (4096 + 576) | ||
47 | |||
48 | #if (CONFIG_CODEC == MAS3587F) || defined(SIMULATOR) | ||
49 | |||
50 | #if CONFIG_TUNER & S1A0903X01 | ||
51 | int mpeg_get_mas_pllfreq(void); | ||
52 | #endif | ||
53 | |||
54 | #endif | ||
55 | unsigned long mpeg_get_last_header(void); | ||
56 | |||
57 | /* in order to keep the recording here, I have to expose this */ | ||
58 | void rec_tick(void); | ||
59 | void playback_tick(void); /* FixMe: get rid of this, use mp3_get_playtime() */ | ||
60 | |||
61 | void audio_set_track_changed_event(void (*handler)(struct mp3entry *id3)); | ||
62 | void audio_set_track_buffer_event(void (*handler)(struct mp3entry *id3)); | ||
63 | void audio_set_track_unbuffer_event(void (*handler)(struct mp3entry *id3)); | ||
64 | void audio_set_cuesheet_callback(bool (*handler)(const char *filename)); | ||
65 | |||
66 | #endif | ||
diff --git a/apps/recorder/pcm_record.c b/apps/recorder/pcm_record.c new file mode 100644 index 0000000000..b1ea535470 --- /dev/null +++ b/apps/recorder/pcm_record.c | |||
@@ -0,0 +1,1788 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2005 by Linus Nielsen Feltzing | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | #include "pcm_record.h" | ||
22 | #include "system.h" | ||
23 | #include "kernel.h" | ||
24 | #include "logf.h" | ||
25 | #include "thread.h" | ||
26 | #include <string.h> | ||
27 | #include "ata.h" | ||
28 | #include "usb.h" | ||
29 | #include "buffer.h" | ||
30 | #include "general.h" | ||
31 | #include "audio.h" | ||
32 | #include "sound.h" | ||
33 | #include "id3.h" | ||
34 | #ifdef HAVE_SPDIF_IN | ||
35 | #include "spdif.h" | ||
36 | #endif | ||
37 | |||
38 | /***************************************************************************/ | ||
39 | |||
40 | extern struct thread_entry *codec_thread_p; | ||
41 | |||
42 | /** General recording state **/ | ||
43 | static bool is_recording; /* We are recording */ | ||
44 | static bool is_paused; /* We have paused */ | ||
45 | static unsigned long errors; /* An error has occured */ | ||
46 | static unsigned long warnings; /* Warning */ | ||
47 | static int flush_interrupts = 0; /* Number of messages queued that | ||
48 | should interrupt a flush in | ||
49 | progress - | ||
50 | for a safety net and a prompt | ||
51 | response to stop, split and pause | ||
52 | requests - | ||
53 | only interrupts a flush initiated | ||
54 | by pcmrec_flush(0) */ | ||
55 | |||
56 | /* Utility functions for setting/clearing flushing interrupt flag */ | ||
57 | static inline void flush_interrupt(void) | ||
58 | { | ||
59 | flush_interrupts++; | ||
60 | logf("flush int: %d", flush_interrupts); | ||
61 | } | ||
62 | |||
63 | static inline void clear_flush_interrupt(void) | ||
64 | { | ||
65 | if (--flush_interrupts < 0) | ||
66 | flush_interrupts = 0; | ||
67 | } | ||
68 | |||
69 | /** Stats on encoded data for current file **/ | ||
70 | static size_t num_rec_bytes; /* Num bytes recorded */ | ||
71 | static unsigned long num_rec_samples; /* Number of PCM samples recorded */ | ||
72 | |||
73 | /** Stats on encoded data for all files from start to stop **/ | ||
74 | #if 0 | ||
75 | static unsigned long long accum_rec_bytes; /* total size written to chunks */ | ||
76 | static unsigned long long accum_pcm_samples; /* total pcm count processed */ | ||
77 | #endif | ||
78 | |||
79 | /* Keeps data about current file and is sent as event data for codec */ | ||
80 | static struct enc_file_event_data rec_fdata IDATA_ATTR = | ||
81 | { | ||
82 | .chunk = NULL, | ||
83 | .new_enc_size = 0, | ||
84 | .new_num_pcm = 0, | ||
85 | .rec_file = -1, | ||
86 | .num_pcm_samples = 0 | ||
87 | }; | ||
88 | |||
89 | /** These apply to current settings **/ | ||
90 | static int rec_source; /* current rec_source setting */ | ||
91 | static int rec_frequency; /* current frequency setting */ | ||
92 | static unsigned long sample_rate; /* Sample rate in HZ */ | ||
93 | static int num_channels; /* Current number of channels */ | ||
94 | static int rec_mono_mode; /* how mono is created */ | ||
95 | static struct encoder_config enc_config; /* Current encoder configuration */ | ||
96 | static unsigned long pre_record_ticks; /* pre-record time in ticks */ | ||
97 | |||
98 | /**************************************************************************** | ||
99 | use 2 circular buffers: | ||
100 | pcm_buffer=DMA output buffer: chunks (8192 Bytes) of raw pcm audio data | ||
101 | enc_buffer=encoded audio buffer: storage for encoder output data | ||
102 | |||
103 | Flow: | ||
104 | 1. when entering recording_screen DMA feeds the ringbuffer pcm_buffer | ||
105 | 2. if enough pcm data are available the encoder codec does encoding of pcm | ||
106 | chunks (4-8192 Bytes) into ringbuffer enc_buffer in codec_thread | ||
107 | 3. pcmrec_callback detects enc_buffer 'near full' and writes data to disk | ||
108 | |||
109 | Functions calls (basic encoder steps): | ||
110 | 1.main: audio_load_encoder(); start the encoder | ||
111 | 2.encoder: enc_get_inputs(); get encoder recording settings | ||
112 | 3.encoder: enc_set_parameters(); set the encoder parameters | ||
113 | 4.encoder: enc_get_pcm_data(); get n bytes of unprocessed pcm data | ||
114 | 5.encoder: enc_unget_pcm_data(); put n bytes of data back (optional) | ||
115 | 6.encoder: enc_get_chunk(); get a ptr to next enc chunk | ||
116 | 7.encoder: <process enc chunk> compress and store data to enc chunk | ||
117 | 8.encoder: enc_finish_chunk(); inform main about chunk processed and | ||
118 | is available to be written to a file. | ||
119 | Encoder can place any number of chunks | ||
120 | of PCM data in a single output chunk | ||
121 | but must stay within its output chunk | ||
122 | size | ||
123 | 9.encoder: repeat 4. to 8. | ||
124 | A.pcmrec: enc_events_callback(); called for certain events | ||
125 | |||
126 | (*) Optional step | ||
127 | ****************************************************************************/ | ||
128 | |||
129 | /** buffer parameters where incoming PCM data is placed **/ | ||
130 | #define PCM_NUM_CHUNKS 256 /* Power of 2 */ | ||
131 | #define PCM_CHUNK_SIZE 8192 /* Power of 2 */ | ||
132 | #define PCM_CHUNK_MASK (PCM_NUM_CHUNKS*PCM_CHUNK_SIZE - 1) | ||
133 | |||
134 | #define GET_PCM_CHUNK(offset) ((long *)(pcm_buffer + (offset))) | ||
135 | #define GET_ENC_CHUNK(index) ENC_CHUNK_HDR(enc_buffer + enc_chunk_size*(index)) | ||
136 | #define INC_ENC_INDEX(index) \ | ||
137 | { if (++index >= enc_num_chunks) index = 0; } | ||
138 | #define DEC_ENC_INDEX(index) \ | ||
139 | { if (--index < 0) index = enc_num_chunks - 1; } | ||
140 | |||
141 | static size_t rec_buffer_size; /* size of available buffer */ | ||
142 | static unsigned char *pcm_buffer; /* circular recording buffer */ | ||
143 | static unsigned char *enc_buffer; /* circular encoding buffer */ | ||
144 | #ifdef DEBUG | ||
145 | static unsigned long *wrap_id_p; /* magic at wrap position - a debugging | ||
146 | aid to check if the encoder data | ||
147 | spilled out of its chunk */ | ||
148 | #endif /* DEBUG */ | ||
149 | static volatile int dma_wr_pos; /* current DMA write pos */ | ||
150 | static int pcm_rd_pos; /* current PCM read pos */ | ||
151 | static int pcm_enc_pos; /* position encoder is processing */ | ||
152 | static volatile bool dma_lock; /* lock DMA write position */ | ||
153 | static int enc_wr_index; /* encoder chunk write index */ | ||
154 | static int enc_rd_index; /* encoder chunk read index */ | ||
155 | static int enc_num_chunks; /* number of chunks in ringbuffer */ | ||
156 | static size_t enc_chunk_size; /* maximum encoder chunk size */ | ||
157 | static unsigned long enc_sample_rate; /* sample rate used by encoder */ | ||
158 | static bool pcmrec_context = false; /* called by pcmrec thread? */ | ||
159 | static bool pcm_buffer_empty; /* all pcm chunks processed? */ | ||
160 | |||
161 | /** file flushing **/ | ||
162 | static int low_watermark; /* Low watermark to stop flush */ | ||
163 | static int high_watermark; /* max chunk limit for data flush */ | ||
164 | static unsigned long spinup_time = 35*HZ/10; /* Fudged spinup time */ | ||
165 | static int last_ata_spinup_time = -1;/* previous spin time used */ | ||
166 | #ifdef HAVE_PRIORITY_SCHEDULING | ||
167 | static int flood_watermark; /* boost thread priority when here */ | ||
168 | #endif | ||
169 | |||
170 | /* Constants that control watermarks */ | ||
171 | #define LOW_SECONDS 1 /* low watermark time till empty */ | ||
172 | #define MINI_CHUNKS 10 /* chunk count for mini flush */ | ||
173 | #ifdef HAVE_PRIORITY_SCHEDULING | ||
174 | #define PRIO_SECONDS 10 /* max flush time before priority boost */ | ||
175 | #endif | ||
176 | #if MEM <= 16 | ||
177 | #define PANIC_SECONDS 5 /* flood watermark time until full */ | ||
178 | #define FLUSH_SECONDS 7 /* flush watermark time until full */ | ||
179 | #else | ||
180 | #define PANIC_SECONDS 8 | ||
181 | #define FLUSH_SECONDS 10 | ||
182 | #endif /* MEM */ | ||
183 | |||
184 | /** encoder events **/ | ||
185 | static void (*enc_events_callback)(enum enc_events event, void *data); | ||
186 | |||
187 | /** Path queue for files to write **/ | ||
188 | #define FNQ_MIN_NUM_PATHS 16 /* minimum number of paths to hold */ | ||
189 | #define FNQ_MAX_NUM_PATHS 64 /* maximum number of paths to hold */ | ||
190 | static unsigned char *fn_queue; /* pointer to first filename */ | ||
191 | static ssize_t fnq_size; /* capacity of queue in bytes */ | ||
192 | static int fnq_rd_pos; /* current read position */ | ||
193 | static int fnq_wr_pos; /* current write position */ | ||
194 | #define FNQ_NEXT(pos) \ | ||
195 | ({ int p = (pos) + MAX_PATH; \ | ||
196 | if (p >= fnq_size) \ | ||
197 | p = 0; \ | ||
198 | p; }) | ||
199 | #define FNQ_PREV(pos) \ | ||
200 | ({ int p = (pos) - MAX_PATH; \ | ||
201 | if (p < 0) \ | ||
202 | p = fnq_size - MAX_PATH; \ | ||
203 | p; }) | ||
204 | |||
205 | enum | ||
206 | { | ||
207 | PCMREC_FLUSH_INTERRUPTABLE = 0x8000000, /* Flush can be interrupted by | ||
208 | incoming messages - combine | ||
209 | with other constants */ | ||
210 | PCMREC_FLUSH_ALL = 0x7ffffff, /* Flush all files */ | ||
211 | PCMREC_FLUSH_MINI = 0x7fffffe, /* Flush a small number of | ||
212 | chunks */ | ||
213 | PCMREC_FLUSH_IF_HIGH = 0x0000000, /* Flush if high watermark | ||
214 | reached */ | ||
215 | }; | ||
216 | |||
217 | /***************************************************************************/ | ||
218 | |||
219 | static struct event_queue pcmrec_queue SHAREDBSS_ATTR; | ||
220 | static struct queue_sender_list pcmrec_queue_send SHAREDBSS_ATTR; | ||
221 | static long pcmrec_stack[3*DEFAULT_STACK_SIZE/sizeof(long)]; | ||
222 | static const char pcmrec_thread_name[] = "pcmrec"; | ||
223 | static struct thread_entry *pcmrec_thread_p; | ||
224 | |||
225 | static void pcmrec_thread(void); | ||
226 | |||
227 | enum | ||
228 | { | ||
229 | PCMREC_NULL = 0, | ||
230 | PCMREC_INIT, /* enable recording */ | ||
231 | PCMREC_CLOSE, /* close recording */ | ||
232 | PCMREC_OPTIONS, /* set recording options */ | ||
233 | PCMREC_RECORD, /* record a new file */ | ||
234 | PCMREC_STOP, /* stop the current recording */ | ||
235 | PCMREC_PAUSE, /* pause the current recording */ | ||
236 | PCMREC_RESUME, /* resume the current recording */ | ||
237 | #if 0 | ||
238 | PCMREC_FLUSH_NUM, /* flush a number of files out */ | ||
239 | #endif | ||
240 | }; | ||
241 | |||
242 | /*******************************************************************/ | ||
243 | /* Functions that are not executing in the pcmrec_thread first */ | ||
244 | /*******************************************************************/ | ||
245 | |||
246 | /* Callback for when more data is ready - called in interrupt context */ | ||
247 | static int pcm_rec_have_more(int status) | ||
248 | { | ||
249 | if (status < 0) | ||
250 | { | ||
251 | /* some error condition */ | ||
252 | if (status == DMA_REC_ERROR_DMA) | ||
253 | { | ||
254 | /* Flush recorded data to disk and stop recording */ | ||
255 | queue_post(&pcmrec_queue, PCMREC_STOP, 0); | ||
256 | return -1; | ||
257 | } | ||
258 | /* else try again next transmission */ | ||
259 | } | ||
260 | else if (!dma_lock) | ||
261 | { | ||
262 | /* advance write position */ | ||
263 | int next_pos = (dma_wr_pos + PCM_CHUNK_SIZE) & PCM_CHUNK_MASK; | ||
264 | |||
265 | /* set pcm ovf if processing start position is inside current | ||
266 | write chunk */ | ||
267 | if ((unsigned)(pcm_enc_pos - next_pos) < PCM_CHUNK_SIZE) | ||
268 | warnings |= PCMREC_W_PCM_BUFFER_OVF; | ||
269 | |||
270 | dma_wr_pos = next_pos; | ||
271 | } | ||
272 | |||
273 | pcm_record_more(GET_PCM_CHUNK(dma_wr_pos), PCM_CHUNK_SIZE); | ||
274 | return 0; | ||
275 | } /* pcm_rec_have_more */ | ||
276 | |||
277 | static void reset_hardware(void) | ||
278 | { | ||
279 | /* reset pcm to defaults (playback only) */ | ||
280 | pcm_set_frequency(HW_SAMPR_DEFAULT); | ||
281 | audio_set_output_source(AUDIO_SRC_PLAYBACK); | ||
282 | pcm_apply_settings(); | ||
283 | } | ||
284 | |||
285 | /** pcm_rec_* group **/ | ||
286 | |||
287 | /** | ||
288 | * Clear all errors and warnings | ||
289 | */ | ||
290 | void pcm_rec_error_clear(void) | ||
291 | { | ||
292 | errors = warnings = 0; | ||
293 | } /* pcm_rec_error_clear */ | ||
294 | |||
295 | /** | ||
296 | * Check mode, errors and warnings | ||
297 | */ | ||
298 | unsigned long pcm_rec_status(void) | ||
299 | { | ||
300 | unsigned long ret = 0; | ||
301 | |||
302 | if (is_recording) | ||
303 | ret |= AUDIO_STATUS_RECORD; | ||
304 | else if (pre_record_ticks) | ||
305 | ret |= AUDIO_STATUS_PRERECORD; | ||
306 | |||
307 | if (is_paused) | ||
308 | ret |= AUDIO_STATUS_PAUSE; | ||
309 | |||
310 | if (errors) | ||
311 | ret |= AUDIO_STATUS_ERROR; | ||
312 | |||
313 | if (warnings) | ||
314 | ret |= AUDIO_STATUS_WARNING; | ||
315 | |||
316 | return ret; | ||
317 | } /* pcm_rec_status */ | ||
318 | |||
319 | /** | ||
320 | * Return warnings that have occured since recording started | ||
321 | */ | ||
322 | unsigned long pcm_rec_get_warnings(void) | ||
323 | { | ||
324 | return warnings; | ||
325 | } | ||
326 | |||
327 | #if 0 | ||
328 | int pcm_rec_current_bitrate(void) | ||
329 | { | ||
330 | if (accum_pcm_samples == 0) | ||
331 | return 0; | ||
332 | |||
333 | return (int)(8*accum_rec_bytes*enc_sample_rate / (1000*accum_pcm_samples)); | ||
334 | } /* pcm_rec_current_bitrate */ | ||
335 | #endif | ||
336 | |||
337 | #if 0 | ||
338 | int pcm_rec_encoder_afmt(void) | ||
339 | { | ||
340 | return enc_config.afmt; | ||
341 | } /* pcm_rec_encoder_afmt */ | ||
342 | #endif | ||
343 | |||
344 | #if 0 | ||
345 | int pcm_rec_rec_format(void) | ||
346 | { | ||
347 | return afmt_rec_format[enc_config.afmt]; | ||
348 | } /* pcm_rec_rec_format */ | ||
349 | #endif | ||
350 | |||
351 | #ifdef HAVE_SPDIF_IN | ||
352 | unsigned long pcm_rec_sample_rate(void) | ||
353 | { | ||
354 | /* Which is better ?? */ | ||
355 | #if 0 | ||
356 | return enc_sample_rate; | ||
357 | #endif | ||
358 | return sample_rate; | ||
359 | } /* audio_get_sample_rate */ | ||
360 | #endif | ||
361 | |||
362 | /** | ||
363 | * Creates pcmrec_thread | ||
364 | */ | ||
365 | void pcm_rec_init(void) | ||
366 | { | ||
367 | queue_init(&pcmrec_queue, true); | ||
368 | pcmrec_thread_p = | ||
369 | create_thread(pcmrec_thread, pcmrec_stack, sizeof(pcmrec_stack), | ||
370 | 0, pcmrec_thread_name IF_PRIO(, PRIORITY_RECORDING) | ||
371 | IF_COP(, CPU)); | ||
372 | queue_enable_queue_send(&pcmrec_queue, &pcmrec_queue_send, | ||
373 | pcmrec_thread_p); | ||
374 | } /* pcm_rec_init */ | ||
375 | |||
376 | /** audio_* group **/ | ||
377 | |||
378 | /** | ||
379 | * Initializes recording - call before calling any other recording function | ||
380 | */ | ||
381 | void audio_init_recording(unsigned int buffer_offset) | ||
382 | { | ||
383 | logf("audio_init_recording"); | ||
384 | queue_send(&pcmrec_queue, PCMREC_INIT, 0); | ||
385 | logf("audio_init_recording done"); | ||
386 | (void)buffer_offset; | ||
387 | } /* audio_init_recording */ | ||
388 | |||
389 | /** | ||
390 | * Closes recording - call audio_stop_recording first | ||
391 | */ | ||
392 | void audio_close_recording(void) | ||
393 | { | ||
394 | logf("audio_close_recording"); | ||
395 | queue_send(&pcmrec_queue, PCMREC_CLOSE, 0); | ||
396 | logf("audio_close_recording done"); | ||
397 | } /* audio_close_recording */ | ||
398 | |||
399 | /** | ||
400 | * Sets recording parameters | ||
401 | */ | ||
402 | void audio_set_recording_options(struct audio_recording_options *options) | ||
403 | { | ||
404 | logf("audio_set_recording_options"); | ||
405 | queue_send(&pcmrec_queue, PCMREC_OPTIONS, (intptr_t)options); | ||
406 | logf("audio_set_recording_options done"); | ||
407 | } /* audio_set_recording_options */ | ||
408 | |||
409 | /** | ||
410 | * Start recording if not recording or else split | ||
411 | */ | ||
412 | void audio_record(const char *filename) | ||
413 | { | ||
414 | logf("audio_record: %s", filename); | ||
415 | flush_interrupt(); | ||
416 | queue_send(&pcmrec_queue, PCMREC_RECORD, (intptr_t)filename); | ||
417 | logf("audio_record_done"); | ||
418 | } /* audio_record */ | ||
419 | |||
420 | /** | ||
421 | * audio_record wrapper for API compatibility with HW codec | ||
422 | */ | ||
423 | void audio_new_file(const char *filename) | ||
424 | { | ||
425 | audio_record(filename); | ||
426 | } /* audio_new_file */ | ||
427 | |||
428 | /** | ||
429 | * Stop current recording if recording | ||
430 | */ | ||
431 | void audio_stop_recording(void) | ||
432 | { | ||
433 | logf("audio_stop_recording"); | ||
434 | flush_interrupt(); | ||
435 | queue_post(&pcmrec_queue, PCMREC_STOP, 0); | ||
436 | logf("audio_stop_recording done"); | ||
437 | } /* audio_stop_recording */ | ||
438 | |||
439 | /** | ||
440 | * Pause current recording | ||
441 | */ | ||
442 | void audio_pause_recording(void) | ||
443 | { | ||
444 | logf("audio_pause_recording"); | ||
445 | flush_interrupt(); | ||
446 | queue_post(&pcmrec_queue, PCMREC_PAUSE, 0); | ||
447 | logf("audio_pause_recording done"); | ||
448 | } /* audio_pause_recording */ | ||
449 | |||
450 | /** | ||
451 | * Resume current recording if paused | ||
452 | */ | ||
453 | void audio_resume_recording(void) | ||
454 | { | ||
455 | logf("audio_resume_recording"); | ||
456 | queue_post(&pcmrec_queue, PCMREC_RESUME, 0); | ||
457 | logf("audio_resume_recording done"); | ||
458 | } /* audio_resume_recording */ | ||
459 | |||
460 | /** | ||
461 | * Note that microphone is mono, only left value is used | ||
462 | * See audiohw_set_recvol() for exact ranges. | ||
463 | * | ||
464 | * @param type AUDIO_GAIN_MIC, AUDIO_GAIN_LINEIN | ||
465 | * | ||
466 | */ | ||
467 | void audio_set_recording_gain(int left, int right, int type) | ||
468 | { | ||
469 | //logf("rcmrec: t=%d l=%d r=%d", type, left, right); | ||
470 | audiohw_set_recvol(left, right, type); | ||
471 | } /* audio_set_recording_gain */ | ||
472 | |||
473 | /** Information about current state **/ | ||
474 | |||
475 | /** | ||
476 | * Return current recorded time in ticks (playback eqivalent time) | ||
477 | */ | ||
478 | unsigned long audio_recorded_time(void) | ||
479 | { | ||
480 | if (!is_recording || enc_sample_rate == 0) | ||
481 | return 0; | ||
482 | |||
483 | /* return actual recorded time a la encoded data even if encoder rate | ||
484 | doesn't match the pcm rate */ | ||
485 | return (long)(HZ*(unsigned long long)num_rec_samples / enc_sample_rate); | ||
486 | } /* audio_recorded_time */ | ||
487 | |||
488 | /** | ||
489 | * Return number of bytes encoded to output | ||
490 | */ | ||
491 | unsigned long audio_num_recorded_bytes(void) | ||
492 | { | ||
493 | if (!is_recording) | ||
494 | return 0; | ||
495 | |||
496 | return num_rec_bytes; | ||
497 | } /* audio_num_recorded_bytes */ | ||
498 | |||
499 | /***************************************************************************/ | ||
500 | /* */ | ||
501 | /* Functions that execute in the context of pcmrec_thread */ | ||
502 | /* */ | ||
503 | /***************************************************************************/ | ||
504 | |||
505 | /** Filename Queue **/ | ||
506 | |||
507 | /* returns true if the queue is empty */ | ||
508 | static inline bool pcmrec_fnq_is_empty(void) | ||
509 | { | ||
510 | return fnq_rd_pos == fnq_wr_pos; | ||
511 | } /* pcmrec_fnq_is_empty */ | ||
512 | |||
513 | /* empties the filename queue */ | ||
514 | static inline void pcmrec_fnq_set_empty(void) | ||
515 | { | ||
516 | fnq_rd_pos = fnq_wr_pos; | ||
517 | } /* pcmrec_fnq_set_empty */ | ||
518 | |||
519 | /* returns true if the queue is full */ | ||
520 | static bool pcmrec_fnq_is_full(void) | ||
521 | { | ||
522 | ssize_t size = fnq_wr_pos - fnq_rd_pos; | ||
523 | if (size < 0) | ||
524 | size += fnq_size; | ||
525 | |||
526 | return size >= fnq_size - MAX_PATH; | ||
527 | } /* pcmrec_fnq_is_full */ | ||
528 | |||
529 | /* queue another filename - will overwrite oldest one if full */ | ||
530 | static bool pcmrec_fnq_add_filename(const char *filename) | ||
531 | { | ||
532 | strncpy(fn_queue + fnq_wr_pos, filename, MAX_PATH); | ||
533 | fnq_wr_pos = FNQ_NEXT(fnq_wr_pos); | ||
534 | |||
535 | if (fnq_rd_pos != fnq_wr_pos) | ||
536 | return true; | ||
537 | |||
538 | /* queue full */ | ||
539 | fnq_rd_pos = FNQ_NEXT(fnq_rd_pos); | ||
540 | return true; | ||
541 | } /* pcmrec_fnq_add_filename */ | ||
542 | |||
543 | /* replace the last filename added */ | ||
544 | static bool pcmrec_fnq_replace_tail(const char *filename) | ||
545 | { | ||
546 | int pos; | ||
547 | |||
548 | if (pcmrec_fnq_is_empty()) | ||
549 | return false; | ||
550 | |||
551 | pos = FNQ_PREV(fnq_wr_pos); | ||
552 | |||
553 | strncpy(fn_queue + pos, filename, MAX_PATH); | ||
554 | |||
555 | return true; | ||
556 | } /* pcmrec_fnq_replace_tail */ | ||
557 | |||
558 | /* pulls the next filename from the queue */ | ||
559 | static bool pcmrec_fnq_get_filename(char *filename) | ||
560 | { | ||
561 | if (pcmrec_fnq_is_empty()) | ||
562 | return false; | ||
563 | |||
564 | if (filename) | ||
565 | strncpy(filename, fn_queue + fnq_rd_pos, MAX_PATH); | ||
566 | |||
567 | fnq_rd_pos = FNQ_NEXT(fnq_rd_pos); | ||
568 | return true; | ||
569 | } /* pcmrec_fnq_get_filename */ | ||
570 | |||
571 | /* close the file number pointed to by fd_p */ | ||
572 | static void pcmrec_close_file(int *fd_p) | ||
573 | { | ||
574 | if (*fd_p < 0) | ||
575 | return; /* preserve error */ | ||
576 | |||
577 | if (close(*fd_p) != 0) | ||
578 | errors |= PCMREC_E_IO; | ||
579 | |||
580 | *fd_p = -1; | ||
581 | } /* pcmrec_close_file */ | ||
582 | |||
583 | /** Data Flushing **/ | ||
584 | |||
585 | /** | ||
586 | * called after callback to update sizes if codec changed the amount of data | ||
587 | * a chunk represents | ||
588 | */ | ||
589 | static inline void pcmrec_update_sizes_inl(size_t prev_enc_size, | ||
590 | unsigned long prev_num_pcm) | ||
591 | { | ||
592 | if (rec_fdata.new_enc_size != prev_enc_size) | ||
593 | { | ||
594 | ssize_t size_diff = rec_fdata.new_enc_size - prev_enc_size; | ||
595 | num_rec_bytes += size_diff; | ||
596 | #if 0 | ||
597 | accum_rec_bytes += size_diff; | ||
598 | #endif | ||
599 | } | ||
600 | |||
601 | if (rec_fdata.new_num_pcm != prev_num_pcm) | ||
602 | { | ||
603 | unsigned long pcm_diff = rec_fdata.new_num_pcm - prev_num_pcm; | ||
604 | num_rec_samples += pcm_diff; | ||
605 | #if 0 | ||
606 | accum_pcm_samples += pcm_diff; | ||
607 | #endif | ||
608 | } | ||
609 | } /* pcmrec_update_sizes_inl */ | ||
610 | |||
611 | /* don't need to inline every instance */ | ||
612 | static void pcmrec_update_sizes(size_t prev_enc_size, | ||
613 | unsigned long prev_num_pcm) | ||
614 | { | ||
615 | pcmrec_update_sizes_inl(prev_enc_size, prev_num_pcm); | ||
616 | } /* pcmrec_update_sizes */ | ||
617 | |||
618 | static void pcmrec_start_file(void) | ||
619 | { | ||
620 | size_t enc_size = rec_fdata.new_enc_size; | ||
621 | unsigned long num_pcm = rec_fdata.new_num_pcm; | ||
622 | int curr_rec_file = rec_fdata.rec_file; | ||
623 | char filename[MAX_PATH]; | ||
624 | |||
625 | /* must always pull the filename that matches with this queue */ | ||
626 | if (!pcmrec_fnq_get_filename(filename)) | ||
627 | { | ||
628 | logf("start file: fnq empty"); | ||
629 | *filename = '\0'; | ||
630 | errors |= PCMREC_E_FNQ_DESYNC; | ||
631 | } | ||
632 | else if (errors != 0) | ||
633 | { | ||
634 | logf("start file: error already"); | ||
635 | } | ||
636 | else if (curr_rec_file >= 0) | ||
637 | { | ||
638 | /* Any previous file should have been closed */ | ||
639 | logf("start file: file already open"); | ||
640 | errors |= PCMREC_E_FNQ_DESYNC; | ||
641 | } | ||
642 | |||
643 | if (errors != 0) | ||
644 | rec_fdata.chunk->flags |= CHUNKF_ERROR; | ||
645 | |||
646 | /* encoder can set error flag here and should increase | ||
647 | enc_new_size and pcm_new_size to reflect additional | ||
648 | data written if any */ | ||
649 | rec_fdata.filename = filename; | ||
650 | enc_events_callback(ENC_START_FILE, &rec_fdata); | ||
651 | |||
652 | if (errors == 0 && (rec_fdata.chunk->flags & CHUNKF_ERROR)) | ||
653 | { | ||
654 | logf("start file: enc error"); | ||
655 | errors |= PCMREC_E_ENCODER; | ||
656 | } | ||
657 | |||
658 | if (errors != 0) | ||
659 | { | ||
660 | pcmrec_close_file(&curr_rec_file); | ||
661 | /* Write no more to this file */ | ||
662 | rec_fdata.chunk->flags |= CHUNKF_END_FILE; | ||
663 | } | ||
664 | else | ||
665 | { | ||
666 | pcmrec_update_sizes(enc_size, num_pcm); | ||
667 | } | ||
668 | |||
669 | rec_fdata.chunk->flags &= ~CHUNKF_START_FILE; | ||
670 | } /* pcmrec_start_file */ | ||
671 | |||
672 | static inline void pcmrec_write_chunk(void) | ||
673 | { | ||
674 | size_t enc_size = rec_fdata.new_enc_size; | ||
675 | unsigned long num_pcm = rec_fdata.new_num_pcm; | ||
676 | |||
677 | if (errors != 0) | ||
678 | rec_fdata.chunk->flags |= CHUNKF_ERROR; | ||
679 | |||
680 | enc_events_callback(ENC_WRITE_CHUNK, &rec_fdata); | ||
681 | |||
682 | if ((long)rec_fdata.chunk->flags >= 0) | ||
683 | { | ||
684 | pcmrec_update_sizes_inl(enc_size, num_pcm); | ||
685 | } | ||
686 | else if (errors == 0) | ||
687 | { | ||
688 | logf("wr chk enc error %lu %lu", | ||
689 | rec_fdata.chunk->enc_size, rec_fdata.chunk->num_pcm); | ||
690 | errors |= PCMREC_E_ENCODER; | ||
691 | } | ||
692 | } /* pcmrec_write_chunk */ | ||
693 | |||
694 | static void pcmrec_end_file(void) | ||
695 | { | ||
696 | /* all data in output buffer for current file will have been | ||
697 | written and encoder can now do any nescessary steps to | ||
698 | finalize the written file */ | ||
699 | size_t enc_size = rec_fdata.new_enc_size; | ||
700 | unsigned long num_pcm = rec_fdata.new_num_pcm; | ||
701 | |||
702 | enc_events_callback(ENC_END_FILE, &rec_fdata); | ||
703 | |||
704 | if (errors == 0) | ||
705 | { | ||
706 | if (rec_fdata.chunk->flags & CHUNKF_ERROR) | ||
707 | { | ||
708 | logf("end file: enc error"); | ||
709 | errors |= PCMREC_E_ENCODER; | ||
710 | } | ||
711 | else | ||
712 | { | ||
713 | pcmrec_update_sizes(enc_size, num_pcm); | ||
714 | } | ||
715 | } | ||
716 | |||
717 | /* Force file close if error */ | ||
718 | if (errors != 0) | ||
719 | pcmrec_close_file(&rec_fdata.rec_file); | ||
720 | |||
721 | rec_fdata.chunk->flags &= ~CHUNKF_END_FILE; | ||
722 | } /* pcmrec_end_file */ | ||
723 | |||
724 | /** | ||
725 | * Update buffer watermarks with spinup time compensation | ||
726 | * | ||
727 | * All this assumes reasonable data rates, chunk sizes and sufficient | ||
728 | * memory for the most part. Some dumb checks are included but perhaps | ||
729 | * are pointless since this all will break down at extreme limits that | ||
730 | * are currently not applicable to any supported device. | ||
731 | */ | ||
732 | static void pcmrec_refresh_watermarks(void) | ||
733 | { | ||
734 | logf("ata spinup: %d", ata_spinup_time); | ||
735 | |||
736 | /* set the low mark for when flushing stops if automatic */ | ||
737 | low_watermark = (LOW_SECONDS*4*sample_rate + (enc_chunk_size-1)) | ||
738 | / enc_chunk_size; | ||
739 | logf("low wmk: %d", low_watermark); | ||
740 | |||
741 | #ifdef HAVE_PRIORITY_SCHEDULING | ||
742 | /* panic boost thread priority if 2 seconds of ground is lost - | ||
743 | this allows encoder to boost with just under a second of | ||
744 | pcm data (if not yet full enough to boost itself) | ||
745 | and not falsely trip the alarm. */ | ||
746 | flood_watermark = enc_num_chunks - | ||
747 | (PANIC_SECONDS*4*sample_rate + (enc_chunk_size-1)) | ||
748 | / enc_chunk_size; | ||
749 | |||
750 | if (flood_watermark < low_watermark) | ||
751 | { | ||
752 | logf("warning: panic < low"); | ||
753 | flood_watermark = low_watermark; | ||
754 | } | ||
755 | |||
756 | logf("flood at: %d", flood_watermark); | ||
757 | #endif | ||
758 | spinup_time = last_ata_spinup_time = ata_spinup_time; | ||
759 | |||
760 | /* write at 8s + st remaining in enc_buffer - range 12s to | ||
761 | 20s total - default to 3.5s spinup. */ | ||
762 | if (spinup_time == 0) | ||
763 | spinup_time = 35*HZ/10; /* default - cozy */ | ||
764 | else if (spinup_time < 2*HZ) | ||
765 | spinup_time = 2*HZ; /* ludicrous - ramdisk? */ | ||
766 | else if (spinup_time > 10*HZ) | ||
767 | spinup_time = 10*HZ; /* do you have a functioning HD? */ | ||
768 | |||
769 | /* try to start writing with 10s remaining after disk spinup */ | ||
770 | high_watermark = enc_num_chunks - | ||
771 | ((FLUSH_SECONDS*HZ + spinup_time)*4*sample_rate + | ||
772 | (enc_chunk_size-1)*HZ) / (enc_chunk_size*HZ); | ||
773 | |||
774 | if (high_watermark < low_watermark) | ||
775 | { | ||
776 | high_watermark = low_watermark; | ||
777 | low_watermark /= 2; | ||
778 | logf("warning: low 'write at'"); | ||
779 | } | ||
780 | |||
781 | logf("write at: %d", high_watermark); | ||
782 | } /* pcmrec_refresh_watermarks */ | ||
783 | |||
784 | /** | ||
785 | * Process the chunks | ||
786 | * | ||
787 | * This function is called when queue_get_w_tmo times out. | ||
788 | * | ||
789 | * Set flush_num to the number of files to flush to disk or to | ||
790 | * a PCMREC_FLUSH_* constant. | ||
791 | */ | ||
792 | static void pcmrec_flush(unsigned flush_num) | ||
793 | { | ||
794 | #ifdef HAVE_PRIORITY_SCHEDULING | ||
795 | static unsigned long last_flush_tick; /* tick when function returned */ | ||
796 | unsigned long start_tick; /* When flush started */ | ||
797 | unsigned long prio_tick; /* Timeout for auto boost */ | ||
798 | int prio_pcmrec; /* Current thread priority for pcmrec */ | ||
799 | int prio_codec; /* Current thread priority for codec */ | ||
800 | #endif | ||
801 | int num_ready; /* Number of chunks ready at start */ | ||
802 | unsigned remaining; /* Number of file starts remaining */ | ||
803 | unsigned chunks_flushed; /* Chunks flushed (for mini flush only) */ | ||
804 | bool interruptable; /* Flush can be interupted */ | ||
805 | |||
806 | num_ready = enc_wr_index - enc_rd_index; | ||
807 | if (num_ready < 0) | ||
808 | num_ready += enc_num_chunks; | ||
809 | |||
810 | /* save interruptable flag and remove it to get the actual count */ | ||
811 | interruptable = (flush_num & PCMREC_FLUSH_INTERRUPTABLE) != 0; | ||
812 | flush_num &= ~PCMREC_FLUSH_INTERRUPTABLE; | ||
813 | |||
814 | if (flush_num == 0) | ||
815 | { | ||
816 | if (!is_recording) | ||
817 | return; | ||
818 | |||
819 | if (ata_spinup_time != last_ata_spinup_time) | ||
820 | pcmrec_refresh_watermarks(); | ||
821 | |||
822 | /* enough available? no? then leave */ | ||
823 | if (num_ready < high_watermark) | ||
824 | return; | ||
825 | } /* endif (flush_num == 0) */ | ||
826 | |||
827 | #ifdef HAVE_PRIORITY_SCHEDULING | ||
828 | start_tick = current_tick; | ||
829 | prio_tick = start_tick + PRIO_SECONDS*HZ + spinup_time; | ||
830 | |||
831 | if (flush_num == 0 && TIME_BEFORE(current_tick, last_flush_tick + HZ/2)) | ||
832 | { | ||
833 | /* if we're getting called too much and this isn't forced, | ||
834 | boost stat by expiring timeout in advance */ | ||
835 | logf("too frequent flush"); | ||
836 | prio_tick = current_tick - 1; | ||
837 | } | ||
838 | |||
839 | prio_pcmrec = -1; | ||
840 | prio_codec = -1; /* GCC is too stoopid to figure out it doesn't | ||
841 | need init */ | ||
842 | #endif | ||
843 | |||
844 | logf("writing:%d(%d):%s%s", num_ready, flush_num, | ||
845 | interruptable ? "i" : "", | ||
846 | flush_num == PCMREC_FLUSH_MINI ? "m" : ""); | ||
847 | |||
848 | cpu_boost(true); | ||
849 | |||
850 | remaining = flush_num; | ||
851 | chunks_flushed = 0; | ||
852 | |||
853 | while (num_ready > 0) | ||
854 | { | ||
855 | /* check current number of encoder chunks */ | ||
856 | int num = enc_wr_index - enc_rd_index; | ||
857 | if (num < 0) | ||
858 | num += enc_num_chunks; | ||
859 | |||
860 | if (num <= low_watermark && | ||
861 | (flush_num == PCMREC_FLUSH_IF_HIGH || num <= 0)) | ||
862 | { | ||
863 | logf("low data: %d", num); | ||
864 | break; /* data remaining is below threshold */ | ||
865 | } | ||
866 | |||
867 | if (interruptable && flush_interrupts > 0) | ||
868 | { | ||
869 | logf("int at: %d", num); | ||
870 | break; /* interrupted */ | ||
871 | } | ||
872 | |||
873 | #ifdef HAVE_PRIORITY_SCHEDULING | ||
874 | if (prio_pcmrec == -1 && (num >= flood_watermark || | ||
875 | TIME_AFTER(current_tick, prio_tick))) | ||
876 | { | ||
877 | /* losing ground or holding without progress - boost | ||
878 | priority until finished */ | ||
879 | logf("pcmrec: boost (%s)", | ||
880 | num >= flood_watermark ? "num" : "time"); | ||
881 | prio_pcmrec = thread_set_priority(NULL, | ||
882 | thread_get_priority(NULL) - 4); | ||
883 | prio_codec = thread_set_priority(codec_thread_p, | ||
884 | thread_get_priority(codec_thread_p) - 4); | ||
885 | } | ||
886 | #endif | ||
887 | |||
888 | rec_fdata.chunk = GET_ENC_CHUNK(enc_rd_index); | ||
889 | rec_fdata.new_enc_size = rec_fdata.chunk->enc_size; | ||
890 | rec_fdata.new_num_pcm = rec_fdata.chunk->num_pcm; | ||
891 | |||
892 | if (rec_fdata.chunk->flags & CHUNKF_START_FILE) | ||
893 | { | ||
894 | pcmrec_start_file(); | ||
895 | if (--remaining == 0) | ||
896 | num_ready = 0; /* stop on next loop - must write this | ||
897 | chunk if it has data */ | ||
898 | } | ||
899 | |||
900 | pcmrec_write_chunk(); | ||
901 | |||
902 | if (rec_fdata.chunk->flags & CHUNKF_END_FILE) | ||
903 | pcmrec_end_file(); | ||
904 | |||
905 | INC_ENC_INDEX(enc_rd_index); | ||
906 | |||
907 | if (errors != 0) | ||
908 | { | ||
909 | pcmrec_end_file(); | ||
910 | break; | ||
911 | } | ||
912 | |||
913 | if (flush_num == PCMREC_FLUSH_MINI && | ||
914 | ++chunks_flushed >= MINI_CHUNKS) | ||
915 | { | ||
916 | logf("mini flush break"); | ||
917 | break; | ||
918 | } | ||
919 | /* no yielding; the file apis called in the codecs do that | ||
920 | sufficiently */ | ||
921 | } /* end while */ | ||
922 | |||
923 | /* sync file */ | ||
924 | if (rec_fdata.rec_file >= 0 && fsync(rec_fdata.rec_file) != 0) | ||
925 | errors |= PCMREC_E_IO; | ||
926 | |||
927 | cpu_boost(false); | ||
928 | |||
929 | #ifdef HAVE_PRIORITY_SCHEDULING | ||
930 | if (prio_pcmrec != -1) | ||
931 | { | ||
932 | /* return to original priorities */ | ||
933 | logf("pcmrec: unboost priority"); | ||
934 | thread_set_priority(NULL, prio_pcmrec); | ||
935 | thread_set_priority(codec_thread_p, prio_codec); | ||
936 | } | ||
937 | |||
938 | last_flush_tick = current_tick; /* save tick when we left */ | ||
939 | #endif | ||
940 | |||
941 | logf("done"); | ||
942 | } /* pcmrec_flush */ | ||
943 | |||
944 | /** | ||
945 | * Marks a new stream in the buffer and gives the encoder a chance for special | ||
946 | * handling of transition from one to the next. The encoder may change the | ||
947 | * chunk that ends the old stream by requesting more chunks and similiarly for | ||
948 | * the new but must always advance the position though the interface. It can | ||
949 | * later reject any data it cares to when writing the file but should mark the | ||
950 | * chunk so it can recognize this. ENC_WRITE_CHUNK event must be able to accept | ||
951 | * a NULL data pointer without error as well. | ||
952 | */ | ||
953 | static int pcmrec_get_chunk_index(struct enc_chunk_hdr *chunk) | ||
954 | { | ||
955 | return ((char *)chunk - (char *)enc_buffer) / enc_chunk_size; | ||
956 | } /* pcmrec_get_chunk_index */ | ||
957 | |||
958 | static struct enc_chunk_hdr * pcmrec_get_prev_chunk(int index) | ||
959 | { | ||
960 | DEC_ENC_INDEX(index); | ||
961 | return GET_ENC_CHUNK(index); | ||
962 | } /* pcmrec_get_prev_chunk */ | ||
963 | |||
964 | static void pcmrec_new_stream(const char *filename, /* next file name */ | ||
965 | unsigned long flags, /* CHUNKF_* flags */ | ||
966 | int pre_index) /* index for prerecorded data */ | ||
967 | { | ||
968 | logf("pcmrec_new_stream"); | ||
969 | char path[MAX_PATH]; /* place to copy filename so sender can be released */ | ||
970 | |||
971 | struct enc_buffer_event_data data; | ||
972 | bool (*fnq_add_fn)(const char *) = NULL; /* function to use to add | ||
973 | new filename */ | ||
974 | struct enc_chunk_hdr *start = NULL; /* pointer to starting chunk of | ||
975 | stream */ | ||
976 | bool did_flush = false; /* did a flush occurr? */ | ||
977 | |||
978 | if (filename) | ||
979 | strncpy(path, filename, MAX_PATH); | ||
980 | queue_reply(&pcmrec_queue, 0); /* We have all we need */ | ||
981 | |||
982 | data.pre_chunk = NULL; | ||
983 | data.chunk = GET_ENC_CHUNK(enc_wr_index); | ||
984 | |||
985 | /* end chunk */ | ||
986 | if (flags & CHUNKF_END_FILE) | ||
987 | { | ||
988 | data.chunk->flags &= CHUNKF_START_FILE | CHUNKF_END_FILE; | ||
989 | |||
990 | if (data.chunk->flags & CHUNKF_START_FILE) | ||
991 | { | ||
992 | /* cannot start and end on same unprocessed chunk */ | ||
993 | logf("file end on start"); | ||
994 | flags &= ~CHUNKF_END_FILE; | ||
995 | } | ||
996 | else if (enc_rd_index == enc_wr_index) | ||
997 | { | ||
998 | /* all data flushed but file not ended - chunk will be left | ||
999 | empty */ | ||
1000 | logf("end on dead end"); | ||
1001 | data.chunk->flags = 0; | ||
1002 | data.chunk->enc_size = 0; | ||
1003 | data.chunk->num_pcm = 0; | ||
1004 | data.chunk->enc_data = NULL; | ||
1005 | INC_ENC_INDEX(enc_wr_index); | ||
1006 | data.chunk = GET_ENC_CHUNK(enc_wr_index); | ||
1007 | } | ||
1008 | else | ||
1009 | { | ||
1010 | struct enc_chunk_hdr *last = pcmrec_get_prev_chunk(enc_wr_index); | ||
1011 | |||
1012 | if (last->flags & CHUNKF_END_FILE) | ||
1013 | { | ||
1014 | /* end already processed and marked - can't end twice */ | ||
1015 | logf("file end again"); | ||
1016 | flags &= ~CHUNKF_END_FILE; | ||
1017 | } | ||
1018 | } | ||
1019 | } | ||
1020 | |||
1021 | /* start chunk */ | ||
1022 | if (flags & CHUNKF_START_FILE) | ||
1023 | { | ||
1024 | bool pre = flags & CHUNKF_PRERECORD; | ||
1025 | |||
1026 | if (pre) | ||
1027 | { | ||
1028 | logf("stream prerecord start"); | ||
1029 | start = data.pre_chunk = GET_ENC_CHUNK(pre_index); | ||
1030 | start->flags &= CHUNKF_START_FILE | CHUNKF_PRERECORD; | ||
1031 | } | ||
1032 | else | ||
1033 | { | ||
1034 | logf("stream normal start"); | ||
1035 | start = data.chunk; | ||
1036 | start->flags &= CHUNKF_START_FILE; | ||
1037 | } | ||
1038 | |||
1039 | /* if encoder hasn't yet processed the last start - abort the start | ||
1040 | of the previous file queued or else it will be empty and invalid */ | ||
1041 | if (start->flags & CHUNKF_START_FILE) | ||
1042 | { | ||
1043 | logf("replacing fnq tail: %s", filename); | ||
1044 | fnq_add_fn = pcmrec_fnq_replace_tail; | ||
1045 | } | ||
1046 | else | ||
1047 | { | ||
1048 | logf("adding filename: %s", filename); | ||
1049 | fnq_add_fn = pcmrec_fnq_add_filename; | ||
1050 | } | ||
1051 | } | ||
1052 | |||
1053 | data.flags = flags; | ||
1054 | pcmrec_context = true; /* switch encoder context */ | ||
1055 | enc_events_callback(ENC_REC_NEW_STREAM, &data); | ||
1056 | pcmrec_context = false; /* switch back */ | ||
1057 | |||
1058 | if (flags & CHUNKF_END_FILE) | ||
1059 | { | ||
1060 | int i = pcmrec_get_chunk_index(data.chunk); | ||
1061 | pcmrec_get_prev_chunk(i)->flags |= CHUNKF_END_FILE; | ||
1062 | } | ||
1063 | |||
1064 | if (start) | ||
1065 | { | ||
1066 | if (!(flags & CHUNKF_PRERECORD)) | ||
1067 | { | ||
1068 | /* get stats on data added to start - sort of a prerecord | ||
1069 | operation */ | ||
1070 | int i = pcmrec_get_chunk_index(data.chunk); | ||
1071 | struct enc_chunk_hdr *chunk = data.chunk; | ||
1072 | |||
1073 | logf("start data: %d %d", i, enc_wr_index); | ||
1074 | |||
1075 | num_rec_bytes = 0; | ||
1076 | num_rec_samples = 0; | ||
1077 | |||
1078 | while (i != enc_wr_index) | ||
1079 | { | ||
1080 | num_rec_bytes += chunk->enc_size; | ||
1081 | num_rec_samples += chunk->num_pcm; | ||
1082 | INC_ENC_INDEX(i); | ||
1083 | chunk = GET_ENC_CHUNK(i); | ||
1084 | } | ||
1085 | |||
1086 | start->flags &= ~CHUNKF_START_FILE; | ||
1087 | start = data.chunk; | ||
1088 | } | ||
1089 | |||
1090 | start->flags |= CHUNKF_START_FILE; | ||
1091 | |||
1092 | /* flush all pending files out if full and adding */ | ||
1093 | if (fnq_add_fn == pcmrec_fnq_add_filename && pcmrec_fnq_is_full()) | ||
1094 | { | ||
1095 | logf("fnq full"); | ||
1096 | pcmrec_flush(PCMREC_FLUSH_ALL); | ||
1097 | did_flush = true; | ||
1098 | } | ||
1099 | |||
1100 | fnq_add_fn(path); | ||
1101 | } | ||
1102 | |||
1103 | /* Make sure to complete any interrupted high watermark */ | ||
1104 | if (!did_flush) | ||
1105 | pcmrec_flush(PCMREC_FLUSH_IF_HIGH); | ||
1106 | } /* pcmrec_new_stream */ | ||
1107 | |||
1108 | /** event handlers for pcmrec thread */ | ||
1109 | |||
1110 | /* PCMREC_INIT */ | ||
1111 | static void pcmrec_init(void) | ||
1112 | { | ||
1113 | unsigned char *buffer; | ||
1114 | |||
1115 | /* warings and errors */ | ||
1116 | warnings = | ||
1117 | errors = 0; | ||
1118 | |||
1119 | pcmrec_close_file(&rec_fdata.rec_file); | ||
1120 | rec_fdata.rec_file = -1; | ||
1121 | |||
1122 | /* pcm FIFO */ | ||
1123 | dma_lock = true; | ||
1124 | pcm_rd_pos = 0; | ||
1125 | dma_wr_pos = 0; | ||
1126 | pcm_enc_pos = 0; | ||
1127 | |||
1128 | /* encoder FIFO */ | ||
1129 | enc_wr_index = 0; | ||
1130 | enc_rd_index = 0; | ||
1131 | |||
1132 | /* filename queue */ | ||
1133 | fnq_rd_pos = 0; | ||
1134 | fnq_wr_pos = 0; | ||
1135 | |||
1136 | /* stats */ | ||
1137 | num_rec_bytes = 0; | ||
1138 | num_rec_samples = 0; | ||
1139 | #if 0 | ||
1140 | accum_rec_bytes = 0; | ||
1141 | accum_pcm_samples = 0; | ||
1142 | #endif | ||
1143 | |||
1144 | pre_record_ticks = 0; | ||
1145 | |||
1146 | is_recording = false; | ||
1147 | is_paused = false; | ||
1148 | |||
1149 | buffer = audio_get_recording_buffer(&rec_buffer_size); | ||
1150 | |||
1151 | /* Line align pcm_buffer 2^4=16 bytes */ | ||
1152 | pcm_buffer = (unsigned char *)ALIGN_UP_P2((uintptr_t)buffer, 4); | ||
1153 | enc_buffer = pcm_buffer + ALIGN_UP_P2(PCM_NUM_CHUNKS*PCM_CHUNK_SIZE + | ||
1154 | PCM_MAX_FEED_SIZE, 2); | ||
1155 | /* Adjust available buffer for possible align advancement */ | ||
1156 | rec_buffer_size -= pcm_buffer - buffer; | ||
1157 | |||
1158 | pcm_init_recording(); | ||
1159 | } /* pcmrec_init */ | ||
1160 | |||
1161 | /* PCMREC_CLOSE */ | ||
1162 | static void pcmrec_close(void) | ||
1163 | { | ||
1164 | dma_lock = true; | ||
1165 | pre_record_ticks = 0; /* Can't be prerecording any more */ | ||
1166 | warnings = 0; | ||
1167 | pcm_close_recording(); | ||
1168 | reset_hardware(); | ||
1169 | audio_remove_encoder(); | ||
1170 | } /* pcmrec_close */ | ||
1171 | |||
1172 | /* PCMREC_OPTIONS */ | ||
1173 | static void pcmrec_set_recording_options( | ||
1174 | struct audio_recording_options *options) | ||
1175 | { | ||
1176 | /* stop DMA transfer */ | ||
1177 | dma_lock = true; | ||
1178 | pcm_stop_recording(); | ||
1179 | |||
1180 | rec_frequency = options->rec_frequency; | ||
1181 | rec_source = options->rec_source; | ||
1182 | num_channels = options->rec_channels == 1 ? 1 : 2; | ||
1183 | rec_mono_mode = options->rec_mono_mode; | ||
1184 | pre_record_ticks = options->rec_prerecord_time * HZ; | ||
1185 | enc_config = options->enc_config; | ||
1186 | enc_config.afmt = rec_format_afmt[enc_config.rec_format]; | ||
1187 | |||
1188 | #ifdef HAVE_SPDIF_IN | ||
1189 | if (rec_source == AUDIO_SRC_SPDIF) | ||
1190 | { | ||
1191 | /* must measure SPDIF sample rate before configuring codecs */ | ||
1192 | unsigned long sr = spdif_measure_frequency(); | ||
1193 | /* round to master list for SPDIF rate */ | ||
1194 | int index = round_value_to_list32(sr, audio_master_sampr_list, | ||
1195 | SAMPR_NUM_FREQ, false); | ||
1196 | sample_rate = audio_master_sampr_list[index]; | ||
1197 | /* round to HW playback rates for monitoring */ | ||
1198 | index = round_value_to_list32(sr, hw_freq_sampr, | ||
1199 | HW_NUM_FREQ, false); | ||
1200 | pcm_set_frequency(hw_freq_sampr[index]); | ||
1201 | /* encoders with a limited number of rates do their own rounding */ | ||
1202 | } | ||
1203 | else | ||
1204 | #endif | ||
1205 | { | ||
1206 | /* set sample rate from frequency selection */ | ||
1207 | sample_rate = rec_freq_sampr[rec_frequency]; | ||
1208 | pcm_set_frequency(sample_rate); | ||
1209 | } | ||
1210 | |||
1211 | /* set monitoring */ | ||
1212 | audio_set_output_source(rec_source); | ||
1213 | |||
1214 | /* apply hardware setting to start monitoring now */ | ||
1215 | pcm_apply_settings(); | ||
1216 | |||
1217 | queue_reply(&pcmrec_queue, 0); /* Release sender */ | ||
1218 | |||
1219 | if (audio_load_encoder(enc_config.afmt)) | ||
1220 | { | ||
1221 | /* start DMA transfer */ | ||
1222 | dma_lock = pre_record_ticks == 0; | ||
1223 | pcm_record_data(pcm_rec_have_more, GET_PCM_CHUNK(dma_wr_pos), | ||
1224 | PCM_CHUNK_SIZE); | ||
1225 | } | ||
1226 | else | ||
1227 | { | ||
1228 | logf("set rec opt: enc load failed"); | ||
1229 | errors |= PCMREC_E_LOAD_ENCODER; | ||
1230 | } | ||
1231 | } /* pcmrec_set_recording_options */ | ||
1232 | |||
1233 | /* PCMREC_RECORD - start recording (not gapless) | ||
1234 | or split stream (gapless) */ | ||
1235 | static void pcmrec_record(const char *filename) | ||
1236 | { | ||
1237 | unsigned long pre_sample_ticks; | ||
1238 | int rd_start; | ||
1239 | unsigned long flags; | ||
1240 | int pre_index; | ||
1241 | |||
1242 | logf("pcmrec_record: %s", filename); | ||
1243 | |||
1244 | /* reset stats */ | ||
1245 | num_rec_bytes = 0; | ||
1246 | num_rec_samples = 0; | ||
1247 | |||
1248 | if (!is_recording) | ||
1249 | { | ||
1250 | #if 0 | ||
1251 | accum_rec_bytes = 0; | ||
1252 | accum_pcm_samples = 0; | ||
1253 | #endif | ||
1254 | warnings = 0; /* reset warnings */ | ||
1255 | |||
1256 | rd_start = enc_wr_index; | ||
1257 | pre_sample_ticks = 0; | ||
1258 | |||
1259 | pcmrec_refresh_watermarks(); | ||
1260 | |||
1261 | if (pre_record_ticks) | ||
1262 | { | ||
1263 | int i = rd_start; | ||
1264 | /* calculate number of available chunks */ | ||
1265 | unsigned long avail_pre_chunks = (enc_wr_index - enc_rd_index + | ||
1266 | enc_num_chunks) % enc_num_chunks; | ||
1267 | /* overflow at 974 seconds of prerecording at 44.1kHz */ | ||
1268 | unsigned long pre_record_sample_ticks = | ||
1269 | enc_sample_rate*pre_record_ticks; | ||
1270 | int pre_chunks = 0; /* Counter to limit prerecorded time to | ||
1271 | prevent flood state at outset */ | ||
1272 | |||
1273 | logf("pre-st: %ld", pre_record_sample_ticks); | ||
1274 | |||
1275 | /* Get exact measure of recorded data as number of samples aren't | ||
1276 | nescessarily going to be the max for each chunk */ | ||
1277 | for (; avail_pre_chunks-- > 0;) | ||
1278 | { | ||
1279 | struct enc_chunk_hdr *chunk; | ||
1280 | unsigned long chunk_sample_ticks; | ||
1281 | |||
1282 | DEC_ENC_INDEX(i); | ||
1283 | |||
1284 | chunk = GET_ENC_CHUNK(i); | ||
1285 | |||
1286 | /* must have data to be counted */ | ||
1287 | if (chunk->enc_data == NULL) | ||
1288 | continue; | ||
1289 | |||
1290 | chunk_sample_ticks = chunk->num_pcm*HZ; | ||
1291 | |||
1292 | rd_start = i; | ||
1293 | pre_sample_ticks += chunk_sample_ticks; | ||
1294 | num_rec_bytes += chunk->enc_size; | ||
1295 | num_rec_samples += chunk->num_pcm; | ||
1296 | pre_chunks++; | ||
1297 | |||
1298 | /* stop here if enough already */ | ||
1299 | if (pre_chunks >= high_watermark || | ||
1300 | pre_sample_ticks >= pre_record_sample_ticks) | ||
1301 | { | ||
1302 | logf("pre-chks: %d", pre_chunks); | ||
1303 | break; | ||
1304 | } | ||
1305 | } | ||
1306 | |||
1307 | #if 0 | ||
1308 | accum_rec_bytes = num_rec_bytes; | ||
1309 | accum_pcm_samples = num_rec_samples; | ||
1310 | #endif | ||
1311 | } | ||
1312 | |||
1313 | enc_rd_index = rd_start; | ||
1314 | |||
1315 | /* filename queue should be empty */ | ||
1316 | if (!pcmrec_fnq_is_empty()) | ||
1317 | { | ||
1318 | logf("fnq: not empty!"); | ||
1319 | pcmrec_fnq_set_empty(); | ||
1320 | } | ||
1321 | |||
1322 | flags = CHUNKF_START_FILE; | ||
1323 | if (pre_sample_ticks > 0) | ||
1324 | flags |= CHUNKF_PRERECORD; | ||
1325 | |||
1326 | pre_index = enc_rd_index; | ||
1327 | |||
1328 | dma_lock = false; | ||
1329 | is_paused = false; | ||
1330 | is_recording = true; | ||
1331 | } | ||
1332 | else | ||
1333 | { | ||
1334 | /* already recording, just split the stream */ | ||
1335 | logf("inserting split"); | ||
1336 | flags = CHUNKF_START_FILE | CHUNKF_END_FILE; | ||
1337 | pre_index = 0; | ||
1338 | } | ||
1339 | |||
1340 | pcmrec_new_stream(filename, flags, pre_index); | ||
1341 | logf("pcmrec_record done"); | ||
1342 | } /* pcmrec_record */ | ||
1343 | |||
1344 | /* PCMREC_STOP */ | ||
1345 | static void pcmrec_stop(void) | ||
1346 | { | ||
1347 | logf("pcmrec_stop"); | ||
1348 | |||
1349 | if (is_recording) | ||
1350 | { | ||
1351 | dma_lock = true; /* lock dma write position */ | ||
1352 | |||
1353 | /* flush all available data first to avoid overflow while waiting | ||
1354 | for encoding to finish */ | ||
1355 | pcmrec_flush(PCMREC_FLUSH_ALL); | ||
1356 | |||
1357 | /* wait for encoder to finish remaining data */ | ||
1358 | while (errors == 0 && !pcm_buffer_empty) | ||
1359 | yield(); | ||
1360 | |||
1361 | /* end stream at last data */ | ||
1362 | pcmrec_new_stream(NULL, CHUNKF_END_FILE, 0); | ||
1363 | |||
1364 | /* flush anything else encoder added */ | ||
1365 | pcmrec_flush(PCMREC_FLUSH_ALL); | ||
1366 | |||
1367 | /* remove any pending file start not yet processed - should be at | ||
1368 | most one at enc_wr_index */ | ||
1369 | pcmrec_fnq_get_filename(NULL); | ||
1370 | /* encoder should abort any chunk it was in midst of processing */ | ||
1371 | GET_ENC_CHUNK(enc_wr_index)->flags = CHUNKF_ABORT; | ||
1372 | |||
1373 | /* filename queue should be empty */ | ||
1374 | if (!pcmrec_fnq_is_empty()) | ||
1375 | { | ||
1376 | logf("fnq: not empty!"); | ||
1377 | pcmrec_fnq_set_empty(); | ||
1378 | } | ||
1379 | |||
1380 | /* be absolutely sure the file is closed */ | ||
1381 | if (errors != 0) | ||
1382 | pcmrec_close_file(&rec_fdata.rec_file); | ||
1383 | rec_fdata.rec_file = -1; | ||
1384 | |||
1385 | is_recording = false; | ||
1386 | is_paused = false; | ||
1387 | dma_lock = pre_record_ticks == 0; | ||
1388 | } | ||
1389 | else | ||
1390 | { | ||
1391 | logf("not recording"); | ||
1392 | } | ||
1393 | |||
1394 | logf("pcmrec_stop done"); | ||
1395 | } /* pcmrec_stop */ | ||
1396 | |||
1397 | /* PCMREC_PAUSE */ | ||
1398 | static void pcmrec_pause(void) | ||
1399 | { | ||
1400 | logf("pcmrec_pause"); | ||
1401 | |||
1402 | if (!is_recording) | ||
1403 | { | ||
1404 | logf("not recording"); | ||
1405 | } | ||
1406 | else if (is_paused) | ||
1407 | { | ||
1408 | logf("already paused"); | ||
1409 | } | ||
1410 | else | ||
1411 | { | ||
1412 | dma_lock = true; | ||
1413 | is_paused = true; | ||
1414 | } | ||
1415 | |||
1416 | logf("pcmrec_pause done"); | ||
1417 | } /* pcmrec_pause */ | ||
1418 | |||
1419 | /* PCMREC_RESUME */ | ||
1420 | static void pcmrec_resume(void) | ||
1421 | { | ||
1422 | logf("pcmrec_resume"); | ||
1423 | |||
1424 | if (!is_recording) | ||
1425 | { | ||
1426 | logf("not recording"); | ||
1427 | } | ||
1428 | else if (!is_paused) | ||
1429 | { | ||
1430 | logf("not paused"); | ||
1431 | } | ||
1432 | else | ||
1433 | { | ||
1434 | is_paused = false; | ||
1435 | is_recording = true; | ||
1436 | dma_lock = false; | ||
1437 | } | ||
1438 | |||
1439 | logf("pcmrec_resume done"); | ||
1440 | } /* pcmrec_resume */ | ||
1441 | |||
1442 | static void pcmrec_thread(void) __attribute__((noreturn)); | ||
1443 | static void pcmrec_thread(void) | ||
1444 | { | ||
1445 | struct queue_event ev; | ||
1446 | |||
1447 | logf("thread pcmrec start"); | ||
1448 | |||
1449 | while(1) | ||
1450 | { | ||
1451 | if (is_recording) | ||
1452 | { | ||
1453 | /* Poll periodically to flush data */ | ||
1454 | queue_wait_w_tmo(&pcmrec_queue, &ev, HZ/5); | ||
1455 | |||
1456 | if (ev.id == SYS_TIMEOUT) | ||
1457 | { | ||
1458 | /* Messages that interrupt this will complete it */ | ||
1459 | pcmrec_flush(PCMREC_FLUSH_IF_HIGH | | ||
1460 | PCMREC_FLUSH_INTERRUPTABLE); | ||
1461 | continue; | ||
1462 | } | ||
1463 | } | ||
1464 | else | ||
1465 | { | ||
1466 | /* Not doing anything - sit and wait for commands */ | ||
1467 | queue_wait(&pcmrec_queue, &ev); | ||
1468 | } | ||
1469 | |||
1470 | switch (ev.id) | ||
1471 | { | ||
1472 | case PCMREC_INIT: | ||
1473 | pcmrec_init(); | ||
1474 | break; | ||
1475 | |||
1476 | case PCMREC_CLOSE: | ||
1477 | pcmrec_close(); | ||
1478 | break; | ||
1479 | |||
1480 | case PCMREC_OPTIONS: | ||
1481 | pcmrec_set_recording_options( | ||
1482 | (struct audio_recording_options *)ev.data); | ||
1483 | break; | ||
1484 | |||
1485 | case PCMREC_RECORD: | ||
1486 | clear_flush_interrupt(); | ||
1487 | pcmrec_record((const char *)ev.data); | ||
1488 | break; | ||
1489 | |||
1490 | case PCMREC_STOP: | ||
1491 | clear_flush_interrupt(); | ||
1492 | pcmrec_stop(); | ||
1493 | break; | ||
1494 | |||
1495 | case PCMREC_PAUSE: | ||
1496 | clear_flush_interrupt(); | ||
1497 | pcmrec_pause(); | ||
1498 | break; | ||
1499 | |||
1500 | case PCMREC_RESUME: | ||
1501 | pcmrec_resume(); | ||
1502 | break; | ||
1503 | #if 0 | ||
1504 | case PCMREC_FLUSH_NUM: | ||
1505 | pcmrec_flush((unsigned)ev.data); | ||
1506 | break; | ||
1507 | #endif | ||
1508 | case SYS_USB_CONNECTED: | ||
1509 | if (is_recording) | ||
1510 | break; | ||
1511 | pcmrec_close(); | ||
1512 | usb_acknowledge(SYS_USB_CONNECTED_ACK); | ||
1513 | usb_wait_for_disconnect(&pcmrec_queue); | ||
1514 | flush_interrupts = 0; | ||
1515 | break; | ||
1516 | } /* end switch */ | ||
1517 | } /* end while */ | ||
1518 | } /* pcmrec_thread */ | ||
1519 | |||
1520 | /****************************************************************************/ | ||
1521 | /* */ | ||
1522 | /* following functions will be called by the encoder codec */ | ||
1523 | /* in a free-threaded manner */ | ||
1524 | /* */ | ||
1525 | /****************************************************************************/ | ||
1526 | |||
1527 | /* pass the encoder settings to the encoder */ | ||
1528 | void enc_get_inputs(struct enc_inputs *inputs) | ||
1529 | { | ||
1530 | inputs->sample_rate = sample_rate; | ||
1531 | inputs->num_channels = num_channels; | ||
1532 | inputs->rec_mono_mode = rec_mono_mode; | ||
1533 | inputs->config = &enc_config; | ||
1534 | } /* enc_get_inputs */ | ||
1535 | |||
1536 | /* set the encoder dimensions (called by encoder codec at initialization and | ||
1537 | termination) */ | ||
1538 | void enc_set_parameters(struct enc_parameters *params) | ||
1539 | { | ||
1540 | size_t bufsize, resbytes; | ||
1541 | |||
1542 | logf("enc_set_parameters"); | ||
1543 | |||
1544 | if (!params) | ||
1545 | { | ||
1546 | logf("reset"); | ||
1547 | /* Encoder is terminating */ | ||
1548 | memset(&enc_config, 0, sizeof (enc_config)); | ||
1549 | enc_sample_rate = 0; | ||
1550 | cancel_cpu_boost(); /* Make sure no boost remains */ | ||
1551 | return; | ||
1552 | } | ||
1553 | |||
1554 | enc_sample_rate = params->enc_sample_rate; | ||
1555 | logf("enc sampr:%lu", enc_sample_rate); | ||
1556 | |||
1557 | pcm_rd_pos = dma_wr_pos; | ||
1558 | pcm_enc_pos = pcm_rd_pos; | ||
1559 | |||
1560 | enc_config.afmt = params->afmt; | ||
1561 | /* addition of the header is always implied - chunk size 4-byte aligned */ | ||
1562 | enc_chunk_size = | ||
1563 | ALIGN_UP_P2(ENC_CHUNK_HDR_SIZE + params->chunk_size, 2); | ||
1564 | enc_events_callback = params->events_callback; | ||
1565 | |||
1566 | logf("chunk size:%lu", enc_chunk_size); | ||
1567 | |||
1568 | /*** Configure the buffers ***/ | ||
1569 | |||
1570 | /* Layout of recording buffer: | ||
1571 | * [ax] = possible alignment x multiple | ||
1572 | * [sx] = possible size alignment of x multiple | ||
1573 | * |[a16]|[s4]:PCM Buffer+PCM Guard|[s4 each]:Encoder Chunks|-> | ||
1574 | * |[[s4]:Reserved Bytes]|Filename Queue->|[space]| | ||
1575 | */ | ||
1576 | resbytes = ALIGN_UP_P2(params->reserve_bytes, 2); | ||
1577 | logf("resbytes:%lu", resbytes); | ||
1578 | |||
1579 | bufsize = rec_buffer_size - (enc_buffer - pcm_buffer) - | ||
1580 | resbytes - FNQ_MIN_NUM_PATHS*MAX_PATH | ||
1581 | #ifdef DEBUG | ||
1582 | - sizeof (*wrap_id_p) | ||
1583 | #endif | ||
1584 | ; | ||
1585 | |||
1586 | enc_num_chunks = bufsize / enc_chunk_size; | ||
1587 | logf("num chunks:%d", enc_num_chunks); | ||
1588 | |||
1589 | /* get real amount used by encoder chunks */ | ||
1590 | bufsize = enc_num_chunks*enc_chunk_size; | ||
1591 | logf("enc size:%lu", bufsize); | ||
1592 | |||
1593 | #ifdef DEBUG | ||
1594 | /* add magic at wraparound for spillover checks */ | ||
1595 | wrap_id_p = SKIPBYTES((unsigned long *)enc_buffer, bufsize); | ||
1596 | bufsize += sizeof (*wrap_id_p); | ||
1597 | *wrap_id_p = ENC_CHUNK_MAGIC; | ||
1598 | #endif | ||
1599 | |||
1600 | /** set OUT parameters **/ | ||
1601 | params->enc_buffer = enc_buffer; | ||
1602 | params->buf_chunk_size = enc_chunk_size; | ||
1603 | params->num_chunks = enc_num_chunks; | ||
1604 | |||
1605 | /* calculate reserve buffer start and return pointer to encoder */ | ||
1606 | params->reserve_buffer = NULL; | ||
1607 | if (resbytes > 0) | ||
1608 | { | ||
1609 | params->reserve_buffer = enc_buffer + bufsize; | ||
1610 | bufsize += resbytes; | ||
1611 | } | ||
1612 | |||
1613 | /* place filename queue at end of buffer using up whatever remains */ | ||
1614 | fnq_rd_pos = 0; /* reset */ | ||
1615 | fnq_wr_pos = 0; /* reset */ | ||
1616 | fn_queue = enc_buffer + bufsize; | ||
1617 | fnq_size = pcm_buffer + rec_buffer_size - fn_queue; | ||
1618 | fnq_size /= MAX_PATH; | ||
1619 | if (fnq_size > FNQ_MAX_NUM_PATHS) | ||
1620 | fnq_size = FNQ_MAX_NUM_PATHS; | ||
1621 | fnq_size *= MAX_PATH; | ||
1622 | logf("fnq files:%ld", fnq_size / MAX_PATH); | ||
1623 | |||
1624 | #if defined(DEBUG) | ||
1625 | logf("ab :%08lX", (uintptr_t)audiobuf); | ||
1626 | logf("pcm:%08lX", (uintptr_t)pcm_buffer); | ||
1627 | logf("enc:%08lX", (uintptr_t)enc_buffer); | ||
1628 | logf("res:%08lX", (uintptr_t)params->reserve_buffer); | ||
1629 | logf("wip:%08lX", (uintptr_t)wrap_id_p); | ||
1630 | logf("fnq:%08lX", (uintptr_t)fn_queue); | ||
1631 | logf("end:%08lX", (uintptr_t)fn_queue + fnq_size); | ||
1632 | logf("abe:%08lX", (uintptr_t)audiobufend); | ||
1633 | #endif | ||
1634 | |||
1635 | /* init all chunk headers and reset indexes */ | ||
1636 | enc_rd_index = 0; | ||
1637 | for (enc_wr_index = enc_num_chunks; enc_wr_index > 0; ) | ||
1638 | { | ||
1639 | struct enc_chunk_hdr *chunk = GET_ENC_CHUNK(--enc_wr_index); | ||
1640 | #ifdef DEBUG | ||
1641 | chunk->id = ENC_CHUNK_MAGIC; | ||
1642 | #endif | ||
1643 | chunk->flags = 0; | ||
1644 | } | ||
1645 | |||
1646 | logf("enc_set_parameters done"); | ||
1647 | } /* enc_set_parameters */ | ||
1648 | |||
1649 | /* return encoder chunk at current write position - | ||
1650 | NOTE: can be called by pcmrec thread when splitting streams */ | ||
1651 | struct enc_chunk_hdr * enc_get_chunk(void) | ||
1652 | { | ||
1653 | struct enc_chunk_hdr *chunk = GET_ENC_CHUNK(enc_wr_index); | ||
1654 | |||
1655 | #ifdef DEBUG | ||
1656 | if (chunk->id != ENC_CHUNK_MAGIC || *wrap_id_p != ENC_CHUNK_MAGIC) | ||
1657 | { | ||
1658 | errors |= PCMREC_E_CHUNK_OVF; | ||
1659 | logf("finish chk ovf: %d", enc_wr_index); | ||
1660 | } | ||
1661 | #endif | ||
1662 | |||
1663 | chunk->flags &= CHUNKF_START_FILE; | ||
1664 | |||
1665 | if (!is_recording) | ||
1666 | chunk->flags |= CHUNKF_PRERECORD; | ||
1667 | |||
1668 | return chunk; | ||
1669 | } /* enc_get_chunk */ | ||
1670 | |||
1671 | /* releases the current chunk into the available chunks - | ||
1672 | NOTE: can be called by pcmrec thread when splitting streams */ | ||
1673 | void enc_finish_chunk(void) | ||
1674 | { | ||
1675 | struct enc_chunk_hdr *chunk = GET_ENC_CHUNK(enc_wr_index); | ||
1676 | |||
1677 | if ((long)chunk->flags < 0) | ||
1678 | { | ||
1679 | /* encoder set error flag */ | ||
1680 | errors |= PCMREC_E_ENCODER; | ||
1681 | logf("finish chk enc error"); | ||
1682 | } | ||
1683 | |||
1684 | /* advance enc_wr_index to the next encoder chunk */ | ||
1685 | INC_ENC_INDEX(enc_wr_index); | ||
1686 | |||
1687 | if (enc_rd_index != enc_wr_index) | ||
1688 | { | ||
1689 | num_rec_bytes += chunk->enc_size; | ||
1690 | num_rec_samples += chunk->num_pcm; | ||
1691 | #if 0 | ||
1692 | accum_rec_bytes += chunk->enc_size; | ||
1693 | accum_pcm_samples += chunk->num_pcm; | ||
1694 | #endif | ||
1695 | } | ||
1696 | else if (is_recording) /* buffer full */ | ||
1697 | { | ||
1698 | /* keep current position and put up warning flag */ | ||
1699 | warnings |= PCMREC_W_ENC_BUFFER_OVF; | ||
1700 | logf("enc_buffer ovf"); | ||
1701 | DEC_ENC_INDEX(enc_wr_index); | ||
1702 | if (pcmrec_context) | ||
1703 | { | ||
1704 | /* if stream splitting, keep this out of circulation and | ||
1705 | flush a small number, then readd - cannot risk losing | ||
1706 | stream markers */ | ||
1707 | logf("mini flush"); | ||
1708 | pcmrec_flush(PCMREC_FLUSH_MINI); | ||
1709 | INC_ENC_INDEX(enc_wr_index); | ||
1710 | } | ||
1711 | } | ||
1712 | else | ||
1713 | { | ||
1714 | /* advance enc_rd_index for prerecording */ | ||
1715 | INC_ENC_INDEX(enc_rd_index); | ||
1716 | } | ||
1717 | } /* enc_finish_chunk */ | ||
1718 | |||
1719 | /* passes a pointer to next chunk of unprocessed wav data */ | ||
1720 | /* TODO: this really should give the actual size returned */ | ||
1721 | unsigned char * enc_get_pcm_data(size_t size) | ||
1722 | { | ||
1723 | int wp = dma_wr_pos; | ||
1724 | size_t avail = (wp - pcm_rd_pos) & PCM_CHUNK_MASK; | ||
1725 | |||
1726 | /* limit the requested pcm data size */ | ||
1727 | if (size > PCM_MAX_FEED_SIZE) | ||
1728 | size = PCM_MAX_FEED_SIZE; | ||
1729 | |||
1730 | if (avail >= size) | ||
1731 | { | ||
1732 | unsigned char *ptr = pcm_buffer + pcm_rd_pos; | ||
1733 | int next_pos = (pcm_rd_pos + size) & PCM_CHUNK_MASK; | ||
1734 | |||
1735 | pcm_enc_pos = pcm_rd_pos; | ||
1736 | pcm_rd_pos = next_pos; | ||
1737 | |||
1738 | /* ptr must point to continous data at wraparound position */ | ||
1739 | if ((size_t)pcm_rd_pos < size) | ||
1740 | { | ||
1741 | memcpy(pcm_buffer + PCM_NUM_CHUNKS*PCM_CHUNK_SIZE, | ||
1742 | pcm_buffer, pcm_rd_pos); | ||
1743 | } | ||
1744 | |||
1745 | if (avail >= (sample_rate << 2)) | ||
1746 | { | ||
1747 | /* Filling up - boost codec */ | ||
1748 | trigger_cpu_boost(); | ||
1749 | } | ||
1750 | |||
1751 | pcm_buffer_empty = false; | ||
1752 | return ptr; | ||
1753 | } | ||
1754 | |||
1755 | /* not enough data available - encoder should idle */ | ||
1756 | pcm_buffer_empty = true; | ||
1757 | |||
1758 | cancel_cpu_boost(); | ||
1759 | |||
1760 | /* Sleep long enough to allow one frame on average */ | ||
1761 | sleep(0); | ||
1762 | |||
1763 | return NULL; | ||
1764 | } /* enc_get_pcm_data */ | ||
1765 | |||
1766 | /* puts some pcm data back in the queue */ | ||
1767 | size_t enc_unget_pcm_data(size_t size) | ||
1768 | { | ||
1769 | int wp = dma_wr_pos; | ||
1770 | size_t old_avail = ((pcm_rd_pos - wp) & PCM_CHUNK_MASK) - | ||
1771 | 2*PCM_CHUNK_SIZE; | ||
1772 | |||
1773 | /* allow one interrupt to occur during this call and not have the | ||
1774 | new read position inside the DMA destination chunk */ | ||
1775 | if ((ssize_t)old_avail > 0) | ||
1776 | { | ||
1777 | /* limit size to amount of old data remaining */ | ||
1778 | if (size > old_avail) | ||
1779 | size = old_avail; | ||
1780 | |||
1781 | pcm_enc_pos = (pcm_rd_pos - size) & PCM_CHUNK_MASK; | ||
1782 | pcm_rd_pos = pcm_enc_pos; | ||
1783 | |||
1784 | return size; | ||
1785 | } | ||
1786 | |||
1787 | return 0; | ||
1788 | } /* enc_unget_pcm_data */ | ||
diff --git a/apps/recorder/pcm_record.h b/apps/recorder/pcm_record.h new file mode 100644 index 0000000000..f805313fe5 --- /dev/null +++ b/apps/recorder/pcm_record.h | |||
@@ -0,0 +1,66 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2005 by Linus Nielsen Feltzing | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #ifndef PCM_RECORD_H | ||
23 | #define PCM_RECORD_H | ||
24 | |||
25 | #define DMA_REC_ERROR_DMA (-1) | ||
26 | #ifdef HAVE_SPDIF_REC | ||
27 | #define DMA_REC_ERROR_SPDIF (-2) | ||
28 | #endif | ||
29 | |||
30 | /** Warnings **/ | ||
31 | /* pcm (dma) buffer has overflowed */ | ||
32 | #define PCMREC_W_PCM_BUFFER_OVF 0x00000001 | ||
33 | /* encoder output buffer has overflowed */ | ||
34 | #define PCMREC_W_ENC_BUFFER_OVF 0x00000002 | ||
35 | /** Errors **/ | ||
36 | /* failed to load encoder */ | ||
37 | #define PCMREC_E_LOAD_ENCODER 0x80001000 | ||
38 | /* error originating in encoder */ | ||
39 | #define PCMREC_E_ENCODER 0x80002000 | ||
40 | /* filename queue has desynced from stream markers */ | ||
41 | #define PCMREC_E_FNQ_DESYNC 0x80004000 | ||
42 | /* I/O error has occurred */ | ||
43 | #define PCMREC_E_IO 0x80008000 | ||
44 | #ifdef DEBUG | ||
45 | /* encoder has written past end of allotted space */ | ||
46 | #define PCMREC_E_CHUNK_OVF 0x80010000 | ||
47 | #endif /* DEBUG */ | ||
48 | |||
49 | /** General functions for high level codec recording **/ | ||
50 | /* pcm_rec_error_clear is deprecated for general use. audio_error_clear | ||
51 | should be used */ | ||
52 | void pcm_rec_error_clear(void); | ||
53 | /* pcm_rec_status is deprecated for general use. audio_status merges the | ||
54 | results for consistency with the hardware codec version */ | ||
55 | unsigned long pcm_rec_status(void); | ||
56 | unsigned long pcm_rec_get_warnings(void); | ||
57 | void pcm_rec_init(void); | ||
58 | int pcm_rec_current_bitrate(void); | ||
59 | int pcm_rec_encoder_afmt(void); /* AFMT_* value, AFMT_UNKNOWN if none */ | ||
60 | int pcm_rec_rec_format(void); /* Format index or -1 otherwise */ | ||
61 | unsigned long pcm_rec_sample_rate(void); | ||
62 | int pcm_get_num_unprocessed(void); | ||
63 | |||
64 | /* audio.h contains audio_* recording functions */ | ||
65 | |||
66 | #endif /* PCM_RECORD_H */ | ||
diff --git a/apps/recorder/recording.c b/apps/recorder/recording.c index 6f5c7df09a..d29db390c7 100644 --- a/apps/recorder/recording.c +++ b/apps/recorder/recording.c | |||
@@ -40,6 +40,7 @@ | |||
40 | #include "spdif.h" | 40 | #include "spdif.h" |
41 | #endif | 41 | #endif |
42 | #endif /* CONFIG_CODEC == SWCODEC */ | 42 | #endif /* CONFIG_CODEC == SWCODEC */ |
43 | #include "pcm_record.h" | ||
43 | #include "recording.h" | 44 | #include "recording.h" |
44 | #include "mp3_playback.h" | 45 | #include "mp3_playback.h" |
45 | #include "mas.h" | 46 | #include "mas.h" |
diff --git a/apps/replaygain.h b/apps/replaygain.h new file mode 100644 index 0000000000..dbc079b1d3 --- /dev/null +++ b/apps/replaygain.h | |||
@@ -0,0 +1,33 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2005 Magnus Holmgren | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #ifndef _REPLAYGAIN_H | ||
23 | #define _REPLAYGAIN_H | ||
24 | |||
25 | #include "id3.h" | ||
26 | |||
27 | long get_replaygain_int(long int_gain); | ||
28 | long parse_replaygain(const char* key, const char* value, | ||
29 | struct mp3entry* entry, char* buffer, int length); | ||
30 | long parse_replaygain_int(bool album, long gain, long peak, | ||
31 | struct mp3entry* entry, char* buffer, int length); | ||
32 | |||
33 | #endif | ||