Merge tag 'upstream/1.4' into master-v1.4
Chris Boot
10 years ago
0 | Version 1.0 - 15/2/13 | |
1 | ===================== | |
2 | - initial release | |
3 | ||
4 | Version 1.1 - 12/4/13 | |
5 | ===================== | |
6 | ||
7 | Minor changes | |
8 | - add timeout on slimproto connection to detect dead server | |
9 | - fix issue with clipping on windows by disabling portaudio dither | |
10 | - silence alsa error messages on linux alsa builds unless debugging is enabled | |
11 | - hide some additional error messages unless debuging is enabled so usb dacs produce less error messages when turned off and on | |
12 | ||
13 | Version 1.2 - 6/7/13 | |
14 | ==================== | |
15 | ||
16 | Features | |
17 | - support of upsampling via libsoxr | |
18 | ||
19 | Minor changes | |
20 | - command line option for setting the service address now requires "-s" before the server address | |
21 | - fixes a bug where the channels could become swapped when using S16_LE ALSA output | |
22 | - falls back to polling for a new server if one is not found for more than 30 seconds | |
23 | - fixes play of wav/aiff local files when the LocalPlayer plugin is active | |
24 | ||
25 | Version 1.3 - 6/10/13 | |
26 | ===================== | |
27 | ||
28 | Features | |
29 | - support for wma/alac decode via ffmpeg library (requires compilation with -DFFMPEG) | |
30 | - support for export of audio data to jivelite to enable visulizations on linux (requires compilation with -DVISEXPORT) | |
31 | ||
32 | Minor changes | |
33 | - support async as well as sync resampling rates | |
34 | - support on/off of audio device with portaudio | |
35 | - improved gapless support for aac/mad when skipping to mid track (based on patches from Wouter Ellenbroek) | |
36 | - various bug fixes | |
37 | ||
38 | Version 1.3.1 - 25/11/13 | |
39 | ======================== | |
40 | ||
41 | Minor changes | |
42 | - support of compile time linking for distro packaging, uses -DLINKALL option | |
43 | ||
44 | Version 1.4 (beta) | |
45 | ================== | |
46 | ||
47 | Features | |
48 | - native support of dsd playback to dop capable dac or via conversion to pcm and resampling | |
49 | - support dop in flac playback to dop dacs | |
50 | - support of output to stdout | |
51 | ||
52 | Minor changes | |
53 | - support of resampling only when sample rate is not natively supported | |
54 | - fix problem with libmpg123 playback not playing to end of track | |
55 | - add ablity for player name change to be stored locally in a file (to emulate hardware where name is stored on player) | |
56 |
2 | 2 | LDFLAGS ?= -lasound -lpthread -lm -lrt |
3 | 3 | EXECUTABLE ?= squeezelite |
4 | 4 | |
5 | SOURCES = main.c slimproto.c utils.c output.c buffer.c stream.c decode.c process.c resample.c flac.c pcm.c mad.c vorbis.c faad.c mpg.c ffmpeg.c | |
6 | DEPS = squeezelite.h slimproto.h | |
5 | # passing one or more of these in $(OPTS) enables optional feature inclusion | |
6 | OPT_DSD = -DDSD | |
7 | OPT_FF = -DFFMPEG | |
8 | OPT_LINKALL = -DLINKALL | |
9 | OPT_RESAMPLE= -DRESAMPLE | |
10 | OPT_VIS = -DVISEXPORT | |
7 | 11 | |
8 | UNAME = $(shell uname -s) | |
12 | SOURCES = \ | |
13 | main.c slimproto.c buffer.c stream.c utils.c \ | |
14 | output.c output_alsa.c output_pa.c output_stdout.c output_pack.c decode.c \ | |
15 | flac.c pcm.c mad.c vorbis.c faad.c mpg.c | |
9 | 16 | |
10 | ifneq (,$(findstring -DLINKALL, $(CFLAGS))) | |
11 | LDFLAGS += -lFLAC -lmad -lvorbisfile -lfaad -lmpg123 | |
12 | ifneq (,$(findstring -DFFMPEG, $(CFLAGS))) | |
13 | LDFLAGS += -lavcodec -lavformat -lavutil | |
17 | SOURCES_DSD = dsd.c dop.c dsd2pcm/dsd2pcm.c | |
18 | SOURCES_FF = ffmpeg.c | |
19 | SOURCES_RESAMPLE = process.c resample.c | |
20 | SOURCES_VIS = output_vis.c | |
21 | ||
22 | LINK_LINUX = -ldl | |
23 | ||
24 | LINKALL = -lFLAC -lmad -lvorbisfile -lfaad -lmpg123 | |
25 | LINKALL_FF = -lavcodec -lavformat -lavutil | |
26 | LINKALL_RESAMPLE = -lsoxr | |
27 | ||
28 | DEPS = squeezelite.h slimproto.h | |
29 | ||
30 | UNAME = $(shell uname -s) | |
31 | ||
32 | # add optional sources | |
33 | ifneq (,$(findstring $(OPT_DSD), $(CFLAGS))) | |
34 | SOURCES += $(SOURCES_DSD) | |
14 | 35 | endif |
15 | ifneq (,$(findstring -DRESAMPLE, $(CFLAGS))) | |
16 | LDFLAGS += -lsoxr | |
36 | ifneq (,$(findstring $(OPT_FF), $(CFLAGS))) | |
37 | SOURCES += $(SOURCES_FF) | |
38 | endif | |
39 | ifneq (,$(findstring $(OPT_RESAMPLE), $(CFLAGS))) | |
40 | SOURCES += $(SOURCES_RESAMPLE) | |
41 | endif | |
42 | ifneq (,$(findstring $(OPT_VIS), $(CFLAGS))) | |
43 | SOURCES += $(SOURCES_VIS) | |
44 | endif | |
45 | ||
46 | # add optional link options | |
47 | ifneq (,$(findstring $(OPT_LINKALL), $(CFLAGS))) | |
48 | LDFLAGS += $(LINKALL) | |
49 | ifneq (,$(findstring $(OPT_FF), $(CFLAGS))) | |
50 | LDFLAGS += $(LINKALL_FF) | |
51 | endif | |
52 | ifneq (,$(findstring $(OPT_RESAMPLE), $(CFLAGS))) | |
53 | LDFLAGS += $(LINKALL_RESAMPLE) | |
17 | 54 | endif |
18 | 55 | else |
19 | # if not LINKALL and linux add -ldl | |
56 | # if not LINKALL and linux add LINK_LINUX | |
20 | 57 | ifeq ($(UNAME), Linux) |
21 | LDFLAGS += -ldl | |
58 | LDFLAGS += $(LINK_LINUX) | |
22 | 59 | endif |
23 | 60 | endif |
24 | 61 | |
32 | 69 | $(OBJECTS): $(DEPS) |
33 | 70 | |
34 | 71 | .c.o: |
35 | $(CC) $(CFLAGS) $< -c -o $@ | |
72 | $(CC) $(CFLAGS) $(CPPFLAGS) $< -c -o $@ | |
36 | 73 | |
37 | 74 | clean: |
38 | 75 | rm -f $(OBJECTS) $(EXECUTABLE) |
128 | 128 | LOG_INFO("init decode"); |
129 | 129 | |
130 | 130 | // register codecs |
131 | // alc,wma,wmap,wmal,aac,spt,ogg,ogf,flc,aif,pcm,mp3 | |
131 | // dsf,dff,alc,wma,wmap,wmal,aac,spt,ogg,ogf,flc,aif,pcm,mp3 | |
132 | 132 | i = 0; |
133 | #if DSD | |
134 | if (!opt || strstr(opt, "dsd")) codecs[i++] = register_dsd(); | |
135 | #endif | |
133 | 136 | #if FFMPEG |
134 | 137 | if (!opt || strstr(opt, "alac")) codecs[i++] = register_ff("alc"); |
135 | 138 | if (!opt || strstr(opt, "wma")) codecs[i++] = register_ff("wma"); |
190 | 193 | UNLOCK_D; |
191 | 194 | } |
192 | 195 | |
193 | unsigned decode_newstream(unsigned sample_rate, unsigned max_sample_rate) { | |
196 | unsigned decode_newstream(unsigned sample_rate, unsigned supported_rates[]) { | |
194 | 197 | |
195 | 198 | MAY_PROCESS( |
196 | 199 | if (decode.process) { |
197 | return process_newstream(&decode.direct, sample_rate, max_sample_rate); | |
200 | return process_newstream(&decode.direct, sample_rate, supported_rates); | |
198 | 201 | } |
199 | 202 | ); |
200 | 203 | |
221 | 224 | if (codecs[i] && codecs[i]->id == format) { |
222 | 225 | |
223 | 226 | if (codec && codec != codecs[i]) { |
224 | LOG_INFO("closing codec"); | |
227 | LOG_INFO("closing codec: '%c'", codec->id); | |
225 | 228 | codec->close(); |
226 | 229 | } |
227 | 230 |
0 | /* | |
1 | * Squeezelite - lightweight headless squeezebox emulator | |
2 | * | |
3 | * (c) Adrian Smith 2012, 2013, triode1@btinternet.com | |
4 | * | |
5 | * This program is free software: you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation, either version 3 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | * | |
18 | */ | |
19 | ||
20 | // DSP over PCM (DOP) specific functions | |
21 | ||
22 | #include "squeezelite.h" | |
23 | ||
24 | #if DSD | |
25 | ||
26 | extern struct buffer *outputbuf; | |
27 | extern struct outputstate output; | |
28 | ||
29 | #define LOCK_O mutex_lock(outputbuf->mutex) | |
30 | #define UNLOCK_O mutex_unlock(outputbuf->mutex) | |
31 | ||
32 | // check for 32 dop marker frames to see if this is dop in flac | |
33 | // dop is always encoded in 24 bit samples with marker 0x0005xxxx or 0x00FAxxxx | |
34 | bool is_flac_dop(u32_t *lptr, u32_t *rptr, frames_t frames) { | |
35 | int matched = 0; | |
36 | u32_t next = 0; | |
37 | ||
38 | while (frames--) { | |
39 | if (((*lptr & 0x00FF0000) == 0x00050000 && (*rptr & 0x00FF0000) == 0x00050000) || | |
40 | ((*lptr & 0x00FF0000) == 0x00FA0000 && (*rptr & 0x00FF0000) == 0x00FA0000)) { | |
41 | if (*lptr >> 24 == next) { | |
42 | matched++; | |
43 | next = ( 0x05 + 0xFA ) - next; | |
44 | } else { | |
45 | next = *lptr >> 24; | |
46 | matched = 1; | |
47 | } | |
48 | } else { | |
49 | return false; | |
50 | } | |
51 | if (matched == 32) { | |
52 | return true; | |
53 | } | |
54 | ||
55 | ++lptr; ++rptr; | |
56 | } | |
57 | return false; | |
58 | } | |
59 | ||
60 | // update the dop marker for frames in the output buffer | |
61 | // performaned on all output including silence to maintain marker phase consitency | |
62 | void update_dop_marker(u32_t *ptr, frames_t frames) { | |
63 | static u32_t marker = 0x05; | |
64 | while (frames--) { | |
65 | u32_t scaled_marker = marker << 24; | |
66 | *ptr = (*ptr & 0x00FFFFFF) | scaled_marker; | |
67 | ++ptr; | |
68 | *ptr = (*ptr & 0x00FFFFFF) | scaled_marker; | |
69 | ++ptr; | |
70 | marker = ( 0x05 + 0xFA ) - marker; | |
71 | } | |
72 | } | |
73 | ||
74 | // fill silence buffer with 10101100 which represents dop silence | |
75 | // leave marker zero it will be updated at output, leave lsb zero | |
76 | void dop_silence_frames(u32_t *ptr, frames_t frames) { | |
77 | while (frames--) { | |
78 | *ptr++ = 0x00ACAC00; | |
79 | *ptr++ = 0x00ACAC00; | |
80 | } | |
81 | } | |
82 | ||
83 | void dop_init(bool enable) { | |
84 | LOCK_O; | |
85 | output.has_dop = enable; | |
86 | UNLOCK_O; | |
87 | } | |
88 | ||
89 | #endif // DSD |
0 | /* | |
1 | * Squeezelite - lightweight headless squeezebox emulator | |
2 | * | |
3 | * (c) Adrian Smith 2012, 2013, triode1@btinternet.com | |
4 | * | |
5 | * This program is free software: you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation, either version 3 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | * | |
18 | */ | |
19 | ||
20 | // dsd support | |
21 | ||
22 | #include "squeezelite.h" | |
23 | ||
24 | #if DSD | |
25 | ||
26 | // use dsd2pcm from Sebastian Gesemann for conversion to pcm: | |
27 | #include "./dsd2pcm/dsd2pcm.h" | |
28 | ||
29 | extern log_level loglevel; | |
30 | ||
31 | extern struct buffer *streambuf; | |
32 | extern struct buffer *outputbuf; | |
33 | extern struct streamstate stream; | |
34 | extern struct outputstate output; | |
35 | extern struct decodestate decode; | |
36 | extern struct processstate process; | |
37 | ||
38 | #define LOCK_S mutex_lock(streambuf->mutex) | |
39 | #define UNLOCK_S mutex_unlock(streambuf->mutex) | |
40 | #define LOCK_O mutex_lock(outputbuf->mutex) | |
41 | #define UNLOCK_O mutex_unlock(outputbuf->mutex) | |
42 | #if PROCESS | |
43 | #define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex) | |
44 | #define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex) | |
45 | #define LOCK_O_not_direct if (!decode.direct) mutex_lock(outputbuf->mutex) | |
46 | #define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex) | |
47 | #define IF_DIRECT(x) if (decode.direct) { x } | |
48 | #define IF_PROCESS(x) if (!decode.direct) { x } | |
49 | #else | |
50 | #define LOCK_O_direct mutex_lock(outputbuf->mutex) | |
51 | #define UNLOCK_O_direct mutex_unlock(outputbuf->mutex) | |
52 | #define LOCK_O_not_direct | |
53 | #define UNLOCK_O_not_direct | |
54 | #define IF_DIRECT(x) { x } | |
55 | #define IF_PROCESS(x) | |
56 | #endif | |
57 | ||
58 | #define BLOCK 4096 // expected size of dsd block | |
59 | #define BLOCK_FRAMES BLOCK * BYTES_PER_FRAME | |
60 | #define WRAP_BUF_SIZE 16 | |
61 | ||
62 | typedef enum { UNKNOWN=0, DSF, DSDIFF } dsd_type; | |
63 | ||
64 | static bool dop = false; // local copy of output.has_dop to avoid holding output lock | |
65 | ||
66 | struct dsd { | |
67 | dsd_type type; | |
68 | u32_t consume; | |
69 | u32_t sample_rate; | |
70 | u32_t channels; | |
71 | u64_t sample_bytes; | |
72 | u32_t block_size; | |
73 | bool lsb_first; | |
74 | dsd2pcm_ctx *dsd2pcm_ctx[2]; | |
75 | float *transfer[2]; | |
76 | }; | |
77 | ||
78 | static struct dsd *d; | |
79 | ||
80 | static u64_t unpack64be(const u8_t *p) { | |
81 | return | |
82 | (u64_t)p[0] << 56 | (u64_t)p[1] << 48 | (u64_t)p[2] << 40 | (u64_t)p[3] << 32 | | |
83 | (u64_t)p[4] << 24 | (u64_t)p[5] << 16 | (u64_t)p[6] << 8 | (u64_t)p[7]; | |
84 | } | |
85 | ||
86 | static u64_t unpack64le(const u8_t *p) { | |
87 | return | |
88 | (u64_t)p[7] << 56 | (u64_t)p[6] << 48 | (u64_t)p[5] << 40 | (u64_t)p[4] << 32 | | |
89 | (u64_t)p[3] << 24 | (u64_t)p[2] << 16 | (u64_t)p[1] << 8 | (u64_t)p[0]; | |
90 | } | |
91 | ||
92 | static u32_t unpack32le(const u8_t *p) { | |
93 | return | |
94 | (u32_t)p[3] << 24 | (u32_t)p[2] << 16 | (u32_t)p[1] << 8 | (u32_t)p[0]; | |
95 | } | |
96 | ||
97 | static int _read_header(void) { | |
98 | unsigned bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); | |
99 | s32_t consume; | |
100 | ||
101 | if (!d->type && bytes >= 4) { | |
102 | if (!memcmp(streambuf->readp, "FRM8", 4)) { | |
103 | d->type = DSDIFF; | |
104 | } else if (!memcmp(streambuf->readp, "DSD ", 4)) { | |
105 | d->type = DSF; | |
106 | } else { | |
107 | LOG_WARN("bad type"); | |
108 | return -1; | |
109 | } | |
110 | } | |
111 | ||
112 | while (bytes >= 16) { | |
113 | char id[5]; | |
114 | u64_t len = d->type == DSDIFF ? unpack64be(streambuf->readp + 4) : unpack64le(streambuf->readp + 4); | |
115 | memcpy(id, streambuf->readp, 4); | |
116 | id[4] = '\0'; | |
117 | consume = 0; | |
118 | ||
119 | if (d->type == DSDIFF) { | |
120 | if (!strcmp(id, "FRM8")) { | |
121 | if (!memcmp(streambuf->readp + 12, "DSD ", 4)) { | |
122 | consume = 16; // read into | |
123 | } else { | |
124 | LOG_WARN("bad dsdiff FRM8"); | |
125 | return -1; | |
126 | } | |
127 | } | |
128 | if (!strcmp(id, "PROP") && !memcmp(streambuf->readp + 12, "SND ", 4)) { | |
129 | consume = 16; // read into | |
130 | } | |
131 | if (!strcmp(id, "FVER")) { | |
132 | LOG_INFO("DSDIFF version: %u.%u.%u.%u", *(streambuf->readp + 12), *(streambuf->readp + 13), | |
133 | *(streambuf->readp + 14), *(streambuf->readp + 15)); | |
134 | } | |
135 | if (!strcmp(id, "FS ")) { | |
136 | d->sample_rate = unpackN((void *)(streambuf->readp + 12)); | |
137 | LOG_INFO("sample rate: %u", d->sample_rate); | |
138 | } | |
139 | if (!strcmp(id, "CHNL")) { | |
140 | d->channels = unpackn((void *)(streambuf->readp + 12)); | |
141 | LOG_INFO("channels: %u", d->channels); | |
142 | } | |
143 | if (!strcmp(id, "DSD ")) { | |
144 | LOG_INFO("found dsd len: " FMT_u64, len); | |
145 | d->sample_bytes = len; | |
146 | _buf_inc_readp(streambuf, 12); | |
147 | bytes -= 12; | |
148 | return 1; // got to the audio | |
149 | } | |
150 | } | |
151 | ||
152 | if (d->type == DSF) { | |
153 | if (!strcmp(id, "fmt ")) { | |
154 | if (bytes >= len && bytes >= 52) { | |
155 | u32_t version = unpack32le((void *)(streambuf->readp + 12)); | |
156 | u32_t format = unpack32le((void *)(streambuf->readp + 16)); | |
157 | LOG_INFO("DSF version: %u format: %u", version, format); | |
158 | if (format != 0) { | |
159 | LOG_WARN("only support DSD raw format"); | |
160 | return -1; | |
161 | } | |
162 | d->channels = unpack32le((void *)(streambuf->readp + 24)); | |
163 | d->sample_rate = unpack32le((void *)(streambuf->readp + 28)); | |
164 | d->lsb_first = (unpack32le((void *)(streambuf->readp + 32)) == 1); | |
165 | d->sample_bytes = unpack64le((void *)(streambuf->readp + 36)) / 8; | |
166 | d->block_size = unpack32le((void *)(streambuf->readp + 44)); | |
167 | LOG_INFO("channels: %u", d->channels); | |
168 | LOG_INFO("sample rate: %u", d->sample_rate); | |
169 | LOG_INFO("lsb first: %u", d->lsb_first); | |
170 | LOG_INFO("sample bytes: " FMT_u64, d->sample_bytes); | |
171 | LOG_INFO("block size: %u", d->block_size); | |
172 | } else { | |
173 | consume = -1; // come back later | |
174 | } | |
175 | } | |
176 | if (!strcmp(id, "data")) { | |
177 | LOG_INFO("found dsd len: " FMT_u64, len); | |
178 | _buf_inc_readp(streambuf, 12); | |
179 | bytes -= 12; | |
180 | return 1; // got to the audio | |
181 | } | |
182 | } | |
183 | ||
184 | // default to consuming whole chunk | |
185 | if (!consume) { | |
186 | consume = (s32_t)((d->type == DSDIFF) ? len + 12 : len); | |
187 | } | |
188 | ||
189 | if (bytes >= consume) { | |
190 | LOG_DEBUG("id: %s len: " FMT_u64 " consume: %d", id, len, consume); | |
191 | _buf_inc_readp(streambuf, consume); | |
192 | bytes -= consume; | |
193 | } else if (consume > 0) { | |
194 | LOG_DEBUG("id: %s len: " FMT_u64 " consume: %d - partial consume: %u", id, len, consume, bytes); | |
195 | _buf_inc_readp(streambuf, bytes); | |
196 | d->consume = consume - bytes; | |
197 | break; | |
198 | } else { | |
199 | break; | |
200 | } | |
201 | } | |
202 | ||
203 | return 0; | |
204 | } | |
205 | ||
206 | static decode_state _decode_dsf(void) { | |
207 | ||
208 | // samples in streambuf are interleaved on block basis | |
209 | // we transfer whole blocks for all channels in one call and so itterate the while loop below to handle wraps | |
210 | ||
211 | unsigned bytes = _buf_used(streambuf); | |
212 | unsigned block_left = d->block_size; | |
213 | ||
214 | unsigned bytes_per_frame = dop ? 2 : 1; | |
215 | ||
216 | if (bytes < d->block_size * d->channels) { | |
217 | LOG_WARN("stream too short"); | |
218 | return DECODE_ERROR; | |
219 | } | |
220 | ||
221 | IF_PROCESS( | |
222 | process.in_frames = 0; | |
223 | ); | |
224 | ||
225 | while (block_left) { | |
226 | ||
227 | frames_t frames, out, count; | |
228 | unsigned bytes_read; | |
229 | ||
230 | u8_t *iptrl = (u8_t *)streambuf->readp; | |
231 | u8_t *iptrr = (u8_t *)streambuf->readp + d->block_size; | |
232 | u32_t *optr; | |
233 | ||
234 | if (iptrr >= streambuf->wrap) { | |
235 | iptrr -= streambuf->size; | |
236 | } | |
237 | ||
238 | bytes = min(block_left, min(streambuf->wrap - iptrl, streambuf->wrap - iptrr)); | |
239 | ||
240 | IF_DIRECT( | |
241 | out = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; | |
242 | optr = (u32_t *)outputbuf->writep; | |
243 | ); | |
244 | IF_PROCESS( | |
245 | out = process.max_in_frames - process.in_frames; | |
246 | optr = (u32_t *)(process.inbuf + process.in_frames * BYTES_PER_FRAME); | |
247 | ); | |
248 | ||
249 | frames = min(bytes, d->sample_bytes) / bytes_per_frame; | |
250 | if (frames == 0) { | |
251 | // /2 for dop should never result in 0 as header len is always even | |
252 | LOG_WARN("frames got to zero"); | |
253 | return DECODE_ERROR; | |
254 | } | |
255 | ||
256 | frames = min(frames, out); | |
257 | frames = min(frames, BLOCK); | |
258 | bytes_read = frames * bytes_per_frame; | |
259 | ||
260 | count = frames; | |
261 | ||
262 | if (dop) { | |
263 | ||
264 | if (d->channels == 1) { | |
265 | if (d->lsb_first) { | |
266 | while (count--) { | |
267 | *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 16 | dsd2pcm_bitreverse[*(iptrl+1)] << 8; | |
268 | *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 16 | dsd2pcm_bitreverse[*(iptrl+1)] << 8; | |
269 | iptrl += 2; | |
270 | } | |
271 | } else { | |
272 | while (count--) { | |
273 | *(optr++) = *(iptrl) << 16 | *(iptrl+1) << 8; | |
274 | *(optr++) = *(iptrl) << 16 | *(iptrl+1) << 8; | |
275 | iptrl += 2; | |
276 | } | |
277 | } | |
278 | } else { | |
279 | if (d->lsb_first) { | |
280 | while (count--) { | |
281 | *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 16 | dsd2pcm_bitreverse[*(iptrl+1)] << 8; | |
282 | *(optr++) = dsd2pcm_bitreverse[*(iptrr)] << 16 | dsd2pcm_bitreverse[*(iptrr+1)] << 8; | |
283 | iptrl += 2; | |
284 | iptrr += 2; | |
285 | } | |
286 | } else { | |
287 | while (count--) { | |
288 | *(optr++) = *(iptrl) << 16 | *(iptrl+1) << 8; | |
289 | *(optr++) = *(iptrr) << 16 | *(iptrr+1) << 8; | |
290 | iptrl += 2; | |
291 | iptrr += 2; | |
292 | } | |
293 | } | |
294 | } | |
295 | ||
296 | } else { | |
297 | ||
298 | if (d->channels == 1) { | |
299 | float *iptrf = d->transfer[0]; | |
300 | dsd2pcm_translate(d->dsd2pcm_ctx[0], frames, iptrl, 1, d->lsb_first, iptrf, 1); | |
301 | while (count--) { | |
302 | double scaled = *iptrf++ * 0x7fffffff; | |
303 | if (scaled > 2147483647.0) scaled = 2147483647.0; | |
304 | if (scaled < -2147483648.0) scaled = -2147483648.0; | |
305 | *optr++ = (s32_t)scaled; | |
306 | *optr++ = (s32_t)scaled; | |
307 | } | |
308 | } else { | |
309 | float *iptrfl = d->transfer[0]; | |
310 | float *iptrfr = d->transfer[1]; | |
311 | dsd2pcm_translate(d->dsd2pcm_ctx[0], frames, iptrl, 1, d->lsb_first, iptrfl, 1); | |
312 | dsd2pcm_translate(d->dsd2pcm_ctx[1], frames, iptrr, 1, d->lsb_first, iptrfr, 1); | |
313 | while (count--) { | |
314 | double scaledl = *iptrfl++ * 0x7fffffff; | |
315 | double scaledr = *iptrfr++ * 0x7fffffff; | |
316 | if (scaledl > 2147483647.0) scaledl = 2147483647.0; | |
317 | if (scaledl < -2147483648.0) scaledl = -2147483648.0; | |
318 | if (scaledr > 2147483647.0) scaledr = 2147483647.0; | |
319 | if (scaledr < -2147483648.0) scaledr = -2147483648.0; | |
320 | *optr++ = (s32_t)scaledl; | |
321 | *optr++ = (s32_t)scaledr; | |
322 | } | |
323 | } | |
324 | ||
325 | } | |
326 | ||
327 | _buf_inc_readp(streambuf, bytes_read); | |
328 | ||
329 | block_left -= bytes_read; | |
330 | ||
331 | if (d->sample_bytes > bytes_read) { | |
332 | d->sample_bytes -= bytes_read; | |
333 | } else { | |
334 | LOG_INFO("end of track samples"); | |
335 | block_left = 0; | |
336 | d->sample_bytes = 0; | |
337 | } | |
338 | ||
339 | IF_DIRECT( | |
340 | _buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME); | |
341 | ); | |
342 | IF_PROCESS( | |
343 | process.in_frames += frames; | |
344 | ); | |
345 | ||
346 | LOG_SDEBUG("write %u frames", frames); | |
347 | } | |
348 | ||
349 | // skip the other channel blocks | |
350 | // the right channel has already been read and is guarenteed to be in streambuf so can be skipped immediately | |
351 | if (d->channels > 1) { | |
352 | _buf_inc_readp(streambuf, d->block_size); | |
353 | } | |
354 | if (d->channels > 2) { | |
355 | d->consume = d->block_size * (d->channels - 2); | |
356 | } | |
357 | ||
358 | return DECODE_RUNNING; | |
359 | } | |
360 | ||
361 | static decode_state _decode_dsdiff(void) { | |
362 | ||
363 | // samples in streambuf are interleaved on byte per channel | |
364 | // we process as little as necessary per call and only need to handle frames wrapping round streambuf | |
365 | ||
366 | unsigned bytes_per_frame, bytes_read; | |
367 | frames_t out, frames, count; | |
368 | u8_t *iptr; | |
369 | u32_t *optr; | |
370 | u8_t tmp[WRAP_BUF_SIZE]; | |
371 | ||
372 | unsigned bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); | |
373 | ||
374 | IF_DIRECT( | |
375 | out = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; | |
376 | ); | |
377 | IF_PROCESS( | |
378 | out = process.max_in_frames; | |
379 | ); | |
380 | ||
381 | if (dop) { | |
382 | bytes_per_frame = d->channels * 2; | |
383 | } else { | |
384 | bytes_per_frame = d->channels; | |
385 | out = min(out, BLOCK); | |
386 | } | |
387 | ||
388 | frames = min(min(bytes, d->sample_bytes) / bytes_per_frame, out); | |
389 | bytes_read = frames * bytes_per_frame; | |
390 | ||
391 | iptr = (u8_t *)streambuf->readp; | |
392 | ||
393 | IF_DIRECT( | |
394 | optr = (u32_t *)outputbuf->writep; | |
395 | ); | |
396 | IF_PROCESS( | |
397 | optr = (u32_t *)process.inbuf; | |
398 | ); | |
399 | ||
400 | // handle wrap around end of streambuf and partial dop frame at end of stream | |
401 | if (!frames && bytes < bytes_per_frame) { | |
402 | memset(tmp, 0x69, WRAP_BUF_SIZE); // 0x69 = dsd silence | |
403 | memcpy(tmp, streambuf->readp, bytes); | |
404 | if (_buf_used(streambuf) > bytes_per_frame) { | |
405 | memcpy(tmp + bytes, streambuf->buf, bytes_per_frame - bytes); | |
406 | bytes_read = bytes_per_frame; | |
407 | } else { | |
408 | bytes_read = bytes; | |
409 | } | |
410 | iptr = tmp; | |
411 | frames = 1; | |
412 | } | |
413 | ||
414 | count = frames; | |
415 | ||
416 | if (dop) { | |
417 | ||
418 | if (d->channels == 1) { | |
419 | while (count--) { | |
420 | *(optr++) = *(iptr) << 16 | *(iptr+1) << 8; | |
421 | *(optr++) = *(iptr) << 16 | *(iptr+1) << 8; | |
422 | iptr += bytes_per_frame; | |
423 | } | |
424 | } else { | |
425 | while (count--) { | |
426 | *(optr++) = *(iptr ) << 16 | *(iptr + d->channels) << 8; | |
427 | *(optr++) = *(iptr+1) << 16 | *(iptr + d->channels + 1) << 8; | |
428 | iptr += bytes_per_frame; | |
429 | } | |
430 | } | |
431 | ||
432 | } else { | |
433 | ||
434 | if (d->channels == 1) { | |
435 | float *iptrf = d->transfer[0]; | |
436 | dsd2pcm_translate(d->dsd2pcm_ctx[0], frames, iptr, 1, 0, iptrf, 1); | |
437 | while (count--) { | |
438 | double scaled = *iptrf++ * 0x7fffffff; | |
439 | if (scaled > 2147483647.0) scaled = 2147483647.0; | |
440 | if (scaled < -2147483648.0) scaled = -2147483648.0; | |
441 | *optr++ = (s32_t)scaled; | |
442 | *optr++ = (s32_t)scaled; | |
443 | } | |
444 | } else { | |
445 | float *iptrfl = d->transfer[0]; | |
446 | float *iptrfr = d->transfer[1]; | |
447 | dsd2pcm_translate(d->dsd2pcm_ctx[0], frames, iptr, d->channels, 0, iptrfl, 1); | |
448 | dsd2pcm_translate(d->dsd2pcm_ctx[1], frames, iptr + 1, d->channels, 0, iptrfr, 1); | |
449 | while (count--) { | |
450 | double scaledl = *iptrfl++ * 0x7fffffff; | |
451 | double scaledr = *iptrfr++ * 0x7fffffff; | |
452 | if (scaledl > 2147483647.0) scaledl = 2147483647.0; | |
453 | if (scaledl < -2147483648.0) scaledl = -2147483648.0; | |
454 | if (scaledr > 2147483647.0) scaledr = 2147483647.0; | |
455 | if (scaledr < -2147483648.0) scaledr = -2147483648.0; | |
456 | *optr++ = (s32_t)scaledl; | |
457 | *optr++ = (s32_t)scaledr; | |
458 | } | |
459 | } | |
460 | ||
461 | } | |
462 | ||
463 | _buf_inc_readp(streambuf, bytes_read); | |
464 | ||
465 | if (d->sample_bytes > bytes_read) { | |
466 | d->sample_bytes -= bytes_read; | |
467 | } else { | |
468 | LOG_INFO("end of track samples"); | |
469 | d->sample_bytes = 0; | |
470 | } | |
471 | ||
472 | IF_DIRECT( | |
473 | _buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME); | |
474 | ); | |
475 | IF_PROCESS( | |
476 | process.in_frames = frames; | |
477 | ); | |
478 | ||
479 | LOG_SDEBUG("write %u frames", frames); | |
480 | ||
481 | return DECODE_RUNNING; | |
482 | } | |
483 | ||
484 | ||
485 | static decode_state dsd_decode(void) { | |
486 | decode_state ret; | |
487 | ||
488 | LOCK_S; | |
489 | ||
490 | if ((stream.state <= DISCONNECT && !_buf_used(streambuf)) || (!decode.new_stream && d->sample_bytes == 0)) { | |
491 | UNLOCK_S; | |
492 | return DECODE_COMPLETE; | |
493 | } | |
494 | ||
495 | if (d->consume) { | |
496 | unsigned consume = min(d->consume, min(_buf_used(streambuf), _buf_cont_read(streambuf))); | |
497 | LOG_DEBUG("consume: %u of %u", consume, d->consume); | |
498 | _buf_inc_readp(streambuf, consume); | |
499 | d->consume -= consume; | |
500 | if (d->consume) { | |
501 | UNLOCK_S; | |
502 | return DECODE_RUNNING; | |
503 | } | |
504 | } | |
505 | ||
506 | if (decode.new_stream) { | |
507 | int r = _read_header(); | |
508 | if (r < 1) { | |
509 | UNLOCK_S; | |
510 | return DECODE_ERROR; | |
511 | } | |
512 | if (r == 0) { | |
513 | UNLOCK_S; | |
514 | return DECODE_RUNNING; | |
515 | } | |
516 | // otherwise got to start of audio | |
517 | ||
518 | LOCK_O; | |
519 | ||
520 | LOG_INFO("setting track_start"); | |
521 | output.track_start = outputbuf->writep; | |
522 | ||
523 | dop = output.has_dop; | |
524 | ||
525 | if (dop && d->sample_rate / 16 > output.supported_rates[0]) { | |
526 | LOG_INFO("DOP sample rate too high for device - converting to PCM"); | |
527 | dop = false; | |
528 | } | |
529 | ||
530 | if (dop) { | |
531 | LOG_INFO("DOP output"); | |
532 | output.next_dop = true; | |
533 | output.next_sample_rate = d->sample_rate / 16; | |
534 | output.fade = FADE_INACTIVE; | |
535 | } else { | |
536 | LOG_INFO("DSD to PCM output"); | |
537 | output.next_dop = false; | |
538 | output.next_sample_rate = decode_newstream(d->sample_rate / 8, output.supported_rates); | |
539 | if (output.fade_mode) _checkfade(true); | |
540 | } | |
541 | ||
542 | decode.new_stream = false; | |
543 | ||
544 | UNLOCK_O; | |
545 | } | |
546 | ||
547 | LOCK_O_direct; | |
548 | ||
549 | switch (d->type) { | |
550 | case DSF: | |
551 | ret = _decode_dsf(); | |
552 | break; | |
553 | case DSDIFF: | |
554 | ret = _decode_dsdiff(); | |
555 | break; | |
556 | default: | |
557 | ret = DECODE_ERROR; | |
558 | } | |
559 | ||
560 | UNLOCK_O_direct; | |
561 | UNLOCK_S; | |
562 | ||
563 | return ret; | |
564 | } | |
565 | ||
566 | static void dsd_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { | |
567 | d->type = UNKNOWN; | |
568 | ||
569 | if (!d->dsd2pcm_ctx[0]) { | |
570 | d->dsd2pcm_ctx[0] = dsd2pcm_init(); | |
571 | d->dsd2pcm_ctx[1] = dsd2pcm_init(); | |
572 | } else { | |
573 | dsd2pcm_reset(d->dsd2pcm_ctx[0]); | |
574 | dsd2pcm_reset(d->dsd2pcm_ctx[1]); | |
575 | } | |
576 | if (!d->transfer[1]) { | |
577 | d->transfer[0] = malloc(sizeof(float) * BLOCK); | |
578 | d->transfer[1] = malloc(sizeof(float) * BLOCK); | |
579 | } | |
580 | } | |
581 | ||
582 | static void dsd_close(void) { | |
583 | if (d->dsd2pcm_ctx[0]) { | |
584 | dsd2pcm_destroy(d->dsd2pcm_ctx[0]); | |
585 | dsd2pcm_destroy(d->dsd2pcm_ctx[1]); | |
586 | d->dsd2pcm_ctx[0] = NULL; | |
587 | d->dsd2pcm_ctx[1] = NULL; | |
588 | } | |
589 | if (d->transfer[0]) { | |
590 | free(d->transfer[0]); | |
591 | free(d->transfer[1]); | |
592 | d->transfer[0] = NULL; | |
593 | d->transfer[1] = NULL; | |
594 | } | |
595 | } | |
596 | ||
597 | struct codec *register_dsd(void) { | |
598 | static struct codec ret = { | |
599 | 'd', // id | |
600 | "dsf,dff", // types | |
601 | BLOCK * 2, // min read | |
602 | BLOCK_FRAMES,// min space | |
603 | dsd_open, // open | |
604 | dsd_close, // close | |
605 | dsd_decode, // decode | |
606 | }; | |
607 | ||
608 | d = malloc(sizeof(struct dsd)); | |
609 | if (!d) { | |
610 | return NULL; | |
611 | } | |
612 | ||
613 | memset(d, 0, sizeof(struct dsd)); | |
614 | ||
615 | dsd2pcm_precalc(); | |
616 | ||
617 | LOG_INFO("using dsd"); | |
618 | return &ret; | |
619 | } | |
620 | ||
621 | #endif // DSD |
0 | Copyright 2009, 2011 Sebastian Gesemann. All rights reserved. | |
1 | ||
2 | Redistribution and use in source and binary forms, with or without modification, are | |
3 | permitted provided that the following conditions are met: | |
4 | ||
5 | 1. Redistributions of source code must retain the above copyright notice, this list of | |
6 | conditions and the following disclaimer. | |
7 | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list | |
9 | of conditions and the following disclaimer in the documentation and/or other materials | |
10 | provided with the distribution. | |
11 | ||
12 | THIS SOFTWARE IS PROVIDED BY SEBASTIAN GESEMANN ''AS IS'' AND ANY EXPRESS OR IMPLIED | |
13 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
14 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SEBASTIAN GESEMANN OR | |
15 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
16 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
17 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |
18 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
19 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |
20 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
21 | ||
22 | The views and conclusions contained in the software and documentation are those of the | |
23 | authors and should not be interpreted as representing official policies, either expressed | |
24 | or implied, of Sebastian Gesemann. |
0 | /* | |
1 | ||
2 | Copyright 2009, 2011 Sebastian Gesemann. All rights reserved. | |
3 | ||
4 | Redistribution and use in source and binary forms, with or without modification, are | |
5 | permitted provided that the following conditions are met: | |
6 | ||
7 | 1. Redistributions of source code must retain the above copyright notice, this list of | |
8 | conditions and the following disclaimer. | |
9 | ||
10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list | |
11 | of conditions and the following disclaimer in the documentation and/or other materials | |
12 | provided with the distribution. | |
13 | ||
14 | THIS SOFTWARE IS PROVIDED BY SEBASTIAN GESEMANN ''AS IS'' AND ANY EXPRESS OR IMPLIED | |
15 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
16 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SEBASTIAN GESEMANN OR | |
17 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
18 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |
20 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
21 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |
22 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
23 | ||
24 | The views and conclusions contained in the software and documentation are those of the | |
25 | authors and should not be interpreted as representing official policies, either expressed | |
26 | or implied, of Sebastian Gesemann. | |
27 | ||
28 | ---- | |
29 | ||
30 | Additions (c) Adrian Smith, 2013 under same licence terms: | |
31 | - expose bitreverse array as dsd2pcm_bitreverse | |
32 | - expose precalc function as dsd2pcm_precalc to allow it to be initalised | |
33 | ||
34 | */ | |
35 | ||
36 | #include <stdlib.h> | |
37 | #include <string.h> | |
38 | ||
39 | #include "dsd2pcm.h" | |
40 | ||
41 | #define HTAPS 48 /* number of FIR constants */ | |
42 | #define FIFOSIZE 16 /* must be a power of two */ | |
43 | #define FIFOMASK (FIFOSIZE-1) /* bit mask for FIFO offsets */ | |
44 | #define CTABLES ((HTAPS+7)/8) /* number of "8 MACs" lookup tables */ | |
45 | ||
46 | #if FIFOSIZE*8 < HTAPS*2 | |
47 | #error "FIFOSIZE too small" | |
48 | #endif | |
49 | ||
50 | /* | |
51 | * Properties of this 96-tap lowpass filter when applied on a signal | |
52 | * with sampling rate of 44100*64 Hz: | |
53 | * | |
54 | * () has a delay of 17 microseconds. | |
55 | * | |
56 | * () flat response up to 48 kHz | |
57 | * | |
58 | * () if you downsample afterwards by a factor of 8, the | |
59 | * spectrum below 70 kHz is practically alias-free. | |
60 | * | |
61 | * () stopband rejection is about 160 dB | |
62 | * | |
63 | * The coefficient tables ("ctables") take only 6 Kibi Bytes and | |
64 | * should fit into a modern processor's fast cache. | |
65 | */ | |
66 | ||
67 | /* | |
68 | * The 2nd half (48 coeffs) of a 96-tap symmetric lowpass filter | |
69 | */ | |
70 | static const double htaps[HTAPS] = { | |
71 | 0.09950731974056658, | |
72 | 0.09562845727714668, | |
73 | 0.08819647126516944, | |
74 | 0.07782552527068175, | |
75 | 0.06534876523171299, | |
76 | 0.05172629311427257, | |
77 | 0.0379429484910187, | |
78 | 0.02490921351762261, | |
79 | 0.0133774746265897, | |
80 | 0.003883043418804416, | |
81 | -0.003284703416210726, | |
82 | -0.008080250212687497, | |
83 | -0.01067241812471033, | |
84 | -0.01139427235000863, | |
85 | -0.0106813877974587, | |
86 | -0.009007905078766049, | |
87 | -0.006828859761015335, | |
88 | -0.004535184322001496, | |
89 | -0.002425035959059578, | |
90 | -0.0006922187080790708, | |
91 | 0.0005700762133516592, | |
92 | 0.001353838005269448, | |
93 | 0.001713709169690937, | |
94 | 0.001742046839472948, | |
95 | 0.001545601648013235, | |
96 | 0.001226696225277855, | |
97 | 0.0008704322683580222, | |
98 | 0.0005381636200535649, | |
99 | 0.000266446345425276, | |
100 | 7.002968738383528e-05, | |
101 | -5.279407053811266e-05, | |
102 | -0.0001140625650874684, | |
103 | -0.0001304796361231895, | |
104 | -0.0001189970287491285, | |
105 | -9.396247155265073e-05, | |
106 | -6.577634378272832e-05, | |
107 | -4.07492895872535e-05, | |
108 | -2.17407957554587e-05, | |
109 | -9.163058931391722e-06, | |
110 | -2.017460145032201e-06, | |
111 | 1.249721855219005e-06, | |
112 | 2.166655190537392e-06, | |
113 | 1.930520892991082e-06, | |
114 | 1.319400334374195e-06, | |
115 | 7.410039764949091e-07, | |
116 | 3.423230509967409e-07, | |
117 | 1.244182214744588e-07, | |
118 | 3.130441005359396e-08 | |
119 | }; | |
120 | ||
121 | static float ctables[CTABLES][256]; | |
122 | unsigned char dsd2pcm_bitreverse[256]; | |
123 | static int precalculated = 0; | |
124 | ||
125 | void dsd2pcm_precalc(void) | |
126 | { | |
127 | int t, e, m, k; | |
128 | double acc; | |
129 | if (precalculated) return; | |
130 | for (t=0, e=0; t<256; ++t) { | |
131 | dsd2pcm_bitreverse[t] = e; | |
132 | for (m=128; m && !((e^=m)&m); m>>=1) | |
133 | ; | |
134 | } | |
135 | for (t=0; t<CTABLES; ++t) { | |
136 | k = HTAPS - t*8; | |
137 | if (k>8) k=8; | |
138 | for (e=0; e<256; ++e) { | |
139 | acc = 0.0; | |
140 | for (m=0; m<k; ++m) { | |
141 | acc += (((e >> (7-m)) & 1)*2-1) * htaps[t*8+m]; | |
142 | } | |
143 | ctables[CTABLES-1-t][e] = (float)acc; | |
144 | } | |
145 | } | |
146 | precalculated = 1; | |
147 | } | |
148 | ||
149 | struct dsd2pcm_ctx_s | |
150 | { | |
151 | unsigned char fifo[FIFOSIZE]; | |
152 | unsigned fifopos; | |
153 | }; | |
154 | ||
155 | extern dsd2pcm_ctx* dsd2pcm_init() | |
156 | { | |
157 | dsd2pcm_ctx* ptr; | |
158 | if (!precalculated) dsd2pcm_precalc(); | |
159 | ptr = (dsd2pcm_ctx*) malloc(sizeof(dsd2pcm_ctx)); | |
160 | if (ptr) dsd2pcm_reset(ptr); | |
161 | return ptr; | |
162 | } | |
163 | ||
164 | extern void dsd2pcm_destroy(dsd2pcm_ctx* ptr) | |
165 | { | |
166 | free(ptr); | |
167 | } | |
168 | ||
169 | extern dsd2pcm_ctx* dsd2pcm_clone(dsd2pcm_ctx* ptr) | |
170 | { | |
171 | dsd2pcm_ctx* p2; | |
172 | p2 = (dsd2pcm_ctx*) malloc(sizeof(dsd2pcm_ctx)); | |
173 | if (p2) { | |
174 | memcpy(p2,ptr,sizeof(dsd2pcm_ctx)); | |
175 | } | |
176 | return p2; | |
177 | } | |
178 | ||
179 | extern void dsd2pcm_reset(dsd2pcm_ctx* ptr) | |
180 | { | |
181 | int i; | |
182 | for (i=0; i<FIFOSIZE; ++i) | |
183 | ptr->fifo[i] = 0x69; /* my favorite silence pattern */ | |
184 | ptr->fifopos = 0; | |
185 | /* 0x69 = 01101001 | |
186 | * This pattern "on repeat" makes a low energy 352.8 kHz tone | |
187 | * and a high energy 1.0584 MHz tone which should be filtered | |
188 | * out completely by any playback system --> silence | |
189 | */ | |
190 | } | |
191 | ||
192 | extern void dsd2pcm_translate( | |
193 | dsd2pcm_ctx* ptr, | |
194 | size_t samples, | |
195 | const unsigned char *src, ptrdiff_t src_stride, | |
196 | int lsbf, | |
197 | float *dst, ptrdiff_t dst_stride) | |
198 | { | |
199 | unsigned ffp; | |
200 | unsigned i; | |
201 | unsigned bite1, bite2; | |
202 | unsigned char* p; | |
203 | double acc; | |
204 | ffp = ptr->fifopos; | |
205 | lsbf = lsbf ? 1 : 0; | |
206 | while (samples-- > 0) { | |
207 | bite1 = *src & 0xFFu; | |
208 | if (lsbf) bite1 = dsd2pcm_bitreverse[bite1]; | |
209 | ptr->fifo[ffp] = bite1; src += src_stride; | |
210 | p = ptr->fifo + ((ffp-CTABLES) & FIFOMASK); | |
211 | *p = dsd2pcm_bitreverse[*p & 0xFF]; | |
212 | acc = 0; | |
213 | for (i=0; i<CTABLES; ++i) { | |
214 | bite1 = ptr->fifo[(ffp -i) & FIFOMASK] & 0xFF; | |
215 | bite2 = ptr->fifo[(ffp-(CTABLES*2-1)+i) & FIFOMASK] & 0xFF; | |
216 | acc += ctables[i][bite1] + ctables[i][bite2]; | |
217 | } | |
218 | *dst = (float)acc; dst += dst_stride; | |
219 | ffp = (ffp + 1) & FIFOMASK; | |
220 | } | |
221 | ptr->fifopos = ffp; | |
222 | } | |
223 |
0 | /* | |
1 | ||
2 | Copyright 2009, 2011 Sebastian Gesemann. All rights reserved. | |
3 | ||
4 | Redistribution and use in source and binary forms, with or without modification, are | |
5 | permitted provided that the following conditions are met: | |
6 | ||
7 | 1. Redistributions of source code must retain the above copyright notice, this list of | |
8 | conditions and the following disclaimer. | |
9 | ||
10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list | |
11 | of conditions and the following disclaimer in the documentation and/or other materials | |
12 | provided with the distribution. | |
13 | ||
14 | THIS SOFTWARE IS PROVIDED BY SEBASTIAN GESEMANN ''AS IS'' AND ANY EXPRESS OR IMPLIED | |
15 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
16 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SEBASTIAN GESEMANN OR | |
17 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
18 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |
20 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
21 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |
22 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
23 | ||
24 | The views and conclusions contained in the software and documentation are those of the | |
25 | authors and should not be interpreted as representing official policies, either expressed | |
26 | or implied, of Sebastian Gesemann. | |
27 | ||
28 | ---- | |
29 | ||
30 | Marked additions (c) Adrian Smith, 2013 under same licence terms | |
31 | ||
32 | */ | |
33 | ||
34 | #ifndef DSD2PCM_H_INCLUDED | |
35 | #define DSD2PCM_H_INCLUDED | |
36 | ||
37 | #include <stddef.h> | |
38 | #include <string.h> | |
39 | ||
40 | #ifdef __cplusplus | |
41 | extern "C" { | |
42 | #endif | |
43 | ||
44 | struct dsd2pcm_ctx_s; | |
45 | ||
46 | typedef struct dsd2pcm_ctx_s dsd2pcm_ctx; | |
47 | ||
48 | /** | |
49 | * initializes a "dsd2pcm engine" for one channel | |
50 | * (precomputes tables and allocates memory) | |
51 | * | |
52 | * This is the only function that is not thread-safe in terms of the | |
53 | * POSIX thread-safety definition because it modifies global state | |
54 | * (lookup tables are computed during the first call) | |
55 | */ | |
56 | extern dsd2pcm_ctx* dsd2pcm_init(void); | |
57 | ||
58 | /** | |
59 | * deinitializes a "dsd2pcm engine" | |
60 | * (releases memory, don't forget!) | |
61 | */ | |
62 | extern void dsd2pcm_destroy(dsd2pcm_ctx *ctx); | |
63 | ||
64 | /** | |
65 | * clones the context and returns a pointer to the | |
66 | * newly allocated copy | |
67 | */ | |
68 | extern dsd2pcm_ctx* dsd2pcm_clone(dsd2pcm_ctx *ctx); | |
69 | ||
70 | /** | |
71 | * resets the internal state for a fresh new stream | |
72 | */ | |
73 | extern void dsd2pcm_reset(dsd2pcm_ctx *ctx); | |
74 | ||
75 | /** | |
76 | * "translates" a stream of octets to a stream of floats | |
77 | * (8:1 decimation) | |
78 | * @param ctx -- pointer to abstract context (buffers) | |
79 | * @param samples -- number of octets/samples to "translate" | |
80 | * @param src -- pointer to first octet (input) | |
81 | * @param src_stride -- src pointer increment | |
82 | * @param lsbitfirst -- bitorder, 0=msb first, 1=lsbfirst | |
83 | * @param dst -- pointer to first float (output) | |
84 | * @param dst_stride -- dst pointer increment | |
85 | */ | |
86 | extern void dsd2pcm_translate(dsd2pcm_ctx *ctx, | |
87 | size_t samples, | |
88 | const unsigned char *src, ptrdiff_t src_stride, | |
89 | int lsbitfirst, | |
90 | float *dst, ptrdiff_t dst_stride); | |
91 | ||
92 | /** | |
93 | * Additions by Adrian Smith (c) 2013 for Squeezelite | |
94 | */ | |
95 | extern unsigned char dsd2pcm_bitreverse[]; | |
96 | ||
97 | extern void dsd2pcm_precalc(void); | |
98 | /** | |
99 | * End of addition | |
100 | */ | |
101 | ||
102 | #ifdef __cplusplus | |
103 | } /* extern "C" */ | |
104 | #endif | |
105 | ||
106 | #endif /* include guard DSD2PCM_H_INCLUDED */ | |
107 |
374 | 374 | |
375 | 375 | LOCK_O; |
376 | 376 | LOG_INFO("setting track_start"); |
377 | output.next_sample_rate = decode_newstream(samplerate, output.max_sample_rate); | |
377 | output.next_sample_rate = decode_newstream(samplerate, output.supported_rates); | |
378 | IF_DSD( output.next_dop = false; ) | |
378 | 379 | output.track_start = outputbuf->writep; |
379 | 380 | if (output.fade_mode) _checkfade(true); |
380 | 381 | decode.new_stream = false; |
337 | 337 | |
338 | 338 | LOCK_O; |
339 | 339 | LOG_INFO("setting track_start"); |
340 | output.next_sample_rate = decode_newstream(ff->codecC->sample_rate, output.max_sample_rate); | |
340 | output.next_sample_rate = decode_newstream(ff->codecC->sample_rate, output.supported_rates); | |
341 | IF_DSD( output.next_dop = false; ) | |
341 | 342 | output.track_start = outputbuf->writep; |
342 | 343 | if (output.fade_mode) _checkfade(true); |
343 | 344 | decode.new_stream = false; |
113 | 113 | if (decode.new_stream) { |
114 | 114 | LOCK_O; |
115 | 115 | LOG_INFO("setting track_start"); |
116 | output.next_sample_rate = decode_newstream(frame->header.sample_rate, output.max_sample_rate); | |
117 | 116 | output.track_start = outputbuf->writep; |
117 | decode.new_stream = false; | |
118 | ||
119 | #if DSD | |
120 | if (output.has_dop && bits_per_sample == 24 && is_flac_dop((u32_t *)lptr, (u32_t *)rptr, frames)) { | |
121 | LOG_INFO("file contains DOP"); | |
122 | output.next_dop = true; | |
123 | output.next_sample_rate = frame->header.sample_rate; | |
124 | output.fade = FADE_INACTIVE; | |
125 | } else { | |
126 | output.next_sample_rate = decode_newstream(frame->header.sample_rate, output.supported_rates); | |
127 | output.next_dop = false; | |
128 | if (output.fade_mode) _checkfade(true); | |
129 | } | |
130 | #else | |
131 | output.next_sample_rate = decode_newstream(frame->header.sample_rate, output.supported_rates); | |
118 | 132 | if (output.fade_mode) _checkfade(true); |
119 | decode.new_stream = false; | |
133 | #endif | |
134 | ||
120 | 135 | UNLOCK_O; |
121 | 136 | } |
122 | 137 |
208 | 208 | if (decode.new_stream) { |
209 | 209 | LOCK_O; |
210 | 210 | LOG_INFO("setting track_start"); |
211 | output.next_sample_rate = decode_newstream(m->synth.pcm.samplerate, output.max_sample_rate); | |
211 | output.next_sample_rate = decode_newstream(m->synth.pcm.samplerate, output.supported_rates); | |
212 | IF_DSD( output.next_dop = false; ) | |
212 | 213 | output.track_start = outputbuf->writep; |
213 | 214 | if (output.fade_mode) _checkfade(true); |
214 | 215 | decode.new_stream = false; |
27 | 27 | printf(TITLE " See -t for license terms\n" |
28 | 28 | "Usage: %s [options]\n" |
29 | 29 | " -s <server>[:<port>]\tConnect to specified server, otherwise uses autodiscovery to find server\n" |
30 | " -o <output device>\tSpecify output device, default \"default\"\n" | |
30 | " -o <output device>\tSpecify output device, default \"default\", - = output to stdout\n" | |
31 | 31 | " -l \t\t\tList output devices\n" |
32 | 32 | #if ALSA |
33 | 33 | " -a <b>:<p>:<f>:<m>\tSpecify ALSA params to open output device, b = buffer time in ms or size in bytes, p = period count or size in bytes, f sample format (16|24|24_3|32), m = use mmap (0|1)\n" |
39 | 39 | " -a <l>\t\tSpecify Portaudio params to open output device, l = target latency in ms\n" |
40 | 40 | #endif |
41 | 41 | #endif |
42 | " -a <f>\t\tSpecify sample format (16|24|32) of output file when using -o - to output samples to stdout (interleaved little endian only)\n" | |
42 | 43 | " -b <stream>:<output>\tSpecify internal Stream and Output buffer sizes in Kbytes\n" |
43 | 44 | " -c <codec1>,<codec2>\tRestrict codecs to those specified, otherwise load all available codecs; known codecs: " |
44 | 45 | #if FFMPEG |
50 | 51 | " -f <logfile>\t\tWrite debug to logfile\n" |
51 | 52 | " -m <mac addr>\t\tSet mac address, format: ab:cd:ef:12:34:56\n" |
52 | 53 | " -n <name>\t\tSet the player name\n" |
54 | " -N <filename>\t\tStore player name in filename to allow server defined name changes to be shared between servers (not suppored with -n)\n" | |
53 | 55 | #if ALSA |
54 | 56 | " -p <priority>\t\tSet real time priority of output thread (1-99)\n" |
55 | 57 | #endif |
56 | " -r <rate>\t\tMax sample rate for output device, enables output device to be off when squeezelite is started\n" | |
58 | " -r <rates>\t\tSpecify sample rates supported by device, enables output device to be off when squeezelite is started; rates = <maxrate> | <minrate>-<maxrate> | <rate1>,<rate2>,<rate3>\n" | |
57 | 59 | #if RESAMPLE |
58 | " -u [params]\t\tUpsample, params = <recipe>:<flags>:<attenuation>:<precision>:<passband_end>:<stopband_start>:<phase_response>,\n" | |
59 | " \t\t\t recipe = (v|h|m|l|q)(|L|I|M)(|s), (|X) = async - resample to max rate for device, otherwise resample to max sync rate\n" | |
60 | " -R -u [params]\tResample, params = <recipe>:<flags>:<attenuation>:<precision>:<passband_end>:<stopband_start>:<phase_response>,\n" | |
61 | " \t\t\t recipe = (v|h|m|l|q)(L|I|M)(s) [E|X], E = exception - resample only if native rate not supported, X = async - resample to max rate for device, otherwise to max sync rate\n" | |
60 | 62 | " \t\t\t flags = num in hex,\n" |
61 | 63 | " \t\t\t attenuation = attenuation in dB to apply (default is -1db if not explicitly set),\n" |
62 | 64 | " \t\t\t precision = number of bits precision (NB. HQ = 20. VHQ = 28),\n" |
64 | 66 | " \t\t\t stopband_start = number in percent (Aliasing/imaging control. > passband_end),\n" |
65 | 67 | " \t\t\t phase_response = 0-100 (0 = minimum / 50 = linear / 100 = maximum)\n" |
66 | 68 | #endif |
69 | #if DSD | |
70 | " -D\t\t\tOutput device supports DSD over PCM (DoP)\n" | |
71 | #endif | |
67 | 72 | #if VISEXPORT |
68 | 73 | " -v \t\t\tVisulizer support\n" |
69 | 74 | #endif |
72 | 77 | #endif |
73 | 78 | " -t \t\t\tLicense terms\n" |
74 | 79 | "\n" |
75 | "Build options: " | |
80 | "Build options:" | |
76 | 81 | #if LINUX |
77 | "LINUX" | |
82 | " LINUX" | |
78 | 83 | #endif |
79 | 84 | #if WIN |
80 | "WIN" | |
85 | " WIN" | |
81 | 86 | #endif |
82 | 87 | #if OSX |
83 | "OSX" | |
88 | " OSX" | |
84 | 89 | #endif |
85 | 90 | #if ALSA |
86 | 91 | " ALSA" |
105 | 110 | #endif |
106 | 111 | #if VISEXPORT |
107 | 112 | " VISEXPORT" |
113 | #endif | |
114 | #if DSD | |
115 | " DSD" | |
116 | #endif | |
117 | #if LINKALL | |
118 | " LINKALL" | |
108 | 119 | #endif |
109 | 120 | "\n\n", |
110 | 121 | argv0); |
122 | 133 | "GNU General Public License for more details.\n\n" |
123 | 134 | "You should have received a copy of the GNU General Public License\n" |
124 | 135 | "along with this program. If not, see <http://www.gnu.org/licenses/>.\n\n" |
136 | #if DSD | |
137 | "Contains dsd2pcm library Copyright 2009, 2011 Sebastian Gesemann which\n" | |
138 | "is subject to its own license.\n\n" | |
139 | #endif | |
125 | 140 | ); |
126 | 141 | } |
127 | 142 | |
137 | 152 | char *output_device = "default"; |
138 | 153 | char *codecs = NULL; |
139 | 154 | char *name = NULL; |
155 | char *namefile = NULL; | |
140 | 156 | char *logfile = NULL; |
141 | 157 | u8_t mac[6]; |
142 | 158 | unsigned stream_buf_size = STREAMBUF_SIZE; |
143 | 159 | unsigned output_buf_size = 0; // set later |
144 | unsigned max_rate = 0; | |
145 | char *upsample = NULL; | |
160 | unsigned rates[MAX_SUPPORTED_SAMPLERATES] = { 0 }; | |
161 | char *resample = NULL; | |
162 | char *output_params = NULL; | |
146 | 163 | #if LINUX |
147 | 164 | bool daemonize = false; |
148 | 165 | #endif |
149 | 166 | #if ALSA |
150 | unsigned alsa_buffer = ALSA_BUFFER_TIME; | |
151 | unsigned alsa_period = ALSA_PERIOD_COUNT; | |
152 | char *alsa_sample_fmt = NULL; | |
153 | bool alsa_mmap = true; | |
154 | 167 | unsigned rt_priority = OUTPUT_RT_PRIORITY; |
155 | 168 | #endif |
156 | #if PORTAUDIO | |
157 | unsigned pa_latency = 0; | |
158 | int pa_osx_playnice = -1; | |
169 | #if DSD | |
170 | bool dop = false; | |
159 | 171 | #endif |
160 | 172 | #if VISEXPORT |
161 | 173 | bool visexport = false; |
173 | 185 | |
174 | 186 | while (optind < argc && strlen(argv[optind]) >= 2 && argv[optind][0] == '-') { |
175 | 187 | char *opt = argv[optind] + 1; |
176 | if (strstr("oabcdfmnprs", opt) && optind < argc - 1) { | |
188 | if (strstr("oabcdfmnNprs", opt) && optind < argc - 1) { | |
177 | 189 | optarg = argv[optind + 1]; |
178 | 190 | optind += 2; |
179 | 191 | } else if (strstr("ltz" |
180 | 192 | #if RESAMPLE |
181 | "u" | |
193 | "uR" | |
194 | #endif | |
195 | #if DSD | |
196 | "D" | |
182 | 197 | #endif |
183 | 198 | #if VISEXPORT |
184 | 199 | "v" |
195 | 210 | case 'o': |
196 | 211 | output_device = optarg; |
197 | 212 | break; |
198 | case 'a': | |
199 | { | |
200 | #if ALSA | |
201 | char *t = next_param(optarg, ':'); | |
202 | char *c = next_param(NULL, ':'); | |
203 | char *s = next_param(NULL, ':'); | |
204 | char *m = next_param(NULL, ':'); | |
205 | if (t) alsa_buffer = atoi(t); | |
206 | if (c) alsa_period = atoi(c); | |
207 | if (s) alsa_sample_fmt = s; | |
208 | if (m) alsa_mmap = atoi(m); | |
209 | #endif | |
210 | #if PORTAUDIO | |
211 | char *l = next_param(optarg, ':'); | |
212 | char *p = next_param(NULL, ':'); | |
213 | if (l) pa_latency = (unsigned)atoi(l); | |
214 | if (p) pa_osx_playnice = atoi(p); | |
215 | #endif | |
216 | } | |
213 | case 'a': | |
214 | output_params = optarg; | |
217 | 215 | break; |
218 | 216 | case 'b': |
219 | 217 | { |
260 | 258 | } |
261 | 259 | break; |
262 | 260 | case 'r': |
263 | max_rate = atoi(optarg); | |
261 | if (strstr(optarg,",")) { | |
262 | // parse sample rates and sort them | |
263 | char *r = next_param(optarg, ','); | |
264 | unsigned tmp[MAX_SUPPORTED_SAMPLERATES] = { 0 }; | |
265 | int i, j; | |
266 | int last = 999999; | |
267 | for (i = 0; r && i < MAX_SUPPORTED_SAMPLERATES; ++i) { | |
268 | tmp[i] = atoi(r); | |
269 | r = next_param(NULL, ','); | |
270 | } | |
271 | for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) { | |
272 | int largest = 0; | |
273 | for (j = 0; j < MAX_SUPPORTED_SAMPLERATES; ++j) { | |
274 | if (tmp[j] > largest && tmp[j] < last) { | |
275 | largest = tmp[j]; | |
276 | } | |
277 | } | |
278 | rates[i] = last = largest; | |
279 | } | |
280 | } else { | |
281 | // optstr is <min>-<max> or <max>, extract rates from test rates within this range | |
282 | unsigned ref[] TEST_RATES; | |
283 | char *str1 = next_param(optarg, '-'); | |
284 | char *str2 = next_param(NULL, '-'); | |
285 | unsigned max = str2 ? atoi(str2) : (str1 ? atoi(str1) : ref[0]); | |
286 | unsigned min = str1 && str2 ? atoi(str1) : 0; | |
287 | unsigned tmp; | |
288 | int i, j; | |
289 | if (max < min) { tmp = max; max = min; min = tmp; } | |
290 | rates[0] = max; | |
291 | for (i = 0, j = 1; i < MAX_SUPPORTED_SAMPLERATES; ++i) { | |
292 | if (ref[i] < rates[j-1] && ref[i] >= min) { | |
293 | rates[j++] = ref[i]; | |
294 | } | |
295 | } | |
296 | } | |
264 | 297 | break; |
265 | 298 | case 's': |
266 | 299 | server = optarg; |
267 | 300 | break; |
268 | 301 | case 'n': |
269 | 302 | name = optarg; |
303 | break; | |
304 | case 'N': | |
305 | namefile = optarg; | |
270 | 306 | break; |
271 | 307 | #if ALSA |
272 | 308 | case 'p': |
283 | 319 | break; |
284 | 320 | #if RESAMPLE |
285 | 321 | case 'u': |
322 | case 'R': | |
286 | 323 | if (optind < argc && argv[optind] && argv[optind][0] != '-') { |
287 | upsample = argv[optind++]; | |
324 | resample = argv[optind++]; | |
288 | 325 | } else { |
289 | upsample = ""; | |
290 | } | |
326 | resample = ""; | |
327 | } | |
328 | break; | |
329 | #endif | |
330 | #if DSD | |
331 | case 'D': | |
332 | dop = true; | |
291 | 333 | break; |
292 | 334 | #endif |
293 | 335 | #if VISEXPORT |
317 | 359 | signal(SIGHUP, sighandler); |
318 | 360 | #endif |
319 | 361 | |
320 | // set the output buffer size if not specified on the command line to take account of upsampling | |
362 | // set the output buffer size if not specified on the command line, take account of resampling | |
321 | 363 | if (!output_buf_size) { |
322 | 364 | output_buf_size = OUTPUTBUF_SIZE; |
323 | if (upsample) { | |
324 | output_buf_size *= max_rate ? max_rate / 44100 : 8; | |
365 | if (resample) { | |
366 | unsigned scale = 8; | |
367 | if (rates[0]) { | |
368 | scale = rates[0] / 44100; | |
369 | if (scale > 8) scale = 8; | |
370 | if (scale < 1) scale = 1; | |
371 | } | |
372 | output_buf_size *= scale; | |
325 | 373 | } |
326 | 374 | } |
327 | 375 | |
345 | 393 | |
346 | 394 | stream_init(log_stream, stream_buf_size); |
347 | 395 | |
348 | #if ALSA | |
349 | output_init(log_output, output_device, output_buf_size, alsa_buffer, alsa_period, alsa_sample_fmt, alsa_mmap, | |
350 | max_rate, rt_priority); | |
396 | if (!strcmp(output_device, "-")) { | |
397 | output_init_stdout(log_output, output_buf_size, output_params, rates); | |
398 | } else { | |
399 | #if ALSA | |
400 | output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rt_priority); | |
351 | 401 | #endif |
352 | 402 | #if PORTAUDIO |
353 | output_init(log_output, output_device, output_buf_size, pa_latency, pa_osx_playnice, max_rate); | |
403 | output_init_pa(log_output, output_device, output_buf_size, output_params, rates); | |
404 | #endif | |
405 | } | |
406 | ||
407 | #if DSD | |
408 | dop_init(dop); | |
354 | 409 | #endif |
355 | 410 | |
356 | 411 | #if VISEXPORT |
357 | 412 | if (visexport) { |
358 | output_vis_init(mac); | |
413 | output_vis_init(log_output, mac); | |
359 | 414 | } |
360 | 415 | #endif |
361 | 416 | |
362 | 417 | decode_init(log_decode, codecs); |
363 | 418 | |
364 | 419 | #if RESAMPLE |
365 | if (upsample) { | |
366 | process_init(upsample); | |
367 | } | |
368 | #endif | |
369 | ||
370 | slimproto(log_slimproto, server, mac, name); | |
420 | if (resample) { | |
421 | process_init(resample); | |
422 | } | |
423 | #endif | |
424 | ||
425 | if (name && namefile) { | |
426 | printf("-n and -N option should not be used at same time\n"); | |
427 | exit(0); | |
428 | } | |
429 | ||
430 | slimproto(log_slimproto, server, mac, name, namefile); | |
371 | 431 | |
372 | 432 | decode_close(); |
373 | 433 | stream_close(); |
374 | output_close(); | |
434 | ||
435 | if (!strcmp(output_device, "-")) { | |
436 | output_close_stdout(); | |
437 | } else { | |
438 | #if ALSA | |
439 | output_close_alsa(); | |
440 | #endif | |
441 | #if PORTAUDIO | |
442 | output_close_pa(); | |
443 | #endif | |
444 | } | |
375 | 445 | |
376 | 446 | #if WIN |
377 | 447 | winsock_close(); |
101 | 101 | bytes = min(bytes, READ_SIZE); |
102 | 102 | space = min(space, WRITE_SIZE); |
103 | 103 | |
104 | if (stream.state <= DISCONNECT && bytes == 0) { | |
105 | UNLOCK_O_direct; | |
106 | UNLOCK_S; | |
107 | return DECODE_COMPLETE; | |
108 | } | |
109 | ||
110 | 104 | if (m->use16bit) { |
111 | 105 | space = (space / BYTES_PER_FRAME) * 4; |
112 | 106 | } |
128 | 122 | |
129 | 123 | LOG_INFO("setting track_start"); |
130 | 124 | LOCK_O_not_direct; |
131 | output.next_sample_rate = decode_newstream(rate, output.max_sample_rate); | |
125 | output.next_sample_rate = decode_newstream(rate, output.supported_rates); | |
126 | IF_DSD( output.next_dop = false; ) | |
132 | 127 | output.track_start = outputbuf->writep; |
133 | 128 | if (output.fade_mode) _checkfade(true); |
134 | 129 | decode.new_stream = false; |
162 | 157 | ); |
163 | 158 | |
164 | 159 | UNLOCK_O_direct; |
165 | UNLOCK_S; | |
166 | 160 | |
167 | 161 | LOG_SDEBUG("write %u frames", size / BYTES_PER_FRAME); |
168 | 162 | |
169 | if (ret == MPG123_DONE) { | |
163 | if (ret == MPG123_DONE || (bytes == 0 && size == 0 && stream.state <= DISCONNECT)) { | |
164 | UNLOCK_S; | |
170 | 165 | LOG_INFO("stream complete"); |
171 | 166 | return DECODE_COMPLETE; |
172 | 167 | } |
168 | ||
169 | UNLOCK_S; | |
173 | 170 | |
174 | 171 | if (ret == MPG123_ERR) { |
175 | 172 | LOG_WARN("Error"); |
17 | 17 | * |
18 | 18 | */ |
19 | 19 | |
20 | // Output using Alsa or Portaudio: | |
21 | // - ALSA output is the preferred output for linux as it allows direct hardware access | |
22 | // - PortAudio is the output output supported on other platforms and also builds on linux for test purposes | |
20 | // Common output function | |
23 | 21 | |
24 | 22 | #include "squeezelite.h" |
25 | #if ALSA | |
26 | #include <alsa/asoundlib.h> | |
27 | #include <sys/mman.h> | |
28 | #include <malloc.h> | |
23 | ||
24 | static log_level loglevel; | |
25 | ||
26 | struct outputstate output; | |
27 | ||
28 | static struct buffer buf; | |
29 | ||
30 | struct buffer *outputbuf = &buf; | |
31 | ||
32 | u8_t *silencebuf; | |
33 | #if DSD | |
34 | u8_t *silencebuf_dop; | |
29 | 35 | #endif |
30 | #if PORTAUDIO | |
31 | #include <portaudio.h> | |
32 | #if OSX | |
33 | #include <pa_mac_core.h> | |
34 | #endif | |
35 | #endif | |
36 | #if VISEXPORT && !ALSA | |
37 | #include <sys/mman.h> | |
38 | #include <fcntl.h> | |
39 | #endif | |
40 | ||
41 | #if ALSA | |
42 | ||
43 | #define MAX_SILENCE_FRAMES 1024 | |
44 | #define MAX_DEVICE_LEN 128 | |
45 | ||
46 | static snd_pcm_format_t fmts[] = { SND_PCM_FORMAT_S32_LE, SND_PCM_FORMAT_S24_LE, SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S16_LE, | |
47 | SND_PCM_FORMAT_UNKNOWN }; | |
48 | #if SL_LITTLE_ENDIAN | |
49 | #define NATIVE_FORMAT SND_PCM_FORMAT_S32_LE | |
50 | #else | |
51 | #define NATIVE_FORMAT SND_PCM_FORMAT_S32_BE | |
52 | #endif | |
53 | ||
54 | // ouput device | |
55 | static struct { | |
56 | char device[MAX_DEVICE_LEN + 1]; | |
57 | snd_pcm_format_t format; | |
58 | snd_pcm_uframes_t buffer_size; | |
59 | snd_pcm_uframes_t period_size; | |
60 | unsigned rate; | |
61 | bool mmap; | |
62 | u8_t *write_buf; | |
63 | } alsa; | |
64 | ||
65 | static u8_t silencebuf[MAX_SILENCE_FRAMES * BYTES_PER_FRAME]; | |
66 | ||
67 | #endif // ALSA | |
68 | ||
69 | #if PORTAUDIO | |
70 | ||
71 | #define MAX_SILENCE_FRAMES 102400 // silencebuf not used in pa case so set large | |
72 | ||
73 | // ouput device | |
74 | static struct { | |
75 | unsigned rate; | |
76 | PaStream *stream; | |
77 | } pa; | |
78 | ||
79 | #endif // PORTAUDIO | |
80 | ||
81 | #if VISEXPORT | |
82 | ||
83 | #define VIS_BUF_SIZE 16384 | |
84 | #define VIS_LOCK_NS 1000000 // ns to wait for vis wrlock | |
85 | ||
86 | static struct vis_t { | |
87 | pthread_rwlock_t rwlock; | |
88 | u32_t buf_size; | |
89 | u32_t buf_index; | |
90 | bool running; | |
91 | u32_t rate; | |
92 | time_t updated; | |
93 | s16_t buffer[VIS_BUF_SIZE]; | |
94 | } *vis_mmap = NULL; | |
95 | ||
96 | static char vis_shm_path[40]; | |
97 | static int vis_fd = -1; | |
98 | ||
99 | #endif // VISEXPORT | |
100 | ||
101 | static log_level loglevel; | |
102 | ||
103 | struct outputstate output; | |
104 | ||
105 | static struct buffer buf; | |
106 | ||
107 | struct buffer *outputbuf = &buf; | |
108 | ||
109 | static bool running = true; | |
110 | 36 | |
111 | 37 | #define LOCK mutex_lock(outputbuf->mutex) |
112 | 38 | #define UNLOCK mutex_unlock(outputbuf->mutex) |
113 | 39 | |
114 | #define MAX_SCALESAMPLE 0x7fffffffffffLL | |
115 | #define MIN_SCALESAMPLE -MAX_SCALESAMPLE | |
116 | ||
117 | static inline s32_t gain(s32_t gain, s32_t sample) { | |
118 | s64_t res = (s64_t)gain * (s64_t)sample; | |
119 | if (res > MAX_SCALESAMPLE) res = MAX_SCALESAMPLE; | |
120 | if (res < MIN_SCALESAMPLE) res = MIN_SCALESAMPLE; | |
121 | return (s32_t) (res >> 16); | |
122 | } | |
123 | ||
124 | static inline s32_t to_gain(float f) { | |
125 | return (s32_t)(f * 65536.0F); | |
126 | } | |
127 | ||
128 | #if ALSA | |
129 | ||
130 | void list_devices(void) { | |
131 | void **hints, **n; | |
132 | if (snd_device_name_hint(-1, "pcm", &hints) >= 0) { | |
133 | n = hints; | |
134 | printf("Output devices:\n"); | |
135 | while (*n) { | |
136 | char *name = snd_device_name_get_hint(*n, "NAME"); | |
137 | char *desc = snd_device_name_get_hint(*n, "DESC"); | |
138 | if (name) printf(" %-30s", name); | |
139 | if (desc) { | |
140 | char *s1 = strtok(desc, "\n"); | |
141 | char *s2 = strtok(NULL, "\n"); | |
142 | if (s1) printf(" - %s", s1); | |
143 | if (s2) printf(" - %s", s2); | |
144 | } | |
145 | printf("\n"); | |
146 | if (name) free(name); | |
147 | if (desc) free(desc); | |
148 | n++; | |
149 | } | |
150 | snd_device_name_free_hint(hints); | |
151 | } | |
152 | printf("\n"); | |
153 | } | |
154 | ||
155 | static void *alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) { | |
156 | va_list args; | |
157 | if ((loglevel >= lINFO && err == 0) || loglevel >= lDEBUG) { | |
158 | fprintf(stderr, "%s ALSA %s:%d ", logtime(), function, line); | |
159 | va_start(args, fmt); | |
160 | vfprintf(stderr, fmt, args); | |
161 | fprintf(stderr, "\n"); | |
162 | fflush(stderr); | |
163 | } | |
164 | return NULL; | |
165 | } | |
166 | ||
167 | static void alsa_close(snd_pcm_t *pcmp) { | |
168 | int err; | |
169 | if ((err = snd_pcm_close(pcmp)) < 0) { | |
170 | LOG_INFO("snd_pcm_close error: %s", snd_strerror(err)); | |
171 | } | |
172 | } | |
173 | ||
174 | static bool test_open(const char *device, u32_t *max_rate) { | |
175 | int err; | |
176 | snd_pcm_t *pcm; | |
177 | snd_pcm_hw_params_t *hw_params; | |
178 | hw_params = (snd_pcm_hw_params_t *) alloca(snd_pcm_hw_params_sizeof()); | |
179 | memset(hw_params, 0, snd_pcm_hw_params_sizeof()); | |
180 | ||
181 | // open device | |
182 | if ((err = snd_pcm_open(&pcm, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { | |
183 | LOG_ERROR("playback open error: %s", snd_strerror(err)); | |
184 | return false; | |
185 | } | |
186 | ||
187 | // get max params | |
188 | if ((err = snd_pcm_hw_params_any(pcm, hw_params)) < 0) { | |
189 | LOG_ERROR("hwparam init error: %s", snd_strerror(err)); | |
190 | return false; | |
191 | } | |
192 | ||
193 | // get max rate | |
194 | if ((err = snd_pcm_hw_params_get_rate_max(hw_params, max_rate, 0)) < 0) { | |
195 | LOG_ERROR("unable to get max sample rate: %s", snd_strerror(err)); | |
196 | return false; | |
197 | } | |
198 | ||
199 | if (*max_rate > 384000) { | |
200 | *max_rate = 384000; | |
201 | } | |
202 | ||
203 | if ((err = snd_pcm_close(pcm)) < 0) { | |
204 | LOG_ERROR("snd_pcm_close error: %s", snd_strerror(err)); | |
205 | return false; | |
206 | } | |
207 | ||
208 | return true; | |
209 | } | |
210 | ||
211 | static bool pcm_probe(const char *device) { | |
212 | int err; | |
213 | snd_pcm_t *pcm; | |
214 | ||
215 | if ((err = snd_pcm_open(&pcm, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { | |
216 | return false; | |
217 | } | |
218 | ||
219 | if ((err = snd_pcm_close(pcm)) < 0) { | |
220 | LOG_ERROR("snd_pcm_close error: %s", snd_strerror(err)); | |
221 | } | |
222 | ||
223 | return true; | |
224 | } | |
225 | ||
226 | static int alsa_open(snd_pcm_t **pcmp, const char *device, unsigned sample_rate, unsigned alsa_buffer, unsigned alsa_period) { | |
227 | int err; | |
228 | snd_pcm_hw_params_t *hw_params; | |
229 | snd_pcm_hw_params_alloca(&hw_params); | |
230 | ||
231 | // close if already open | |
232 | if (*pcmp) alsa_close(*pcmp); | |
233 | ||
234 | // reset params | |
235 | alsa.rate = 0; | |
236 | alsa.period_size = 0; | |
237 | strcpy(alsa.device, device); | |
238 | ||
239 | if (strlen(device) > MAX_DEVICE_LEN - 4 - 1) { | |
240 | LOG_ERROR("device name too long: %s", device); | |
241 | return -1; | |
242 | } | |
243 | ||
244 | bool retry; | |
245 | do { | |
246 | // open device | |
247 | if ((err = snd_pcm_open(pcmp, alsa.device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { | |
248 | LOG_ERROR("playback open error: %s", snd_strerror(err)); | |
249 | return err; | |
250 | } | |
251 | ||
252 | // init params | |
253 | memset(hw_params, 0, snd_pcm_hw_params_sizeof()); | |
254 | if ((err = snd_pcm_hw_params_any(*pcmp, hw_params)) < 0) { | |
255 | LOG_ERROR("hwparam init error: %s", snd_strerror(err)); | |
256 | return err; | |
257 | } | |
258 | ||
259 | // open hw: devices without resampling, if sample rate fails try plughw: with resampling | |
260 | bool hw = !strncmp(alsa.device, "hw:", 3); | |
261 | retry = false; | |
262 | ||
263 | if ((err = snd_pcm_hw_params_set_rate_resample(*pcmp, hw_params, !hw)) < 0) { | |
264 | LOG_ERROR("resampling setup failed: %s", snd_strerror(err)); | |
265 | return err; | |
266 | } | |
267 | ||
268 | if ((err = snd_pcm_hw_params_set_rate(*pcmp, hw_params, sample_rate, 0)) < 0) { | |
269 | if (hw) { | |
270 | strcpy(alsa.device + 4, device); | |
271 | memcpy(alsa.device, "plug", 4); | |
272 | LOG_INFO("reopening device %s in plug mode as %s for resampling", device, alsa.device); | |
273 | snd_pcm_close(*pcmp); | |
274 | retry = true; | |
275 | } | |
276 | } | |
277 | ||
278 | } while (retry); | |
279 | ||
280 | // set access | |
281 | if (!alsa.mmap || snd_pcm_hw_params_set_access(*pcmp, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) { | |
282 | if ((err = snd_pcm_hw_params_set_access(*pcmp, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { | |
283 | LOG_ERROR("access type not available: %s", snd_strerror(err)); | |
284 | return err; | |
285 | } | |
286 | alsa.mmap = false; | |
287 | } | |
288 | ||
289 | // set the sample format | |
290 | snd_pcm_format_t *fmt = alsa.format ? &alsa.format : (snd_pcm_format_t *)fmts; | |
291 | do { | |
292 | if (snd_pcm_hw_params_set_format(*pcmp, hw_params, *fmt) >= 0) { | |
293 | LOG_INFO("opened device %s using format: %s sample rate: %u mmap: %u", alsa.device, snd_pcm_format_name(*fmt), sample_rate, alsa.mmap); | |
294 | alsa.format = *fmt; | |
295 | break; | |
296 | } | |
297 | if (alsa.format) { | |
298 | LOG_ERROR("unable to open audio device requested format: %s", snd_pcm_format_name(alsa.format)); | |
299 | return -1; | |
300 | } | |
301 | ++fmt; | |
302 | if (*fmt == SND_PCM_FORMAT_UNKNOWN) { | |
303 | LOG_ERROR("unable to open audio device with any supported format"); | |
304 | return -1; | |
305 | } | |
306 | } while (*fmt != SND_PCM_FORMAT_UNKNOWN); | |
307 | ||
308 | // set channels | |
309 | if ((err = snd_pcm_hw_params_set_channels (*pcmp, hw_params, 2)) < 0) { | |
310 | LOG_ERROR("channel count not available: %s", snd_strerror(err)); | |
311 | return err; | |
312 | } | |
313 | ||
314 | // set period size - value of < 50 treated as period count, otherwise size in bytes | |
315 | if (alsa_period < 50) { | |
316 | unsigned count = alsa_period; | |
317 | if ((err = snd_pcm_hw_params_set_periods_near(*pcmp, hw_params, &count, 0)) < 0) { | |
318 | LOG_ERROR("unable to set period count %s", snd_strerror(err)); | |
319 | return err; | |
320 | } | |
321 | } else { | |
322 | snd_pcm_uframes_t size = alsa_period; | |
323 | int dir = 0; | |
324 | if ((err = snd_pcm_hw_params_set_period_size_near(*pcmp, hw_params, &size, &dir)) < 0) { | |
325 | LOG_ERROR("unable to set period size %s", snd_strerror(err)); | |
326 | return err; | |
327 | } | |
328 | } | |
329 | ||
330 | // set buffer size - value of < 500 treated as buffer time in ms, otherwise size in bytes | |
331 | if (alsa_buffer < 500) { | |
332 | unsigned time = alsa_buffer * 1000; | |
333 | int dir = 0; | |
334 | if ((err = snd_pcm_hw_params_set_buffer_time_near(*pcmp, hw_params, &time, &dir)) < 0) { | |
335 | LOG_ERROR("unable to set buffer time %s", snd_strerror(err)); | |
336 | return err; | |
337 | } | |
338 | } else { | |
339 | snd_pcm_uframes_t size = alsa_buffer; | |
340 | if ((err = snd_pcm_hw_params_set_buffer_size_near(*pcmp, hw_params, &size)) < 0) { | |
341 | LOG_ERROR("unable to set buffer size %s", snd_strerror(err)); | |
342 | return err; | |
343 | } | |
344 | } | |
345 | ||
346 | // get period_size | |
347 | if ((err = snd_pcm_hw_params_get_period_size(hw_params, &alsa.period_size, 0)) < 0) { | |
348 | LOG_ERROR("unable to get period size: %s", snd_strerror(err)); | |
349 | return err; | |
350 | } | |
351 | ||
352 | // get buffer_size | |
353 | if ((err = snd_pcm_hw_params_get_buffer_size(hw_params, &alsa.buffer_size)) < 0) { | |
354 | LOG_ERROR("unable to get buffer size: %s", snd_strerror(err)); | |
355 | return err; | |
356 | } | |
357 | ||
358 | LOG_INFO("buffer: %u period: %u -> buffer size: %u period size: %u", alsa_buffer, alsa_period, alsa.buffer_size, alsa.period_size); | |
359 | ||
360 | // create an intermediate buffer for non mmap case for all but NATIVE_FORMAT | |
361 | // this is used to pack samples into the output format before calling writei | |
362 | if (!alsa.mmap && !alsa.write_buf && alsa.format != NATIVE_FORMAT) { | |
363 | alsa.write_buf = malloc(alsa.period_size * BYTES_PER_FRAME); | |
364 | if (!alsa.write_buf) { | |
365 | LOG_ERROR("unable to malloc write_buf"); | |
366 | return -1; | |
367 | } | |
368 | } | |
369 | ||
370 | // set params | |
371 | if ((err = snd_pcm_hw_params(*pcmp, hw_params)) < 0) { | |
372 | LOG_ERROR("unable to set hw params: %s", snd_strerror(err)); | |
373 | return err; | |
374 | } | |
375 | ||
376 | // dump info | |
377 | if (loglevel == lSDEBUG) { | |
378 | static snd_output_t *debug_output; | |
379 | snd_output_stdio_attach(&debug_output, stderr, 0); | |
380 | snd_pcm_dump(*pcmp, debug_output); | |
381 | } | |
382 | ||
383 | // this indicates we have opened the device ok | |
384 | alsa.rate = sample_rate; | |
385 | ||
386 | return 0; | |
387 | } | |
388 | ||
389 | #endif // ALSA | |
390 | ||
391 | #if PORTAUDIO | |
392 | ||
393 | void list_devices(void) { | |
394 | PaError err; | |
395 | int i; | |
396 | ||
397 | if ((err = Pa_Initialize()) != paNoError) { | |
398 | LOG_WARN("error initialising port audio: %s", Pa_GetErrorText(err)); | |
399 | return; | |
400 | } | |
401 | ||
402 | printf("Output devices:\n"); | |
403 | for (i = 0; i < Pa_GetDeviceCount(); ++i) { | |
404 | printf(" %i - %s [%s]\n", i, Pa_GetDeviceInfo(i)->name, Pa_GetHostApiInfo(Pa_GetDeviceInfo(i)->hostApi)->name); | |
405 | } | |
406 | printf("\n"); | |
407 | ||
408 | if ((err = Pa_Terminate()) != paNoError) { | |
409 | LOG_WARN("error closing port audio: %s", Pa_GetErrorText(err)); | |
410 | } | |
411 | } | |
412 | ||
413 | static int pa_device_id(const char *device) { | |
414 | int len = strlen(device); | |
415 | int i; | |
416 | ||
417 | if (!strncmp(device, "default", 7)) { | |
418 | return Pa_GetDefaultOutputDevice(); | |
419 | } | |
420 | if (len >= 1 && len <= 2 && device[0] >= '0' && device[0] <= '9') { | |
421 | return atoi(device); | |
422 | } | |
423 | ||
424 | for (i = 0; i < Pa_GetDeviceCount(); ++i) { | |
425 | if (!strncmp(Pa_GetDeviceInfo(i)->name, device, len)) { | |
426 | return i; | |
427 | } | |
428 | } | |
429 | ||
430 | return -1; | |
431 | } | |
432 | ||
433 | static int pa_callback(const void *pa_input, void *pa_output, unsigned long pa_frames_wanted, | |
434 | const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData); | |
435 | ||
436 | static bool test_open(const char *device, u32_t *max_rate) { | |
437 | PaStreamParameters outputParameters; | |
438 | PaError err; | |
439 | u32_t rates[] = { 384000, 352800, 192000, 176400, 96000, 88200, 48000, 44100, 0 }; | |
440 | int device_id, i; | |
441 | ||
442 | if ((device_id = pa_device_id(device)) == -1) { | |
443 | LOG_INFO("device %s not found", device); | |
444 | return false; | |
445 | } | |
446 | ||
447 | outputParameters.device = device_id; | |
448 | outputParameters.channelCount = 2; | |
449 | outputParameters.sampleFormat = paInt32; | |
450 | outputParameters.suggestedLatency = | |
451 | output.latency ? (double)output.latency/(double)1000 : Pa_GetDeviceInfo(outputParameters.device)->defaultHighOutputLatency; | |
452 | outputParameters.hostApiSpecificStreamInfo = NULL; | |
453 | ||
454 | // check supported sample rates | |
455 | // Note use Pa_OpenStream as it appears more reliable than Pa_OpenStream on some windows apis | |
456 | for (i = 0; rates[i]; ++i) { | |
457 | err = Pa_OpenStream(&pa.stream, NULL, &outputParameters, (double)rates[i], paFramesPerBufferUnspecified, paNoFlag, | |
458 | pa_callback, NULL); | |
459 | if (err == paNoError) { | |
460 | Pa_CloseStream(pa.stream); | |
461 | *max_rate = rates[i]; | |
462 | break; | |
463 | } | |
464 | } | |
465 | ||
466 | if (!rates[i]) { | |
467 | LOG_WARN("no available rate found"); | |
468 | return false; | |
469 | } | |
470 | ||
471 | if ((err = Pa_OpenStream(&pa.stream, NULL, &outputParameters, (double)*max_rate, paFramesPerBufferUnspecified, | |
472 | paNoFlag, pa_callback, NULL)) != paNoError) { | |
473 | LOG_WARN("error opening stream: %s", Pa_GetErrorText(err)); | |
474 | return false; | |
475 | } | |
476 | ||
477 | if ((err = Pa_CloseStream(pa.stream)) != paNoError) { | |
478 | LOG_WARN("error closing stream: %s", Pa_GetErrorText(err)); | |
479 | return false; | |
480 | } | |
481 | ||
482 | pa.stream = NULL; | |
483 | ||
484 | return true; | |
485 | } | |
486 | ||
487 | static void pa_stream_finished(void *userdata) { | |
488 | if (running) { | |
489 | LOG_INFO("stream finished"); | |
490 | LOCK; | |
491 | output.pa_reopen = true; | |
40 | // functions starting _* are called with mutex locked | |
41 | ||
42 | frames_t _output_frames(frames_t avail) { | |
43 | ||
44 | frames_t frames, size; | |
45 | bool silence; | |
46 | ||
47 | s32_t cross_gain_in = 0, cross_gain_out = 0; s32_t *cross_ptr = NULL; | |
48 | ||
49 | s32_t gainL = output.current_replay_gain ? gain(output.gainL, output.current_replay_gain) : output.gainL; | |
50 | s32_t gainR = output.current_replay_gain ? gain(output.gainR, output.current_replay_gain) : output.gainR; | |
51 | ||
52 | frames = _buf_used(outputbuf) / BYTES_PER_FRAME; | |
53 | silence = false; | |
54 | ||
55 | // start when threshold met | |
56 | if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 100 && frames > output.start_frames) { | |
57 | output.state = OUTPUT_RUNNING; | |
58 | LOG_INFO("start buffer frames: %u", frames); | |
492 | 59 | wake_controller(); |
493 | UNLOCK; | |
494 | } | |
495 | } | |
496 | ||
497 | static thread_type monitor_thread; | |
498 | bool monitor_thread_running = false; | |
499 | ||
500 | static void *pa_monitor() { | |
501 | bool output_off; | |
502 | ||
503 | LOCK; | |
504 | ||
505 | if (monitor_thread_running) { | |
506 | LOG_DEBUG("monitor thread already running"); | |
507 | UNLOCK; | |
508 | return 0; | |
509 | } | |
510 | ||
511 | LOG_DEBUG("start monitor thread"); | |
512 | ||
513 | monitor_thread_running = true; | |
514 | output_off = (output.state == OUTPUT_OFF); | |
515 | ||
516 | while (monitor_thread_running) { | |
517 | if (output_off) { | |
518 | if (output.state != OUTPUT_OFF) { | |
519 | LOG_INFO("output on"); | |
520 | break; | |
521 | } | |
60 | } | |
61 | ||
62 | // skip ahead - consume outputbuf but play nothing | |
63 | if (output.state == OUTPUT_SKIP_FRAMES) { | |
64 | if (frames > 0) { | |
65 | frames_t skip = min(frames, output.skip_frames); | |
66 | LOG_INFO("skip %u of %u frames", skip, output.skip_frames); | |
67 | frames -= skip; | |
68 | output.frames_played += skip; | |
69 | while (skip > 0) { | |
70 | frames_t cont_frames = min(skip, _buf_cont_read(outputbuf) / BYTES_PER_FRAME); | |
71 | skip -= cont_frames; | |
72 | _buf_inc_readp(outputbuf, cont_frames * BYTES_PER_FRAME); | |
73 | } | |
74 | } | |
75 | output.state = OUTPUT_RUNNING; | |
76 | } | |
77 | ||
78 | // pause frames - play silence for required frames | |
79 | if (output.state == OUTPUT_PAUSE_FRAMES) { | |
80 | LOG_INFO("pause %u frames", output.pause_frames); | |
81 | if (output.pause_frames == 0) { | |
82 | output.state = OUTPUT_RUNNING; | |
522 | 83 | } else { |
523 | // this is a hack to partially support hot plugging of devices | |
524 | // we rely on terminating and reinitalising PA to get an updated list of devices and use name for output.device | |
525 | LOG_INFO("probing device %s", output.device); | |
526 | Pa_Terminate(); | |
527 | Pa_Initialize(); | |
528 | pa.stream = NULL; | |
529 | if (pa_device_id(output.device) != -1) { | |
530 | LOG_INFO("device reopen"); | |
531 | break; | |
532 | } | |
533 | } | |
534 | ||
535 | UNLOCK; | |
536 | sleep(output_off ? 1 : 5); | |
537 | LOCK; | |
538 | } | |
539 | ||
540 | LOG_DEBUG("end monitor thread"); | |
541 | ||
542 | monitor_thread_running = false; | |
543 | pa.stream = NULL; | |
544 | ||
545 | _pa_open(); | |
546 | ||
547 | UNLOCK; | |
548 | ||
549 | return 0; | |
550 | } | |
551 | ||
552 | void _pa_open(void) { | |
553 | PaStreamParameters outputParameters; | |
554 | PaError err = paNoError; | |
555 | int device_id; | |
556 | ||
557 | if (pa.stream) { | |
558 | if ((err = Pa_CloseStream(pa.stream)) != paNoError) { | |
559 | LOG_WARN("error closing stream: %s", Pa_GetErrorText(err)); | |
560 | } | |
561 | } | |
562 | ||
563 | if (output.state == OUTPUT_OFF) { | |
564 | // we get called when transitioning to OUTPUT_OFF to create the probe thread | |
565 | // set err to avoid opening device and logging messages | |
566 | err = 1; | |
567 | ||
568 | } else if ((device_id = pa_device_id(output.device)) == -1) { | |
569 | LOG_INFO("device %s not found", output.device); | |
570 | err = 1; | |
571 | ||
572 | } else { | |
573 | ||
574 | outputParameters.device = device_id; | |
575 | outputParameters.channelCount = 2; | |
576 | outputParameters.sampleFormat = paInt32; | |
577 | outputParameters.suggestedLatency = | |
578 | output.latency ? (double)output.latency/(double)1000 : Pa_GetDeviceInfo(outputParameters.device)->defaultHighOutputLatency; | |
579 | outputParameters.hostApiSpecificStreamInfo = NULL; | |
84 | silence = true; | |
85 | frames = min(avail, output.pause_frames); | |
86 | frames = min(frames, MAX_SILENCE_FRAMES); | |
87 | output.pause_frames -= frames; | |
88 | } | |
89 | } | |
90 | ||
91 | // start at - play silence until jiffies reached | |
92 | if (output.state == OUTPUT_START_AT) { | |
93 | u32_t now = gettime_ms(); | |
94 | if (now >= output.start_at || output.start_at > now + 10000) { | |
95 | output.state = OUTPUT_RUNNING; | |
96 | } else { | |
97 | u32_t delta_frames = (output.start_at - now) * output.current_sample_rate / 1000; | |
98 | silence = true; | |
99 | frames = min(avail, delta_frames); | |
100 | frames = min(frames, MAX_SILENCE_FRAMES); | |
101 | } | |
102 | } | |
103 | ||
104 | // play silence if buffering or no frames | |
105 | if (output.state <= OUTPUT_BUFFER || frames == 0) { | |
106 | silence = true; | |
107 | frames = min(avail, MAX_SILENCE_FRAMES); | |
108 | } | |
109 | ||
110 | LOG_SDEBUG("avail: %d frames: %d silence: %d", avail, frames, silence); | |
111 | frames = min(frames, avail); | |
112 | size = frames; | |
113 | ||
114 | while (size > 0) { | |
115 | frames_t out_frames; | |
116 | frames_t cont_frames = _buf_cont_read(outputbuf) / BYTES_PER_FRAME; | |
117 | int wrote; | |
580 | 118 | |
581 | #if OSX | |
582 | // enable pro mode which aims to avoid resampling if possible | |
583 | // see http://code.google.com/p/squeezelite/issues/detail?id=11 & http://code.google.com/p/squeezelite/issues/detail?id=37 | |
584 | // command line controls osx_playnice which is -1 if not specified, 0 or 1 - choose playnice if -1 or 1 | |
585 | PaMacCoreStreamInfo macInfo; | |
586 | unsigned long streamInfoFlags; | |
587 | if (output.osx_playnice) { | |
588 | LOG_INFO("opening device in PlayNice mode"); | |
589 | streamInfoFlags = paMacCorePlayNice; | |
590 | } else { | |
591 | LOG_INFO("opening device in Pro mode"); | |
592 | streamInfoFlags = paMacCorePro; | |
593 | } | |
594 | PaMacCore_SetupStreamInfo(&macInfo, streamInfoFlags); | |
595 | outputParameters.hostApiSpecificStreamInfo = &macInfo; | |
596 | #endif | |
597 | } | |
598 | ||
599 | if (!err && | |
600 | (err = Pa_OpenStream(&pa.stream, NULL, &outputParameters, (double)output.current_sample_rate, paFramesPerBufferUnspecified, | |
601 | paPrimeOutputBuffersUsingStreamCallback | paDitherOff, pa_callback, NULL)) != paNoError) { | |
602 | LOG_WARN("error opening device %i - %s : %s", outputParameters.device, Pa_GetDeviceInfo(outputParameters.device)->name, | |
603 | Pa_GetErrorText(err)); | |
604 | } | |
605 | ||
606 | if (!err) { | |
607 | LOG_INFO("opened device %i - %s at %u latency %u ms", outputParameters.device, Pa_GetDeviceInfo(outputParameters.device)->name, | |
608 | (unsigned int)Pa_GetStreamInfo(pa.stream)->sampleRate, (unsigned int)(Pa_GetStreamInfo(pa.stream)->outputLatency * 1000)); | |
609 | ||
610 | pa.rate = output.current_sample_rate; | |
611 | ||
612 | if ((err = Pa_SetStreamFinishedCallback(pa.stream, pa_stream_finished)) != paNoError) { | |
613 | LOG_WARN("error setting finish callback: %s", Pa_GetErrorText(err)); | |
614 | } | |
615 | ||
616 | if ((err = Pa_StartStream(pa.stream)) != paNoError) { | |
617 | LOG_WARN("error starting stream: %s", Pa_GetErrorText(err)); | |
618 | } | |
619 | } | |
620 | ||
621 | if (err && !monitor_thread_running) { | |
622 | // create a thread to check for output state change or device return | |
623 | #if LINUX || OSX | |
624 | pthread_create(&monitor_thread, NULL, pa_monitor, NULL); | |
625 | #endif | |
626 | #if WIN | |
627 | monitor_thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&pa_monitor, NULL, 0, NULL); | |
628 | #endif | |
629 | } | |
630 | } | |
631 | ||
632 | #endif // PORTAUDIO | |
633 | ||
634 | ||
635 | #if ALSA | |
636 | ||
637 | // output thread for Alsa | |
638 | ||
639 | static void *output_thread(void *arg) { | |
640 | snd_pcm_t *pcmp = NULL; | |
641 | bool start = true; | |
642 | bool output_off = false, probe_device = (arg != NULL); | |
643 | int err; | |
644 | ||
645 | while (running) { | |
646 | ||
647 | // disabled output - player is off | |
648 | while (output_off) { | |
649 | usleep(100000); | |
650 | LOCK; | |
651 | output_off = (output.state == OUTPUT_OFF); | |
652 | UNLOCK; | |
653 | if (!running) return 0; | |
654 | } | |
655 | ||
656 | // wait until device returns - to allow usb audio devices to be turned off | |
657 | if (probe_device) { | |
658 | while (!pcm_probe(output.device)) { | |
659 | LOG_DEBUG("waiting for device %s to return", output.device); | |
660 | sleep(5); | |
661 | } | |
662 | probe_device = false; | |
663 | } | |
664 | ||
665 | if (!pcmp || alsa.rate != output.current_sample_rate) { | |
666 | LOG_INFO("open output device: %s", output.device); | |
667 | if (!!alsa_open(&pcmp, output.device, output.current_sample_rate, output.buffer, output.period)) { | |
668 | sleep(5); | |
119 | if (output.track_start && !silence) { | |
120 | if (output.track_start == outputbuf->readp) { | |
121 | LOG_INFO("track start sample rate: %u replay_gain: %u", output.next_sample_rate, output.next_replay_gain); | |
122 | output.frames_played = 0; | |
123 | output.track_started = true; | |
124 | output.current_sample_rate = output.next_sample_rate; | |
125 | IF_DSD( output.dop = output.next_dop; ) | |
126 | if (!output.fade == FADE_ACTIVE || !output.fade_mode == FADE_CROSSFADE) { | |
127 | output.current_replay_gain = output.next_replay_gain; | |
128 | } | |
129 | output.track_start = NULL; | |
669 | 130 | continue; |
670 | } | |
671 | start = true; | |
672 | } | |
673 | ||
674 | snd_pcm_state_t state = snd_pcm_state(pcmp); | |
675 | ||
676 | if (state == SND_PCM_STATE_XRUN) { | |
677 | LOG_INFO("XRUN"); | |
678 | if ((err = snd_pcm_recover(pcmp, -EPIPE, 1)) < 0) { | |
679 | LOG_INFO("XRUN recover failed: %s", snd_strerror(err)); | |
680 | } | |
681 | start = true; | |
682 | continue; | |
683 | } else if (state == SND_PCM_STATE_SUSPENDED) { | |
684 | if ((err = snd_pcm_recover(pcmp, -ESTRPIPE, 1)) < 0) { | |
685 | LOG_INFO("SUSPEND recover failed: %s", snd_strerror(err)); | |
686 | } | |
687 | } else if (state == SND_PCM_STATE_DISCONNECTED) { | |
688 | LOG_INFO("Device %s no longer available", output.device); | |
689 | alsa_close(pcmp); | |
690 | pcmp = NULL; | |
691 | probe_device = true; | |
692 | continue; | |
693 | } | |
694 | ||
695 | if (start && alsa.mmap) { | |
696 | if ((err = snd_pcm_start(pcmp)) < 0) { | |
697 | if ((err = snd_pcm_recover(pcmp, err, 1)) < 0) { | |
698 | if (err == -ENODEV) { | |
699 | LOG_INFO("Device %s no longer available", output.device); | |
700 | alsa_close(pcmp); | |
701 | pcmp = NULL; | |
702 | probe_device = true; | |
703 | continue; | |
704 | } | |
705 | LOG_INFO("start error: %s", snd_strerror(err)); | |
131 | } else if (output.track_start > outputbuf->readp) { | |
132 | // reduce cont_frames so we find the next track start at beginning of next chunk | |
133 | cont_frames = min(cont_frames, (output.track_start - outputbuf->readp) / BYTES_PER_FRAME); | |
134 | } | |
135 | } | |
136 | ||
137 | IF_DSD( | |
138 | if (output.dop) { | |
139 | gainL = gainR = FIXED_ONE; | |
140 | } | |
141 | ) | |
142 | ||
143 | if (output.fade && !silence) { | |
144 | if (output.fade == FADE_DUE) { | |
145 | if (output.fade_start == outputbuf->readp) { | |
146 | LOG_INFO("fade start reached"); | |
147 | output.fade = FADE_ACTIVE; | |
148 | } else if (output.fade_start > outputbuf->readp) { | |
149 | cont_frames = min(cont_frames, (output.fade_start - outputbuf->readp) / BYTES_PER_FRAME); | |
706 | 150 | } |
707 | } else { | |
708 | start = false; | |
709 | } | |
710 | } | |
711 | ||
712 | snd_pcm_sframes_t avail = snd_pcm_avail_update(pcmp); | |
713 | ||
714 | if (avail < 0) { | |
715 | if ((err = snd_pcm_recover(pcmp, avail, 1)) < 0) { | |
716 | if (err == -ENODEV) { | |
717 | LOG_INFO("Device %s no longer available", output.device); | |
718 | alsa_close(pcmp); | |
719 | pcmp = NULL; | |
720 | probe_device = true; | |
721 | continue; | |
722 | } | |
723 | LOG_WARN("recover failed: %s", snd_strerror(err)); | |
724 | } | |
725 | start = true; | |
726 | continue; | |
727 | } | |
728 | ||
729 | if (avail < alsa.period_size) { | |
730 | if ((err = snd_pcm_wait(pcmp, 1000)) < 0) { | |
731 | if ((err = snd_pcm_recover(pcmp, err, 1)) < 0) { | |
732 | LOG_INFO("pcm wait error: %s", snd_strerror(err)); | |
733 | } | |
734 | start = true; | |
735 | continue; | |
736 | } | |
737 | avail = snd_pcm_avail_update(pcmp); | |
738 | } | |
739 | ||
740 | // restrict avail in writei mode as write_buf is restricted to period_size | |
741 | if (!alsa.mmap) { | |
742 | avail = min(avail, alsa.period_size); | |
743 | } | |
744 | ||
745 | // avoid spinning in cases where wait returns but no bytes available (seen with pulse audio) | |
746 | if (avail == 0) { | |
747 | LOG_SDEBUG("avail 0 - sleeping"); | |
748 | usleep(10000); | |
749 | continue; | |
750 | } | |
751 | ||
752 | LOCK; | |
753 | ||
754 | // turn off if requested | |
755 | if (output.state == OUTPUT_OFF) { | |
756 | UNLOCK; | |
757 | alsa_close(pcmp); | |
758 | pcmp = NULL; | |
759 | output_off = true; | |
760 | LOG_INFO("disabling output"); | |
761 | #if VISEXPORT | |
762 | if (vis_mmap) { | |
763 | pthread_rwlock_wrlock(&vis_mmap->rwlock); | |
764 | vis_mmap->running = false; | |
765 | pthread_rwlock_unlock(&vis_mmap->rwlock); | |
766 | } | |
767 | #endif | |
768 | continue; | |
769 | } | |
770 | ||
771 | #endif // ALSA | |
772 | ||
773 | #if PORTAUDIO | |
774 | static int pa_callback(const void *pa_input, void *pa_output, unsigned long pa_frames_wanted, | |
775 | const PaStreamCallbackTimeInfo *time_info, PaStreamCallbackFlags statusFlags, void *userData) { | |
776 | ||
777 | u8_t *optr = (u8_t *)pa_output; | |
778 | frames_t avail = pa_frames_wanted; | |
779 | #endif | |
780 | frames_t frames, size; | |
781 | bool silence; | |
782 | ||
783 | s32_t cross_gain_in = 0, cross_gain_out = 0; s32_t *cross_ptr = NULL; | |
784 | ||
785 | #if PORTAUDIO | |
786 | LOCK; | |
787 | #endif | |
788 | ||
789 | frames = _buf_used(outputbuf) / BYTES_PER_FRAME; | |
790 | silence = false; | |
791 | ||
792 | // start when threshold met, note: avail * 4 may need tuning | |
793 | if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 100 && | |
794 | #if ALSA | |
795 | frames > alsa.buffer_size * 2 | |
796 | #endif | |
797 | #if PORTAUDIO | |
798 | frames > avail * 4 | |
799 | #endif | |
800 | ) { | |
801 | output.state = OUTPUT_RUNNING; | |
802 | wake_controller(); | |
803 | } | |
804 | ||
805 | // skip ahead - consume outputbuf but play nothing | |
806 | if (output.state == OUTPUT_SKIP_FRAMES) { | |
807 | if (frames > 0) { | |
808 | frames_t skip = min(frames, output.skip_frames); | |
809 | LOG_INFO("skip %u of %u frames", skip, output.skip_frames); | |
810 | frames -= skip; | |
811 | output.frames_played += skip; | |
812 | while (skip > 0) { | |
813 | frames_t cont_frames = min(skip, _buf_cont_read(outputbuf) / BYTES_PER_FRAME); | |
814 | skip -= cont_frames; | |
815 | _buf_inc_readp(outputbuf, cont_frames * BYTES_PER_FRAME); | |
816 | } | |
817 | } | |
818 | output.state = OUTPUT_RUNNING; | |
819 | } | |
820 | ||
821 | // pause frames - play silence for required frames | |
822 | if (output.state == OUTPUT_PAUSE_FRAMES) { | |
823 | LOG_INFO("pause %u frames", output.pause_frames); | |
824 | if (output.pause_frames == 0) { | |
825 | output.state = OUTPUT_RUNNING; | |
826 | } else { | |
827 | silence = true; | |
828 | frames = min(avail, output.pause_frames); | |
829 | frames = min(frames, MAX_SILENCE_FRAMES); | |
830 | output.pause_frames -= frames; | |
831 | } | |
832 | } | |
833 | ||
834 | // start at - play slience until jiffies reached | |
835 | if (output.state == OUTPUT_START_AT) { | |
836 | u32_t now = gettime_ms(); | |
837 | if (now >= output.start_at || output.start_at > now + 10000) { | |
838 | output.state = OUTPUT_RUNNING; | |
839 | } else { | |
840 | u32_t delta_frames = (output.start_at - now) * output.current_sample_rate / 1000; | |
841 | silence = true; | |
842 | frames = min(avail, delta_frames); | |
843 | frames = min(frames, MAX_SILENCE_FRAMES); | |
844 | } | |
845 | } | |
846 | ||
847 | // play slience if buffering or no frames | |
848 | if (output.state <= OUTPUT_BUFFER || frames == 0) { | |
849 | silence = true; | |
850 | frames = min(avail, MAX_SILENCE_FRAMES); | |
851 | } | |
852 | ||
853 | LOG_SDEBUG("avail: %d frames: %d silence: %d", avail, frames, silence); | |
854 | frames = min(frames, avail); | |
855 | size = frames; | |
856 | ||
857 | #if ALSA | |
858 | snd_pcm_sframes_t delay; | |
859 | snd_pcm_delay(pcmp, &delay); | |
860 | if (delay >= 0) { | |
861 | output.device_frames = delay; | |
862 | } else { | |
863 | LOG_WARN("snd_pcm_delay returns: %d", delay); | |
864 | if (delay == -EPIPE) { | |
865 | // EPIPE indicates underrun - attempt to recover | |
866 | UNLOCK; | |
867 | continue; | |
868 | } | |
869 | } | |
870 | #endif | |
871 | #if PORTAUDIO | |
872 | output.device_frames = (unsigned)((time_info->outputBufferDacTime - Pa_GetStreamTime(pa.stream)) * output.current_sample_rate); | |
873 | #endif | |
874 | output.updated = gettime_ms(); | |
875 | ||
876 | while (size > 0) { | |
877 | frames_t out_frames; | |
878 | ||
879 | frames_t cont_frames = _buf_cont_read(outputbuf) / BYTES_PER_FRAME; | |
880 | ||
881 | s32_t gainL = output.current_replay_gain ? gain(output.gainL, output.current_replay_gain) : output.gainL; | |
882 | s32_t gainR = output.current_replay_gain ? gain(output.gainR, output.current_replay_gain) : output.gainR; | |
883 | ||
884 | if (output.track_start && !silence) { | |
885 | if (output.track_start == outputbuf->readp) { | |
886 | LOG_INFO("track start sample rate: %u replay_gain: %u", output.next_sample_rate, output.next_replay_gain); | |
887 | output.frames_played = 0; | |
888 | output.track_started = true; | |
889 | output.current_sample_rate = output.next_sample_rate; | |
890 | if (!output.fade == FADE_ACTIVE || !output.fade_mode == FADE_CROSSFADE) { | |
151 | } | |
152 | if (output.fade == FADE_ACTIVE) { | |
153 | // find position within fade | |
154 | frames_t cur_f = outputbuf->readp >= output.fade_start ? (outputbuf->readp - output.fade_start) / BYTES_PER_FRAME : | |
155 | (outputbuf->readp + outputbuf->size - output.fade_start) / BYTES_PER_FRAME; | |
156 | frames_t dur_f = output.fade_end >= output.fade_start ? (output.fade_end - output.fade_start) / BYTES_PER_FRAME : | |
157 | (output.fade_end + outputbuf->size - output.fade_start) / BYTES_PER_FRAME; | |
158 | if (cur_f >= dur_f) { | |
159 | if (output.fade_mode == FADE_INOUT && output.fade_dir == FADE_DOWN) { | |
160 | LOG_INFO("fade down complete, starting fade up"); | |
161 | output.fade_dir = FADE_UP; | |
162 | output.fade_start = outputbuf->readp; | |
163 | output.fade_end = outputbuf->readp + dur_f * BYTES_PER_FRAME; | |
164 | if (output.fade_end >= outputbuf->wrap) { | |
165 | output.fade_end -= outputbuf->size; | |
166 | } | |
167 | cur_f = 0; | |
168 | } else if (output.fade_mode == FADE_CROSSFADE) { | |
169 | LOG_INFO("crossfade complete"); | |
170 | if (_buf_used(outputbuf) >= dur_f * BYTES_PER_FRAME) { | |
171 | _buf_inc_readp(outputbuf, dur_f * BYTES_PER_FRAME); | |
172 | LOG_INFO("skipped crossfaded start"); | |
173 | } else { | |
174 | LOG_WARN("unable to skip crossfaded start"); | |
175 | } | |
176 | output.fade = FADE_INACTIVE; | |
891 | 177 | output.current_replay_gain = output.next_replay_gain; |
892 | } | |
893 | output.track_start = NULL; | |
894 | if (output.current_sample_rate != output.next_sample_rate) { | |
895 | output.current_sample_rate = output.next_sample_rate; | |
896 | break; | |
897 | } | |
898 | continue; | |
899 | } else if (output.track_start > outputbuf->readp) { | |
900 | // reduce cont_frames so we find the next track start at beginning of next chunk | |
901 | cont_frames = min(cont_frames, (output.track_start - outputbuf->readp) / BYTES_PER_FRAME); | |
902 | } | |
903 | } | |
904 | ||
905 | if (output.fade && !silence) { | |
906 | if (output.fade == FADE_DUE) { | |
907 | if (output.fade_start == outputbuf->readp) { | |
908 | LOG_INFO("fade start reached"); | |
909 | output.fade = FADE_ACTIVE; | |
910 | } else if (output.fade_start > outputbuf->readp) { | |
911 | cont_frames = min(cont_frames, (output.fade_start - outputbuf->readp) / BYTES_PER_FRAME); | |
178 | } else { | |
179 | LOG_INFO("fade complete"); | |
180 | output.fade = FADE_INACTIVE; | |
912 | 181 | } |
913 | 182 | } |
914 | if (output.fade == FADE_ACTIVE) { | |
915 | // find position within fade | |
916 | frames_t cur_f = outputbuf->readp >= output.fade_start ? (outputbuf->readp - output.fade_start) / BYTES_PER_FRAME : | |
917 | (outputbuf->readp + outputbuf->size - output.fade_start) / BYTES_PER_FRAME; | |
918 | frames_t dur_f = output.fade_end >= output.fade_start ? (output.fade_end - output.fade_start) / BYTES_PER_FRAME : | |
919 | (output.fade_end + outputbuf->size - output.fade_start) / BYTES_PER_FRAME; | |
920 | if (cur_f >= dur_f) { | |
921 | if (output.fade_mode == FADE_INOUT && output.fade_dir == FADE_DOWN) { | |
922 | LOG_INFO("fade down complete, starting fade up"); | |
923 | output.fade_dir = FADE_UP; | |
924 | output.fade_start = outputbuf->readp; | |
925 | output.fade_end = outputbuf->readp + dur_f * BYTES_PER_FRAME; | |
926 | if (output.fade_end >= outputbuf->wrap) { | |
927 | output.fade_end -= outputbuf->size; | |
183 | // if fade in progress set fade gain, ensure cont_frames reduced so we get to end of fade at start of chunk | |
184 | if (output.fade) { | |
185 | if (output.fade_end > outputbuf->readp) { | |
186 | cont_frames = min(cont_frames, (output.fade_end - outputbuf->readp) / BYTES_PER_FRAME); | |
187 | } | |
188 | if (output.fade_dir == FADE_UP || output.fade_dir == FADE_DOWN) { | |
189 | // fade in, in-out, out handled via altering standard gain | |
190 | s32_t fade_gain; | |
191 | if (output.fade_dir == FADE_DOWN) { | |
192 | cur_f = dur_f - cur_f; | |
193 | } | |
194 | fade_gain = to_gain((float)cur_f / (float)dur_f); | |
195 | gainL = gain(gainL, fade_gain); | |
196 | gainR = gain(gainR, fade_gain); | |
197 | } | |
198 | if (output.fade_dir == FADE_CROSS) { | |
199 | // cross fade requires special treatment - performed later based on these values | |
200 | // support different replay gain for old and new track by retaining old value until crossfade completes | |
201 | if (_buf_used(outputbuf) / BYTES_PER_FRAME > dur_f + size) { | |
202 | cross_gain_in = to_gain((float)cur_f / (float)dur_f); | |
203 | cross_gain_out = FIXED_ONE - cross_gain_in; | |
204 | if (output.current_replay_gain) { | |
205 | cross_gain_out = gain(cross_gain_out, output.current_replay_gain); | |
928 | 206 | } |
929 | cur_f = 0; | |
930 | } else if (output.fade_mode == FADE_CROSSFADE) { | |
931 | LOG_INFO("crossfade complete"); | |
932 | if (_buf_used(outputbuf) >= dur_f * BYTES_PER_FRAME) { | |
933 | _buf_inc_readp(outputbuf, dur_f * BYTES_PER_FRAME); | |
934 | LOG_INFO("skipped crossfaded start"); | |
935 | } else { | |
936 | LOG_WARN("unable to skip crossfaded start"); | |
207 | if (output.next_replay_gain) { | |
208 | cross_gain_in = gain(cross_gain_in, output.next_replay_gain); | |
937 | 209 | } |
938 | output.fade = FADE_INACTIVE; | |
939 | output.current_replay_gain = output.next_replay_gain; | |
210 | gainL = output.gainL; | |
211 | gainR = output.gainR; | |
212 | cross_ptr = (s32_t *)(output.fade_end + cur_f * BYTES_PER_FRAME); | |
940 | 213 | } else { |
941 | LOG_INFO("fade complete"); | |
214 | LOG_INFO("unable to continue crossfade - too few samples"); | |
942 | 215 | output.fade = FADE_INACTIVE; |
943 | 216 | } |
944 | 217 | } |
945 | // if fade in progress set fade gain, ensure cont_frames reduced so we get to end of fade at start of chunk | |
946 | if (output.fade) { | |
947 | if (output.fade_end > outputbuf->readp) { | |
948 | cont_frames = min(cont_frames, (output.fade_end - outputbuf->readp) / BYTES_PER_FRAME); | |
949 | } | |
950 | if (output.fade_dir == FADE_UP || output.fade_dir == FADE_DOWN) { | |
951 | // fade in, in-out, out handled via altering standard gain | |
952 | s32_t fade_gain; | |
953 | if (output.fade_dir == FADE_DOWN) { | |
954 | cur_f = dur_f - cur_f; | |
955 | } | |
956 | fade_gain = to_gain((float)cur_f / (float)dur_f); | |
957 | gainL = gain(gainL, fade_gain); | |
958 | gainR = gain(gainR, fade_gain); | |
959 | } | |
960 | if (output.fade_dir == FADE_CROSS) { | |
961 | // cross fade requires special treatment - performed later based on these values | |
962 | // support different replay gain for old and new track by retaining old value until crossfade completes | |
963 | if (_buf_used(outputbuf) / BYTES_PER_FRAME > dur_f + size) { | |
964 | cross_gain_in = to_gain((float)cur_f / (float)dur_f); | |
965 | cross_gain_out = FIXED_ONE - cross_gain_in; | |
966 | if (output.current_replay_gain) { | |
967 | cross_gain_out = gain(cross_gain_out, output.current_replay_gain); | |
968 | } | |
969 | if (output.next_replay_gain) { | |
970 | cross_gain_in = gain(cross_gain_in, output.next_replay_gain); | |
971 | } | |
972 | gainL = output.gainL; | |
973 | gainR = output.gainR; | |
974 | cross_ptr = (s32_t *)(output.fade_end + cur_f * BYTES_PER_FRAME); | |
975 | } else { | |
976 | LOG_INFO("unable to continue crossfade - too few samples"); | |
977 | output.fade = FADE_INACTIVE; | |
978 | } | |
979 | } | |
980 | } | |
981 | 218 | } |
982 | 219 | } |
983 | ||
984 | out_frames = !silence ? min(size, cont_frames) : size; | |
985 | ||
986 | #if ALSA | |
987 | if (alsa.mmap || alsa.format != NATIVE_FORMAT) { | |
988 | ||
989 | // in all alsa cases except NATIVE_FORMAT non mmap we take this path: | |
990 | // - mmap: scale and pack to output format, write direct into mmap region | |
991 | // - non mmap: scale and pack into alsa.write_buf, which is the used with writei to send to alsa | |
992 | const snd_pcm_channel_area_t *areas; | |
993 | snd_pcm_uframes_t offset; | |
994 | snd_pcm_uframes_t alsa_frames = (snd_pcm_uframes_t)out_frames; | |
995 | ||
996 | if (alsa.mmap) { | |
997 | ||
998 | snd_pcm_avail_update(pcmp); | |
999 | ||
1000 | if ((err = snd_pcm_mmap_begin(pcmp, &areas, &offset, &alsa_frames)) < 0) { | |
1001 | LOG_WARN("error from mmap_begin: %s", snd_strerror(err)); | |
1002 | break; | |
1003 | } | |
1004 | ||
1005 | out_frames = (frames_t)alsa_frames; | |
1006 | ||
1007 | // temp for debugging mmap issues | |
1008 | if ((areas[0].first + offset * areas[0].step) % 8 != 0) { | |
1009 | LOG_ERROR("Error: mmap offset not multiple of 8!"); | |
1010 | } | |
1011 | } | |
1012 | ||
1013 | // perform crossfade buffer copying here as we do not know the actual out_frames value until here | |
1014 | if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && cross_ptr) { | |
1015 | s32_t *ptr = (s32_t *)(void *)outputbuf->readp; | |
1016 | frames_t count = out_frames * 2; | |
1017 | while (count--) { | |
1018 | if (cross_ptr > (s32_t *)outputbuf->wrap) { | |
1019 | cross_ptr -= outputbuf->size / BYTES_PER_FRAME * 2; | |
1020 | } | |
1021 | *ptr = gain(cross_gain_out, *ptr) + gain(cross_gain_in, *cross_ptr); | |
1022 | ptr++; cross_ptr++; | |
1023 | } | |
1024 | } | |
1025 | ||
1026 | void *outputptr = alsa.mmap ? (areas[0].addr + (areas[0].first + offset * areas[0].step) / 8) : alsa.write_buf; | |
1027 | s32_t *inputptr = (s32_t *) (silence ? silencebuf : outputbuf->readp); | |
1028 | frames_t cnt = out_frames; | |
1029 | ||
1030 | switch(alsa.format) { | |
1031 | case SND_PCM_FORMAT_S16_LE: | |
1032 | { | |
1033 | u32_t *optr = (u32_t *)(void *)outputptr; | |
1034 | #if SL_LITTLE_ENDIAN | |
1035 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
1036 | while (cnt--) { | |
1037 | *(optr++) = (*(inputptr) >> 16 & 0x0000ffff) | (*(inputptr + 1) & 0xffff0000); | |
1038 | inputptr += 2; | |
1039 | } | |
1040 | } else { | |
1041 | while (cnt--) { | |
1042 | *(optr++) = (gain(gainL, *(inputptr)) >> 16 & 0x0000ffff) | (gain(gainR, *(inputptr+1)) & 0xffff0000); | |
1043 | inputptr += 2; | |
1044 | } | |
1045 | } | |
1046 | #else | |
1047 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
1048 | while (cnt--) { | |
1049 | s32_t lsample = *(inputptr++); | |
1050 | s32_t rsample = *(inputptr++); | |
1051 | *(optr++) = | |
1052 | (lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 | | |
1053 | (rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24; | |
1054 | } | |
1055 | } else { | |
1056 | while (cnt--) { | |
1057 | s32_t lsample = gain(gainL, *(inputptr++)); | |
1058 | s32_t rsample = gain(gainR, *(inputptr++)); | |
1059 | *(optr++) = | |
1060 | (lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 | | |
1061 | (rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24; | |
1062 | } | |
1063 | } | |
1064 | #endif | |
1065 | } | |
1066 | break; | |
1067 | case SND_PCM_FORMAT_S24_LE: | |
1068 | { | |
1069 | u32_t *optr = (u32_t *)(void *)outputptr; | |
1070 | #if SL_LITTLE_ENDIAN | |
1071 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
1072 | while (cnt--) { | |
1073 | *(optr++) = *(inputptr++) >> 8; | |
1074 | *(optr++) = *(inputptr++) >> 8; | |
1075 | } | |
1076 | } else { | |
1077 | while (cnt--) { | |
1078 | *(optr++) = gain(gainL, *(inputptr++)) >> 8; | |
1079 | *(optr++) = gain(gainR, *(inputptr++)) >> 8; | |
1080 | } | |
1081 | } | |
1082 | #else | |
1083 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
1084 | while (cnt--) { | |
1085 | s32_t lsample = *(inputptr++); | |
1086 | s32_t rsample = *(inputptr++); | |
1087 | *(optr++) = | |
1088 | (lsample & 0xff000000) >> 16 | (lsample & 0x00ff0000) | (lsample & 0x0000ff00 << 16); | |
1089 | *(optr++) = | |
1090 | (rsample & 0xff000000) >> 16 | (rsample & 0x00ff0000) | (rsample & 0x0000ff00 << 16); | |
1091 | } | |
1092 | } else { | |
1093 | while (cnt--) { | |
1094 | s32_t lsample = gain(gainL, *(inputptr++)); | |
1095 | s32_t rsample = gain(gainR, *(inputptr++)); | |
1096 | *(optr++) = | |
1097 | (lsample & 0xff000000) >> 16 | (lsample & 0x00ff0000) | (lsample & 0x0000ff00 << 16); | |
1098 | *(optr++) = | |
1099 | (rsample & 0xff000000) >> 16 | (rsample & 0x00ff0000) | (rsample & 0x0000ff00 << 16); | |
1100 | } | |
1101 | } | |
1102 | #endif | |
1103 | } | |
1104 | break; | |
1105 | case SND_PCM_FORMAT_S24_3LE: | |
1106 | { | |
1107 | u8_t *optr = (u8_t *)(void *)outputptr; | |
1108 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
1109 | while (cnt) { | |
1110 | // attempt to do 32 bit memory accesses - move 2 frames at once: 16 bytes -> 12 bytes | |
1111 | // falls through to exception case when not aligned or if less than 2 frames to move | |
1112 | if (((uintptr_t)optr & 0x3) == 0 && cnt >= 2) { | |
1113 | u32_t *o_ptr = (u32_t *)(void *)optr; | |
1114 | while (cnt >= 2) { | |
1115 | s32_t l1 = *(inputptr++); s32_t r1 = *(inputptr++); | |
1116 | s32_t l2 = *(inputptr++); s32_t r2 = *(inputptr++); | |
1117 | #if SL_LITTLE_ENDIAN | |
1118 | *(o_ptr++) = (l1 & 0xffffff00) >> 8 | (r1 & 0x0000ff00) << 16; | |
1119 | *(o_ptr++) = (r1 & 0xffff0000) >> 16 | (l2 & 0x00ffff00) << 8; | |
1120 | *(o_ptr++) = (l2 & 0xff000000) >> 24 | (r2 & 0xffffff00); | |
1121 | #else | |
1122 | *(o_ptr++) = (l1 & 0x0000ff00) << 16 | (l1 & 0x00ff0000) | (l1 & 0xff000000) >> 16 | | |
1123 | (r1 & 0x0000ff00) >> 8; | |
1124 | *(o_ptr++) = (r1 & 0x00ff0000) << 8 | (r1 & 0xff000000) >> 8 | (l2 & 0x0000ff00) | | |
1125 | (l2 & 0x00ff0000) >> 16; | |
1126 | *(o_ptr++) = (l2 & 0xff000000) | (r2 & 0x0000ff00) << 8 | (r2 & 0x00ff0000) >> 8 | | |
1127 | (r2 & 0xff000000) >> 24; | |
1128 | #endif | |
1129 | optr += 12; | |
1130 | cnt -= 2; | |
1131 | } | |
1132 | } else { | |
1133 | s32_t lsample = *(inputptr++); | |
1134 | s32_t rsample = *(inputptr++); | |
1135 | *(optr++) = (lsample & 0x0000ff00) >> 8; | |
1136 | *(optr++) = (lsample & 0x00ff0000) >> 16; | |
1137 | *(optr++) = (lsample & 0xff000000) >> 24; | |
1138 | *(optr++) = (rsample & 0x0000ff00) >> 8; | |
1139 | *(optr++) = (rsample & 0x00ff0000) >> 16; | |
1140 | *(optr++) = (rsample & 0xff000000) >> 24; | |
1141 | cnt--; | |
1142 | } | |
1143 | } | |
1144 | } else { | |
1145 | while (cnt) { | |
1146 | // attempt to do 32 bit memory accesses - move 2 frames at once: 16 bytes -> 12 bytes | |
1147 | // falls through to exception case when not aligned or if less than 2 frames to move | |
1148 | if (((uintptr_t)optr & 0x3) == 0 && cnt >= 2) { | |
1149 | u32_t *o_ptr = (u32_t *)(void *)optr; | |
1150 | while (cnt >= 2) { | |
1151 | s32_t l1 = gain(gainL, *(inputptr++)); s32_t r1 = gain(gainR, *(inputptr++)); | |
1152 | s32_t l2 = gain(gainL, *(inputptr++)); s32_t r2 = gain(gainR, *(inputptr++)); | |
1153 | #if SL_LITTLE_ENDIAN | |
1154 | *(o_ptr++) = (l1 & 0xffffff00) >> 8 | (r1 & 0x0000ff00) << 16; | |
1155 | *(o_ptr++) = (r1 & 0xffff0000) >> 16 | (l2 & 0x00ffff00) << 8; | |
1156 | *(o_ptr++) = (l2 & 0xff000000) >> 24 | (r2 & 0xffffff00); | |
1157 | #else | |
1158 | *(o_ptr++) = (l1 & 0x0000ff00) << 16 | (l1 & 0x00ff0000) | (l1 & 0xff000000) >> 16 | | |
1159 | (r1 & 0x0000ff00) >> 8; | |
1160 | *(o_ptr++) = (r1 & 0x00ff0000) << 8 | (r1 & 0xff000000) >> 8 | (l2 & 0x0000ff00) | | |
1161 | (l2 & 0x00ff0000) >> 16; | |
1162 | *(o_ptr++) = (l2 & 0xff000000) | (r2 & 0x0000ff00) << 8 | (r2 & 0x00ff0000) >> 8 | | |
1163 | (r2 & 0xff000000) >> 24; | |
1164 | #endif | |
1165 | optr += 12; | |
1166 | cnt -= 2; | |
1167 | } | |
1168 | } else { | |
1169 | s32_t lsample = gain(gainL, *(inputptr++)); | |
1170 | s32_t rsample = gain(gainR, *(inputptr++)); | |
1171 | *(optr++) = (lsample & 0x0000ff00) >> 8; | |
1172 | *(optr++) = (lsample & 0x00ff0000) >> 16; | |
1173 | *(optr++) = (lsample & 0xff000000) >> 24; | |
1174 | *(optr++) = (rsample & 0x0000ff00) >> 8; | |
1175 | *(optr++) = (rsample & 0x00ff0000) >> 16; | |
1176 | *(optr++) = (rsample & 0xff000000) >> 24; | |
1177 | cnt--; | |
1178 | } | |
1179 | } | |
1180 | } | |
1181 | } | |
1182 | break; | |
1183 | case SND_PCM_FORMAT_S32_LE: | |
1184 | { | |
1185 | u32_t *optr = (u32_t *)(void *)outputptr; | |
1186 | #if SL_LITTLE_ENDIAN | |
1187 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
1188 | memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME); | |
1189 | } else { | |
1190 | while (cnt--) { | |
1191 | *(optr++) = gain(gainL, *(inputptr++)); | |
1192 | *(optr++) = gain(gainR, *(inputptr++)); | |
1193 | } | |
1194 | } | |
1195 | #else | |
1196 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
1197 | while (cnt--) { | |
1198 | s32_t lsample = *(inputptr++); | |
1199 | s32_t rsample = *(inputptr++); | |
1200 | *(optr++) = | |
1201 | (lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 | | |
1202 | (lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24; | |
1203 | *(optr++) = | |
1204 | (rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 | | |
1205 | (rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24; | |
1206 | } | |
1207 | } else { | |
1208 | while (cnt--) { | |
1209 | s32_t lsample = gain(gainL, *(inputptr++)); | |
1210 | s32_t rsample = gain(gainR, *(inputptr++)); | |
1211 | *(optr++) = | |
1212 | (lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 | | |
1213 | (lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24; | |
1214 | *(optr++) = | |
1215 | (rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 | | |
1216 | (rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24; | |
1217 | } | |
1218 | } | |
1219 | #endif | |
1220 | } | |
1221 | break; | |
1222 | default: | |
1223 | break; | |
1224 | } | |
1225 | ||
1226 | if (alsa.mmap) { | |
1227 | snd_pcm_sframes_t w = snd_pcm_mmap_commit(pcmp, offset, out_frames); | |
1228 | if (w < 0 || w != out_frames) { | |
1229 | LOG_WARN("mmap_commit error"); | |
1230 | break; | |
1231 | } | |
1232 | } else { | |
1233 | snd_pcm_sframes_t w = snd_pcm_writei(pcmp, alsa.write_buf, out_frames); | |
1234 | if (w < 0) { | |
1235 | if (w != -EAGAIN && ((err = snd_pcm_recover(pcmp, w, 1)) < 0)) { | |
1236 | static unsigned recover_count = 0; | |
1237 | LOG_WARN("recover failed: %s [%u]", snd_strerror(err), ++recover_count); | |
1238 | if (recover_count >= 10) { | |
1239 | recover_count = 0; | |
1240 | alsa_close(pcmp); | |
1241 | pcmp = NULL; | |
1242 | } | |
1243 | } | |
1244 | break; | |
1245 | } else { | |
1246 | if (w != out_frames) { | |
1247 | LOG_WARN("writei only wrote %u of %u", w, out_frames); | |
1248 | } | |
1249 | out_frames = w; | |
1250 | } | |
1251 | } | |
1252 | ||
1253 | } else { | |
1254 | ||
1255 | #endif // ALSA | |
1256 | ||
1257 | #if PORTAUDIO | |
1258 | if (1) { | |
1259 | #endif | |
1260 | if (!silence) { | |
1261 | ||
1262 | if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && cross_ptr) { | |
1263 | s32_t *ptr = (s32_t *)(void *)outputbuf->readp; | |
1264 | frames_t count = out_frames * 2; | |
1265 | while (count--) { | |
1266 | if (cross_ptr > (s32_t *)outputbuf->wrap) { | |
1267 | cross_ptr -= outputbuf->size / BYTES_PER_FRAME * 2; | |
1268 | } | |
1269 | *ptr = gain(cross_gain_out, *ptr) + gain(cross_gain_in, *cross_ptr); | |
1270 | ptr++; cross_ptr++; | |
1271 | } | |
1272 | } | |
1273 | ||
1274 | if (gainL != FIXED_ONE || gainR!= FIXED_ONE) { | |
1275 | unsigned count = out_frames; | |
1276 | s32_t *ptrL = (s32_t *)(void *)outputbuf->readp; | |
1277 | s32_t *ptrR = (s32_t *)(void *)outputbuf->readp + 1; | |
1278 | while (count--) { | |
1279 | *ptrL = gain(gainL, *ptrL); | |
1280 | *ptrR = gain(gainR, *ptrR); | |
1281 | ptrL += 2; | |
1282 | ptrR += 2; | |
1283 | } | |
1284 | } | |
1285 | } | |
1286 | #if ALSA | |
1287 | // only used in S32_LE non mmap LE case, write the 32 samples straight with writei, no need for intermediate buffer | |
1288 | snd_pcm_sframes_t w = snd_pcm_writei(pcmp, silence ? silencebuf : outputbuf->readp, out_frames); | |
1289 | if (w < 0) { | |
1290 | if (w != -EAGAIN && ((err = snd_pcm_recover(pcmp, w, 1)) < 0)) { | |
1291 | static unsigned recover_count = 0; | |
1292 | LOG_WARN("recover failed: %s [%u]", snd_strerror(err), ++recover_count); | |
1293 | if (recover_count >= 10) { | |
1294 | recover_count = 0; | |
1295 | alsa_close(pcmp); | |
1296 | pcmp = NULL; | |
1297 | } | |
1298 | } | |
1299 | break; | |
1300 | } else { | |
1301 | if (w != out_frames) { | |
1302 | LOG_WARN("writei only wrote %u of %u", w, out_frames); | |
1303 | } | |
1304 | out_frames = w; | |
1305 | } | |
1306 | #endif | |
1307 | #if PORTAUDIO | |
1308 | if (!silence) { | |
1309 | memcpy(optr, outputbuf->readp, out_frames * BYTES_PER_FRAME); | |
1310 | } else { | |
1311 | memset(optr, 0, out_frames * BYTES_PER_FRAME); | |
1312 | } | |
1313 | ||
1314 | optr += out_frames * BYTES_PER_FRAME; | |
1315 | #endif | |
1316 | } | |
1317 | ||
1318 | size -= out_frames; | |
1319 | ||
1320 | #if VISEXPORT | |
1321 | // attempt to write audio to vis_mmap but do not wait more than VIS_LOCK_NS to get wrlock | |
1322 | // this can result in missing audio export to the mmap region, but this is preferable dropping audio | |
1323 | if (vis_mmap) { | |
1324 | int err; | |
1325 | err = pthread_rwlock_trywrlock(&vis_mmap->rwlock); | |
1326 | if (err) { | |
1327 | struct timespec ts; | |
1328 | clock_gettime(CLOCK_REALTIME, &ts); | |
1329 | ts.tv_nsec += VIS_LOCK_NS; | |
1330 | if (ts.tv_nsec > 1000000000) { | |
1331 | ts.tv_sec += 1; | |
1332 | ts.tv_nsec -= 1000000000; | |
1333 | } | |
1334 | err = pthread_rwlock_timedwrlock(&vis_mmap->rwlock, &ts); | |
1335 | } | |
1336 | ||
1337 | if (err) { | |
1338 | LOG_DEBUG("failed to get wrlock - skipping visulizer export"); | |
1339 | ||
1340 | } else { | |
1341 | ||
1342 | if (silence) { | |
1343 | vis_mmap->running = false; | |
1344 | } else { | |
1345 | frames_t vis_cnt = out_frames; | |
1346 | s32_t *ptr = (s32_t *) outputbuf->readp; | |
1347 | unsigned i = vis_mmap->buf_index; | |
1348 | ||
1349 | if (!output.current_replay_gain) { | |
1350 | while (vis_cnt--) { | |
1351 | vis_mmap->buffer[i++] = *(ptr++) >> 16; | |
1352 | vis_mmap->buffer[i++] = *(ptr++) >> 16; | |
1353 | if (i == VIS_BUF_SIZE) i = 0; | |
1354 | } | |
1355 | } else { | |
1356 | while (vis_cnt--) { | |
1357 | vis_mmap->buffer[i++] = gain(*(ptr++), output.current_replay_gain) >> 16; | |
1358 | vis_mmap->buffer[i++] = gain(*(ptr++), output.current_replay_gain) >> 16; | |
1359 | if (i == VIS_BUF_SIZE) i = 0; | |
1360 | } | |
1361 | } | |
1362 | ||
1363 | vis_mmap->updated = time(NULL); | |
1364 | vis_mmap->running = true; | |
1365 | vis_mmap->buf_index = i; | |
1366 | vis_mmap->rate = output.current_sample_rate; | |
1367 | } | |
1368 | ||
1369 | pthread_rwlock_unlock(&vis_mmap->rwlock); | |
1370 | } | |
1371 | } | |
1372 | #endif | |
220 | } | |
221 | ||
222 | out_frames = !silence ? min(size, cont_frames) : size; | |
223 | ||
224 | wrote = output.write_cb(out_frames, silence, gainL, gainR, cross_gain_in, cross_gain_out, &cross_ptr); | |
225 | ||
226 | if (wrote < 0) { | |
227 | LOG_WARN("error in write cb"); | |
228 | frames -= out_frames; | |
229 | break; | |
230 | } else { | |
231 | out_frames = (frames_t)wrote; | |
232 | } | |
233 | ||
234 | size -= out_frames; | |
235 | ||
236 | _vis_export(outputbuf, &output, out_frames, silence); | |
237 | ||
238 | if (!silence) { | |
239 | _buf_inc_readp(outputbuf, out_frames * BYTES_PER_FRAME); | |
240 | output.frames_played += out_frames; | |
241 | } | |
242 | } | |
1373 | 243 | |
1374 | if (!silence) { | |
1375 | _buf_inc_readp(outputbuf, out_frames * BYTES_PER_FRAME); | |
1376 | output.frames_played += out_frames; | |
1377 | } | |
1378 | } | |
1379 | ||
1380 | LOG_SDEBUG("wrote %u frames", frames); | |
1381 | ||
1382 | #if ALSA | |
1383 | UNLOCK; | |
1384 | } | |
1385 | ||
1386 | return 0; | |
244 | LOG_SDEBUG("wrote %u frames", frames); | |
245 | ||
246 | return frames; | |
1387 | 247 | } |
1388 | #endif | |
1389 | ||
1390 | #if PORTAUDIO | |
1391 | if (frames < pa_frames_wanted) { | |
1392 | LOG_SDEBUG("pad with silence"); | |
1393 | memset(optr, 0, (pa_frames_wanted - frames) * BYTES_PER_FRAME); | |
1394 | } | |
1395 | ||
1396 | if (output.state == OUTPUT_OFF) { | |
1397 | LOG_INFO("output off"); | |
1398 | UNLOCK; | |
1399 | return paComplete; | |
1400 | } else if (pa.rate != output.current_sample_rate) { | |
1401 | UNLOCK; | |
1402 | return paComplete; | |
1403 | } else { | |
1404 | UNLOCK; | |
1405 | return paContinue; | |
1406 | } | |
1407 | } | |
1408 | #endif | |
1409 | 248 | |
1410 | 249 | void _checkfade(bool start) { |
1411 | 250 | frames_t bytes; |
1469 | 308 | } |
1470 | 309 | } |
1471 | 310 | |
1472 | #if ALSA | |
1473 | static pthread_t thread; | |
1474 | void output_init(log_level level, const char *device, unsigned output_buf_size, unsigned alsa_buffer, unsigned alsa_period, | |
1475 | const char *alsa_sample_fmt, bool mmap, unsigned max_rate, unsigned rt_priority) { | |
1476 | #endif | |
1477 | #if PORTAUDIO | |
1478 | void output_init(log_level level, const char *device, unsigned output_buf_size, unsigned latency, int osx_playnice, unsigned max_rate) { | |
1479 | PaError err; | |
1480 | #endif | |
311 | void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[]) { | |
312 | unsigned i; | |
313 | ||
1481 | 314 | loglevel = level; |
1482 | ||
1483 | LOG_INFO("init output"); | |
1484 | 315 | |
1485 | 316 | output_buf_size = output_buf_size - (output_buf_size % BYTES_PER_FRAME); |
1486 | 317 | LOG_DEBUG("outputbuf size: %u", output_buf_size); |
1487 | 318 | |
1488 | 319 | buf_init(outputbuf, output_buf_size); |
1489 | 320 | if (!outputbuf->buf) { |
1490 | LOG_ERROR("unable to malloc buffer"); | |
321 | LOG_ERROR("unable to malloc output buffer"); | |
1491 | 322 | exit(0); |
1492 | 323 | } |
1493 | 324 | |
1494 | LOCK; | |
325 | silencebuf = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME); | |
326 | if (!silencebuf) { | |
327 | LOG_ERROR("unable to malloc silence buffer"); | |
328 | exit(0); | |
329 | } | |
330 | memset(silencebuf, 0, MAX_SILENCE_FRAMES * BYTES_PER_FRAME); | |
331 | ||
332 | IF_DSD( | |
333 | silencebuf_dop = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME); | |
334 | if (!silencebuf_dop) { | |
335 | LOG_ERROR("unable to malloc silence dop buffer"); | |
336 | exit(0); | |
337 | } | |
338 | dop_silence_frames((u32_t *)silencebuf_dop, MAX_SILENCE_FRAMES); | |
339 | ) | |
1495 | 340 | |
1496 | 341 | output.state = OUTPUT_STOPPED; |
1497 | output.current_sample_rate = 44100; | |
1498 | 342 | output.device = device; |
1499 | 343 | output.fade = FADE_INACTIVE; |
1500 | ||
1501 | #if ALSA | |
1502 | alsa.mmap = mmap; | |
1503 | alsa.write_buf = NULL; | |
1504 | alsa.format = 0; | |
1505 | output.buffer = alsa_buffer; | |
1506 | output.period = alsa_period; | |
1507 | ||
1508 | memset(silencebuf, 0, sizeof(silencebuf)); | |
1509 | ||
1510 | if (alsa_sample_fmt) { | |
1511 | if (!strcmp(alsa_sample_fmt, "32")) alsa.format = SND_PCM_FORMAT_S32_LE; | |
1512 | if (!strcmp(alsa_sample_fmt, "24")) alsa.format = SND_PCM_FORMAT_S24_LE; | |
1513 | if (!strcmp(alsa_sample_fmt, "24_3")) alsa.format = SND_PCM_FORMAT_S24_3LE; | |
1514 | if (!strcmp(alsa_sample_fmt, "16")) alsa.format = SND_PCM_FORMAT_S16_LE; | |
1515 | } | |
1516 | ||
1517 | LOG_INFO("requested alsa_buffer: %u alsa_period: %u format: %s mmap: %u", output.buffer, output.period, | |
1518 | alsa_sample_fmt ? alsa_sample_fmt : "any", alsa.mmap); | |
1519 | ||
1520 | snd_lib_error_set_handler((snd_lib_error_handler_t)alsa_error_handler); | |
1521 | #endif | |
1522 | ||
1523 | #if PORTAUDIO | |
1524 | output.latency = latency; | |
1525 | output.osx_playnice = osx_playnice; | |
1526 | pa.stream = NULL; | |
1527 | ||
1528 | LOG_INFO("requested latency: %u", output.latency); | |
1529 | ||
1530 | if ((err = Pa_Initialize()) != paNoError) { | |
1531 | LOG_WARN("error initialising port audio: %s", Pa_GetErrorText(err)); | |
1532 | UNLOCK; | |
1533 | exit(0); | |
1534 | } | |
1535 | #endif | |
1536 | ||
1537 | if (!max_rate) { | |
1538 | if (!test_open(output.device, &output.max_sample_rate)) { | |
344 | output.error_opening = false; | |
345 | ||
346 | if (!rates[0]) { | |
347 | if (!test_open(output.device, output.supported_rates)) { | |
1539 | 348 | LOG_ERROR("unable to open output device"); |
1540 | UNLOCK; | |
1541 | 349 | exit(0); |
1542 | 350 | } |
1543 | 351 | } else { |
1544 | output.max_sample_rate = max_rate; | |
1545 | } | |
1546 | ||
1547 | LOG_INFO("output: %s maxrate: %u", output.device, output.max_sample_rate); | |
1548 | ||
1549 | #if ALSA | |
1550 | ||
1551 | #if LINUX | |
1552 | // RT linux - aim to avoid pagefaults by locking memory: | |
1553 | // https://rt.wiki.kernel.org/index.php/Threaded_RT-application_with_memory_locking_and_stack_handling_example | |
1554 | if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) { | |
1555 | LOG_INFO("unable to lock memory: %s", strerror(errno)); | |
1556 | } else { | |
1557 | LOG_INFO("memory locked"); | |
1558 | } | |
1559 | ||
1560 | mallopt(M_TRIM_THRESHOLD, -1); | |
1561 | mallopt(M_MMAP_MAX, 0); | |
1562 | ||
1563 | touch_memory(silencebuf, MAX_SILENCE_FRAMES * BYTES_PER_FRAME); | |
1564 | touch_memory(outputbuf->buf, outputbuf->size); | |
1565 | #endif | |
1566 | ||
1567 | // start output thread | |
1568 | pthread_attr_t attr; | |
1569 | pthread_attr_init(&attr); | |
1570 | pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE); | |
1571 | pthread_create(&thread, &attr, output_thread, max_rate ? "probe" : NULL); | |
1572 | pthread_attr_destroy(&attr); | |
1573 | ||
1574 | // try to set this thread to real-time scheduler class, only works as root or if user has permission | |
1575 | struct sched_param param; | |
1576 | param.sched_priority = rt_priority; | |
1577 | if (pthread_setschedparam(thread, SCHED_FIFO, ¶m) != 0) { | |
1578 | LOG_DEBUG("unable to set output sched fifo: %s", strerror(errno)); | |
1579 | } else { | |
1580 | LOG_DEBUG("set output sched fifo rt: %u", param.sched_priority); | |
1581 | } | |
1582 | #endif | |
1583 | ||
1584 | #if PORTAUDIO | |
1585 | _pa_open(); | |
1586 | #endif | |
1587 | ||
1588 | UNLOCK; | |
352 | for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) { | |
353 | output.supported_rates[i] = rates[i]; | |
354 | } | |
355 | } | |
356 | ||
357 | // set initial sample rate, preferring 44100 | |
358 | for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) { | |
359 | if (output.supported_rates[i] == 44100) { | |
360 | output.default_sample_rate = 44100; | |
361 | break; | |
362 | } | |
363 | } | |
364 | if (!output.default_sample_rate) { | |
365 | output.default_sample_rate = rates[0]; | |
366 | } | |
367 | ||
368 | output.current_sample_rate = output.default_sample_rate; | |
369 | ||
370 | if (loglevel >= lINFO) { | |
371 | char rates_buf[10 * MAX_SUPPORTED_SAMPLERATES] = ""; | |
372 | for (i = 0; output.supported_rates[i]; ++i) { | |
373 | char s[10]; | |
374 | sprintf(s, "%d ", output.supported_rates[i]); | |
375 | strcat(rates_buf, s); | |
376 | } | |
377 | LOG_INFO("supported rates: %s", rates_buf); | |
378 | } | |
1589 | 379 | } |
1590 | 380 | |
1591 | #if VISEXPORT | |
1592 | void output_vis_init(u8_t *mac) { | |
1593 | sprintf(vis_shm_path, "/squeezelite-%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); | |
1594 | ||
1595 | LOCK; | |
1596 | vis_fd = shm_open(vis_shm_path, O_CREAT | O_RDWR, 0666); | |
1597 | if (vis_fd != -1) { | |
1598 | if (ftruncate(vis_fd, sizeof(struct vis_t)) == 0) { | |
1599 | vis_mmap = (struct vis_t *)mmap(NULL, sizeof(struct vis_t), PROT_READ | PROT_WRITE, MAP_SHARED, vis_fd, 0); | |
1600 | } | |
1601 | } | |
1602 | ||
1603 | if (vis_mmap > 0) { | |
1604 | pthread_rwlockattr_t attr; | |
1605 | pthread_rwlockattr_init(&attr); | |
1606 | pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); | |
1607 | pthread_rwlock_init(&vis_mmap->rwlock, &attr); | |
1608 | vis_mmap->buf_size = VIS_BUF_SIZE; | |
1609 | vis_mmap->running = false; | |
1610 | vis_mmap->rate = output.current_sample_rate; | |
1611 | pthread_rwlockattr_destroy(&attr); | |
1612 | LOG_INFO("opened visulizer shared memory as %s", vis_shm_path); | |
1613 | } else { | |
1614 | LOG_WARN("unable to open visualizer shared memory"); | |
1615 | vis_mmap = NULL; | |
1616 | } | |
1617 | UNLOCK; | |
381 | void output_close_common(void) { | |
382 | buf_destroy(outputbuf); | |
383 | free(silencebuf); | |
384 | IF_DSD( | |
385 | free(silencebuf_dop); | |
386 | ) | |
1618 | 387 | } |
1619 | #endif | |
1620 | 388 | |
1621 | 389 | void output_flush(void) { |
1622 | 390 | LOG_INFO("flush output buffer"); |
1625 | 393 | output.fade = FADE_INACTIVE; |
1626 | 394 | if (output.state != OUTPUT_OFF) { |
1627 | 395 | output.state = OUTPUT_STOPPED; |
396 | if (output.error_opening) { | |
397 | output.current_sample_rate = output.default_sample_rate; | |
398 | } | |
1628 | 399 | } |
1629 | 400 | output.frames_played = 0; |
1630 | 401 | UNLOCK; |
1631 | 402 | } |
1632 | ||
1633 | void output_close(void) { | |
1634 | #if PORTAUDIO | |
1635 | PaError err; | |
1636 | #endif | |
1637 | ||
1638 | LOG_INFO("close output"); | |
1639 | ||
1640 | LOCK; | |
1641 | ||
1642 | running = false; | |
1643 | ||
1644 | #if ALSA | |
1645 | UNLOCK; | |
1646 | pthread_join(thread, NULL); | |
1647 | if (alsa.write_buf) free(alsa.write_buf); | |
1648 | #endif | |
1649 | ||
1650 | #if PORTAUDIO | |
1651 | monitor_thread_running = false; | |
1652 | ||
1653 | if (pa.stream) { | |
1654 | if ((err = Pa_AbortStream(pa.stream)) != paNoError) { | |
1655 | LOG_WARN("error closing stream: %s", Pa_GetErrorText(err)); | |
1656 | } | |
1657 | } | |
1658 | if ((err = Pa_Terminate()) != paNoError) { | |
1659 | LOG_WARN("error closing port audio: %s", Pa_GetErrorText(err)); | |
1660 | } | |
1661 | UNLOCK; | |
1662 | #endif | |
1663 | ||
1664 | #if VISEXPORT | |
1665 | if (vis_mmap) { | |
1666 | pthread_rwlock_destroy(&vis_mmap->rwlock); | |
1667 | munmap(vis_mmap, sizeof(struct vis_t)); | |
1668 | } | |
1669 | ||
1670 | if (vis_fd != -1) { | |
1671 | shm_unlink(vis_shm_path); | |
1672 | close(vis_fd); | |
1673 | } | |
1674 | #endif | |
1675 | ||
1676 | buf_destroy(outputbuf); | |
1677 | } |
0 | /* | |
1 | * Squeezelite - lightweight headless squeezebox emulator | |
2 | * | |
3 | * (c) Adrian Smith 2012, 2013, triode1@btinternet.com | |
4 | * | |
5 | * This program is free software: you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation, either version 3 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | * | |
18 | */ | |
19 | ||
20 | // Output using Alsa | |
21 | ||
22 | #include "squeezelite.h" | |
23 | ||
24 | #if ALSA | |
25 | ||
26 | #include <alsa/asoundlib.h> | |
27 | #include <sys/mman.h> | |
28 | #include <malloc.h> | |
29 | ||
30 | #define MAX_DEVICE_LEN 128 | |
31 | ||
32 | static snd_pcm_format_t fmts[] = { SND_PCM_FORMAT_S32_LE, SND_PCM_FORMAT_S24_LE, SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S16_LE, | |
33 | SND_PCM_FORMAT_UNKNOWN }; | |
34 | ||
35 | #if SL_LITTLE_ENDIAN | |
36 | #define NATIVE_FORMAT SND_PCM_FORMAT_S32_LE | |
37 | #else | |
38 | #define NATIVE_FORMAT SND_PCM_FORMAT_S32_BE | |
39 | #endif | |
40 | ||
41 | // ouput device | |
42 | static struct { | |
43 | char device[MAX_DEVICE_LEN + 1]; | |
44 | snd_pcm_format_t format; | |
45 | snd_pcm_uframes_t buffer_size; | |
46 | snd_pcm_uframes_t period_size; | |
47 | unsigned rate; | |
48 | bool mmap; | |
49 | bool reopen; | |
50 | u8_t *write_buf; | |
51 | } alsa; | |
52 | ||
53 | static snd_pcm_t *pcmp = NULL; | |
54 | ||
55 | extern u8_t *silencebuf; | |
56 | #if DSD | |
57 | extern u8_t *silencebuf_dop; | |
58 | #endif | |
59 | ||
60 | static log_level loglevel; | |
61 | ||
62 | static bool running = true; | |
63 | ||
64 | extern struct outputstate output; | |
65 | extern struct buffer *outputbuf; | |
66 | ||
67 | #define LOCK mutex_lock(outputbuf->mutex) | |
68 | #define UNLOCK mutex_unlock(outputbuf->mutex) | |
69 | ||
70 | void list_devices(void) { | |
71 | void **hints, **n; | |
72 | if (snd_device_name_hint(-1, "pcm", &hints) >= 0) { | |
73 | n = hints; | |
74 | printf("Output devices:\n"); | |
75 | while (*n) { | |
76 | char *name = snd_device_name_get_hint(*n, "NAME"); | |
77 | char *desc = snd_device_name_get_hint(*n, "DESC"); | |
78 | if (name) printf(" %-30s", name); | |
79 | if (desc) { | |
80 | char *s1 = strtok(desc, "\n"); | |
81 | char *s2 = strtok(NULL, "\n"); | |
82 | if (s1) printf(" - %s", s1); | |
83 | if (s2) printf(" - %s", s2); | |
84 | } | |
85 | printf("\n"); | |
86 | if (name) free(name); | |
87 | if (desc) free(desc); | |
88 | n++; | |
89 | } | |
90 | snd_device_name_free_hint(hints); | |
91 | } | |
92 | printf("\n"); | |
93 | } | |
94 | ||
95 | static void *alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) { | |
96 | va_list args; | |
97 | if ((loglevel >= lINFO && err == 0) || loglevel >= lDEBUG) { | |
98 | fprintf(stderr, "%s ALSA %s:%d ", logtime(), function, line); | |
99 | va_start(args, fmt); | |
100 | vfprintf(stderr, fmt, args); | |
101 | fprintf(stderr, "\n"); | |
102 | fflush(stderr); | |
103 | } | |
104 | return NULL; | |
105 | } | |
106 | ||
107 | static void alsa_close(void) { | |
108 | int err; | |
109 | if ((err = snd_pcm_close(pcmp)) < 0) { | |
110 | LOG_INFO("snd_pcm_close error: %s", snd_strerror(err)); | |
111 | } | |
112 | } | |
113 | ||
114 | bool test_open(const char *device, unsigned rates[]) { | |
115 | int err; | |
116 | snd_pcm_t *pcm; | |
117 | snd_pcm_hw_params_t *hw_params; | |
118 | hw_params = (snd_pcm_hw_params_t *) alloca(snd_pcm_hw_params_sizeof()); | |
119 | memset(hw_params, 0, snd_pcm_hw_params_sizeof()); | |
120 | ||
121 | // open device | |
122 | if ((err = snd_pcm_open(&pcm, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { | |
123 | LOG_ERROR("playback open error: %s", snd_strerror(err)); | |
124 | return false; | |
125 | } | |
126 | ||
127 | // get max params | |
128 | if ((err = snd_pcm_hw_params_any(pcm, hw_params)) < 0) { | |
129 | LOG_ERROR("hwparam init error: %s", snd_strerror(err)); | |
130 | return false; | |
131 | } | |
132 | ||
133 | // find supported sample rates to enable client side resampling of non supported rates | |
134 | unsigned i, ind; | |
135 | unsigned ref[] TEST_RATES; | |
136 | ||
137 | for (i = 0, ind = 0; ref[i]; ++i) { | |
138 | if (snd_pcm_hw_params_test_rate(pcm, hw_params, ref[i], 0) == 0) { | |
139 | rates[ind++] = ref[i]; | |
140 | } | |
141 | } | |
142 | ||
143 | if ((err = snd_pcm_close(pcm)) < 0) { | |
144 | LOG_ERROR("snd_pcm_close error: %s", snd_strerror(err)); | |
145 | return false; | |
146 | } | |
147 | ||
148 | return true; | |
149 | } | |
150 | ||
151 | static bool pcm_probe(const char *device) { | |
152 | int err; | |
153 | snd_pcm_t *pcm; | |
154 | ||
155 | if ((err = snd_pcm_open(&pcm, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { | |
156 | return false; | |
157 | } | |
158 | ||
159 | if ((err = snd_pcm_close(pcm)) < 0) { | |
160 | LOG_ERROR("snd_pcm_close error: %s", snd_strerror(err)); | |
161 | } | |
162 | ||
163 | return true; | |
164 | } | |
165 | ||
166 | static int alsa_open(const char *device, unsigned sample_rate, unsigned alsa_buffer, unsigned alsa_period) { | |
167 | int err; | |
168 | snd_pcm_hw_params_t *hw_params; | |
169 | snd_pcm_hw_params_alloca(&hw_params); | |
170 | ||
171 | // close if already open | |
172 | if (pcmp) alsa_close(); | |
173 | ||
174 | // reset params | |
175 | alsa.rate = 0; | |
176 | alsa.period_size = 0; | |
177 | strcpy(alsa.device, device); | |
178 | ||
179 | if (strlen(device) > MAX_DEVICE_LEN - 4 - 1) { | |
180 | LOG_ERROR("device name too long: %s", device); | |
181 | return -1; | |
182 | } | |
183 | ||
184 | bool retry; | |
185 | do { | |
186 | // open device | |
187 | if ((err = snd_pcm_open(&pcmp, alsa.device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { | |
188 | LOG_ERROR("playback open error: %s", snd_strerror(err)); | |
189 | return err; | |
190 | } | |
191 | ||
192 | // init params | |
193 | memset(hw_params, 0, snd_pcm_hw_params_sizeof()); | |
194 | if ((err = snd_pcm_hw_params_any(pcmp, hw_params)) < 0) { | |
195 | LOG_ERROR("hwparam init error: %s", snd_strerror(err)); | |
196 | return err; | |
197 | } | |
198 | ||
199 | // open hw: devices without resampling, if sample rate fails try plughw: with resampling | |
200 | bool hw = !strncmp(alsa.device, "hw:", 3); | |
201 | retry = false; | |
202 | ||
203 | if ((err = snd_pcm_hw_params_set_rate_resample(pcmp, hw_params, !hw)) < 0) { | |
204 | LOG_ERROR("resampling setup failed: %s", snd_strerror(err)); | |
205 | return err; | |
206 | } | |
207 | ||
208 | if ((err = snd_pcm_hw_params_set_rate(pcmp, hw_params, sample_rate, 0)) < 0) { | |
209 | if (hw) { | |
210 | strcpy(alsa.device + 4, device); | |
211 | memcpy(alsa.device, "plug", 4); | |
212 | LOG_INFO("reopening device %s in plug mode as %s for resampling", device, alsa.device); | |
213 | snd_pcm_close(pcmp); | |
214 | retry = true; | |
215 | } | |
216 | } | |
217 | ||
218 | } while (retry); | |
219 | ||
220 | // set access | |
221 | if (!alsa.mmap || snd_pcm_hw_params_set_access(pcmp, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) { | |
222 | if ((err = snd_pcm_hw_params_set_access(pcmp, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { | |
223 | LOG_ERROR("access type not available: %s", snd_strerror(err)); | |
224 | return err; | |
225 | } | |
226 | alsa.mmap = false; | |
227 | } | |
228 | ||
229 | // set the sample format | |
230 | snd_pcm_format_t *fmt = alsa.format ? &alsa.format : (snd_pcm_format_t *)fmts; | |
231 | do { | |
232 | if (snd_pcm_hw_params_set_format(pcmp, hw_params, *fmt) >= 0) { | |
233 | LOG_INFO("opened device %s using format: %s sample rate: %u mmap: %u", alsa.device, snd_pcm_format_name(*fmt), sample_rate, alsa.mmap); | |
234 | alsa.format = *fmt; | |
235 | break; | |
236 | } | |
237 | if (alsa.format) { | |
238 | LOG_ERROR("unable to open audio device requested format: %s", snd_pcm_format_name(alsa.format)); | |
239 | return -1; | |
240 | } | |
241 | ++fmt; | |
242 | if (*fmt == SND_PCM_FORMAT_UNKNOWN) { | |
243 | LOG_ERROR("unable to open audio device with any supported format"); | |
244 | return -1; | |
245 | } | |
246 | } while (*fmt != SND_PCM_FORMAT_UNKNOWN); | |
247 | ||
248 | // set the output format to be used by _scale_and_pack | |
249 | switch(alsa.format) { | |
250 | case SND_PCM_FORMAT_S32_LE: | |
251 | output.format = S32_LE; break; | |
252 | case SND_PCM_FORMAT_S24_LE: | |
253 | output.format = S24_LE; break; | |
254 | case SND_PCM_FORMAT_S24_3LE: | |
255 | output.format = S24_3LE; break; | |
256 | case SND_PCM_FORMAT_S16_LE: | |
257 | output.format = S16_LE; break; | |
258 | default: | |
259 | break; | |
260 | } | |
261 | ||
262 | // set channels | |
263 | if ((err = snd_pcm_hw_params_set_channels (pcmp, hw_params, 2)) < 0) { | |
264 | LOG_ERROR("channel count not available: %s", snd_strerror(err)); | |
265 | return err; | |
266 | } | |
267 | ||
268 | // set period size - value of < 50 treated as period count, otherwise size in bytes | |
269 | if (alsa_period < 50) { | |
270 | unsigned count = alsa_period; | |
271 | if ((err = snd_pcm_hw_params_set_periods_near(pcmp, hw_params, &count, 0)) < 0) { | |
272 | LOG_ERROR("unable to set period count %s", snd_strerror(err)); | |
273 | return err; | |
274 | } | |
275 | } else { | |
276 | snd_pcm_uframes_t size = alsa_period; | |
277 | int dir = 0; | |
278 | if ((err = snd_pcm_hw_params_set_period_size_near(pcmp, hw_params, &size, &dir)) < 0) { | |
279 | LOG_ERROR("unable to set period size %s", snd_strerror(err)); | |
280 | return err; | |
281 | } | |
282 | } | |
283 | ||
284 | // set buffer size - value of < 500 treated as buffer time in ms, otherwise size in bytes | |
285 | if (alsa_buffer < 500) { | |
286 | unsigned time = alsa_buffer * 1000; | |
287 | int dir = 0; | |
288 | if ((err = snd_pcm_hw_params_set_buffer_time_near(pcmp, hw_params, &time, &dir)) < 0) { | |
289 | LOG_ERROR("unable to set buffer time %s", snd_strerror(err)); | |
290 | return err; | |
291 | } | |
292 | } else { | |
293 | snd_pcm_uframes_t size = alsa_buffer; | |
294 | if ((err = snd_pcm_hw_params_set_buffer_size_near(pcmp, hw_params, &size)) < 0) { | |
295 | LOG_ERROR("unable to set buffer size %s", snd_strerror(err)); | |
296 | return err; | |
297 | } | |
298 | } | |
299 | ||
300 | // get period_size | |
301 | if ((err = snd_pcm_hw_params_get_period_size(hw_params, &alsa.period_size, 0)) < 0) { | |
302 | LOG_ERROR("unable to get period size: %s", snd_strerror(err)); | |
303 | return err; | |
304 | } | |
305 | ||
306 | // get buffer_size | |
307 | if ((err = snd_pcm_hw_params_get_buffer_size(hw_params, &alsa.buffer_size)) < 0) { | |
308 | LOG_ERROR("unable to get buffer size: %s", snd_strerror(err)); | |
309 | return err; | |
310 | } | |
311 | ||
312 | LOG_INFO("buffer: %u period: %u -> buffer size: %u period size: %u", alsa_buffer, alsa_period, alsa.buffer_size, alsa.period_size); | |
313 | ||
314 | // ensure we have two buffer sizes of samples before starting output | |
315 | output.start_frames = alsa.buffer_size * 2; | |
316 | ||
317 | // create an intermediate buffer for non mmap case for all but NATIVE_FORMAT | |
318 | // this is used to pack samples into the output format before calling writei | |
319 | if (!alsa.mmap && !alsa.write_buf && alsa.format != NATIVE_FORMAT) { | |
320 | alsa.write_buf = malloc(alsa.buffer_size * BYTES_PER_FRAME); | |
321 | if (!alsa.write_buf) { | |
322 | LOG_ERROR("unable to malloc write_buf"); | |
323 | return -1; | |
324 | } | |
325 | } | |
326 | ||
327 | // set params | |
328 | if ((err = snd_pcm_hw_params(pcmp, hw_params)) < 0) { | |
329 | LOG_ERROR("unable to set hw params: %s", snd_strerror(err)); | |
330 | return err; | |
331 | } | |
332 | ||
333 | // dump info | |
334 | if (loglevel == lSDEBUG) { | |
335 | static snd_output_t *debug_output; | |
336 | snd_output_stdio_attach(&debug_output, stderr, 0); | |
337 | snd_pcm_dump(pcmp, debug_output); | |
338 | } | |
339 | ||
340 | // this indicates we have opened the device ok | |
341 | alsa.rate = sample_rate; | |
342 | ||
343 | return 0; | |
344 | } | |
345 | ||
346 | static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, | |
347 | s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr) { | |
348 | ||
349 | const snd_pcm_channel_area_t *areas; | |
350 | snd_pcm_uframes_t offset; | |
351 | void *outputptr; | |
352 | s32_t *inputptr; | |
353 | int err; | |
354 | ||
355 | if (alsa.mmap) { | |
356 | snd_pcm_uframes_t alsa_frames = (snd_pcm_uframes_t)out_frames; | |
357 | ||
358 | snd_pcm_avail_update(pcmp); | |
359 | ||
360 | if ((err = snd_pcm_mmap_begin(pcmp, &areas, &offset, &alsa_frames)) < 0) { | |
361 | LOG_WARN("error from mmap_begin: %s", snd_strerror(err)); | |
362 | return -1; | |
363 | } | |
364 | ||
365 | out_frames = (frames_t)alsa_frames; | |
366 | } | |
367 | ||
368 | if (!silence) { | |
369 | // applying cross fade is delayed until this point as mmap_begin can change out_frames | |
370 | if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && *cross_ptr) { | |
371 | _apply_cross(outputbuf, out_frames, cross_gain_in, cross_gain_out, cross_ptr); | |
372 | } | |
373 | } | |
374 | ||
375 | inputptr = (s32_t *) (silence ? silencebuf : outputbuf->readp); | |
376 | ||
377 | IF_DSD( | |
378 | if (output.dop) { | |
379 | if (silence) { | |
380 | inputptr = (s32_t *) silencebuf_dop; | |
381 | } | |
382 | update_dop_marker((u32_t *) inputptr, out_frames); | |
383 | } | |
384 | ) | |
385 | ||
386 | if (alsa.mmap || alsa.format != NATIVE_FORMAT) { | |
387 | ||
388 | outputptr = alsa.mmap ? (areas[0].addr + (areas[0].first + offset * areas[0].step) / 8) : alsa.write_buf; | |
389 | ||
390 | _scale_and_pack_frames(outputptr, inputptr, out_frames, gainL, gainR, output.format); | |
391 | ||
392 | } else { | |
393 | ||
394 | outputptr = (void *)inputptr; | |
395 | ||
396 | if (!silence) { | |
397 | ||
398 | if (gainL != FIXED_ONE || gainR!= FIXED_ONE) { | |
399 | _apply_gain(outputbuf, out_frames, gainL, gainR); | |
400 | } | |
401 | } | |
402 | } | |
403 | ||
404 | if (alsa.mmap) { | |
405 | ||
406 | snd_pcm_sframes_t w = snd_pcm_mmap_commit(pcmp, offset, out_frames); | |
407 | if (w < 0 || w != out_frames) { | |
408 | LOG_WARN("mmap_commit error"); | |
409 | return -1; | |
410 | } | |
411 | ||
412 | } else { | |
413 | ||
414 | snd_pcm_sframes_t w = snd_pcm_writei(pcmp, outputptr, out_frames); | |
415 | if (w < 0) { | |
416 | //if (w != -EAGAIN && ((err = snd_pcm_recover(pcmp, w, 1)) < 0)) { | |
417 | if (((err = snd_pcm_recover(pcmp, w, 1)) < 0)) { | |
418 | static unsigned recover_count = 0; | |
419 | LOG_WARN("recover failed: %s [%u]", snd_strerror(err), ++recover_count); | |
420 | if (recover_count >= 10) { | |
421 | recover_count = 0; | |
422 | alsa_close(); | |
423 | pcmp = NULL; | |
424 | } | |
425 | } | |
426 | return -1; | |
427 | } else { | |
428 | if (w != out_frames) { | |
429 | LOG_WARN("writei only wrote %u of %u", w, out_frames); | |
430 | } | |
431 | out_frames = w; | |
432 | } | |
433 | } | |
434 | ||
435 | return (int)out_frames; | |
436 | } | |
437 | ||
438 | static void *output_thread(void *arg) { | |
439 | bool start = true; | |
440 | bool output_off = false, probe_device = (arg != NULL); | |
441 | int err; | |
442 | ||
443 | while (running) { | |
444 | ||
445 | // disabled output - player is off | |
446 | while (output_off) { | |
447 | usleep(100000); | |
448 | LOCK; | |
449 | output_off = (output.state == OUTPUT_OFF); | |
450 | UNLOCK; | |
451 | if (!running) return 0; | |
452 | } | |
453 | ||
454 | // wait until device returns - to allow usb audio devices to be turned off | |
455 | if (probe_device) { | |
456 | while (!pcm_probe(output.device)) { | |
457 | LOG_DEBUG("waiting for device %s to return", output.device); | |
458 | sleep(5); | |
459 | } | |
460 | probe_device = false; | |
461 | } | |
462 | ||
463 | if (!pcmp || alsa.rate != output.current_sample_rate) { | |
464 | LOG_INFO("open output device: %s", output.device); | |
465 | LOCK; | |
466 | ||
467 | // FIXME - some alsa hardware requires opening twice for a new sample rate to work | |
468 | // this is a workaround which should be removed | |
469 | if (alsa.reopen) { | |
470 | alsa_open(output.device, output.current_sample_rate, output.buffer, output.period); | |
471 | } | |
472 | ||
473 | if (!!alsa_open(output.device, output.current_sample_rate, output.buffer, output.period)) { | |
474 | output.error_opening = true; | |
475 | UNLOCK; | |
476 | sleep(5); | |
477 | continue; | |
478 | } | |
479 | output.error_opening = false; | |
480 | start = true; | |
481 | UNLOCK; | |
482 | } | |
483 | ||
484 | snd_pcm_state_t state = snd_pcm_state(pcmp); | |
485 | ||
486 | if (state == SND_PCM_STATE_XRUN) { | |
487 | LOG_INFO("XRUN"); | |
488 | if ((err = snd_pcm_recover(pcmp, -EPIPE, 1)) < 0) { | |
489 | LOG_INFO("XRUN recover failed: %s", snd_strerror(err)); | |
490 | } | |
491 | start = true; | |
492 | continue; | |
493 | } else if (state == SND_PCM_STATE_SUSPENDED) { | |
494 | if ((err = snd_pcm_recover(pcmp, -ESTRPIPE, 1)) < 0) { | |
495 | LOG_INFO("SUSPEND recover failed: %s", snd_strerror(err)); | |
496 | } | |
497 | } else if (state == SND_PCM_STATE_DISCONNECTED) { | |
498 | LOG_INFO("Device %s no longer available", output.device); | |
499 | alsa_close(); | |
500 | pcmp = NULL; | |
501 | probe_device = true; | |
502 | continue; | |
503 | } | |
504 | ||
505 | snd_pcm_sframes_t avail = snd_pcm_avail_update(pcmp); | |
506 | ||
507 | if (avail < 0) { | |
508 | if ((err = snd_pcm_recover(pcmp, avail, 1)) < 0) { | |
509 | if (err == -ENODEV) { | |
510 | LOG_INFO("Device %s no longer available", output.device); | |
511 | alsa_close(); | |
512 | pcmp = NULL; | |
513 | probe_device = true; | |
514 | continue; | |
515 | } | |
516 | LOG_WARN("recover failed: %s", snd_strerror(err)); | |
517 | } | |
518 | start = true; | |
519 | continue; | |
520 | } | |
521 | ||
522 | if (avail < alsa.period_size) { | |
523 | if (start) { | |
524 | if (alsa.mmap && ((err = snd_pcm_start(pcmp)) < 0)) { | |
525 | if ((err = snd_pcm_recover(pcmp, err, 1)) < 0) { | |
526 | if (err == -ENODEV) { | |
527 | LOG_INFO("Device %s no longer available", output.device); | |
528 | alsa_close(); | |
529 | pcmp = NULL; | |
530 | probe_device = true; | |
531 | continue; | |
532 | } | |
533 | LOG_INFO("start error: %s", snd_strerror(err)); | |
534 | } | |
535 | } else { | |
536 | start = false; | |
537 | } | |
538 | } else { | |
539 | if ((err = snd_pcm_wait(pcmp, 1000)) < 0) { | |
540 | if ((err = snd_pcm_recover(pcmp, err, 1)) < 0) { | |
541 | LOG_INFO("pcm wait error: %s", snd_strerror(err)); | |
542 | } | |
543 | start = true; | |
544 | } | |
545 | } | |
546 | continue; | |
547 | } | |
548 | ||
549 | // restrict avail in writei mode as write_buf is restricted to period_size | |
550 | if (!alsa.mmap) { | |
551 | avail = min(avail, alsa.period_size); | |
552 | } | |
553 | ||
554 | // avoid spinning in cases where wait returns but no bytes available (seen with pulse audio) | |
555 | if (avail == 0) { | |
556 | LOG_SDEBUG("avail 0 - sleeping"); | |
557 | usleep(10000); | |
558 | continue; | |
559 | } | |
560 | ||
561 | LOCK; | |
562 | ||
563 | // turn off if requested | |
564 | if (output.state == OUTPUT_OFF) { | |
565 | UNLOCK; | |
566 | LOG_INFO("disabling output"); | |
567 | alsa_close(); | |
568 | pcmp = NULL; | |
569 | output_off = true; | |
570 | vis_stop(); | |
571 | continue; | |
572 | } | |
573 | ||
574 | // measure output delay | |
575 | snd_pcm_sframes_t delay; | |
576 | if ((err = snd_pcm_delay(pcmp, &delay)) < 0) { | |
577 | if (err == -EPIPE) { | |
578 | // EPIPE indicates underrun - attempt to recover | |
579 | UNLOCK; | |
580 | continue; | |
581 | } else { | |
582 | LOG_DEBUG("snd_pcm_delay returns: %d", err); | |
583 | } | |
584 | } else { | |
585 | output.device_frames = delay; | |
586 | output.updated = gettime_ms(); | |
587 | } | |
588 | ||
589 | // process frames | |
590 | _output_frames(avail); | |
591 | ||
592 | UNLOCK; | |
593 | } | |
594 | ||
595 | return 0; | |
596 | } | |
597 | ||
598 | static pthread_t thread; | |
599 | ||
600 | void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rt_priority) { | |
601 | ||
602 | unsigned alsa_buffer = ALSA_BUFFER_TIME; | |
603 | unsigned alsa_period = ALSA_PERIOD_COUNT; | |
604 | char *alsa_sample_fmt = NULL; | |
605 | bool alsa_mmap = true; | |
606 | bool alsa_reopen = false; | |
607 | ||
608 | char *t = next_param(params, ':'); | |
609 | char *c = next_param(NULL, ':'); | |
610 | char *s = next_param(NULL, ':'); | |
611 | char *m = next_param(NULL, ':'); | |
612 | char *r = next_param(NULL, ':'); | |
613 | ||
614 | if (t) alsa_buffer = atoi(t); | |
615 | if (c) alsa_period = atoi(c); | |
616 | if (s) alsa_sample_fmt = s; | |
617 | if (m) alsa_mmap = atoi(m); | |
618 | if (r) alsa_reopen = atoi(r); | |
619 | ||
620 | loglevel = level; | |
621 | ||
622 | LOG_INFO("init output"); | |
623 | ||
624 | memset(&output, 0, sizeof(output)); | |
625 | ||
626 | alsa.mmap = alsa_mmap; | |
627 | alsa.write_buf = NULL; | |
628 | alsa.format = 0; | |
629 | alsa.reopen = alsa_reopen; | |
630 | output.format = 0; | |
631 | output.buffer = alsa_buffer; | |
632 | output.period = alsa_period; | |
633 | output.start_frames = 0; | |
634 | output.write_cb = &_write_frames; | |
635 | ||
636 | if (alsa_sample_fmt) { | |
637 | if (!strcmp(alsa_sample_fmt, "32")) alsa.format = SND_PCM_FORMAT_S32_LE; | |
638 | if (!strcmp(alsa_sample_fmt, "24")) alsa.format = SND_PCM_FORMAT_S24_LE; | |
639 | if (!strcmp(alsa_sample_fmt, "24_3")) alsa.format = SND_PCM_FORMAT_S24_3LE; | |
640 | if (!strcmp(alsa_sample_fmt, "16")) alsa.format = SND_PCM_FORMAT_S16_LE; | |
641 | } | |
642 | ||
643 | LOG_INFO("requested alsa_buffer: %u alsa_period: %u format: %s mmap: %u", output.buffer, output.period, | |
644 | alsa_sample_fmt ? alsa_sample_fmt : "any", alsa.mmap); | |
645 | ||
646 | snd_lib_error_set_handler((snd_lib_error_handler_t)alsa_error_handler); | |
647 | ||
648 | output_init_common(level, device, output_buf_size, rates); | |
649 | ||
650 | #if LINUX | |
651 | // RT linux - aim to avoid pagefaults by locking memory: | |
652 | // https://rt.wiki.kernel.org/index.php/Threaded_RT-application_with_memory_locking_and_stack_handling_example | |
653 | if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) { | |
654 | LOG_INFO("unable to lock memory: %s", strerror(errno)); | |
655 | } else { | |
656 | LOG_INFO("memory locked"); | |
657 | } | |
658 | ||
659 | mallopt(M_TRIM_THRESHOLD, -1); | |
660 | mallopt(M_MMAP_MAX, 0); | |
661 | ||
662 | touch_memory(silencebuf, MAX_SILENCE_FRAMES * BYTES_PER_FRAME); | |
663 | touch_memory(outputbuf->buf, outputbuf->size); | |
664 | #endif | |
665 | ||
666 | // start output thread | |
667 | pthread_attr_t attr; | |
668 | pthread_attr_init(&attr); | |
669 | pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE); | |
670 | pthread_create(&thread, &attr, output_thread, rates[0] ? "probe" : NULL); | |
671 | pthread_attr_destroy(&attr); | |
672 | ||
673 | // try to set this thread to real-time scheduler class, only works as root or if user has permission | |
674 | struct sched_param param; | |
675 | param.sched_priority = rt_priority; | |
676 | if (pthread_setschedparam(thread, SCHED_FIFO, ¶m) != 0) { | |
677 | LOG_DEBUG("unable to set output sched fifo: %s", strerror(errno)); | |
678 | } else { | |
679 | LOG_DEBUG("set output sched fifo rt: %u", param.sched_priority); | |
680 | } | |
681 | } | |
682 | ||
683 | void output_close_alsa(void) { | |
684 | LOG_INFO("close output"); | |
685 | ||
686 | LOCK; | |
687 | running = false; | |
688 | UNLOCK; | |
689 | ||
690 | pthread_join(thread, NULL); | |
691 | ||
692 | if (alsa.write_buf) free(alsa.write_buf); | |
693 | ||
694 | output_close_common(); | |
695 | } | |
696 | ||
697 | #endif // ALSA | |
698 |
0 | /* | |
1 | * Squeezelite - lightweight headless squeezebox emulator | |
2 | * | |
3 | * (c) Adrian Smith 2012, 2013, triode1@btinternet.com | |
4 | * | |
5 | * This program is free software: you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation, either version 3 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | * | |
18 | */ | |
19 | ||
20 | // Portaudio output | |
21 | ||
22 | #include "squeezelite.h" | |
23 | ||
24 | #if PORTAUDIO | |
25 | ||
26 | #include <portaudio.h> | |
27 | #if OSX | |
28 | #include <pa_mac_core.h> | |
29 | #endif | |
30 | ||
31 | // ouput device | |
32 | static struct { | |
33 | unsigned rate; | |
34 | PaStream *stream; | |
35 | } pa; | |
36 | ||
37 | static log_level loglevel; | |
38 | ||
39 | static bool running = true; | |
40 | ||
41 | extern struct outputstate output; | |
42 | extern struct buffer *outputbuf; | |
43 | ||
44 | #define LOCK mutex_lock(outputbuf->mutex) | |
45 | #define UNLOCK mutex_unlock(outputbuf->mutex) | |
46 | ||
47 | extern u8_t *silencebuf; | |
48 | #if DSD | |
49 | extern u8_t *silencebuf_dop; | |
50 | #endif | |
51 | ||
52 | void list_devices(void) { | |
53 | PaError err; | |
54 | int i; | |
55 | ||
56 | if ((err = Pa_Initialize()) != paNoError) { | |
57 | LOG_WARN("error initialising port audio: %s", Pa_GetErrorText(err)); | |
58 | return; | |
59 | } | |
60 | ||
61 | printf("Output devices:\n"); | |
62 | for (i = 0; i < Pa_GetDeviceCount(); ++i) { | |
63 | printf(" %i - %s [%s]\n", i, Pa_GetDeviceInfo(i)->name, Pa_GetHostApiInfo(Pa_GetDeviceInfo(i)->hostApi)->name); | |
64 | } | |
65 | printf("\n"); | |
66 | ||
67 | if ((err = Pa_Terminate()) != paNoError) { | |
68 | LOG_WARN("error closing port audio: %s", Pa_GetErrorText(err)); | |
69 | } | |
70 | } | |
71 | ||
72 | static int pa_device_id(const char *device) { | |
73 | int len = strlen(device); | |
74 | int i; | |
75 | ||
76 | if (!strncmp(device, "default", 7)) { | |
77 | return Pa_GetDefaultOutputDevice(); | |
78 | } | |
79 | if (len >= 1 && len <= 2 && device[0] >= '0' && device[0] <= '9') { | |
80 | return atoi(device); | |
81 | } | |
82 | ||
83 | #define DEVICE_ID_MAXLEN 256 | |
84 | for (i = 0; i < Pa_GetDeviceCount(); ++i) { | |
85 | char tmp[DEVICE_ID_MAXLEN]; | |
86 | snprintf(tmp, DEVICE_ID_MAXLEN, "%s [%s]", Pa_GetDeviceInfo(i)->name, Pa_GetHostApiInfo(Pa_GetDeviceInfo(i)->hostApi)->name); | |
87 | if (!strncmp(tmp, device, len)) { | |
88 | return i; | |
89 | } | |
90 | } | |
91 | ||
92 | return -1; | |
93 | } | |
94 | ||
95 | static int pa_callback(const void *pa_input, void *pa_output, unsigned long pa_frames_wanted, | |
96 | const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData); | |
97 | ||
98 | bool test_open(const char *device, unsigned rates[]) { | |
99 | PaStreamParameters outputParameters; | |
100 | PaError err; | |
101 | unsigned ref[] TEST_RATES; | |
102 | int device_id, i, ind; | |
103 | ||
104 | if ((device_id = pa_device_id(device)) == -1) { | |
105 | LOG_INFO("device %s not found", device); | |
106 | return false; | |
107 | } | |
108 | ||
109 | outputParameters.device = device_id; | |
110 | outputParameters.channelCount = 2; | |
111 | outputParameters.sampleFormat = paInt32; | |
112 | outputParameters.suggestedLatency = | |
113 | output.latency ? (double)output.latency/(double)1000 : Pa_GetDeviceInfo(outputParameters.device)->defaultHighOutputLatency; | |
114 | outputParameters.hostApiSpecificStreamInfo = NULL; | |
115 | ||
116 | // check supported sample rates | |
117 | // Note use Pa_OpenStream as it appears more reliable than Pa_IsFormatSupported on some windows apis | |
118 | for (i = 0, ind = 0; ref[i]; ++i) { | |
119 | err = Pa_OpenStream(&pa.stream, NULL, &outputParameters, (double)ref[i], paFramesPerBufferUnspecified, paNoFlag, | |
120 | pa_callback, NULL); | |
121 | if (err == paNoError) { | |
122 | Pa_CloseStream(pa.stream); | |
123 | rates[ind++] = ref[i]; | |
124 | } | |
125 | } | |
126 | ||
127 | if (!rates[0]) { | |
128 | LOG_WARN("no available rate found"); | |
129 | return false; | |
130 | } | |
131 | ||
132 | pa.stream = NULL; | |
133 | return true; | |
134 | } | |
135 | ||
136 | static void pa_stream_finished(void *userdata) { | |
137 | if (running) { | |
138 | LOG_INFO("stream finished"); | |
139 | LOCK; | |
140 | output.pa_reopen = true; | |
141 | wake_controller(); | |
142 | UNLOCK; | |
143 | } | |
144 | } | |
145 | ||
146 | static thread_type monitor_thread; | |
147 | bool monitor_thread_running = false; | |
148 | ||
149 | static void *pa_monitor() { | |
150 | bool output_off; | |
151 | ||
152 | LOCK; | |
153 | ||
154 | if (monitor_thread_running) { | |
155 | LOG_DEBUG("monitor thread already running"); | |
156 | UNLOCK; | |
157 | return 0; | |
158 | } | |
159 | ||
160 | LOG_DEBUG("start monitor thread"); | |
161 | ||
162 | monitor_thread_running = true; | |
163 | output_off = (output.state == OUTPUT_OFF); | |
164 | ||
165 | while (monitor_thread_running) { | |
166 | if (output_off) { | |
167 | if (output.state != OUTPUT_OFF) { | |
168 | LOG_INFO("output on"); | |
169 | break; | |
170 | } | |
171 | } else { | |
172 | // this is a hack to partially support hot plugging of devices | |
173 | // we rely on terminating and reinitalising PA to get an updated list of devices and use name for output.device | |
174 | LOG_INFO("probing device %s", output.device); | |
175 | Pa_Terminate(); | |
176 | Pa_Initialize(); | |
177 | pa.stream = NULL; | |
178 | if (pa_device_id(output.device) != -1) { | |
179 | LOG_INFO("device reopen"); | |
180 | break; | |
181 | } | |
182 | } | |
183 | ||
184 | UNLOCK; | |
185 | sleep(output_off ? 1 : 5); | |
186 | LOCK; | |
187 | } | |
188 | ||
189 | LOG_DEBUG("end monitor thread"); | |
190 | ||
191 | monitor_thread_running = false; | |
192 | pa.stream = NULL; | |
193 | ||
194 | _pa_open(); | |
195 | ||
196 | UNLOCK; | |
197 | ||
198 | return 0; | |
199 | } | |
200 | ||
201 | void _pa_open(void) { | |
202 | PaStreamParameters outputParameters; | |
203 | PaError err = paNoError; | |
204 | int device_id; | |
205 | ||
206 | if (pa.stream) { | |
207 | if ((err = Pa_CloseStream(pa.stream)) != paNoError) { | |
208 | LOG_WARN("error closing stream: %s", Pa_GetErrorText(err)); | |
209 | } | |
210 | } | |
211 | ||
212 | if (output.state == OUTPUT_OFF) { | |
213 | // we get called when transitioning to OUTPUT_OFF to create the probe thread | |
214 | // set err to avoid opening device and logging messages | |
215 | err = 1; | |
216 | ||
217 | } else if ((device_id = pa_device_id(output.device)) == -1) { | |
218 | LOG_INFO("device %s not found", output.device); | |
219 | err = 1; | |
220 | ||
221 | } else { | |
222 | ||
223 | outputParameters.device = device_id; | |
224 | outputParameters.channelCount = 2; | |
225 | outputParameters.sampleFormat = paInt32; | |
226 | outputParameters.suggestedLatency = | |
227 | output.latency ? (double)output.latency/(double)1000 : Pa_GetDeviceInfo(outputParameters.device)->defaultHighOutputLatency; | |
228 | outputParameters.hostApiSpecificStreamInfo = NULL; | |
229 | ||
230 | #if OSX | |
231 | // enable pro mode which aims to avoid resampling if possible | |
232 | // see http://code.google.com/p/squeezelite/issues/detail?id=11 & http://code.google.com/p/squeezelite/issues/detail?id=37 | |
233 | // command line controls osx_playnice which is -1 if not specified, 0 or 1 - choose playnice if -1 or 1 | |
234 | PaMacCoreStreamInfo macInfo; | |
235 | unsigned long streamInfoFlags; | |
236 | if (output.osx_playnice) { | |
237 | LOG_INFO("opening device in PlayNice mode"); | |
238 | streamInfoFlags = paMacCorePlayNice; | |
239 | } else { | |
240 | LOG_INFO("opening device in Pro mode"); | |
241 | streamInfoFlags = paMacCorePro; | |
242 | } | |
243 | PaMacCore_SetupStreamInfo(&macInfo, streamInfoFlags); | |
244 | outputParameters.hostApiSpecificStreamInfo = &macInfo; | |
245 | #endif | |
246 | } | |
247 | ||
248 | if (!err && | |
249 | (err = Pa_OpenStream(&pa.stream, NULL, &outputParameters, (double)output.current_sample_rate, paFramesPerBufferUnspecified, | |
250 | paPrimeOutputBuffersUsingStreamCallback | paDitherOff, pa_callback, NULL)) != paNoError) { | |
251 | LOG_WARN("error opening device %i - %s : %s", outputParameters.device, Pa_GetDeviceInfo(outputParameters.device)->name, | |
252 | Pa_GetErrorText(err)); | |
253 | } | |
254 | ||
255 | if (!err) { | |
256 | LOG_INFO("opened device %i - %s at %u latency %u ms", outputParameters.device, Pa_GetDeviceInfo(outputParameters.device)->name, | |
257 | (unsigned int)Pa_GetStreamInfo(pa.stream)->sampleRate, (unsigned int)(Pa_GetStreamInfo(pa.stream)->outputLatency * 1000)); | |
258 | ||
259 | pa.rate = output.current_sample_rate; | |
260 | ||
261 | if ((err = Pa_SetStreamFinishedCallback(pa.stream, pa_stream_finished)) != paNoError) { | |
262 | LOG_WARN("error setting finish callback: %s", Pa_GetErrorText(err)); | |
263 | } | |
264 | ||
265 | if ((err = Pa_StartStream(pa.stream)) != paNoError) { | |
266 | LOG_WARN("error starting stream: %s", Pa_GetErrorText(err)); | |
267 | } | |
268 | } | |
269 | ||
270 | if (err && !monitor_thread_running) { | |
271 | vis_stop(); | |
272 | ||
273 | // create a thread to check for output state change or device return | |
274 | #if LINUX || OSX | |
275 | pthread_create(&monitor_thread, NULL, pa_monitor, NULL); | |
276 | #endif | |
277 | #if WIN | |
278 | monitor_thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&pa_monitor, NULL, 0, NULL); | |
279 | #endif | |
280 | } | |
281 | ||
282 | output.error_opening = !err; | |
283 | } | |
284 | ||
285 | static u8_t *optr; | |
286 | ||
287 | static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, | |
288 | s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr) { | |
289 | ||
290 | if (!silence) { | |
291 | ||
292 | if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && *cross_ptr) { | |
293 | _apply_cross(outputbuf, out_frames, cross_gain_in, cross_gain_out, cross_ptr); | |
294 | } | |
295 | ||
296 | if (gainL != FIXED_ONE || gainR!= FIXED_ONE) { | |
297 | _apply_gain(outputbuf, out_frames, gainL, gainR); | |
298 | } | |
299 | ||
300 | IF_DSD( | |
301 | if (output.dop) { | |
302 | update_dop_marker((u32_t *) outputbuf->readp, out_frames); | |
303 | } | |
304 | ) | |
305 | ||
306 | memcpy(optr, outputbuf->readp, out_frames * BYTES_PER_FRAME); | |
307 | ||
308 | } else { | |
309 | ||
310 | u8_t *buf = silencebuf; | |
311 | ||
312 | IF_DSD( | |
313 | if (output.dop) { | |
314 | buf = silencebuf_dop; | |
315 | update_dop_marker((u32_t *) buf, out_frames); | |
316 | } | |
317 | ) | |
318 | ||
319 | memcpy(optr, buf, out_frames * BYTES_PER_FRAME); | |
320 | } | |
321 | ||
322 | optr += out_frames * BYTES_PER_FRAME; | |
323 | ||
324 | return (int)out_frames; | |
325 | } | |
326 | ||
327 | static int pa_callback(const void *pa_input, void *pa_output, unsigned long pa_frames_wanted, | |
328 | const PaStreamCallbackTimeInfo *time_info, PaStreamCallbackFlags statusFlags, void *userData) { | |
329 | int ret; | |
330 | frames_t frames; | |
331 | ||
332 | optr = (u8_t *)pa_output; | |
333 | ||
334 | LOCK; | |
335 | ||
336 | output.device_frames = (unsigned)((time_info->outputBufferDacTime - Pa_GetStreamTime(pa.stream)) * output.current_sample_rate); | |
337 | output.updated = gettime_ms(); | |
338 | ||
339 | frames = _output_frames(pa_frames_wanted); | |
340 | ||
341 | if (frames < pa_frames_wanted) { | |
342 | LOG_SDEBUG("pad with silence"); | |
343 | memset(optr, 0, (pa_frames_wanted - frames) * BYTES_PER_FRAME); | |
344 | } | |
345 | ||
346 | if (output.state == OUTPUT_OFF) { | |
347 | LOG_INFO("output off"); | |
348 | ret = paComplete; | |
349 | } else if (pa.rate != output.current_sample_rate) { | |
350 | ret = paComplete; | |
351 | } else { | |
352 | ret = paContinue; | |
353 | } | |
354 | ||
355 | UNLOCK; | |
356 | ||
357 | return ret; | |
358 | } | |
359 | ||
360 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[]) { | |
361 | PaError err; | |
362 | unsigned latency = 0; | |
363 | int osx_playnice = -1; | |
364 | ||
365 | char *l = next_param(params, ':'); | |
366 | char *p = next_param(NULL, ':'); | |
367 | ||
368 | if (l) latency = (unsigned)atoi(l); | |
369 | if (p) osx_playnice = atoi(p); | |
370 | ||
371 | loglevel = level; | |
372 | ||
373 | LOG_INFO("init output"); | |
374 | ||
375 | memset(&output, 0, sizeof(output)); | |
376 | ||
377 | output.latency = latency; | |
378 | output.osx_playnice = osx_playnice; | |
379 | output.format = 0; | |
380 | output.start_frames = 0; | |
381 | output.write_cb = &_write_frames; | |
382 | pa.stream = NULL; | |
383 | ||
384 | LOG_INFO("requested latency: %u", output.latency); | |
385 | ||
386 | if ((err = Pa_Initialize()) != paNoError) { | |
387 | LOG_WARN("error initialising port audio: %s", Pa_GetErrorText(err)); | |
388 | exit(0); | |
389 | } | |
390 | ||
391 | output_init_common(level, device, output_buf_size, rates); | |
392 | ||
393 | LOCK; | |
394 | ||
395 | _pa_open(); | |
396 | ||
397 | UNLOCK; | |
398 | } | |
399 | ||
400 | void output_close_pa(void) { | |
401 | PaError err; | |
402 | ||
403 | LOG_INFO("close output"); | |
404 | ||
405 | LOCK; | |
406 | ||
407 | running = false; | |
408 | monitor_thread_running = false; | |
409 | ||
410 | if (pa.stream) { | |
411 | if ((err = Pa_AbortStream(pa.stream)) != paNoError) { | |
412 | LOG_WARN("error closing stream: %s", Pa_GetErrorText(err)); | |
413 | } | |
414 | } | |
415 | ||
416 | if ((err = Pa_Terminate()) != paNoError) { | |
417 | LOG_WARN("error closing port audio: %s", Pa_GetErrorText(err)); | |
418 | } | |
419 | ||
420 | UNLOCK; | |
421 | ||
422 | output_close_common(); | |
423 | } | |
424 | ||
425 | #endif // PORTAUDIO |
0 | /* | |
1 | * Squeezelite - lightweight headless squeezebox emulator | |
2 | * | |
3 | * (c) Adrian Smith 2012, 2013, triode1@btinternet.com | |
4 | * | |
5 | * This program is free software: you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation, either version 3 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | * | |
18 | */ | |
19 | ||
20 | // Scale and pack functions | |
21 | ||
22 | #include "squeezelite.h" | |
23 | ||
24 | #define MAX_SCALESAMPLE 0x7fffffffffffLL | |
25 | #define MIN_SCALESAMPLE -MAX_SCALESAMPLE | |
26 | ||
27 | // inlining these on windows prevents them being linkable... | |
28 | #if !WIN | |
29 | inline | |
30 | #endif | |
31 | s32_t gain(s32_t gain, s32_t sample) { | |
32 | s64_t res = (s64_t)gain * (s64_t)sample; | |
33 | if (res > MAX_SCALESAMPLE) res = MAX_SCALESAMPLE; | |
34 | if (res < MIN_SCALESAMPLE) res = MIN_SCALESAMPLE; | |
35 | return (s32_t) (res >> 16); | |
36 | } | |
37 | #if !WIN | |
38 | inline | |
39 | #endif | |
40 | s32_t to_gain(float f) { | |
41 | return (s32_t)(f * 65536.0F); | |
42 | } | |
43 | ||
44 | void _scale_and_pack_frames(void *outputptr, s32_t *inputptr, frames_t cnt, s32_t gainL, s32_t gainR, output_format format) { | |
45 | switch(format) { | |
46 | case S16_LE: | |
47 | { | |
48 | u32_t *optr = (u32_t *)(void *)outputptr; | |
49 | #if SL_LITTLE_ENDIAN | |
50 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
51 | while (cnt--) { | |
52 | *(optr++) = (*(inputptr) >> 16 & 0x0000ffff) | (*(inputptr + 1) & 0xffff0000); | |
53 | inputptr += 2; | |
54 | } | |
55 | } else { | |
56 | while (cnt--) { | |
57 | *(optr++) = (gain(gainL, *(inputptr)) >> 16 & 0x0000ffff) | (gain(gainR, *(inputptr+1)) & 0xffff0000); | |
58 | inputptr += 2; | |
59 | } | |
60 | } | |
61 | #else | |
62 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
63 | while (cnt--) { | |
64 | s32_t lsample = *(inputptr++); | |
65 | s32_t rsample = *(inputptr++); | |
66 | *(optr++) = | |
67 | (lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 | | |
68 | (rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24; | |
69 | } | |
70 | } else { | |
71 | while (cnt--) { | |
72 | s32_t lsample = gain(gainL, *(inputptr++)); | |
73 | s32_t rsample = gain(gainR, *(inputptr++)); | |
74 | *(optr++) = | |
75 | (lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 | | |
76 | (rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24; | |
77 | } | |
78 | } | |
79 | #endif | |
80 | } | |
81 | break; | |
82 | case S24_LE: | |
83 | { | |
84 | u32_t *optr = (u32_t *)(void *)outputptr; | |
85 | #if SL_LITTLE_ENDIAN | |
86 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
87 | while (cnt--) { | |
88 | *(optr++) = *(inputptr++) >> 8; | |
89 | *(optr++) = *(inputptr++) >> 8; | |
90 | } | |
91 | } else { | |
92 | while (cnt--) { | |
93 | *(optr++) = gain(gainL, *(inputptr++)) >> 8; | |
94 | *(optr++) = gain(gainR, *(inputptr++)) >> 8; | |
95 | } | |
96 | } | |
97 | #else | |
98 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
99 | while (cnt--) { | |
100 | s32_t lsample = *(inputptr++); | |
101 | s32_t rsample = *(inputptr++); | |
102 | *(optr++) = | |
103 | (lsample & 0xff000000) >> 16 | (lsample & 0x00ff0000) | (lsample & 0x0000ff00 << 16); | |
104 | *(optr++) = | |
105 | (rsample & 0xff000000) >> 16 | (rsample & 0x00ff0000) | (rsample & 0x0000ff00 << 16); | |
106 | } | |
107 | } else { | |
108 | while (cnt--) { | |
109 | s32_t lsample = gain(gainL, *(inputptr++)); | |
110 | s32_t rsample = gain(gainR, *(inputptr++)); | |
111 | *(optr++) = | |
112 | (lsample & 0xff000000) >> 16 | (lsample & 0x00ff0000) | (lsample & 0x0000ff00 << 16); | |
113 | *(optr++) = | |
114 | (rsample & 0xff000000) >> 16 | (rsample & 0x00ff0000) | (rsample & 0x0000ff00 << 16); | |
115 | } | |
116 | } | |
117 | #endif | |
118 | } | |
119 | break; | |
120 | case S24_3LE: | |
121 | { | |
122 | u8_t *optr = (u8_t *)(void *)outputptr; | |
123 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
124 | while (cnt) { | |
125 | // attempt to do 32 bit memory accesses - move 2 frames at once: 16 bytes -> 12 bytes | |
126 | // falls through to exception case when not aligned or if less than 2 frames to move | |
127 | if (((uintptr_t)optr & 0x3) == 0 && cnt >= 2) { | |
128 | u32_t *o_ptr = (u32_t *)(void *)optr; | |
129 | while (cnt >= 2) { | |
130 | s32_t l1 = *(inputptr++); s32_t r1 = *(inputptr++); | |
131 | s32_t l2 = *(inputptr++); s32_t r2 = *(inputptr++); | |
132 | #if SL_LITTLE_ENDIAN | |
133 | *(o_ptr++) = (l1 & 0xffffff00) >> 8 | (r1 & 0x0000ff00) << 16; | |
134 | *(o_ptr++) = (r1 & 0xffff0000) >> 16 | (l2 & 0x00ffff00) << 8; | |
135 | *(o_ptr++) = (l2 & 0xff000000) >> 24 | (r2 & 0xffffff00); | |
136 | #else | |
137 | *(o_ptr++) = (l1 & 0x0000ff00) << 16 | (l1 & 0x00ff0000) | (l1 & 0xff000000) >> 16 | | |
138 | (r1 & 0x0000ff00) >> 8; | |
139 | *(o_ptr++) = (r1 & 0x00ff0000) << 8 | (r1 & 0xff000000) >> 8 | (l2 & 0x0000ff00) | | |
140 | (l2 & 0x00ff0000) >> 16; | |
141 | *(o_ptr++) = (l2 & 0xff000000) | (r2 & 0x0000ff00) << 8 | (r2 & 0x00ff0000) >> 8 | | |
142 | (r2 & 0xff000000) >> 24; | |
143 | #endif | |
144 | optr += 12; | |
145 | cnt -= 2; | |
146 | } | |
147 | } else { | |
148 | s32_t lsample = *(inputptr++); | |
149 | s32_t rsample = *(inputptr++); | |
150 | *(optr++) = (lsample & 0x0000ff00) >> 8; | |
151 | *(optr++) = (lsample & 0x00ff0000) >> 16; | |
152 | *(optr++) = (lsample & 0xff000000) >> 24; | |
153 | *(optr++) = (rsample & 0x0000ff00) >> 8; | |
154 | *(optr++) = (rsample & 0x00ff0000) >> 16; | |
155 | *(optr++) = (rsample & 0xff000000) >> 24; | |
156 | cnt--; | |
157 | } | |
158 | } | |
159 | } else { | |
160 | while (cnt) { | |
161 | // attempt to do 32 bit memory accesses - move 2 frames at once: 16 bytes -> 12 bytes | |
162 | // falls through to exception case when not aligned or if less than 2 frames to move | |
163 | if (((uintptr_t)optr & 0x3) == 0 && cnt >= 2) { | |
164 | u32_t *o_ptr = (u32_t *)(void *)optr; | |
165 | while (cnt >= 2) { | |
166 | s32_t l1 = gain(gainL, *(inputptr++)); s32_t r1 = gain(gainR, *(inputptr++)); | |
167 | s32_t l2 = gain(gainL, *(inputptr++)); s32_t r2 = gain(gainR, *(inputptr++)); | |
168 | #if SL_LITTLE_ENDIAN | |
169 | *(o_ptr++) = (l1 & 0xffffff00) >> 8 | (r1 & 0x0000ff00) << 16; | |
170 | *(o_ptr++) = (r1 & 0xffff0000) >> 16 | (l2 & 0x00ffff00) << 8; | |
171 | *(o_ptr++) = (l2 & 0xff000000) >> 24 | (r2 & 0xffffff00); | |
172 | #else | |
173 | *(o_ptr++) = (l1 & 0x0000ff00) << 16 | (l1 & 0x00ff0000) | (l1 & 0xff000000) >> 16 | | |
174 | (r1 & 0x0000ff00) >> 8; | |
175 | *(o_ptr++) = (r1 & 0x00ff0000) << 8 | (r1 & 0xff000000) >> 8 | (l2 & 0x0000ff00) | | |
176 | (l2 & 0x00ff0000) >> 16; | |
177 | *(o_ptr++) = (l2 & 0xff000000) | (r2 & 0x0000ff00) << 8 | (r2 & 0x00ff0000) >> 8 | | |
178 | (r2 & 0xff000000) >> 24; | |
179 | #endif | |
180 | optr += 12; | |
181 | cnt -= 2; | |
182 | } | |
183 | } else { | |
184 | s32_t lsample = gain(gainL, *(inputptr++)); | |
185 | s32_t rsample = gain(gainR, *(inputptr++)); | |
186 | *(optr++) = (lsample & 0x0000ff00) >> 8; | |
187 | *(optr++) = (lsample & 0x00ff0000) >> 16; | |
188 | *(optr++) = (lsample & 0xff000000) >> 24; | |
189 | *(optr++) = (rsample & 0x0000ff00) >> 8; | |
190 | *(optr++) = (rsample & 0x00ff0000) >> 16; | |
191 | *(optr++) = (rsample & 0xff000000) >> 24; | |
192 | cnt--; | |
193 | } | |
194 | } | |
195 | } | |
196 | } | |
197 | break; | |
198 | case S32_LE: | |
199 | { | |
200 | u32_t *optr = (u32_t *)(void *)outputptr; | |
201 | #if SL_LITTLE_ENDIAN | |
202 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
203 | memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME); | |
204 | } else { | |
205 | while (cnt--) { | |
206 | *(optr++) = gain(gainL, *(inputptr++)); | |
207 | *(optr++) = gain(gainR, *(inputptr++)); | |
208 | } | |
209 | } | |
210 | #else | |
211 | if (gainL == FIXED_ONE && gainR == FIXED_ONE) { | |
212 | while (cnt--) { | |
213 | s32_t lsample = *(inputptr++); | |
214 | s32_t rsample = *(inputptr++); | |
215 | *(optr++) = | |
216 | (lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 | | |
217 | (lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24; | |
218 | *(optr++) = | |
219 | (rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 | | |
220 | (rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24; | |
221 | } | |
222 | } else { | |
223 | while (cnt--) { | |
224 | s32_t lsample = gain(gainL, *(inputptr++)); | |
225 | s32_t rsample = gain(gainR, *(inputptr++)); | |
226 | *(optr++) = | |
227 | (lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 | | |
228 | (lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24; | |
229 | *(optr++) = | |
230 | (rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 | | |
231 | (rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24; | |
232 | } | |
233 | } | |
234 | #endif | |
235 | } | |
236 | break; | |
237 | default: | |
238 | break; | |
239 | } | |
240 | } | |
241 | ||
242 | #if !WIN | |
243 | inline | |
244 | #endif | |
245 | void _apply_cross(struct buffer *outputbuf, frames_t out_frames, s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr) { | |
246 | s32_t *ptr = (s32_t *)(void *)outputbuf->readp; | |
247 | frames_t count = out_frames * 2; | |
248 | while (count--) { | |
249 | if (*cross_ptr > (s32_t *)outputbuf->wrap) { | |
250 | *cross_ptr -= outputbuf->size / BYTES_PER_FRAME * 2; | |
251 | } | |
252 | *ptr = gain(cross_gain_out, *ptr) + gain(cross_gain_in, **cross_ptr); | |
253 | ptr++; (*cross_ptr)++; | |
254 | } | |
255 | } | |
256 | ||
257 | #if !WIN | |
258 | inline | |
259 | #endif | |
260 | void _apply_gain(struct buffer *outputbuf, frames_t count, s32_t gainL, s32_t gainR) { | |
261 | s32_t *ptrL = (s32_t *)(void *)outputbuf->readp; | |
262 | s32_t *ptrR = (s32_t *)(void *)outputbuf->readp + 1; | |
263 | while (count--) { | |
264 | *ptrL = gain(gainL, *ptrL); | |
265 | *ptrR = gain(gainR, *ptrR); | |
266 | ptrL += 2; | |
267 | ptrR += 2; | |
268 | } | |
269 | } |
0 | /* | |
1 | * Squeezelite - lightweight headless squeezebox emulator | |
2 | * | |
3 | * (c) Adrian Smith 2012, 2013, triode1@btinternet.com | |
4 | * | |
5 | * This program is free software: you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation, either version 3 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | * | |
18 | */ | |
19 | ||
20 | // Stdout output | |
21 | ||
22 | #include "squeezelite.h" | |
23 | ||
24 | #define FRAME_BLOCK MAX_SILENCE_FRAMES | |
25 | ||
26 | static log_level loglevel; | |
27 | ||
28 | static bool running = true; | |
29 | ||
30 | extern struct outputstate output; | |
31 | extern struct buffer *outputbuf; | |
32 | ||
33 | #define LOCK mutex_lock(outputbuf->mutex) | |
34 | #define UNLOCK mutex_unlock(outputbuf->mutex) | |
35 | ||
36 | extern u8_t *silencebuf; | |
37 | #if DSD | |
38 | extern u8_t *silencebuf_dop; | |
39 | #endif | |
40 | ||
41 | // buffer to hold output data so we can block on writing outside of output lock, allocated on init | |
42 | static u8_t *buf; | |
43 | static unsigned buffill; | |
44 | static int bytes_per_frame; | |
45 | ||
46 | static int _stdout_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, | |
47 | s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr) { | |
48 | ||
49 | u8_t *obuf; | |
50 | ||
51 | if (!silence) { | |
52 | ||
53 | if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && *cross_ptr) { | |
54 | _apply_cross(outputbuf, out_frames, cross_gain_in, cross_gain_out, cross_ptr); | |
55 | } | |
56 | ||
57 | obuf = outputbuf->readp; | |
58 | ||
59 | } else { | |
60 | ||
61 | obuf = silencebuf; | |
62 | } | |
63 | ||
64 | IF_DSD( | |
65 | if (output.dop) { | |
66 | if (silence) { | |
67 | obuf = silencebuf_dop; | |
68 | } | |
69 | update_dop_marker((u32_t *)obuf, out_frames); | |
70 | } | |
71 | ) | |
72 | ||
73 | _scale_and_pack_frames(buf + buffill * bytes_per_frame, (s32_t *)(void *)obuf, out_frames, gainL, gainR, output.format); | |
74 | ||
75 | buffill += out_frames; | |
76 | ||
77 | return (int)out_frames; | |
78 | } | |
79 | ||
80 | static void *output_thread() { | |
81 | ||
82 | LOCK; | |
83 | ||
84 | switch (output.format) { | |
85 | case S32_LE: | |
86 | bytes_per_frame = 4 * 2; break; | |
87 | case S24_3LE: | |
88 | bytes_per_frame = 3 * 2; break; | |
89 | case S16_LE: | |
90 | bytes_per_frame = 2 * 2; break; | |
91 | default: | |
92 | bytes_per_frame = 4 * 2; break; | |
93 | break; | |
94 | } | |
95 | ||
96 | UNLOCK; | |
97 | ||
98 | while (running) { | |
99 | ||
100 | LOCK; | |
101 | ||
102 | output.device_frames = 0; | |
103 | output.updated = gettime_ms(); | |
104 | ||
105 | _output_frames(FRAME_BLOCK); | |
106 | ||
107 | UNLOCK; | |
108 | ||
109 | if (buffill) { | |
110 | fwrite(buf, bytes_per_frame, buffill, stdout); | |
111 | buffill = 0; | |
112 | } | |
113 | ||
114 | } | |
115 | ||
116 | return 0; | |
117 | } | |
118 | ||
119 | static thread_type thread; | |
120 | ||
121 | void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[]) { | |
122 | loglevel = level; | |
123 | ||
124 | LOG_INFO("init output stdout"); | |
125 | ||
126 | buf = malloc(FRAME_BLOCK * BYTES_PER_FRAME); | |
127 | if (!buf) { | |
128 | LOG_ERROR("unable to malloc buf"); | |
129 | return; | |
130 | } | |
131 | buffill = 0; | |
132 | ||
133 | memset(&output, 0, sizeof(output)); | |
134 | ||
135 | output.format = S32_LE; | |
136 | output.start_frames = FRAME_BLOCK * 2; | |
137 | output.write_cb = &_stdout_write_frames; | |
138 | ||
139 | if (params) { | |
140 | if (!strcmp(params, "32")) output.format = S32_LE; | |
141 | if (!strcmp(params, "24")) output.format = S24_3LE; | |
142 | if (!strcmp(params, "16")) output.format = S16_LE; | |
143 | } | |
144 | ||
145 | // ensure output rate is specified to avoid test open | |
146 | if (!rates[0]) { | |
147 | rates[0] = 44100; | |
148 | } | |
149 | ||
150 | output_init_common(level, "-", output_buf_size, rates); | |
151 | ||
152 | #if LINUX || OSX | |
153 | pthread_attr_t attr; | |
154 | pthread_attr_init(&attr); | |
155 | pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE); | |
156 | pthread_create(&thread, &attr, output_thread, NULL); | |
157 | pthread_attr_destroy(&attr); | |
158 | #endif | |
159 | #if WIN | |
160 | thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&output_thread, NULL, 0, NULL); | |
161 | #endif | |
162 | } | |
163 | ||
164 | void output_close_stdout(void) { | |
165 | LOG_INFO("close output"); | |
166 | ||
167 | LOCK; | |
168 | running = false; | |
169 | UNLOCK; | |
170 | ||
171 | free(buf); | |
172 | ||
173 | output_close_common(); | |
174 | } |
0 | /* | |
1 | * Squeezelite - lightweight headless squeezebox emulator | |
2 | * | |
3 | * (c) Adrian Smith 2012, 2013, triode1@btinternet.com | |
4 | * | |
5 | * This program is free software: you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation, either version 3 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | * | |
18 | */ | |
19 | ||
20 | // Export audio samples for visualiser process (16 bit only best endevours) | |
21 | ||
22 | #include "squeezelite.h" | |
23 | ||
24 | #if VISEXPORT | |
25 | ||
26 | #include <sys/mman.h> | |
27 | #include <fcntl.h> | |
28 | ||
29 | #define VIS_BUF_SIZE 16384 | |
30 | #define VIS_LOCK_NS 1000000 // ns to wait for vis wrlock | |
31 | ||
32 | static struct vis_t { | |
33 | pthread_rwlock_t rwlock; | |
34 | u32_t buf_size; | |
35 | u32_t buf_index; | |
36 | bool running; | |
37 | u32_t rate; | |
38 | time_t updated; | |
39 | s16_t buffer[VIS_BUF_SIZE]; | |
40 | } *vis_mmap = NULL; | |
41 | ||
42 | static char vis_shm_path[40]; | |
43 | static int vis_fd = -1; | |
44 | ||
45 | static log_level loglevel; | |
46 | ||
47 | // attempt to write audio to vis_mmap but do not wait more than VIS_LOCK_NS to get wrlock | |
48 | // this can result in missing audio export to the mmap region, but this is preferable dropping audio | |
49 | void _vis_export(struct buffer *outputbuf, struct outputstate *output, frames_t out_frames, bool silence) { | |
50 | if (vis_mmap) { | |
51 | int err; | |
52 | ||
53 | err = pthread_rwlock_trywrlock(&vis_mmap->rwlock); | |
54 | if (err) { | |
55 | struct timespec ts; | |
56 | clock_gettime(CLOCK_REALTIME, &ts); | |
57 | ts.tv_nsec += VIS_LOCK_NS; | |
58 | if (ts.tv_nsec > 1000000000) { | |
59 | ts.tv_sec += 1; | |
60 | ts.tv_nsec -= 1000000000; | |
61 | } | |
62 | err = pthread_rwlock_timedwrlock(&vis_mmap->rwlock, &ts); | |
63 | } | |
64 | ||
65 | if (err) { | |
66 | LOG_DEBUG("failed to get wrlock - skipping visulizer export"); | |
67 | ||
68 | } else { | |
69 | ||
70 | if (silence) { | |
71 | vis_mmap->running = false; | |
72 | } else { | |
73 | frames_t vis_cnt = out_frames; | |
74 | s32_t *ptr = (s32_t *) outputbuf->readp; | |
75 | unsigned i = vis_mmap->buf_index; | |
76 | ||
77 | if (!output->current_replay_gain) { | |
78 | while (vis_cnt--) { | |
79 | vis_mmap->buffer[i++] = *(ptr++) >> 16; | |
80 | vis_mmap->buffer[i++] = *(ptr++) >> 16; | |
81 | if (i == VIS_BUF_SIZE) i = 0; | |
82 | } | |
83 | } else { | |
84 | while (vis_cnt--) { | |
85 | vis_mmap->buffer[i++] = gain(*(ptr++), output->current_replay_gain) >> 16; | |
86 | vis_mmap->buffer[i++] = gain(*(ptr++), output->current_replay_gain) >> 16; | |
87 | if (i == VIS_BUF_SIZE) i = 0; | |
88 | } | |
89 | } | |
90 | ||
91 | vis_mmap->updated = time(NULL); | |
92 | vis_mmap->running = true; | |
93 | vis_mmap->buf_index = i; | |
94 | vis_mmap->rate = output->current_sample_rate; | |
95 | } | |
96 | ||
97 | pthread_rwlock_unlock(&vis_mmap->rwlock); | |
98 | } | |
99 | } | |
100 | } | |
101 | ||
102 | void vis_stop(void) { | |
103 | if (vis_mmap) { | |
104 | pthread_rwlock_wrlock(&vis_mmap->rwlock); | |
105 | vis_mmap->running = false; | |
106 | pthread_rwlock_unlock(&vis_mmap->rwlock); | |
107 | } | |
108 | } | |
109 | ||
110 | void output_vis_init(log_level level, u8_t *mac) { | |
111 | loglevel = level; | |
112 | ||
113 | sprintf(vis_shm_path, "/squeezelite-%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); | |
114 | ||
115 | vis_fd = shm_open(vis_shm_path, O_CREAT | O_RDWR, 0666); | |
116 | if (vis_fd != -1) { | |
117 | if (ftruncate(vis_fd, sizeof(struct vis_t)) == 0) { | |
118 | vis_mmap = (struct vis_t *)mmap(NULL, sizeof(struct vis_t), PROT_READ | PROT_WRITE, MAP_SHARED, vis_fd, 0); | |
119 | } | |
120 | } | |
121 | ||
122 | if (vis_mmap > 0) { | |
123 | pthread_rwlockattr_t attr; | |
124 | pthread_rwlockattr_init(&attr); | |
125 | pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); | |
126 | pthread_rwlock_init(&vis_mmap->rwlock, &attr); | |
127 | vis_mmap->buf_size = VIS_BUF_SIZE; | |
128 | vis_mmap->running = false; | |
129 | vis_mmap->rate = 44100; | |
130 | pthread_rwlockattr_destroy(&attr); | |
131 | LOG_INFO("opened visulizer shared memory as %s", vis_shm_path); | |
132 | } else { | |
133 | LOG_WARN("unable to open visualizer shared memory"); | |
134 | vis_mmap = NULL; | |
135 | } | |
136 | } | |
137 | ||
138 | #endif // VISEXPORT |
58 | 58 | static u32_t sample_size; |
59 | 59 | static u32_t channels; |
60 | 60 | static bool bigendian; |
61 | static bool limit; | |
62 | static u32_t audio_left; | |
63 | static u32_t bytes_per_frame; | |
61 | 64 | |
62 | 65 | typedef enum { UNKNOWN = 0, WAVE, AIFF } header_format; |
63 | 66 | |
64 | void _check_header(void) { | |
67 | static void _check_header(void) { | |
65 | 68 | u8_t *ptr = streambuf->readp; |
66 | 69 | unsigned bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); |
67 | 70 | header_format format = UNKNOWN; |
99 | 102 | if (format == WAVE && !memcmp(ptr, "data", 4)) { |
100 | 103 | ptr += 8; |
101 | 104 | _buf_inc_readp(streambuf, ptr - streambuf->readp); |
105 | audio_left = len; | |
106 | limit = true; | |
102 | 107 | return; |
103 | 108 | } |
104 | 109 | |
107 | 112 | // following 4 bytes is blocksize - ignored |
108 | 113 | ptr += 8 + 8; |
109 | 114 | _buf_inc_readp(streambuf, ptr + offset - streambuf->readp); |
115 | audio_left = len; | |
116 | limit = true; | |
110 | 117 | return; |
111 | 118 | } |
112 | 119 | |
149 | 156 | } |
150 | 157 | |
151 | 158 | static decode_state pcm_decode(void) { |
152 | size_t in, out; | |
159 | unsigned bytes, in, out; | |
153 | 160 | frames_t frames, count; |
154 | 161 | u32_t *optr; |
155 | 162 | u8_t *iptr; |
163 | u8_t tmp[16]; | |
156 | 164 | |
157 | 165 | LOCK_S; |
158 | 166 | |
162 | 170 | |
163 | 171 | LOCK_O_direct; |
164 | 172 | |
165 | in = min(_buf_used(streambuf), _buf_cont_read(streambuf)) / (channels * sample_size); | |
173 | bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); | |
166 | 174 | |
167 | 175 | IF_DIRECT( |
168 | 176 | out = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; |
171 | 179 | out = process.max_in_frames; |
172 | 180 | ); |
173 | 181 | |
174 | if (stream.state <= DISCONNECT && in == 0) { | |
182 | if ((stream.state <= DISCONNECT && bytes == 0) || (limit && audio_left == 0)) { | |
175 | 183 | UNLOCK_O_direct; |
176 | 184 | UNLOCK_S; |
177 | 185 | return DECODE_COMPLETE; |
180 | 188 | if (decode.new_stream) { |
181 | 189 | LOG_INFO("setting track_start"); |
182 | 190 | LOCK_O_not_direct; |
183 | output.next_sample_rate = decode_newstream(sample_rate, output.max_sample_rate); | |
191 | output.next_sample_rate = decode_newstream(sample_rate, output.supported_rates); | |
184 | 192 | output.track_start = outputbuf->writep; |
193 | IF_DSD( output.next_dop = false; ) | |
185 | 194 | if (output.fade_mode) _checkfade(true); |
186 | 195 | decode.new_stream = false; |
187 | 196 | UNLOCK_O_not_direct; |
188 | 197 | IF_PROCESS( |
189 | 198 | out = process.max_in_frames; |
190 | 199 | ); |
200 | bytes_per_frame = channels * sample_size; | |
201 | } | |
202 | ||
203 | IF_DIRECT( | |
204 | optr = (u32_t *)outputbuf->writep; | |
205 | ); | |
206 | IF_PROCESS( | |
207 | optr = (u32_t *)process.inbuf; | |
208 | ); | |
209 | iptr = (u8_t *)streambuf->readp; | |
210 | ||
211 | in = bytes / bytes_per_frame; | |
212 | ||
213 | // handle frame wrapping round end of streambuf | |
214 | // - only need if resizing of streambuf does not avoid this, could occur in localfile case | |
215 | if (in == 0 && bytes > 0 && _buf_used(streambuf) >= bytes_per_frame) { | |
216 | memcpy(tmp, iptr, bytes); | |
217 | memcpy(tmp + bytes, streambuf->buf, bytes_per_frame - bytes); | |
218 | iptr = tmp; | |
219 | in = 1; | |
191 | 220 | } |
192 | 221 | |
193 | 222 | frames = min(in, out); |
194 | 223 | frames = min(frames, MAX_DECODE_FRAMES); |
195 | 224 | |
196 | IF_DIRECT( | |
197 | optr = (u32_t *)outputbuf->writep; | |
198 | ); | |
199 | IF_PROCESS( | |
200 | optr = (u32_t *)process.inbuf; | |
201 | ); | |
202 | ||
203 | iptr = (u8_t *)streambuf->readp; | |
225 | if (limit && frames * bytes_per_frame > audio_left) { | |
226 | LOG_INFO("reached end of audio"); | |
227 | frames = audio_left / bytes_per_frame; | |
228 | } | |
204 | 229 | |
205 | 230 | count = frames * channels; |
206 | 231 | |
308 | 333 | |
309 | 334 | LOG_SDEBUG("decoded %u frames", frames); |
310 | 335 | |
311 | _buf_inc_readp(streambuf, frames * channels * sample_size); | |
336 | _buf_inc_readp(streambuf, frames * bytes_per_frame); | |
337 | ||
338 | if (limit) { | |
339 | audio_left -= frames * bytes_per_frame; | |
340 | } | |
312 | 341 | |
313 | 342 | IF_DIRECT( |
314 | 343 | _buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME); |
328 | 357 | sample_rate = sample_rates[rate - '0']; |
329 | 358 | channels = chan - '0'; |
330 | 359 | bigendian = (endianness == '0'); |
360 | limit = false; | |
331 | 361 | |
332 | 362 | LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian); |
333 | 363 | buf_adjust(streambuf, sample_size * channels); |
115 | 115 | } |
116 | 116 | |
117 | 117 | // new stream - called with decode mutex set |
118 | unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned max_sample_rate) { | |
118 | unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned supported_rates[]) { | |
119 | 119 | |
120 | bool active = NEWSTREAM_FUNC(&process, raw_sample_rate, max_sample_rate); | |
120 | bool active = NEWSTREAM_FUNC(&process, raw_sample_rate, supported_rates); | |
121 | 121 | |
122 | 122 | LOG_INFO("processing: %s", active ? "active" : "inactive"); |
123 | 123 |
39 | 39 | double q_stopband_begin; /* Aliasing/imaging control; > passband_end 1 */ |
40 | 40 | double scale; |
41 | 41 | bool max_rate; |
42 | bool exception; | |
42 | 43 | #if !LINKALL |
43 | 44 | // soxr symbols to be dynamically loaded |
44 | 45 | soxr_io_spec_t (* soxr_io_spec)(soxr_datatype_t itype, soxr_datatype_t otype); |
121 | 122 | } |
122 | 123 | } |
123 | 124 | |
124 | bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned max_sample_rate) { | |
125 | unsigned outrate; | |
126 | ||
127 | if (r->max_rate) { | |
128 | outrate = max_sample_rate; | |
129 | } else { | |
130 | outrate = raw_sample_rate; | |
131 | while (outrate <= max_sample_rate) outrate <<= 1; | |
132 | outrate >>= 1; | |
125 | bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned supported_rates[]) { | |
126 | unsigned outrate = 0; | |
127 | int i; | |
128 | ||
129 | if (r->exception) { | |
130 | // find direct match - avoid resampling | |
131 | for (i = 0; supported_rates[i]; i++) { | |
132 | if (raw_sample_rate == supported_rates[i]) { | |
133 | outrate = raw_sample_rate; | |
134 | break; | |
135 | } | |
136 | } | |
137 | // else find next highest sync sample rate | |
138 | while (!outrate && i >= 0) { | |
139 | if (supported_rates[i] > raw_sample_rate && supported_rates[i] % raw_sample_rate == 0) { | |
140 | outrate = supported_rates[i]; | |
141 | break; | |
142 | } | |
143 | i--; | |
144 | } | |
145 | } | |
146 | ||
147 | if (!outrate) { | |
148 | if (r->max_rate) { | |
149 | // resample to max rate for device | |
150 | outrate = supported_rates[0]; | |
151 | } else { | |
152 | // resample to max sync sample rate | |
153 | for (i = 0; supported_rates[i]; i++) { | |
154 | if (supported_rates[i] % raw_sample_rate == 0 || raw_sample_rate % supported_rates[i] == 0) { | |
155 | outrate = supported_rates[i]; | |
156 | break; | |
157 | } | |
158 | } | |
159 | } | |
160 | if (!outrate) { | |
161 | outrate = supported_rates[0]; | |
162 | } | |
133 | 163 | } |
134 | 164 | |
135 | 165 | process->in_sample_rate = raw_sample_rate; |
234 | 264 | r->resampler = NULL; |
235 | 265 | r->old_clips = 0; |
236 | 266 | r->max_rate = false; |
267 | r->exception = false; | |
237 | 268 | |
238 | 269 | if (!load_soxr()) { |
239 | 270 | LOG_WARN("resampling disabled"); |
273 | 304 | if (strchr(recipe, 's')) r->q_recipe |= SOXR_STEEP_FILTER; |
274 | 305 | // X = async resampling to max_rate |
275 | 306 | if (strchr(recipe, 'X')) r->max_rate = true; |
307 | // E = exception, only resample if native rate is not supported | |
308 | if (strchr(recipe, 'E')) r->exception = true; | |
276 | 309 | } |
277 | 310 | |
278 | 311 | if (flags) { |
74 | 74 | bool sentSTMu, sentSTMo, sentSTMl; |
75 | 75 | u32_t new_server; |
76 | 76 | char *new_server_cap; |
77 | #define PLAYER_NAME_LEN 64 | |
78 | char player_name[PLAYER_NAME_LEN + 1] = ""; | |
79 | const char *name_file = NULL; | |
77 | 80 | |
78 | 81 | void send_packet(u8_t *packet, size_t len) { |
79 | 82 | u8_t *ptr = packet; |
381 | 384 | UNLOCK_O; |
382 | 385 | } |
383 | 386 | |
387 | static void process_setd(u8_t *pkt, int len) { | |
388 | struct setd_packet *setd = (struct setd_packet *)pkt; | |
389 | ||
390 | // handle player name query and change | |
391 | if (setd->id == 0) { | |
392 | if (len == 5) { | |
393 | if (strlen(player_name)) { | |
394 | sendSETDName(player_name); | |
395 | } | |
396 | } else if (len > 5) { | |
397 | strncpy(player_name, setd->data, PLAYER_NAME_LEN); | |
398 | player_name[PLAYER_NAME_LEN] = '\0'; | |
399 | LOG_INFO("set name: %s", setd->data); | |
400 | // confirm change to server | |
401 | sendSETDName(setd->data); | |
402 | // write name to name_file if -N option set | |
403 | if (name_file) { | |
404 | FILE *fp = fopen(name_file, "w"); | |
405 | if (fp) { | |
406 | LOG_INFO("storing name in %s", name_file); | |
407 | fputs(player_name, fp); | |
408 | fclose(fp); | |
409 | } else { | |
410 | LOG_WARN("unable to store new name in %s", name_file); | |
411 | } | |
412 | } | |
413 | } | |
414 | } | |
415 | } | |
416 | ||
384 | 417 | #define SYNC_CAP ",SyncgroupID=" |
385 | 418 | #define SYNC_CAP_LEN 13 |
386 | 419 | |
417 | 450 | { "codc", process_codc }, |
418 | 451 | { "aude", process_aude }, |
419 | 452 | { "audg", process_audg }, |
453 | { "setd", process_setd }, | |
420 | 454 | { "serv", process_serv }, |
421 | 455 | { "", NULL }, |
422 | 456 | }; |
677 | 711 | return s.sin_addr.s_addr; |
678 | 712 | } |
679 | 713 | |
680 | void slimproto(log_level level, char *server, u8_t mac[6], const char *name) { | |
714 | void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile) { | |
681 | 715 | struct sockaddr_in serv_addr; |
682 | 716 | static char fixed_cap[128], var_cap[128] = ""; |
683 | 717 | bool reconnect = false; |
702 | 736 | slimproto_port = PORT; |
703 | 737 | } |
704 | 738 | |
739 | if (name) { | |
740 | strncpy(player_name, name, PLAYER_NAME_LEN); | |
741 | player_name[PLAYER_NAME_LEN] = '\0'; | |
742 | } | |
743 | ||
744 | if (namefile) { | |
745 | FILE *fp; | |
746 | name_file = namefile; | |
747 | fp = fopen(namefile, "r"); | |
748 | if (fp) { | |
749 | if (!fgets(player_name, PLAYER_NAME_LEN, fp)) { | |
750 | player_name[PLAYER_NAME_LEN] = '\0'; | |
751 | } else { | |
752 | LOG_INFO("retrived name %s from %s", player_name, name_file); | |
753 | } | |
754 | fclose(fp); | |
755 | } | |
756 | } | |
757 | ||
705 | 758 | if (!running) return; |
706 | 759 | |
707 | 760 | LOCK_O; |
708 | sprintf(fixed_cap, ",MaxSampleRate=%u", output.max_sample_rate); | |
761 | sprintf(fixed_cap, ",MaxSampleRate=%u", output.supported_rates[0]); | |
709 | 762 | |
710 | 763 | for (i = 0; i < MAX_CODECS; i++) { |
711 | 764 | if (codecs[i] && codecs[i]->id && strlen(fixed_cap) < 128 - 10) { |
780 | 833 | |
781 | 834 | sendHELO(reconnect, fixed_cap, var_cap, mac); |
782 | 835 | |
783 | if (name) { | |
784 | sendSETDName(name); | |
785 | } | |
786 | ||
787 | 836 | slimproto_run(); |
788 | 837 | |
789 | 838 | if (!reconnect) { |
144 | 144 | // possible sync group |
145 | 145 | }; |
146 | 146 | |
147 | // S:P:Squeezebox2 | |
148 | struct setd_packet { | |
149 | char opcode[4]; | |
150 | u8_t id; | |
151 | char data[]; | |
152 | }; | |
153 | ||
147 | 154 | // codec open - this is an extension to slimproto to allow the server to read the header and then return decode params |
148 | 155 | struct codc_packet { |
149 | 156 | char opcode[4]; |
17 | 17 | * |
18 | 18 | */ |
19 | 19 | |
20 | // make may define: PORTAUDIO, SELFPIPE or RESAMPLE to influence build | |
21 | ||
22 | #define VERSION "v1.3.1" | |
20 | // make may define: PORTAUDIO, SELFPIPE, RESAMPLE, VISEXPORT, DSD, LINKALL to influence build | |
21 | ||
22 | #define VERSION "v1.4" | |
23 | 23 | |
24 | 24 | // build detection |
25 | 25 | #if defined(linux) |
85 | 85 | #define VISEXPORT 0 |
86 | 86 | #endif |
87 | 87 | |
88 | #if defined(DSD) | |
89 | #undef DSD | |
90 | #define DSD 1 | |
91 | #define IF_DSD(x) { x } | |
92 | #else | |
93 | #undef DSD | |
94 | #define DSD 0 | |
95 | #define IF_DSD(x) | |
96 | #endif | |
97 | ||
88 | 98 | #if defined(LINKALL) |
89 | 99 | #undef LINKALL |
90 | 100 | #define LINKALL 1 // link all libraries at build time - requires all to be available at run time |
96 | 106 | #if !LINKALL |
97 | 107 | |
98 | 108 | // dynamically loaded libraries at run time |
109 | ||
99 | 110 | #if LINUX |
100 | 111 | #define LIBFLAC "libFLAC.so.8" |
101 | 112 | #define LIBMAD "libmad.so.0" |
236 | 247 | #define ERROR_WOULDBLOCK WSAEWOULDBLOCK |
237 | 248 | #define open _open |
238 | 249 | #define read _read |
250 | #define snprintf _snprintf | |
239 | 251 | |
240 | 252 | #define in_addr_t u32_t |
241 | 253 | #define socklen_t int |
292 | 304 | #else |
293 | 305 | #error can not support u64_t |
294 | 306 | #endif |
307 | ||
308 | #define MAX_SILENCE_FRAMES 2048 | |
295 | 309 | |
296 | 310 | #define FIXED_ONE 0x10000 |
297 | 311 | |
368 | 382 | void buf_destroy(struct buffer *buf); |
369 | 383 | |
370 | 384 | // slimproto.c |
371 | void slimproto(log_level level, char *server, u8_t mac[6], const char *name); | |
385 | void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile); | |
372 | 386 | void slimproto_stop(void); |
373 | 387 | void wake_controller(void); |
374 | 388 | |
434 | 448 | void decode_init(log_level level, const char *opt); |
435 | 449 | void decode_close(void); |
436 | 450 | void decode_flush(void); |
437 | unsigned decode_newstream(unsigned sample_rate, unsigned max_sample_rate); | |
451 | unsigned decode_newstream(unsigned sample_rate, unsigned supported_rates[]); | |
438 | 452 | void codec_open(u8_t format, u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness); |
439 | 453 | |
440 | 454 | #if PROCESS |
442 | 456 | void process_samples(void); |
443 | 457 | void process_drain(void); |
444 | 458 | void process_flush(void); |
445 | unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned max_sample_rate); | |
459 | unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned supported_rates[]); | |
446 | 460 | void process_init(char *opt); |
447 | 461 | #endif |
448 | 462 | |
450 | 464 | // resample.c |
451 | 465 | void resample_samples(struct processstate *process); |
452 | 466 | bool resample_drain(struct processstate *process); |
453 | bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned max_sample_rate); | |
467 | bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned supported_rates[]); | |
454 | 468 | void resample_flush(void); |
455 | 469 | bool resample_init(char *opt); |
456 | 470 | #endif |
457 | 471 | |
458 | // output.c | |
472 | // output.c output_alsa.c output_pa.c output_pack.c | |
459 | 473 | typedef enum { OUTPUT_OFF = -1, OUTPUT_STOPPED = 0, OUTPUT_BUFFER, OUTPUT_RUNNING, |
460 | 474 | OUTPUT_PAUSE_FRAMES, OUTPUT_SKIP_FRAMES, OUTPUT_START_AT } output_state; |
475 | ||
476 | typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE } output_format; | |
461 | 477 | |
462 | 478 | typedef enum { FADE_INACTIVE = 0, FADE_DUE, FADE_ACTIVE } fade_state; |
463 | 479 | typedef enum { FADE_UP = 1, FADE_DOWN, FADE_CROSS } fade_dir; |
464 | 480 | typedef enum { FADE_NONE = 0, FADE_CROSSFADE, FADE_IN, FADE_OUT, FADE_INOUT } fade_mode; |
465 | 481 | |
482 | #define MAX_SUPPORTED_SAMPLERATES 16 | |
483 | #define TEST_RATES = { 384000, 352800, 192000, 176400, 96000, 88200, 48000, 44100, 32000, 24000, 22500, 16000, 12000, 11025, 8000, 0 } | |
484 | ||
466 | 485 | struct outputstate { |
467 | 486 | output_state state; |
487 | output_format format; | |
468 | 488 | const char *device; |
469 | 489 | #if ALSA |
470 | 490 | unsigned buffer; |
476 | 496 | unsigned latency; |
477 | 497 | int osx_playnice; |
478 | 498 | #endif |
499 | int (* write_cb)(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr); | |
500 | unsigned start_frames; | |
479 | 501 | unsigned frames_played; |
480 | 502 | unsigned current_sample_rate; |
481 | unsigned max_sample_rate; | |
503 | unsigned supported_rates[MAX_SUPPORTED_SAMPLERATES]; // ordered largest first so [0] is max_rate | |
504 | unsigned default_sample_rate; | |
505 | bool error_opening; | |
482 | 506 | unsigned device_frames; |
483 | 507 | u32_t updated; |
484 | 508 | u32_t current_replay_gain; |
499 | 523 | fade_dir fade_dir; |
500 | 524 | fade_mode fade_mode; // set by slimproto |
501 | 525 | unsigned fade_secs; // set by slimproto |
502 | }; | |
503 | ||
526 | #if DSD | |
527 | bool next_dop; // set in decode thread | |
528 | bool dop; | |
529 | bool has_dop; // set in dop_init - output device supports dop | |
530 | #endif | |
531 | }; | |
532 | ||
533 | void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[]); | |
534 | void output_close_common(void); | |
535 | void output_flush(void); | |
536 | // _* called with mutex locked | |
537 | frames_t _output_frames(frames_t avail); | |
538 | void _checkfade(bool); | |
539 | ||
540 | // output_alsa.c | |
541 | #if ALSA | |
504 | 542 | void list_devices(void); |
505 | #if ALSA | |
506 | void output_init(log_level level, const char *device, unsigned output_buf_size, unsigned alsa_buffer, unsigned alsa_period, const char *alsa_sample_fmt, bool mmap, unsigned max_rate, unsigned rt_priority); | |
507 | #endif | |
543 | bool test_open(const char *device, unsigned rates[]); | |
544 | void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rt_priority); | |
545 | void output_close_alsa(void); | |
546 | #endif | |
547 | ||
548 | // output_pa.c | |
508 | 549 | #if PORTAUDIO |
509 | void output_init(log_level level, const char *device, unsigned output_buf_size, unsigned latency, int osx_playnice, unsigned max_rate); | |
510 | #endif | |
550 | void list_devices(void); | |
551 | bool test_open(const char *device, unsigned rates[]); | |
552 | void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[]); | |
553 | void output_close_pa(void); | |
554 | void _pa_open(void); | |
555 | #endif | |
556 | ||
557 | // output_stdout.c | |
558 | void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[]); | |
559 | void output_close_stdout(void); | |
560 | ||
561 | // output_pack.c | |
562 | void _scale_and_pack_frames(void *outputptr, s32_t *inputptr, frames_t cnt, s32_t gainL, s32_t gainR, output_format format); | |
563 | void _apply_cross(struct buffer *outputbuf, frames_t out_frames, s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr); | |
564 | void _apply_gain(struct buffer *outputbuf, frames_t count, s32_t gainL, s32_t gainR); | |
565 | s32_t gain(s32_t gain, s32_t sample); | |
566 | s32_t to_gain(float f); | |
567 | ||
568 | // output_vis.c | |
511 | 569 | #if VISEXPORT |
512 | void output_vis_init(u8_t *mac); | |
513 | #endif | |
514 | void output_flush(void); | |
515 | void output_close(void); | |
516 | // _* called with mutex locked | |
517 | void _checkfade(bool); | |
518 | void _pa_open(void); | |
570 | void _vis_export(struct buffer *outputbuf, struct outputstate *output, frames_t out_frames, bool silence); | |
571 | void output_vis_init(log_level level, u8_t *mac); | |
572 | void vis_stop(void); | |
573 | #else | |
574 | #define _vis_export(...) | |
575 | #define vis_stop() | |
576 | #endif | |
577 | ||
578 | // dop.c | |
579 | #if DSD | |
580 | bool is_flac_dop(u32_t *lptr, u32_t *rptr, frames_t frames); | |
581 | void update_dop_marker(u32_t *ptr, frames_t frames); | |
582 | void dop_silence_frames(u32_t *ptr, frames_t frames); | |
583 | void dop_init(bool enable); | |
584 | #endif | |
519 | 585 | |
520 | 586 | // codecs |
521 | #define MAX_CODECS 8 | |
587 | #define MAX_CODECS 9 | |
522 | 588 | |
523 | 589 | struct codec *register_flac(void); |
524 | 590 | struct codec *register_pcm(void); |
526 | 592 | struct codec *register_mpg(void); |
527 | 593 | struct codec *register_vorbis(void); |
528 | 594 | struct codec *register_faad(void); |
595 | struct codec *register_dsd(void); | |
529 | 596 | struct codec *register_ff(const char *codec); |
30 | 30 | |
31 | 31 | struct vorbis { |
32 | 32 | OggVorbis_File *vf; |
33 | bool opened; | |
33 | 34 | #if !LINKALL |
34 | 35 | // vorbis symbols to be dynamically loaded - from either vorbisfile or vorbisidec (tremor) version of library |
35 | 36 | vorbis_info *(* ov_info)(OggVorbis_File *vf, int link); |
141 | 142 | UNLOCK_S; |
142 | 143 | return DECODE_COMPLETE; |
143 | 144 | } |
145 | v->opened = true; | |
144 | 146 | |
145 | 147 | info = OV(v, info, v->vf, -1); |
146 | 148 | |
147 | 149 | LOG_INFO("setting track_start"); |
148 | 150 | LOCK_O_not_direct; |
149 | output.next_sample_rate = decode_newstream(info->rate, output.max_sample_rate); | |
151 | output.next_sample_rate = decode_newstream(info->rate, output.supported_rates); | |
152 | IF_DSD( output.next_dop = false; ) | |
150 | 153 | output.track_start = outputbuf->writep; |
151 | 154 | if (output.fade_mode) _checkfade(true); |
152 | 155 | decode.new_stream = false; |
243 | 246 | static void vorbis_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { |
244 | 247 | if (!v->vf) { |
245 | 248 | v->vf = malloc(sizeof(OggVorbis_File) + 128); // add some padding as struct size may be larger |
249 | memset(v->vf, 0, sizeof(OggVorbis_File) + 128); | |
246 | 250 | } else { |
251 | if (v->opened) { | |
252 | OV(v, clear, v->vf); | |
253 | v->opened = false; | |
254 | } | |
255 | } | |
256 | } | |
257 | ||
258 | static void vorbis_close(void) { | |
259 | if (v->opened) { | |
247 | 260 | OV(v, clear, v->vf); |
248 | } | |
249 | } | |
250 | ||
251 | static void vorbis_close(void) { | |
252 | OV(v, clear, v->vf); | |
261 | v->opened = false; | |
262 | } | |
253 | 263 | free(v->vf); |
254 | 264 | v->vf = NULL; |
255 | 265 | } |
304 | 314 | } |
305 | 315 | |
306 | 316 | v->vf = NULL; |
317 | v->opened = false; | |
307 | 318 | |
308 | 319 | if (!load_vorbis()) { |
309 | 320 | return NULL; |