Initial community commit

This commit is contained in:
Jef
2024-09-24 14:54:57 +02:00
parent 537bcbc862
commit 20d28e80a5
16810 changed files with 4640254 additions and 2 deletions

View File

@ -0,0 +1,238 @@
#include "AMFDispatch.h"
AMFDispatch::AMFDispatch(AMFMixedArray *array)
{
object=array;
if (object)
object->AddRef();
refCount=1;
}
AMFDispatch::~AMFDispatch()
{
if (object)
object->Release();
}
STDMETHODIMP AMFDispatch::QueryInterface(REFIID riid, PVOID *ppvObject)
{
if (!ppvObject)
return E_POINTER;
else if (IsEqualIID(riid, IID_IDispatch))
*ppvObject = (IDispatch *)this;
else if (IsEqualIID(riid, IID_IUnknown))
*ppvObject = this;
else
{
*ppvObject = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
ULONG AMFDispatch::AddRef(void)
{
return InterlockedIncrement((volatile LONG *)&refCount);
}
ULONG AMFDispatch::Release(void)
{
ULONG count = InterlockedDecrement((volatile LONG *)&refCount);
if (count == 0)
delete this;
return count;
}
enum
{
DISP_AMF_DEBUGPRINT,
DISP_AMF_MAX,
};
#define CHECK_ID(str, id) if (wcscmp(rgszNames[i], L##str) == 0) { rgdispid[i] = id; continue; }
HRESULT AMFDispatch::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
{
bool unknowns = false;
for (unsigned int i = 0;i != cNames;i++)
{
CHECK_ID("DebugPrint", DISP_AMF_DEBUGPRINT)
if (object)
{
//size_t index = object->array.getPosition(rgszNames[i]);
size_t index = 0;
for (auto it = object->array.begin(); it != object->array.end(); it++, index++)
{
if (wcscmp(it->first.c_str(), rgszNames[i]) == 0)
{
break;
}
}
if (index != object->array.size())
{
rgdispid[i] = (DISPID)index + DISP_AMF_MAX;
continue;
}
}
rgdispid[i] = DISPID_UNKNOWN;
unknowns = true;
}
if (unknowns)
return DISP_E_UNKNOWNNAME;
else
return S_OK;
}
HRESULT AMFDispatch::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
{
return E_NOTIMPL;
}
HRESULT AMFDispatch::GetTypeInfoCount(unsigned int FAR * pctinfo)
{
return E_NOTIMPL;
}
static void AMFType_To_Variant(AMFType *obj, VARIANT *pvarResult)
{
VariantInit(pvarResult);
switch(obj->type)
{
case AMFType::TYPE_DOUBLE: // double
{
AMFDouble *cast_obj = static_cast<AMFDouble *>(obj);
V_VT(pvarResult) = VT_R8;
V_R8(pvarResult) = cast_obj->val;
}
break;
case AMFType::TYPE_BOOL: // bool
{
AMFBoolean *cast_obj = static_cast<AMFBoolean *>(obj);
V_VT(pvarResult) = VT_BOOL;
V_BOOL(pvarResult) = cast_obj->boolean;
}
break;
case AMFType::TYPE_MOVIE: // movie (basically just a URL)
case AMFType::TYPE_STRING: // string
{
AMFString *cast_obj = static_cast<AMFString *>(obj);
V_VT(pvarResult) = VT_BSTR;
V_BSTR(pvarResult) = SysAllocString(cast_obj->str);
}
break;
case AMFType::TYPE_LONG_STRING: // string
{
AMFLongString *cast_obj = static_cast<AMFLongString *>(obj);
V_VT(pvarResult) = VT_BSTR;
V_BSTR(pvarResult) = SysAllocString(cast_obj->str);
}
break;
case AMFType::TYPE_MIXEDARRAY:
{
AMFMixedArray *cast_obj = static_cast<AMFMixedArray *>(obj);
V_VT(pvarResult) = VT_DISPATCH;
V_DISPATCH(pvarResult) = new AMFDispatch(cast_obj);
}
break;
case AMFType::TYPE_DATE:
{
AMFTime *cast_obj = static_cast<AMFTime *>(obj);
V_VT(pvarResult) = VT_DATE;
V_DATE(pvarResult) = cast_obj->val;
}
break;
case AMFType::TYPE_ARRAY:
{
AMFArray *cast_obj = static_cast<AMFArray *>(obj);
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = 0;
rgsabound[0].cElements = (ULONG)cast_obj->array.size();
SAFEARRAY *psa = SafeArrayCreate(VT_VARIANT, 1, rgsabound);
VARIANT **data;
SafeArrayAccessData(psa, (void **)&data);
for (size_t i=0;i!=cast_obj->array.size();i++)
{
AMFType_To_Variant(cast_obj->array[i], data[i]);
}
SafeArrayUnaccessData(psa);
V_VT(pvarResult) = VT_ARRAY;
V_ARRAY(pvarResult) = psa;
}
break;
}
}
HRESULT AMFDispatch::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
{
if (pvarResult)
VariantInit(pvarResult);
switch(dispid)
{
case DISP_AMF_DEBUGPRINT:
{
wchar_t debugstring[4096]=L"";
wchar_t *str = debugstring;
size_t len = 4096;
object->DebugPrint(1, str, len);
V_VT(pvarResult) = VT_BSTR;
V_BSTR(pvarResult) = SysAllocString(debugstring);
}
return S_OK;
}
size_t index = dispid - DISP_AMF_MAX;
if (index >= object->array.size())
return DISP_E_MEMBERNOTFOUND;
//AMFType *obj = object->array.at(index).second;
AMFType* obj = 0;
auto it = object->array.begin();
while (index--)
{
it++;
}
if (it != object->array.end())
{
obj = it->second;
}
if (!obj)
return S_OK;
switch(obj->type)
{
case AMFType::TYPE_DOUBLE:
case AMFType::TYPE_BOOL:
case AMFType::TYPE_STRING:
case AMFType::TYPE_MIXEDARRAY:
case AMFType::TYPE_ARRAY:
AMFType_To_Variant(obj, pvarResult);
return S_OK;
case AMFType::TYPE_OBJECT: // object
// TODO
return DISP_E_TYPEMISMATCH;
case AMFType::TYPE_NULL: // null
return S_OK;
case AMFType::TYPE_REFERENCE: // reference
return DISP_E_TYPEMISMATCH;
case AMFType::TYPE_TERMINATOR:
// TODO?
return DISP_E_TYPEMISMATCH;
case AMFType::TYPE_DATE: // date
return DISP_E_TYPEMISMATCH;
case AMFType::TYPE_LONG_STRING: // long string
return DISP_E_TYPEMISMATCH;
case AMFType::TYPE_XML: // XML
return DISP_E_TYPEMISMATCH;
default:
return DISP_E_TYPEMISMATCH;
}
return S_OK;
}

View File

@ -0,0 +1,28 @@
#pragma once
/*
Ben Allison
April 30, 2008
This class implements an IDispatch interface around an AMFObject
*/
#include <oaidl.h>
#include "AMFObject.h"
class AMFDispatch : public IDispatch
{
public:
AMFDispatch(AMFMixedArray *array);
~AMFDispatch();
STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
STDMETHOD_(ULONG, AddRef)(void);
STDMETHOD_(ULONG, Release)(void);
private:
// *** IDispatch Methods ***
STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
volatile ULONG refCount;
AMFMixedArray *object;
};

View File

@ -0,0 +1,319 @@
#include "AMFObject.h"
#include <strsafe.h>
void AMFMixedArray::DebugPrint(int spaces, wchar_t *&str, size_t &len)
{
AMFTypeList::iterator itr;
StringCchCopyEx(str, len, L"Mixed Array [\n", &str, &len, 0);
for (itr=array.begin();itr!=array.end();itr++)
{
for (int i=0;i<spaces;i++)
StringCchCopyEx(str, len, L" ", &str, &len,0);
if (itr->first.c_str() != 0 && itr->first.c_str()[0])
StringCchPrintfEx(str, len, &str, &len, 0, L"%s: ", itr->first.c_str());
if (itr->second)
itr->second->DebugPrint(spaces+1, str, len);
else
StringCchCopyEx(str, len, L"(null)\n", &str, &len, 0);
}
StringCchCopyEx(str, len, L"]\n", &str, &len, 0);
}
size_t AMFMixedArray::Read(uint8_t *data, size_t size)
{
size_t read = 0;
uint32_t maxIndex = FLV::Read32(data);
// TODO? array.reserve(maxIndex);
data += 4;
size -= 4;
read += 4;
while (size)
{
AMFString amfString;
size_t skip = amfString.Read(data, size);
data += skip;
size -= skip;
read += skip;
uint8_t type = *data;
data++;
size--;
read++;
AMFType *obj = MakeObject(type);
if (obj)
{
obj->type = type;
size_t skip = obj->Read(data, size);
data += skip;
size -= skip;
read += skip;
array[amfString.str] = obj;
}
else
break;
if (type == TYPE_TERMINATOR)
break;
}
return read;
}
AMFMixedArray::~AMFMixedArray()
{
for (AMFTypeList::iterator itr=array.begin();itr!=array.end();itr++)
{
delete itr->second;
}
}
void AMFObj::DebugPrint(int spaces, wchar_t *&str, size_t &len)
{
StringCchCopyEx(str, len, L"Object (TODO)\n", &str, &len, 0);
}
size_t AMFObj::Read(uint8_t *data, size_t size)
{
size_t read = 0;
while (size)
{
AMFString amfString;
size_t skip = amfString.Read(data, size);
data += skip;
size -= skip;
read += skip;
uint8_t type = *data;
data++;
size--;
read++;
AMFType *obj = MakeObject(type);
if (obj)
{
obj->type = type;
size_t skip = obj->Read(data, size);
data += skip;
size -= skip;
read += skip;
}
else
return false;
if (type == TYPE_TERMINATOR)
break;
}
return read;
}
void AMFArray::DebugPrint(int spaces, wchar_t *&str, size_t &len)
{
StringCchCopyEx(str, len, L"Array [\n", &str, &len, 0);
for (size_t i=0;i!=array.size();i++)
{
for (int s=0;s<spaces;s++)
StringCchCopyEx(str, len, L" ", &str, &len,0);
StringCchPrintfEx(str, len, &str, &len, 0, L"%u: ", i);
array[i]->DebugPrint(spaces+1, str, len);
}
StringCchCopyEx(str, len, L"]\n", &str, &len, 0);
}
size_t AMFArray::Read(uint8_t *data, size_t size)
{
size_t read = 0;
uint32_t arrayLength = FLV::Read32(data);
array.reserve(arrayLength);
data += 4;
read += 4;
size -= 4;
for (uint32_t i=0;i!=arrayLength;i++)
{
uint8_t type = *data;
data++;
read++;
size--;
AMFType *obj = MakeObject(type);
size_t skip = obj->Read(data, size);
//array[i]=obj;
array.push_back(obj);
data += skip;
read += skip;
size -= skip;
}
return read;
}
AMFArray::~AMFArray()
{
for (size_t i=0;i!=array.size();i++)
{
delete array[i];
}
}
/* --- String --- */
AMFString::AMFString() : str(0)
{}
AMFString::~AMFString()
{
free(str);
}
void AMFString::DebugPrint(int spaces, wchar_t *&str, size_t &len)
{
StringCchPrintfEx(str, len, &str, &len, 0, L"%s\n", this->str);
}
size_t AMFString::Read(uint8_t *data, size_t size)
{
if (size < 2)
return 0;
unsigned __int16 strlength = FLV::Read16(data);
data += 2;
size -= 2;
if (strlength > size)
return 0;
char *utf8string = (char *)calloc(strlength, sizeof(char));
memcpy(utf8string, data, strlength);
int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8string, strlength, 0, 0);
str = (wchar_t *)calloc(wideLen + 2, sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, utf8string, strlength, str, wideLen);
str[wideLen] = 0;
free(utf8string);
return strlength + 2;
}
/* --- Long String --- */
AMFLongString::AMFLongString() : str(0)
{}
AMFLongString::~AMFLongString()
{
free(str);
}
void AMFLongString::DebugPrint(int spaces, wchar_t *&str, size_t &len)
{
StringCchPrintfEx(str, len, &str, &len, 0, L"%s\n", this->str);
}
size_t AMFLongString::Read(uint8_t *data, size_t size)
{
if (size < 4)
return 0;
uint32_t strlength = FLV::Read32(data);
data += 4;
size -= 4;
if (strlength > size)
return 0;
char *utf8string = (char *)calloc(strlength, sizeof(char));
memcpy(utf8string, data, strlength);
int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8string, strlength, 0, 0);
str = (wchar_t *)calloc(wideLen + 2, sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, utf8string, strlength, str, wideLen);
str[wideLen] = 0;
free(utf8string);
return strlength + 4;
}
/* --- Double --- */
void AMFDouble::DebugPrint(int spaces, wchar_t *&str, size_t &len)
{
StringCchPrintfEx(str, len, &str, &len, 0, L"%f\n", val);
}
/* --- Boolean --- */
void AMFBoolean::DebugPrint(int spaces, wchar_t *&str, size_t &len)
{
StringCchPrintfEx(str, len, &str, &len, 0, L"%s\n", boolean?L"true":L"false");
}
/* --- Time --- */
static size_t MakeDateString(__time64_t convertTime, wchar_t *dest, size_t destlen)
{
SYSTEMTIME sysTime;
tm *newtime = _localtime64(&convertTime);
dest[0] = 0; // so we can bail out easily
if (newtime)
{
sysTime.wYear = (WORD)(newtime->tm_year + 1900);
sysTime.wMonth = (WORD)(newtime->tm_mon + 1);
sysTime.wDayOfWeek = (WORD)newtime->tm_wday;
sysTime.wDay = (WORD)newtime->tm_mday;
sysTime.wHour = (WORD)newtime->tm_hour;
sysTime.wMinute = (WORD)newtime->tm_min;
sysTime.wSecond = (WORD)newtime->tm_sec;
sysTime.wMilliseconds = 0;
int charsWritten = GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &sysTime, NULL, dest, (int)destlen);
if (charsWritten)
{
size_t dateSize = charsWritten-1;
dest += dateSize;
destlen -= dateSize;
if (destlen)
{
*dest++ = L' ';
destlen--;
dateSize++;
}
int charsWritten2 = GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &sysTime, NULL, dest, (int)destlen);
if (charsWritten2)
{
dateSize+=(charsWritten2-1);
}
return dateSize;
}
}
return 1;
}
void AMFTime::DebugPrint(int spaces, wchar_t *&str, size_t &len)
{
size_t written = MakeDateString((__time64_t)val, str, len);
str+=written;
len-=written;
if (len>=2)
{
str[0]='\n';
str[1]=0;
len--;
str++;
}
}
/* --- Terminator --- */
void AMFTerminator::DebugPrint(int spaces, wchar_t *&str, size_t &len)
{
StringCchCopyEx(str, len, L"array terminator\n", &str, &len, 0);
}
/* --- Reference --- */
void AMFReference::DebugPrint(int spaces, wchar_t *&str, size_t &len)
{
StringCchPrintfEx(str, len, &str, &len, 0, L"%u\n", val);
}

