Mercurial > foo_out_sdl
comparison foosdk/sdk/foobar2000/helpers/input_helpers.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:e9bb126753e7 | 1:20d02a178406 |
|---|---|
| 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 } |
