winamp/Src/Plugins/Input/in_mp3/DecodeThread.cpp

811 lines
19 KiB
C++
Raw Permalink Normal View History

2024-09-24 12:54:57 +00:00
#include "DecodeThread.h"
#include "giofile.h"
#include "main.h"
#include "pdtimer.h"
#include "mpegutil.h"
#include "../Winamp/wa_ipc.h"
#include "config.h"
#include <shlwapi.h>
#include "adts.h"
#include "adts_vlb.h"
#include <foundation/error.h>
// {19450308-90D7-4E45-8A9D-DC71E67123E2}
static const GUID adts_aac_guid =
{ 0x19450308, 0x90d7, 0x4e45, { 0x8a, 0x9d, 0xdc, 0x71, 0xe6, 0x71, 0x23, 0xe2 } };
// {4192FE3F-E843-445c-8D62-51BE5EE5E68C}
static const GUID adts_mp2_guid =
{ 0x4192fe3f, 0xe843, 0x445c, { 0x8d, 0x62, 0x51, 0xbe, 0x5e, 0xe5, 0xe6, 0x8c } };
extern int m_is_stream;
extern bool m_is_stream_seekable;
// post this to the main window at end of file (after playback as stopped)
#define WM_WA_MPEG_EOF WM_USER+2
/* public data */
int last_decode_pos_ms;
int decode_pos_ms; // current decoding position, in milliseconds.
volatile int seek_needed; // if != -1, it is the point that the decode
// thread should seek to, in ms.
int g_ds;
size_t g_bits;
int g_sndopened;
int g_bufferstat;
int g_length = -1000;
int g_vis_enabled;
volatile int g_closeaudio = 0;
CGioFile *g_playing_file=0;
/* private data */
static size_t g_samplebuf_used;
static int need_prebuffer;
static int g_srate, g_nch, g_br_add, g_br_div, g_avg_vbr_br;
int g_br;
class EndCutter
{
public:
EndCutter() : buffer(0), cutSize(0), filledSize(0), preCutSize(0), preCut(0), decoderDelay(0)
{}
~EndCutter()
{
free(buffer);
}
void SetEndSize(int postSize)
{
postSize -= decoderDelay;
if (postSize < 0)
postSize = 0;
else if (postSize)
{
free(buffer);
buffer = (char *)calloc(postSize, sizeof(char));
cutSize = postSize;
}
}
void SetSize(int decoderDelaySize, int preSize, int postSize)
{
decoderDelay = decoderDelaySize;
SetEndSize(postSize);
preCutSize = preSize;
preCut = preCutSize + decoderDelay;
}
void Flush(int time_in_ms)
{
if (time_in_ms == 0) // TODO: calculate actual delay if we seek within the encoder delay area
preCut = preCutSize; // reset precut size if we seek to the start
filledSize = 0;
mod.outMod->Flush(time_in_ms);
}
void Write(char *out, int outSize)
{
if (!out && (!outSize))
{
mod.outMod->Write(0, 0);
return ;
}
// cut pre samples, if necessary
int pre = min(preCut, outSize);
out += pre;
outSize -= pre;
preCut -= pre;
if (!outSize)
return ;
int remainingFill = cutSize - filledSize;
int fillWrite = min(outSize - remainingFill, filledSize); // only write fill buffer if we've got enough left to fill it up
if (fillWrite > 0)
{
mod.outMod->Write((char *)buffer, fillWrite);
if (cutSize - fillWrite)
memmove(buffer, buffer + fillWrite, cutSize - fillWrite);
filledSize -= fillWrite;
}
remainingFill = cutSize - filledSize;
int outWrite = max(0, outSize - remainingFill);
if (outWrite)
mod.outMod->Write((char *)out, outWrite);
out += outWrite;
outSize -= outWrite;
if (outSize)
{
memcpy(buffer + filledSize, out, outSize);
filledSize += outSize;
}
}
char *buffer;
int cutSize;
int filledSize;
int preCut, preCutSize, decoderDelay;
};
class DecodeLoop
{
public:
DecodeLoop() : decoder(0)
{
isAac = 0;
isEAAC = 0;
last_bpos = -1;
need_synclight = true;
done = 0;
br = 0;
g_framesize = 0;
maxlatency = 0;
sampleFrameSize = 0;
memset(&g_samplebuf, 0, sizeof(g_samplebuf));
}
~DecodeLoop()
{
if (decoder)
{
decoder->Close();
decoder->Release();
}
decoder=0;
}
DWORD Loop();
DWORD OpenDecoder();
void Seek(int seekPosition);
void PreBuffer();
void Decode();
void Viz();
void CalculateCodecDelay();
DWORD OpenOutput(int numChannels, int sampleRate, int bitsPerSample);
void SetupStream();
BYTE g_samplebuf[6*3*2*2*1152];
int g_framesize;
int isAac;
int isEAAC;
CGioFile file;
int maxlatency;
int last_bpos;
bool need_synclight;
int done; // set to TRUE if decoding has finished, 2 if all has been written
size_t br;
EndCutter endCutter;
int sampleFrameSize;
adts *decoder;
};
static int CalcPreBuffer(int buffer_setting, int bitrate)
{
if (bitrate < 8)
bitrate = 8;
else if (bitrate > 320)
bitrate = 320;
int prebuffer = (buffer_setting * bitrate) / 128;
if (prebuffer > 100)
prebuffer=100;
return prebuffer;
}
void DecodeLoop::SetupStream()
{
char buf[1024] = {0};
int len;
m_is_stream = file.IsStream();
//Wait until we have data...
while (!killDecodeThread && file.Peek(buf, 1024, &len) == NErr_Success && !len)
Sleep(50);
m_is_stream_seekable = file.IsStreamSeekable();
char *content_type = file.m_content_type;
if (content_type)
{
if (!_strnicmp(content_type, "misc/ultravox", 13))
{
switch (file.uvox_last_message)
{
case 0x8001:
case 0x8003:
isEAAC = 1;
isAac = 1;
break;
case 0x8000:
isAac = 1;
break;
}
}
else if (!_strnicmp(content_type, "audio/aac", 9))
{
isEAAC = 1;
isAac = 1;
}
else if (!_strnicmp(content_type, "audio/aacp", 10))
{
isEAAC = 1;
isAac = 1;
}
else if (!_strnicmp(content_type, "audio/apl", 10))
{
isEAAC = 1;
isAac = 1;
}
}
// todo: poll until connected to see if we get aac uvox frames or a content-type:aac header
}
DWORD DecodeLoop::OpenOutput(int numChannels, int sampleRate, int bitsPerSample)
{
maxlatency = mod.outMod->Open(sampleRate, numChannels, bitsPerSample, -1, -1);
// maxlatency is the maxium latency between a outMod->Write() call and
// when you hear those samples. In ms. Used primarily by the visualization
// system.
if (maxlatency < 0) // error opening device
{
PostMessage(mod.hMainWindow, WM_COMMAND, 40047, 0);
return 0;
}
g_sndopened = 1;
if (maxlatency == 0 && file.IsStream() == 2) // can't use with disk writer
{
if (!killDecodeThread)
{
EnterCriticalSection(&g_lfnscs);
WASABI_API_LNGSTRING_BUF(IDS_CANNOT_WRITE_STREAMS_TO_DISK,lastfn_status,256);
LeaveCriticalSection(&g_lfnscs);
PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
}
if (!killDecodeThread) Sleep(200);
if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
g_bufferstat = 0;
g_closeaudio = 1;
return 0;
}
if (paused) mod.outMod->Pause(1);
// set the output plug-ins default volume.
// volume is 0-255, -666 is a token for
// current volume.
mod.outMod->SetVolume(-666);
return 1;
}
void DecodeLoop::CalculateCodecDelay()
{
int decoderDelaySamples = (int)decoder->GetDecoderDelay();
endCutter.SetSize(decoderDelaySamples*sampleFrameSize,
file.prepad*sampleFrameSize,
file.postpad*sampleFrameSize);
}
void DecodeLoop::Viz()
{
if (!config_fastvis || (decoder->GetLayer() != 3 || g_ds))
{
int vis_waveNch;
int vis_specNch;
int csa = mod.SAGetMode();
int is_vis_running = mod.VSAGetMode(&vis_specNch, &vis_waveNch);
if (csa || is_vis_running)
{
int l = 576 * sampleFrameSize;
int ti = decode_pos_ms;
{
if (g_ds == 2)
{
memcpy(g_samplebuf + g_samplebuf_used, g_samplebuf, g_samplebuf_used);
}
size_t pos = 0;
while (pos < g_samplebuf_used)
{
int a, b;
if (mod.SAGetMode()) mod.SAAddPCMData((char *)g_samplebuf + pos, g_nch, (int)g_bits, ti);
if (mod.VSAGetMode(&a, &b)) mod.VSAAddPCMData((char *)g_samplebuf + pos, g_nch, (int)g_bits, ti);
ti += ((l / sampleFrameSize * 1000) / g_srate);
pos += l >> g_ds;
}
}
}
}
else
{
int l = (576 * (int)g_bits * g_nch);
int ti = decode_pos_ms;
size_t pos = 0;
int x = 0;
while (pos < g_samplebuf_used)
{
do_layer3_vis((short*)(g_samplebuf + pos), &g_vis_table[x++][0][0][0], g_nch, ti);
ti += (l / g_nch / 2 * 1000) / g_srate;
pos += l;
}
}
}
void DecodeLoop::Decode()
{
while (g_samplebuf_used < (size_t)g_framesize && !killDecodeThread && seek_needed == -1)
{
size_t newl = 0;
size_t br=0;
size_t endCut=0;
int res = decoder->Decode(&file, g_samplebuf + g_samplebuf_used, sizeof(g_samplebuf) / 2 - g_samplebuf_used, &newl, &br, &endCut);
if (config_gapless && endCut)
endCutter.SetEndSize((int)endCut* sampleFrameSize);
// we're not using switch here because we sometimes need to break out of the while loop
if (res == adts::SUCCESS)
{
if (!file.m_vbr_frames)
{
if (br) {
bool do_real_br=false;
if (!(config_miscopts&2) && br != decoder->GetCurrentBitrate())
{
do_real_br=true;
}
int r = (int)br;
g_br_add += r;
g_br_div++;
r = (g_br_add + g_br_div / 2) / g_br_div;
if (g_br != r)
{
need_synclight = false;
g_br = r;
if (!file.m_vbr_frames && file.IsSeekable()) g_length = MulDiv(file.GetContentLength(), 8, g_br);
if (!do_real_br)
mod.SetInfo(g_br, -1, -1, 1);
}
if (do_real_br)
mod.SetInfo((int)br, -1, -1, 1);
}
}
else
{
if (br) {
int r;
if (!(config_miscopts&2) || !g_avg_vbr_br)
r = (int)br;
else r = g_avg_vbr_br;
if (g_br != r)
{
need_synclight = false;
g_br = r;
mod.SetInfo(g_br, -1, -1, 1);
}
}
}
if (need_synclight)
{
need_synclight = false;
mod.SetInfo(-1, -1, -1, 1);
}
g_samplebuf_used += newl;
}
else if (res == adts::ENDOFFILE)
{
done = 1;
break;
}
else if (res == adts::NEEDMOREDATA)
{
if (file.IsStream() && !need_synclight)
{
need_synclight = true; mod.SetInfo(-1, -1, -1, 0);
}
if (file.IsStream() && !mod.outMod->IsPlaying())
{
need_prebuffer = CalcPreBuffer(config_http_prebuffer_underrun, (int)br);
}
break;
}
else
{
if (!need_synclight) mod.SetInfo(-1, -1, -1, 0);
need_synclight = true;
break;
}
}
}
void DecodeLoop::PreBuffer()
{
int p = file.RunStream();
int pa = file.PercentAvailable();
if (pa >= need_prebuffer || p == 2)
{
EnterCriticalSection(&g_lfnscs);
lastfn_status[0] = 0;
LeaveCriticalSection(&g_lfnscs);
PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
need_prebuffer = 0;
g_bufferstat = 0;
last_bpos = -1;
}
else
{
int bpos = pa * 100 / need_prebuffer;
if (!g_bufferstat) g_bufferstat = decode_pos_ms;
if (bpos != last_bpos)
{
last_bpos = bpos;
EnterCriticalSection(&g_lfnscs);
if (stricmp(lastfn_status, "stream temporarily interrupted"))
{
char langbuf[512] = {0};
wsprintfA(lastfn_status, WASABI_API_LNGSTRING_BUF(IDS_BUFFER_X,langbuf,512), bpos);
}
LeaveCriticalSection(&g_lfnscs);
int csa = mod.SAGetMode();
char tempdata[75*2] = {0, };
int x;
if (csa&1)
{
for (x = 0; x < bpos*75 / 100; x ++)
{
tempdata[x] = x * 16 / 75;
}
}
if (csa&2)
{
int offs = (csa & 1) ? 75 : 0;
x = 0;
while (x < bpos*75 / 100)
{
tempdata[offs + x++] = -6 + x * 14 / 75;
}
while (x < 75)
{
tempdata[offs + x++] = 0;
}
}
if (csa == 4)
{
tempdata[0] = tempdata[1] = (bpos * 127 / 100);
}
if (csa) mod.SAAdd(tempdata, ++g_bufferstat, (csa == 3) ? 0x80000003 : csa);
PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
}
}
}
void DecodeLoop::Seek(int seekPosition)
{
if (done == 3)
return;
done=0;
int br = (int)decoder->GetCurrentBitrate();
need_prebuffer = CalcPreBuffer(config_http_prebuffer_underrun, br);
if (need_prebuffer < 1) need_prebuffer = 5;
last_decode_pos_ms = decode_pos_ms = seekPosition;
seek_needed = -1;
endCutter.Flush(decode_pos_ms);
decoder->Flush(&file);
done = 0;
g_samplebuf_used = 0;
int r = g_br;
if (g_br_div) r = (g_br_add + g_br_div / 2) / g_br_div;
file.Seek(decode_pos_ms, r);
// need_prebuffer=config_http_prebuffer/8;
// g_br_add=g_br_div=0;
}
DWORD DecodeLoop::OpenDecoder()
{
mod.UsesOutputPlug &= ~8;
if (isAac)
{
if (isEAAC)
{
waServiceFactory *factory = mod.service->service_getServiceByGuid(adts_aac_guid);
if (factory)
decoder = (adts *)factory->getInterface();
mod.UsesOutputPlug|=8;
}
if (!decoder)
{
decoder = new ADTS_VLB;
mod.UsesOutputPlug &= ~8;
}
}
else
{
waServiceFactory *factory = mod.service->service_getServiceByGuid(adts_mp2_guid);
if (factory)
decoder = (adts *)factory->getInterface();
mod.UsesOutputPlug|=8;
}
if (decoder) {
decoder->SetDecoderHooks(mp3GiveVisData, mp2Equalize, mp3Equalize);
}
if (decoder
&& decoder->Initialize(AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"mono", false),
config_downmix == 2,
AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"surround", true),
(int)AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"bits", 16), true, false,
(config_miscopts&1)/*crc*/) == adts::SUCCESS
&& decoder->Open(&file))
{
// sync to stream
while (1)
{
switch (decoder->Sync(&file, g_samplebuf, sizeof(g_samplebuf), &g_samplebuf_used, &br))
{
case adts::SUCCESS:
return 1;
case adts::FAILURE:
case adts::ENDOFFILE:
if (!killDecodeThread)
{
if (!lastfn_status_err)
{
EnterCriticalSection(&g_lfnscs);
WASABI_API_LNGSTRING_BUF(IDS_ERROR_SYNCING_TO_STREAM,lastfn_status,256);
LeaveCriticalSection(&g_lfnscs);
PostMessage(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE);
}
}
if (!killDecodeThread) Sleep(200);
if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
return 0;
case adts::NEEDMOREDATA:
if (!killDecodeThread && file.IsStream()) Sleep(25);
if (killDecodeThread) return 0;
}
}
}
return 0;
}
DWORD DecodeLoop::Loop()
{
last_decode_pos_ms = 0;
if (file.Open(lastfn, config_max_bufsize_k) != NErr_Success)
{
if (!killDecodeThread) Sleep(200);
if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
return 0;
}
if (file.IsSeekable()) mod.is_seekable = 1;
wchar_t *ext = PathFindExtension(lastfn);
if (!_wcsicmp(ext, L".aac")
|| !_wcsicmp(ext, L".vlb")
|| !_wcsicmp(ext, L".apl"))
{
if (file.IsStream())
SetupStream();
else
{
isAac = 1;
if (!_wcsicmp(ext, L".aac") || !_wcsicmp(ext, L".apl")) isEAAC = 1;
}
}
else if (file.IsStream())
SetupStream();
if (OpenDecoder() == 0)
return 0;
EnterCriticalSection(&streamInfoLock);
g_playing_file = &file;
if (file.uvox_3901)
{
PostMessage(mod.hMainWindow, WM_WA_IPC, (WPARAM) "0x3901", IPC_METADATA_CHANGED);
PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
}
LeaveCriticalSection(&streamInfoLock);
EnterCriticalSection(&g_lfnscs);
lastfn_status[0] = 0;
LeaveCriticalSection(&g_lfnscs);
lastfn_data_ready = 1;
// TODO? if (decoder != &aacp) // hack because aac+ bitrate isn't accurate at this point
br = decoder->GetCurrentBitrate();
need_prebuffer = CalcPreBuffer(config_http_prebuffer, (int)br);
if (((!(config_eqmode&4) && decoder->GetLayer() == 3) ||
((config_eqmode&8) && decoder->GetLayer() < 3)))
{
mod.UsesOutputPlug |= 2;
}
else
mod.UsesOutputPlug &= ~2;
decoder->CalculateFrameSize(&g_framesize);
decoder->GetOutputParameters(&g_bits, &g_nch, &g_srate);
if (!killDecodeThread && file.IsStream() == 1)
{
DWORD_PTR dw;
if (!killDecodeThread) SendMessageTimeout(mod.hMainWindow, WM_USER, 0, IPC_UPDTITLE, SMTO_BLOCK, 100, &dw);
if (!killDecodeThread) SendMessageTimeout(mod.hMainWindow, WM_TIMER, 38, 0, SMTO_BLOCK, 100, &dw);
}
sampleFrameSize = g_nch * ((int)g_bits/8);
if (config_gapless)
CalculateCodecDelay();
if (OpenOutput(g_nch, g_srate, (int)g_bits) == 0)
return 0;
/* ----- send info to winamp and vis: bitrate, etc ----- */
g_br = (int)decoder->GetCurrentBitrate();
g_br_add = g_br;
g_br_div = 1;
g_avg_vbr_br = file.GetAvgVBRBitrate();
mod.SetInfo(g_br, g_srate / 1000, g_nch, 0);
// initialize visualization stuff
mod.SAVSAInit((maxlatency << g_ds), g_srate);
mod.VSASetInfo(g_srate, g_nch);
/* ----- end send info to winamp and vis ----- */
if (file.IsSeekable() && g_br)
{
mod.is_seekable = 1;
if (!file.m_vbr_frames) g_length = MulDiv(file.GetContentLength(), 8, g_br);
else g_length = file.m_vbr_ms;
}
if (file.IsStream())
{
if (need_prebuffer < config_http_prebuffer / 2)
need_prebuffer = config_http_prebuffer / 2;
}
while (!killDecodeThread)
{
if (seek_needed != -1)
Seek(seek_needed);
if (need_prebuffer && file.IsStream() && maxlatency && !file.EndOf())
PreBuffer();
int needsleep = 1;
if (done == 2) // done was set to TRUE during decoding, signaling eof
{
mod.outMod->CanWrite(); // some output drivers need CanWrite
// to be called on a regular basis.
if (!mod.outMod->IsPlaying())
{
// we're done playing, so tell Winamp and quit the thread.
if (!killDecodeThread) PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
done=3;
break;
}
}
else
{
int fs = (g_framesize * ((mod.dsp_isactive() == 1) ? 2 : 1));
// TODO: we should really support partial writes, there's no gaurantee that CanWrite() will EVER get big enough
if (mod.outMod->CanWrite() >= fs && (!need_prebuffer || !file.IsStream() || !maxlatency))
// CanWrite() returns the number of bytes you can write, so we check that
// to the block size. the reason we multiply the block size by two if
// mod.dsp_isactive() is that DSP plug-ins can change it by up to a
// factor of two (for tempo adjustment).
{
int p = mod.SAGetMode();
g_vis_enabled = ((p & 1) || p == 4);
if (!g_vis_enabled)
{
int s, a;
mod.VSAGetMode(&s, &a);
if (s) g_vis_enabled = 1;
}
Decode();
if ((g_samplebuf_used >= (size_t)g_framesize || (done && g_samplebuf_used > 0)) && seek_needed == -1)
{
// adjust decode position variable
if (file.isSeekReset())
last_decode_pos_ms = decode_pos_ms = 0;
else
decode_pos_ms += ((int)g_samplebuf_used / sampleFrameSize * 1000) / g_srate;
// if we have a DSP plug-in, then call it on our samples
if (mod.dsp_isactive())
{
g_samplebuf_used = mod.dsp_dosamples((short *)g_samplebuf, (int)g_samplebuf_used / sampleFrameSize, (int)g_bits, g_nch, g_srate) * sampleFrameSize;
}
Viz();
endCutter.Write((char *)g_samplebuf, (int)g_samplebuf_used);
g_samplebuf_used = 0;
needsleep = 0;
//memcpy(g_samplebuf,g_samplebuf+r,g_samplebuf_used);
}
if (done)
{
endCutter.Write(0, 0);
done = 2;
}
}
}
if (decode_pos_ms > last_decode_pos_ms + 1000)
{
last_decode_pos_ms = decode_pos_ms;
}
if (needsleep) Sleep(10);
// if we can't write data, wait a little bit. Otherwise, continue
// through the loop writing more data (without sleeping)
}
/* ---- change some globals to let everyone know we're done */
EnterCriticalSection(&g_lfnscs);
lastfn_status[0] = 0;
LeaveCriticalSection(&g_lfnscs);
g_bufferstat = 0;
g_closeaudio = 1;
/* ---- */
return 0;
}
DWORD WINAPI DecodeThread(LPVOID b)
{
DecodeLoop loop;
DWORD ret = loop.Loop();
EnterCriticalSection(&streamInfoLock);
g_playing_file = 0;
LeaveCriticalSection(&streamInfoLock);
return ret;
}