View File

@ -0,0 +1,182 @@
#ifndef NULLSOFT_AMFOBJECT_H
#define NULLSOFT_AMFOBJECT_H
#include "FLVUtil.h"
#include <windows.h>
#include <map>
#include <string>
#include <vector>
#include <time.h>
class AMFType // no, not bowling, ActionScript Message Format
{
public:
AMFType() : refCount(1) {}
virtual ~AMFType() {}
virtual size_t Read(uint8_t *data, size_t size)=0; // returns number of bytes read, 0 on failure
uint8_t type;
virtual void DebugPrint(int spaces, wchar_t *&str, size_t &len)=0;
enum
{
TYPE_DOUBLE = 0x0,
TYPE_BOOL = 0x1,
TYPE_STRING = 0x2,
TYPE_OBJECT = 0x3,
TYPE_MOVIE = 0x4,
TYPE_NULL = 0x5,
TYPE_REFERENCE = 0x7,
TYPE_MIXEDARRAY = 0x8,
TYPE_TERMINATOR = 0x9,
TYPE_ARRAY = 0xA,
TYPE_DATE = 0xB,
TYPE_LONG_STRING = 0xC,
TYPE_XML = 0xF,
};
ULONG AddRef(void)
{
return InterlockedIncrement((volatile LONG *)&refCount);
}
ULONG Release(void)
{
ULONG count = InterlockedDecrement((volatile LONG *)&refCount);
if (count == 0)
delete this;
return count;
}
private:
ULONG refCount;
};
class AMFString : public AMFType
{
public:
AMFString();
~AMFString();
void DebugPrint(int spaces, wchar_t *&str, size_t &len);
size_t Read(uint8_t *data, size_t size);
wchar_t *str;
};
class AMFLongString : public AMFType
{
public:
AMFLongString();
~AMFLongString();
void DebugPrint(int spaces, wchar_t *&str, size_t &len);
size_t Read(uint8_t *data, size_t size);
wchar_t *str;
};
class AMFObj : public AMFType
{
public:
size_t Read(uint8_t *data, size_t size);
void DebugPrint(int spaces, wchar_t *&str, size_t &len);
};
class AMFArray : public AMFType
{
public:
size_t Read(uint8_t *data, size_t size);
void DebugPrint(int spaces, wchar_t *&str, size_t &len);
~AMFArray();
std::vector<AMFType*> array;
};
class AMFMixedArray : public AMFType
{
public:
size_t Read(uint8_t *data, size_t size);
void DebugPrint(int spaces, wchar_t *&str, size_t &len);
~AMFMixedArray();
typedef std::map<std::wstring, AMFType *> AMFTypeList;
AMFTypeList array;
};
class AMFDouble : public AMFType
{
public:
void DebugPrint(int spaces, wchar_t *&str, size_t &len);
size_t Read(uint8_t *data, size_t size)
{
if (size < 8)
return 0;
val = FLV::ReadDouble(data);
return 8;
}
double val;
};
class AMFTime : public AMFType
{
public:
void DebugPrint(int spaces, wchar_t *&str, size_t &len);
size_t Read(uint8_t *data, size_t size)
{
if (size < 10)
return 0;
val = FLV::ReadDouble(data);
data+=8;
offset = FLV::Read16(data);
return 10;
}
double val; // same epoch as time_t, just stored as a double instead of an unsigned int
int16_t offset; // offset in minutes from UTC. presumably from the encoding machine's timezone
};
class AMFBoolean : public AMFType
{
public:
void DebugPrint(int spaces, wchar_t *&str, size_t &len);
size_t Read(uint8_t *data, size_t size)
{
if (size < 1)
return 0;
boolean = !!data[0];
return 1;
}
bool boolean;
};
class AMFTerminator : public AMFType
{
public:
void DebugPrint(int spaces, wchar_t *&str, size_t &len);
size_t Read(uint8_t *data, size_t size)
{
return 0;
}
};
class AMFReference : public AMFType
{
public:
void DebugPrint(int spaces, wchar_t *&str, size_t &len);
size_t Read(uint8_t *data, size_t size)
{
if (size < 2)
return 0;
val = FLV::Read16(data);
return 2;
}
uint16_t val;
};
inline double AMFGetDouble(AMFType *obj)
{
if (obj->type == 0x0)
return ((AMFDouble *)obj)->val;
return 0;
}
AMFType *MakeObject(uint8_t type);
#endif

View File

@ -0,0 +1,129 @@
#include "Main.h"
#include "BackgroundDownloader.h"
#include "..\..\..\Components\wac_network\wac_network_http_receiver_api.h"
#include "api__in_flv.h"
#include "api/service/waservicefactory.h"
#include "../nu/AutoChar.h"
#include <strsafe.h>
#define HTTP_BUFFER_SIZE 65536
// {C0A565DC-0CFE-405a-A27C-468B0C8A3A5C}
static const GUID internetConfigGroupGUID =
{
0xc0a565dc, 0xcfe, 0x405a, { 0xa2, 0x7c, 0x46, 0x8b, 0xc, 0x8a, 0x3a, 0x5c }
};
static void SetUserAgent( api_httpreceiver *http )
{
char agent[ 256 ] = { 0 };
StringCchPrintfA( agent, 256, "User-Agent: %S/%S", WASABI_API_APP->main_getAppName(), WASABI_API_APP->main_getVersionNumString() );
http->addheader( agent );
}
static int FeedHTTP( api_httpreceiver *http, Downloader::DownloadCallback *callback, int *downloaded )
{
char downloadedData[ HTTP_BUFFER_SIZE ] = { 0 };
int result = 0;
int downloadSize = http->get_bytes( downloadedData, HTTP_BUFFER_SIZE );
*downloaded = downloadSize;
if ( downloadSize )
{
result = callback->OnData( downloadedData, downloadSize );
}
return result;
}
static void RunDownload( api_httpreceiver *http, Downloader::DownloadCallback *callback )
{
int ret;
int downloaded = 0;
do
{
ret = http->run();
Sleep( 55 );
do
{
if ( FeedHTTP( http, callback, &downloaded ) != 0 )
return;
} while ( downloaded == HTTP_BUFFER_SIZE );
} while ( ret == HTTPRECEIVER_RUN_OK );
// finish off the data
do
{
if ( FeedHTTP( http, callback, &downloaded ) != 0 )
return;
} while ( downloaded );
}
bool Downloader::Download(const char *url, Downloader::DownloadCallback *callback, uint64_t startPosition)
{
api_httpreceiver *http = 0;
waServiceFactory *sf = plugin.service->service_getServiceByGuid(httpreceiverGUID);
if (sf) http = (api_httpreceiver *)sf->getInterface();
if (!http)
return false;
int use_proxy = 1;
bool proxy80 = AGAVE_API_CONFIG->GetBool(internetConfigGroupGUID, L"proxy80", false);
if (proxy80 && strstr(url, ":") && (!strstr(url, ":80/") && strstr(url, ":80") != (url + strlen(url) - 3)))
use_proxy = 0;
const wchar_t *proxy = use_proxy?AGAVE_API_CONFIG->GetString(internetConfigGroupGUID, L"proxy", 0):0;
http->AllowCompression();
http->open(API_DNS_AUTODNS, HTTP_BUFFER_SIZE, (proxy && proxy[0]) ? (const char *)AutoChar(proxy) : NULL);
if (startPosition > 0)
{
char temp[128] = {0};
StringCchPrintfA(temp, 128, "Range: bytes=%d-", startPosition);
http->addheader(temp);
}
SetUserAgent(http);
http->connect(url);
int ret;
do
{
Sleep(55);
ret = http->run();
if (ret == -1) // connection failed
break;
// ---- check our reply code ----
int replycode = http->getreplycode();
switch (replycode)
{
case 0:
case 100:
break;
case 200:
{
if (callback->OnConnect(http) != 0)
{
sf->releaseInterface(http);
return false;
}
RunDownload(http, callback);
sf->releaseInterface(http);
return true;
}
break;
default:
sf->releaseInterface(http);
return false;
}
}
while (ret == HTTPRECEIVER_RUN_OK);
//const char *er = http->geterrorstr();
sf->releaseInterface(http);
return false;
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <bfc/platform/types.h>
class api_httpreceiver;
class Downloader
{
public:
class DownloadCallback
{
public:
virtual int OnConnect(api_httpreceiver *http)=0;
virtual int OnData(void *buffer, size_t bufferSize)=0;
};
bool Download(const char *url, DownloadCallback *callback, uint64_t startPosition = 0);
};

View File

@ -0,0 +1,29 @@
#include <bfc/platform/types.h>
#include <windows.h>
#include <strsafe.h>
#include "api__in_flv.h"
#include "resource.h"
extern "C" __declspec(dllexport)
int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, size_t destlen)
{
if (!_stricmp(data, "type"))
{
dest[0]='1';
dest[1]=0;
return 1;
}
else if (!_stricmp(data, "family"))
{
int len;
const wchar_t *p;
if (!fn || !fn[0]) return 0;
len = lstrlenW(fn);
if (len < 4 || L'.' != fn[len - 4]) return 0;
p = &fn[len - 3];
if (!_wcsicmp(p, L"FLV") && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(IDS_FAMILY_STRING))) return 1;
return 0;
}
return 0;
}

View File

@ -0,0 +1,34 @@
#include "FLVAudioHeader.h"
/*
(c) 2006 Nullsoft, Inc.
Author: Ben Allison benski@nullsoft.com
*/
/*
soundType 1 bit (byte & 0x01) >> 0 0: mono, 1: stereo
soundSize 1 bit (byte & 0x02)>> 1 0: 8-bit, 2: 16-bit
soundRate 2 bits (byte & 0x0C) >> 2 0: 5.5 kHz, 1: 11 kHz, 2: 22 kHz, 3: 44 kHz
soundFormat 4 bits (byte & 0xf0) >> 4 0: Uncompressed, 1: ADPCM, 2: MP3, 5: Nellymoser 8kHz mono, 6: Nellymoser
*/
bool FLVAudioHeader::Read(unsigned __int8 *data, size_t size)
{
if (size < 1)
return false; // header size too small
unsigned __int8 byte = data[0];
stereo = !!((byte & 0x01) >> 0);
bits = ((byte & 0x02) >> 1) ? 16 : 8;
switch ((byte & 0x0C) >> 2)
{
case 0: sampleRate = 5512; break;
case 1: sampleRate = 11025; break;
case 2: sampleRate = 22050; break;
case 3: sampleRate = 44100; break;
}
format = (byte & 0xf0) >> 4;
return true;
}

View File

@ -0,0 +1,31 @@
#ifndef NULLSOFT_FLVAUDIOHEADER_H
#define NULLSOFT_FLVAUDIOHEADER_H
namespace FLV
{
const int AUDIO_FORMAT_PCM = 0;
const int AUDIO_FORMAT_ADPCM = 1;
const int AUDIO_FORMAT_MP3 = 2;
const int AUDIO_FORMAT_PCM_LE = 3; // little endian
const int AUDIO_FORMAT_NELLYMOSER_16KHZ = 4;
const int AUDIO_FORMAT_NELLYMOSER_8KHZ = 5;
const int AUDIO_FORMAT_NELLYMOSER = 6;
const int AUDIO_FORMAT_A_LAW = 7;
const int AUDIO_FORMAT_MU_LAW = 8;
const int AUDIO_FORMAT_AAC = 10;
const int AUDIO_FORMAT_MP3_8KHZ = 14;
};
class FLVAudioHeader
{
public:
bool Read(unsigned __int8 *data, size_t size); // size must be >=1, returns "true" if this was a valid header
// attributes, consider these read-only
bool stereo;
int bits;
int sampleRate;
int format;
};
#endif

View File

