|
1
|
1 #include "StdAfx.h"
|
|
|
2
|
|
|
3 #include "input_helpers.h"
|
|
|
4 #include "fullFileBuffer.h"
|
|
|
5 #include "file_list_helper.h"
|
|
|
6 #include "fileReadAhead.h"
|
|
|
7 #include <SDK/file_info_impl.h>
|
|
|
8 #include "readers_lite.h"
|
|
|
9
|
|
|
10 #define LOCAL_DEBUG_PRINT(...) // PFC_DEBUG_PRINT(__VA_ARGS__)
|
|
|
11
|
|
|
12 #define FILE_DECODEDAUDIO_PRINT(...) LOCAL_DEBUG_PRINT("file_decodeaudio(", pfc::format_ptr(this), "): ", __VA_ARGS__)
|
|
|
13
|
|
|
14 input_helper::ioFilter_t input_helper::ioFilter_full_buffer(t_filesize val) {
|
|
|
15 if (val == 0) return nullptr;
|
|
|
16 return [val] ( file_ptr & f, const char * path, abort_callback & aborter) {
|
|
|
17 if (!filesystem::g_is_remote_or_unrecognized(path)) {
|
|
|
18 if (f.is_empty()) filesystem::g_open_read(f, path, aborter);
|
|
|
19 if (!f->is_in_memory() && !f->is_remote() && f->can_seek() && f->get_size(aborter) <= val) {
|
|
|
20 try {
|
|
|
21 f = createFileMemMirrorAsync(f, nullptr, aborter);
|
|
|
22 return true;
|
|
|
23 } catch (std::bad_alloc const &) {} // keep orig file object
|
|
|
24 }
|
|
|
25 }
|
|
|
26 return false;
|
|
|
27 };
|
|
|
28 }
|
|
|
29
|
|
|
30 input_helper::ioFilter_t input_helper::ioFilter_block_buffer(size_t arg) {
|
|
|
31 if (arg == 0) return nullptr;
|
|
|
32
|
|
|
33 return [arg](file_ptr & p_file, const char * p_path, abort_callback & p_abort) {
|
|
|
34 if (!filesystem::g_is_remote_or_unrecognized(p_path)) {
|
|
|
35 if (p_file.is_empty()) filesystem::g_open_read(p_file, p_path, p_abort);
|
|
|
36 if (!p_file->is_in_memory() && !p_file->is_remote() && p_file->can_seek()) {
|
|
|
37 file_cached::g_create(p_file, p_file, p_abort, (size_t)arg);
|
|
|
38 return true;
|
|
|
39 }
|
|
|
40 }
|
|
|
41 return false;
|
|
|
42 };
|
|
|
43 }
|
|
|
44
|
|
|
45 static bool looksLikePlaylist(filesystem::ptr const& fs, const char* path) {
|
|
|
46 auto ext = fs->get_extension(path);
|
|
|
47 // match against common internet playlist extensions
|
|
|
48 // yes, this could query fb2k services instead, but uses a fixed list by design
|
|
|
49 for (auto walk : { "m3u", "pls", "m3u8", "asx" }) {
|
|
|
50 if (pfc::stringEqualsI_ascii(walk, ext)) return true;
|
|
|
51 }
|
|
|
52 return false;
|
|
|
53 }
|
|
|
54 static input_helper::ioFilter_t makeReadAhead(size_t arg, bool bRemote) {
|
|
|
55 if (arg == 0) return nullptr;
|
|
|
56
|
|
|
57 return [arg, bRemote](file_ptr & p_file, const char * p_path, abort_callback & p_abort) {
|
|
|
58 if (p_file.is_empty()) {
|
|
|
59 filesystem::ptr fs;
|
|
|
60 if (!filesystem::g_get_interface(fs, p_path)) return false;
|
|
|
61 if (bRemote != fs->is_remote(p_path)) return false;
|
|
|
62 if (looksLikePlaylist(fs, p_path)) return false;
|
|
|
63 fs->open(p_file, p_path, filesystem::open_mode_read, p_abort);
|
|
|
64 } else if (bRemote != p_file->is_remote()) return false;
|
|
|
65 if (p_file->is_in_memory()) return false;
|
|
|
66 p_file = fileCreateReadAhead(p_file, (size_t)arg, p_abort);
|
|
|
67 return true;
|
|
|
68 };
|
|
|
69 }
|
|
|
70
|
|
|
71 input_helper::ioFilter_t input_helper::ioFilter_remote_read_ahead(size_t arg) {
|
|
|
72 return makeReadAhead(arg, true);
|
|
|
73 }
|
|
|
74
|
|
|
75 input_helper::ioFilter_t input_helper::ioFilter_local_read_ahead(size_t arg) {
|
|
|
76 return makeReadAhead(arg, false);
|
|
|
77 }
|
|
|
78
|
|
|
79 void input_helper::open(trackRef location, abort_callback & abort, decodeOpen_t const & other) {
|
|
|
80 this->open( trackGetLocation(location), abort, other);
|
|
|
81 }
|
|
|
82
|
|
|
83 void input_helper::open(service_ptr_t<file> p_filehint,trackRef p_location,unsigned p_flags,abort_callback & p_abort,bool p_from_redirect,bool p_skip_hints)
|
|
|
84 {
|
|
|
85 open(p_filehint,trackGetLocation( p_location ),p_flags,p_abort,p_from_redirect,p_skip_hints);
|
|
|
86 }
|
|
|
87
|
|
|
88 bool input_helper::test_if_lockless(abort_callback& a) {
|
|
|
89 if (m_file_in_memory || extended_param(input_params::is_tag_write_safe) != 0) return true;
|
|
|
90
|
|
|
91 #if 0
|
|
|
92 // doesn't work with current components because they don't support seeking_expensive either
|
|
|
93 if (extended_param(input_params::seeking_expensive)) {
|
|
|
94 // could be that the decoder doesn't understand is_tag_write_safe
|
|
|
95 if (matchProtocol("file", m_path)) {
|
|
|
96 try {
|
|
|
97 fileOpenWriteExisting(m_path, a);
|
|
|
98 return true;
|
|
|
99 } catch (exception_io const &) {}
|
|
|
100 }
|
|
|
101 }
|
|
|
102 #endif
|
|
|
103 return false;
|
|
|
104 }
|
|
|
105 void input_helper::fileOpenTools(service_ptr_t<file> & p_file,const char * p_path,input_helper::ioFilters_t const & filters, abort_callback & p_abort) {
|
|
|
106 for( auto & f : filters ) {
|
|
|
107 if (f) {
|
|
|
108 if (f(p_file, p_path, p_abort)) break;
|
|
|
109 }
|
|
|
110 }
|
|
|
111 }
|
|
|
112
|
|
|
113 bool input_helper::need_file_reopen(const char * newPath) const {
|
|
|
114 return m_input.is_empty() || playable_location::path_compare(m_path, newPath) != 0;
|
|
|
115 }
|
|
|
116
|
|
|
117 bool input_helper::open_path(const char * path, abort_callback & abort, decodeOpen_t const & other) {
|
|
|
118 abort.check();
|
|
|
119
|
|
|
120 m_logger = other.m_logger;
|
|
|
121
|
|
|
122 if (!need_file_reopen(path)) {
|
|
|
123 if ( other.m_logger.is_valid() ) {
|
|
|
124 input_decoder_v2::ptr v2;
|
|
|
125 if (m_input->service_query_t(v2)) v2->set_logger(other.m_logger);
|
|
|
126 }
|
|
|
127 return false;
|
|
|
128 }
|
|
|
129 m_input.release();
|
|
|
130
|
|
|
131 service_ptr_t<file> l_file = other.m_hint;
|
|
|
132 fileOpenTools(l_file, path, other.m_ioFilters, abort);
|
|
|
133
|
|
|
134 m_file_in_memory = l_file.is_valid() && l_file->is_in_memory();
|
|
|
135
|
|
|
136 TRACK_CODE("input_entry::g_open_for_decoding",
|
|
|
137 m_input ^= input_entry::g_open(input_decoder::class_guid, l_file, path, m_logger, abort, other.m_from_redirect );
|
|
|
138 );
|
|
|
139
|
|
|
140 if (!other.m_skip_hints) {
|
|
|
141 try {
|
|
|
142 if ( other.m_infoHook ) {
|
|
|
143 other.m_infoHook( m_input, path, abort );
|
|
|
144 }
|
|
|
145 #ifdef FOOBAR2000_HAVE_METADB
|
|
|
146 metadb_io::get()->hint_reader(m_input.get_ptr(), path, abort);
|
|
|
147 #endif
|
|
|
148 }
|
|
|
149 catch (exception_io_data const &) {
|
|
|
150 //Don't fail to decode when this barfs, might be barfing when reading info from another subsong than the one we're trying to decode etc.
|
|
|
151 m_input.release();
|
|
|
152 if (l_file.is_valid()) l_file->reopen(abort);
|
|
|
153 TRACK_CODE("input_entry::g_open_for_decoding",
|
|
|
154 m_input ^= input_entry::g_open(input_decoder::class_guid, l_file, path, m_logger, abort, other.m_from_redirect);
|
|
|
155 );
|
|
|
156 }
|
|
|
157 }
|
|
|
158
|
|
|
159 if (other.m_shim) m_input = other.m_shim(m_input, path, abort);
|
|
|
160
|
|
|
161 m_path = path;
|
|
|
162 return true;
|
|
|
163 }
|
|
|
164
|
|
|
165 void input_helper::open_decoding(t_uint32 subsong, t_uint32 flags, abort_callback & p_abort) {
|
|
|
166 TRACK_CODE("input_decoder::initialize", m_input->initialize(subsong, flags, p_abort));
|
|
|
167 }
|
|
|
168
|
|
|
169 void input_helper::open(const playable_location & location, abort_callback & abort, decodeOpen_t const & other) {
|
|
|
170 open_path(location.get_path(), abort, other);
|
|
|
171
|
|
|
172 if (other.m_setSampleRate != 0) {
|
|
|
173 this->extended_param(input_params::set_preferred_sample_rate, other.m_setSampleRate, nullptr, 0);
|
|
|
174 }
|
|
|
175
|
|
|
176 open_decoding(location.get_subsong(), other.m_flags, abort);
|
|
|
177 }
|
|
|
178
|
|
|
179 void input_helper::attach(input_decoder::ptr dec, const char * path) {
|
|
|
180 m_input = dec;
|
|
|
181 m_path = path;
|
|
|
182 }
|
|
|
183
|
|
|
184 void input_helper::open(service_ptr_t<file> p_filehint, const playable_location & p_location, unsigned p_flags, abort_callback & p_abort, bool p_from_redirect, bool p_skip_hints) {
|
|
|
185 decodeOpen_t o;
|
|
|
186 o.m_hint = p_filehint;
|
|
|
187 o.m_flags = p_flags;
|
|
|
188 o.m_from_redirect = p_from_redirect;
|
|
|
189 o.m_skip_hints = p_skip_hints;
|
|
|
190 this->open(p_location, p_abort, o);
|
|
|
191 }
|
|
|
192
|
|
|
193
|
|
|
194 void input_helper::close() {
|
|
|
195 m_input.release();
|
|
|
196 }
|
|
|
197
|
|
|
198 bool input_helper::is_open() {
|
|
|
199 return m_input.is_valid();
|
|
|
200 }
|
|
|
201
|
|
|
202 void input_helper::set_pause(bool state) {
|
|
|
203 input_decoder_v3::ptr v3;
|
|
|
204 if (m_input->service_query_t(v3)) v3->set_pause(state);
|
|
|
205 }
|
|
|
206 bool input_helper::flush_on_pause() {
|
|
|
207 input_decoder_v3::ptr v3;
|
|
|
208 if (m_input->service_query_t(v3)) return v3->flush_on_pause();
|
|
|
209 else return false;
|
|
|
210 }
|
|
|
211
|
|
|
212
|
|
|
213 void input_helper::set_logger(event_logger::ptr ptr) {
|
|
|
214 m_logger = ptr;
|
|
|
215 input_decoder_v2::ptr v2;
|
|
|
216 if (m_input->service_query_t(v2)) v2->set_logger(ptr);
|
|
|
217 }
|
|
|
218
|
|
|
219 bool input_helper::run_raw_v2(audio_chunk& p_chunk, mem_block_container& p_raw, uint32_t knownBPS,abort_callback& p_abort) {
|
|
|
220 PFC_ASSERT(knownBPS >= 8 && knownBPS <= 32);
|
|
|
221 input_decoder_v2::ptr v2;
|
|
|
222 if (v2 &= m_input) {
|
|
|
223 try {
|
|
|
224 return v2->run_raw(p_chunk, p_raw, p_abort);
|
|
|
225 // UGLY: it may STILL FAIL with exception_not_implemented due to some underlying code not being able to handle the request!!
|
|
|
226 } catch (pfc::exception_not_implemented const &) {}
|
|
|
227 }
|
|
|
228 if (!m_input->run(p_chunk, p_abort)) return false;
|
|
|
229
|
|
|
230 uint32_t pad = knownBPS + 7; pad -= pad % 8;
|
|
|
231 p_chunk.toFixedPoint(p_raw, pad, knownBPS);
|
|
|
232 return true;
|
|
|
233 }
|
|
|
234
|
|
|
235 bool input_helper::run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) {
|
|
|
236 input_decoder_v2::ptr v2;
|
|
|
237 if (!m_input->service_query_t(v2)) throw pfc::exception_not_implemented();
|
|
|
238 return v2->run_raw(p_chunk, p_raw, p_abort);
|
|
|
239 }
|
|
|
240
|
|
|
241 bool input_helper::run(audio_chunk & p_chunk, abort_callback & p_abort) {
|
|
|
242 TRACK_CODE("input_decoder::run", return m_input->run(p_chunk, p_abort));
|
|
|
243 }
|
|
|
244
|
|
|
245 void input_helper::seek(double seconds, abort_callback & p_abort) {
|
|
|
246 TRACK_CODE("input_decoder::seek", m_input->seek(seconds, p_abort));
|
|
|
247 }
|
|
|
248
|
|
|
249 bool input_helper::can_seek() {
|
|
|
250 return m_input->can_seek();
|
|
|
251 }
|
|
|
252
|
|
|
253 bool input_helper::query_position( double & val ) {
|
|
|
254 return extended_param(input_params::query_position, 0, &val, sizeof(val) ) != 0;
|
|
|
255 }
|
|
|
256
|
|
|
257 size_t input_helper::extended_param(const GUID & type, size_t arg1, void * arg2, size_t arg2size) {
|
|
|
258 input_decoder_v4::ptr v4;
|
|
|
259 if (v4 &= m_input) {
|
|
|
260 return v4->extended_param(type, arg1, arg2, arg2size);
|
|
|
261 }
|
|
|
262 return 0;
|
|
|
263 }
|
|
|
264 input_helper::decodeInfo_t input_helper::decode_info() {
|
|
|
265 decodeInfo_t ret = {};
|
|
|
266 if (m_input.is_valid()) {
|
|
|
267 ret.m_can_seek = can_seek();
|
|
|
268 ret.m_flush_on_pause = flush_on_pause();
|
|
|
269 if (ret.m_can_seek) {
|
|
|
270 ret.m_seeking_expensive = extended_param(input_params::seeking_expensive, 0, nullptr, 0) != 0;
|
|
|
271 }
|
|
|
272 }
|
|
|
273 return ret;
|
|
|
274 }
|
|
|
275
|
|
|
276 void input_helper::on_idle(abort_callback & p_abort) {
|
|
|
277 if (m_input.is_valid()) {
|
|
|
278 TRACK_CODE("input_decoder::on_idle",m_input->on_idle(p_abort));
|
|
|
279 }
|
|
|
280 }
|
|
|
281
|
|
|
282 bool input_helper::get_dynamic_info(file_info & p_out,double & p_timestamp_delta) {
|
|
|
283 TRACK_CODE("input_decoder::get_dynamic_info",return m_input->get_dynamic_info(p_out,p_timestamp_delta));
|
|
|
284 }
|
|
|
285
|
|
|
286 bool input_helper::get_dynamic_info_track(file_info & p_out,double & p_timestamp_delta) {
|
|
|
287 TRACK_CODE("input_decoder::get_dynamic_info_track",return m_input->get_dynamic_info_track(p_out,p_timestamp_delta));
|
|
|
288 }
|
|
|
289
|
|
|
290 void input_helper::get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) {
|
|
|
291 TRACK_CODE("input_decoder::get_info",m_input->get_info(p_subsong,p_info,p_abort));
|
|
|
292 }
|
|
|
293
|
|
|
294 const char * input_helper::get_path() const {
|
|
|
295 return m_path;
|
|
|
296 }
|
|
|
297
|
|
|
298
|
|
|
299 input_helper::input_helper()
|
|
|
300 {
|
|
|
301 }
|
|
|
302
|
|
|
303
|
|
|
304 void input_helper::g_get_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect) {
|
|
|
305 service_ptr_t<input_info_reader> instance;
|
|
|
306 input_entry::g_open_for_info_read(instance,0,p_location.get_path(),p_abort,p_from_redirect);
|
|
|
307 instance->get_info(p_location.get_subsong_index(),p_info,p_abort);
|
|
|
308 }
|
|
|
309
|
|
|
310 void input_helper::g_set_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect) {
|
|
|
311 service_ptr_t<input_info_writer> instance;
|
|
|
312 input_entry::g_open_for_info_write(instance,0,p_location.get_path(),p_abort,p_from_redirect);
|
|
|
313 instance->set_info(p_location.get_subsong_index(),p_info,p_abort);
|
|
|
314 instance->commit(p_abort);
|
|
|
315 }
|
|
|
316
|
|
|
317 #ifdef FOOBAR2000_HAVE_METADB
|
|
|
318 bool dead_item_filter::run(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,bit_array_var & p_mask) {
|
|
|
319 file_list_helper::file_list_from_metadb_handle_list path_list;
|
|
|
320 path_list.init_from_list(p_list);
|
|
|
321 metadb_handle_list valid_handles;
|
|
|
322 auto l_metadb = metadb::get();
|
|
|
323 for(t_size pathidx=0;pathidx<path_list.get_count();pathidx++)
|
|
|
324 {
|
|
|
325 if (is_aborting()) return false;
|
|
|
326 on_progress(pathidx,path_list.get_count());
|
|
|
327
|
|
|
328 const char * path = path_list[pathidx];
|
|
|
329
|
|
|
330 if (filesystem::g_is_remote_safe(path)) {
|
|
|
331 metadb_handle_ptr temp;
|
|
|
332 l_metadb->handle_create(temp,make_playable_location(path,0));
|
|
|
333 valid_handles.add_item(temp);
|
|
|
334 } else {
|
|
|
335 try {
|
|
|
336 service_ptr_t<input_info_reader> reader;
|
|
|
337
|
|
|
338 input_entry::g_open_for_info_read(reader,0,path,*this);
|
|
|
339 t_uint32 count = reader->get_subsong_count();
|
|
|
340 for(t_uint32 n=0;n<count && !is_aborting();n++) {
|
|
|
341 metadb_handle_ptr ptr;
|
|
|
342 t_uint32 index = reader->get_subsong(n);
|
|
|
343 l_metadb->handle_create(ptr,make_playable_location(path,index));
|
|
|
344 valid_handles.add_item(ptr);
|
|
|
345 }
|
|
|
346 } catch(...) {}
|
|
|
347 }
|
|
|
348 }
|
|
|
349
|
|
|
350 if (is_aborting()) return false;
|
|
|
351
|
|
|
352 valid_handles.sort_by_pointer();
|
|
|
353 for(t_size listidx=0;listidx<p_list.get_count();listidx++) {
|
|
|
354 bool dead = valid_handles.bsearch_by_pointer(p_list[listidx]) == ~0;
|
|
|
355 if (dead) FB2K_console_formatter() << "Dead item: " << p_list[listidx];
|
|
|
356 p_mask.set(listidx,dead);
|
|
|
357 }
|
|
|
358 return !is_aborting();
|
|
|
359 }
|
|
|
360
|
|
|
361 namespace {
|
|
|
362
|
|
|
363 class dead_item_filter_impl_simple : public dead_item_filter
|
|
|
364 {
|
|
|
365 public:
|
|
|
366 inline dead_item_filter_impl_simple(abort_callback & p_abort) : m_abort(p_abort) {}
|
|
|
367 bool is_aborting() const {return m_abort.is_aborting();}
|
|
|
368 abort_callback_event get_abort_event() const {return m_abort.get_abort_event();}
|
|
|
369 void on_progress(t_size p_position,t_size p_total) {}
|
|
|
370 private:
|
|
|
371 abort_callback & m_abort;
|
|
|
372 };
|
|
|
373
|
|
|
374 }
|
|
|
375
|
|
|
376 bool input_helper::g_mark_dead(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,bit_array_var & p_mask,abort_callback & p_abort)
|
|
|
377 {
|
|
|
378 dead_item_filter_impl_simple filter(p_abort);
|
|
|
379 return filter.run(p_list,p_mask);
|
|
|
380 }
|
|
|
381
|
|
|
382 #endif // #ifdef FOOBAR2000_HAVE_METADB
|
|
|
383
|
|
|
384 void input_info_read_helper::open(const char * p_path,abort_callback & p_abort) {
|
|
|
385 if (m_input.is_empty() || playable_location::path_compare(m_path,p_path) != 0)
|
|
|
386 {
|
|
|
387 TRACK_CODE("input_entry::g_open_for_info_read",input_entry::g_open_for_info_read(m_input,0,p_path,p_abort));
|
|
|
388
|
|
|
389 m_path = p_path;
|
|
|
390 }
|
|
|
391 }
|
|
|
392
|
|
|
393 void input_info_read_helper::get_info(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,abort_callback & p_abort) {
|
|
|
394 open(p_location.get_path(),p_abort);
|
|
|
395
|
|
|
396 TRACK_CODE("input_info_reader::get_file_stats",p_stats = m_input->get_file_stats(p_abort));
|
|
|
397
|
|
|
398 TRACK_CODE("input_info_reader::get_info",m_input->get_info(p_location.get_subsong_index(),p_info,p_abort));
|
|
|
399 }
|
|
|
400
|
|
|
401 #ifdef FOOBAR2000_HAVE_METADB
|
|
|
402 void input_info_read_helper::get_info_check(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,bool & p_reloaded,abort_callback & p_abort) {
|
|
|
403 open(p_location.get_path(),p_abort);
|
|
|
404 t_filestats newstats;
|
|
|
405 TRACK_CODE("input_info_reader::get_file_stats",newstats = m_input->get_file_stats(p_abort));
|
|
|
406 if (metadb_handle::g_should_reload(p_stats,newstats,true)) {
|
|
|
407 p_stats = newstats;
|
|
|
408 TRACK_CODE("input_info_reader::get_info",m_input->get_info(p_location.get_subsong_index(),p_info,p_abort));
|
|
|
409 p_reloaded = true;
|
|
|
410 } else {
|
|
|
411 p_reloaded = false;
|
|
|
412 }
|
|
|
413 }
|
|
|
414 #endif // #ifdef FOOBAR2000_HAVE_METADB
|
|
|
415
|
|
|
416 // openAudioData code
|
|
|
417
|
|
|
418 static constexpr openAudioDataFormat formatNative = (sizeof(audio_sample) == sizeof(double)) ? openAudioDataFormat::float64 : openAudioDataFormat::float32;
|
|
|
419
|
|
|
420 namespace {
|
|
|
421
|
|
|
422 class file_decodedaudio : public file_readonly {
|
|
|
423 public:
|
|
|
424 void init(const playable_location & loc, input_helper::decodeOpen_t const & arg, openAudioDataFormat format, abort_callback & aborter) {
|
|
|
425 FILE_DECODEDAUDIO_PRINT("opening: ", loc );
|
|
|
426 m_length = -1; m_lengthKnown = false;
|
|
|
427 m_format = format;
|
|
|
428 m_subsong = loc.get_subsong();
|
|
|
429 m_decoder ^= input_entry::g_open( input_decoder::class_guid, arg.m_hint, loc.get_path(), arg.m_logger, aborter, arg.m_from_redirect);
|
|
|
430 m_seekable = ( arg.m_flags & input_flag_no_seeking ) == 0 && m_decoder->can_seek();
|
|
|
431 m_flags = arg.m_flags;
|
|
|
432 reopenDecoder(aborter);
|
|
|
433 readChunk(aborter, true);
|
|
|
434 }
|
|
|
435 void init(const playable_location & loc, bool bSeekable, file::ptr fileHint, abort_callback & aborter) {
|
|
|
436 FILE_DECODEDAUDIO_PRINT("opening: ", loc);
|
|
|
437 m_length = -1; m_lengthKnown = false;
|
|
|
438 m_subsong = loc.get_subsong();
|
|
|
439 input_entry::g_open_for_decoding(m_decoder, fileHint, loc.get_path(), aborter);
|
|
|
440 m_seekable = bSeekable && m_decoder->can_seek();
|
|
|
441 reopenDecoder(aborter);
|
|
|
442 readChunk(aborter, true);
|
|
|
443 }
|
|
|
444
|
|
|
445 void reopen(abort_callback & aborter) override {
|
|
|
446
|
|
|
447 // Have valid chunk and it is the first chunk in the stream? Reset read pointers and bail, no need to reopen
|
|
|
448 if (m_chunk.get_sample_count() > 0 && m_chunkBytesPtr == m_currentPosition) {
|
|
|
449 m_currentPosition = 0;
|
|
|
450 m_chunkBytesPtr = 0;
|
|
|
451 m_eof = false;
|
|
|
452 return;
|
|
|
453 }
|
|
|
454
|
|
|
455 reopenDecoder(aborter);
|
|
|
456 readChunk(aborter);
|
|
|
457 }
|
|
|
458
|
|
|
459 bool is_remote() override {
|
|
|
460 return false;
|
|
|
461 }
|
|
|
462 t_filetimestamp get_timestamp(abort_callback & p_abort) override {
|
|
|
463 return m_decoder->get_file_stats(p_abort).m_timestamp;
|
|
|
464 }
|
|
|
465 void on_idle(abort_callback & p_abort) override {
|
|
|
466 m_decoder->on_idle(p_abort);
|
|
|
467 }
|
|
|
468 bool get_content_type(pfc::string_base & p_out) override {
|
|
|
469 return false;
|
|
|
470 }
|
|
|
471 bool can_seek() override {
|
|
|
472 return m_seekable;
|
|
|
473 }
|
|
|
474 void seek(t_filesize p_position, abort_callback & p_abort) override {
|
|
|
475 if (!m_seekable) throw exception_io_object_not_seekable();
|
|
|
476
|
|
|
477
|
|
|
478 {
|
|
|
479 t_filesize chunkBegin = m_currentPosition - m_chunkBytesPtr;
|
|
|
480 t_filesize chunkEnd = chunkBegin + curChunkBytes();
|
|
|
481 if (p_position >= chunkBegin && p_position < chunkEnd) {
|
|
|
482 m_chunkBytesPtr = (size_t)(p_position - chunkBegin);
|
|
|
483 m_currentPosition = p_position;
|
|
|
484 m_eof = false;
|
|
|
485 return;
|
|
|
486 }
|
|
|
487 }
|
|
|
488
|
|
|
489 {
|
|
|
490 t_filesize s = get_size(p_abort);
|
|
|
491 if (s != filesize_invalid) {
|
|
|
492 if (p_position > s) throw exception_io_seek_out_of_range();
|
|
|
493 if (p_position == s) {
|
|
|
494 m_chunk.reset(); m_curChunkBytes = 0; m_chunkBytesPtr = 0;
|
|
|
495 m_currentPosition = p_position;
|
|
|
496 m_eof = true;
|
|
|
497 return;
|
|
|
498 }
|
|
|
499 }
|
|
|
500 }
|
|
|
501
|
|
|
502
|
|
|
503
|
|
|
504 const auto row = m_spec.chanCount * sampleBytes();
|
|
|
505 t_filesize samples = p_position / row;
|
|
|
506 const double seekTime = audio_math::samples_to_time(samples, m_spec.sampleRate);
|
|
|
507 m_decoder->seek(seekTime, p_abort);
|
|
|
508 m_eof = false; // do this before readChunk
|
|
|
509 if (!this->readChunk(p_abort)) {
|
|
|
510 throw std::runtime_error( ( PFC_string_formatter() << "Premature EOF in referenced audio file at " << pfc::format_time_ex(seekTime, 6) << " out of " << pfc::format_time_ex(length(p_abort), 6) ).get_ptr() );
|
|
|
511 }
|
|
|
512 m_chunkBytesPtr = p_position % row;
|
|
|
513 if (m_chunkBytesPtr > curChunkBytes()) {
|
|
|
514 // Should not ever happen
|
|
|
515 m_chunkBytesPtr = 0;
|
|
|
516 throw std::runtime_error("Decoder returned invalid data");
|
|
|
517 }
|
|
|
518
|
|
|
519 m_currentPosition = p_position;
|
|
|
520 m_eof = false;
|
|
|
521 }
|
|
|
522
|
|
|
523 t_filesize get_size(abort_callback & aborter) override {
|
|
|
524 const double l = length(aborter);
|
|
|
525 if (l <= 0) return filesize_invalid;
|
|
|
526 return audio_math::time_to_samples(l, m_spec.sampleRate) * m_spec.chanCount * sampleBytes();
|
|
|
527 }
|
|
|
528 t_filesize get_position(abort_callback & p_abort) override {
|
|
|
529 return m_currentPosition;
|
|
|
530 }
|
|
|
531 t_size read(void * p_buffer, t_size p_bytes, abort_callback & p_abort) override {
|
|
|
532 FILE_DECODEDAUDIO_PRINT("read(", p_bytes, ")");
|
|
|
533 size_t done = 0;
|
|
|
534 for (;;) {
|
|
|
535 p_abort.check();
|
|
|
536 {
|
|
|
537 const size_t inChunk = curChunkBytes();
|
|
|
538 const size_t inChunkRemaining = inChunk - m_chunkBytesPtr;
|
|
|
539 const size_t delta = pfc::min_t<size_t>(inChunkRemaining, (p_bytes - done));
|
|
|
540 memcpy((uint8_t*)p_buffer + done, (const uint8_t*)m_curChunkPtr + m_chunkBytesPtr, delta);
|
|
|
541 m_chunkBytesPtr += delta;
|
|
|
542 done += delta;
|
|
|
543 m_currentPosition += delta;
|
|
|
544
|
|
|
545 if (done == p_bytes) break;
|
|
|
546 }
|
|
|
547 if (!readChunk(p_abort)) break;
|
|
|
548 }
|
|
|
549 FILE_DECODEDAUDIO_PRINT("read(", p_bytes, ") returning ", done);
|
|
|
550 return done;
|
|
|
551 }
|
|
|
552 audio_chunk::spec_t const & get_spec() const { return m_spec; }
|
|
|
553 private:
|
|
|
554 void reopenDecoder(abort_callback & aborter) {
|
|
|
555 uint32_t flags = m_flags | input_flag_no_looping;
|
|
|
556 if (!m_seekable) flags |= input_flag_no_seeking;
|
|
|
557
|
|
|
558 m_decoder->initialize(m_subsong, flags, aborter);
|
|
|
559 m_eof = false;
|
|
|
560 m_currentPosition = 0;
|
|
|
561 }
|
|
|
562 size_t curChunkBytes() const {
|
|
|
563 return m_curChunkBytes;
|
|
|
564 }
|
|
|
565 bool readChunk(abort_callback & aborter, bool initial = false) {
|
|
|
566 m_chunkBytesPtr = 0;
|
|
|
567 FILE_DECODEDAUDIO_PRINT("readChunk()");
|
|
|
568 for (;;) {
|
|
|
569 if (m_eof || !m_decoder->run(m_chunk, aborter)) {
|
|
|
570 if (initial) throw std::runtime_error("Decoder produced no data");
|
|
|
571 m_eof = true;
|
|
|
572 m_chunk.reset(); m_curChunkBytes = 0;
|
|
|
573 FILE_DECODEDAUDIO_PRINT("readChunk() EOF");
|
|
|
574 return false;
|
|
|
575 }
|
|
|
576 if (m_chunk.is_valid()) break;
|
|
|
577 }
|
|
|
578 PFC_ASSERT(m_chunk.get_peak() < 100);
|
|
|
579 audio_chunk::spec_t spec = m_chunk.get_spec();
|
|
|
580 if (initial) m_spec = spec;
|
|
|
581 else if (m_spec != spec) throw std::runtime_error("Sample format change in mid stream");
|
|
|
582
|
|
|
583 auto inPtr = m_chunk.get_data();
|
|
|
584 const size_t inCount = m_chunk.get_used_size();
|
|
|
585 m_curChunkBytes = inCount * sampleBytes();
|
|
|
586 FILE_DECODEDAUDIO_PRINT("readChunk(): ", m_chunk.get_sample_count(), " samples, ", m_curChunkBytes, " bytes");
|
|
|
587 if ( m_format == formatNative ) {
|
|
|
588 m_curChunkPtr = inPtr;
|
|
|
589 } else {
|
|
|
590 m_altFormatBuffer.grow_size(m_curChunkBytes);
|
|
|
591 m_curChunkPtr = m_altFormatBuffer.get_ptr();
|
|
|
592 switch (m_format) {
|
|
|
593 case openAudioDataFormat::float32:
|
|
|
594 { auto out = reinterpret_cast<float*>(m_curChunkPtr); for (size_t walk = 0; walk < inCount; ++walk) out[walk] = (float)inPtr[walk]; }
|
|
|
595 break;
|
|
|
596 case openAudioDataFormat::float64:
|
|
|
597 { auto out = reinterpret_cast<double*>(m_curChunkPtr); for (size_t walk = 0; walk < inCount; ++walk) out[walk] = (double)inPtr[walk]; }
|
|
|
598 break;
|
|
|
599 default:
|
|
|
600 PFC_ASSERT(!"???");
|
|
|
601 }
|
|
|
602 }
|
|
|
603
|
|
|
604 return true;
|
|
|
605 }
|
|
|
606 double length(abort_callback & aborter) {
|
|
|
607 if (!m_lengthKnown) {
|
|
|
608 file_info_impl temp;
|
|
|
609 m_decoder->get_info(m_subsong, temp, aborter);
|
|
|
610 m_length = temp.get_length();
|
|
|
611 m_lengthKnown = true;
|
|
|
612 }
|
|
|
613 return m_length;
|
|
|
614 }
|
|
|
615 unsigned sampleBytes() const {
|
|
|
616 return m_format == openAudioDataFormat::float32 ? 4 : 8;
|
|
|
617 }
|
|
|
618 audio_chunk_fast_impl m_chunk;
|
|
|
619 size_t m_chunkBytesPtr;
|
|
|
620 audio_chunk::spec_t m_spec;
|
|
|
621 double m_length;
|
|
|
622 bool m_lengthKnown;
|
|
|
623 bool m_seekable;
|
|
|
624 uint32_t m_subsong;
|
|
|
625 input_decoder::ptr m_decoder;
|
|
|
626 bool m_eof;
|
|
|
627 t_filesize m_currentPosition;
|
|
|
628 unsigned m_flags = 0;
|
|
|
629 openAudioDataFormat m_format = formatNative;
|
|
|
630
|
|
|
631 pfc::array_t<uint8_t> m_altFormatBuffer;
|
|
|
632 void* m_curChunkPtr = nullptr;
|
|
|
633 size_t m_curChunkBytes = 0;
|
|
|
634 };
|
|
|
635
|
|
|
636 }
|
|
|
637
|
|
|
638 openAudioData_t openAudioData3(playable_location const& loc, input_helper::decodeOpen_t const& openArg, openAudioDataFormat format, abort_callback& aborter) {
|
|
|
639 service_ptr_t<file_decodedaudio> f; f = new service_impl_t < file_decodedaudio >;
|
|
|
640 f->init(loc, openArg, format, aborter);
|
|
|
641
|
|
|
642 openAudioData_t oad = {};
|
|
|
643 oad.audioData = f;
|
|
|
644 oad.audioSpec = f->get_spec();
|
|
|
645 return oad;
|
|
|
646 }
|
|
|
647
|
|
|
648 openAudioData_t openAudioData2(playable_location const & loc, input_helper::decodeOpen_t const & openArg, abort_callback & aborter) {
|
|
|
649 return openAudioData3(loc, openArg, formatNative, aborter);
|
|
|
650 }
|
|
|
651
|
|
|
652 openAudioData_t openAudioData(playable_location const & loc, bool bSeekable, file::ptr fileHint, abort_callback & aborter) {
|
|
|
653 service_ptr_t<file_decodedaudio> f; f = new service_impl_t < file_decodedaudio > ;
|
|
|
654 f->init(loc, bSeekable, fileHint, aborter);
|
|
|
655
|
|
|
656 openAudioData_t oad = {};
|
|
|
657 oad.audioData = f;
|
|
|
658 oad.audioSpec = f->get_spec();
|
|
|
659 return oad;
|
|
|
660 }
|