Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 625 lines (569 sloc) 22.851 kb
e9c07dd @hzeller o add documentation
authored
1 // Copyright (C) 2012 Henner Zeller <h.zeller@acm.org>
2 //
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation; either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
15
a9d3e53 @hzeller o Found a project name: "Folve". Some renamings because of that.
authored
16 #include "folve-filesystem.h"
17
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
18 #include <FLAC/metadata.h>
e9c07dd @hzeller o add documentation
authored
19 #include <errno.h>
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
20 #include <fcntl.h>
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
21 #include <sndfile.h>
5830c5a @hzeller o Switch logging to syslog()
authored
22 #include <stdarg.h>
e9c07dd @hzeller o add documentation
authored
23 #include <stdio.h>
24 #include <string.h>
25 #include <strings.h>
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
26 #include <sys/stat.h>
27 #include <sys/types.h>
5830c5a @hzeller o Switch logging to syslog()
authored
28 #include <syslog.h>
e9c07dd @hzeller o add documentation
authored
29 #include <unistd.h>
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
30
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
31 #include <map>
a9d3e53 @hzeller o Found a project name: "Folve". Some renamings because of that.
authored
32 #include <string>
6c6dedb @hzeller o Some readme tweaks and readability improvements.
authored
33 #include <zita-convolver.h>
e9c07dd @hzeller o add documentation
authored
34
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
35 #include "conversion-buffer.h"
a9d3e53 @hzeller o Found a project name: "Folve". Some renamings because of that.
authored
36 #include "file-handler-cache.h"
37 #include "file-handler.h"
8268c37 @hzeller o Bubble up error messages in status server.
authored
38 #include "util.h"
6c6dedb @hzeller o Some readme tweaks and readability improvements.
authored
39
fd65cee @hzeller o add basic zita filter.
authored
40 #include "zita-config.h"
41
8268c37 @hzeller o Bubble up error messages in status server.
authored
42 using folve::Appendf;
3e883b7 @hzeller o Match filters in sequence
authored
43 using folve::StringPrintf;
0aee3bb @hzeller o Show base dir that is mounted.
authored
44
8268c37 @hzeller o Bubble up error messages in status server.
authored
45 static bool global_debug = false;
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
46
5830c5a @hzeller o Switch logging to syslog()
authored
47 static void DebugLogf(const char *format, ...) {
48 if (!global_debug) return;
49 va_list ap;
50 va_start(ap, format);
51 vsyslog(LOG_DEBUG, format, ap);
52 va_end(ap);
53 }
b5c1e96 @hzeller o more useful output on status server.
authored
54
e9c07dd @hzeller o add documentation
authored
55 namespace {
56
5476cc2 @hzeller o provide absolute path.
authored
57 // Very simple filter that just passes the original file through. Used for
58 // everything that is not a sound-file.
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
59 class PassThroughFilter : public FileHandler {
e9c07dd @hzeller o add documentation
authored
60 public:
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
61 PassThroughFilter(int filedes, int filter_id,
62 const HandlerStats &known_stats)
63 : FileHandler(filter_id), filedes_(filedes), info_stats_(known_stats) {
8268c37 @hzeller o Bubble up error messages in status server.
authored
64 info_stats_.message.append("; pass through.");
5830c5a @hzeller o Switch logging to syslog()
authored
65 DebugLogf("Creating PassThrough filter for '%s'",
66 known_stats.filename.c_str());
e9c07dd @hzeller o add documentation
authored
67 }
680f590 @hzeller o report opening error.
authored
68 ~PassThroughFilter() { close(filedes_); }
69
e9c07dd @hzeller o add documentation
authored
70 virtual int Read(char *buf, size_t size, off_t offset) {
71 const int result = pread(filedes_, buf, size, offset);
72 return result == -1 ? -errno : result;
73 }
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
74 virtual int Stat(struct stat *st) {
75 return fstat(filedes_, st);
76 }
223f392 @hzeller o Have a separate HandlerStats object that contains relevant
authored
77 virtual void GetHandlerStatus(struct HandlerStats *stats) {
8268c37 @hzeller o Bubble up error messages in status server.
authored
78 *stats = info_stats_;
223f392 @hzeller o Have a separate HandlerStats object that contains relevant
authored
79 }
e9c07dd @hzeller o add documentation
authored
80
81 private:
82 const int filedes_;
8268c37 @hzeller o Bubble up error messages in status server.
authored
83 HandlerStats info_stats_;
e9c07dd @hzeller o add documentation
authored
84 };
85
3e883b7 @hzeller o Match filters in sequence
authored
86 static bool FindFirstAccessiblePath(const std::vector<std::string> &path,
87 std::string *match) {
88 for (size_t i = 0; i < path.size(); ++i) {
89 if (access(path[i].c_str(), R_OK) == 0) {
90 *match = path[i];
91 return true;
92 }
93 }
94 return false;
95 }
96
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
97 class SndFileHandler :
98 public FileHandler,
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
99 public ConversionBuffer::SoundSource {
e9c07dd @hzeller o add documentation
authored
100 public:
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
101 // Attempt to create a SndFileHandler from the given file descriptor. This
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
102 // returns NULL if this is not a sound-file or if there is no available
103 // convolution filter configuration available.
8268c37 @hzeller o Bubble up error messages in status server.
authored
104 // "partial_file_info" will be set to information known so far, including
105 // error message.
223f392 @hzeller o Have a separate HandlerStats object that contains relevant
authored
106 static FileHandler *Create(int filedes, const char *fs_path,
8268c37 @hzeller o Bubble up error messages in status server.
authored
107 const char *underlying_file,
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
108 int filter_id,
8268c37 @hzeller o Bubble up error messages in status server.
authored
109 const std::string &zita_config_dir,
110 HandlerStats *partial_file_info) {
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
111 SF_INFO in_info;
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
112 memset(&in_info, 0, sizeof(in_info));
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
113 SNDFILE *snd = sf_open_fd(filedes, SFM_READ, &in_info, 0);
114 if (snd == NULL) {
5830c5a @hzeller o Switch logging to syslog()
authored
115 syslog(LOG_ERR, "File %s: %s", underlying_file, sf_strerror(NULL));
8268c37 @hzeller o Bubble up error messages in status server.
authored
116 partial_file_info->message = sf_strerror(NULL);
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
117 return NULL;
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
118 }
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
119
7e756eb @hzeller o Fix problem with re-initializing the convproc. Turns out that the conf...
authored
120 int bits = 16;
31bd9ce @hzeller o README update.
authored
121 if ((in_info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_24) bits = 24;
122 if ((in_info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_32) bits = 32;
e2619a6 @hzeller o skipping debug message.
authored
123
8268c37 @hzeller o Bubble up error messages in status server.
authored
124 // Remember whatever we could got to know in the partial file info.
125 Appendf(&partial_file_info->format, "%.1fkHz, %d Bit",
126 in_info.samplerate / 1000.0, bits);
127 partial_file_info->duration_seconds = in_info.frames / in_info.samplerate;
128
3e883b7 @hzeller o Match filters in sequence
authored
129 std::vector<std::string> path_choices;
130 // From specific to non-specific.
131 path_choices.push_back(StringPrintf("%s/filter-%d-%d-%d.conf",
132 zita_config_dir.c_str(),
133 in_info.samplerate,
134 in_info.channels, bits));
135 path_choices.push_back(StringPrintf("%s/filter-%d-%d.conf",
136 zita_config_dir.c_str(),
137 in_info.samplerate,
138 in_info.channels));
139 path_choices.push_back(StringPrintf("%s/filter-%d.conf",
140 zita_config_dir.c_str(),
141 in_info.samplerate));
142 const int max_choice = path_choices.size() - 1;
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
143 std::string config_path;
3e883b7 @hzeller o Match filters in sequence
authored
144 const bool found_config = FindFirstAccessiblePath(path_choices,
145 &config_path);
a2aa9a4 @hzeller o let error messages make it to the console.
authored
146 if (found_config) {
5830c5a @hzeller o Switch logging to syslog()
authored
147 DebugLogf("File %s: filter config %s", underlying_file,
148 config_path.c_str());
a2aa9a4 @hzeller o let error messages make it to the console.
authored
149 } else {
3e883b7 @hzeller o Match filters in sequence
authored
150 syslog(LOG_ERR, "File %s: couldn't find filter config %s...%s",
151 underlying_file,
152 path_choices[0].c_str(), path_choices[max_choice].c_str());
153 partial_file_info->message = "Missing ( " + path_choices[0]
154 + "<br/> ... " + path_choices[max_choice] + " )";
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
155 sf_close(snd);
156 return NULL;
b5c1e96 @hzeller o more useful output on status server.
authored
157 }
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
158 return new SndFileHandler(fs_path, filter_id,
159 underlying_file, filedes, snd, in_info,
8268c37 @hzeller o Bubble up error messages in status server.
authored
160 *partial_file_info, config_path);
e9c07dd @hzeller o add documentation
authored
161 }
162
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
163 virtual ~SndFileHandler() {
680f590 @hzeller o report opening error.
authored
164 Close();
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
165 if (zita_.convproc) {
d10c1b0 @hzeller o test static requirement.
authored
166 zita_.convproc->stop_process();
167 zita_.convproc->cleanup();
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
168 delete zita_.convproc;
169 }
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
170 delete output_buffer_;
171 delete [] raw_sample_buffer_;
172 }
173
e9c07dd @hzeller o add documentation
authored
174 virtual int Read(char *buf, size_t size, off_t offset) {
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
175 if (error_) return -1;
47c2b03 @hzeller o better skip mode detection. Only if someone _really_ jumps to the
authored
176 // If this is a skip suspiciously at the very end of the file as
177 // reported by stat, we don't do any encoding, just return garbage.
1118ad9 @hzeller o comment update.
authored
178 // (otherwise we'd to convolve up to that point).
179 //
180 // While indexing, media players do this sometimes apparently.
181 // And sometimes not even to the very end but 'almost' at the end.
182 // So add some FudeOverhang
bcb77e7 @hzeller o Ran with Amarok over my files. Looks like it sometimes only 'almost'
authored
183 static const int kFudgeOverhang = 512;
abb0471 @hzeller o remove debug message from last commit.
authored
184 // But of course only if this is really a skip, not a regular approaching
185 // end-of-file.
bcb77e7 @hzeller o Ran with Amarok over my files. Looks like it sometimes only 'almost'
authored
186 if (output_buffer_->FileSize() < offset
187 && (int) (offset + size + kFudgeOverhang) >= file_stat_.st_size) {
188 const int pretended_bytes = std::min((off_t)size,
189 file_stat_.st_size - offset);
190 if (pretended_bytes > 0) {
191 memset(buf, 0x00, pretended_bytes);
192 return pretended_bytes;
680f590 @hzeller o report opening error.
authored
193 } else {
194 return 0;
195 }
47c2b03 @hzeller o better skip mode detection. Only if someone _really_ jumps to the
authored
196 }
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
197 // The following read might block and call WriteToSoundfile() until the
198 // buffer is filled.
199 return output_buffer_->Read(buf, size, offset);
e9c07dd @hzeller o add documentation
authored
200 }
201
223f392 @hzeller o Have a separate HandlerStats object that contains relevant
authored
202 virtual void GetHandlerStatus(struct HandlerStats *stats) {
8268c37 @hzeller o Bubble up error messages in status server.
authored
203 *stats = base_stats_;
b5c1e96 @hzeller o more useful output on status server.
authored
204 const int frames_done = total_frames_ - input_frames_left_;
223f392 @hzeller o Have a separate HandlerStats object that contains relevant
authored
205 if (frames_done == 0 || total_frames_ == 0)
206 stats->progress = 0.0;
207 else
208 stats->progress = 1.0 * frames_done / total_frames_;
b5c1e96 @hzeller o more useful output on status server.
authored
209 }
210
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
211 virtual int Stat(struct stat *st) {
ba7fde8 @hzeller o implement size estimate. If the user stat() or fstat() the file,
authored
212 if (output_buffer_->FileSize() > start_estimating_size_) {
213 const int frames_done = total_frames_ - input_frames_left_;
214 if (frames_done > 0) {
215 const float estimated_end = 1.0 * total_frames_ / frames_done;
216 off_t new_size = estimated_end * output_buffer_->FileSize();
217 // Report a bit bigger size which is less harmful than programs
218 // reading short.
219 new_size += 16384;
453489f @hzeller o smallish whitespace changes.
authored
220 if (new_size > file_stat_.st_size) { // Only go forward in size.
ba7fde8 @hzeller o implement size estimate. If the user stat() or fstat() the file,
authored
221 file_stat_.st_size = new_size;
222 }
223 }
224 }
225 *st = file_stat_;
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
226 return 0;
227 }
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
228
e9c07dd @hzeller o add documentation
authored
229 private:
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
230 // TODO(hzeller): trim parameter list.
231 SndFileHandler(const char *fs_path, int filter_id,
223f392 @hzeller o Have a separate HandlerStats object that contains relevant
authored
232 const char *underlying_file, int filedes, SNDFILE *snd_in,
8268c37 @hzeller o Bubble up error messages in status server.
authored
233 const SF_INFO &in_info, const HandlerStats &file_info,
234 const std::string &config_path)
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
235 : FileHandler(filter_id),
236 filedes_(filedes), snd_in_(snd_in), total_frames_(in_info.frames),
8268c37 @hzeller o Bubble up error messages in status server.
authored
237 channels_(in_info.channels), base_stats_(file_info),
238 config_path_(config_path),
b5c1e96 @hzeller o more useful output on status server.
authored
239 error_(false), output_buffer_(NULL),
ba7fde8 @hzeller o implement size estimate. If the user stat() or fstat() the file,
authored
240 snd_out_(NULL),
241 raw_sample_buffer_(NULL), input_frames_left_(in_info.frames) {
242
243 // Initial stat that we're going to report to clients. We'll adapt
244 // the filesize as we see it grow. Some clients continuously monitor
245 // the size of the file to check when to stop.
246 fstat(filedes_, &file_stat_);
47c2b03 @hzeller o better skip mode detection. Only if someone _really_ jumps to the
authored
247 start_estimating_size_ = 0.4 * file_stat_.st_size;
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
248
8e36eff @hzeller o comment update.
authored
249 // The flac header we get is more rich than what we can create via
250 // sndfile. So if we have one, just copy it.
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
251 copy_flac_header_verbatim_ = LooksLikeInputIsFlac(in_info, filedes);
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
252
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
253 // Initialize zita config, but don't allocate converter quite yet.
254 memset(&zita_, 0, sizeof(zita_));
255 zita_.fsamp = in_info.samplerate;
256 zita_.ninp = in_info.channels;
257 zita_.nout = in_info.channels;
258
259 // Create a conversion buffer that creates a soundfile of a particular
8e36eff @hzeller o comment update.
authored
260 // format that we choose here. Essentially we want to generate mostly what
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
261 // our input is.
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
262 SF_INFO out_info = in_info;
31bd9ce @hzeller o README update.
authored
263 out_info.seekable = 0;
264 if ((in_info.format & SF_FORMAT_TYPEMASK) == SF_FORMAT_OGG) {
8e36eff @hzeller o comment update.
authored
265 // If the input was ogg, we're re-coding this to flac, because it
266 // wouldn't let us stream the output.
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
267 out_info.format = SF_FORMAT_FLAC;
f11d8cd @hzeller o implement initial skip mode detection.
authored
268 out_info.format |= SF_FORMAT_PCM_16;
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
269 }
a85bebb @hzeller o Fix Flac header manually. Luckily the streamheader is a fixed
authored
270 else if ((in_info.format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV) {
271 out_info.format = SF_FORMAT_FLAC; // recode as flac.
272 out_info.format |= SF_FORMAT_PCM_24;
31bd9ce @hzeller o README update.
authored
273 }
274 else { // original format.
275 out_info.format = in_info.format;
276 }
277
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
278 output_buffer_ = new ConversionBuffer(this, out_info);
279 }
280
f8cb9e5 @hzeller o redacting MD5.
authored
281 virtual void SetOutputSoundfile(ConversionBuffer *out_buffer,
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
282 SNDFILE *sndfile) {
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
283 snd_out_ = sndfile;
284 if (snd_out_ == NULL) {
285 error_ = true;
5830c5a @hzeller o Switch logging to syslog()
authored
286 syslog(LOG_ERR, "Opening output: %s", sf_strerror(NULL));
8268c37 @hzeller o Bubble up error messages in status server.
authored
287 base_stats_.message = sf_strerror(NULL);
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
288 return;
289 }
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
290 if (copy_flac_header_verbatim_) {
8e36eff @hzeller o comment update.
authored
291 out_buffer->set_sndfile_writes_enabled(false);
f8cb9e5 @hzeller o redacting MD5.
authored
292 CopyFlacHeader(out_buffer);
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
293 } else {
f8cb9e5 @hzeller o redacting MD5.
authored
294 out_buffer->set_sndfile_writes_enabled(true);
8e36eff @hzeller o comment update.
authored
295 GenerateHeaderFromInputFile(out_buffer);
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
296 }
297 // Now flush the header: that way if someone only reads the metadata, then
298 // our AddMoreSoundData() is never called.
8e36eff @hzeller o comment update.
authored
299 // We need to do this even if we copied our own header: that way we make
300 // sure that the sndfile-header is flushed into the nirwana before we
301 // re-enable sndfile_writes.
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
302 sf_command(snd_out_, SFC_UPDATE_HEADER_NOW, NULL, 0);
a85bebb @hzeller o Fix Flac header manually. Luckily the streamheader is a fixed
authored
303
304 // -- time for some hackery ...
305 // If we have copied the header over from the original, we need to
306 // redact the values for min/max blocksize and min/max framesize with
307 // what SNDFILE is going to use, otherwise programs will trip over this.
308 // http://flac.sourceforge.net/format.html
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
309 if (copy_flac_header_verbatim_) {
a85bebb @hzeller o Fix Flac header manually. Luckily the streamheader is a fixed
authored
310 out_buffer->WriteCharAt((1152 & 0xFF00) >> 8, 8);
311 out_buffer->WriteCharAt((1152 & 0x00FF) , 9);
06a0312 @hzeller o Are we writing bigger blocksizes later while encoding ? To
authored
312 out_buffer->WriteCharAt((32768 & 0xFF00) >> 8, 10);
313 out_buffer->WriteCharAt((32768 & 0x00FF) , 11);
a85bebb @hzeller o Fix Flac header manually. Luckily the streamheader is a fixed
authored
314 for (int i = 12; i < 18; ++i) out_buffer->WriteCharAt(0, i);
315 } else {
316 // .. and if SNDFILE writes the header, it misses out in writing the
317 // number of samples to be expected. So let's fill that in.
318 // The MD5 sum starts at position strlen("fLaC") + 4 + 18 = 26
319 // The 32 bits before that are the samples (and another 4 bit before that,
320 // ignoring that for now).
321 out_buffer->WriteCharAt((total_frames_ & 0xFF000000) >> 24, 22);
322 out_buffer->WriteCharAt((total_frames_ & 0x00FF0000) >> 16, 23);
323 out_buffer->WriteCharAt((total_frames_ & 0x0000FF00) >> 8, 24);
324 out_buffer->WriteCharAt((total_frames_ & 0x000000FF), 25);
325 }
f8cb9e5 @hzeller o redacting MD5.
authored
326
327 out_buffer->set_sndfile_writes_enabled(true); // ready for sound-stream.
5830c5a @hzeller o Switch logging to syslog()
authored
328 DebugLogf("Header init done (%s).", base_stats_.filename.c_str());
550c4e0 @hzeller o allow short read in header area, but not in the stream
authored
329 out_buffer->HeaderFinished();
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
330 }
331
47c2b03 @hzeller o better skip mode detection. Only if someone _really_ jumps to the
authored
332 virtual bool AddMoreSoundData() {
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
333 if (!input_frames_left_)
334 return false;
335 if (!zita_.convproc) {
8e36eff @hzeller o comment update.
authored
336 // First time we're called.
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
337 zita_.convproc = new Convproc();
23b24f0 @hzeller o Write zita config problems to syslog.
authored
338 if ((config(&zita_, config_path_.c_str()) != 0)
339 || zita_.convproc->inpdata(channels_ - 1) == NULL
340 || zita_.convproc->outdata(channels_ - 1) == NULL) {
341 syslog(LOG_ERR, "filter-config %s is broken. Please fix. "
342 "Won't play this stream %s (simulating empty file)",
343 config_path_.c_str(), base_stats_.filename.c_str());
8268c37 @hzeller o Bubble up error messages in status server.
authored
344 base_stats_.message = "Problem parsing " + config_path_;
0c8561b @hzeller o Handle configuration file error gracefully.
authored
345 input_frames_left_ = 0;
a2aa9a4 @hzeller o let error messages make it to the console.
authored
346 Close();
0c8561b @hzeller o Handle configuration file error gracefully.
authored
347 return false;
348 }
f11d8cd @hzeller o implement initial skip mode detection.
authored
349 raw_sample_buffer_ = new float[zita_.fragm * channels_];
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
350 zita_.convproc->start_process(0, 0);
351 }
0706452 @hzeller o Report broken input file in UI.
authored
352 const int r = sf_readf_float(snd_in_, raw_sample_buffer_, zita_.fragm);
9c0ba15 @hzeller o work around broken files - don't endlessloop :)
authored
353 if (r == 0) {
5830c5a @hzeller o Switch logging to syslog()
authored
354 syslog(LOG_ERR, "Expected %d frames left, gave buffer sized %d, "
355 "but got EOF; corrupt file '%s' ?",
356 input_frames_left_, zita_.fragm, base_stats_.filename.c_str());
0706452 @hzeller o Report broken input file in UI.
authored
357 base_stats_.message = "Premature EOF in input file.";
9c0ba15 @hzeller o work around broken files - don't endlessloop :)
authored
358 input_frames_left_ = 0;
359 Close();
360 return false;
361 }
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
362 if (r < (int) zita_.fragm) {
8e36eff @hzeller o comment update.
authored
363 // Zero out the rest of the buffer.
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
364 const int missing = zita_.fragm - r;
365 memset(raw_sample_buffer_ + r * channels_, 0,
366 missing * channels_ * sizeof(float));
fd65cee @hzeller o add basic zita filter.
authored
367 }
368
369 // Separate channels.
370 for (int ch = 0; ch < channels_; ++ch) {
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
371 float *dest = zita_.convproc->inpdata(ch);
fd65cee @hzeller o add basic zita filter.
authored
372 for (int j = 0; j < r; ++j) {
373 dest[j] = raw_sample_buffer_[j * channels_ + ch];
374 }
375 }
376
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
377 zita_.convproc->process();
fd65cee @hzeller o add basic zita filter.
authored
378
379 // Join channels again.
380 for (int ch = 0; ch < channels_; ++ch) {
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
381 float *source = zita_.convproc->outdata(ch);
fd65cee @hzeller o add basic zita filter.
authored
382 for (int j = 0; j < r; ++j) {
383 raw_sample_buffer_[j * channels_ + ch] = source[j];
384 }
385 }
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
386 sf_writef_float(snd_out_, raw_sample_buffer_, r);
387 input_frames_left_ -= r;
31bd9ce @hzeller o README update.
authored
388 if (input_frames_left_ == 0) {
680f590 @hzeller o report opening error.
authored
389 Close();
31bd9ce @hzeller o README update.
authored
390 }
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
391 return input_frames_left_;
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
392 }
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
393
8e36eff @hzeller o comment update.
authored
394 void CopyBytes(int fd, off_t pos, ConversionBuffer *out, size_t len) {
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
395 char buf[256];
396 while (len > 0) {
397 ssize_t r = pread(fd, buf, std::min(sizeof(buf), len), pos);
8e36eff @hzeller o comment update.
authored
398 if (r <= 0) return;
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
399 out->Append(buf, r);
400 len -= r;
401 pos += r;
402 }
403 }
404
f8cb9e5 @hzeller o redacting MD5.
authored
405 void CopyFlacHeader(ConversionBuffer *out_buffer) {
5830c5a @hzeller o Switch logging to syslog()
authored
406 DebugLogf("Provide FLAC header from original file %s",
407 base_stats_.filename.c_str());
f8cb9e5 @hzeller o redacting MD5.
authored
408 out_buffer->Append("fLaC", 4);
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
409 off_t pos = 4;
410 unsigned char header[4];
f8cb9e5 @hzeller o redacting MD5.
authored
411 bool need_finish_padding = false;
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
412 while (pread(filedes_, header, sizeof(header), pos) == sizeof(header)) {
413 pos += sizeof(header);
414 bool is_last = header[0] & 0x80;
415 unsigned int type = header[0] & 0x7F;
416 unsigned int byte_len = (header[1] << 16) + (header[2] << 8) + header[3];
5830c5a @hzeller o Switch logging to syslog()
authored
417 DebugLogf(" %02x %02x %02x %02x type: %d, len: %6u %s ",
418 header[0], header[1], header[2], header[3],
419 type, byte_len, is_last ? "(last)" : "(cont)");
f8cb9e5 @hzeller o redacting MD5.
authored
420 need_finish_padding = false;
421 if (type == FLAC__METADATA_TYPE_STREAMINFO && byte_len == 34) {
422 out_buffer->Append(&header, sizeof(header));
8e36eff @hzeller o comment update.
authored
423 // Copy everything but the MD5 at the end - which we set to empty.
f8cb9e5 @hzeller o redacting MD5.
authored
424 CopyBytes(filedes_, pos, out_buffer, byte_len - 16);
425 for (int i = 0; i < 16; ++i) out_buffer->Append("\0", 1);
5830c5a @hzeller o Switch logging to syslog()
authored
426 // TODO append log (copy streaminfo, but redacted MD5)
f8cb9e5 @hzeller o redacting MD5.
authored
427 }
428 else if (type == FLAC__METADATA_TYPE_SEEKTABLE) {
429 // The SEEKTABLE header we skip, because it is bogus after encoding.
5830c5a @hzeller o Switch logging to syslog()
authored
430 // TODO append log (skip the seektable)
f8cb9e5 @hzeller o redacting MD5.
authored
431 need_finish_padding = is_last; // if we were last, force finish block.
432 }
433 else {
434 out_buffer->Append(&header, sizeof(header));
435 CopyBytes(filedes_, pos, out_buffer, byte_len);
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
436 }
f8cb9e5 @hzeller o redacting MD5.
authored
437 pos += byte_len;
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
438 if (is_last)
439 break;
440 }
f8cb9e5 @hzeller o redacting MD5.
authored
441 if (need_finish_padding) { // if the last block was not is_last: pad.
5830c5a @hzeller o Switch logging to syslog()
authored
442 DebugLogf("write padding");
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
443 memset(&header, 0, sizeof(header));
444 header[0] = 0x80 /* is last */ | FLAC__METADATA_TYPE_PADDING;
f8cb9e5 @hzeller o redacting MD5.
authored
445 out_buffer->Append(&header, sizeof(header));
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
446 }
447 }
448
8e36eff @hzeller o comment update.
authored
449 void GenerateHeaderFromInputFile(ConversionBuffer *out_buffer) {
5830c5a @hzeller o Switch logging to syslog()
authored
450 DebugLogf("Generate header from original ID3-tags.");
8e36eff @hzeller o comment update.
authored
451 out_buffer->set_sndfile_writes_enabled(true);
452 // Copy ID tags that are supported by sndfile.
453 for (int i = SF_STR_FIRST; i <= SF_STR_LAST; ++i) {
454 const char *s = sf_get_string(snd_in_, i);
455 if (s != NULL) {
456 sf_set_string(snd_out_, i, s);
457 }
458 }
459 }
460
680f590 @hzeller o report opening error.
authored
461 void Close() {
462 if (snd_out_ == NULL) return; // done.
463 output_buffer_->set_sndfile_writes_enabled(false);
464 if (snd_in_) sf_close(snd_in_);
465 if (snd_out_) sf_close(snd_out_);
466 snd_out_ = NULL;
467 close(filedes_);
468 }
469
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
470 bool LooksLikeInputIsFlac(const SF_INFO &sndinfo, int filedes) {
471 if ((sndinfo.format & SF_FORMAT_TYPEMASK) != SF_FORMAT_FLAC)
472 return false;
473 // However some files contain flac encoded stuff, but are not flac files
474 // by themselve. So we can't copy headers verbatim. Sanity check header.
475 char flac_magic[4];
476 if (pread(filedes, flac_magic, sizeof(flac_magic), 0) != sizeof(flac_magic))
477 return false;
478 return memcmp(flac_magic, "fLaC", sizeof(flac_magic)) == 0;
479 }
480
e9c07dd @hzeller o add documentation
authored
481 const int filedes_;
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
482 SNDFILE *const snd_in_;
a85bebb @hzeller o Fix Flac header manually. Luckily the streamheader is a fixed
authored
483 const unsigned int total_frames_;
b5c1e96 @hzeller o more useful output on status server.
authored
484 const int channels_;
8268c37 @hzeller o Bubble up error messages in status server.
authored
485 HandlerStats base_stats_;
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
486 const std::string config_path_;
487
ba7fde8 @hzeller o implement size estimate. If the user stat() or fstat() the file,
authored
488 struct stat file_stat_; // we dynamically report a changing size.
489 off_t start_estimating_size_; // essentially const.
490
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
491 bool error_;
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
492 bool copy_flac_header_verbatim_;
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
493 ConversionBuffer *output_buffer_;
494 SNDFILE *snd_out_;
495
496 // Used in conversion.
497 float *raw_sample_buffer_;
498 int input_frames_left_;
d10c1b0 @hzeller o test static requirement.
authored
499 ZitaConfig zita_;
e9c07dd @hzeller o add documentation
authored
500 };
501 } // namespace
502
8c7e5c1 @hzeller o Only if -D is given, the debug toggle is active in the UI.
authored
503 FolveFilesystem::FolveFilesystem()
504 : current_cfg_index_(0), debug_ui_enabled_(false),
505 open_file_cache_(3), total_file_openings_(0), total_file_reopen_(0) {
506 config_dirs_.push_back(""); // The first config is special: empty.
507 }
ba7fde8 @hzeller o implement size estimate. If the user stat() or fstat() the file,
authored
508
8268c37 @hzeller o Bubble up error messages in status server.
authored
509 FileHandler *FolveFilesystem::CreateFromDescriptor(int filedes,
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
510 int cfg_idx,
8268c37 @hzeller o Bubble up error messages in status server.
authored
511 const char *fs_path,
512 const char *underlying_file) {
513 HandlerStats file_info;
514 file_info.filename = fs_path;
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
515 if (cfg_idx != 0) {
516 FileHandler *filter = SndFileHandler::Create(filedes, fs_path,
517 underlying_file,
518 cfg_idx,
519 config_dirs()[cfg_idx],
520 &file_info);
521 if (filter != NULL) return filter;
522 } else {
523 file_info.message = "No filter config selected.";
524 }
e9c07dd @hzeller o add documentation
authored
525
8b18938 @hzeller o some comment changes. Ready to do some flac handling.
authored
526 // Every other file-type is just passed through as is.
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
527 return new PassThroughFilter(filedes, cfg_idx, file_info);
528 }
529
530 std::string FolveFilesystem::CacheKey(int config_idx, const char *fs_path) {
531 std::string result;
532 Appendf(&result, "%d/%s", config_idx, fs_path);
533 return result;
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
534 }
535
536 // Implementation of the C functions in filter-interface.h
a9d3e53 @hzeller o Found a project name: "Folve". Some renamings because of that.
authored
537 FileHandler *FolveFilesystem::CreateHandler(const char *fs_path,
538 const char *underlying_path) {
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
539 const int config_idx = current_cfg_index_;
540 const std::string cache_key = CacheKey(config_idx, fs_path);
541 FileHandler *handler = open_file_cache_.FindAndPin(cache_key);
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
542 if (handler == NULL) {
543 int filedes = open(underlying_path, O_RDONLY);
680f590 @hzeller o report opening error.
authored
544 if (filedes < 0)
545 return NULL;
b5c1e96 @hzeller o more useful output on status server.
authored
546 ++total_file_openings_;
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
547 handler = CreateFromDescriptor(filedes, config_idx,
548 fs_path, underlying_path);
549 handler = open_file_cache_.InsertPinned(cache_key, handler);
b5c1e96 @hzeller o more useful output on status server.
authored
550 } else {
551 ++total_file_reopen_;
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
552 }
553 return handler;
e9c07dd @hzeller o add documentation
authored
554 }
555
a9d3e53 @hzeller o Found a project name: "Folve". Some renamings because of that.
authored
556 int FolveFilesystem::StatByFilename(const char *fs_path, struct stat *st) {
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
557 const std::string cache_key = CacheKey(current_cfg_index_, fs_path);
558 FileHandler *handler = open_file_cache_.FindAndPin(cache_key);
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
559 if (handler == 0)
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
560 return -1;
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
561 ssize_t result = handler->Stat(st);
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
562 open_file_cache_.Unpin(cache_key);
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
563 return result;
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
564 }
565
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
566 void FolveFilesystem::Close(const char *fs_path, const FileHandler *handler) {
567 const std::string cache_key = CacheKey(handler->filter_id(), fs_path);
568 open_file_cache_.Unpin(cache_key);
569 }
570
571 void FolveFilesystem::SwitchCurrentConfigIndex(int i) {
572 if (i < 0 || i >= (int) config_dirs_.size())
573 return;
5830c5a @hzeller o Switch logging to syslog()
authored
574 if (i != current_cfg_index_) {
575 if (i == 0) {
576 syslog(LOG_INFO, "Switching to pass-through mode.");
577 } else {
578 syslog(LOG_INFO, "Switching config directory to '%s'",
579 config_dirs()[i].c_str());
580 }
581 current_cfg_index_ = i;
582 }
e9c07dd @hzeller o add documentation
authored
583 }
fd65cee @hzeller o add basic zita filter.
authored
584
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
585 static bool IsDirectory(const std::string &path) {
586 if (path.empty()) return false;
587 struct stat st;
588 if (stat(path.c_str(), &st) != 0)
589 return false;
590 return (st.st_mode & S_IFMT) == S_IFDIR;
591 }
592
5830c5a @hzeller o Switch logging to syslog()
authored
593 void FolveFilesystem::SetDebugMode(bool b) {
249865a @hzeller o Make debug mode switchable in UI.
authored
594 if (b != global_debug) {
595 syslog(LOG_INFO, "Switch debug mode %s.", b ? "on" : "off");
596 global_debug = b;
597 }
5830c5a @hzeller o Switch logging to syslog()
authored
598 }
599 bool FolveFilesystem::IsDebugMode() const { return global_debug; }
600
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
601 bool FolveFilesystem::CheckInitialized() {
602 if (underlying_dir().empty()) {
603 fprintf(stderr, "Don't know the underlying directory to read from.\n");
604 return false;
605 }
606 if (!IsDirectory(underlying_dir())) {
607 fprintf(stderr, "<underlying-dir>: '%s' not a directory.\n",
608 underlying_dir().c_str());
609 return false;
610 }
611
612 for (size_t i = 1; i < config_dirs_.size(); ++i) {
613 if (!IsDirectory(config_dirs_[i])) {
614 fprintf(stderr, "<config-dir>: '%s' not a directory.\n",
615 config_dirs_[i].c_str());
616 return false;
617 }
618 }
619 if (config_dirs_.size() > 1) {
620 // By default, lets set the index to the first filter the user provided.
621 SwitchCurrentConfigIndex(1);
622 }
623 return true;
fd65cee @hzeller o add basic zita filter.
authored
624 }
Something went wrong with that request. Please try again.