@ -0,0 +1,189 @@
#include "FLVCOM.h"
#include "AMFDispatch.h"
FLVCOM flvCOM;
extern bool mute;
static HANDLE DuplicateCurrentThread()
{
HANDLE fakeHandle = GetCurrentThread();
HANDLE copiedHandle = 0;
HANDLE processHandle = GetCurrentProcess();
DuplicateHandle(processHandle, fakeHandle, processHandle, &copiedHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
return copiedHandle;
}
enum
{
DISP_FLV_REGISTER_CALLBACK,
DISP_FLV_UNREGISTER_CALLBACK,
DISP_FLV_SETMUTE,
};
#define CHECK_ID(str, id) if (wcscmp(rgszNames[i], L##str) == 0) { rgdispid[i] = id; continue; }
HRESULT FLVCOM::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
{
bool unknowns = false;
for (unsigned int i = 0;i != cNames;i++)
{
CHECK_ID("RegisterCallback", DISP_FLV_REGISTER_CALLBACK)
CHECK_ID("UnregisterCallback", DISP_FLV_UNREGISTER_CALLBACK)
CHECK_ID("SetMute", DISP_FLV_SETMUTE)
rgdispid[i] = DISPID_UNKNOWN;
unknowns = true;
}
if (unknowns)
return DISP_E_UNKNOWNNAME;
else
return S_OK;
}
HRESULT FLVCOM::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
{
return E_NOTIMPL;
}
HRESULT FLVCOM::GetTypeInfoCount(unsigned int FAR * pctinfo)
{
return E_NOTIMPL;
}
HRESULT FLVCOM::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
{
if (pvarResult)
VariantInit(pvarResult);
switch (dispid)
{
case DISP_FLV_SETMUTE:
{
if (pdispparams->rgvarg[0].boolVal)
mute = true;
else
mute = false;
}
return S_OK;
case DISP_FLV_REGISTER_CALLBACK:
{
IDispatch *callback = pdispparams->rgvarg[0].pdispVal;
callbacks.push_back(DispatchCallbackInfo(callback, GetCurrentThreadId(), DuplicateCurrentThread()));
return S_OK;
}
break;
case DISP_FLV_UNREGISTER_CALLBACK:
{
IDispatch *callback = pdispparams->rgvarg[0].pdispVal;
size_t numCallbacks = callbacks.size();
while (numCallbacks--)
{
if (callbacks[numCallbacks].dispatch == callback)
{
CloseHandle(callbacks[numCallbacks].threadHandle);
callbacks.erase(callbacks.begin() + numCallbacks);
}
}
return S_OK;
}
break;
}
return DISP_E_MEMBERNOTFOUND;
}
STDMETHODIMP FLVCOM::QueryInterface(REFIID riid, PVOID *ppvObject)
{
if (!ppvObject)
return E_POINTER;
else if (IsEqualIID(riid, IID_IDispatch))
*ppvObject = (IDispatch *)this;
else if (IsEqualIID(riid, IID_IUnknown))
*ppvObject = this;
else
{
*ppvObject = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
ULONG FLVCOM::AddRef(void)
{
return 0;
}
ULONG FLVCOM::Release(void)
{
return 0;
}
static void CallDispatchMethod(IDispatch *dispatch, DISPPARAMS &params, OLECHAR *name)
{
try
{
unsigned int ret;
DISPID dispid;
if (SUCCEEDED(dispatch->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_SYSTEM_DEFAULT, &dispid)))
dispatch->Invoke(dispid, GUID_NULL, 0, DISPATCH_METHOD, &params, 0, 0, &ret);
}
catch (...)
{}
}
struct APCdata
{
IDispatch *disp;
wchar_t *name;
AMFDispatch *amf;
};
static VOID CALLBACK MetadataAPC(ULONG_PTR param)
{
APCdata *data = (APCdata *)param;
DISPPARAMS params;
VARIANT argument;
params.cArgs = 1;
params.cNamedArgs = 0;
params.rgdispidNamedArgs = 0;
params.rgvarg = &argument;
VariantInit(&argument);
V_VT(&argument) = VT_DISPATCH;
V_DISPATCH(&argument) = data->amf;
CallDispatchMethod(data->disp, params, data->name);
data->disp->Release();
data->amf->Release();
free(data->name);
delete data;
}
void FLVCOM::MetadataCallback(FLVMetadata::Tag *tag)
{
if (!callbacks.empty())
{
AMFDispatch *disp = new AMFDispatch(tag->parameters); // we're newing this we can refcount
DWORD curThreadId = GetCurrentThreadId();
for (size_t i = 0;i != callbacks.size();i++)
{
APCdata *data = new APCdata;
data->disp = callbacks[i].dispatch;
data->disp->AddRef();
data->name = _wcsdup(tag->name.str);
data->amf = disp;
data->amf->AddRef();
if (curThreadId == callbacks[i].threadId)
MetadataAPC((ULONG_PTR)data);
else
{
if (callbacks[i].threadHandle)
QueueUserAPC(MetadataAPC, callbacks[i].threadHandle, (ULONG_PTR)data);
}
}
disp->Release();
}
}

View File

@ -0,0 +1,37 @@
#pragma once
/*
Ben Allison
April 30, 2008
*/
#include <oaidl.h>
#include <vector>
#include "FLVMetadata.h"
struct DispatchCallbackInfo
{
DispatchCallbackInfo() : dispatch(0), threadId(0), threadHandle(0) {}
DispatchCallbackInfo(IDispatch *d, DWORD id, HANDLE _handle) : dispatch(d), threadId(id), threadHandle(_handle) {}
IDispatch *dispatch;
DWORD threadId;
HANDLE threadHandle;
};
class FLVCOM : public IDispatch
{
public:
STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
STDMETHOD_(ULONG, AddRef)(void);
STDMETHOD_(ULONG, Release)(void);
void MetadataCallback(FLVMetadata::Tag *tag);
private:
// *** IDispatch Methods ***
STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
std::vector<DispatchCallbackInfo> callbacks;
};
extern FLVCOM flvCOM;

View File

@ -0,0 +1,41 @@
#include "FLVHeader.h"
#include "FLVUtil.h"
/*
(c) 2006 Nullsoft, Inc.
Author: Ben Allison benski@nullsoft.com
*/
#define FLV_BITMASK_AUDIO 0x4
#define FLV_BITMASK_VIDEO 0x1
/*
FLV Header spec
Signature - uint8[3] - must equal "FLV"
Version - uint8 - only known version is 1
Flags - uint8 - bitmask, 4 is audio, 1 is video
Offset - uint32 - total size of header (9), big endian
*/
bool FLVHeader::Read(uint8_t *data, size_t size)
{
if (size < 9)
return false; // too small to be an FLV header
if (data[0] != 'F' || data[1] != 'L' || data[2] != 'V')
return false; // invalid signature
version = data[3];
hasAudio = !!(data[4] & FLV_BITMASK_AUDIO);
hasVideo = data[4] & FLV_BITMASK_VIDEO;
headerSize = FLV::Read32(&data[5]);
if (headerSize != 9)
return false;
return true;
}

View File

@ -0,0 +1,18 @@
#ifndef NULLSOFT_FLVHEADER_H
#define NULLSOFT_FLVHEADER_H
#include <bfc/platform/types.h>
class FLVHeader
{
public:
FLVHeader() : version(0), hasAudio(0), hasVideo(0), headerSize(0) {}
bool Read(uint8_t *data, size_t size); // size must be >=9, returns "true" if this was a valid header
// attributes, consider these read-only
uint8_t version;
bool hasAudio, hasVideo;
uint32_t headerSize;
};
#endif

View File

@ -0,0 +1,112 @@
#include "FLVMetadata.h"
#include "FLVUtil.h"
#include <windows.h>
/*
(c) 2006 Nullsoft, Inc.
Author: Ben Allison benski@nullsoft.com
*/
/*
type - uint8 -
length - uint16
*/
AMFType *MakeObject(uint8_t type)
{
switch(type)
{
case AMFType::TYPE_DOUBLE: // double
return new AMFDouble;
case AMFType::TYPE_BOOL: // bool
return new AMFBoolean;
case AMFType::TYPE_STRING: // string
return new AMFString;
case AMFType::TYPE_OBJECT: // object
return new AMFObj;
case AMFType::TYPE_MOVIE: // movie (basically just a URL)
return new AMFString;
case AMFType::TYPE_NULL: // null
return 0;
case AMFType::TYPE_REFERENCE: // reference
return 0;
case AMFType::TYPE_MIXEDARRAY:
return new AMFMixedArray;
case AMFType::TYPE_TERMINATOR:
return new AMFTerminator;
case AMFType::TYPE_ARRAY:
return new AMFArray;
case AMFType::TYPE_DATE: // date
return new AMFTime;
case AMFType::TYPE_LONG_STRING: // long string
return new AMFLongString;
case AMFType::TYPE_XML: // XML
return 0;
default:
return 0;
}
}
FLVMetadata::FLVMetadata()
{
}
FLVMetadata::~FLVMetadata()
{
for ( FLVMetadata::Tag *tag : tags )
delete tag;
}
bool FLVMetadata::Read(uint8_t *data, size_t size)
{
// TODO: there can be multiple name/value pairs so we could read them all
while(size)
{
uint8_t type=*data;
data++;
size--;
if (type == 0 && size >= 2 && data[0] == 0 && data[1] == AMFType::TYPE_TERMINATOR) // check for terminator
return true; // array is done
if (type != AMFType::TYPE_STRING) // first entry is a string, verify this
return false; // malformed, lets bail
FLVMetadata::Tag *tag = new FLVMetadata::Tag;
// read name
size_t skip = tag->name.Read(data, size);
data+=skip;
size-=skip;
type=*data;
data++;
size--;
if (type != AMFType::TYPE_MIXEDARRAY) // second entry is an associative array, verify this
{
delete tag;
return false; // malformed, lets bail
}
tag->parameters = new AMFMixedArray; // we're new'ing this because we need to reference count
skip = tag->parameters->Read(data, size);
data+=skip;
size-=skip;
tags.push_back(tag);
}
return true;
}
FLVMetadata::Tag::Tag() : parameters(0)
{
}
FLVMetadata::Tag::~Tag()
{
if (parameters)
parameters->Release();
}

View File

@ -0,0 +1,24 @@
#ifndef NULLSOFT_FLVMETADATA_H
#define NULLSOFT_FLVMETADATA_H
#include "AMFObject.h"
#include <vector>
class FLVMetadata
{
public:
FLVMetadata();
~FLVMetadata();
bool Read(uint8_t *data, size_t size);
struct Tag
{
Tag();
~Tag();
AMFString name;
AMFMixedArray *parameters; // needs to be pointer so we can refcount
};
std::vector<Tag*> tags;
};
#endif

View File

@ -0,0 +1 @@
#include "FLVProcessor.h"

View File

@ -0,0 +1,40 @@
#pragma once
#include <bfc/platform/types.h>
#include "FLVStreamHeader.h"
#include "FLVHeader.h"
struct FrameData
{
FLVStreamHeader header;
uint64_t location;
bool keyFrame;
};
enum
{
FLV_OK=0,
FLV_NEED_MORE_DATA=1,
FLV_ERROR=-1,
FLVPROCESSOR_WRITE_OK=0,
FLVPROCESSOR_WRITE_ERROR=1,
FLVPROCESSOR_WRITE_WAIT=2,
};
class FLVProcessor
{
public:
virtual ~FLVProcessor() {}
virtual int Write(void *data, size_t datalen, size_t *written) = 0;
virtual int Process()=0;
virtual uint64_t Seek(uint64_t position)=0;
virtual size_t Read(void *data, size_t bytes)=0;
virtual uint64_t GetProcessedPosition()=0;
virtual bool GetFrame(size_t frameIndex, FrameData &frameData)=0;
virtual uint32_t GetMaxTimestamp()=0;
virtual bool GetPosition(int time_in_ms, size_t *frameIndex, bool needsVideoKeyFrame)=0;
virtual bool IsStreaming()=0;
virtual FLVHeader *GetHeader() = 0;
};

View File

@ -0,0 +1,211 @@
#include "FLVReader.h"
#include "FLVHeader.h"
#include "FLVStreamheader.h"
#include "BackgroundDownloader.h"
#include "../nu/AutoChar.h"
#include "FileProcessor.h"
#include "ProgressiveProcessor.h"
#include "StreamProcessor.h"
#include "../../..\Components\wac_network\wac_network_http_receiver_api.h"
#include <shlwapi.h>
FLVReader::FLVReader(const wchar_t *_url)
{
processor = 0;
url = _wcsdup(_url);
end_of_stream=false;
killswitch=false;
DWORD threadId;
flvThread=CreateThread(NULL, NULL, ParserThreadStub, this, 0, &threadId);
SetThreadPriority(flvThread, THREAD_PRIORITY_BELOW_NORMAL);
}
FLVReader::~FLVReader()
{
free(url);
delete processor;
}
uint64_t FLVReader::Seek(uint64_t position)
{
if (processor)
return processor->Seek(position);
else
return -1;
}
size_t FLVReader::Read(void *data, size_t bytes)
{
if (processor)
return processor->Read(data, bytes);
else
return 0;
}
uint64_t FLVReader::GetProcessedPosition()
{
if (processor)
return processor->GetProcessedPosition();
else
return 0;
}
void FLVReader::ProcessFile()
{
if (!processor)
return;
while (processor->Process() == FLV_OK)
{
if (killswitch)
return ;
}
}
int FLVReader::OnConnect(api_httpreceiver *http)
{
if (http->content_length())
processor = new ProgressiveProcessor;
else
processor = new StreamProcessor;
return 0;
}
int FLVReader::OnData(void *data, size_t datalen)
{
if (!processor)
return 1;
bool needSleep=false;
while (datalen)
{
if (killswitch)
return 1;
if (needSleep)
{
Sleep(10);
needSleep=false;
}
size_t written=0;
switch (processor->Write(data, datalen, &written))
{
case FLVPROCESSOR_WRITE_ERROR:
return 1;
case FLVPROCESSOR_WRITE_WAIT:
needSleep=true;
break;
}
datalen -= written;
if (written)
{
while (1)
{
if (killswitch)
return 1;
int ret = processor->Process();
if (ret == FLV_OK)
continue;
if (ret == FLV_NEED_MORE_DATA)
break;
if (ret == FLV_ERROR)
return 1;
}
}
}
return !!killswitch;
}
bool FLVReader::GetFrame(size_t frameIndex, FrameData &frameData)
{
if (processor)
return processor->GetFrame(frameIndex, frameData);
else
return false;
}
/*
Test URLs (valid of as oct 23 2007)
http://pdl.stream.aol.com/aol/us/aolmusic/artists/astralwerks/2220s/2220s_shootyourgun_hakjfh_700_dl.flv
http://pdl.stream.aol.com/aol/us/aolmusic/sessions/2007/amberpacific/suc_amberpacific_fallbackintomylife_700_dl.flv
http://pdl.stream.aol.com/aol/us/aolmusic/sessions/2007/amberpacific/suc_amberpacific_gonesoyoung_700_dl.flv
http://pdl.stream.aol.com/aol/us/aolmusic/sessions/2007/amberpacific/suc_amberpacific_soyesterday_700_dl.flv
http://pdl.stream.aol.com/aol/us/aolmusic/sessions/2007/amberpacific/suc_amberpacific_watchingoverme_700_dl.flv
a potential crasher:
http://pdl.stream.aol.com/aol/us/aolcomvideo/TVGuide/johncmcginley_6346/johncmcginley_6346_460_700_dl.flv
FLV streams:
http://208.80.52.96/CBS_R20_452P_F128?ext=.flv
http://208.80.54.37/KROQFM?ext=.flv
http://208.80.52.96/CBS_R20_568P_F128?ext=.flv
*/
DWORD FLVReader::ParserThread()
{
if (PathIsURL(url))
{
Downloader downloader;
downloader.Download(AutoChar(url), this);
}
else
{
processor = new FileProcessor(url);
ProcessFile();
}
end_of_stream=true;
return 0;
}
void FLVReader::Kill()
{
killswitch=true;
WaitForSingleObject(flvThread, INFINITE);
CloseHandle(flvThread);
}
void FLVReader::SignalKill()
{
killswitch=true;
}
bool FLVReader::IsEOF()
{
return end_of_stream;
}
bool FLVReader::GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame)
{
if (processor)
return processor->GetPosition(time_in_ms, frameIndex, needVideoKeyFrame);
else
return false;
}
uint32_t FLVReader::GetMaxTimestamp()
{
if (processor)
return processor->GetMaxTimestamp();
else
return 0;
}
bool FLVReader::IsStreaming()
{
if (processor)
return processor->IsStreaming();
else
return true;
}
FLVHeader *FLVReader::GetHeader()
{
if (processor)
return processor->GetHeader();
else
return 0;
}

View File

@ -0,0 +1,49 @@
#ifndef NULLSOFT_IN_FLV_FLVREADER_H
#define NULLSOFT_IN_FLV_FLVREADER_H
#include <windows.h>
#include <bfc/platform/types.h>
#include "FLVStreamheader.h"
#include "../nu/AutoLock.h"
#include "BackgroundDownloader.h"
#include "FLVProcessor.h"
class FLVReader : private Downloader::DownloadCallback
{
public:
FLVReader(const wchar_t *_url);
~FLVReader();
bool GetFrame(size_t frameIndex, FrameData &frameData);
bool GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame);
uint64_t Seek(uint64_t position);
size_t Read(void *data, size_t bytes);
void Kill();
void SignalKill();
bool IsEOF();
uint32_t GetMaxTimestamp();
uint64_t GetProcessedPosition();
bool IsStreaming();
FLVHeader *GetHeader();
private:
void ProcessFile();
int OnConnect(api_httpreceiver *http);
int OnData(void *data, size_t datalen);
int Process();
DWORD CALLBACK ParserThread();
static DWORD CALLBACK ParserThreadStub(LPVOID param) { return ((FLVReader *)param)->ParserThread(); }
private:
bool killswitch;
HANDLE flvThread;
bool end_of_stream;
wchar_t *url;
/* because we won't know until after opening the stream whether it's progressive download
or a real-time stream, we need have a pointer to a virtual base class to do the processing
we'll create a different one depending on what kind of stream */
FLVProcessor *processor;
};
#endif

