// DTS Coherent Acoustics decoder for foobar2000 // Originally based on dtsdec, Copyright (C) 2004 Gildas Bazin // (C) 2000-2003 Michel Lespinasse // (C) 1999-2000 Aaron Holtzman // Now based on dcadec, Copyright (C) 2015 foo86 // foobar2000 component copyright (C) 2004-2006 Janne Hyvärinen // (C) 2006-2018 Christopher Snowhill // // Changes: // 0.6.8 (2019-02-20): Fixed plain DTS file seeking // 0.6.7 (2019-01-11): Fixed decode postprocessor handling multiple packets in a single decode call // 0.6.6 (2019-01-10): Fixed decode postprocessor handling pre-stream silence // 0.6.5 (2019-01-10): Hopefully fixed Windows XP support // 0.6.4 (2019-01-10): Fixed decode postprocessor to handle invalid packets as invalid stream instead of throwing an exception // 0.6.3 (2019-01-09): Added extra format information discovery // 0.6.2 (2019-01-09): Fixed the decode postprocessor // 0.6.1 (2019-01-07): Fixed the packet decoder // 0.6.0 (2019-01-07): Switched over to using FFmpeg // 0.5.5 (2019-01-05): Fixed packet decoder analyze support detection, and implemented support for DTS-HD in MP4 container // 0.5.4 (2018-01-14): Updated to version 1.4 SDK // 0.5.3 (2017-02-04): Added link to about string // 0.5.2 (2016-02-21): Updated the credits, and fixed a UTF-8 encoding issue // 0.5.1 (2016-02-21): Added support for DTS header specifying a sample delay // 0.5.0 (2016-02-19): Added .dtshd and .dtsma file name extensions // 0.4.9 (2016-02-19): Fix several glaring faults in my conversion of the updated dca stream parser // 0.4.8 (2016-02-18): Fix frame size calculation when dealing with 14 bit streams, especially with odd frame lengths // 0.4.7 (2016-02-17): Fix a buffer overrun issue with the decode postprocessor when dealing with incorrect data // 0.4.6 (2016-02-14): Massive update to dcadec-0.2, now with no-C99 backport // 0.4.5 (2015-05-23): Fixed a potential crash with the decode postprocessor // 0.4.4 (2015-05-23): Fixed decode postprocessor handling packets split across audio chunks by retaining several state variables // 0.4.3 (2015-04-16): Made DTS codec profile info more in line with official branding // 0.4.2 (2015-04-14): Removed usage of the popcnt opcode, which requires arbitrary new processors, and fixed profile string generation // 0.4.1 (2015-04-13): Added 14 bit to 16 bit packing to packet decoder // 0.4.0 (2015-04-13): Replaced libdca with libdcadec // 0.3.3 (2014-02-16): Implemented support for packet decoding from MP4 files // 0.3.2 (2013-11-03): Fixed channel mode reporting in the event that dts_flags has an invalid channel number field // 0.3.1 (2013-01-31): Amended decode postprocessor to emit silence in the same format as the DTS signal // 0.3.0 (2010-09-03): Added support for 48KHz source streams to the decode postprocessor // 0.2.9 (2010-05-23): Implemented decode_postprocessor interface, removed DSP // 0.2.8 (2010-01-11): Updated to 1.0 SDK // 0.2.7 (2009-12-13): Found and fixed another false positive with the DSP // 0.2.6 (2009-12-13): Really fixed DTS decoder DSP this time // 0.2.5 (2009-12-05): Fixed heap corruption on bad DTS files, DSP doesn't output until two consecutive frames are found // 0.2.4 (2009-05-02): Fixed a bug in DTS DSP and packet decoder for when dca_syncinfo fails // 0.2.3 (2009-03-30): Fixed tag writing // 0.2.2 (2008-10-26): Restricted silence generation to DTS WAV files // 0.2.1 (2008-09-28): Seeking accuracy improvements, SDK's Decoder Validator was broken and didn't notice the bugs // 0.2.0 (2008-09-13): Added preliminary DSP decoder for use with DTS audio CDs and WAVs without changing extension // 0.1.9 (2008-09-02): Small change: output silence instead of nothing when decoding a block fails // 0.1.8 (2008-09-02): Tagging configuration is back (in Advanced preferences), updated libdca to version 0.0.5, seeking accuracy improved // 0.1.7 (2006-07-22): Added support for internal cuesheets // 0.1.6 (2006-07-12): Playback from cuesheets always started from the beginning of the file, fixed // 0.1.5 (2006-07-09): foobar2000 v0.9.x conversion // 0.1.4 (2005-07-24): Accepts header with reversed word order // 0.1.3 (2005-03-28): Smarter channel order fixing. Now uses 9 channel output only for files that output to center rear speaker. // Added channel order hack also to packet decoder, added configuration // 0.1.2 (2005-03-28): Fixed crashing with invalid files // 0.1.1 (2005-03-26): Hack to fix output channel order for odd channel modes // 0.1 (2005-03-20): Fixed incorrect handling for files that had no LFE channel, more accurate seeking // 0.0.9 (2005-01-01): Fixed hanging at the end of DTS files // 0.0.8 (2004-12-27): Improved DTS-in-wav detection // 0.0.7 (2004-10-23): Added DTS header seeking to support more DTS-in-wav files // 0.0.6 (2004-10-20): Removed most of the changes for 0.0.5 and replaced with hacks for now // 0.0.5 (2004-10-17): Fixes to raw DTS handling // 0.0.4 (2004-10-15): Simplified packet decoder, added codec reporting, fixed typo in version number // 0.0.3 (2004-10-15): Added Matroska packet decoder support #define FD_VERSION "0.6.8" //#define DTS_DEBUG // print status info to console #include "../SDK/foobar2000.h" #include "../helpers/helpers.h" using namespace pfc; #include "ffmpeg_shared.h" enum { BUFFER_SIZE = 24576, HEADER_SIZE = 14, FRAME_SAMPLES = 256, }; //cfg_int cfg_drc ( "drc", 0 ); // Dynamic range compression defaults to off //cfg_int cfg_tag ( "tag", 1 ); // Write APEv2 tags by default static const GUID guid_dts_branch = { 0x879389e3, 0x8c4, 0x4db0, { 0x8c, 0xb7, 0xec, 0x51, 0x3d, 0xb5, 0xd9, 0x14 } }; static const GUID guid_enable_tag = { 0x39e19ab2, 0xfb84, 0x4657, { 0xbf, 0x8c, 0x7a, 0x55, 0x7a, 0x35, 0xce, 0x72 } }; //static const GUID guid_enable_drc = { 0xe254b211, 0xc3f4, 0x40b6, { 0x91, 0xc, 0xa8, 0x3e, 0xe, 0x7f, 0x61, 0x2f } }; static advconfig_branch_factory dts_tagging_branch("DTS", guid_dts_branch, advconfig_branch::guid_branch_tagging, 0); static advconfig_checkbox_factory g_cfg_tag("Enable APEv2 tag writing", guid_enable_tag, guid_dts_branch, 0, true); //static advconfig_checkbox_factory g_cfg_drc("DTS - Use dynamic range compression", guid_enable_drc, advconfig_branch::guid_branch_decoding, 0, false); // ------------------------------------- static void parse_tagtype_internal(const char *p_tagtype, bool &p_have_id3v1, bool &p_have_id3v2, bool &p_have_apev2) { const char *tagtype = p_tagtype; while(*tagtype) { unsigned delta = 0; while(tagtype[delta] != 0 && tagtype[delta] != '|') delta++; if (delta > 0) { if (!stricmp_utf8_ex(tagtype, delta, "apev1", ~0) || !stricmp_utf8_ex(tagtype, delta, "apev2", ~0)) p_have_apev2 = true; else if (!stricmp_utf8_ex(tagtype, delta, "id3v1", ~0)) p_have_id3v1 = true; else if (!stricmp_utf8_ex(tagtype, delta, "id3v2", ~0)) p_have_id3v2 = true; } tagtype += delta; while(*tagtype == '|') tagtype++; } } class input_dts : public input_stubs { private: service_ptr_t r; ffmpeg_codec_data * data; t_filesize header_pos; bool eof, iswav; /* uint8_t buf[BUFFER_SIZE]; uint8_t *bufptr, *bufpos; */ t_filesize tag_offset; int dts_flags, nch, srate, bps, frame_size; double real_bitrate; unsigned int skip_samples; unsigned int skip_decoding_packets; unsigned int channel_mask; unsigned int silence_bytes; unsigned int delay_samples; unsigned int dynamic_interval; unsigned int dynamic_last_decoded; unsigned int dynamic_decoded_this_interval; unsigned int dynamic_blocks_per_interval; unsigned int dynamic_bitrate_total; unsigned int dynamic_current_bitrate; unsigned int dynamic_bitrate_count; pfc::array_t dynamic_bitrates; __int64 skip_wav_header(file::ptr & r, abort_callback &p_abort) { t_filesize pos = r->get_position(p_abort); t_filesize filesize = r->get_size(p_abort); for (;;) { char temp[4]; DWORD tmp; r->read(temp, 4, p_abort); if (memcmp(temp, "RIFF", 4)) break; r->read(temp, 4, p_abort); r->read(temp, 4, p_abort); if (memcmp(temp, "WAVE", 4)) break; for (;;) { if (r->read(temp, 4, p_abort) != 4) break; if (!memcmp(temp, "fmt ", 4)) break; //success if (!memcmp(temp, "data", 4)) break; //already got data chunk but no fmt if (r->read(&tmp, 4, p_abort) != 4) break; if (tmp & 1) tmp++; if (tmp == 0 || r->get_position(p_abort) + tmp > filesize - 8) break; r->seek(tmp + r->get_position(p_abort), p_abort); } if (memcmp(temp, "fmt ", 4)) break; __int64 position = r->get_position(p_abort) - 4; if (r->read(&tmp, 4, p_abort) != 4) break; if (tmp < 14 || tmp > 64 * 1024) break; r->seek(tmp + r->get_position(p_abort), p_abort); char code[4]; do { position += 8 + tmp + (tmp & 1); //word-align all blocksizes r->seek(position, p_abort); if (r->read(code, 4, p_abort) != 4) break; if (r->read(temp, 4, p_abort) != 4) break; } while (memcmp(code, "data", 4)); if (memcmp(code, "data", 4)) break; position += 8; r->seek(position, p_abort); return position - pos; } r->seek(pos, p_abort); return 0; } public: input_dts() { data = 0; } ~input_dts() { abort_callback_dummy temp; if (data) free_ffmpeg(data, temp); } bool decode_can_seek() { return r->can_seek(); } bool decode_get_dynamic_info(file_info &p_out, double &p_timestamp_delta) { if (dynamic_decoded_this_interval >= dynamic_interval) { dynamic_decoded_this_interval %= dynamic_interval; unsigned int bps = dynamic_bitrate_total / dynamic_bitrate_count; p_out.info_set_int("bitrate", (bps + 500) / 1000); p_timestamp_delta = -((double)dynamic_last_decoded / (double)srate); return true; } return false; } bool decode_get_dynamic_info_track(file_info &p_out, double &p_timestamp_delta) { return false; } void decode_on_idle(abort_callback &p_abort) { r->on_idle(p_abort); } static bool g_is_our_content_type(const char *p_content_type) { return (!strcmp(p_content_type, "audio/x-dts")) || (!strcmp(p_content_type, "audio/dts")); } static bool g_is_our_path(const char *p_path, const char *p_extension) { return (stricmp_utf8(p_extension, "dts") == 0) || (stricmp_utf8(p_extension, "dtswav") == 0) || (stricmp_utf8(p_extension, "dtshd") == 0) || (stricmp_utf8(p_extension, "dtsma") == 0); } t_filestats get_file_stats(abort_callback &p_abort) { return r->get_stats(p_abort); } void open(service_ptr_t p_filehint, const char *p_path, t_input_open_reason p_reason, abort_callback &p_abort) { if (p_reason==input_open_info_write && g_cfg_tag.get_static_instance().get_state()==false) throw exception_io_unsupported_format(); r = p_filehint; input_open_file_helper(r, p_path, p_reason, p_abort); tag_offset = 0; try { file_info_impl t; tag_processor::read_trailing_ex(r, t, tag_offset, p_abort); } catch(exception_io_data) { /*file had no trailing tags */ } if (tag_offset == 0) tag_offset = r->get_size(p_abort); header_pos = 0; tag_processor::skip_id3v2(r, header_pos, p_abort); __int64 t = skip_wav_header(r, p_abort); if (t > 0) iswav = true; else iswav = false; header_pos += t; this->r = r; data = init_ffmpeg_offset(r, header_pos, tag_offset - header_pos, p_abort); if (!data) { throw exception_io_data("Failed to initialize DTS decoder"); } nch = srate = 0; skip_samples = 0; silence_bytes = 0; #ifdef DTS_DEBUG skipped_bytes = 0; decoded_bytes = 0; #endif eof = false; channel_mask = 0; delay_samples = data->skipSamples; frame_size = data->frameSize ? data->frameSize : 512; nch = data->channels; srate = data->sampleRate; bps = data->bitsPerSample; if (!srate || !nch) { free_ffmpeg(data, p_abort); data = 0; throw exception_io_data(); } this->channel_mask = audio_chunk::g_guess_channel_config(nch); } void get_info(file_info &p_info, abort_callback &p_abort) { t_filesize current_pos = r->get_position(p_abort); t_filesize next_pos; __int64 offset; //t_filesize tag_offset = r->get_size(p_abort); try { //tag_processor::read_id3v2(r, p_info, p_abort); tag_processor::read_id3v2_trailing(r, p_info, p_abort); } catch(exception_io_data) { /*file had no tags */ } r->seek(current_pos, p_abort); info_set_dts_info(p_info, data); } void decode_initialize(unsigned p_flags, abort_callback &p_abort) { reset_ffmpeg(data, p_abort); dynamic_interval = srate / 4; dynamic_last_decoded = 0; dynamic_decoded_this_interval = 0; dynamic_blocks_per_interval = 0; } bool decode_run(audio_chunk &chunk, abort_callback &p_abort) { if (data->endOfAudio || data->endOfStream) return false; bool ret; do { ret = decode_ffmpeg(data, chunk, p_abort); if (ret) { dynamic_last_decoded = chunk.get_sample_count(); dynamic_decoded_this_interval += dynamic_last_decoded; if (!dynamic_blocks_per_interval) { dynamic_blocks_per_interval = (dynamic_interval + (dynamic_last_decoded - 1)) / dynamic_last_decoded; dynamic_bitrates.set_count(dynamic_blocks_per_interval); dynamic_bitrates.fill_null(); dynamic_current_bitrate = 0; dynamic_bitrate_total = 0; dynamic_bitrate_count = 0; } dynamic_bitrate_total -= dynamic_bitrates[dynamic_current_bitrate]; dynamic_bitrates[dynamic_current_bitrate] = data->bitrate; dynamic_bitrate_total += data->bitrate; dynamic_current_bitrate = (dynamic_current_bitrate + 1) % dynamic_blocks_per_interval; if (dynamic_bitrate_count < dynamic_blocks_per_interval) ++dynamic_bitrate_count; if (data->samplesToDiscard > 0) { int last_decoded = chunk.get_sample_count(); if (data->samplesToDiscard >= last_decoded) { data->samplesToDiscard -= last_decoded; continue; } else { last_decoded -= data->samplesToDiscard; memmove(chunk.get_data(), chunk.get_data() + data->samplesToDiscard * data->channels, last_decoded * data->channels * sizeof(audio_sample)); chunk.set_sample_count(last_decoded); data->samplesToDiscard = 0; } } } } while (ret && data->samplesToDiscard > 0); return ret; } void decode_seek(double newpos, abort_callback &p_abort) { r->ensure_seekable(); //throw exceptions if someone called decode_seek() despite of our input having reported itself as nonseekable. seek_ffmpeg(data, newpos, p_abort); } void retag(const file_info &p_info, abort_callback &p_abort) { if (g_cfg_tag.get_static_instance().get_state() == false) throw exception_tagging_unsupported(); // exit out when tags are not wanted r->ensure_seekable(); const char *tagtype = p_info.info_get("tagtype"); if (tagtype == 0) tagtype = ""; bool id3v1=false, id3v2=false, apev2=false; parse_tagtype_internal(tagtype, id3v1, id3v2, apev2); if (!id3v2 && !apev2) apev2 = true; tag_processor::write_multi(r, p_info, p_abort, id3v1, id3v2, apev2); tag_offset = 0; try { file_info_impl t; tag_processor::read_trailing_ex(r, t, tag_offset, p_abort); } catch (exception_io_data) { /*file had no trailing tags */ } if (tag_offset == 0) tag_offset = r->get_size(p_abort); header_pos = 0; tag_processor::skip_id3v2(r, header_pos, p_abort); __int64 t = skip_wav_header(r, p_abort); if (t > 0) iswav = true; else iswav = false; header_pos += t; if (data) { data->start = header_pos; // assume data->size doesn't need adjusting, stream should still be the same data->offset = data->logical_offset + data->start; } } void remove_tags(abort_callback & p_abort) { if (g_cfg_tag.get_static_instance().get_state() == false) throw exception_tagging_unsupported(); tag_processor::remove_id3v2(r, p_abort); tag_processor::remove_trailing(r, p_abort); tag_offset = 0; header_pos = 0; __int64 t = skip_wav_header(r, p_abort); if (t > 0) iswav = true; else iswav = false; header_pos += t; if (data) { data->start = header_pos; // assume data->size doesn't need adjusting, stream should still be the same data->offset = data->logical_offset + data->start; } } static GUID g_get_guid() { static const GUID guid = { 0xe20f55a1, 0xb219, 0x486e,{ 0xba, 0xd3, 0x8b, 0xaa, 0xec, 0xf4, 0x2e, 0x56 } }; return guid; } static const char * g_get_name() { return "DTS decoder"; } }; static input_cuesheet_factory_t g_input_dts_factory; #include "../patrons.h" class version_dts : public componentversion { public: virtual void get_file_name(pfc::string_base & out) { out = core_api::get_my_file_name(); } virtual void get_component_name(pfc::string_base & out) { out = "DTS decoder"; } virtual void get_component_version(pfc::string_base & out) { out = FD_VERSION; } virtual void get_about_message(pfc::string_base & out) { out = "DTS decoding powered by FFmpeg.\n"; out += "\n"; out += "Using FFmpeg version: "; out += av_version_info(); out += "\n"; out += "\n"; out += u8"foobar2000 component by Janne Hyvärinen and Christopher Snowhill.\n"; out += "Licensed under GNU GPL.\n\n"; out += "https://www.patreon.com/kode54\n\n"; out += MY_PATRONS; } }; static service_factory_single_t g_componentversion_dts_factory; DECLARE_FILE_TYPE("DTS files", "*.DTS;*.DTSWAV;*.DTSHD;*.DTSMA"); VALIDATE_COMPONENT_FILENAME("foo_input_dts.dll");