winamp/Src/Plugins/Input/in_flac/DecodeThread.cpp
2024-09-24 14:54:57 +02:00

720 lines
20 KiB
C++

/*
** 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;
}