View File

@ -0,0 +1,36 @@
#include "FLVStreamHeader.h"
#include "FLVUtil.h"
/*
(c) 2006 Nullsoft, Inc.
Author: Ben Allison benski@nullsoft.com
*/
/*
PreviousTagSize - uint32 - total size of previous tag, presumably for stream continuity checking. big endian
Type - uint8 - what does this frame contain? 0x12=meta, 0x8=audio, 0x9=video
BodyLength - uint24 - size of the data following this header. big endian
Timestamp - uint24 - timestamp (milliseconds). big endian
Timestamp High - uint8 - high 8 bits of timestamp (to form 32bit timestamp)
Stream ID - uint24 - always zero
*/
bool FLVStreamHeader::Read(uint8_t *data, size_t size)
{
if (size < 15)
return false; // header size too small
previousSize = FLV::Read32(&data[0]);
type = data[4];
dataSize = FLV::Read24(&data[5]);
timestamp = FLV::Read24(&data[8]);
uint8_t timestampHigh = FLV::Read8(&data[11]);
timestamp |= (timestampHigh << 24);
streamID = FLV::Read24(&data[12]);
if (streamID != 0)
return false;
return true;
}

View File

@ -0,0 +1,27 @@
#ifndef NULLSOFT_FLVSTREAMHEADER_H
#define NULLSOFT_FLVSTREAMHEADER_H
#include <bfc/platform/types.h>
namespace FLV
{
enum
{
FRAME_TYPE_AUDIO = 0x8,
FRAME_TYPE_VIDEO = 0x9,
FRAME_TYPE_METADATA = 0x12,
};
}
class FLVStreamHeader
{
public:
bool Read(unsigned __int8 *data, size_t size); // size must be >=15, returns "true" if this was a valid header
// attributes, consider these read-only
uint32_t previousSize;
uint8_t type;
uint32_t dataSize;
uint32_t timestamp;
uint32_t streamID;
};
#endif

View File

@ -0,0 +1,90 @@
#ifndef NULLSOFT_FLVUTIL_H
#define NULLSOFT_FLVUTIL_H
#include <memory.h>
#include <bfc/platform/types.h>
namespace FLV
{
// reads 32 bits from data and converts from big endian
inline uint32_t Read32(uint8_t *data)
{
uint32_t returnVal;
#ifdef __BIG_ENDIAN__
returnVal = *(uint32_t *)(&data[0]);
#else
// need to swap endianness
uint8_t *swap = (uint8_t *)&returnVal;
swap[0]=data[3];
swap[1]=data[2];
swap[2]=data[1];
swap[3]=data[0];
#endif
return returnVal;
}
// reads 24 bits from data, converts from big endian, and returns as a 32bit int
inline uint32_t Read24(uint8_t *data)
{
uint32_t returnVal=0;
uint8_t *swap = (uint8_t *)&returnVal;
#ifdef __BIG_ENDIAN__
swap[1]=data[0];
swap[2]=data[1];
swap[3]=data[2];
returnVal = *(uint32_t *)(&data[0]);
#else
// need to swap endianness
swap[0]=data[2];
swap[1]=data[1];
swap[2]=data[0];
#endif
return returnVal;
}
// reads 16 bits from data and converts from big endian
inline uint16_t Read16(uint8_t *data)
{
uint16_t returnVal;
#ifdef __BIG_ENDIAN__
returnVal = *(uint16_t *)(&data[0]);
#else
// need to swap endianness
uint8_t *swap = (uint8_t *)&returnVal;
swap[0]=data[1];
swap[1]=data[0];
#endif
return returnVal;
}
// reads 16 bits from data and converts from big endian
inline uint8_t Read8(uint8_t *data)
{
uint8_t returnVal;
returnVal = *(uint8_t *)(&data[0]);
return returnVal;
}
// reads a double from data
inline double ReadDouble(uint8_t *data)
{
double returnVal;
#ifdef __BIG_ENDIAN__
memcpy(&returnVal, data, 8);
#else
uint8_t *swap = (uint8_t *)&returnVal;
swap[0]=data[7];
swap[1]=data[6];
swap[2]=data[5];
swap[3]=data[4];
swap[4]=data[3];
swap[5]=data[2];
swap[6]=data[1];
swap[7]=data[0];
#endif
return returnVal;
}
}
#endif

View File

@ -0,0 +1,24 @@
#include "FLVVideoHeader.h"
/*
(c) 2006 Nullsoft, Inc.
Author: Ben Allison benski@nullsoft.com
*/
/*
codecID - 4 bits - 2: Sorensen H.263, 3: Screen video, 4: On2 VP6
frameType - 4 bits - 1: keyframe, 2: inter frame, 3: disposable inter frame
*/
bool FLVVideoHeader::Read(unsigned __int8 *data, size_t size)
{
if (size < 1)
return false; // header size too small
unsigned __int8 byte = data[0];
format = (byte & 0x0f) >> 0;
frameType = (byte & 0xf0) >> 4;
return true;
}

View File

@ -0,0 +1,32 @@
#ifndef NULLSOFT_FLVVIDEOHEADER_H
#define NULLSOFT_FLVVIDEOHEADER_H
namespace FLV
{
const int VIDEO_FORMAT_JPEG = 1;
const int VIDEO_FORMAT_SORENSON = 2; // H.263
const int VIDEO_FORMAT_SCREEN = 3;
const int VIDEO_FORMAT_VP6 = 4;
const int VIDEO_FORMAT_VP62 = 5;
const int VIDEO_FORMAT_SCREEN_V2 = 6;
const int VIDEO_FORMAT_AVC = 7; // MPEG-4 Part 10
const int VIDEO_FRAMETYPE_KEYFRAME = 1;
const int VIDEO_FRAMETYPE_IFRAME = 2;
const int VIDEO_FRAMETYPE_IFRAME_DISPOSABLE = 3;
const int VIDEO_FRAMETYPE_GENERATED = 4;
const int VIDEO_FRAMETYPE_INFO = 5;
};
class FLVVideoHeader
{
public:
bool Read(unsigned __int8 *data, size_t size); // size must be >=1, returns "true" if this was a valid header
// attributes, consider these read-only
int format;
int frameType;
};
#endif

View File

@ -0,0 +1,201 @@
#include "FileProcessor.h"
#include "FLVHeader.h"
#include "FLVVideoHeader.h"
static int64_t Seek64(HANDLE hf, int64_t distance, DWORD MoveMethod)
{
LARGE_INTEGER li;
li.QuadPart = distance;
li.LowPart = SetFilePointer(hf, li.LowPart, &li.HighPart, MoveMethod);
if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
{
li.QuadPart = -1;
}
return li.QuadPart;
}
int64_t FileSize64(HANDLE file)
{
LARGE_INTEGER position;
position.QuadPart=0;
position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart);
if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
return INVALID_FILE_SIZE;
else
return position.QuadPart;
}
FileProcessor::FileProcessor()
{
Init();
}
FileProcessor::FileProcessor(const wchar_t *filename)
{
Init();
processedCursor=CreateFile(filename, GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
readCursor=CreateFile(filename, GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
writePosition=FileSize64(readCursor);
}
FileProcessor::~FileProcessor()
{
if (processedCursor != INVALID_HANDLE_VALUE)
CloseHandle(processedCursor);
if (readCursor != INVALID_HANDLE_VALUE)
CloseHandle(readCursor);
}
void FileProcessor::Init()
{
processedPosition=0;
processedCursor=INVALID_HANDLE_VALUE;
writePosition=0;
readCursor=INVALID_HANDLE_VALUE;
flen=0;
maxTimeStamp=0;
headerOK=false;
}
int FileProcessor::Process()
{
int64_t oldPosition = Seek64(processedCursor, 0, FILE_CURRENT);
// read file header if we're at the beginning
if (processedPosition == 0)
{
if (writePosition-processedPosition >= 9) // need at least nine bytes for the FLV file header
{
uint8_t data[9] = {0};
DWORD bytesRead=0;
ReadFile(processedCursor, data, 9, &bytesRead, NULL);
if (header.Read(data, bytesRead))
{
headerOK=true;
Nullsoft::Utility::AutoLock lock(frameGuard);
processedPosition += bytesRead;
return FLV_OK; // we'll make our logic just a little bit more sane by only processing one frame per pass.
}
else
return FLV_ERROR;
}
Seek64(processedCursor, oldPosition, FILE_BEGIN); // rollback
return FLV_NEED_MORE_DATA;
}
if (writePosition-processedPosition >= 15) // need at least fifteen bytes for the FLV frame header
{
uint8_t data[15] = {0};
DWORD bytesRead=0;
ReadFile(processedCursor, data, 15, &bytesRead, NULL);
FrameData frameData;
if (frameData.header.Read(data, bytesRead))
{
if (frameData.header.dataSize + processedPosition + 15 <= writePosition)
{
frameData.keyFrame = false;
if (frameData.header.type == FLV::FRAME_TYPE_VIDEO)
{
FLVVideoHeader videoHeader;
DWORD videoHeaderRead=0;
ReadFile(processedCursor, data, 1, &videoHeaderRead, NULL);
if (videoHeader.Read(data, bytesRead))
{
if (videoHeader.frameType == FLV::VIDEO_FRAMETYPE_KEYFRAME)
{
frameData.keyFrame = true;
}
}
Seek64(processedCursor, -(int64_t)videoHeaderRead, FILE_CURRENT);
// roll back file cursor from ReadFile
}
{ // critsec enter
Nullsoft::Utility::AutoLock lock(frameGuard);
// record the offset where we found it
frameData.location = processedPosition;
maxTimeStamp=max(maxTimeStamp, frameData.header.timestamp);
frames.push_back(frameData);
} // critsec leave
Seek64(processedCursor, frameData.header.dataSize, FILE_CURRENT);
Nullsoft::Utility::AutoLock lock(frameGuard);
processedPosition+=bytesRead+frameData.header.dataSize;
return FLV_OK;
}
Seek64(processedCursor, oldPosition, FILE_BEGIN); // rollback
return FLV_NEED_MORE_DATA;
}
else
{
return FLV_ERROR;
}
}
Seek64(processedCursor, oldPosition, FILE_BEGIN); // rollback
return FLV_NEED_MORE_DATA;
}
uint32_t FileProcessor::GetMaxTimestamp()
{
return maxTimeStamp;
}
bool FileProcessor::GetFrame(size_t frameIndex, FrameData &frameData)
{
Nullsoft::Utility::AutoLock lock(frameGuard);
if (frameIndex < frames.size())
{
frameData = frames[frameIndex];
return true;
}
return false;
}
uint64_t FileProcessor::GetProcessedPosition()
{
Nullsoft::Utility::AutoLock lock(frameGuard);
return processedPosition;
}
size_t FileProcessor::Read(void *data, size_t bytes)
{
DWORD bytesRead = 0;
ReadFile(readCursor, data, (DWORD)bytes, &bytesRead, NULL);
return bytesRead;
}
uint64_t FileProcessor::Seek(uint64_t position)
{
return Seek64(readCursor, position, FILE_BEGIN);
}
bool FileProcessor::GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame)
{
Nullsoft::Utility::AutoLock lock(frameGuard);
// TODO: binary search
for (size_t f=0;f!=frames.size();f++)
{
if (frames[f].header.timestamp >= (uint32_t)time_in_ms)
{
if (needVideoKeyFrame)
{
while (f != 0 && !frames[f].keyFrame)
f--;
}
*frameIndex = f;
return true;
}
}
return false;
}
FLVHeader *FileProcessor::GetHeader()
{
if (headerOK)
return &header;
else
return 0;
}

View File

@ -0,0 +1,43 @@
#pragma once
#include "FLVProcessor.h"
#include <windows.h>
#include <vector>
#include "../nu/AutoLock.h"
class FileProcessor : public FLVProcessor
{
public:
FileProcessor(const wchar_t *filename);
~FileProcessor();
private:
/* FLVProcessor virtual method overrides */
int Write(void *data, size_t datalen, size_t *written) { return 1; }
uint64_t GetProcessedPosition();
uint32_t GetMaxTimestamp();
bool GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame);
bool IsStreaming() { return false; }
FLVHeader *GetHeader();
public:
/* FLVProcessor virtual method overrides, continued
(these are public so FileProcessor can be used standalone */
int Process();
uint64_t Seek(uint64_t position);
size_t Read(void *data, size_t bytes);
bool GetFrame(size_t frameIndex, FrameData &frameData);
protected:
FileProcessor();
uint64_t processedPosition;
uint64_t writePosition;
HANDLE processedCursor, readCursor;
// file positions of samples
std::vector<FrameData> frames;
Nullsoft::Utility::LockGuard frameGuard;
uint64_t flen;
private:
void Init();
uint32_t maxTimeStamp;
FLVHeader header;
bool headerOK;
};

