diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Input/in_flac/DecodeThread.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/Plugins/Input/in_flac/DecodeThread.cpp')
-rw-r--r-- | Src/Plugins/Input/in_flac/DecodeThread.cpp | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/Src/Plugins/Input/in_flac/DecodeThread.cpp b/Src/Plugins/Input/in_flac/DecodeThread.cpp new file mode 100644 index 00000000..38c7fcb6 --- /dev/null +++ b/Src/Plugins/Input/in_flac/DecodeThread.cpp @@ -0,0 +1,719 @@ +/* +** Copyright (C) 2007-2011 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +** Author: Ben Allison benski@winamp.com +** Created: March 1, 2007 +** +*/ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include "main.h" +#include <windows.h> +#include <math.h> +#include <assert.h> +#include <locale.h> +#include <FLAC/all.h> +#include "StreamFileWin32.h" +#include "../Winamp/wa_ipc.h" +#include "QuickBuf.h" +#include "api__in_flv.h" +#include "../nu/AudioOutput.h" +#include "../Agave/Language/api_language.h" +#include "FLACFileCallbacks.h" +#include "nx/nxpath.h" + +int m_force_seek = -1; // el hacko grande +const DWORD PAUSE_TIMEOUT = 100; // number of milliseconds to sleep for when paused + +static bool paused = false; + +// could probably move this inside client_data +int realbits; +int bps; +int samplerate; +int channels; +volatile int currentSongLength=-1000; +int averageBitrate; + +// if input plugins weren't single-instance only, we could put this in thread-local-storage +static FLAC__StreamDecoder *decoder = 0; +static uint64_t fileSize = 0; +static FLAC__uint64 decodePosition = 0; + +double gain = 1.0; +static double buffer_scale=1.0; + +class FLACWait +{ +public: + int WaitOrAbort(int time_in_ms) + { + if (WaitForSingleObject(killswitch, time_in_ms) == WAIT_OBJECT_0) + return 1; + return 0; + } +}; + + +volatile int bufferCount=0; +static void Buffering(int bufStatus, const wchar_t *displayString) +{ + if (bufStatus < 0 || bufStatus > 100) + return; + + char tempdata[75*2] = {0, }; + + int csa = plugin.SAGetMode(); + if (csa & 1) + { + for (int x = 0; x < bufStatus*75 / 100; x ++) + tempdata[x] = x * 16 / 75; + } + else if (csa&2) + { + int offs = (csa & 1) ? 75 : 0; + int x = 0; + while (x < bufStatus*75 / 100) + { + tempdata[offs + x++] = -6 + x * 14 / 75; + } + while (x < 75) + { + tempdata[offs + x++] = 0; + } + } + else if (csa == 4) + { + tempdata[0] = tempdata[1] = (bufStatus * 127 / 100); + } + if (csa) plugin.SAAdd(tempdata, ++bufferCount, (csa == 3) ? 0x80000003 : csa); + + /* + TODO + wchar_t temp[64] = {0}; + StringCchPrintf(temp, 64, L"%s: %d%%",displayString, bufStatus); + SetStatus(temp); + */ + //SetVideoStatusText(temp); // TODO: find a way to set the old status back + // videoOutput->notifyBufferState(static_cast<int>(bufStatus*2.55f)); +} + +static nu::AudioOutput<FLACWait> audio_output(&plugin); + +#pragma region Truncaters +inline static void clip(double &x, double a, double b) +{ + double x1 = fabs(x - a); + double x2 = fabs(x - b); + x = x1 + (a + b); + x -= x2; + x *= 0.5; +} + +static void InterleaveAndTruncate32(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + // TODO: this can be sped up significantly + FLAC__int32 *output = (FLAC__int32 *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + double appliedGain = gain * (double)buffer[c][b]; + // TODO: add dither + clip(appliedGain, -2147483648., 2147483647.); + *output = (FLAC__int32)appliedGain; + output++; + } + } +} + +static void InterleaveAndTruncate24(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + char *output = (char *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + double appliedGain = gain * (double)buffer[c][b]; + // TODO: add dither + clip(appliedGain, -8388608., 8388607.); + FLAC__int32 sample = (FLAC__int32)appliedGain; + + // little endian specific code + output[0] = (unsigned char)(sample); + output[1] = (unsigned char)(sample >> 8); + output[2] = (unsigned char)(sample >> 16); + output += 3; + } + } +} + +static void InterleaveAndTruncate16(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + short *output = (short *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + double appliedGain = gain * (double)buffer[c][b]; + // TODO: add dither + clip(appliedGain, -32768., 32767.); + FLAC__int32 sample = (FLAC__int32)appliedGain; + + *output = (short) sample ; + output ++; + } + } +} + +static void InterleaveAndTruncate8(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + unsigned char *output = (unsigned char *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + double appliedGain = gain * (double)buffer[c][b]; + // TODO: add dither + clip(appliedGain, -128., 127.); + FLAC__int32 sample = (FLAC__int32)appliedGain; + + *output = (unsigned char)(sample + 128); + output++; + } + } +} + +/* --- Versions without gain adjustment --- */ + +static void InterleaveAndTruncate32(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize) +{ + FLAC__int32 *output = (FLAC__int32 *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + *output = buffer[c][b]; + output++; + } + } +} + +static void InterleaveAndTruncate24(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize) +{ + char *output = (char *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + FLAC__int32 sample = buffer[c][b]; + + // little endian specific code + output[0] = (unsigned char)(sample); + output[1] = (unsigned char)(sample >> 8); + output[2] = (unsigned char)(sample >> 16); + output += 3; + } + } +} + +static void InterleaveAndTruncate16(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize) +{ + short *output = (short *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + *output = (short) buffer[c][b]; + output ++; + } + } +} + +static void InterleaveAndTruncate8(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize) +{ + unsigned char *output = (unsigned char *)_output; + + for (int b = 0;b < blocksize;b++) + { + for (int c = 0;c < channels;c++) + { + *output = (unsigned char)(buffer[c][b] + 128); + output++; + } + } +} + +void InterleaveAndTruncate(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain) +{ + if (gain == 1.0) // if it's EXACTLY 1.0, i.e. from a hardcoded value. not meant to be a "RG happens to be 1.0" check + { + switch(bps) + { + case 8: + InterleaveAndTruncate8(buffer, _output, bps, channels, blocksize); + break; + case 16: + InterleaveAndTruncate16(buffer, _output, bps, channels, blocksize); + break; + case 24: + InterleaveAndTruncate24(buffer, _output, bps, channels, blocksize); + break; + case 32: + InterleaveAndTruncate32(buffer, _output, bps, channels, blocksize); + break; + } + } + else // apply replay gain + { + switch(bps) + { + case 8: + InterleaveAndTruncate8(buffer, _output, bps, channels, blocksize, gain); + break; + case 16: + InterleaveAndTruncate16(buffer, _output, bps, channels, blocksize, gain); + break; + case 24: + InterleaveAndTruncate24(buffer, _output, bps, channels, blocksize, gain); + break; + case 32: + InterleaveAndTruncate32(buffer, _output, bps, channels, blocksize, gain); + break; + } + } +} +#pragma endregion + +QuickBuf output; +FLAC__uint64 lastoutputtime; +static FLAC__StreamDecoderWriteStatus OnAudio(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) +{ + // TODO: if frame bps/samplerate/channels doesn't equal our own, we'll probably have to close & re-open the audio output + // mux buffer into interleaved samples + FLAC__uint64 newPosition; + FLAC__stream_decoder_get_decode_position(decoder, &newPosition); + FLAC__uint64 delta = newPosition - decodePosition; + decodePosition = newPosition; + if (!config_average_bitrate) + plugin.SetInfo((int)(delta / (125.*frame->header.blocksize / samplerate)), -1, -1, -1); + else if (fixBitrate) + { + fixBitrate = false; + plugin.SetInfo(averageBitrate, -1, -1, -1); + } + + if (frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER) + lastoutputtime = 1000ULL*frame->header.number.sample_number / (FLAC__uint64)samplerate; + else + lastoutputtime = 0; + + int byteLength = (bps / 8) * channels * frame->header.blocksize; + output.Reserve(byteLength*2); + InterleaveAndTruncate(buffer, output, bps, channels, frame->header.blocksize, gain); + + audio_output.Write(output, byteLength); + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +static int GetBits(int incoming) +{ + int bits = AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"bits", 16); + if (bits > 16 && AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)) + return bits; + + return min(bits, incoming); +} + +static double GetGain(const FLAC__StreamMetadata *metadata) +{ + if (AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)) + { + if (metadata) + { + int gainPos = -1, peakPos = -1; + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0)) + { + case 0: // track + gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_GAIN"); + if (gainPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_GAIN"); + + peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_PEAK"); + if (peakPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_PEAK"); + + break; + case 1: + gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_GAIN"); + if (gainPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_GAIN"); + + peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_PEAK"); + if (peakPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_PEAK"); + break; + } + + double dB = 0, peak = 1.0; + _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale(); + if (gainPos >= 0) + { + const char *entry = (const char *)metadata->data.vorbis_comment.comments[gainPos].entry; + const char *value = strchr(entry, '='); // find the first equal + if (value++) + { + if (value[0] == '+') + dB = _atof_l(&value[1], C_locale); + else + dB = _atof_l(value, C_locale); + } + } + else + { + dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return pow(10.0, dB / 20.0); + } + + if (peakPos >= 0) + { + const char *entry = (const char *)metadata->data.vorbis_comment.comments[peakPos].entry; + const char *value = strchr(entry, '='); // find the first equal + if (value++) + { + peak = _atof_l(value, C_locale); + } + } + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_mode", 1)) + { + case 0: // apply gain + return pow(10.0, dB / 20.0); + break; + case 1: // apply gain, but don't clip + return min(pow(10.0, dB / 20.0), 1.0 / peak); + break; + case 2: // normalize + return 1.0 / peak; + break; + case 3: // prevent clipping + if (peak > 1.0) + return 1.0 / peak; + else + return 1.0; + } + } + else + { + double dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return pow(10.0, dB / 20.0); + } + } + + return 1.0; +} + +static void OnMetadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) +{ + switch (metadata->type) + { + case FLAC__METADATA_TYPE_STREAMINFO: + { + realbits = metadata->data.stream_info.bits_per_sample; + channels = metadata->data.stream_info.channels; + samplerate = metadata->data.stream_info.sample_rate; + bps = GetBits(metadata->data.stream_info.bits_per_sample); + gain = GetGain(0) * pow(2., (double)(bps - realbits)); + if (metadata->data.stream_info.total_samples) + { + currentSongLength = (int)((metadata->data.stream_info.total_samples*1000ULL)/(uint64_t)samplerate); + averageBitrate = (int)(fileSize / (125 * metadata->data.stream_info.total_samples / (__int64)samplerate)); + } + else + { + currentSongLength=-1000; + averageBitrate=0; + } + } + break; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + gain = GetGain(metadata) * pow(2., (double)(bps - realbits)); + break; + } +} + +static void OnError(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + //client_data = client_data; // dummy line so i can set a breakpoint +} + +void CALLBACK APCPause(ULONG_PTR data) +{ + paused = !!data; + plugin.outMod->Pause(!!paused); +} + +void CALLBACK APCSeek(ULONG_PTR data) +{ + // TODO: break out of end-of-file handling if necessary + buffer_scale=1.0; + int time_in_ms = (int)data; + lastoutputtime=time_in_ms; // cheating a bit here :) + audio_output.Flush(time_in_ms); + __int64 frames = Int32x32To64(time_in_ms, samplerate) / 1000; + FLAC__StreamDecoderState state = FLAC__stream_decoder_get_state(decoder); + plugin.outMod->Pause(0); + FLAC__stream_decoder_seek_absolute(decoder, frames); + plugin.outMod->Pause(paused); + if (FLAC__stream_decoder_get_state(decoder) == FLAC__STREAM_DECODER_SEEK_ERROR) + { + FLAC__stream_decoder_flush(decoder); + } +} + +static const double prebuffer_seconds = 0.5; +static bool DoBuffering(nx_file_t file) +{ + // TODO: check for disconnect, etc. + + // anything less than half-a-second and we'll rebuffer. but when we rebuffer, we'll get 2 seconds worth of audio + // also, every time we're forced to re-buffer, we'll double the buffer amount + + uint64_t required; + if (averageBitrate > 0) + { + required = (uint64_t)((double)averageBitrate * 1000.0 * buffer_scale * prebuffer_seconds / 8.0); + } + else /* no bitrate specified, let's assume 1000kbps */ + { + required = (uint64_t)(1000.0 * 1000.0 * buffer_scale * prebuffer_seconds / 8.0); + } + + uint64_t available; + if (NXFileProgressiveDownloaderAvailable(file, required, &available) == NErr_True) + return true; + + Buffering(0, 0); + bufferCount=lastoutputtime; + required *= 4; + buffer_scale *= 2.0; + + while (NXFileProgressiveDownloaderAvailable(file, required, &available) == NErr_False) + { + int percent = (int)((double)available * 100.0 / (double)required); + if (percent <= 0) + percent=1; + if (percent > 99) + percent=99; + + Buffering(percent, 0); + if (WaitForSingleObject(killswitch, 10) == WAIT_OBJECT_0) + { + return false; + } + } + Buffering(100, 0); + bufferCount=0; + return true; +} + +extern HANDLE threadStarted; +DWORD CALLBACK FLACThread(LPVOID param) +{ + buffer_scale=1.0; + bool streaming=false; + nx_file_t file; + FLACClientData state; + audio_output.Init(plugin.outMod); + paused=false; + SetEvent(threadStarted); + nx_uri_t filename = (nx_uri_t)param; + decodePosition = 0; + gain = 1.0; + bufferCount = 0; + decoder = FLAC__stream_decoder_new(); + if (decoder == 0) + { + if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + free(filename); + return 0; + } + + FLAC__stream_decoder_set_md5_checking(decoder, false); + FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); + + plugin.is_seekable = 1; + + + int ret; + + if (NXPathIsURL(filename) == NErr_True) + { + // Display a window to request to download + // + MessageBox(NULL, L"Cannot stream flac file, please download it", NULL, 0); + /* + char agent[256] = { 0 }; + snprintf(agent, 256, "User-Agent: %S/%S", "Winamp", "5.9.1"); + ret = NXFileOpenProgressiveDownloader(&file, filename, nx_file_FILE_read_binary, 0, agent); // TODO: calculate real user agent + */ + return NErr_Disabled; + } + else + { + ret = NXFileOpenFile(&file, filename, nx_file_FILE_read_binary); + } + + if (ret != NErr_Success) + { + FLAC__stream_decoder_delete(decoder); + if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + NXURIRelease(filename); + return 0; + } + + state.SetFile(file); + + NXFileLength(file, &fileSize); + + if (FLAC__stream_decoder_init_stream( + decoder, + FLAC_NXFile_Read, + FLAC_NXFile_Seek, + FLAC_NXFile_Tell, + FLAC_NXFile_Length, + FLAC_NXFile_EOF, + OnAudio, + OnMetadata, // or NULL + OnError, + &state + ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) + { + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + NXURIRelease(filename); + return 0; + } + + FLAC__stream_decoder_process_until_end_of_metadata(decoder); + + + + if (!audio_output.Open(0, channels, samplerate, bps)) + { + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0) + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + + NXURIRelease(filename); + return 0; + } + + plugin.SetInfo(averageBitrate, -1, -1, -1); + plugin.outMod->SetVolume(volume); + plugin.outMod->SetPan(pan); + + if (streaming && !DoBuffering(file)) + { + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + NXURIRelease(filename); + + return 0; + } + + if (m_force_seek != -1) + APCSeek((ULONG_PTR)m_force_seek); + m_force_seek = -1; + + HANDLE events[] = {killswitch}; + DWORD timeout = 0; + while (true) + { + DWORD result = WaitForMultipleObjectsEx(sizeof(events) / sizeof(*events), events, FALSE, timeout, TRUE); + switch (result) + { + case WAIT_OBJECT_0:// kill thread + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + NXURIRelease(filename); + return 0; + + case WAIT_TIMEOUT: + timeout = paused ? PAUSE_TIMEOUT : 0; + if (streaming && !DoBuffering(file)) + { + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + NXURIRelease(filename); + + return 0; + } + + if (!paused) + { + FLAC__bool decode_successful = FLAC__stream_decoder_process_single(decoder); + + FLAC__StreamDecoderState FLACstate = FLAC__stream_decoder_get_state(decoder); + + if (FLACstate == FLAC__STREAM_DECODER_END_OF_STREAM) + { + audio_output.Write(0, 0); + if (audio_output.WaitWhilePlaying()) + { + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + NXFileRelease(file); + NXURIRelease(filename); + return 0; + } + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + timeout = INFINITE; // sit and wait for killswitch + } + else if (!decode_successful) + { + // some other error - abort playback + // if we can find FLAC files with errors, we might be able to gracefully handle some errors + PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + timeout = INFINITE; // sit and wait for killswitch + } + } + break; + } + } + + return 0; +} |