/* ** 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 #include #include #include #include #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(bufStatus*2.55f)); } static nu::AudioOutput 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; }