View File

@ -0,0 +1,703 @@
#include "main.h"
#include <windows.h>
#include "api__in_flv.h"
#include "../Winamp/wa_ipc.h"
#include "FLVHeader.h"
#include "FLVStreamHeader.h"
#include "FLVAudioHeader.h"
#include "FLVVideoHeader.h"
#include "FLVMetadata.h"
#include <malloc.h>
#include <stdio.h>
#include <shlwapi.h>
#include "VideoThread.h"
#include "FLVReader.h"
#include "resource.h"
#include "FLVCOM.h"
#include <api/service/waservicefactory.h>
#include "ifc_flvaudiodecoder.h"
#include "svc_flvdecoder.h"
#include "../nu/AudioOutput.h"
#define PRE_BUFFER_MS 5000.0
#define PRE_BUFFER_MAX (1024*1024)
uint32_t last_timestamp=0;
static bool audioOpened;
int bufferCount;
static int bits, channels, sampleRate;
static double dataRate_audio, dataRate_video;
static double dataRate;
static uint64_t prebuffer;
bool mute=false;
uint32_t first_timestamp;
static size_t audio_buffered=0;
void VideoStop();
static ifc_flvaudiodecoder *audioDecoder=0;
static bool checked_in_swf=false;
extern bool video_only;
class FLVWait
{
public:
int WaitOrAbort(int len)
{
if (WaitForSingleObject(killswitch, len) == WAIT_OBJECT_0)
return 1;
return 0;
}
};
static nu::AudioOutput<FLVWait> outputter(&plugin);
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 bool Audio_IsSupported(int type)
{
size_t n = 0;
waServiceFactory *factory = NULL;
while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++))
{
svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface();
if (creator)
{
int supported = creator->HandlesAudio(type);
factory->releaseInterface(creator);
if (supported == svc_flvdecoder::CREATEDECODER_SUCCESS)
return true;
}
}
return false;
}
static ifc_flvaudiodecoder *CreateAudioDecoder(const FLVAudioHeader &header)
{
ifc_flvaudiodecoder *audio_decoder=0;
size_t n=0;
waServiceFactory *factory = NULL;
while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++))
{
svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface();
if (creator)
{
if (creator->CreateAudioDecoder(header.stereo, header.bits, header.sampleRate, header.format, &audio_decoder) == FLV_AUDIO_SUCCESS)
return audio_decoder;
factory->releaseInterface(creator);
}
}
return 0;
}
void OnStart()
{
Video_Init();
audioOpened = false;
audioDecoder = 0;
bufferCount=0;
mute = false;
audio_buffered=0;
if (!videoOutput)
videoOutput = (IVideoOutput *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT);
}
void Audio_Close()
{
if (audioDecoder)
audioDecoder->Close();
audioDecoder=0;
}
static bool OpenAudio(unsigned int sampleRate, unsigned int channels, unsigned int bits)
{
if (!outputter.Open(0, channels, sampleRate, bits, -666))
return false;
audioOpened = true;
return true;
}
static void DoBuffer(FLVReader &reader)
{
// TODO: we should pre-buffer after getting the first audio + video frames
// so we can estimate bitrate
for (;;)
{
uint64_t processedPosition = reader.GetProcessedPosition();
if (processedPosition < prebuffer && !reader.IsEOF())
{
Buffering((int)((100ULL * processedPosition) / prebuffer), WASABI_API_LNGSTRINGW(IDS_BUFFERING));
if (WaitForSingleObject(killswitch, 100) == WAIT_OBJECT_0)
break;
else
continue;
}
else
break;
}
}
char pcmdata[65536] = {0};
size_t outlen = 32768;
static uint32_t audio_type;
static void OnAudio(FLVReader &reader, void *data, size_t length, const FLVAudioHeader &header)
{
if (!audioDecoder)
{
audioDecoder = CreateAudioDecoder(header);
audio_type = header.format;
video_only=false;
}
if (audioDecoder)
{
if (!audioDecoder->Ready())
{
//first_timestamp = -1;
}
outlen = sizeof(pcmdata)/2 - audio_buffered;
double bitrate = 0;
int ret = audioDecoder->DecodeSample(data, length, pcmdata+audio_buffered, &outlen, &bitrate);
if (ret == FLV_AUDIO_SUCCESS)
{
outlen+=audio_buffered;
audio_buffered=0;
if (bitrate && dataRate_audio != bitrate)
{
dataRate_audio = bitrate;
dataRate = dataRate_audio + dataRate_video;
plugin.SetInfo((int)dataRate, -1, -1, 1);
}
if (!audioOpened)
{
// pre-populate values for decoders that use the header info (e.g. ADPCM)
sampleRate=header.sampleRate;
channels = header.stereo?2:1;
bits = header.bits;
if (audioDecoder->GetOutputFormat((unsigned int *)&sampleRate, (unsigned int *)&channels, (unsigned int *)&bits) == FLV_AUDIO_SUCCESS)
{
// buffer (if needed)
prebuffer = (uint64_t)(dataRate * PRE_BUFFER_MS / 8.0);
if (prebuffer > PRE_BUFFER_MAX)
prebuffer=PRE_BUFFER_MAX;
DoBuffer(reader); // benski> admittedly a crappy place to call this
if (WaitForSingleObject(killswitch, 0) == WAIT_OBJECT_0)
return;
OpenAudio(sampleRate, channels, bits);
}
}
if (mute)
{
if (bits == 8) // 8 bit is signed so 128 is zero voltage
memset(pcmdata, 0x80, outlen);
else
memset(pcmdata, 0, outlen);
}
if (audioOpened && outlen)
{
outputter.Write(pcmdata, outlen);
}
else
{
audio_buffered=outlen;
}
}
else if (ret == FLV_AUDIO_NEEDS_MORE_INPUT)
{
plugin.SetInfo(-1, -1, -1, 0);
}
}
}
#define PREBUFFER_BYTES 2048ULL
enum
{
CODEC_CHECK_NONE=0,
CODEC_CHECK_AUDIO=1,
CODEC_CHECK_VIDEO=2,
CODEC_CHECK_UNSURE = -1,
};
static bool CheckSWF()
{
if (!checked_in_swf)
{
const wchar_t *pluginsDir = (const wchar_t *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORYW);
wchar_t in_swf_path[MAX_PATH] = {0};
PathCombineW(in_swf_path, pluginsDir, L"in_swf.dll");
in_swf = LoadLibraryW(in_swf_path);
checked_in_swf = true;
}
return !!in_swf;
}
static void CALLBACK SWFAPC(ULONG_PTR param)
{
if (in_swf)
{
typedef In_Module *(*MODULEGETTER)();
MODULEGETTER moduleGetter=0;
moduleGetter = (MODULEGETTER)GetProcAddress(in_swf, "winampGetInModule2");
if (moduleGetter)
swf_mod = moduleGetter();
}
if (swf_mod)
{
if (swf_mod->Play(playFile))
swf_mod=0;
}
if (!swf_mod)
{
if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
}
}
static bool Audio_DecoderReady()
{
if (!audioDecoder)
return true;
return !!audioDecoder->Ready();
}
static bool DecodersReady()
{
return Audio_DecoderReady() && Video_DecoderReady();
}
DWORD CALLBACK PlayProcedure(LPVOID param)
{
int needCodecCheck=CODEC_CHECK_UNSURE;
int missingCodecs=CODEC_CHECK_NONE;
size_t codecCheckFrame=0;
dataRate_audio=0;
dataRate_video=0;
dataRate=0;
first_timestamp=-1;
outputter.Init(plugin.outMod);
FLVReader reader(playFile);
size_t i=0;
bool hasDuration=false;
OnStart();
plugin.is_seekable=0;
prebuffer = PREBUFFER_BYTES;
bool first_frame_parsed=false;
for (;;)
{
DoBuffer(reader);
if (WaitForSingleObject(killswitch, paused?100:0) == WAIT_OBJECT_0)
break;
if (paused)
continue;
if (m_need_seek != -1 && DecodersReady() && first_frame_parsed)
{
if (reader.GetPosition(m_need_seek, &i, video_opened))
{
VideoFlush();
if (audioDecoder)
audioDecoder->Flush();
FrameData frameData;
reader.GetFrame(i, frameData);
outputter.Flush(frameData.header.timestamp);
if (video_only)
{
video_clock.Seek(frameData.header.timestamp);
}
}
uint32_t first_timestamp = 0;
m_need_seek=-1;
}
// update the movie length
if (!hasDuration)
{
if (reader.IsStreaming())
{
hasDuration=true;
g_length = -1000;
plugin.is_seekable=0;
}
else
{
g_length=reader.GetMaxTimestamp();
if (g_length != -1000)
plugin.is_seekable=1;
}
PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
}
/** if it's a local file or an HTTP asset with content-length
** we verify that the FLV has codecs we can decode
** depending on settings, we will do one of the following with unsupport assets
** 1) Play anyway (e.g. an H.264 video might play audio only)
** 2) Use in_swf to play back
** 3) skip
**/
if (needCodecCheck == CODEC_CHECK_UNSURE)
{
FLVHeader *header = reader.GetHeader();
if (header)
{
needCodecCheck=CODEC_CHECK_NONE;
if (!reader.IsStreaming())
{
if (header->hasVideo)
needCodecCheck |= CODEC_CHECK_VIDEO;
if (header->hasAudio)
needCodecCheck |= CODEC_CHECK_AUDIO;
}
if (!header->hasAudio)
{
video_only=true;
video_clock.Start();
}
}
}
if (needCodecCheck)
{
FrameData frameData;
if (reader.GetFrame(codecCheckFrame, frameData))
{
FLVStreamHeader &frameHeader = frameData.header;
if ((needCodecCheck & CODEC_CHECK_AUDIO) && frameHeader.type == FLV::FRAME_TYPE_AUDIO)
{
reader.Seek(frameData.location+15); // TODO: check for -1 return value
uint8_t data[1] = {0};
FLVAudioHeader audioHeader;
size_t bytesRead = reader.Read(data, 1);
if (audioHeader.Read(data, bytesRead))
{
if (Audio_IsSupported(audioHeader.format))
{
needCodecCheck &= ~CODEC_CHECK_AUDIO;
}
else
{
needCodecCheck &= ~CODEC_CHECK_AUDIO;
missingCodecs|=CODEC_CHECK_AUDIO;
video_only=true;
}
}
}
if ((needCodecCheck & CODEC_CHECK_VIDEO) && frameHeader.type == FLV::FRAME_TYPE_VIDEO)
{
reader.Seek(frameData.location+15); // TODO: check for -1 return value
uint8_t data[1] = {0};
FLVVideoHeader videoHeader;
size_t bytesRead = reader.Read(data, 1);
if (videoHeader.Read(data, bytesRead))
{
if (Video_IsSupported(videoHeader.format))
{
needCodecCheck &= ~CODEC_CHECK_VIDEO;
}
else
{
needCodecCheck &= ~CODEC_CHECK_VIDEO;
missingCodecs|=CODEC_CHECK_VIDEO;
}
}
}
codecCheckFrame++;
}
else if (reader.IsEOF())
break;
else if (WaitForSingleObject(killswitch, 55) == WAIT_OBJECT_0)
break;
}
if (needCodecCheck)
continue; // don't start decoding until we've done our codec check
if (missingCodecs)
{
// use in_swf to play this one
if (CheckSWF())
{
HANDLE mainThread = WASABI_API_APP->main_getMainThreadHandle();
if (mainThread)
{
reader.Kill();
Audio_Close();
Video_Stop();
Video_Close();
QueueUserAPC(SWFAPC, mainThread,0);
CloseHandle(mainThread);
return 0;
}
}
else
{
FLVHeader *header = reader.GetHeader();
if (header)
{
bool can_play_something = false;
if (header->hasVideo && !(missingCodecs & CODEC_CHECK_VIDEO))
can_play_something = true; // we can play video
else if (header->hasAudio && !(missingCodecs & CODEC_CHECK_AUDIO))
can_play_something = true; // we can play audio
if (can_play_something)
{
missingCodecs=false;
continue;
}
}
break; // no header or no codecs at all, bail out
}
}
/* --- End Codec Check --- */
FrameData frameData;
if (reader.GetFrame(i, frameData))
{
i++;
uint8_t data[2] = {0};
FLVStreamHeader &frameHeader = frameData.header;
reader.Seek(frameData.location+15); // TODO: check for -1 return value
switch (frameHeader.type)
{
default:
#ifdef _DEBUG
DebugBreak();
#endif
break;
case FLV::FRAME_TYPE_AUDIO: // audio
first_frame_parsed=true;
if (m_need_seek == -1 || !Audio_DecoderReady())
{
FLVAudioHeader audioHeader;
size_t bytesRead = reader.Read(data, 1);
if (audioHeader.Read(data, bytesRead))
{
size_t dataSize = frameHeader.dataSize - 1;
uint8_t *audiodata = (uint8_t *)calloc(dataSize, sizeof(uint8_t));
if (audiodata)
{
bytesRead = reader.Read(audiodata, dataSize);
if (bytesRead != dataSize)
break;
if (!reader.IsStreaming())
{
if (first_timestamp == -1)
first_timestamp = frameHeader.timestamp;
last_timestamp = frameHeader.timestamp;
last_timestamp = plugin.outMod->GetWrittenTime();
}
OnAudio(reader, audiodata, dataSize, audioHeader);
free(audiodata);
}
}
}
break;
case FLV::FRAME_TYPE_VIDEO: // video
first_frame_parsed=true;
if (m_need_seek == -1 || !Video_DecoderReady())
{
FLVVideoHeader videoHeader;
size_t bytesRead = reader.Read(data, 1);
if (videoHeader.Read(data, bytesRead))
{
size_t dataSize = frameHeader.dataSize - 1;
uint8_t *videodata = (uint8_t *)calloc(dataSize, sizeof(uint8_t));
if (videodata)
{
bytesRead = reader.Read(videodata, dataSize);
if (bytesRead != dataSize)
{
free(videodata);
break;
}
if (!OnVideo(videodata, dataSize, videoHeader.format, frameHeader.timestamp))
free(videodata);
}
}
}
break;
case FLV::FRAME_TYPE_METADATA: // metadata
{
first_frame_parsed=true;
size_t dataSize = frameHeader.dataSize;
uint8_t *metadatadata= (uint8_t *)calloc(dataSize, sizeof(uint8_t));
if (metadatadata)
{
size_t bytesRead = reader.Read(metadatadata, dataSize);
if (bytesRead != dataSize)
{
free(metadatadata);
break;
}
FLVMetadata metadata;
metadata.Read(metadatadata, dataSize);
for ( FLVMetadata::Tag *tag : metadata.tags )
{
if (!_wcsicmp(tag->name.str, L"onMetaData"))
{
AMFType *amf_stream_title;
amf_stream_title = tag->parameters->array[L"streamTitle"];
if (amf_stream_title && amf_stream_title->type == AMFType::TYPE_STRING)
{
AMFString *stream_title_string = (AMFString *)amf_stream_title;
Nullsoft::Utility::AutoLock stream_lock(stream_title_guard);
free(stream_title);
stream_title = _wcsdup(stream_title_string->str);
PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
}
AMFType *w, *h;
w = tag->parameters->array[L"width"];
h = tag->parameters->array[L"height"];
if (w && h)
{
width = (int)AMFGetDouble(w);
height = (int)AMFGetDouble(h);
}
AMFType *duration;
duration=tag->parameters->array[L"duration"];
if (duration)
{
hasDuration=true;
plugin.is_seekable=1;
g_length = (int)(AMFGetDouble(duration)*1000.0);
}
// grab the data rate. we'll need this to determine a good pre-buffer.
AMFType *videoDataRate, *audioDataRate;
videoDataRate=tag->parameters->array[L"videodatarate"];
audioDataRate=tag->parameters->array[L"audiodatarate"];
if (videoDataRate || audioDataRate)
{
dataRate_audio = audioDataRate?AMFGetDouble(audioDataRate):0.0;
dataRate_video = videoDataRate?AMFGetDouble(videoDataRate):0.0;
dataRate = dataRate_audio + dataRate_video;
if (dataRate < 1.0f)
dataRate = 720.0f;
plugin.SetInfo((int)dataRate, -1, -1, 1);
prebuffer = (uint64_t)(dataRate * PRE_BUFFER_MS / 8.0);
if (prebuffer > PRE_BUFFER_MAX)
prebuffer=PRE_BUFFER_MAX;
PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
}
}
flvCOM.MetadataCallback(tag);
}
free(metadatadata);
}
}
break;
}
}
else if (reader.IsEOF())
break;
else if (WaitForSingleObject(killswitch, 55) == WAIT_OBJECT_0)
break;
}
reader.SignalKill();
if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
{
outputter.Write(0,0);
outputter.WaitWhilePlaying();
if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
}
SetEvent(killswitch);
Video_Stop();
Video_Close();
reader.Kill();
Audio_Close();
return 0;
}

