mirror of
https://github.com/WinampDesktop/winamp.git
synced 2024-09-24 15:54:12 +00:00
720 lines
20 KiB
C++
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;
|
||
|
}
|