diff options
Diffstat (limited to 'Src/Plugins/Input/in_flv')
45 files changed, 4671 insertions, 0 deletions
diff --git a/Src/Plugins/Input/in_flv/AMFDispatch.cpp b/Src/Plugins/Input/in_flv/AMFDispatch.cpp new file mode 100644 index 00000000..0bd8af76 --- /dev/null +++ b/Src/Plugins/Input/in_flv/AMFDispatch.cpp @@ -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; +} diff --git a/Src/Plugins/Input/in_flv/AMFDispatch.h b/Src/Plugins/Input/in_flv/AMFDispatch.h new file mode 100644 index 00000000..3a593875 --- /dev/null +++ b/Src/Plugins/Input/in_flv/AMFDispatch.h @@ -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; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/AMFObject.cpp b/Src/Plugins/Input/in_flv/AMFObject.cpp new file mode 100644 index 00000000..a0c5be38 --- /dev/null +++ b/Src/Plugins/Input/in_flv/AMFObject.cpp @@ -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); +} diff --git a/Src/Plugins/Input/in_flv/AMFObject.h b/Src/Plugins/Input/in_flv/AMFObject.h new file mode 100644 index 00000000..bb423a9c --- /dev/null +++ b/Src/Plugins/Input/in_flv/AMFObject.h @@ -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
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/BackgroundDownloader.cpp b/Src/Plugins/Input/in_flv/BackgroundDownloader.cpp new file mode 100644 index 00000000..bcb3b716 --- /dev/null +++ b/Src/Plugins/Input/in_flv/BackgroundDownloader.cpp @@ -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; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/BackgroundDownloader.h b/Src/Plugins/Input/in_flv/BackgroundDownloader.h new file mode 100644 index 00000000..01b08e9a --- /dev/null +++ b/Src/Plugins/Input/in_flv/BackgroundDownloader.h @@ -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); + +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/ExtendedInfo.cpp b/Src/Plugins/Input/in_flv/ExtendedInfo.cpp new file mode 100644 index 00000000..c7a807f0 --- /dev/null +++ b/Src/Plugins/Input/in_flv/ExtendedInfo.cpp @@ -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; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVAudioHeader.cpp b/Src/Plugins/Input/in_flv/FLVAudioHeader.cpp new file mode 100644 index 00000000..5042bf42 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVAudioHeader.cpp @@ -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; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVAudioHeader.h b/Src/Plugins/Input/in_flv/FLVAudioHeader.h new file mode 100644 index 00000000..8a749a4a --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVAudioHeader.h @@ -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
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVCOM.cpp b/Src/Plugins/Input/in_flv/FLVCOM.cpp new file mode 100644 index 00000000..5d6b4cc5 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVCOM.cpp @@ -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 ¶ms, 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, ¶ms, 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(); + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVCOM.h b/Src/Plugins/Input/in_flv/FLVCOM.h new file mode 100644 index 00000000..e521bf80 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVCOM.h @@ -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;
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVHeader.cpp b/Src/Plugins/Input/in_flv/FLVHeader.cpp new file mode 100644 index 00000000..3b5bc1ee --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVHeader.cpp @@ -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; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVHeader.h b/Src/Plugins/Input/in_flv/FLVHeader.h new file mode 100644 index 00000000..9fc283c5 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVHeader.h @@ -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
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVMetadata.cpp b/Src/Plugins/Input/in_flv/FLVMetadata.cpp new file mode 100644 index 00000000..b98e801b --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVMetadata.cpp @@ -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(); + }
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVMetadata.h b/Src/Plugins/Input/in_flv/FLVMetadata.h new file mode 100644 index 00000000..e67f26bc --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVMetadata.h @@ -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
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVProcessor.cpp b/Src/Plugins/Input/in_flv/FLVProcessor.cpp new file mode 100644 index 00000000..961549d8 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVProcessor.cpp @@ -0,0 +1 @@ +#include "FLVProcessor.h" diff --git a/Src/Plugins/Input/in_flv/FLVProcessor.h b/Src/Plugins/Input/in_flv/FLVProcessor.h new file mode 100644 index 00000000..061ff08b --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVProcessor.h @@ -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; +}; + + diff --git a/Src/Plugins/Input/in_flv/FLVReader.cpp b/Src/Plugins/Input/in_flv/FLVReader.cpp new file mode 100644 index 00000000..c756cc70 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVReader.cpp @@ -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; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVReader.h b/Src/Plugins/Input/in_flv/FLVReader.h new file mode 100644 index 00000000..27e68183 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVReader.h @@ -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
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVStreamHeader.cpp b/Src/Plugins/Input/in_flv/FLVStreamHeader.cpp new file mode 100644 index 00000000..8f1ef49e --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVStreamHeader.cpp @@ -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; + + +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVStreamHeader.h b/Src/Plugins/Input/in_flv/FLVStreamHeader.h new file mode 100644 index 00000000..855d4159 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVStreamHeader.h @@ -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
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVUtil.h b/Src/Plugins/Input/in_flv/FLVUtil.h new file mode 100644 index 00000000..9b9da776 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVUtil.h @@ -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
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVVideoHeader.cpp b/Src/Plugins/Input/in_flv/FLVVideoHeader.cpp new file mode 100644 index 00000000..44c2cf69 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVVideoHeader.cpp @@ -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; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FLVVideoHeader.h b/Src/Plugins/Input/in_flv/FLVVideoHeader.h new file mode 100644 index 00000000..6a40d886 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FLVVideoHeader.h @@ -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
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FileProcessor.cpp b/Src/Plugins/Input/in_flv/FileProcessor.cpp new file mode 100644 index 00000000..f2351ee0 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FileProcessor.cpp @@ -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; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/FileProcessor.h b/Src/Plugins/Input/in_flv/FileProcessor.h new file mode 100644 index 00000000..d0fb6bd3 --- /dev/null +++ b/Src/Plugins/Input/in_flv/FileProcessor.h @@ -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; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/PlayThread.cpp b/Src/Plugins/Input/in_flv/PlayThread.cpp new file mode 100644 index 00000000..33fa735e --- /dev/null +++ b/Src/Plugins/Input/in_flv/PlayThread.cpp @@ -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; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/ProgressiveProcessor.cpp b/Src/Plugins/Input/in_flv/ProgressiveProcessor.cpp new file mode 100644 index 00000000..c4e3c5d7 --- /dev/null +++ b/Src/Plugins/Input/in_flv/ProgressiveProcessor.cpp @@ -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; +} diff --git a/Src/Plugins/Input/in_flv/ProgressiveProcessor.h b/Src/Plugins/Input/in_flv/ProgressiveProcessor.h new file mode 100644 index 00000000..92102771 --- /dev/null +++ b/Src/Plugins/Input/in_flv/ProgressiveProcessor.h @@ -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]; +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/StreamProcessor.cpp b/Src/Plugins/Input/in_flv/StreamProcessor.cpp new file mode 100644 index 00000000..d5331cdc --- /dev/null +++ b/Src/Plugins/Input/in_flv/StreamProcessor.cpp @@ -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; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/StreamProcessor.h b/Src/Plugins/Input/in_flv/StreamProcessor.h new file mode 100644 index 00000000..cd66ccdd --- /dev/null +++ b/Src/Plugins/Input/in_flv/StreamProcessor.h @@ -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; +}; + diff --git a/Src/Plugins/Input/in_flv/VideoThread.cpp b/Src/Plugins/Input/in_flv/VideoThread.cpp new file mode 100644 index 00000000..e07d58d0 --- /dev/null +++ b/Src/Plugins/Input/in_flv/VideoThread.cpp @@ -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, ×tamp) == 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(); +} diff --git a/Src/Plugins/Input/in_flv/VideoThread.h b/Src/Plugins/Input/in_flv/VideoThread.h new file mode 100644 index 00000000..3d2d7487 --- /dev/null +++ b/Src/Plugins/Input/in_flv/VideoThread.h @@ -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
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/api__in_flv.h b/Src/Plugins/Input/in_flv/api__in_flv.h new file mode 100644 index 00000000..911b9eb2 --- /dev/null +++ b/Src/Plugins/Input/in_flv/api__in_flv.h @@ -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 diff --git a/Src/Plugins/Input/in_flv/ifc_flvaudiodecoder.h b/Src/Plugins/Input/in_flv/ifc_flvaudiodecoder.h new file mode 100644 index 00000000..66ae264b --- /dev/null +++ b/Src/Plugins/Input/in_flv/ifc_flvaudiodecoder.h @@ -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); +} + diff --git a/Src/Plugins/Input/in_flv/ifc_flvvideodecoder.h b/Src/Plugins/Input/in_flv/ifc_flvvideodecoder.h new file mode 100644 index 00000000..1ee09833 --- /dev/null +++ b/Src/Plugins/Input/in_flv/ifc_flvvideodecoder.h @@ -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 +} diff --git a/Src/Plugins/Input/in_flv/in_flv.rc b/Src/Plugins/Input/in_flv/in_flv.rc new file mode 100644 index 00000000..61854c33 --- /dev/null +++ b/Src/Plugins/Input/in_flv/in_flv.rc @@ -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 + diff --git a/Src/Plugins/Input/in_flv/in_flv.sln b/Src/Plugins/Input/in_flv/in_flv.sln new file mode 100644 index 00000000..7bdd0028 --- /dev/null +++ b/Src/Plugins/Input/in_flv/in_flv.sln @@ -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 diff --git a/Src/Plugins/Input/in_flv/in_flv.vcxproj b/Src/Plugins/Input/in_flv/in_flv.vcxproj new file mode 100644 index 00000000..cd373c26 --- /dev/null +++ b/Src/Plugins/Input/in_flv/in_flv.vcxproj @@ -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>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/in_flv.vcxproj.filters b/Src/Plugins/Input/in_flv/in_flv.vcxproj.filters new file mode 100644 index 00000000..8e199126 --- /dev/null +++ b/Src/Plugins/Input/in_flv/in_flv.vcxproj.filters @@ -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>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/main.cpp b/Src/Plugins/Input/in_flv/main.cpp new file mode 100644 index 00000000..d51d0b2b --- /dev/null +++ b/Src/Plugins/Input/in_flv/main.cpp @@ -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; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/main.h b/Src/Plugins/Input/in_flv/main.h new file mode 100644 index 00000000..ba3f4376 --- /dev/null +++ b/Src/Plugins/Input/in_flv/main.h @@ -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
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/resource.h b/Src/Plugins/Input/in_flv/resource.h new file mode 100644 index 00000000..22a49a42 --- /dev/null +++ b/Src/Plugins/Input/in_flv/resource.h @@ -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 diff --git a/Src/Plugins/Input/in_flv/svc_flvdecoder.h b/Src/Plugins/Input/in_flv/svc_flvdecoder.h new file mode 100644 index 00000000..1093d18a --- /dev/null +++ b/Src/Plugins/Input/in_flv/svc_flvdecoder.h @@ -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); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_flv/version.rc2 b/Src/Plugins/Input/in_flv/version.rc2 new file mode 100644 index 00000000..871c3650 --- /dev/null +++ b/Src/Plugins/Input/in_flv/version.rc2 @@ -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 © 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 |