View File

@ -0,0 +1,34 @@
#include "ProgressiveProcessor.h"
ProgressiveProcessor::ProgressiveProcessor()
{
tempFile[0]=0;
writeCursor=INVALID_HANDLE_VALUE;
wchar_t tempPath[MAX_PATH-14] = {0};
GetTempPath(MAX_PATH-14, tempPath);
GetTempFileName(tempPath, L"wfv", 0, tempFile);
writeCursor=CreateFile(tempFile, GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
processedCursor=CreateFile(tempFile, GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
readCursor=CreateFile(tempFile, GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
}
ProgressiveProcessor::~ProgressiveProcessor()
{
if (writeCursor != INVALID_HANDLE_VALUE)
CloseHandle(writeCursor);
if (tempFile[0])
DeleteFile(tempFile);
}
int ProgressiveProcessor::Write(void *data, size_t datalen, size_t *written)
{
DWORD dw_written=0;
WriteFile(writeCursor, data, (DWORD)datalen, &dw_written, NULL);
*written=dw_written;
writePosition+=dw_written;
return 0;
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "FileProcessor.h"
class ProgressiveProcessor : public FileProcessor
{
public:
ProgressiveProcessor();
~ProgressiveProcessor();
private:
/* FLVProcessor virtual method overrides */
int Write(void *data, size_t datalen, size_t *written);
private:
HANDLE writeCursor;
wchar_t tempFile[MAX_PATH];
};

View File

@ -0,0 +1,112 @@
#include "StreamProcessor.h"
#include "FLVHeader.h"
StreamProcessor::StreamProcessor()
{
buffer.reserve(1024*1024);
bytesWritten=0;
readHeader=false;
}
int StreamProcessor::Write(void *data, size_t datalen, size_t *written)
{
*written = buffer.write(data, datalen);
bytesWritten += *written;
if (*written != datalen)
return FLVPROCESSOR_WRITE_WAIT; // tell the FLVReader we need a break :)
return FLVPROCESSOR_WRITE_OK;
}
int StreamProcessor::Process()
{
// we're actually not going to parse anything until the GetFrame() call.
// since we can't seek anyway
// but we do need to validate that this is an FLV stream
if (!readHeader)
{
if (buffer.size() >= 9) // see if we have enough data
{
uint8_t data[9] = {0};
buffer.read(data, 9);
if (header.Read(data, 9))
{
readHeader=true;
return FLV_OK;
}
else // not an FLV header
{
return FLV_ERROR;
}
}
}
return FLV_NEED_MORE_DATA;
}
uint64_t StreamProcessor::GetProcessedPosition()
{
// since we parse the bitstream on-demand, we'll just return how many bytes we've buffered so far
if (readHeader) // make sure we've at least found the main FLV header
return bytesWritten;
else
return 0;
}
uint32_t StreamProcessor::GetMaxTimestamp()
{
return -1000; // it's a stream! no length!
}
uint64_t StreamProcessor::Seek(uint64_t position)
{
// we can't really seek in a traditional sense, but since Seek gets called to simply advance the read pointer,
// we'll advance within our buffer
// we always set FrameData::location to 0, so can just call like this
return buffer.advance((size_t)position);
}
size_t StreamProcessor::Read(void *data, size_t bytes)
{
// easy :)
return buffer.read(data, bytes);
}
// the fun happens here
bool StreamProcessor::GetFrame(size_t frameIndex, FrameData &frameData)
{
// since this is a stream, we're going to ignore frameIndex and just give them the next frame
if (buffer.size() >= 15)
{
uint8_t data[15] = {0};
buffer.peek(data, 15);
if (frameData.header.Read(data, 15))
{
// first, make sure we have enough data buffered to read the whole thing
// because the next thing to get called after this function is Read()
if (frameData.header.dataSize + 15 <= buffer.size())
{
// since we're streaming and only processing one frame at a time
// we're going to set the returned frame's location to 0 (start of the ring buffer)
frameData.location = 0;
return true;
}
}
}
return false; // not ready for a frame yet
}
bool StreamProcessor::GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame)
{
return false; // can't seek!
}
FLVHeader *StreamProcessor::GetHeader()
{
if (readHeader)
return &header;
else
return 0;
}

View File

@ -0,0 +1,28 @@
#pragma once
#include "FLVProcessor.h"
#include "../nu/RingBuffer.h"
class StreamProcessor : public FLVProcessor
{
public:
StreamProcessor();
private:
int Write(void *data, size_t datalen, size_t *written);
int Process();
uint64_t Seek(uint64_t position);
size_t Read(void *data, size_t bytes);
uint64_t GetProcessedPosition();
bool GetFrame(size_t frameIndex, FrameData &frameData);
uint32_t GetMaxTimestamp();
bool GetPosition(int time_in_ms, size_t *frameIndex, bool needVideoKeyFrame);
bool IsStreaming() { return true; }
FLVHeader *GetHeader();
private:
RingBuffer buffer;
uint64_t bytesWritten;
bool readHeader;
FLVHeader header;
};

View File

@ -0,0 +1,291 @@
#include "main.h"
#include "VideoThread.h"
#include "api__in_flv.h"
#include "FLVVideoHeader.h"
#include <shlwapi.h>
#include <windows.h>
#include "../nu/threadname.h"
#include <api/service/waservicefactory.h>
#include "../nu/AutoLock.h"
#include "../nu/SampleQueue.h"
int width, height;
IVideoOutput *videoOutput=0;
static HANDLE videoThread=0;
static volatile LONG video_flush=0;
static ifc_flvvideodecoder *videoDecoder=0;
bool video_opened=false;
static HANDLE coded_frames_event=0;
static HANDLE video_flush_event=0;
static Nullsoft::Utility::LockGuard coded_frames_guard;
extern bool video_only;
void Video_Init()
{
video_opened=false;
videoDecoder=0;
videoThread=0;
width=0;
height=0;
if (coded_frames_event == 0)
coded_frames_event = CreateEvent(NULL, FALSE, FALSE, NULL);
if (video_flush_event == 0)
video_flush_event = CreateEvent(NULL, TRUE, FALSE, NULL);
video_flush=0;
}
struct FRAMEDATA
{
FRAMEDATA()
{
data=0;
length=0;
timestamp=0;
}
~FRAMEDATA()
{
free(data);
}
void Reset()
{
free(data);
data=0;
length=0;
timestamp=0;
}
void Set(void *_data, size_t _length, uint32_t ts)
{
data = _data;
length = _length;
timestamp = ts;
}
void *data;
size_t length;
uint32_t timestamp;
};
static SampleQueue<FRAMEDATA> coded_frames;
extern int GetOutputTime();
static void DecodeVideo(FRAMEDATA *framedata)
{
if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
{
int decodeResult = videoDecoder->DecodeSample(framedata->data, framedata->length, framedata->timestamp);
if (decodeResult == FLV_VIDEO_SUCCESS)
{
void *data, *decoder_data;
uint64_t timestamp=framedata->timestamp;
while (videoDecoder->GetPicture(&data, &decoder_data, &timestamp) == FLV_VIDEO_SUCCESS)
{
if (!video_opened)
{
int color_format;
if (videoDecoder->GetOutputFormat(&width, &height, &color_format) == FLV_VIDEO_SUCCESS)
{
videoOutput->extended(VIDUSER_SET_THREAD_SAFE, 1, 0);
videoOutput->open(width, height, 0, 1.0, color_format);
video_opened=true;
}
}
if (video_opened)
{
again:
int realTime =(int)GetOutputTime();
if (timestamp > (realTime+5))
{
HANDLE handles[] = {killswitch, video_flush_event};
int ret=WaitForMultipleObjects(2, handles, FALSE, (DWORD)(timestamp-realTime));
if (ret != WAIT_TIMEOUT)
{
videoDecoder->FreePicture(data, decoder_data);
framedata->Reset();
return ;
}
goto again; // TODO: handle paused state a little better than this
}
videoOutput->draw(data);
}
videoDecoder->FreePicture(data, decoder_data);
}
}
}
framedata->Reset();
}
DWORD CALLBACK VideoProcedure(LPVOID param)
{
SetThreadName(-1,"FLV_VideoProcedure");
HANDLE wait_handles[] = { killswitch, video_flush_event, coded_frames_event};
int ret;
do
{
ret = WaitForMultipleObjects(3, wait_handles, FALSE, INFINITE);
if (ret == WAIT_OBJECT_0 + 1)
{
if (video_flush)
{
InterlockedDecrement(&video_flush);
if (videoDecoder)
videoDecoder->Flush();
}
ResetEvent(video_flush_event);
}
else if (ret == WAIT_OBJECT_0 + 2)
{
FRAMEDATA *frame_data = 0;
while (frame_data = coded_frames.PopProcessed())
{
DecodeVideo(frame_data);
frame_data->Reset();
coded_frames.PushFree(frame_data);
}
}
} while (ret != WAIT_OBJECT_0);
if (video_opened && videoOutput)
videoOutput->close();
video_opened=false;
return 0;
}
bool Video_IsSupported(int type)
{
size_t n = 0;
waServiceFactory *factory = NULL;
while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++))
{
svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface();
if (creator)
{
int supported = creator->HandlesVideo(type);
factory->releaseInterface(creator);
if (supported == svc_flvdecoder::CREATEDECODER_SUCCESS)
return true;
}
}
return false;
}
static ifc_flvvideodecoder *CreateVideoDecoder(int type)
{
ifc_flvvideodecoder *video_decoder = 0;
size_t n = 0;
waServiceFactory *factory = NULL;
while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++))
{
svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface();
if (creator)
{
if (creator->CreateVideoDecoder(type, width, height, &video_decoder) == FLV_VIDEO_SUCCESS)
return video_decoder;
factory->releaseInterface(creator);
}
}
return 0;
}
// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F}
static const GUID playbackConfigGroupGUID =
{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } };
bool OnVideo(void *data, size_t length, int type, unsigned __int32 timestamp)
{
if (!videoDecoder)
{
videoDecoder = CreateVideoDecoder(type);
}
if (videoDecoder)
{
if (!video_only && !videoThread)
{
DWORD threadId;
videoThread = CreateThread(0, 0, VideoProcedure, 0, 0, &threadId);
SetThreadPriority(videoThread, (INT)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST));
}
FRAMEDATA *new_frame = coded_frames.PopFree();
if (new_frame)
{
new_frame->Set(data, length, timestamp);
if (video_only)
{
DecodeVideo(new_frame);
new_frame->Reset();
coded_frames.PushFree(new_frame);
}
else
{
coded_frames.PushProcessed(new_frame);
SetEvent(coded_frames_event);
}
}
return true;
}
return false;
}
void Video_Stop()
{
if (video_only)
{
ResetEvent(coded_frames_event);
Nullsoft::Utility::AutoLock l(coded_frames_guard);
coded_frames.Trim();
if (video_opened && videoOutput)
videoOutput->close();
video_opened=false;
}
else
{
if (videoThread)
WaitForSingleObject(videoThread, INFINITE);
videoThread=0;
InterlockedIncrement(&video_flush);
ResetEvent(coded_frames_event);
Nullsoft::Utility::AutoLock l(coded_frames_guard);
coded_frames.Trim();
}
}
void Video_Close()
{
video_opened=false;
if (videoDecoder)
{
videoDecoder->Close();
videoDecoder=0;
}
}
void VideoFlush()
{
if (video_only)
{
if (videoDecoder)
videoDecoder->Flush();
}
else if (videoThread)
{
InterlockedIncrement(&video_flush);
ResetEvent(coded_frames_event);
coded_frames.Trim();
SetEvent(video_flush_event);
}
}
bool Video_DecoderReady()
{
if (!videoDecoder)
return true;
return !!videoDecoder->Ready();
}

