winamp/Src/Plugins/DSP/sc_serv3/protocol_m4aClient.cpp

675 lines
20 KiB
C++
Raw Permalink Normal View History

2024-09-24 12:54:57 +00:00
#if 0
#ifdef _WIN32
#include <winsock2.h>
#endif
#include <stdio.h>
#include "protocol_m4aClient.h"
#include "ripList.h"
#include "stats.h"
#include "streamData.h"
#include "w3cLog.h"
#include "global.h"
#include "bandwidth.h"
#include "MP3Header.h"
#include "ADTSHeader.h"
#include "file/fileUtils.h"
#include "services/stdServiceImpl.h"
using namespace std;
using namespace uniString;
using namespace stringUtil;
#define DEBUG_LOG(x) { if (gOptions.m4aClientDebug()) DLOG((x)); }
#define AD_DEBUG_LOG(x) { if (gOptions.adMetricsDebug()) DLOG((x)); }
protocol_m4aClient::protocol_m4aClient(const socketOps::tSOCKET s, const streamData::streamID_t streamID,
const utf8 &hostName, const utf8 &addr, const u_short port,
const utf8 &userAgent, const utf8 &XFF, const utf8 &referer,
const bool headRequest) throw(exception)
: protocol_shoutcastClient(s, port, streamData::M4A, hostName,
(streamID <= 0 || streamID > INT_MAX ? DEFAULT_CLIENT_STREAM_ID : streamID),
stats::getNewClientId(), userAgent, referer, addr, XFF, headRequest),
m_state(&protocol_m4aClient::state_AttachToStream), m_nextState(0)
{
DEBUG_LOG(m_clientLogString + __FUNCTION__);
}
protocol_m4aClient::~protocol_m4aClient() throw()
{
cleanup("M4A", gOptions.m4aClientDebug());
}
//////////////////////////////////////////////////////////////////////////////
void protocol_m4aClient::timeSlice() throw(exception)
{
int ret = doTimeSlice(result);
if (ret == 1)
{
m_state = &protocol_m4aClient::state_Stream;
return;
}
else if (ret == 2)
{
return;
}
(this->*m_state)();
}
void protocol_m4aClient::state_Close() throw(exception)
{
DEBUG_LOG(m_clientLogString + __FUNCTION__);
m_result.done();
}
void protocol_m4aClient::state_SendText() throw(exception)
{
#if defined(_DEBUG) || defined(DEBUG)
DEBUG_LOG(m_clientLogString + __FUNCTION__);
#endif
if (sendText())
{
m_metaIntervalCounter = 0;
m_state = m_nextState;
}
}
// find the appropriate stream and try to attach to it
void protocol_m4aClient::state_AttachToStream() throw(exception)
{
DEBUG_LOG(m_clientLogString + __FUNCTION__);
m_streamData = streamData::accessStream(m_streamID);
if (!m_streamData)
{
if (processReject("M4A", bandWidth::CLIENT_M4A_SENT, MSG_ICY_HTTP401,
MSG_ICY_HTTP401_LEN, &read_bitrate, &dataType))
{
goto fall_through;
}
m_state = &protocol_m4aClient::state_SendText;
m_nextState = &protocol_m4aClient::state_Close;
}
else
{
fall_through:
const utf8 movedUrl = gOptions.stream_movedUrl(m_streamID);
if (movedUrl.empty())
{
const int add = processAdd("FLV", bandWidth::CLIENT_M4A_SENT,
MSG_ICY_HTTP401, MSG_ICY_HTTP401_LEN, movedUrl,
(m_streamData ? m_streamData->streamBackupServer() : ""));
if (add != 1)
{
m_state = &protocol_m4aClient::state_SendText;
m_nextState = &protocol_m4aClient::state_Close;
}
else
{
const bool isPodcast = (!m_streamData && (gOptions.getBackupLoop(m_streamID) == 1));
m_OKResponse = MSG_ICY_HTTP200 + "Content-Type:audio/mp4\r\n";
if (!m_streamData)
{
utf8 path = getStreamPath(m_streamID);
if (!path.empty() && path.find(utf8("/")) == 0)
{
path = path.substr(1);
}
if (isPodcast)
{
m_OKResponse += "Content-Disposition:attachment;filename=\"" + path + "\"\r\n"
"Content-Length:" + tos(m_backupFile.size()) + "\r\n";
}
}
if (gOptions.clacks())
{
m_OKResponse += "X-Clacks-Overhead:GNU Terry Pratchett\r\n";
}
m_OKResponse += "\r\n";
DEBUG_LOG(m_clientLogString + "Sending [" + eol() + stripWhitespace(m_OKResponse) + eol() + "]");
m_outBuffer = m_OKResponse.c_str();
bandWidth::updateAmount(bandWidth::CLIENT_M4A_SENT, (m_outBufferSize = (int)m_OKResponse.size()));
m_state = &protocol_m4aClient::state_SendText;
if (!m_headRequest)
{
m_nextState = &protocol_m4aClient::state_InitiateStream;
}
else
{
m_removeClientFromStats = false;
m_ignoreDisconnect = true;
m_nextState = &protocol_m4aClient::state_Close;
}
m_result.write();
m_result.timeoutSID(m_streamID);
m_result.run();
// when the client is added, we get back the unique id of the connection
// but we now check for being > 0 as we need to filter out some of the
// YP connections from being counted as valid clients for stats, etc
reportNewListener("M4A");
}
}
else
{
// if we get to here then we attempt to redirect the clients to the moved url
// which is useful if the stream has moved hosting or it has been deprecated.
streamMovedOrRejected("M4A", bandWidth::CLIENT_M4A_SENT, movedUrl, 2);
m_state = &protocol_m4aClient::state_SendText;
m_nextState = &protocol_m4aClient::state_Close;
}
}
}
void protocol_m4aClient::state_SendIntro() throw(exception)
{
state_SendIntroFile();
if (m_introFile.empty())
{
acquireIntroFile();
if (m_introFile.empty())
{
m_state = &protocol_m4aClient::state_Stream;
}
else
{
m_state = &protocol_m4aClient::state_SendIntroFile;
}
}
}
void protocol_m4aClient::state_InitiateStream() throw(exception)
{
DEBUG_LOG(m_clientLogString + __FUNCTION__);
resetReadPtr();
if (!m_streamData || (m_streamData && m_introFile.empty()))
{
// send intro file if we have it
acquireIntroFile();
m_state = (m_introFile.empty() ? &protocol_m4aClient::state_Stream : &protocol_m4aClient::state_SendIntroFile);
}
else
{
m_state = &protocol_m4aClient::state_SendIntro;
}
setW3CState();
m_result.run();
}
// handle state where we are sending intro files
void protocol_m4aClient::state_SendIntroFile() throw(exception)
{
DEBUG_LOG(m_clientLogString + __FUNCTION__);
resetCommon();
time_t cur_time;
const bool debug = gOptions.m4aClientDebug();
const int autoDumpTime = detectAutoDumpTimeout(cur_time, m_lastActivityTime, (m_clientLogString +
"Timeout waiting to send data"),
(!m_ignoreDisconnect && debug), m_streamID);
if (m_shortSend.empty() && (m_frameCount > calculateFrameLimit(cur_time)))
{
if (calculateDelay((autoDumpTime - (int)(cur_time - m_lastActivityTime))))
{
// if we're at the limit then we're going to need to sit and spin
// which will need to be a bit short of the required frame rate
// so that we've got a bit of leeway on scheduling delays, etc
return;
}
}
int amt = (int)(m_introFile.size() - m_introFileOffset);
if (amt == 0)
{
// we're done with the intro file
m_introFile.clear();
m_introFile.resize(0);
m_introFileOffset = 0;
m_lastActivityTime = ::time(NULL);
m_state = &protocol_m4aClient::state_Stream;
resetReadPtr();
m_result.run();
}
else if (amt > 0)
{
const bool debug = gOptions.m4aClientDebug();
checkListenerIsValid(debug);
if (!m_lastSentMetadata.empty())
{
logW3C();
m_lastSentMetadata.clear();
}
amt = min(amt, SEND_SIZE);
int len = (int)amt,
frames = 0; // this will be uvox frames as we pre-process earlier to ensure
// that the audio frames are frame-synced when this is created
bool advert = false;
doM4AFrameSync(m_streamData->streamUvoxDataType(), debug, m_introFileOffset,
(const char*)&m_introFile[0], cur_time, m_streamData->streamBitrate(),
m_streamData->streamSampleRate(), len, frames, advert);
int rval = doSend(debug, cur_time, autoDumpTime);
if (rval > 0)
{
bandWidth::updateAmount(bandWidth::CLIENT_M4A_SENT, rval);
m_bytesSentForCurrentTitle += rval;
m_totalBytesSent += rval;
m_lastActivityTime = ::time(NULL);
m_metaIntervalCounter += rval;
// we adjust by the pre-M4A size
m_introFileOffset += len;
}
updateFrameCount(frames, cur_time);
handleShortSend(autoDumpTime, cur_time, rval);
}
}
// handle state where we are sending backup files
void protocol_m4aClient::state_SendBackupFile() throw(exception)
{
DEBUG_LOG(m_clientLogString + __FUNCTION__);
resetCommon();
int amt = (int)(m_backupFile.size() - m_backupFileOffset);
if (streamData::isSourceConnected(m_streamID))
{
if (m_streamData)
{
m_streamData->releaseStream();
}
m_streamData = streamData::accessStream(m_streamID);
// we're done with the backup file
m_backupFile.clear();
m_backupFile.resize(0);
m_backupFileOffset = 0;
m_backupLoopTries = 0;
m_lastActivityTime = ::time(NULL);
m_state = &protocol_m4aClient::state_Stream;
resetReadPtr();
m_result.run();
}
else if (amt == 0)
{
const int backuploop = gOptions.getBackupLoop(m_streamID);
if (!backuploop || (m_backupLoopTries <= backuploop))
{
// we're done with the backup file. get more data
acquireBackupFile();
if (!m_backupFile.empty())
{
++m_backupLoopTries;
resetReadPtr();
m_lastActivityTime = ::time(NULL);
m_state = &protocol_m4aClient::state_Stream;
}
}
else if (backuploop && (m_backupLoopTries < backuploop))
{
m_backupFileOffset = 0;
m_backupFile.clear();
}
else
{
resetReadPtr();
m_backupFileOffset = 0;
m_lastActivityTime = ::time(NULL);
m_state = &protocol_m4aClient::state_Stream;
}
m_result.run();
}
else if (amt > 0)
{
const bool debug = gOptions.m4aClientDebug();
checkListenerIsValid(debug);
time_t cur_time;
const int autoDumpTime = detectAutoDumpTimeout(cur_time, m_lastActivityTime, (m_clientLogString +
"Timeout waiting to send data"),
(!m_ignoreDisconnect && debug), m_streamID);
if (m_shortSend.empty() && (m_frameCount > calculateFrameLimit(cur_time)))
{
if (calculateDelay((autoDumpTime - (int)(cur_time - m_lastActivityTime))))
{
// if we're at the limit then we're going to need to sit and spin
// which will need to be a bit short of the required frame rate
// so that we've got a bit of leeway on scheduling delays, etc
return;
}
}
if (!m_lastSentMetadata.empty())
{
logW3C();
m_lastSentMetadata.clear();
}
amt = min(amt, SEND_SIZE);
int len = (int)amt, backup_type = MP3_DATA,
frames = 0; // this will be uvox frames as we pre-process earlier to ensure
// that the audio frames are frame-synced when this is created
if (!m_streamData)
{
utf8 backupFile = gOptions.stream_backupFile(m_streamID);
if (!gOptions.read_stream_backupFile(m_streamID))
{
backupFile = gOptions.backupFile();
}
if (!backupFile.empty())
{
backup_type = ((backupFile.rfind((utf8)".aac") == utf8::npos) ? MP3_DATA : AACP_DATA);
}
}
bool advert = false;
doM4AFrameSync((m_streamData ? m_streamData->streamUvoxDataType() : backup_type),
debug, m_backupFileOffset, (const char*)&m_backupFile[0], cur_time,
(m_streamData ? m_streamData->streamBitrate() : 0),
(m_streamData ? m_streamData->streamSampleRate() : 0), len, frames, advert);
int rval = doSend(debug, cur_time, autoDumpTime);
if (rval > 0)
{
bandWidth::updateAmount(bandWidth::CLIENT_M4A_SENT, rval);
m_bytesSentForCurrentTitle += rval;
m_totalBytesSent += rval;
m_lastActivityTime = ::time(NULL);
m_metaIntervalCounter += rval;
// we adjust by the pre-M4A size
m_backupFileOffset += len;
}
updateFrameCount(frames, cur_time);
handleShortSend(autoDumpTime, cur_time, rval);
}
}
// handle state where we are sending advert content
void protocol_m4aClient::state_SendAdverts() throw(exception)
{
#if defined(_DEBUG) || defined(DEBUG)
DEBUG_LOG(m_clientLogString + __FUNCTION__);
#endif
resetCommon();
streamData::specialFileData *ad = m_adAccess.getAd(m_streamID, m_streamData->advertGroups, !!m_streamData->m_adTest);
int amt = (ad ? (int)(ad->m_sc1Buffer.size() - m_adAccess.offset) : 0);
while (true)
{
// DLOG ("amount remaining to be sent " + tos(amt));
if (amt > 0)
{
break;
}
if (m_adAccess.anotherAd(m_streamID, m_streamData->advertGroups, !!m_streamData->m_adTest))
{
ad = m_adAccess.getAd(m_streamID, m_streamData->advertGroups, !!m_streamData->m_adTest);
amt = (ad ? (int)(ad->m_sc1Buffer.size() - m_adAccess.offset) : 0);
continue;
}
// no more adverts
m_lastActivityTime = ::time(NULL);
m_state = &protocol_m4aClient::state_Stream;
m_adAccess.stopAdRun(m_streamID, m_streamData->advertGroups, !!m_streamData->m_adTest);
// go to the latest point in the ring buffer.
resetReadPtr();
m_result.run();
m_result.timeoutSID(m_streamID);
ILOG(m_clientLogString + "Transitioning back to stream [Agent: `" +
m_userAgent + "', UID: " + tos(m_unique) + ", GRID: " + tos(m_group) + "]");
return;
}
try
{
const bool debug = gOptions.m4aClientDebug();
checkListenerIsValid(debug);
time_t cur_time;
const int autoDumpTime = detectAutoDumpTimeout(cur_time, m_lastActivityTime, (m_clientLogString +
"Timeout waiting to send data"),
(!m_ignoreDisconnect && debug), m_streamID);
if (m_shortSend.empty() && (m_frameCount > calculateFrameLimit(cur_time)))
{
if (calculateDelay((autoDumpTime - (int)(cur_time - m_lastActivityTime))))
{
// if we're at the limit then we're going to need to sit and spin
// which will need to be a bit short of the required frame rate
// so that we've got a bit of leeway on scheduling delays, etc
return;
}
}
if (!m_lastSentMetadata.empty())
{
logW3C();
m_lastSentMetadata.clear();
}
amt = min(amt, SEND_SIZE);
int len = (int)amt,
frames = 0; // this will be uvox frames as we pre-process earlier to ensure
// that the audio frames are frame-synced when this is created
bool advert = false;
doM4AFrameSync(m_streamData->streamUvoxDataType(), (int)m_adAccess.offset,
debug, (const char*)&ad->m_sc1Buffer[0], cur_time,
m_streamData->streamBitrate(), m_streamData->streamSampleRate(),
len, frames, advert);
int rval = doSend(debug, cur_time, autoDumpTime);
if (rval > 0)
{
bandWidth::updateAmount(bandWidth::CLIENT_M4A_SENT, rval);
m_bytesSentForCurrentTitle += rval;
m_totalBytesSent += rval;
m_lastActivityTime = ::time(NULL);
m_metaIntervalCounter += rval;
// we adjust by the pre-M4A size
m_adAccess.offset += len;
m_nextState = &protocol_m4aClient::state_SendText;
}
updateFrameCount(frames, cur_time);
handleShortSend(autoDumpTime, cur_time, rval);
}
catch (std::runtime_error)
{
m_adAccess.stopAdRun(m_streamID, m_streamData->advertGroups, !!m_streamData->m_adTest);
throw;
}
}
void protocol_m4aClient::state_Stream() throw(exception)
{
#if defined(_DEBUG) || defined(DEBUG)
DEBUG_LOG(m_clientLogString + __FUNCTION__);
#endif
resetCommon();
const time_t cur_time = ::time(NULL);
const bool debug = gOptions.m4aClientDebug();
const int autoDumpTime = gOptions.getAutoDumpTime(m_streamID); // don't want this value to change during this call
if ((autoDumpTime > 0) && ((cur_time - m_lastActivityTime) >= autoDumpTime))
{
throwEx<runtime_error>((!m_ignoreDisconnect && debug ?
(m_clientLogString + "Timeout waiting to send data (" +
tos(cur_time) + " " + tos(m_lastActivityTime) + " [" +
tos(cur_time - m_lastActivityTime) + "] )") : (utf8)""));
}
if (m_shortSend.empty() && (m_frameCount > calculateFrameLimit(cur_time)))
{
if (calculateDelay((autoDumpTime - (int)(cur_time - m_lastActivityTime))))
{
// if we're at the limit then we're going to need to sit and spin
// which will need to be a bit short of the required frame rate
// so that we've got a bit of leeway on scheduling delays, etc
return;
}
}
const streamData::ringBuffer_t rb = (m_streamData ? m_streamData->getSc1RingBuffer() : streamData::ringBuffer_t());
streamData::ringBufferAccess_t amt = (m_streamData ? (rb.m_writePtr - m_readPtr) : 0);
if ((amt > 0) && (amt > rb.m_data.size()))
{
// the pointers are too far apart. Underrun
resetReadPtr(&amt);
}
std::vector<__uint8>& rem = getRemainder();
const streamData::ringBufferAccess_t offset = (m_readPtr & rb.m_ptrMask);
// clamp again so we don't read pass the end of the buffer
//
// if we've got more in remainder than what we're wanting
// to send then we'll prioritise the remainder data first
// before trying to acquire more new data to try to send.
amt = min(amt, min((rb.m_data.size() - offset), (streamData::ringBufferAccess_t)max(0, (SEND_SIZE - rem.size()))));
bool advert = false;
int remainder = 0, len = (int)amt,
frames = 0; // this will be uvox frames as we pre-process earlier to ensure
// that the audio frames are frame-synced when this is created
if ((len > 0) || !rem.empty() || !m_shortSend.empty())
{
const std::vector<__uint8>::const_iterator pos = rb.m_data.begin();
std::vector<__uint8>& tempBuf;
// if we had anything left over then now we
// need to copy it back into the buffer and
// adjust the max data amount to be read in
if (!rem.empty() && ((len + rem.size()) <= (BUF_SIZE * 4)))
{
tempBuf.insert(tempBuf.end(), rem.begin(), rem.end());
tempBuf.insert(tempBuf.end(), pos + offset, pos + (offset + len));
len += m_remainderSize;
}
else if (len > 0)
{
tempBuf.insert(tempBuf.end(), pos + offset, pos + (offset + len));
}
rem.clear();
remainder = doM4AFrameSync((m_streamData ? m_streamData->streamUvoxDataType() : MP3_DATA),
debug, 0, cur_time, (m_streamData ? m_streamData->streamBitrate() : 0),
(m_streamData ? m_streamData->streamSampleRate() : 0),
len, frames, advert, true);
}
// if no data then we need to just go to adverts
// otherwise we need to see what we've got and
// if we can, then transition to adverts or spin
// and wait for the current data to be sent and
// then we should be able to re-process into ads
if (m_output.empty())
{
// this will close the stream or go to backups as needed
if (handleNoData("M4A", remainder, amt, autoDumpTime, cur_time))
{
m_state = &protocol_m4aClient::state_SendBackupFile;
}
else
{
if (processAdvertTrigger(advert))
{
m_state = &protocol_m4aClient::state_SendAdverts;
return;
}
// at this point, we're in a bit of a weird state
// as we've not been able to generate / retrieve
// anything and if we don't slwo down things then
// we need to delay things to allow it to catchup
if (m_frameCount > calculateFrameLimit(cur_time))
{
// determine things based on the 'next' second when working out
// the differential as that's how long we want to wait to run
m_result.schedule((int)((__uint64)((cur_time + 1) * 1000) - m_result.m_currentTime));
}
else
{
// just go with an arbitrary value as we cannot
// be certain when we're going to get data next
// and we don't know the impact on playback etc
m_result.schedule(100);
}
m_result.timeout((autoDumpTime - (int)(cur_time - m_lastActivityTime)));
m_result.write();
}
}
else
{
processTitleW3C();
int rval = doSend(debug, cur_time, autoDumpTime);
if (rval > 0)
{
bandWidth::updateAmount(bandWidth::CLIENT_M4A_SENT, rval);
m_bytesSentForCurrentTitle += rval;
m_totalBytesSent += rval;
m_lastActivityTime = ::time(NULL);
m_metaIntervalCounter += rval;
if (processAdvertTrigger(advert))
{
m_state = &protocol_m4aClient::state_SendAdverts;
remainder = 0;
}
}
// as we're keeping a copy of the remainder / non-sent data
// we need to move the readptr on so that it doesn't cause
// the stream to effectively stick on the same point and in
// turn then lead to resetReadPtr() being called (skipping)
m_readPtr += amt;
updateFrameCount(frames, cur_time);
handleShortSend(autoDumpTime, cur_time, rval);
// we only keep the remainder if
// there's no advert to provide
m_remainderSize = remainder;
}
}
void protocol_m4aClient::return_403(void)
{
protocol_shoutcastClient::return_403();
m_state = &protocol_m4aClient::state_SendText;
m_nextState = &protocol_m4aClient::state_Close;
}
#endif