#include "stdafx.h" #include "input_helpers.h" #include "fullFileBuffer.h" #include "file_list_helper.h" #include "fileReadAhead.h" input_helper::ioFilter_t input_helper::ioFilter_full_buffer(t_filesize val ) { if (val == 0) return nullptr; return [val] ( file_ptr & f, const char * path, abort_callback & aborter) { if (!filesystem::g_is_remote_or_unrecognized(path)) { fullFileBuffer a; f = a.open(path, aborter, f, val); return true; } return false; }; } input_helper::ioFilter_t input_helper::ioFilter_block_buffer(size_t arg) { if (arg == 0) return nullptr; return [arg](file_ptr & p_file, const char * p_path, abort_callback & p_abort) { if (!filesystem::g_is_remote_or_unrecognized(p_path)) { if (p_file.is_empty()) filesystem::g_open_read(p_file, p_path, p_abort); if (!p_file->is_in_memory() && !p_file->is_remote() && p_file->can_seek()) { file_cached::g_create(p_file, p_file, p_abort, (size_t)arg); return true; } } return false; }; } static input_helper::ioFilter_t makeReadAhead(size_t arg, bool bRemote) { if (arg == 0) return nullptr; return [arg, bRemote](file_ptr & p_file, const char * p_path, abort_callback & p_abort) { if (p_file.is_empty()) { filesystem::ptr fs; if (!filesystem::g_get_interface(fs, p_path)) return false; if (bRemote != fs->is_remote(p_path)) return false; fs->open(p_file, p_path, filesystem::open_mode_read, p_abort); } else if (bRemote != p_file->is_remote()) return false; if (p_file->is_in_memory()) return false; p_file = fileCreateReadAhead(p_file, (size_t)arg, p_abort); return true; }; } input_helper::ioFilter_t input_helper::ioFilter_remote_read_ahead(size_t arg) { return makeReadAhead(arg, true); } input_helper::ioFilter_t input_helper::ioFilter_local_read_ahead(size_t arg) { return makeReadAhead(arg, false); } void input_helper::open(service_ptr_t p_filehint,metadb_handle_ptr p_location,unsigned p_flags,abort_callback & p_abort,bool p_from_redirect,bool p_skip_hints) { open(p_filehint,p_location->get_location(),p_flags,p_abort,p_from_redirect,p_skip_hints); } void input_helper::fileOpenTools(service_ptr_t & p_file,const char * p_path,input_helper::ioFilters_t const & filters, abort_callback & p_abort) { for( auto walk = filters.begin(); walk != filters.end(); ++ walk ) { auto f = *walk; if (f) { if (f(p_file, p_path, p_abort)) break; } } } bool input_helper::need_file_reopen(const char * newPath) const { return m_input.is_empty() || playable_location::path_compare(m_path, newPath) != 0; } bool input_helper::open_path(const char * path, abort_callback & abort, decodeOpen_t const & other) { abort.check(); m_logger = other.m_logger; if (!need_file_reopen(path)) { if ( other.m_logger.is_valid() ) { input_decoder_v2::ptr v2; if (m_input->service_query_t(v2)) v2->set_logger(other.m_logger); } return false; } m_input.release(); service_ptr_t l_file = other.m_hint; fileOpenTools(l_file, path, other.m_ioFilters, abort); TRACK_CODE("input_entry::g_open_for_decoding", m_input ^= input_entry::g_open(input_decoder::class_guid, l_file, path, m_logger, abort, other.m_from_redirect ); ); #ifndef FOOBAR2000_MODERN if (!other.m_skip_hints) { try { metadb_io::get()->hint_reader(m_input.get_ptr(), path, abort); } catch (exception_io_data) { //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. m_input.release(); if (l_file.is_valid()) l_file->reopen(abort); TRACK_CODE("input_entry::g_open_for_decoding", m_input ^= input_entry::g_open(input_decoder::class_guid, l_file, path, m_logger, abort, other.m_from_redirect); ); } } #endif if (other.m_shim) m_input = other.m_shim(m_input, path, abort); m_path = path; return true; } void input_helper::open_decoding(t_uint32 subsong, t_uint32 flags, abort_callback & p_abort) { TRACK_CODE("input_decoder::initialize", m_input->initialize(subsong, flags, p_abort)); } void input_helper::open(const playable_location & location, abort_callback & abort, decodeOpen_t const & other) { open_path(location.get_path(), abort, other); if (other.m_setSampleRate != 0) { this->extended_param(input_params::set_preferred_sample_rate, other.m_setSampleRate, nullptr, 0); } open_decoding(location.get_subsong(), other.m_flags, abort); } void input_helper::attach(input_decoder::ptr dec, const char * path) { m_input = dec; m_path = path; } void input_helper::open(service_ptr_t p_filehint, const playable_location & p_location, unsigned p_flags, abort_callback & p_abort, bool p_from_redirect, bool p_skip_hints) { decodeOpen_t o; o.m_hint = p_filehint; o.m_flags = p_flags; o.m_from_redirect = p_from_redirect; o.m_skip_hints = p_skip_hints; this->open(p_location, p_abort, o); } void input_helper::close() { m_input.release(); } bool input_helper::is_open() { return m_input.is_valid(); } void input_helper::set_pause(bool state) { input_decoder_v3::ptr v3; if (m_input->service_query_t(v3)) v3->set_pause(state); } bool input_helper::flush_on_pause() { input_decoder_v3::ptr v3; if (m_input->service_query_t(v3)) return v3->flush_on_pause(); else return false; } void input_helper::set_logger(event_logger::ptr ptr) { m_logger = ptr; input_decoder_v2::ptr v2; if (m_input->service_query_t(v2)) v2->set_logger(ptr); } bool input_helper::run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) { input_decoder_v2::ptr v2; if (!m_input->service_query_t(v2)) throw pfc::exception_not_implemented(); return v2->run_raw(p_chunk, p_raw, p_abort); } bool input_helper::run(audio_chunk & p_chunk, abort_callback & p_abort) { TRACK_CODE("input_decoder::run", return m_input->run(p_chunk, p_abort)); } void input_helper::seek(double seconds, abort_callback & p_abort) { TRACK_CODE("input_decoder::seek", m_input->seek(seconds, p_abort)); } bool input_helper::can_seek() { return m_input->can_seek(); } bool input_helper::query_position( double & val ) { return extended_param(input_params::query_position, 0, &val, sizeof(val) ) != 0; } size_t input_helper::extended_param(const GUID & type, size_t arg1, void * arg2, size_t arg2size) { input_decoder_v4::ptr v4; if (v4 &= m_input) { return v4->extended_param(type, arg1, arg2, arg2size); } return 0; } input_helper::decodeInfo_t input_helper::decode_info() { decodeInfo_t ret = {}; if (m_input.is_valid()) { ret.m_can_seek = can_seek(); ret.m_flush_on_pause = flush_on_pause(); if (ret.m_can_seek) { ret.m_seeking_expensive = extended_param(input_params::seeking_expensive, 0, nullptr, 0) != 0; } } return ret; } void input_helper::on_idle(abort_callback & p_abort) { if (m_input.is_valid()) { TRACK_CODE("input_decoder::on_idle",m_input->on_idle(p_abort)); } } bool input_helper::get_dynamic_info(file_info & p_out,double & p_timestamp_delta) { TRACK_CODE("input_decoder::get_dynamic_info",return m_input->get_dynamic_info(p_out,p_timestamp_delta)); } bool input_helper::get_dynamic_info_track(file_info & p_out,double & p_timestamp_delta) { TRACK_CODE("input_decoder::get_dynamic_info_track",return m_input->get_dynamic_info_track(p_out,p_timestamp_delta)); } void input_helper::get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) { TRACK_CODE("input_decoder::get_info",m_input->get_info(p_subsong,p_info,p_abort)); } const char * input_helper::get_path() const { return m_path; } input_helper::input_helper() { } void input_helper::g_get_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect) { service_ptr_t instance; input_entry::g_open_for_info_read(instance,0,p_location.get_path(),p_abort,p_from_redirect); instance->get_info(p_location.get_subsong_index(),p_info,p_abort); } void input_helper::g_set_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect) { service_ptr_t instance; input_entry::g_open_for_info_write(instance,0,p_location.get_path(),p_abort,p_from_redirect); instance->set_info(p_location.get_subsong_index(),p_info,p_abort); instance->commit(p_abort); } bool dead_item_filter::run(const pfc::list_base_const_t & p_list,bit_array_var & p_mask) { file_list_helper::file_list_from_metadb_handle_list path_list; path_list.init_from_list(p_list); metadb_handle_list valid_handles; auto l_metadb = metadb::get(); for(t_size pathidx=0;pathidxhandle_create(temp,make_playable_location(path,0)); valid_handles.add_item(temp); } else { try { service_ptr_t reader; input_entry::g_open_for_info_read(reader,0,path,*this); t_uint32 count = reader->get_subsong_count(); for(t_uint32 n=0;nget_subsong(n); l_metadb->handle_create(ptr,make_playable_location(path,index)); valid_handles.add_item(ptr); } } catch(...) {} } } if (is_aborting()) return false; valid_handles.sort_by_pointer(); for(t_size listidx=0;listidx & p_list,bit_array_var & p_mask,abort_callback & p_abort) { dead_item_filter_impl_simple filter(p_abort); return filter.run(p_list,p_mask); } void input_info_read_helper::open(const char * p_path,abort_callback & p_abort) { if (m_input.is_empty() || playable_location::path_compare(m_path,p_path) != 0) { TRACK_CODE("input_entry::g_open_for_info_read",input_entry::g_open_for_info_read(m_input,0,p_path,p_abort)); m_path = p_path; } } void input_info_read_helper::get_info(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,abort_callback & p_abort) { open(p_location.get_path(),p_abort); TRACK_CODE("input_info_reader::get_file_stats",p_stats = m_input->get_file_stats(p_abort)); TRACK_CODE("input_info_reader::get_info",m_input->get_info(p_location.get_subsong_index(),p_info,p_abort)); } 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) { open(p_location.get_path(),p_abort); t_filestats newstats; TRACK_CODE("input_info_reader::get_file_stats",newstats = m_input->get_file_stats(p_abort)); if (metadb_handle::g_should_reload(p_stats,newstats,true)) { p_stats = newstats; TRACK_CODE("input_info_reader::get_info",m_input->get_info(p_location.get_subsong_index(),p_info,p_abort)); p_reloaded = true; } else { p_reloaded = false; } } // openAudioData code namespace { class file_decodedaudio : public file_readonly { public: void init(const playable_location & loc, input_helper::decodeOpen_t const & arg, abort_callback & aborter) { m_length = -1; m_lengthKnown = false; m_subsong = loc.get_subsong(); m_decoder ^= input_entry::g_open( input_decoder::class_guid, arg.m_hint, loc.get_path(), arg.m_logger, aborter, arg.m_from_redirect); m_seekable = ( arg.m_flags & input_flag_no_seeking ) == 0 && m_decoder->can_seek(); reopenDecoder(aborter); readChunk(aborter, true); } void init(const playable_location & loc, bool bSeekable, file::ptr fileHint, abort_callback & aborter) { m_length = -1; m_lengthKnown = false; m_subsong = loc.get_subsong(); input_entry::g_open_for_decoding(m_decoder, fileHint, loc.get_path(), aborter); m_seekable = bSeekable && m_decoder->can_seek(); reopenDecoder(aborter); readChunk(aborter, true); } void reopen(abort_callback & aborter) { // Have valid chunk and it is the first chunk in the stream? Reset read pointers and bail, no need to reopen if (m_chunk.get_sample_count() > 0 && m_chunkBytesPtr == m_currentPosition) { m_currentPosition = 0; m_chunkBytesPtr = 0; m_eof = false; return; } reopenDecoder(aborter); readChunk(aborter); } bool is_remote() { return false; } #if FOOBAR2000_TARGET_VERSION >= 2000 t_filestats get_stats(abort_callback & aborter) { t_filestats fs = m_decoder->get_file_stats(aborter); fs.m_size = get_size(aborter); return fs; } #else t_filetimestamp get_timestamp(abort_callback & p_abort) { return m_decoder->get_file_stats(p_abort).m_timestamp; } #endif void on_idle(abort_callback & p_abort) { m_decoder->on_idle(p_abort); } bool get_content_type(pfc::string_base & p_out) { return false; } bool can_seek() { return m_seekable; } void seek(t_filesize p_position, abort_callback & p_abort) { if (!m_seekable) throw exception_io_object_not_seekable(); { t_filesize chunkBegin = m_currentPosition - m_chunkBytesPtr; t_filesize chunkEnd = chunkBegin + curChunkBytes(); if (p_position >= chunkBegin && p_position < chunkEnd) { m_chunkBytesPtr = (size_t)(p_position - chunkBegin); m_currentPosition = p_position; m_eof = false; return; } } { t_filesize s = get_size(p_abort); if (s != filesize_invalid) { if (p_position > s) throw exception_io_seek_out_of_range(); if (p_position == s) { m_chunk.reset(); m_chunkBytesPtr = 0; m_currentPosition = p_position; m_eof = true; return; } } } const size_t row = m_spec.chanCount * sizeof(audio_sample); t_filesize samples = p_position / row; const double seekTime = audio_math::samples_to_time(samples, m_spec.sampleRate); m_decoder->seek(seekTime, p_abort); m_eof = false; // do this before readChunk if (!this->readChunk(p_abort)) { 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() ); } m_chunkBytesPtr = p_position % row; if (m_chunkBytesPtr > curChunkBytes()) { // Should not ever happen m_chunkBytesPtr = 0; throw std::runtime_error("Decoder returned invalid data"); } m_currentPosition = p_position; m_eof = false; } t_filesize get_size(abort_callback & aborter) { const double l = length(aborter); if (l <= 0) return filesize_invalid; return audio_math::time_to_samples(l, m_spec.sampleRate) * m_spec.chanCount * sizeof(audio_sample); } t_filesize get_position(abort_callback & p_abort) { return m_currentPosition; } t_size read(void * p_buffer, t_size p_bytes, abort_callback & p_abort) { size_t done = 0; for (;;) { p_abort.check(); { const size_t inChunk = curChunkBytes(); const size_t inChunkRemaining = inChunk - m_chunkBytesPtr; const size_t delta = pfc::min_t(inChunkRemaining, (p_bytes - done)); memcpy((uint8_t*)p_buffer + done, (const uint8_t*)m_chunk.get_data() + m_chunkBytesPtr, delta); m_chunkBytesPtr += delta; done += delta; m_currentPosition += delta; if (done == p_bytes) break; } if (!readChunk(p_abort)) break; } return done; } audio_chunk::spec_t const & get_spec() const { return m_spec; } private: void reopenDecoder(abort_callback & aborter) { uint32_t flags = input_flag_no_looping; if (!m_seekable) flags |= input_flag_no_seeking; m_decoder->initialize(m_subsong, flags, aborter); m_eof = false; m_currentPosition = 0; } size_t curChunkBytes() const { return m_chunk.get_used_size() * sizeof(audio_sample); } bool readChunk(abort_callback & aborter, bool initial = false) { m_chunkBytesPtr = 0; for (;;) { if (m_eof || !m_decoder->run(m_chunk, aborter)) { if (initial) throw std::runtime_error("Decoder produced no data"); m_eof = true; m_chunk.reset(); return false; } if (m_chunk.is_valid()) break; } audio_chunk::spec_t spec = m_chunk.get_spec(); if (initial) m_spec = spec; else if (m_spec != spec) throw std::runtime_error("Sample format change in mid stream"); return true; } double length(abort_callback & aborter) { if (!m_lengthKnown) { file_info_impl temp; m_decoder->get_info(m_subsong, temp, aborter); m_length = temp.get_length(); m_lengthKnown = true; } return m_length; } audio_chunk_fast_impl m_chunk; size_t m_chunkBytesPtr; audio_chunk::spec_t m_spec; double m_length; bool m_lengthKnown; bool m_seekable; uint32_t m_subsong; input_decoder::ptr m_decoder; bool m_eof; t_filesize m_currentPosition; }; } openAudioData_t openAudioData2(playable_location const & loc, input_helper::decodeOpen_t const & openArg, abort_callback & aborter) { service_ptr_t f; f = new service_impl_t < file_decodedaudio >; f->init(loc, openArg, aborter); openAudioData_t oad = {}; oad.audioData = f; oad.audioSpec = f->get_spec(); return oad; } openAudioData_t openAudioData(playable_location const & loc, bool bSeekable, file::ptr fileHint, abort_callback & aborter) { service_ptr_t f; f = new service_impl_t < file_decodedaudio > ; f->init(loc, bSeekable, fileHint, aborter); openAudioData_t oad = {}; oad.audioData = f; oad.audioSpec = f->get_spec(); return oad; }