View File

@ -0,0 +1,21 @@
#ifndef NULLSOFT_IN_FLV_VIDEOTHREAD_H
#define NULLSOFT_IN_FLV_VIDEOTHREAD_H
#include "../nsv/dec_if.h"
#include "../Winamp/wa_ipc.h"
#include "svc_flvdecoder.h"
#include "ifc_flvvideodecoder.h"
bool OnVideo(void *data, size_t length, int type, unsigned __int32 timestamp);
void VideoFlush();
extern int width, height;
extern IVideoOutput *videoOutput;
extern bool video_opened;
void Video_Init();
void Video_Stop();
void Video_Close();
bool Video_IsSupported(int type);
bool Video_DecoderReady();
#endif

View File

@ -0,0 +1,12 @@
#ifndef NULLSOFT_IN_FLV_API_H
#define NULLSOFT_IN_FLV_API_H
#include "../Agave/Config/api_config.h"
extern api_config *AGAVE_API_CONFIG;
#include <api/application/api_application.h>
#define WASABI_API_APP applicationApi
#include "../Agave/Language/api_language.h"
#endif // !NULLSOFT_IN_FLV_API_H

View File

@ -0,0 +1,62 @@
#pragma once
enum
{
FLV_AUDIO_SUCCESS = 0,
FLV_AUDIO_FAILURE = 1,
FLV_AUDIO_NEEDS_MORE_INPUT = 2,
};
class ifc_flvaudiodecoder : public Dispatchable
{
protected:
ifc_flvaudiodecoder() {}
~ifc_flvaudiodecoder() {}
public:
int GetOutputFormat(unsigned int *sample_rate, unsigned int *channels, unsigned int *bits);
int DecodeSample(const void *input_buffer, size_t input_buffer_bytes, void *samples, size_t *samples_size_bytes, double *bitrate);
void Flush();
void Close();
int Ready(); // returns 1 for ready [default], 0 for not ready. Some codecs in FLV use the first packet for decoder config data. return 1 from this once you've gotten it
void SetPreferences(unsigned int max_channels, unsigned int preferred_bits);
DISPATCH_CODES
{
FLV_AUDIO_GETOUTPUTFORMAT = 0,
FLV_AUDIO_DECODE = 1,
FLV_AUDIO_FLUSH = 2,
FLV_AUDIO_CLOSE = 3,
FLV_AUDIO_READY = 4,
FLV_AUDIO_SETPREFERENCES=5,
};
};
inline int ifc_flvaudiodecoder::GetOutputFormat(unsigned int *sample_rate, unsigned int *channels, unsigned int *bits)
{
return _call(FLV_AUDIO_GETOUTPUTFORMAT, (int)FLV_AUDIO_FAILURE, sample_rate, channels, bits);
}
inline int ifc_flvaudiodecoder::DecodeSample(const void *input_buffer, size_t input_buffer_bytes, void *samples, size_t *samples_size_bytes, double *bitrate)
{
return _call(FLV_AUDIO_DECODE, (int)FLV_AUDIO_FAILURE, input_buffer, input_buffer_bytes, samples, samples_size_bytes, bitrate);
}
inline void ifc_flvaudiodecoder::Flush()
{
_voidcall(FLV_AUDIO_FLUSH);
}
inline void ifc_flvaudiodecoder::Close()
{
_voidcall(FLV_AUDIO_CLOSE);
}
inline int ifc_flvaudiodecoder::Ready()
{
return _call(FLV_AUDIO_READY, (int)1); // default to true so that decoders that don't implement won't block in_flv from seeking
}
inline void ifc_flvaudiodecoder::SetPreferences(unsigned int max_channels, unsigned int preferred_bits)
{
_voidcall(FLV_AUDIO_SETPREFERENCES, max_channels, preferred_bits);
}

View File

@ -0,0 +1,67 @@
#pragma once
enum
{
FLV_VIDEO_SUCCESS = 0,
FLV_VIDEO_FAILURE = 1,
};
class ifc_flvvideodecoder : public Dispatchable
{
protected:
ifc_flvvideodecoder() {}
~ifc_flvvideodecoder() {}
public:
int GetOutputFormat(int *x, int *y, int *color_format);
int DecodeSample(const void *inputBuffer, size_t inputBufferBytes, int32_t timestamp);
void Flush();
void Close();
int GetPicture(void **data, void **decoder_data, uint64_t *timestamp);
void FreePicture(void *data, void *decoder_data);
int Ready(); // returns 1 for ready [default], 0 for not ready. Some codecs in FLV use the first packet for decoder config data. return 1 from this once you've gotten it
DISPATCH_CODES
{
FLV_VIDEO_GETOUTPUTFORMAT = 0,
FLV_VIDEO_DECODE = 1,
FLV_VIDEO_FLUSH = 2,
FLV_VIDEO_CLOSE = 3,
FLV_VIDEO_GET_PICTURE = 4,
FLV_VIDEO_FREE_PICTURE = 5,
FLV_VIDEO_READY = 6,
};
};
inline int ifc_flvvideodecoder::GetOutputFormat(int *x, int *y, int *color_format)
{
return _call(FLV_VIDEO_GETOUTPUTFORMAT, (int)FLV_VIDEO_FAILURE, x, y, color_format);
}
inline int ifc_flvvideodecoder::DecodeSample(const void *inputBuffer, size_t inputBufferBytes, int32_t timestamp)
{
return _call(FLV_VIDEO_DECODE, (int)FLV_VIDEO_FAILURE, inputBuffer, inputBufferBytes, timestamp);
}
inline void ifc_flvvideodecoder::Flush()
{
_voidcall(FLV_VIDEO_FLUSH);
}
inline void ifc_flvvideodecoder::Close()
{
_voidcall(FLV_VIDEO_CLOSE);
}
inline int ifc_flvvideodecoder::GetPicture(void **data, void **decoder_data, uint64_t *timestamp)
{
return _call(FLV_VIDEO_GET_PICTURE, (int)FLV_VIDEO_FAILURE, data, decoder_data, timestamp);
}
inline void ifc_flvvideodecoder::FreePicture(void *data, void *decoder_data)
{
_voidcall(FLV_VIDEO_FREE_PICTURE, data, decoder_data);
}
inline int ifc_flvvideodecoder::Ready()
{
return _call(FLV_VIDEO_READY, (int)1); // default to true so that decoders that don't implement won't block in_flv from seeking
}

View File

@ -0,0 +1,85 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""afxres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"#include ""version.rc2""\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDS_NULLSOFT_FLV "Nullsoft Flash Video Decoder v%s"
65535 "{EC959D43-9122-4807-B928-7B46207AFA49}"
END
STRINGTABLE
BEGIN
IDS_NULLSOFT_FLV_OLD "Nullsoft Flash Video Decoder"
IDS_BUFFERING "Buffering"
IDS_FLASH_VIDEO "Flash Video"
IDS_VP6_FLASH_VIDEO "VP6 Flash Video"
IDS_FAMILY_STRING "Flash Video"
IDS_ABOUT_TEXT "%s\n<> 2006-2023 Winamp SA\nWritten by: Ben Allison\nBuild date: %s"
END
#endif // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
#include "version.rc2"
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@ -0,0 +1,30 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29609.76
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_flv", "in_flv.vcxproj", "{405DF558-062D-43AB-B7FB-02C4CF8B28CE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
Debug|x64 = Debug|x64
Release|Win32 = Release|Win32
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Debug|Win32.ActiveCfg = Debug|Win32
{405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Debug|Win32.Build.0 = Debug|Win32
{405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Debug|x64.ActiveCfg = Debug|x64
{405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Debug|x64.Build.0 = Debug|x64
{405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Release|Win32.ActiveCfg = Release|Win32
{405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Release|Win32.Build.0 = Release|Win32
{405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Release|x64.ActiveCfg = Release|x64
{405DF558-062D-43AB-B7FB-02C4CF8B28CE}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B6180127-9081-4796-819C-0E19EBFB2BC4}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,295 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{405DF558-062D-43AB-B7FB-02C4CF8B28CE}</ProjectGuid>
<RootNamespace>in_flv</RootNamespace>
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
<IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
<IncludePath>$(IncludePath)</IncludePath>
<LibraryPath>$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
<IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
<IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
<IncludePath>$(IncludePath)</IncludePath>
<LibraryPath>$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
<IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Label="Vcpkg">
<VcpkgEnabled>false</VcpkgEnabled>
</PropertyGroup>
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<VcpkgConfiguration>Debug</VcpkgConfiguration>
</PropertyGroup>
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<VcpkgConfiguration>Debug</VcpkgConfiguration>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;IN_FLV_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<SmallerTypeCheck>false</SmallerTypeCheck>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BufferSecurityCheck>true</BufferSecurityCheck>
<FunctionLevelLinking>true</FunctionLevelLinking>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
</ClCompile>
<Link>
<AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
<SubSystem>Windows</SubSystem>
<RandomizedBaseAddress>false</RandomizedBaseAddress>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>
<Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
<Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;_USRDLL;IN_FLV_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<SmallerTypeCheck>false</SmallerTypeCheck>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BufferSecurityCheck>true</BufferSecurityCheck>
<FunctionLevelLinking>true</FunctionLevelLinking>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
</ClCompile>
<Link>
<AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
<SubSystem>Windows</SubSystem>
<RandomizedBaseAddress>false</RandomizedBaseAddress>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>
<Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
<Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<Optimization>MinSpace</Optimization>
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
<AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;IN_FLV_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>true</StringPooling>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<BufferSecurityCheck>true</BufferSecurityCheck>
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
<ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>None</DebugInformationFormat>
<DisableSpecificWarnings>4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<SmallerTypeCheck>false</SmallerTypeCheck>
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
</ClCompile>
<Link>
<AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<GenerateDebugInformation>false</GenerateDebugInformation>
<ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<RandomizedBaseAddress>false</RandomizedBaseAddress>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>
<Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
<Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<Optimization>MinSpace</Optimization>
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
<AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;IN_FLV_EXPORTS;UNICODE_INPUT_PLUGIN;_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>true</StringPooling>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<BufferSecurityCheck>true</BufferSecurityCheck>
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
<ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>None</DebugInformationFormat>
<DisableSpecificWarnings>4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
</ClCompile>
<Link>
<AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<GenerateDebugInformation>false</GenerateDebugInformation>
<ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<RandomizedBaseAddress>false</RandomizedBaseAddress>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>
<Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
<Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\..\nu\RingBuffer.cpp" />
<ClCompile Include="..\..\..\nu\SpillBuffer.cpp" />
<ClCompile Include="AMFDispatch.cpp" />
<ClCompile Include="AMFObject.cpp" />
<ClCompile Include="BackgroundDownloader.cpp" />
<ClCompile Include="ExtendedInfo.cpp" />
<ClCompile Include="FileProcessor.cpp" />
<ClCompile Include="FLVAudioHeader.cpp" />
<ClCompile Include="FLVCOM.cpp" />
<ClCompile Include="FLVHeader.cpp" />
<ClCompile Include="FLVMetadata.cpp" />
<ClCompile Include="FLVProcessor.cpp" />
<ClCompile Include="FLVReader.cpp" />
<ClCompile Include="FLVStreamHeader.cpp" />
<ClCompile Include="FLVVideoHeader.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="PlayThread.cpp" />
<ClCompile Include="ProgressiveProcessor.cpp" />
<ClCompile Include="StreamProcessor.cpp" />
<ClCompile Include="VideoThread.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\nu\RingBuffer.h" />
<ClInclude Include="..\..\..\nu\SpillBuffer.h" />
<ClInclude Include="..\..\..\nu\VideoClock.h" />
<ClInclude Include="AMFDispatch.h" />
<ClInclude Include="AMFObject.h" />
<ClInclude Include="api__in_flv.h" />
<ClInclude Include="BackgroundDownloader.h" />
<ClInclude Include="FileProcessor.h" />
<ClInclude Include="FLVAudioHeader.h" />
<ClInclude Include="FLVCOM.h" />
<ClInclude Include="FLVHeader.h" />
<ClInclude Include="FLVMetadata.h" />
<ClInclude Include="FLVProcessor.h" />
<ClInclude Include="FLVReader.h" />
<ClInclude Include="FLVStreamHeader.h" />
<ClInclude Include="FLVUtil.h" />
<ClInclude Include="FLVVideoHeader.h" />
<ClInclude Include="ifc_flvaudiodecoder.h" />
<ClInclude Include="ifc_flvvideodecoder.h" />
<ClInclude Include="main.h" />
<ClInclude Include="ProgressiveProcessor.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="StreamProcessor.h" />
<ClInclude Include="svc_flvdecoder.h" />
<ClInclude Include="VideoThread.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="in_flv.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
<Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="AMFDispatch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="AMFObject.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="BackgroundDownloader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ExtendedInfo.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FileProcessor.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FLVAudioHeader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FLVCOM.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FLVHeader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FLVMetadata.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FLVProcessor.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FLVReader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FLVStreamHeader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FLVVideoHeader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="PlayThread.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ProgressiveProcessor.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="StreamProcessor.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="VideoThread.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\nu\RingBuffer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\nu\SpillBuffer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="VideoThread.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="svc_flvdecoder.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="StreamProcessor.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ProgressiveProcessor.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="main.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ifc_flvvideodecoder.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ifc_flvaudiodecoder.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FLVVideoHeader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FLVUtil.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FLVStreamHeader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FLVReader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FLVProcessor.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FLVMetadata.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FLVAudioHeader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FLVHeader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FLVCOM.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FileProcessor.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="BackgroundDownloader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="api__in_flv.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="AMFObject.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="AMFDispatch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\nu\RingBuffer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\nu\SpillBuffer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\nu\VideoClock.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Header Files">
<UniqueIdentifier>{9916d7e8-2b6f-493b-8618-234dec0cefeb}</UniqueIdentifier>
</Filter>
<Filter Include="Ressource Files">
<UniqueIdentifier>{888ab1e4-8d93-40b5-92f8-35339ac6179a}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files">
<UniqueIdentifier>{0f72d524-22df-4752-8f22-5c1db90c62b0}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="in_flv.rc">
<Filter>Ressource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View File

@ -0,0 +1,432 @@
#include "main.h"
#include <shlwapi.h>
#include "api__in_flv.h"
#include <stdio.h>
#include <api/service/waservicefactory.h>
#include "../Winamp/wa_ipc.h"
#include "resource.h"
#include "FileProcessor.h"
#include "FLVCOM.h"
#include "VideoThread.h"
#include "../f263/flv_f263_decoder.h"
#include <strsafe.h>
#define FLV_PLUGIN_VERSION L"1.47"
template <class api_T>
static void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
{
if (plugin.service)
{
waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
if (factory)
api_t = reinterpret_cast<api_T *>( factory->getInterface() );
}
}
template <class api_T>
static void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
{
if (plugin.service && api_t)
{
waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
if (factory)
factory->releaseInterface(api_t);
}
api_t = NULL;
}
/* Wasabi services */
api_application *WASABI_API_APP=0;
api_config *AGAVE_API_CONFIG=0;
api_language *WASABI_API_LNG = 0;
In_Module *swf_mod = 0;
HMODULE in_swf = 0;
HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
wchar_t pluginName[256] = {0};
wchar_t *playFile=0;
HANDLE killswitch, playthread=0;
int g_length=-1000;
bool video_only=false;
int paused = 0;
nu::VideoClock video_clock;
int m_need_seek=-1;
extern uint32_t last_timestamp;
int pan = 0, volume = -666;
wchar_t *stream_title=0;
Nullsoft::Utility::LockGuard stream_title_guard;
// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F}
static const GUID playbackConfigGroupGUID =
{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } };
void SetFileExtensions(void)
{
static char fileExtensionsString[256] = {0}; // "FLV\0Flash Video\0"
char* end = 0;
size_t remaining;
StringCchCopyExA(fileExtensionsString, 256, "FLV", &end, &remaining, 0);
StringCchCopyExA(end+1, remaining-1, WASABI_API_LNGSTRING(IDS_FLASH_VIDEO), 0, 0, 0);
plugin.FileExtensions = fileExtensionsString;
}
int Init()
{
if (!IsWindow(plugin.hMainWindow))
return IN_INIT_FAILURE;
ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID);
ServiceBuild(WASABI_API_APP, applicationApiServiceGuid);
ServiceBuild(WASABI_API_LNG, languageApiGUID);
// need to have this initialised before we try to do anything with localisation features
WASABI_API_START_LANG(plugin.hDllInstance,InFlvLangGUID);
if (plugin.service->service_getServiceByGuid(flv_h263_guid))
StringCchPrintfW(pluginName,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_FLV),FLV_PLUGIN_VERSION L" (h)");
else
StringCchPrintfW(pluginName,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_FLV),FLV_PLUGIN_VERSION);
plugin.description = (char*)pluginName;
SetFileExtensions();
DispatchInfo flvDisp = { L"FLV", &flvCOM };
SendMessage(plugin.hMainWindow, WM_WA_IPC, (WPARAM)&flvDisp, IPC_ADD_DISPATCH_OBJECT);
killswitch = CreateEvent(NULL, TRUE, FALSE, NULL);
return IN_INIT_SUCCESS;
}
void Quit()
{
CloseHandle(killswitch);
ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID);
ServiceRelease(WASABI_API_APP, applicationApiServiceGuid);
ServiceRelease(WASABI_API_LNG, languageApiGUID);
if (in_swf)
FreeLibrary(in_swf);
}
void GetFileInfo(const wchar_t *file, wchar_t *title, int *length_in_ms)
{
if (!file || !*file)
{
if (swf_mod)
{
swf_mod->GetFileInfo(file, title, length_in_ms);
return;
}
file = playFile;
}
// no title support as it's not always stored in a standard way in FLV
if (title)
{
Nullsoft::Utility::AutoLock stream_lock(stream_title_guard);
if (stream_title && (!file || !file[0]))
{
lstrcpyn(title, stream_title, GETFILEINFO_TITLE_LENGTH);
}
else
{
lstrcpyn(title, file ? file : L"", GETFILEINFO_TITLE_LENGTH);
PathStripPath(title);
}
}
// calculate length
if (file == playFile) // currently playing song?
*length_in_ms = g_length; // easy!
else if (PathIsURLW(file)) // don't calculate lengths for URLs since we'd have to connect
{
*length_in_ms = -1;
}
else
{
// open the file and find the "duration" metadata
FileProcessor processor(file);
size_t frameIndex=0;
FrameData frameData;
int length=-1;
bool found=false;
do
{
// enumerate the frames as we process
// this function will fail the first few times
// because Process() hasn't been called enough
if (processor.GetFrame(frameIndex, frameData))
{
if (frameData.header.type == FLV::FRAME_TYPE_METADATA)
{
if (processor.Seek(frameData.location + 15) == -1)
break;
size_t dataSize = frameData.header.dataSize;
uint8_t *metadatadata= (uint8_t *)calloc(dataSize, sizeof(uint8_t));
if (metadatadata)
{
size_t bytesRead = processor.Read(metadatadata, dataSize);
if (bytesRead != dataSize)
{
free(metadatadata);
break;
}
FLVMetadata metadata;
metadata.Read(metadatadata, dataSize);
for ( FLVMetadata::Tag *tag : metadata.tags )
{
if ( !_wcsicmp( tag->name.str, L"onMetaData" ) )
{
AMFType *duration = tag->parameters->array[ L"duration" ];
if ( duration )
{
length = (int)( AMFGetDouble( duration ) * 1000.0 );
found = true;
}
}
}
free(metadatadata);
}
else
break;
}
frameIndex++;
}
}
while (!found && processor.Process() == FLV_OK); // for local files, any return value other than FLV_OK is a failure
*length_in_ms = length;
}
}
int InfoBox(const wchar_t *file, HWND hwndParent)
{
return INFOBOX_UNCHANGED;
}
int IsOurFile(const wchar_t *file)
{
return 0;
}
int Play(const wchar_t *file)
{
{
Nullsoft::Utility::AutoLock stream_lock(stream_title_guard);
free(stream_title);
stream_title=0;
}
if (!videoOutput)
videoOutput = (IVideoOutput *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT);
video_only=false;
m_need_seek = -1;
video_clock.Reset();
paused=0;
ResetEvent(killswitch);
free(playFile);
playFile = _wcsdup(file);
playthread=CreateThread(0, 0, PlayProcedure, 0, 0, 0);
SetThreadPriority(playthread, (INT)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST));
return 0; // success
}
void Pause()
{
paused = 1;
if (swf_mod)
{
swf_mod->Pause();
}
else if (video_only)
{
video_clock.Pause();
}
else
{
plugin.outMod->Pause(1);
}
}
void UnPause()
{
paused = 0;
if (swf_mod)
{
swf_mod->UnPause();
}
else if (video_only)
{
video_clock.Unpause();
}
else
{
plugin.outMod->Pause(0);
}
}
int IsPaused()
{
if (swf_mod)
return swf_mod->IsPaused();
return paused;
}
void Stop()
{
if (swf_mod)
{
swf_mod->Stop();
swf_mod=0;
return;
}
SetEvent(killswitch);
WaitForSingleObject(playthread, INFINITE);
playthread=0;
plugin.outMod->Close();
plugin.SAVSADeInit();
paused=0;
}
int GetLength()
{
if (swf_mod)
{
return swf_mod->GetLength();
}
return g_length;
}
int GetOutputTime()
{
if (swf_mod)
{
return swf_mod->GetOutputTime();
}
else if (video_only)
{
return video_clock.GetOutputTime();
}
else if (plugin.outMod)
{
return plugin.outMod->GetOutputTime();
}
else
return 0;
}
void SetOutputTime(int time_in_ms)
{
if (swf_mod)
{
swf_mod->SetOutputTime(time_in_ms);
return ;
}
m_need_seek=time_in_ms;
}
void SetVolume(int _volume)
{
if (swf_mod)
{
swf_mod->SetVolume(_volume);
return ;
}
volume = _volume;
if (plugin.outMod)
plugin.outMod->SetVolume(volume);
}
void SetPan(int _pan)
{
if (swf_mod)
{
swf_mod->SetPan(_pan);
return ;
}
pan = _pan;
if (plugin.outMod)
plugin.outMod->SetPan(pan);
}
void EQSet(int on, char data[10], int preamp)
{
if (swf_mod)
{
swf_mod->EQSet(on, data, preamp);
return;
}
}
int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message)
{
MSGBOXPARAMS msgbx = {sizeof(MSGBOXPARAMS),0};
msgbx.lpszText = message;
msgbx.lpszCaption = title;
msgbx.lpszIcon = MAKEINTRESOURCE(102);
msgbx.hInstance = GetModuleHandle(0);
msgbx.dwStyle = MB_USERICON;
msgbx.hwndOwner = parent;
return MessageBoxIndirect(&msgbx);
}
void About(HWND hwndParent)
{
wchar_t message[1024] = {0}, text[1024] = {0};
WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_FLV_OLD,text,1024);
StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT),
plugin.description, TEXT(__DATE__));
DoAboutMessageBox(hwndParent,text,message);
}
In_Module plugin =
{
IN_VER_RET,
"nullsoft(in_flv.dll)",
0,
0,
0 /*"FLV\0Flash Video\0"*/,
1, // not seekable, for now
1,
About,
About,
Init,
Quit,
GetFileInfo,
InfoBox,
IsOurFile,
Play,
Pause,
UnPause,
IsPaused,
Stop,
GetLength,
GetOutputTime,
SetOutputTime,
SetVolume,
SetPan,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
EQSet,
0,
0
};
extern "C" __declspec(dllexport) In_Module * winampGetInModule2()
{
return &plugin;
}

View File

@ -0,0 +1,27 @@
#ifndef NULLSOFT_IN_FLV_MAIN_H
#define NULLSOFT_IN_FLV_MAIN_H
#include <windows.h>
#include "../Winamp/in2.h"
#include "../nu/VideoClock.h"
#include "../nu/AutoLock.h"
/* main.cpp */
extern In_Module plugin;
extern In_Module *swf_mod;
extern HMODULE in_swf;
extern wchar_t *playFile;
extern HANDLE killswitch;
extern int m_need_seek;
extern int paused;
extern int g_length;
extern nu::VideoClock video_clock;
extern wchar_t *stream_title;
extern Nullsoft::Utility::LockGuard stream_title_guard;
/* PlayThread.cpp */
DWORD CALLBACK PlayProcedure(LPVOID param);
#endif

View File

@ -0,0 +1,23 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by in_flv.rc
//
#define IDS_NULLSOFT_FLV_OLD 0
#define IDS_BUFFERING 2
#define IDS_FLASH_VIDEO 3
#define IDS_STRING4 4
#define IDS_VP6_FLASH_VIDEO 4
#define IDS_FAMILY_STRING 5
#define IDS_ABOUT_TEXT 6
#define IDS_NULLSOFT_FLV 65534
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 105
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

View File

@ -0,0 +1,52 @@
#pragma once
#include <bfc/dispatch.h>
#include <api/service/services.h>
class ifc_flvaudiodecoder;
class ifc_flvvideodecoder;
class svc_flvdecoder : public Dispatchable
{
protected:
svc_flvdecoder() {}
~svc_flvdecoder() {}
public:
static FOURCC getServiceType() { return WaSvc::FLVDECODER; }
enum
{
CREATEDECODER_SUCCESS = 0,
CREATEDECODER_NOT_MINE = -1, // graceful failure
CREATEDECODER_FAILURE = 1, // generic failure - format_type is ours but we weren't able to create the decoder
};
int CreateAudioDecoder(int stereo, int bits, int sample_rate, int format, ifc_flvaudiodecoder **decoder);
int CreateVideoDecoder(int format_type, int width, int height, ifc_flvvideodecoder **decoder);
int HandlesAudio(int format_type);
int HandlesVideo(int format_type);
DISPATCH_CODES
{
CREATE_AUDIO_DECODER = 0,
CREATE_VIDEO_DECODER = 1,
HANDLES_AUDIO=2,
HANDLES_VIDEO=3,
};
};
inline int svc_flvdecoder::CreateAudioDecoder(int stereo, int bits, int sample_rate, int format, ifc_flvaudiodecoder **decoder)
{
return _call(CREATE_AUDIO_DECODER, (int)CREATEDECODER_NOT_MINE, stereo, bits, sample_rate, format, decoder);
}
inline int svc_flvdecoder::CreateVideoDecoder(int format_type, int width, int height, ifc_flvvideodecoder **decoder)
{
return _call(CREATE_VIDEO_DECODER, (int)CREATEDECODER_NOT_MINE, format_type,width, height, decoder);
}
inline int svc_flvdecoder::HandlesAudio(int format_type)
{
return _call(HANDLES_AUDIO, (int)CREATEDECODER_NOT_MINE, format_type);
}
inline int svc_flvdecoder::HandlesVideo(int format_type)
{
return _call(HANDLES_VIDEO, (int)CREATEDECODER_NOT_MINE, format_type);
}

View File

@ -0,0 +1,39 @@
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
#include "../../../Winamp/buildType.h"
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,47,0,0
PRODUCTVERSION WINAMP_PRODUCTVER
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x4L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Winamp SA"
VALUE "FileDescription", "Winamp Input Plug-in"
VALUE "FileVersion", "1,47,0,0"
VALUE "InternalName", "Nullsoft Flash Video Decoder"
VALUE "LegalCopyright", "Copyright <20> 2006-2023 Winamp SA"
VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
VALUE "OriginalFilename", "in_flv.dll"
VALUE "ProductName", "Winamp"
VALUE "ProductVersion", STR_WINAMP_PRODUCTVER
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END