diff options
Diffstat (limited to 'Src/Plugins/Input/in_mp4')
31 files changed, 5243 insertions, 0 deletions
diff --git a/Src/Plugins/Input/in_mp4/AlbumArt.cpp b/Src/Plugins/Input/in_mp4/AlbumArt.cpp new file mode 100644 index 00000000..d990162d --- /dev/null +++ b/Src/Plugins/Input/in_mp4/AlbumArt.cpp @@ -0,0 +1,232 @@ +#include "mp4.h" +#include "AlbumArt.h" +#include "api__in_mp4.h" +#include "main.h" +#include "../nu/AutoWide.h" +#include "VirtualIO.h" +#include "Stopper.h" +#include <shlwapi.h> +#include <strsafe.h> + +bool IsMyExtension(const wchar_t *filename) +{ + const wchar_t *extension = PathFindExtension(filename); + if (extension && *extension) + { + wchar_t exts[1024] = L""; + // TODO: build a copy of this at config load time so we don't have to run this every time + GetPrivateProfileStringW(L"in_mp4", L"extensionlist", defaultExtensions, exts, 1024, m_ini); + + extension++; + wchar_t *b = exts; + wchar_t *c = 0; + do + { + wchar_t d[20] = {0}; + StringCchCopyW(d, 15, b); + c = wcschr(b, L';'); + if (c) + { + if ((c-b)<15) + d[c - b] = 0; + } + + if (!_wcsicmp(extension, d)) + return true; + + b = c + 1; + } + while (c); + } + return false; +} + +bool MP4_AlbumArtProvider::IsMine(const wchar_t *filename) +{ + return IsMyExtension(filename); +} + +int MP4_AlbumArtProvider::ProviderType() +{ + return ALBUMARTPROVIDER_TYPE_EMBEDDED; +} + +static int MimeTypeToFlags(const wchar_t *mime_type) +{ + if (!mime_type) + return 0; + + if (!_wcsicmp(mime_type, L"jpeg") + || !_wcsicmp(mime_type, L"jpg") + || !_wcsicmp(mime_type, L"image/jpeg") + || !_wcsicmp(mime_type, L"image/jpg")) + return 13; /* JPEG */ + + if (!_wcsicmp(mime_type, L"png") + || !_wcsicmp(mime_type, L"image/png")) + return 14; /* PNG */ + + if (!_wcsicmp(mime_type, L"gif") + || !_wcsicmp(mime_type, L"image/gif")) + return 12; /* GIF */ + + if (!_wcsicmp(mime_type, L"bmp") + || !_wcsicmp(mime_type, L"image/bmp")) + return 27; /* BMP */ + + return 0; /* default to binary, I guess */ +} + +int MP4_AlbumArtProvider::GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType) +{ + void *reader = CreateUnicodeReader(filename); + if (!reader) + return ALBUMARTPROVIDER_FAILURE; + + MP4FileHandle mp4 = MP4ReadEx(filename, reader, &UnicodeIO); + if (!mp4) + { + DestroyUnicodeReader(reader); + return ALBUMARTPROVIDER_FAILURE; + } + else + { + UnicodeClose(reader); // go ahead and close the file so we don't lock it + } + + u_int8_t *art = 0; + u_int32_t artSize = 0; + int flags = 0; + if (MP4GetMetadataCoverArt(mp4, &art, &artSize, &flags)) + { + *bits = WASABI_API_MEMMGR->sysMalloc(artSize); + memcpy(*bits, art, artSize); + *len=artSize; + /* TODO: use flags */ + *mimeType = 0; // no idea what the mime type is :( + MP4Free(art); + MP4Close(mp4); + DestroyUnicodeReader(reader); + return ALBUMARTPROVIDER_SUCCESS; + } + + MP4Close(mp4); + DestroyUnicodeReader(reader); + return ALBUMARTPROVIDER_FAILURE; +} + +int MP4_AlbumArtProvider::SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType) +{ + MP4FileHandle mp4 = MP4Modify(filename, 0, 0); + if (!mp4) + { + return ALBUMARTPROVIDER_FAILURE; + } + + int flags = MimeTypeToFlags(mimeType); + if (MP4SetMetadataCoverArt(mp4, (u_int8_t *)bits, len, flags)) + { + MP4Close(mp4); + return ALBUMARTPROVIDER_SUCCESS; + } + + MP4Close(mp4); + return ALBUMARTPROVIDER_FAILURE; +} + +int MP4_AlbumArtProvider::DeleteAlbumArt(const wchar_t *filename, const wchar_t *type) +{ + MP4FileHandle mp4 = MP4Modify(filename, 0, 0); + + if (!mp4) + { + return ALBUMARTPROVIDER_FAILURE; + } + + if (MP4DeleteMetadataCoverArt(mp4)) + { + Stopper stopper; + if (!_wcsicmp(filename, lastfn)) + stopper.Stop(); + MP4Close(mp4); + stopper.Play(); + return ALBUMARTPROVIDER_SUCCESS; + } + + MP4Close(mp4); + return ALBUMARTPROVIDER_FAILURE; +} + + +#define CBCLASS MP4_AlbumArtProvider +START_DISPATCH; +CB(SVC_ALBUMARTPROVIDER_PROVIDERTYPE, ProviderType); +CB(SVC_ALBUMARTPROVIDER_GETALBUMARTDATA, GetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_SETALBUMARTDATA, SetAlbumArtData); +CB(SVC_ALBUMARTPROVIDER_DELETEALBUMART, DeleteAlbumArt); +CB(SVC_ALBUMARTPROVIDER_ISMINE, IsMine); +END_DISPATCH; +#undef CBCLASS + +static MP4_AlbumArtProvider albumArtProvider; + +// {315CA473-4A7B-43a9-BB1B-7E1C24B3BFE2} +static const GUID mp4_albumartproviderGUID = + { 0x315ca473, 0x4a7b, 0x43a9, { 0xbb, 0x1b, 0x7e, 0x1c, 0x24, 0xb3, 0xbf, 0xe2 } }; + +FOURCC AlbumArtFactory::GetServiceType() +{ + return svc_albumArtProvider::SERVICETYPE; +} + +const char *AlbumArtFactory::GetServiceName() +{ + return "MP4 Album Art Provider"; +} + +GUID AlbumArtFactory::GetGUID() +{ + return mp4_albumartproviderGUID; +} + +void *AlbumArtFactory::GetInterface(int global_lock) +{ + return &albumArtProvider; +} + +int AlbumArtFactory::SupportNonLockingInterface() +{ + return 1; +} + +int AlbumArtFactory::ReleaseInterface(void *ifc) +{ + //plugin.service->service_unlock(ifc); + return 1; +} + +const char *AlbumArtFactory::GetTestString() +{ + return 0; +} + +int AlbumArtFactory::ServiceNotify(int msg, int param1, int param2) +{ + return 1; +} + +#ifdef CBCLASS +#undef CBCLASS +#endif + +#define CBCLASS AlbumArtFactory +START_DISPATCH; +CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType) +CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName) +CB(WASERVICEFACTORY_GETGUID, GetGUID) +CB(WASERVICEFACTORY_GETINTERFACE, GetInterface) +CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface) +CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface) +CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString) +CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify) +END_DISPATCH;
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/AlbumArt.h b/Src/Plugins/Input/in_mp4/AlbumArt.h new file mode 100644 index 00000000..40dccba5 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/AlbumArt.h @@ -0,0 +1,39 @@ +#ifndef NULLSOFT_IN_MP3_ALBUMART_H +#define NULLSOFT_IN_MP3_ALBUMART_H + +#include "../Agave/AlbumArt/svc_albumArtProvider.h" + +class MP4_AlbumArtProvider : public svc_albumArtProvider +{ +public: + bool IsMine(const wchar_t *filename); + int ProviderType(); + // implementation note: use WASABI_API_MEMMGR to alloc bits and mimetype, so that the recipient can free through that + int GetAlbumArtData(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mimeType); + int SetAlbumArtData(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mimeType); + int DeleteAlbumArt(const wchar_t *filename, const wchar_t *type); +protected: + RECVS_DISPATCH; +}; + +#include <api/service/waservicefactory.h> +#include <api/service/services.h> + +class AlbumArtFactory : public waServiceFactory +{ +public: + FOURCC GetServiceType(); + const char *GetServiceName(); + GUID GetGUID(); + void *GetInterface(int global_lock); + int SupportNonLockingInterface(); + int ReleaseInterface(void *ifc); + const char *GetTestString(); + int ServiceNotify(int msg, int param1, int param2); + +protected: + RECVS_DISPATCH; +}; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/AudioSample.h b/Src/Plugins/Input/in_mp4/AudioSample.h new file mode 100644 index 00000000..df483b26 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/AudioSample.h @@ -0,0 +1,80 @@ +#ifndef NULLSOFT_IN_MP4_AUDIOSAMPLE_H +#define NULLSOFT_IN_MP4_AUDIOSAMPLE_H + +#include "main.h" + +class AudioSample +{ +public: + AudioSample(size_t maxInput, size_t maxOutput) + { + input = (unsigned __int8 *)calloc(maxInput, sizeof(unsigned __int8)); + inputSize = maxInput; + + output = (__int8 *)calloc(maxOutput, sizeof(__int8)); + outputSize = maxOutput; + + inputValid = outputValid = result = sampleRate = numChannels = + bitsPerSample = bitrate = sampleId = timestamp = duration = offset = 0; + outputCursor = 0; + } + ~AudioSample() + { + free(output); + free(input); + } + bool OK() + { + return input && output; + } + // input + unsigned __int8 *input; + size_t inputSize, inputValid; + MP4SampleId sampleId; + + // output + __int8 *output, *outputCursor; + size_t outputSize, outputValid; + MP4Duration duration, offset, timestamp; + int result; + unsigned int sampleRate, numChannels, bitsPerSample; + unsigned int bitrate; +}; + +class VideoSample +{ +public: + VideoSample(size_t maxInput) + { + input = (unsigned __int8 *)calloc(maxInput, sizeof(unsigned __int8)); + inputSize = maxInput; + timestamp = inputValid = 0; + } + ~VideoSample() + { + free(input); + } + bool OK() + { + return !!input; + } + // input + unsigned __int8 *input; + size_t inputSize, inputValid; + + MP4Timestamp timestamp; +}; + +class DecodedVideoSample +{ +public: + ~DecodedVideoSample() + { + decoder->FreePicture(output,decoder_data); + } + void *output; + void *decoder_data; + MP4VideoDecoder *decoder; + MP4Timestamp timestamp; +}; +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/ExtendedInfo.cpp b/Src/Plugins/Input/in_mp4/ExtendedInfo.cpp new file mode 100644 index 00000000..43b4ab4c --- /dev/null +++ b/Src/Plugins/Input/in_mp4/ExtendedInfo.cpp @@ -0,0 +1,1022 @@ +#include "main.h" +#include "VirtualIO.h" +#include "../nu/ns_wc.h" +#include "../nu/AutoChar.h" +#include <vector> +#include "../Winamp/wa_ipc.h" +#include "api__in_mp4.h" +#include "Stopper.h" +#include <shlwapi.h> +#include <strsafe.h> +#include "resource.h" + +static inline wchar_t *IncSafe(wchar_t *val, int x) +{ + while (x--) + { + if (*val) + val++; + } + return val; +} + +bool KeywordMatch(const char *mainString, const char *keyword) +{ + return !_stricmp(mainString, keyword); +} + +bool GetCustomMetadata(MP4FileHandle mp4, char *metadata, wchar_t *dest, int destlen, const char *owner) +{ + u_int8_t *value; + u_int32_t size; + bool success = MP4GetMetadataFreeForm(mp4, metadata, &value, &size, owner); + if (success) + { + int cnt = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)value, size, dest, destlen - 1); + dest[cnt] = 0; + MP4Free(value); + } + else + dest[0] = 0; + + return success; +} + +bool HasVideo(MP4FileHandle infile) +{ + /* find Video track */ + int numTracks = MP4GetNumberOfTracks(infile, NULL, /* subType */ 0); + + for (int i = 0; i < numTracks; i++) + { + MP4TrackId trackId = MP4FindTrackId(infile, i, NULL, /* subType */ 0); + const char* trackType = MP4GetTrackType(infile, trackId); + + if (!lstrcmpA(trackType, MP4_VIDEO_TRACK_TYPE)) + return true; + + } + + /* can't decode this */ + return false; +} +typedef struct __INFOKEYWORD +{ + LPCWSTR buffer; + INT len; +} INFOKEYWORD; + +typedef std::vector<INFOKEYWORD> KeywordList; + +/*static void KeywordList_PushBack(KeywordList *list, LPCWSTR buffer, INT len) +{ + if (NULL == list) return; + + INFOKEYWORD kw = { buffer, len }; + list->push_back(kw); +} + +static LPCWSTR ParseFormatInfoLine(LPCWSTR line, KeywordList *list) +{ + if (NULL == line || L'\0' == *line) return line; + LPCWSTR cursor = line; + + for(;;) + { + switch(*cursor) + { + case L'\r': + if (L'\n' == *(cursor + 1)) + { + KeywordList_PushBack(list, line, (INT)(cursor - line)); + cursor += 2; + return cursor; + } + case L'\0': + KeywordList_PushBack(list, line, (INT)(cursor - line)); + return NULL; + case L'\t': + KeywordList_PushBack(list, line, (INT)(cursor - line)); + line = ++cursor; + break; + default: + cursor++; + break; + } + } +} + +static HRESULT FormatInformationString(LPWSTR pszBuffer, INT cchBufferMax, LPCSTR formatInfo) +{ + if (NULL == pszBuffer) return E_POINTER; + *pszBuffer = L'\0'; + + if (NULL == formatInfo) + return S_OK; + + INT cchFormat = lstrlenA(formatInfo); + if (0 == cchFormat) return S_OK; + + LPWSTR pszFormat = (LPWSTR)malloc(sizeof(WCHAR) * (cchFormat + 1)); + if (NULL == pszFormat) return E_OUTOFMEMORY; + + INT result = MultiByteToWideCharSZ(CP_UTF8, 0, formatInfo, cchFormat, pszFormat, cchFormat + 1); + if (0 == result) + { + DWORD errorCode = GetLastError(); + return HRESULT_FROM_WIN32(errorCode); + } + + HRESULT hr(S_OK); + KeywordList keys; + KeywordList values; + LPCWSTR line = pszFormat; + line = ParseFormatInfoLine(line, &keys); + line = ParseFormatInfoLine(line, &values); + + size_t count = keys.size(); + if (0 != count && count == values.size()) + { + LPWSTR cursor = pszBuffer; + size_t remaining = cchBufferMax; + for (size_t index = 0; index < count; index++) + { + if (cursor != pszBuffer) + hr = StringCchCopyEx(cursor, remaining, L"\r\n", &cursor, &remaining, 0); + + if (SUCCEEDED(hr) && 0 != keys[index].len) + { + hr = StringCchCopyNEx(cursor, remaining, keys[index].buffer, keys[index].len, &cursor, &remaining, 0); + if (SUCCEEDED(hr)) + hr = StringCchCopyEx(cursor, remaining, L":", &cursor, &remaining, 0); + } + + if (SUCCEEDED(hr) && 0 != values[index].len) + { + if (SUCCEEDED(hr) && 0 != keys[index].len) + hr = StringCchCopyEx(cursor, remaining, L" ", &cursor, &remaining, 0); + + hr = StringCchCopyNEx(cursor, remaining, values[index].buffer, values[index].len, &cursor, &remaining, 0); + } + + if (FAILED(hr)) + break; + } + } + else + { + hr = StringCchCopy(pszBuffer, cchBufferMax, pszFormat); + } + + if (NULL != pszFormat) + free(pszFormat); + + return hr; +}*/ + +static MP4FileHandle getFileInfoMP4 = 0; +static wchar_t getFileInfoFn[MAX_PATH]=L""; +static void *getFileInfoReader=0; +static FILETIME ftLastWriteTime; + +// is used to determine if the last write time of the file has changed when +// asked to get the metadata for the same cached file so we can update things +BOOL HasFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData = {0}; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + if(CompareFileTime(&ftLastWriteTime, &fileData.ftLastWriteTime)) + { + ftLastWriteTime = fileData.ftLastWriteTime; + return TRUE; + } + } + return FALSE; +} + +void UpdateFileTimeChanged(const wchar_t *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData; + if (GetFileAttributesExW(fn, GetFileExInfoStandard, &fileData) == TRUE) + { + ftLastWriteTime = fileData.ftLastWriteTime; + } +} + +extern "C" +{ + __declspec( dllexport ) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, size_t destlen) + { + if (KeywordMatch(data, "type")) + { + if (!fn || (fn && !fn[0]) || !PathFileExistsW(fn)) + { + const wchar_t *e = PathFindExtensionW(fn); + DWORD lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + // check known video extensions + if ((CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L".M4V", -1)) + || (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L".MOV", -1)) + || (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L".F4V", -1)) + || (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L".MP4", -1))) + dest[0] = '1'; + else + dest[0] = '0'; + dest[1] = 0; + return 1; + } + } + else if (KeywordMatch(data, "rateable")) + { + dest[0] = '1'; + dest[1] = 0; + return 1; + } + + if (!fn || (fn && !fn[0])) return 0; + + if (KeywordMatch(data, "family")) + { + LPCWSTR e; + int pID = -1; + e = PathFindExtensionW(fn); + if (L'.' != *e) return 0; + e++; + DWORD lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"M4A", -1)) pID = IDS_FAMILY_STRING_M4A; + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"MP4", -1)) pID = IDS_FAMILY_STRING_MPEG4; + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"M4V", -1)) pID = IDS_FAMILY_STRING_M4V; + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"MOV", -1)) pID = IDS_FAMILY_STRING_QUICKTIME; + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"3GP", -1)) pID = IDS_FAMILY_STRING_3GPP; + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"F4V", -1)) pID = IDS_FAMILY_STRING_FLV; + if (pID != -1 && S_OK == StringCchCopyW(dest, destlen, WASABI_API_LNGSTRINGW(pID))) return 1; + return 0; + } + + if (KeywordMatch(data, "mime")) + { + LPCWSTR e; + e = PathFindExtensionW(fn); + if (L'.' != *e) return 0; + e++; + DWORD lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"M4A", -1)) { StringCchCopyW(dest, destlen, L"audio/mp4"); return 1; } + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"MP4", -1)) { StringCchCopyW(dest, destlen, L"audio/mp4"); return 1; } + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"M4V", -1)) { StringCchCopyW(dest, destlen, L"video/mp4"); return 1; } + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"MOV", -1)) { StringCchCopyW(dest, destlen, L"video/quicktime"); return 1; } + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"3GP", -1)) { StringCchCopyW(dest, destlen, L"video/3gp"); return 1; } + else if (CSTR_EQUAL == CompareStringW(lcid, NORM_IGNORECASE, e, -1, L"F4V", -1)) { StringCchCopyW(dest, destlen, L"video/f4v"); return 1; } + return 0; + } + + if (!getFileInfoMP4 || lstrcmpi(getFileInfoFn, fn) || HasFileTimeChanged(fn)) // different file than last time? + { + lstrcpyn(getFileInfoFn, fn, MAX_PATH); + + if (getFileInfoMP4) + MP4Close(getFileInfoMP4); + getFileInfoMP4=0; + + if (getFileInfoReader) + DestroyUnicodeReader(getFileInfoReader); + + getFileInfoReader = CreateUnicodeReader(fn); + if (!getFileInfoReader) + { + getFileInfoFn[0]=0; + return 0; + } + + getFileInfoMP4 = MP4ReadEx(fn, getFileInfoReader, &UnicodeIO); + if (!getFileInfoMP4) + { + DestroyUnicodeReader(getFileInfoReader); + getFileInfoReader=0; + getFileInfoFn[0]=0; + return 0; + } + else + { + UnicodeClose(getFileInfoReader); // go ahead and close the file so we don't lock it + } + } + + bool success = false; + char *pVal = NULL; + uint16_t *pVal_utf16 = NULL; + + if (KeywordMatch(data, "type")) + { + if (GetVideoTrack(getFileInfoMP4) != MP4_INVALID_TRACK_ID) // check for a video track + dest[0] = '1'; + else // assume no video mean audio (not necessarily true, for weird mp4 stuff like systems & text) + dest[0] = '0'; + dest[1] = 0; + success = true; + } + else if (KeywordMatch(data, "lossless")) + { + dest[0]='0'; + dest[1]=0; + MP4TrackId track = GetAudioTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + { + // TODO: benski> We should check more than just ALAC + // potentially asking the mpeg4audio services + // but for now this should suffice. + const char *media_data_name = MP4GetTrackMediaDataName(getFileInfoMP4, track); + if (media_data_name && KeywordMatch (media_data_name, "alac")) + dest[0]='1'; + } + success=true; + } + else if (KeywordMatch(data, "title")) + { + success = MP4GetMetadataName(getFileInfoMP4, &pVal); + if (!success) + success = MP4Get3GPMetadata(getFileInfoMP4, "titl", &pVal_utf16); + } + else if (KeywordMatch(data, "album")) + { + success = MP4GetMetadataAlbum(getFileInfoMP4, &pVal); + if (!success) + success = MP4Get3GPMetadata(getFileInfoMP4, "albm", &pVal_utf16); + } + else if (KeywordMatch(data, "artist")) + { + success = MP4GetMetadataArtist(getFileInfoMP4, &pVal); + if (!success) + success = MP4Get3GPMetadata(getFileInfoMP4, "perf", &pVal_utf16); + } + else if (KeywordMatch(data, "rating")) + { + success = MP4GetMetadataRating(getFileInfoMP4, &pVal); + // add a /* to enable reading from 3GP metadata, otherwise is only used on normal MP4 + if (success)/*/ + if (!success) + { + if ((success = MP4Get3GPMetadata(getFileInfoMP4, "rate", &pVal_utf16))) + { + wchar_t *value = (wchar_t*)pVal_utf16; + if(value && *value) { + int rating = _wtoi(value); + + // keeps things limited to our range of 0-100 + if (rating >= 100) { + rating = 5; + } + // 1-100 case + else if (rating > 0 && rating < 100) { + rating = (rating /= 20); + // shift up by one rating when in next band + // 1-20 = 1, 21-40 = 2, 41-60 = 3, 61-80 = 4, 81-100 = 5 + rating += ((_wtoi(value) - (rating * 20)) > 0); + } + // otherwise just make sure and set zero + else { + rating = 0; + } + + StringCchPrintfW(dest, destlen, L"%u", rating); + MP4Free(pVal_utf16); + return 1; + } + } + } + else/**/ + { + char *value = (char*)pVal; + if(value && *value) { + int rating = atoi(value); + + // keeps things limited to our range of 0-100 + if (rating >= 100) { + rating = 5; + } + // 1-100 case + else if (rating > 0 && rating < 100) { + rating = (rating /= 20); + // shift up by one rating when in next band + // 1-20 = 1, 21-40 = 2, 41-60 = 3, 61-80 = 4, 81-100 = 5 + rating += ((atoi(value) - (rating * 20)) > 0); + } + // otherwise just make sure and set zero + else { + rating = 0; + } + + StringCchPrintfW(dest, destlen, L"%u", rating); + MP4Free(pVal); + return 1; + } + } + } + else if (KeywordMatch(data, "comment")) + success = MP4GetMetadataComment(getFileInfoMP4, &pVal); + else if (KeywordMatch(data, "albumartist")) + success = MP4GetMetadataAlbumArtist(getFileInfoMP4, &pVal); + else if (KeywordMatch(data, "gain")) + { + StringCchPrintfW(dest, destlen, L"%+-.2f dB", (float)log10f(GetGain(getFileInfoMP4, false))*20.0f); + success = true; + } + else if (KeywordMatch(data, "replaygain_track_gain")) + { + GetCustomMetadata(getFileInfoMP4, "replaygain_track_gain", dest, destlen); + success = true; + } + else if (KeywordMatch(data, "replaygain_album_gain")) + { + GetCustomMetadata(getFileInfoMP4, "replaygain_album_gain", dest, destlen); + success = true; + } + else if (KeywordMatch(data, "replaygain_track_peak")) + { + GetCustomMetadata(getFileInfoMP4, "replaygain_track_peak", dest, destlen); + success = true; + } + else if (KeywordMatch(data, "replaygain_album_peak")) + { + GetCustomMetadata(getFileInfoMP4, "replaygain_album_peak", dest, destlen); + success = true; + } + else if (KeywordMatch(data, "bpm")) + { + unsigned __int16 tempo = 0; + success = MP4GetMetadataTempo(getFileInfoMP4, &tempo); + if (success && tempo) + StringCchPrintf(dest, destlen, L"%u", tempo); + } + else if (KeywordMatch(data, "year")) + { + success = MP4GetMetadataYear(getFileInfoMP4, &pVal); + if (!success) + { + uint64_t val = 0; + success = MP4Get3GPMetadataInteger(getFileInfoMP4, "yrrc", &val); + if (success) + { + StringCchPrintf(dest, destlen, L"%I64u", val); + } + } + } + else if (KeywordMatch(data, "bitrate")) + { + uint32_t audio_bitrate = 0, video_bitrate = 0; + MP4TrackId track = GetAudioTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + audio_bitrate = MP4GetTrackBitRate(getFileInfoMP4, track) / 1000; + + track = GetVideoTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + video_bitrate = MP4GetTrackBitRate(getFileInfoMP4, track) / 1000; + + + if (audio_bitrate || video_bitrate) + StringCchPrintf(dest, destlen, L"%u", audio_bitrate+video_bitrate); + else + dest[0] = 0; + success = true; + } + else if (KeywordMatch(data, "height")) + { + MP4TrackId track = GetVideoTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + { + uint16_t height = MP4GetTrackVideoHeight(getFileInfoMP4, track); + if (height) + { + StringCchPrintf(dest, destlen, L"%u", height); + } + else + dest[0]=0; + success=true; + } + } + else if (KeywordMatch(data, "width")) + { + + MP4TrackId track = GetVideoTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + { + uint16_t width = MP4GetTrackVideoWidth(getFileInfoMP4, track); + if (width) + { + StringCchPrintf(dest, destlen, L"%u", width); + } + else + dest[0]=0; + success=true; + } + } + //else if(KeywordMatch(data,"srate")) wsprintf(dest,"%d",srate); + //else if(KeywordMatch(data,"stereo")) wsprintf(dest,"%d",is_stereo); + //else if(KeywordMatch(data,"vbr")) wsprintf(dest,"%d",is_vbr); + else if (KeywordMatch(data, "genre")) + { + success = MP4GetMetadataGenre(getFileInfoMP4, &pVal); + if (!success) + success = MP4Get3GPMetadata(getFileInfoMP4, "gnre", &pVal_utf16); + } + else if (KeywordMatch(data, "disc")) + { + unsigned __int16 dummy = 0, dummy2 = 0; + success = MP4GetMetadataDisk(getFileInfoMP4, &dummy, &dummy2); + if (success && dummy) + { + if (dummy2) + StringCchPrintf(dest, destlen, L"%u/%u", dummy, dummy2); + else + StringCchPrintf(dest, destlen, L"%u", dummy); + } + else + dest[0] = 0; + } + else if (KeywordMatch(data, "track")) + { + unsigned __int16 dummy = 0, dummy2 = 0; + success = MP4GetMetadataTrack(getFileInfoMP4, &dummy, &dummy2); + if (success && dummy) + { + if (dummy2) + StringCchPrintf(dest, destlen, L"%u/%u", dummy, dummy2); + else + StringCchPrintf(dest, destlen, L"%u", dummy); + } + else + dest[0] = 0; + } + else if (KeywordMatch(data, "length")) + { + /* TODO: use sample rate and number of samples from iTunSMPB to get a more exact length */ + //MP4TrackId track = GetAudioTrack(getFileInfoMP4); + //if (track != MP4_INVALID_TRACK_ID) + //{ + // int m_timescale = MP4GetTrackTimeScale(getFileInfoMP4, track); + // unsigned __int64 trackDuration = MP4GetTrackDuration(getFileInfoMP4, track); + // double m_length = (double)(__int64)trackDuration / (double)m_timescale; + // StringCchPrintf(dest, destlen, L"%d", (int)(m_length*1000)); + // success = true; + //} + + double lengthAudio = 0; + double lengthVideo = 0; + double m_length = 0; + MP4TrackId audio_track = GetAudioTrack(getFileInfoMP4); + if (audio_track != -1) + { + int timescale = MP4GetTrackTimeScale(getFileInfoMP4, audio_track); + unsigned __int64 trackDuration = MP4GetTrackDuration(getFileInfoMP4, audio_track); + lengthAudio = (double)(__int64)trackDuration / (double)timescale; + } + MP4TrackId video_track = GetVideoTrack(getFileInfoMP4); + if (video_track != -1) + { + int timescale = MP4GetTrackTimeScale(getFileInfoMP4, video_track); + unsigned __int64 trackDuration = MP4GetTrackDuration(getFileInfoMP4, video_track); + lengthVideo = (double)(__int64)trackDuration / (double)timescale; + } + m_length = (max(lengthAudio, lengthVideo)); + StringCchPrintf(dest, destlen, L"%d", (int)(m_length * 1000)); + success = true; + + } + else if (KeywordMatch(data, "tool")) + success = MP4GetMetadataTool(getFileInfoMP4, &pVal); + else if (KeywordMatch(data, "composer")) + success = MP4GetMetadataWriter(getFileInfoMP4, &pVal); + else if (KeywordMatch(data, "category")) + success = MP4GetMetadataGrouping(getFileInfoMP4, &pVal); + else if (KeywordMatch(data, "GracenoteFileID")) + { + GetCustomMetadata(getFileInfoMP4, "gnid", dest, destlen); + success = true; + } + else if (KeywordMatch(data, "GracenoteExtData")) + { + GetCustomMetadata(getFileInfoMP4, "gnxd", dest, destlen); + success = true; + } + else if (KeywordMatch(data, "publisher")) + { + GetCustomMetadata(getFileInfoMP4, "publisher", dest, destlen, "com.nullsoft.winamp"); + success = true; + } + else if (KeywordMatch(data, "pregap")) + { + wchar_t gap_data[128] = {0}; + if (GetCustomMetadata(getFileInfoMP4, "iTunSMPB", gap_data, 128) && gap_data[0]) + { + wchar_t *itr = IncSafe(gap_data, 9); + + unsigned int pregap = wcstoul(itr, 0, 16); + StringCchPrintfW(dest, destlen, L"%u",pregap); + success=true; + } + } + else if (KeywordMatch(data, "postgap")) + { + wchar_t gap_data[128] = {0}; + if (GetCustomMetadata(getFileInfoMP4, "iTunSMPB", gap_data, 128) && gap_data[0]) + { + wchar_t *itr = IncSafe(gap_data, 18); + + unsigned int postgap = wcstoul(itr, 0, 16); + StringCchPrintfW(dest, destlen, L"%u",postgap); + success=true; + } + } + else if (KeywordMatch(data, "numsamples")) + { + wchar_t gap_data[128] = {0}; + if (GetCustomMetadata(getFileInfoMP4, "iTunSMPB", gap_data, 128) && gap_data[0]) + { + wchar_t *itr = IncSafe(gap_data,27); + + unsigned __int64 numsamples = _wcstoui64(itr, 0, 16); + StringCchPrintfW(dest, destlen, L"%I64u", numsamples); + success=true; + } + } + else if (KeywordMatch(data, "formatinformation")) + { + MP4TrackId track = GetAudioTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + { + char *track_type = MP4PrintAudioInfo(getFileInfoMP4, track); + if (track_type) + { + uint32_t bitrate = MP4GetTrackBitRate(getFileInfoMP4, track); + StringCchPrintfEx(dest, destlen, &dest, &destlen, 0, WASABI_API_LNGSTRINGW(IDS_AUDIO_INFO), track_type, (bitrate + 500) / 1000); + MP4Free(track_type); + } + } + + track = GetVideoTrack(getFileInfoMP4); + if (track != MP4_INVALID_TRACK_ID) + { + char *track_type = MP4PrintVideoInfo(getFileInfoMP4, track); + if (track_type) + { + uint32_t bitrate = MP4GetTrackBitRate(getFileInfoMP4, track); + StringCchPrintfEx(dest, destlen, &dest, &destlen, 0, WASABI_API_LNGSTRINGW(IDS_VIDEO_INFO), track_type, (bitrate + 500) / 1000); + u_int16_t width = MP4GetTrackVideoWidth(getFileInfoMP4, track); + u_int16_t height = MP4GetTrackVideoHeight(getFileInfoMP4, track); + if (width && height) + { + // TODO: framerate, but the MP4GetTrackVideoFrameRate method just guesses based on duration and samples + StringCchPrintfEx(dest, destlen, &dest, &destlen, 0, L"\t%ux%u\n", width, height); + } + MP4Free(track_type); + } + } + + success=true; + } + else // don't understand the name + { +// MP4Close(getFileInfoMP4); + return 0; + } + + if (pVal) + { + MultiByteToWideCharSZ(CP_UTF8, 0, pVal, -1, dest, destlen); + MP4Free(pVal); + } + else if (pVal_utf16) + { + StringCchCopyW(dest, destlen, (const wchar_t *)pVal_utf16); + MP4Free(pVal_utf16); + } + + if (!success) + dest[0] = 0; + + //MP4Close(getFileInfoMP4); + + return 1; + } + + + static MP4FileHandle setFileInfoMP4 = 0; + static int m_last_err = 0; + static wchar_t m_last_ext_fn[MAX_PATH] = L""; + static void *setFileInfoReader = 0; +static bool setFile3GP=false; + +/*static int SetExtendedInfo3GP(const char *data, const wchar_t *val) +{ + if (KeywordMatch(data, "title")) + { + if (val && *val) MP4Set3GPMetadata(setFileInfoMP4, "titl", (const uint16_t *)val); + else MP4Delete3GPMetadata(setFileInfoMP4, "titl"); + } + else if (KeywordMatch(data, "album")) + { + if (val && *val) MP4Set3GPMetadata(setFileInfoMP4, "albm", (const uint16_t *)val); + else MP4Delete3GPMetadata(setFileInfoMP4, "albm"); + } + else if (KeywordMatch(data, "genre")) + { + if (val && *val) MP4Set3GPMetadata(setFileInfoMP4, "gnre", (const uint16_t *)val); + else MP4Delete3GPMetadata(setFileInfoMP4, "gnre"); + } + else if (KeywordMatch(data, "artist")) + { + if (val && *val) MP4Set3GPMetadata(setFileInfoMP4, "perf", (const uint16_t *)val); + else MP4Delete3GPMetadata(setFileInfoMP4, "perf"); + } + else if (KeywordMatch(data, "year")) + { + if (val && *val) MP4Set3GPMetadataInteger(setFileInfoMP4, "yrrc", _wtoi64(val)); + else MP4Delete3GPMetadata(setFileInfoMP4, "yrrc"); + } + else + return 0; + + return 1; +}*/ + +static int SetExtendedInfoMP4(const char *data, const wchar_t *val) +{ + if (KeywordMatch(data, "title")) + { + if (val && *val) MP4SetMetadataName(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataName(setFileInfoMP4); + } + else if (KeywordMatch(data, "album")) + { + if (val && *val) + MP4SetMetadataAlbum(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataAlbum(setFileInfoMP4); + } + else if (KeywordMatch(data, "artist")) + { + if (val && *val) + MP4SetMetadataArtist(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataArtist(setFileInfoMP4); + } + else if (KeywordMatch(data, "rating")) + { + if (val && *val) + { + char temp[128] = {0}; + StringCchPrintfA(temp, 128, "%u", _wtoi(val) * 20); + MP4SetMetadataRating(setFileInfoMP4, temp); + } + else MP4DeleteMetadataRating(setFileInfoMP4); + } + else if (KeywordMatch(data, "comment")) + { + if (val && *val) + MP4SetMetadataComment(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataComment(setFileInfoMP4); + } + else if (KeywordMatch(data, "year")) + { + if (val && *val) + MP4SetMetadataYear(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataYear(setFileInfoMP4); + } + else if (KeywordMatch(data, "genre")) + { + if (val && *val) + MP4SetMetadataGenre(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataGenre(setFileInfoMP4); + } + else if (KeywordMatch(data, "albumartist")) + { + if (val && *val) + MP4SetMetadataAlbumArtist(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataAlbumArtist(setFileInfoMP4); + } + else if (KeywordMatch(data, "replaygain_track_gain")) + { + if (val && *val) + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_gain", "com.apple.iTunes"); + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "replaygain_track_gain", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "org.hydrogenaudio.replaygain"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_gain", "com.apple.iTunes"); + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_gain", "org.hydrogenaudio.replaygain"); + } + } + else if (KeywordMatch(data, "replaygain_track_peak")) + { + if (val && *val) + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_peak", "com.apple.iTunes"); + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "replaygain_track_peak", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "org.hydrogenaudio.replaygain"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_peak", "com.apple.iTunes"); + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_track_peak", "org.hydrogenaudio.replaygain"); + } + } + else if (KeywordMatch(data, "replaygain_album_gain")) + { + if (val && *val) + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_gain", "com.apple.iTunes"); + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "replaygain_album_gain", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "org.hydrogenaudio.replaygain"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_gain", "com.apple.iTunes"); + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_gain", "org.hydrogenaudio.replaygain"); + } + } + else if (KeywordMatch(data, "replaygain_album_peak")) + { + if (val && *val) + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_peak", "com.apple.iTunes"); + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "replaygain_album_peak", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "org.hydrogenaudio.replaygain"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_peak", "com.apple.iTunes"); + MP4DeleteMetadataFreeForm(setFileInfoMP4, "replaygain_album_peak", "org.hydrogenaudio.replaygain"); + } + } + else if (KeywordMatch(data, "track")) + { + int track = _wtoi(val); + if (track) + { + int tracks = 0; + const wchar_t *_tracks = wcschr(val, L'/'); + if (_tracks) tracks = _wtoi(_tracks + 1); + MP4SetMetadataTrack(setFileInfoMP4, track, tracks); + } + else + MP4DeleteMetadataTrack(setFileInfoMP4); + } + else if (KeywordMatch(data, "disc")) + { + int disc = _wtoi(val); + if (disc) + { + int discs = 0; + const wchar_t *_discs = wcschr(val, L'/'); + if (_discs) discs = _wtoi(_discs + 1); + MP4SetMetadataDisk(setFileInfoMP4, disc, discs); + } + else + MP4DeleteMetadataDisk(setFileInfoMP4); + } + else if (KeywordMatch(data, "tool")) + { + if (val && *val) + MP4SetMetadataTool(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataTool(setFileInfoMP4); + } + else if (KeywordMatch(data, "bpm")) + { + if (val && *val && *val != '0') + MP4SetMetadataTempo(setFileInfoMP4, _wtoi(val)); + else + MP4DeleteMetadataTempo(setFileInfoMP4); + } + else if (KeywordMatch(data, "composer")) + { + if (val && *val) + MP4SetMetadataWriter(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataWriter(setFileInfoMP4); + } + else if (KeywordMatch(data, "GracenoteFileID")) + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "gnid", "com.apple.iTunes"); // delete obselete metadata storage scheme + if (val && *val) + { + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "gnid", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "com.gracenote.cddb"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "gnid", "com.gracenote.cddb"); + } + } + else if (KeywordMatch(data, "GracenoteExtData")) + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "gnxd", "com.apple.iTunes");// delete obselete metadata storage scheme + if (val && *val) + { + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "gnxd", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "com.gracenote.cddb"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "gnxd", "com.gracenote.cddb"); + } + } + else if (KeywordMatch(data, "publisher")) + { + if (val && *val) + { + AutoChar utf8(val, CP_UTF8); + MP4SetMetadataFreeForm(setFileInfoMP4, "publisher", (u_int8_t *)(char *)utf8, lstrlenA(utf8), "com.nullsoft.winamp"); + } + else + { + MP4DeleteMetadataFreeForm(setFileInfoMP4, "publisher", "com.nullsoft.winamp"); + } + } + else if (KeywordMatch(data, "category")) + { + if (val && *val) + MP4SetMetadataGrouping(setFileInfoMP4, AutoChar(val, CP_UTF8)); + else MP4DeleteMetadataGrouping(setFileInfoMP4); + } + else + return 0; + + return 1; +} + + static Stopper stopper; + + __declspec( dllexport ) int winampSetExtendedFileInfoW(const wchar_t *fn, const char *data, const wchar_t *val) + { + if (!setFileInfoMP4 || lstrcmpi(m_last_ext_fn, fn)) // different file than last time? + { + m_last_err = 0; + lstrcpyn(m_last_ext_fn, fn, MAX_PATH); + + if (setFileInfoMP4) + MP4Close(setFileInfoMP4); + + /* TODO: make MP4ModifyEx so we can use this + if (setFileInfoReader) + DestroyUnicodeReader(setFileInfoReader); + + setFileInfoReader = CreateUnicodeReader(fn); + if (!setFileInfoReader) + return 0; + */ + + if (!_wcsicmp(m_last_ext_fn, lastfn)) + stopper.Stop(); + + setFileInfoMP4 = MP4Modify(m_last_ext_fn, 0, 0); + if (!setFileInfoMP4) + { + stopper.ChangeTracking(1); // enable stats updating + //DestroyUnicodeReader(setFileInfoReader); + m_last_err = 1; + return 0; + } + + setFile3GP = false; + const char *brand=0; + if (MP4GetStringProperty(setFileInfoMP4, "ftyp.majorBrand", &brand)) + { + if (!strncmp(brand, "3gp6", 4)) + setFile3GP=true; + } + } + /* + if (setFile3GP) + return SetExtendedInfo3GP(data, val); + else*/ + return SetExtendedInfoMP4(data, val); + + } + + __declspec(dllexport) int winampWriteExtendedFileInfo() + { + int err = m_last_err; + m_last_err = 0; + + // clear this as well, so dirty info isn't read back + if (getFileInfoMP4) + MP4Close(getFileInfoMP4); + getFileInfoMP4=0; + + if (getFileInfoReader) + DestroyUnicodeReader(getFileInfoReader); + getFileInfoReader=0; + + if (setFileInfoMP4) + { + MP4Close(setFileInfoMP4); + MP4Optimize(m_last_ext_fn, NULL, 0); //put the fields at the beginning of the file + setFileInfoMP4 = 0; + m_last_ext_fn[0] = 0; + stopper.Play(); + } + + // update last modified so we're not re-queried on our own updates + UpdateFileTimeChanged(m_last_ext_fn); + + return !err; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/ExtendedRead.cpp b/Src/Plugins/Input/in_mp4/ExtendedRead.cpp new file mode 100644 index 00000000..137c8a81 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/ExtendedRead.cpp @@ -0,0 +1,239 @@ +#include <stddef.h> +#include "main.h" +#include "mpeg4audio.h" +#include "api__in_mp4.h" +#include <api/service/waservicefactory.h> +#include <assert.h> +#include "../nu/GaplessRingBuffer.h" +#include "virtualIO.h" +struct ExtendedRead +{ + ExtendedRead() : mp4(0), mp4track(0), sampleId(1), + samples(0), audio(0), audioFactory(0), frameSize(0), reader(0), + sample_rate(0), timescale(0), max_sample_size(0), sample_buffer(0), decode_buffer(0) + {} + ~ExtendedRead() + { + if (mp4) MP4Close(mp4); mp4 = 0; + if (reader) DestroyUnicodeReader(reader); reader=0; + if (audio) + { + audio->Close(); + audioFactory->releaseInterface(audio); + } + audioFactory = 0; + audio = 0; + free(sample_buffer); + sample_buffer=0; + free(decode_buffer); + } + + bool Open(const wchar_t *fn, int *size, int *bps, int *nch, int *srate, bool useFloat); + MP4AudioDecoder *audio; + MP4FileHandle mp4; + MP4TrackId mp4track; + MP4SampleId sampleId, samples; + GaplessRingBuffer ringBuffer; + void *reader; + waServiceFactory *audioFactory; + size_t frameSize; + unsigned int sample_rate; + uint32_t timescale; + uint32_t max_sample_size; + void *sample_buffer; + uint8_t *decode_buffer; +}; + +bool ExtendedRead::Open(const wchar_t *fn, int *size, int *bps, int *nch, int *srate, bool useFloat) +{ + unsigned __int32 pregap, postgap; + int numBits = *bps; + int numChannels = *nch; + + reader = CreateUnicodeReader(fn); + if (!reader) + return false; + mp4 = MP4ReadEx(fn, reader, &UnicodeIO); + if (!mp4) + { + DestroyUnicodeReader(reader); + return false; + } + mp4track = GetAudioTrack(mp4); + if (mp4track == MP4_INVALID_TRACK_ID) return false; + if (!CreateDecoder(mp4, mp4track, audio, audioFactory)) + return false; + + int result; + result = audio->OpenMP4(mp4, mp4track, numBits, numChannels, useFloat); + + if (result != MP4_SUCCESS) + return false; + + + GetGaps(mp4, pregap, postgap); + ConfigureDecoderASC(mp4, mp4track, audio); + + timescale = MP4GetTrackTimeScale(mp4, mp4track); + + samples = MP4GetTrackNumberOfSamples(mp4, mp4track); + MP4SampleId sample = 0; + +// some codecs require a frame or two to get decoded. so we'll go until GetOutputProperties is valid + for (MP4SampleId sample = 1;sample <= samples; sample++) + { + int ret; + if (useFloat) + { + bool verifyFloat = false; + ret = audio->GetOutputPropertiesEx(&sample_rate, reinterpret_cast<unsigned int *>(nch), reinterpret_cast<unsigned int *>(bps), &verifyFloat); + if (ret == MP4_SUCCESS && !verifyFloat) + return false; + } + else + { + ret = audio->GetOutputProperties(&sample_rate, reinterpret_cast<unsigned int *>(nch), reinterpret_cast<unsigned int *>(bps)); + } + if (ret == MP4_SUCCESS) + { + MP4Duration duration = MP4GetTrackDuration(mp4, mp4track); + *srate = sample_rate; + frameSize = (*nch) * (*bps / 8); + size_t outputFrameSize; + *size = duration * frameSize; + if (audio->OutputFrameSize(&outputFrameSize) == MP4_SUCCESS) + { + + } + else + { + outputFrameSize = 65536; // err on the side of caution + } + + decode_buffer = (uint8_t *)malloc(outputFrameSize*frameSize); + ringBuffer.Initialize(outputFrameSize, *bps, *nch, pregap, postgap); + + max_sample_size = MP4GetTrackMaxSampleSize(mp4, mp4track); + sample_buffer = malloc(max_sample_size); + if (sample != 1) { + audio->Flush(); + } + return true; + } + + unsigned char *buffer = NULL; + unsigned __int32 buffer_size = 0; + if (MP4ReadSample(mp4, mp4track, sample, (unsigned __int8 **)&buffer, &buffer_size)) + { + unsigned char tempBuf[65536]; + size_t outSize = 65536; + int err = audio->DecodeSample(buffer, buffer_size, tempBuf, &outSize); + MP4Free(buffer); + + if (err != MP4_SUCCESS) + continue; + } + } + + return false; +} +extern "C" +{ + //returns handle!=0 if successful, 0 if error + //size will return the final nb of bytes written to the output, -1 if unknown + __declspec( dllexport ) intptr_t winampGetExtendedRead_openW(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) + { + ExtendedRead *ext = new ExtendedRead; + if (ext->Open(fn, size, bps, nch, srate, false)) + return reinterpret_cast<intptr_t>(ext); + + delete ext; + return 0; + } + + //returns handle!=0 if successful, 0 if error + //size will return the final nb of bytes written to the output, -1 if unknown + __declspec( dllexport ) intptr_t winampGetExtendedRead_openW_float(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) + { + ExtendedRead *ext = new ExtendedRead; + if (ext->Open(fn, size, bps, nch, srate, true)) + return reinterpret_cast<intptr_t>(ext); + + delete ext; + return 0; + } + + //returns nb of bytes read. -1 if read error (like CD ejected). if (ret == 0), EOF is assumed + __declspec( dllexport ) intptr_t winampGetExtendedRead_getData(intptr_t handle, char *dest, int len, volatile int *killswitch) + { + ExtendedRead *ext = (ExtendedRead *)handle; + + int bytesCopied = 0; + int skip = 0; + len -= (len % ext->frameSize); // round down to the nearest whole frame size + while (len && !*killswitch) + { + size_t copySize = ext->ringBuffer.Read(dest, len); + len -= copySize; + dest += copySize; + bytesCopied += copySize; + + if (ext->ringBuffer.Empty()) + { + size_t outSize = 0; + MP4Duration offset=0,duration=INT_MAX; + if (ext->sampleId <= ext->samples) { + unsigned char *buffer = (unsigned char *)ext->sample_buffer; + unsigned __int32 buffer_size = ext->max_sample_size; + MP4ReadSample(ext->mp4, ext->mp4track, ext->sampleId++, (unsigned __int8 **) & buffer, &buffer_size, 0, &duration, &offset); + + ext->audio->DecodeSample(buffer, buffer_size, ext->decode_buffer, &outSize); // TODO error check + } else { +#if 0 // TODO Drain decode + ext->audio->DecodeSample(0, 0, decode_buffer, &outSize); // TODO Drain method? +#else +#endif + return bytesCopied; + } + + // convert to the track timescale for purposes of duration/offset/gap stuff + int outSamples = MulDiv(outSize, ext->timescale, ext->sample_rate * ext->frameSize); + + if (offset > 0) + outSamples -= min(outSamples, offset); + + if (outSamples > duration) + outSamples = duration; + + // convert back to sample rate timescale + outSize = MulDiv(ext->sample_rate * ext->frameSize, outSamples, ext->timescale); + ext->ringBuffer.Write(ext->decode_buffer+offset*ext->frameSize, outSize); + } + } + return bytesCopied; + } + + // return nonzero on success, zero on failure. + __declspec( dllexport ) int winampGetExtendedRead_setTime(intptr_t handle, int millisecs) + { + ExtendedRead *ext = (ExtendedRead *)handle; + + MP4Duration duration = MP4ConvertToTrackDuration(ext->mp4, ext->mp4track, millisecs, MP4_MSECS_TIME_SCALE); + if(duration == MP4_INVALID_DURATION) return 0; + + MP4SampleId newSampleId = MP4GetSampleIdFromTime(ext->mp4, ext->mp4track, duration); + if(newSampleId > ext->samples) return 0; + + ext->sampleId = newSampleId; + ext->audio->Flush(); + // ext->bufferUsed=0; + ext->ringBuffer.Reset(); + return 1; + } + + __declspec( dllexport ) void winampGetExtendedRead_close(intptr_t handle) + { + ExtendedRead *ext = (ExtendedRead *)handle; + delete ext; + } +} diff --git a/Src/Plugins/Input/in_mp4/Main.cpp b/Src/Plugins/Input/in_mp4/Main.cpp new file mode 100644 index 00000000..59ca1a89 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/Main.cpp @@ -0,0 +1,723 @@ +#define PLUGIN_VERSION L"2.70" +#include "Main.h" +#include <windows.h> +#include <stdio.h> +#include <locale.h> +#include "resource.h" +#include "../Winamp/in2.h" +#include "../Winamp/wa_ipc.h" +#include "../nu/AutoChar.h" +#include "api__in_mp4.h" + +#include <api/service/waservicefactory.h> + +#pragma warning(disable:4786) +#include "mpeg4audio.h" + +#include <shlwapi.h> +#include <malloc.h> +#include "VirtualIO.h" +#include "AlbumArt.h" +#include <assert.h> +#include "../in_wmvdrm/Remaining.h" +#include "VideoThread.h" +#include "RawMediaReader.h" +#include "../nu/Singleton.h" +#include <strsafe.h> + +Remaining remaining; +nu::VideoClock video_clock; + +AlbumArtFactory albumArtFactory; + +wchar_t m_ini[MAX_PATH] = {0}; +int infoDlg(const wchar_t *fn, HWND hwnd); +#define WM_WA_IPC WM_USER +#define WM_WA_MPEG_EOF WM_USER+2 + +HANDLE hThread; +static DWORD WINAPI playProc(LPVOID lpParameter); +static int paused, m_kill; +HANDLE killEvent, seekEvent, pauseEvent; +static int m_opened; + + +static RawMediaReaderService raw_media_reader_service; +static SingletonServiceFactory<svc_raw_media_reader, RawMediaReaderService> raw_factory; +static void stop(); + +// wasabi based services for localisation support +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; + +api_config *AGAVE_API_CONFIG = 0; +api_memmgr *WASABI_API_MEMMGR = 0; +api_application *WASABI_API_APP = 0; + +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_MPEG4_AUDIO_DECODER_OLD,text,1024); + StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), + mod.description, TEXT(__DATE__)); + DoAboutMessageBox(hwndParent,text,message); +} + +static const wchar_t defaultExtensions_nonpro[] = {L"M4A;MP4"}; +static const wchar_t defaultExtensions_pro[] = {L"M4A;MP4;M4V"}; +const wchar_t *defaultExtensions = defaultExtensions_pro; + + +// the return pointer has been malloc'd. Use free() when you are done. +char *BuildExtensions(const char *extensions) +{ + char name[64] = {0}; + WASABI_API_LNGSTRING_BUF(IDS_MP4_FILE,name,64); + size_t length = strlen(extensions) + 1 + strlen(name) + 2; + char *newExt = (char *)calloc(length, sizeof(char)); + char *ret = newExt; // save because we modify newExt + + // copy extensions + StringCchCopyExA(newExt, length, extensions, &newExt, &length, 0); + newExt++; + length--; + + // copy description + StringCchCopyExA(newExt, length, name, &newExt, &length, 0); + newExt++; + length--; + + // double null terminate + assert(length == 1); + *newExt = 0; + + return ret; +} + +int init() +{ + if (!IsWindow(mod.hMainWindow)) + return IN_INIT_FAILURE; + + killEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + seekEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + pauseEvent = CreateEvent(NULL, TRUE, TRUE, NULL); + + mod.service->service_register(&albumArtFactory); + raw_factory.Register(mod.service, &raw_media_reader_service); + + waServiceFactory *sf = mod.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) + AGAVE_API_CONFIG = (api_config *)sf->getInterface(); + sf = mod.service->service_getServiceByGuid(applicationApiServiceGuid); + if (sf) + WASABI_API_APP = (api_application *)sf->getInterface(); + sf = mod.service->service_getServiceByGuid(memMgrApiServiceGuid); + if (sf) + WASABI_API_MEMMGR = (api_memmgr *)sf->getInterface(); + sf = mod.service->service_getServiceByGuid(DownloadManagerGUID); + if (sf) + WAC_API_DOWNLOADMANAGER = (api_downloadManager *)sf->getInterface(); + sf = mod.service->service_getServiceByGuid(ThreadPoolGUID); + if (sf) + WASABI_API_THREADPOOL = (api_threadpool *)sf->getInterface(); + + // loader so that we can get the localisation service api for use + sf = mod.service->service_getServiceByGuid(languageApiGUID); + if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface()); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(mod.hDllInstance,InMp4LangGUID); + + static wchar_t szDescription[256]; + StringCchPrintfW(szDescription,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_MPEG4_AUDIO_DECODER),PLUGIN_VERSION); + mod.description = (char*)szDescription; + + const wchar_t *inipath = (wchar_t *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETINIDIRECTORYW); + + PathCombineW(m_ini, inipath, L"Plugins"); + CreateDirectoryW(m_ini, NULL); + PathAppendW(m_ini, L"in_mp4.ini"); + + wchar_t exts[1024] = {0}; + GetPrivateProfileStringW(L"in_mp4", L"extensionlist", defaultExtensions, exts, 1024, m_ini); + mod.FileExtensions = BuildExtensions(AutoChar(exts)); + return IN_INIT_SUCCESS; +} + +void quit() +{ + CloseHandle(killEvent); + CloseHandle(seekEvent); + + raw_factory.Deregister(mod.service); + waServiceFactory *sf = mod.service->service_getServiceByGuid(AgaveConfigGUID); + if (sf) + sf->releaseInterface(AGAVE_API_CONFIG); + sf = mod.service->service_getServiceByGuid(applicationApiServiceGuid); + if (sf) + sf->releaseInterface(WASABI_API_APP); + sf = mod.service->service_getServiceByGuid(DownloadManagerGUID); + if (sf) + sf->releaseInterface( WAC_API_DOWNLOADMANAGER ); + mod.service->service_deregister(&albumArtFactory); + + free(mod.FileExtensions); +} + +int isourfile(const wchar_t *fn) +{ + return 0; +} + +void config(HWND hwndParent); + +void setoutputtime(int time_in_ms) +{ + m_needseek = time_in_ms; + SetEvent(seekEvent); +} + +MP4TrackId GetVideoTrack(MP4FileHandle infile) +{ + int numTracks = MP4GetNumberOfTracks(infile, NULL, /* subType */ 0); + + for (int i = 0; i < numTracks; i++) + { + MP4TrackId trackId = MP4FindTrackId(infile, i, NULL, /* subType */ 0); + const char* trackType = MP4GetTrackType(infile, trackId); + + if (!lstrcmpA(trackType, MP4_VIDEO_TRACK_TYPE)) + return trackId; + } + + /* can't decode this */ + return MP4_INVALID_TRACK_ID; +} + +MP4TrackId GetAudioTrack(MP4FileHandle infile) +{ + int ret = MP4_INVALID_TRACK_ID; + __try + { + /* find AAC track */ + int numTracks = MP4GetNumberOfTracks(infile, NULL, /* subType */ 0); + + for (int i = 0; i < numTracks; i++) + { + MP4TrackId trackId = MP4FindTrackId(infile, i, NULL, /* subType */ 0); + if (trackId != MP4_INVALID_TRACK_ID) + { + const char* trackType = MP4GetTrackType(infile, trackId); + + if (trackType && !lstrcmpA(trackType, MP4_AUDIO_TRACK_TYPE)) + return trackId; + } + + } + + /* can't decode this */ + return MP4_INVALID_TRACK_ID; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + return MP4_INVALID_TRACK_ID; + } + return ret; +} + +MP4SampleId numSamples, numVideoSamples; +MP4FileHandle MP4hFile; +MP4TrackId audio_track, video_track; + +double m_length; +volatile int m_needseek = -1; +unsigned int audio_srate, audio_nch, audio_bps, audio_bitrate=0; +unsigned int video_bitrate=0; + +wchar_t lastfn[MAX_PATH*4] = L""; + +MP4AudioDecoder *audio = 0; +waServiceFactory *audioFactory = 0, *videoFactory = 0; +MP4VideoDecoder *video = 0; + +uint32_t m_timescale = 0, m_video_timescale = 0; +static void *reader = 0; +bool audio_chunk = false; +enum +{ + READER_UNICODE=0, + READER_HTTP=1, +}; +int reader_type=READER_UNICODE; + +bool open_mp4(const wchar_t *fn) +{ + audio = 0; + video = 0; + if (!_wcsnicmp(fn, L"http://", 7) || !_wcsnicmp(fn, L"https://", 8)) + { + reader = CreateReader(fn, killEvent); + reader_type=READER_HTTP; + MP4hFile = MP4ReadEx(fn, reader, &HTTPIO); + } + else + { + reader = CreateUnicodeReader(fn); + if (!reader) + return false; + reader_type=READER_UNICODE; + MP4hFile = MP4ReadEx(fn, reader, &UnicodeIO); + } + + if (!MP4hFile) + { + return false; + } + + m_opened = 1; + + unsigned int output_bits = AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"bits", 16); + if (output_bits >= 24) + output_bits = 24; + else + output_bits = 16; + + unsigned int max_channels; + // get max channels + if (AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"surround", true)) + max_channels = 6; + else if (AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"mono", false)) + max_channels = 1; + else + max_channels = 2; + + audio_track = GetAudioTrack(MP4hFile); + if (audio_track == MP4_INVALID_TRACK_ID || !CreateDecoder(MP4hFile, audio_track, audio, audioFactory) || audio->OpenMP4(MP4hFile, audio_track, output_bits, max_channels, false) != MP4_SUCCESS) + { + audio = 0; + video_clock.Start(); + } + + video_track = GetVideoTrack(MP4hFile); + if (video_track != MP4_INVALID_TRACK_ID) + { + CreateVideoDecoder(MP4hFile, video_track, video, videoFactory); + if (video) + video->Open(MP4hFile, video_track); + } + else + video=0; + + if (!audio && !video) + { + return false; + } + + numVideoSamples = MP4GetTrackNumberOfSamples(MP4hFile, video_track); + m_video_timescale = MP4GetTrackTimeScale(MP4hFile, video_track); + unsigned __int64 trackDuration; + + double lengthAudio = 0; + double lengthVideo = 0; + + if (audio_track != MP4_INVALID_TRACK_ID) + { + if (audio) + { + ConfigureDecoderASC(MP4hFile, audio_track, audio); + audio_chunk = !!audio->RequireChunks(); + } + else + audio_chunk = false; + + numSamples = audio_chunk?MP4GetTrackNumberOfChunks(MP4hFile, audio_track):MP4GetTrackNumberOfSamples(MP4hFile, audio_track); + m_timescale = MP4GetTrackTimeScale(MP4hFile, audio_track); + trackDuration = MP4GetTrackDuration(MP4hFile, audio_track); + lengthAudio = (double)(__int64)trackDuration / (double)m_timescale; + } + else + { + numSamples = numVideoSamples; + trackDuration = MP4GetTrackDuration(MP4hFile, video_track); + lengthVideo = (double)(__int64)trackDuration / (double)m_video_timescale; + } + + /* length in Sec. */ + m_length = max(lengthAudio, lengthVideo); //(double)(__int64)trackDuration / (double)m_timescale; + + audio_bitrate = MP4GetTrackBitRate(MP4hFile, audio_track) / 1000; + if (video) + video_bitrate = MP4GetTrackBitRate(MP4hFile, video_track) / 1000; + else + video_bitrate = 0; + + if (audio && audio->SetGain(GetGain(MP4hFile)) == MP4_SUCCESS) + mod.UsesOutputPlug |= 8; + else + mod.UsesOutputPlug &= ~8; + + return true; +} + +int play(const wchar_t *fn) +{ + video_clock.Reset(); + if (!videoOutput) // grab this now while we're on the main thread + videoOutput = (IVideoOutput *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT); + audio = 0; + video = 0; + paused = 0; + m_kill = 0; + ResetEvent(killEvent); + m_length = 0; + ResetEvent(seekEvent); + m_needseek = -1; + SetEvent(pauseEvent); + if (m_force_seek != -1) + { + setoutputtime(m_force_seek); + } + m_opened = 0; + + lstrcpynW(lastfn, fn, MAX_PATH*4); + + DWORD thread_id; + HANDLE threadCreatedEvent = CreateEvent(0, FALSE, FALSE, 0); + hThread = CreateThread(NULL, NULL, PlayProc, (LPVOID)threadCreatedEvent, NULL, &thread_id); + SetThreadPriority(hThread, AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST)); + WaitForSingleObject(threadCreatedEvent, INFINITE); + CloseHandle(threadCreatedEvent); + + return 0; +} + +static inline wchar_t *IncSafe(wchar_t *val, int x) +{ + while (x--) + { + if (*val) + val++; + } + + return val; +} + +void GetGaps(MP4FileHandle mp4, unsigned __int32 &pre, unsigned __int32 &post) +{ + wchar_t gap_data[128] = {0}; + if (GetCustomMetadata(mp4, "iTunSMPB", gap_data, 128) && gap_data[0]) + { + wchar_t *itr = IncSafe(gap_data, 9); + + pre = wcstoul(itr, 0, 16); + + itr = IncSafe(itr, 9); + post = wcstoul(itr, 0, 16); + + // don't care about total number of samples, really + /* + itr+=9; + unsigned int numSamples = wcstoul(itr, 0, 16);*/ + + } + else + { + pre = 0; + post = 0; + } +} + +float GetGain(MP4FileHandle mp4, bool allowDefault) +{ + if (AGAVE_API_CONFIG && AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false)) + { + float dB = 0, peak = 1.0f; + wchar_t gain[128] = L"", peakVal[128] = L""; + _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale(); + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0)) + { + case 0: // track + if ((!GetCustomMetadata(mp4, "replaygain_track_gain", gain, 128) || !gain[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetCustomMetadata(mp4, "replaygain_album_gain", gain, 128); + + if ((!GetCustomMetadata(mp4, "replaygain_track_peak", peakVal, 128) || !peakVal[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetCustomMetadata(mp4, "replaygain_album_peak", peakVal, 128); + break; + case 1: + if ((!GetCustomMetadata(mp4, "replaygain_album_gain", gain, 128) || !gain[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetCustomMetadata(mp4, "replaygain_track_gain", gain, 128); + + if ((!GetCustomMetadata(mp4, "replaygain_album_peak", peakVal, 128) || !peakVal[0]) + && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false)) + GetCustomMetadata(mp4, "replaygain_track_peak", peakVal, 128); + break; + } + + if (gain[0]) + { + if (gain[0] == L'+') + dB = (float)_wtof_l(&gain[1],C_locale); + else + dB = (float)_wtof_l(gain,C_locale); + } + else if (allowDefault) + { + dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0); + return powf(10.0f, dB / 20.0f); + } + + if (peakVal[0]) + { + peak = (float)_wtof_l(peakVal,C_locale); + } + + switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_mode", 1)) + { + case 0: // apply gain + return powf(10.0f, dB / 20.0f); + case 1: // apply gain, but don't clip + return min(powf(10.0f, dB / 20.0f), 1.0f / peak); + case 2: // normalize + return 1.0f / peak; + case 3: // prevent clipping + if (peak > 1.0f) + return 1.0f / peak; + else + return 1.0f; + } + + } + + return 1.0f; // no gain +} + + + + +bool first; + +void pause() +{ + paused = 1; + if (audio) + { + mod.outMod->Pause(1); + } + else + { + video_clock.Pause(); + } + ResetEvent(pauseEvent); // pauseEvent signal state is opposite of pause state +} + +void unpause() +{ + paused = 0; + if (audio) + { + mod.outMod->Pause(0); + } + else + { + video_clock.Unpause(); + } + SetEvent(pauseEvent); // pauseEvent signal state is opposite of pause state +} + +int ispaused() +{ + return paused; +} + +void stop() +{ + if (reader && reader_type==READER_HTTP) StopReader(reader); + + lastfn[0] = 0; + SetEvent(killEvent); + m_kill = 1; + WaitForSingleObject(hThread, INFINITE); + + mod.outMod->Close(); + mod.SAVSADeInit(); + + if (m_opened) MP4Close(MP4hFile); + MP4hFile = 0; + m_opened = 0; + + if (audio) + { + audio->Close(); + audioFactory->releaseInterface(audio); + } + + audioFactory = 0; + audio = 0; + + if (video) + { + video->Close(); + videoFactory->releaseInterface(video); + } + videoFactory=0; + video = 0; + + if (reader) + { + if (reader_type == READER_HTTP) + DestroyReader(reader); + else + DestroyUnicodeReader(reader); + } + reader = 0; +} + +int getlength() +{ + return (int)(m_length*1000); +} + +int getoutputtime() +{ + if (m_needseek == -1) + return (int)GetClock(); + else + return m_needseek; // this prevents the seekbar from jumping around while the playthread is seeking +} + +void setvolume(int volume) +{ + mod.outMod->SetVolume(volume); +} +void setpan(int pan) +{ + mod.outMod->SetPan(pan); +} +/* +void FillInfo(HWND hwndDlg, MP4FileHandle hMp4); +void CALLBACK CurrentlyPlayingInfoBox(ULONG_PTR param) +{ + ThreadInfoBox *threadInfo = (ThreadInfoBox *)param; + FillInfo(threadInfo->hwndDlg, MP4hFile); + SetEvent(threadInfo->completionEvent); +} +*/ +void getfileinfo(const wchar_t *filename, wchar_t *title, int *length_in_ms) +{ + if (!filename || !*filename) // currently playing file + { + if (length_in_ms) *length_in_ms = getlength(); + if (title) // get non-path portion.of filename + { + lstrcpynW(title, lastfn, GETFILEINFO_TITLE_LENGTH); + PathStripPathW(title); + PathRemoveExtensionW(title); + } + } + else // some other file + { + if (length_in_ms) // calculate length + { + *length_in_ms = -1000; // the default is unknown file length (-1000). + + MP4FileHandle hMp4 = MP4Read(filename); + if (hMp4) + { + double lengthAudio = 0; + double lengthVideo = 0; + MP4TrackId audio_track = GetAudioTrack(hMp4); + if (audio_track != -1) + { + int timescale = MP4GetTrackTimeScale(hMp4, audio_track); + unsigned __int64 trackDuration = MP4GetTrackDuration(hMp4, audio_track); + lengthAudio = (double)(__int64)trackDuration / (double)timescale; + } + MP4TrackId video_track = GetVideoTrack(hMp4); + if (video_track != -1) + { + int timescale = MP4GetTrackTimeScale(hMp4, video_track); + unsigned __int64 trackDuration = MP4GetTrackDuration(hMp4, video_track); + lengthVideo = (double)(__int64)trackDuration / (double)timescale; + } + *length_in_ms = (int)(max(lengthAudio, lengthVideo) * 1000); + MP4Close(hMp4); + } + } + if (title) // get non path portion of filename + { + lstrcpynW(title, filename, GETFILEINFO_TITLE_LENGTH); + PathStripPathW(title); + PathRemoveExtensionW(title); + } + } +} + +void eq_set(int on, char data[10], int preamp) +{} + +// module definition. + +In_Module mod = +{ + IN_VER_RET, // defined in IN2.H + "nullsoft(in_mp4.dll)", //"Nullsoft MPEG-4 Audio Decoder v1.22" + 0, // hMainWindow (filled in by winamp) + 0, // hDllInstance (filled in by winamp) + 0, // this is a double-null limited list. "EXT\0Description\0EXT\0Description\0" etc. + 1, // is_seekable + 1, // uses output plug-in system + config, + about, + init, + quit, + getfileinfo, + infoDlg, + isourfile, + play, + pause, + unpause, + ispaused, + stop, + + getlength, + getoutputtime, + setoutputtime, + + setvolume, + setpan, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, // visualization calls filled in by winamp + + 0, 0, // dsp calls filled in by winamp + + eq_set, + + NULL, // setinfo call filled in by winamp + + 0 // out_mod filled in by winamp +}; + +extern "C" +{ + __declspec(dllexport) In_Module * winampGetInModule2() + { + return &mod; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/Main.h b/Src/Plugins/Input/in_mp4/Main.h new file mode 100644 index 00000000..661e182d --- /dev/null +++ b/Src/Plugins/Input/in_mp4/Main.h @@ -0,0 +1,76 @@ +#ifndef NULLSOFT_IN_MP4_MAINH +#define NULLSOFT_IN_MP4_MAINH + +#include "mp4.h" +#include "../Winamp/in2.h" +#include "mpeg4audio.h" +#include "mpeg4video.h" +#include "AudioSample.h" +#include "../nu/AutoLock.h" +#include "../nu/VideoClock.h" + +extern nu::VideoClock video_clock; + +MP4TrackId GetAudioTrack(MP4FileHandle infile); +MP4TrackId GetVideoTrack(MP4FileHandle infile); +int GetAACTrack(MP4FileHandle infile); +class waServiceFactory; +bool CreateVideoDecoder(MP4FileHandle file, MP4TrackId track, MP4VideoDecoder *&decoder, waServiceFactory *&serviceFactory); + +class MP4AudioDecoder; + +bool CreateDecoder(MP4FileHandle file, MP4TrackId track, MP4AudioDecoder *&decoder, waServiceFactory *&serviceFactory); + +void ConfigureDecoderASC(MP4FileHandle file, MP4TrackId track, MP4AudioDecoder *decoder); +bool GetCustomMetadata(MP4FileHandle mp4, char *metadata, wchar_t *dest, int destlen, const char *owner=0); +float GetGain(MP4FileHandle mp4, bool allowDefault=true); +void GetGaps(MP4FileHandle mp4, unsigned __int32 &pre, unsigned __int32 &post); + +struct ThreadInfoBox +{ + HWND hwndDlg; + HANDLE completionEvent; +}; +VOID CALLBACK CurrentlyPlayingInfoBox(ULONG_PTR param); + +extern wchar_t lastfn[MAX_PATH*4]; + +extern HANDLE killEvent, seekEvent, pauseEvent; +extern In_Module mod; // the output module +extern MP4FileHandle MP4hFile; +extern MP4TrackId audio_track, video_track; +class AudioSample; +int TryWriteAudio(AudioSample *sample); +extern MP4AudioDecoder *audio; +extern MP4VideoDecoder *video; +extern unsigned int audio_bitrate, video_bitrate; +extern MP4SampleId numSamples, numVideoSamples; +extern DWORD WINAPI PlayProc(LPVOID lpParameter); +extern bool first; + +extern HANDLE hThread; + +extern Nullsoft::Utility::LockGuard play_mp4_guard; + +extern volatile int m_needseek; +void CALLBACK Seek(ULONG_PTR data); +void CALLBACK Pause(ULONG_PTR data); +extern uint32_t m_video_timescale; +extern uint32_t m_timescale; +extern const wchar_t *defaultExtensions; +extern wchar_t m_ini[MAX_PATH]; +char *BuildExtensions(const char *extensions); +extern bool config_show_average_bitrate; +void FlushOutput(); +extern int m_force_seek; +MP4Duration GetClock(); +MP4Duration GetDecodeClock(); +extern bool audio_chunk; +// {B6CB4A7C-A8D0-4c55-8E60-9F7A7A23DA0F} +static const GUID playbackConfigGroupGUID = + { + 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } + }; + + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/PlayThread.cpp b/Src/Plugins/Input/in_mp4/PlayThread.cpp new file mode 100644 index 00000000..661d3003 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/PlayThread.cpp @@ -0,0 +1,417 @@ +#include "main.h" +#include "../winamp/wa_ipc.h" +#include "VideoThread.h" +#include "AudioSample.h" +#include "api__in_mp4.h" +#include <assert.h> +#include <api/service/waservicefactory.h> +#include "../nu/AudioOutput.h" +#include <strsafe.h> + +const DWORD PAUSE_TIMEOUT = 100; // number of milliseconds to sleep for when paused + +static bool audio_opened; +static HANDLE events[3]; +static bool done; +bool open_mp4(const wchar_t *fn); +static AudioSample *sample = 0; +static DWORD waitTime; +static MP4SampleId nextSampleId; +Nullsoft::Utility::LockGuard play_mp4_guard; + +static MP4Duration first_timestamp=0; + +class MP4Wait +{ +public: + int WaitOrAbort(int time_in_ms) + { + WaitForMultipleObjects(3, events, FALSE, INFINITE); // pauseEvent signal state is opposite of pause state + int ret = WaitForMultipleObjects(2, events, FALSE, time_in_ms); + if (ret == WAIT_TIMEOUT) + return 0; + if (ret == WAIT_OBJECT_0+1) + return 2; + return 1; + } +}; + +nu::AudioOutput<MP4Wait> audio_output(&mod); + +MP4Duration GetClock() +{ + if (audio) + { + return audio_output.GetFirstTimestamp() + mod.outMod->GetOutputTime(); + } + else if (video) + { + return video_clock.GetOutputTime(); + } + else + { + return 0; + } +} + +static void OutputSample(AudioSample *sample) +{ + if (first) + { + first_timestamp = MP4ConvertFromTrackTimestamp(MP4hFile, audio_track, sample->timestamp, MP4_MSECS_TIME_SCALE); + first = false; + } + + if (sample->result == MP4_SUCCESS) + { + if (!audio_opened) + { + audio_opened=true; + + if (audio_output.Open(first_timestamp, sample->numChannels, sample->sampleRate, sample->bitsPerSample) == false) + { + PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return ; + } + unsigned __int32 pregap = 0; + unsigned __int32 postgap = 0; + GetGaps(MP4hFile, pregap, postgap); + audio_output.SetDelays(0, pregap, postgap); + mod.SetInfo(audio_bitrate + video_bitrate, sample->sampleRate / 1000, sample->numChannels, 1); + } + + int skip = 0; + int sample_size = (sample->bitsPerSample / 8) * sample->numChannels; + int outSamples = MulDiv(sample->outputValid, m_timescale, sample->sampleRate * sample_size); + /* if (!audio_chunk && outSamples > sample->duration) + outSamples = (int)sample->duration; */ + + if (sample->offset > 0) + { + int cut = (int)min(outSamples, sample->offset); + outSamples -= cut; + skip = cut; + } + + size_t outSize = MulDiv(sample_size * sample->sampleRate, outSamples, m_timescale); + + if (audio_bitrate != sample->bitrate) + { + audio_bitrate = sample->bitrate; + mod.SetInfo(audio_bitrate + video_bitrate, -1, -1, 1); + } + + if (audio_output.Write(sample->output + MulDiv(sample_size * sample->sampleRate, skip, m_timescale), outSize) == 1) + { + return ; + } + + if (sample->sampleId == numSamples) // done! + done = true; // TODO: probably don't want to bail out yet if video is playing + } +} + +static bool DecodeAudioSample(AudioSample *sample) +{ + if (m_needseek != -1) + { + sample->outputValid = 0; + sample->outputCursor = sample->output; + sample->result = MP4_SUCCESS; + sample->sampleRate = m_timescale; + audio->GetOutputProperties(&sample->sampleRate, &sample->numChannels, &sample->bitsPerSample); + if (audio->GetCurrentBitrate(&sample->bitrate) != MP4_SUCCESS || !sample->bitrate) + sample->bitrate = audio_bitrate; + } + else + { + sample->outputValid = sample->outputSize; + sample->outputCursor = 0; + sample->result = audio->DecodeSample(sample->input, sample->inputValid, sample->output, &sample->outputValid); + if (sample->inputValid == 0 && sample->outputValid == 0) { + return false; + } + sample->sampleRate = m_timescale; + audio->GetOutputProperties(&sample->sampleRate, &sample->numChannels, &sample->bitsPerSample); + if (audio->GetCurrentBitrate(&sample->bitrate) != MP4_SUCCESS || !sample->bitrate) + sample->bitrate = audio_bitrate; + OutputSample(sample); + } + return true; +} + +static void ReadNextAudioSample() +{ + if (nextSampleId > numSamples) + { + return; + } + + unsigned __int32 buffer_size = sample->inputSize; + + bool sample_read = false; + play_mp4_guard.Lock(); + if (audio_chunk) + sample_read = MP4ReadChunk(MP4hFile, audio_track, nextSampleId++, (unsigned __int8 **)&sample->input, &buffer_size, &sample->timestamp, &sample->duration); + else + sample_read = MP4ReadSample(MP4hFile, audio_track, nextSampleId++, (unsigned __int8 **)&sample->input, &buffer_size, &sample->timestamp, &sample->duration, &sample->offset); + play_mp4_guard.Unlock(); + if (sample_read) + { + sample->inputValid = buffer_size; + + if (audio_chunk) + { + sample->duration = 0; + sample->offset = 0; + } + sample->sampleId = nextSampleId-1; + + DecodeAudioSample(sample); + } +} + +static bool BuildAudioBuffers() +{ + size_t outputFrameSize; + //if (audio->OutputFrameSize(&outputFrameSize) != MP4_SUCCESS || !outputFrameSize) + //{ + outputFrameSize = 8192 * 6; // fallback size + //} + + u_int32_t maxSize = 0; + if (audio) + { + if (audio_chunk) + maxSize = 65536; // TODO!!!! + else + maxSize = MP4GetTrackMaxSampleSize(MP4hFile, audio_track); + if (!maxSize) + return 0; + + sample = new AudioSample(maxSize, outputFrameSize); + if (!sample->OK()) + { + delete sample; + return false; + } + } + + if (video) + { + maxSize = MP4GetTrackMaxSampleSize(MP4hFile, video_track); + + video_sample = new VideoSample(maxSize); + if (!video_sample->OK()) + { + delete video_sample; + return false; + } + } + + return true; +} + +DWORD WINAPI PlayProc(LPVOID lpParameter) +{ + // set an event when we start. this keeps Windows from queueing an APC before the thread proc even starts (evil, evil windows) + HANDLE threadCreatedEvent = (HANDLE)lpParameter; + SetEvent(threadCreatedEvent); + + video=0; + if (!open_mp4(lastfn)) + { + if (WaitForSingleObject(killEvent, 200) != WAIT_OBJECT_0) + PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + + audio_output.Init(mod.outMod); + + if (videoOutput && video) + { + // TODO: this is really just a placeholder, we should do smarter stuff + // like query the decoder object for a name rather than guess + char set_info[256] = {0}; + char *audio_info = MP4PrintAudioInfo(MP4hFile, audio_track); + char *video_info = 0; + if (video_track != MP4_INVALID_TRACK_ID) + video_info = MP4PrintVideoInfo(MP4hFile, video_track); + + if (video_info) + { + StringCchPrintfA(set_info, 256, "%s, %s %ux%u", audio_info, video_info, MP4GetTrackVideoWidth(MP4hFile, video_track), MP4GetTrackVideoHeight(MP4hFile, video_track)); + videoOutput->extended(VIDUSER_SET_INFOSTRING,(INT_PTR)set_info,0); + MP4Free(video_info); + } + MP4Free(audio_info); + } + + if (!BuildAudioBuffers()) + { + // TODO: benski> more cleanup work has to be done here! + if (WaitForSingleObject(killEvent, 200) != WAIT_OBJECT_0) + PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + return 0; + } + + nextSampleId = 1; + nextVideoSampleId = 1; + if (video) + Video_Init(); + + first = true; + audio_opened = false; + first_timestamp= 0; + + events[0]=killEvent; + events[1]=seekEvent; + events[2]=pauseEvent; + waitTime = audio?0:INFINITE; + done = false; + + while (!done) + { + int ret = WaitForMultipleObjects(2, events, FALSE, waitTime); + switch (ret) + { + case WAIT_OBJECT_0: // kill event + done = true; + break; + + case WAIT_OBJECT_0 + 1: // seek event + { + bool rewind = m_needseek < GetClock(); + // TODO: reset pregap? + MP4SampleId new_video_sample = MP4_INVALID_SAMPLE_ID; + if (video) + { + SetEvent(video_start_flushing); + WaitForSingleObject(video_flush_done, INFINITE); + + MP4Duration duration = MP4ConvertToTrackDuration(MP4hFile, video_track, m_needseek, MP4_MSECS_TIME_SCALE); + if (duration != MP4_INVALID_DURATION) + { + new_video_sample = MP4GetSampleIdFromTime(MP4hFile, video_track, duration, true, rewind); + if (new_video_sample == MP4_INVALID_SAMPLE_ID) + new_video_sample = MP4GetSampleIdFromTime(MP4hFile, video_track, duration, false); // try again without keyframe seeking + + /* TODO: make sure the new seek direction is in the same as the request seek direction. + e.g. make sure a seek FORWARD doesn't go BACKWARD + MP4Timestamp video_timestamp = MP4GetSampleTime(MP4hFile, video_track, seek_video_sample); + int new_time = MP4ConvertFromTrackTimestamp(MP4hFile, video_track, video_timestamp, MP4_MSECS_TIME_SCALE); + if (m_needseek < GetClock()) + video_timestamp = MP4GetSampleIdFromTime(MP4hFile, video_track, duration, true); // first closest keyframe prior + */ + if (new_video_sample != MP4_INVALID_SAMPLE_ID) + { + int m_old_needseek = m_needseek; + + MP4Timestamp video_timestamp = MP4GetSampleTime(MP4hFile, video_track, new_video_sample); + m_needseek = MP4ConvertFromTrackTimestamp(MP4hFile, video_track, video_timestamp, MP4_MSECS_TIME_SCALE); + if (!audio) + { + MP4Timestamp video_timestamp = MP4GetSampleTime(MP4hFile, video_track, new_video_sample); + m_needseek = MP4ConvertFromTrackTimestamp(MP4hFile, video_track, video_timestamp, MP4_MSECS_TIME_SCALE); + video_clock.Seek(m_needseek); + m_needseek = -1; + } + else + { + // TODO check this will just do what is needed + // aim of this is when there is 1 artwork + // frame then we don't lock audio<->video + // as it otherwise prevents audio seeking + if (!m_needseek && m_old_needseek != m_needseek && new_video_sample == 1) + { + m_needseek = m_old_needseek; + } + } + } + } + } + + if (audio) + { + MP4Duration duration = MP4ConvertToTrackDuration(MP4hFile, audio_track, m_needseek, MP4_MSECS_TIME_SCALE); + if (duration != MP4_INVALID_DURATION) + { + MP4SampleId newSampleId = audio_chunk?MP4GetChunkIdFromTime(MP4hFile, audio_track, duration):MP4GetSampleIdFromTime(MP4hFile, audio_track, duration); + if (newSampleId != MP4_INVALID_SAMPLE_ID) + { + audio->Flush(); + + if (video) + { + if (new_video_sample == MP4_INVALID_SAMPLE_ID) + { + SetEvent(video_resume); + } + else + { + nextVideoSampleId = new_video_sample; + SetEvent(video_flush); + } + WaitForSingleObject(video_flush_done, INFINITE); + } + m_needseek = MP4ConvertFromTrackTimestamp(MP4hFile, audio_track, duration, MP4_MILLISECONDS_TIME_SCALE); + ResetEvent(seekEvent); + audio_output.Flush(m_needseek); + m_needseek = -1; + nextSampleId = newSampleId; + continue; + } + } + } + else + { + if (new_video_sample == MP4_INVALID_SAMPLE_ID) + { + SetEvent(video_resume); + } + else + { + nextVideoSampleId = new_video_sample; + SetEvent(video_flush); + } + WaitForSingleObject(video_flush_done, INFINITE); + + ResetEvent(seekEvent); + continue; + } + } + break; + + case WAIT_TIMEOUT: + ReadNextAudioSample(); + break; + } + } + + if (WaitForSingleObject(killEvent, 0) == WAIT_TIMEOUT) // if (!killed) + { + // tell audio decoder about end-of-stream and get remaining audio + /* if (audio) { + audio->EndOfStream(); + + sample->inputValid = 0; + while (DecodeAudioSample(sample)) { + } + } + */ + audio_output.Write(0,0); + audio_output.WaitWhilePlaying(); + + if (WaitForSingleObject(killEvent, 0) == WAIT_TIMEOUT) + PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + } + SetEvent(killEvent); + + // eat the rest of the APC messages + while (SleepEx(0, TRUE) == WAIT_IO_COMPLETION) {} + + if (video) + Video_Close(); + + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/RawMediaReader.cpp b/Src/Plugins/Input/in_mp4/RawMediaReader.cpp new file mode 100644 index 00000000..4d077a6b --- /dev/null +++ b/Src/Plugins/Input/in_mp4/RawMediaReader.cpp @@ -0,0 +1,145 @@ +#include "RawMediaReader.h" +#include "virtualIO.h" +#include <limits.h> + +bool IsMyExtension(const wchar_t *filename); + +int RawMediaReaderService::CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **out_reader) +{ + if (IsMyExtension(filename)) + { + void *unicode_reader = CreateUnicodeReader(filename); + if (!unicode_reader ) + return NErr_FileNotFound; + + MP4FileHandle mp4 = MP4ReadEx(filename, unicode_reader, &UnicodeIO); + if (!mp4) + { + DestroyUnicodeReader(unicode_reader); + return NErr_Malformed; + } + + + RawMediaReader *reader = new RawMediaReader(mp4, unicode_reader); + if (!reader) + { + MP4Close(mp4); + DestroyUnicodeReader(unicode_reader); + return NErr_OutOfMemory; + } + + *out_reader = reader; + return NErr_Success; + } + else + { + return NErr_False; + } +} + +#define CBCLASS RawMediaReaderService +START_DISPATCH; +CB(CREATERAWMEDIAREADER, CreateRawMediaReader); +END_DISPATCH; +#undef CBCLASS + +RawMediaReader::RawMediaReader(MP4FileHandle file, void *reader) : file(file), reader(reader) +{ + track_num=0; + number_of_tracks=MP4GetNumberOfTracks(file); + current_track = MP4_INVALID_TRACK_ID; + chunk_position=0; + chunk_size=0; + chunk_buffer=0; +} + +RawMediaReader::~RawMediaReader() +{ + if (chunk_buffer) + MP4Free(chunk_buffer); + MP4Close(file); + DestroyUnicodeReader(reader); +} + +int RawMediaReader::ReadNextChunk() +{ +again: + /* see if it's time to cycle to the next track */ + if (current_track == MP4_INVALID_TRACK_ID) + { + if (track_num == number_of_tracks) + return NErr_EndOfFile; + + current_track = MP4FindTrackId(file, track_num); + if (current_track == MP4_INVALID_TRACK_ID) + return NErr_EndOfFile; + + track_num++; + + const char* trackType = MP4GetTrackType(file, current_track); + if (!MP4_IS_AUDIO_TRACK_TYPE(trackType) && !MP4_IS_VIDEO_TRACK_TYPE(trackType)) + { + current_track = MP4_INVALID_TRACK_ID; + goto again; + } + + chunk_id = 1; + number_of_chunks= MP4GetTrackNumberOfChunks(file, current_track); + } + + /* see if we've read all of our samples */ + if (chunk_id > number_of_chunks) + { + current_track = MP4_INVALID_TRACK_ID; + goto again; + } + + bool readSuccess = MP4ReadChunk(file, current_track, chunk_id, &chunk_buffer, &chunk_size); + if (!readSuccess) + return NErr_Error; + + chunk_position=0; + chunk_id++; + return NErr_Success; +} + +int RawMediaReader::Read(void *buffer, size_t buffer_size, size_t *bytes_read) +{ + if (buffer_size > INT_MAX) + return NErr_BadParameter; + + if (chunk_position==chunk_size) + { + MP4Free(chunk_buffer); + chunk_buffer=0; + } + + if (!chunk_buffer) + { + int ret = ReadNextChunk(); + if (ret != NErr_Success) + return ret; + } + + size_t to_read = chunk_size-chunk_position; + if (to_read > buffer_size) + to_read = buffer_size; + + memcpy(buffer, &chunk_buffer[chunk_position], to_read); + chunk_position += to_read; + *bytes_read = to_read; + return NErr_Success; +} + +size_t RawMediaReader::Release() +{ + delete this; + return 0; +} + +#define CBCLASS RawMediaReader +START_DISPATCH; +CB(RELEASE, Release); +CB(RAW_READ, Read); +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/RawMediaReader.h b/Src/Plugins/Input/in_mp4/RawMediaReader.h new file mode 100644 index 00000000..9080db25 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/RawMediaReader.h @@ -0,0 +1,40 @@ +#pragma once +#include "../Agave/DecodeFile/svc_raw_media_reader.h" +#include "../Agave/DecodeFile/ifc_raw_media_reader.h" +#include <mp4.h> + +// {5CBD1F27-5A63-4D8C-9297-D74518E1EF3A} +static const GUID mpeg4_raw_reader_guid = +{ 0x5cbd1f27, 0x5a63, 0x4d8c, { 0x92, 0x97, 0xd7, 0x45, 0x18, 0xe1, 0xef, 0x3a } }; + +class RawMediaReaderService : public svc_raw_media_reader +{ +public: + static const char *getServiceName() { return "MPEG-4 Raw Reader"; } + static GUID getServiceGuid() { return mpeg4_raw_reader_guid; } + int CreateRawMediaReader(const wchar_t *filename, ifc_raw_media_reader **reader); +protected: + RECVS_DISPATCH; +}; + +class RawMediaReader : public ifc_raw_media_reader +{ +public: + RawMediaReader(MP4FileHandle file, void *reader); + ~RawMediaReader(); + int Read(void *buffer, size_t buffer_size, size_t *bytes_read); + size_t Release(); +protected: + RECVS_DISPATCH; +private: + uint16_t track_num; + uint32_t number_of_tracks; + MP4TrackId current_track; + MP4FileHandle file; + void *reader; + MP4ChunkId chunk_id; + MP4ChunkId number_of_chunks; + uint32_t chunk_position, chunk_size; + uint8_t *chunk_buffer; + int ReadNextChunk(); +};
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/Stopper.cpp b/Src/Plugins/Input/in_mp4/Stopper.cpp new file mode 100644 index 00000000..8599d0cb --- /dev/null +++ b/Src/Plugins/Input/in_mp4/Stopper.cpp @@ -0,0 +1,47 @@ +#include "Stopper.h" +#include "main.h" +#include "../Winamp/wa_ipc.h" + +int m_force_seek=-1; + +Stopper::Stopper() : isplaying(0), timems(0) +{ +} + +void Stopper::ChangeTracking(bool mode) +{ + SendMessage(mod.hMainWindow, WM_USER, mode, IPC_ALLOW_PLAYTRACKING); // enable / disable stats updating +} + +void Stopper::Stop() +{ + isplaying = SendMessage(mod.hMainWindow, WM_USER, 0, IPC_ISPLAYING); + if (isplaying) + { + ChangeTracking(0); // disable stats updating + timems = SendMessage(mod.hMainWindow, WM_USER, 0, IPC_GETOUTPUTTIME); + SendMessage(mod.hMainWindow, WM_COMMAND, 40047, 0); // Stop + } +} + +void Stopper::Play() +{ + if (isplaying) // this works _most_ of the time, not sure why a small portion of the time it doesnt hrmph :/ + // ideally we should replace it with a system that pauses the decode thread, closes its file, + // does the shit, and reopens and reseeks to the new offset. for gaplessness + { + if (timems) + { + m_force_seek = timems; // SendMessage(mod.hMainWindow,WM_USER,timems,106); + } + else m_force_seek = -1; + SendMessage(mod.hMainWindow, WM_COMMAND, 40045, 0); // Play + m_force_seek = -1; + if (isplaying & 2) + { + SendMessage(mod.hMainWindow, WM_COMMAND, 40046, 0); // Pause + } + ChangeTracking(1); // enable stats updating + } + isplaying = 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/Stopper.h b/Src/Plugins/Input/in_mp4/Stopper.h new file mode 100644 index 00000000..fa6d984e --- /dev/null +++ b/Src/Plugins/Input/in_mp4/Stopper.h @@ -0,0 +1,11 @@ +#pragma once +class Stopper +{ +public: + Stopper(); + void ChangeTracking(bool); + void Stop(); + void Play(); + int isplaying, timems; +}; + diff --git a/Src/Plugins/Input/in_mp4/VideoThread.cpp b/Src/Plugins/Input/in_mp4/VideoThread.cpp new file mode 100644 index 00000000..8b844800 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/VideoThread.cpp @@ -0,0 +1,215 @@ +#include "main.h" +#include "VideoThread.h" +#include "../Winamp/wa_ipc.h" + +VideoSample *video_sample=0; +IVideoOutput *videoOutput=0; +static bool video_reopen=false; +static int height=0; +static int width=0; +static bool video_opened=false; +static int consecutive_early_frames; +HANDLE video_flush = 0, video_start_flushing=0, video_flush_done = 0, video_resume = 0; +static HANDLE video_thread = 0; +MP4SampleId nextVideoSampleId=1; // set in conjunction with video_flush +static void OpenVideo() +{ + if (!video_opened || video_reopen) + { + consecutive_early_frames = 0; + if (!videoOutput) + videoOutput = (IVideoOutput *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT); + + int color_format; + double aspect_ratio=1.0; + if (video && video->GetOutputFormat(&width, &height, &color_format, &aspect_ratio) == MP4_VIDEO_SUCCESS) + { + videoOutput->extended(VIDUSER_SET_THREAD_SAFE, 1, 0); + videoOutput->open(width, height, 0, 1.0/aspect_ratio, color_format); + video_opened = true; + video_reopen = false; + } + } +} + +static DecodedVideoSample *GetNextPicture() +{ + void *data, *decoder_data; + MP4Timestamp timestamp=video_sample?(video_sample->timestamp):0; + switch(video->GetPicture(&data, &decoder_data, ×tamp)) + { + case MP4_VIDEO_OUTPUT_FORMAT_CHANGED: + video_reopen=true; + // fall through + case MP4_VIDEO_SUCCESS: + DecodedVideoSample *decoded = new DecodedVideoSample; + decoded->decoder = video; + decoded->decoder_data = decoder_data; + decoded->timestamp = timestamp; + decoded->output = data; + return decoded; + } + return 0; +} + +static void OutputPicture(DecodedVideoSample *decoded_video_sample) +{ + if (decoded_video_sample) + { + int outputTime = (int)((decoded_video_sample->timestamp*1000ULL)/(uint64_t)m_video_timescale); +again: + MP4Duration realTime = GetClock(); + int time_diff = outputTime - realTime; + if (time_diff > 12 && consecutive_early_frames) // plenty of time, go ahead and turn off frame dropping + { + if (--consecutive_early_frames == 0) + video->HurryUp(0); + } + else if (time_diff < -50) // shit we're way late, start dropping frames + { + video->HurryUp(1); + consecutive_early_frames += 3; + } + if (time_diff > 3) + { + HANDLE handles[] = {killEvent, video_start_flushing}; + if (WaitForMultipleObjects(2, handles, FALSE, outputTime-realTime) != WAIT_TIMEOUT) + { + delete decoded_video_sample; + decoded_video_sample=0; + return; + } + goto again; // TODO: handle paused state a little better than this + } + + OpenVideo(); // open video if we havn't already + + videoOutput->draw(decoded_video_sample->output); + + delete decoded_video_sample; + decoded_video_sample=0; + /* TODO: probably want separate audio and video done flags + if (temp->sampleId == numSamples) // done! + done = true; + */ + } +} + +static bool ReadNextVideoSample() +{ + while (nextVideoSampleId <= numVideoSamples) + { + VideoSample &sample=*video_sample; + unsigned __int32 buffer_size = sample.inputSize; + bool isSync=false; + MP4Duration duration, offset; + play_mp4_guard.Lock(); + bool sample_read=MP4ReadSample(MP4hFile, video_track, nextVideoSampleId++, (unsigned __int8 **)&sample.input, &buffer_size, &sample.timestamp, &duration, &offset, &isSync); + play_mp4_guard.Unlock(); + if (sample_read) + { + // some buggy movies store signed int32 offsets, so let's deal with it + offset = (uint32_t)offset; + int32_t signed_offset = (int32_t)offset; + + sample.timestamp += signed_offset; + //int outputTime = (int)((sample.timestamp*1000ULL) /(uint64_t)m_video_timescale); + sample.inputValid = buffer_size; + return true; + } + } + return false; +} + +static DWORD WINAPI VideoPlayThread(LPVOID parameter) +{ + DWORD waitTime = 0; + HANDLE handles[] = {killEvent, video_flush, video_start_flushing, video_resume}; + while (1) + { + int ret = WaitForMultipleObjects(4, handles, FALSE, waitTime); + if (ret == WAIT_OBJECT_0) // kill + break; + else if (ret == WAIT_OBJECT_0+1) // flush + { + if (video) + video->Flush(); + ResetEvent(video_flush); + waitTime = 0; + SetEvent(video_flush_done); + } + else if (ret == WAIT_OBJECT_0+2) // start flushing + { + waitTime = INFINITE; // this will stop us from decoding samples for a while + ResetEvent(video_start_flushing); + SetEvent(video_flush_done); + } + else if (ret == WAIT_OBJECT_0+3) // resume playback (like flush but don't flush the decoder) + { + ResetEvent(video_resume); + waitTime = 0; + SetEvent(video_flush_done); + } + else if (ret == WAIT_TIMEOUT) + { + if (ReadNextVideoSample()) + { + int ret = video->DecodeSample(video_sample->input, video_sample->inputValid, video_sample->timestamp); + if (ret == MP4_VIDEO_OUTPUT_FORMAT_CHANGED) + video_reopen=true; + if (ret == MP4_VIDEO_AGAIN) + nextVideoSampleId--; + + DecodedVideoSample *picture = 0; + while (picture = GetNextPicture()) + { + OutputPicture(picture); + } + waitTime = 0; + } + else + { + // TODO: tell decoder end-of-file and get any buffers in queue + if (!audio) + PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0); + waitTime = INFINITE; // out of stuff to do, wait for kill or flush + } + } + else // error + break; + } + if (videoOutput) + videoOutput->close(); + return 0; +} + +void Video_Init() +{ + width=0; + height=0; + video_reopen=false; + video_opened=false; + video_flush = CreateEvent(NULL, TRUE, FALSE, NULL); + video_start_flushing = CreateEvent(NULL, TRUE, FALSE, NULL); + video_flush_done = CreateEvent(NULL, FALSE, FALSE, NULL); + video_resume = CreateEvent(NULL, TRUE, FALSE, NULL); + video_thread = CreateThread(0, 0, VideoPlayThread, 0, 0, 0); +} + +void Video_Close() +{ + WaitForSingleObject(video_thread, INFINITE); + CloseHandle(video_thread); + video_thread = 0; + CloseHandle(video_start_flushing); + video_start_flushing=0; + CloseHandle(video_flush); + video_flush=0; + CloseHandle(video_resume); + video_resume=0; + CloseHandle(video_flush_done); + video_flush_done = 0; + + delete video_sample; + video_sample=0; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/VideoThread.h b/Src/Plugins/Input/in_mp4/VideoThread.h new file mode 100644 index 00000000..389e3cf8 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/VideoThread.h @@ -0,0 +1,10 @@ +#pragma once +#include "AudioSample.h" +#include "../Winamp/wa_ipc.h" +void Video_Init(); +void Video_Close(); + +extern IVideoOutput *videoOutput; +extern VideoSample *video_sample; +extern HANDLE video_start_flushing, video_flush, video_flush_done, video_resume; +extern MP4SampleId nextVideoSampleId; // set in conjunction with video_flush
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/VirtualIO.cpp b/Src/Plugins/Input/in_mp4/VirtualIO.cpp new file mode 100644 index 00000000..b5ea4fc9 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/VirtualIO.cpp @@ -0,0 +1,716 @@ +#include "main.h" +#include "VirtualIO.h" +#include "api__in_mp4.h" +#include "api/service/waservicefactory.h" +#include "../../..\Components\wac_network\wac_network_http_receiver_api.h" +#include "../nu/AutoChar.h" +#include "../nu/ProgressTracker.h" + +#include <assert.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 api_httpreceiver *SetupConnection(const char *url, uint64_t start_position, uint64_t end_position) +{ + api_httpreceiver *http = 0; + waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) http = (api_httpreceiver *)sf->getInterface(); + + if (!http) + return http; + + 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->open(API_DNS_AUTODNS, HTTP_BUFFER_SIZE, (proxy && proxy[0]) ? (const char *)AutoChar(proxy) : NULL); + if (start_position && start_position != (uint64_t)-1) + { + if (end_position == (uint64_t)-1) + { + char temp[128] = {0}; + StringCchPrintfA(temp, 128, "Range: bytes=%I64u-", start_position); + http->addheader(temp); + } + else + { + char temp[128] = {0}; + StringCchPrintfA(temp, 128, "Range: bytes=%I64u-%I64u", start_position, end_position); + http->addheader(temp); + } + } + SetUserAgent(http); + http->connect(url); + return http; +} + +static DWORD CALLBACK ProgressiveThread(LPVOID param); + +static __int64 Seek64(HANDLE hf, __int64 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; +} + +int bufferCount; +static void Buffering(int bufStatus, const wchar_t *displayString) +{ + if (bufStatus < 0 || bufStatus > 100) + return; + + char tempdata[75*2] = {0, }; + + int csa = mod.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) mod.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)); +} + +class ProgressiveReader +{ +public: + ProgressiveReader(const char *url, HANDLE killswitch) : killswitch(killswitch) + { + thread_abort = CreateEvent(NULL, FALSE, FALSE, NULL); + download_thread = 0; + progressive_file_read = 0; + progressive_file_write = 0; + + content_length=0; + current_position=0; + stream_disconnected=false; + connected=false; + end_of_file=false; + + wchar_t temppath[MAX_PATH-14] = {0}; // MAX_PATH-14 'cause MSDN said so + GetTempPathW(MAX_PATH-14, temppath); + GetTempFileNameW(temppath, L"wdl", 0, filename); + this->url = _strdup(url); + http = SetupConnection(url, 0, (uint64_t)-1); + + progressive_file_read = CreateFileW(filename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0); + progressive_file_write = CreateFileW(filename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0); + download_thread = CreateThread(0, 0, ProgressiveThread, this, 0, 0); + + while (!connected && !stream_disconnected && WaitForSingleObject(killswitch, 55) == WAIT_TIMEOUT) + { + // nop + } + Buffer(); + } + + ~ProgressiveReader() + { + if (download_thread) + { + SetEvent(thread_abort); + WaitForSingleObject(download_thread, INFINITE); + CloseHandle(download_thread); + } + + if (thread_abort) + { + CloseHandle(thread_abort); + } + + CloseHandle(progressive_file_read); + CloseHandle(progressive_file_write); + DeleteFile(filename); + + if (http) + { + waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) http = (api_httpreceiver *)sf->releaseInterface(http); + http=0; + } + } + + void Buffer() + { + bufferCount=0; + for (int i=0;i<101;i++) + { + Buffering(i, L"Buffering: "); + WaitForSingleObject(killswitch, 55); + } + } + + void OnFinish() + { + stream_disconnected=true; + } + + + bool WaitForPosition(uint64_t position, uint64_t size) + { + do + { + bool valid = progress_tracker.Valid(position, position+size); + if (valid) + return true; + else + { + if (position < current_position) + { + Reconnect(position, position+size); + } + else + { + Buffer(); + } + } + } while (WaitForSingleObject(killswitch, 0) == WAIT_TIMEOUT); + return false; + } + + size_t Read(void *buffer, size_t size) + { + if (WaitForPosition(current_position, (uint64_t)size) == false) + return 0; + + DWORD bytes_read=0; + ReadFile(progressive_file_read, buffer, size, &bytes_read, NULL); + current_position += bytes_read; + return bytes_read; + } + + uint64_t GetFileLength() + { + return content_length; + } + + void Reconnect(uint64_t position, uint64_t end) + { + SetEvent(thread_abort); + WaitForSingleObject(download_thread, INFINITE); + ResetEvent(thread_abort); + + uint64_t new_start, new_end; + progress_tracker.Seek(position, end, &new_start, &new_end); + + CloseHandle(download_thread); + stream_disconnected=false; + connected=false; + if (http) + { + waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID); + if (sf) http = (api_httpreceiver *)sf->releaseInterface(http); + http=0; + } + + http = SetupConnection(url, new_start, new_end); + Seek64(progressive_file_write, new_start, SEEK_SET); + download_thread = CreateThread(0, 0, ProgressiveThread, this, 0, 0); + while (!connected && !stream_disconnected && WaitForSingleObject(killswitch, 55) == WAIT_TIMEOUT) + { + // nop + } + Buffer(); + } + + int SetPosition(uint64_t position) + { + if (position == content_length) + { + end_of_file=true; + } + else + { + if (!progress_tracker.Valid(position, position)) + { + Reconnect(position, (uint64_t)-1); + } + current_position = Seek64(progressive_file_read, position, SEEK_SET); + end_of_file=false; + } + return 0; + } + + int GetPosition(uint64_t *position) + { + if (end_of_file) + *position = content_length; + else + *position = current_position; + return 0; + } + + int EndOfFile() + { + return !!stream_disconnected; + } + + int Close() + { + SetEvent(thread_abort); + while (!stream_disconnected && WaitForSingleObject(killswitch, 55) == WAIT_TIMEOUT) + { + // nop + } + return 0; + } + + /* API used by download thread */ + void Write(const void *data, size_t data_len) + { + DWORD bytes_written = 0; + WriteFile(progressive_file_write, data, data_len, &bytes_written, 0); + progress_tracker.Write(data_len); + } + + int Wait(int milliseconds) + { + HANDLE handles[] = {killswitch, thread_abort}; + int ret = WaitForMultipleObjects(2, handles, FALSE, milliseconds); + if (ret == WAIT_OBJECT_0+1) + return 1; + else if (ret == WAIT_TIMEOUT) + return 0; + else + return -1; + } + + int DoRead(void *buffer, size_t bufferlen) + { + int ret = http->run(); + int bytes_received; + do + { + ret = http->run(); + bytes_received= http->get_bytes(buffer, bufferlen); + if (bytes_received) + Write(buffer, bytes_received); + } while (bytes_received); + return ret; + } + + int Connect() + { + do + { + int ret = http->run(); + if (ret == -1) // connection failed + return ret; + + // ---- check our reply code ---- + int replycode = http->getreplycode(); + switch (replycode) + { + case 0: + case 100: + break; + case 200: + case 206: + { + + const char *content_length_header = http->getheader("Content-Length"); + if (content_length_header) + { + uint64_t new_content_length = _strtoui64(content_length_header, 0, 10); + //InterlockedExchange64((volatile LONGLONG *)&content_length, new_content_length); + content_length = new_content_length; // TODO interlock on win32 + } + connected=true; + + return 0; + } + default: + return -1; + } + } + while (Wait(55) == 0); + return 0; + } + +private: + uint64_t current_position; + volatile uint64_t content_length; + bool end_of_file; + bool stream_disconnected; + bool connected; + char *url; + wchar_t filename[MAX_PATH]; + HANDLE progressive_file_read, progressive_file_write; + ProgressTracker progress_tracker; + HANDLE killswitch; + HANDLE download_thread; + api_httpreceiver *http; + HANDLE thread_abort; +}; + +static DWORD CALLBACK ProgressiveThread(LPVOID param) +{ + ProgressiveReader *reader = (ProgressiveReader *)param; + + if (reader->Connect() == 0) + { + int ret = 0; + while (ret == 0) + { + ret=reader->Wait(10); + if (ret >= 0) + { + char buffer[HTTP_BUFFER_SIZE] = {0}; + reader->DoRead(buffer, sizeof(buffer)); + } + } + } + reader->OnFinish(); + + return 0; +} + +u_int64_t HTTPGetFileLength(void *user) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + return reader->GetFileLength(); +} + +int HTTPSetPosition(void *user, u_int64_t position) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + return reader->SetPosition(position); +} + +int HTTPGetPosition(void *user, u_int64_t *position) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + return reader->GetPosition(position); +} + +size_t HTTPRead(void *user, void *buffer, size_t size) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + return reader->Read(buffer, size); +} + +size_t HTTPWrite(void *user, void *buffer, size_t size) +{ + return 1; +} + +int HTTPEndOfFile(void *user) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + return reader->EndOfFile(); +} + +int HTTPClose(void *user) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + return reader->Close(); +} + +Virtual_IO HTTPIO = +{ + HTTPGetFileLength, + HTTPSetPosition, + HTTPGetPosition, + HTTPRead, + HTTPWrite, + HTTPEndOfFile, + HTTPClose, +}; + +void *CreateReader(const wchar_t *url, HANDLE killswitch) +{ + + if ( WAC_API_DOWNLOADMANAGER ) + { + return new ProgressiveReader(AutoChar(url), killswitch); + } + else + return 0; +} + +void DestroyReader(void *user) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + delete reader; +} + +void StopReader(void *user) +{ + ProgressiveReader *reader = (ProgressiveReader *)user; + reader->Close(); +} + +/* ----------------------------------- */ + +struct Win32_State +{ + Win32_State() + { + memset(buffer, 0, sizeof(buffer)); + handle=0; + endOfFile=false; + position.QuadPart = 0; + event = CreateEvent(NULL, TRUE, TRUE, NULL); + read_offset=0; + io_active=false; + } + ~Win32_State() + { + if (handle && handle != INVALID_HANDLE_VALUE) + CancelIo(handle); + CloseHandle(event); + } + // void *userData; + HANDLE handle; + bool endOfFile; + LARGE_INTEGER position; + HANDLE event; + OVERLAPPED overlapped; + DWORD read_offset; + bool io_active; + char buffer[16384]; +}; + +static __int64 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; +} + +u_int64_t UnicodeGetFileLength(void *user) +{ + Win32_State *state = static_cast<Win32_State *>(user); + assert(state->handle); + return FileSize64(state->handle); +} + + + +int UnicodeSetPosition(void *user, u_int64_t position) +{ + Win32_State *state = static_cast<Win32_State *>(user); + assert(state->handle); + __int64 diff = position - state->position.QuadPart; + + if ((diff+state->read_offset) >= sizeof(state->buffer) + || (diff+state->read_offset) < 0) + { + CancelIo(state->handle); + state->io_active = 0; + state->read_offset = 0; + } + else if (diff) + state->read_offset += (DWORD)diff; + + state->position.QuadPart = position; + state->endOfFile = false; + return 0; +} + +int UnicodeGetPosition(void *user, u_int64_t *position) +{ + Win32_State *state = static_cast<Win32_State *>(user); + assert(state->handle); + *position = state->position.QuadPart; + return 0; +} + +static void DoRead(Win32_State *state) +{ + WaitForSingleObject(state->event, INFINITE); + state->overlapped.hEvent = state->event; + state->overlapped.Offset = state->position.LowPart; + state->overlapped.OffsetHigh = state->position.HighPart; + state->read_offset = 0; + ResetEvent(state->event); + ReadFile(state->handle, state->buffer, sizeof(state->buffer), NULL, &state->overlapped); + //int error = GetLastError();//ERROR_IO_PENDING = 997 + state->io_active=true; +} + +size_t UnicodeRead(void *user, void *buffer, size_t size) +{ + Win32_State *state = static_cast<Win32_State *>(user); + assert(state->handle); + size_t totalRead=0; + HANDLE file = state->handle; + if (!state->io_active) + { + DoRead(state); + } + + if (state->read_offset == sizeof(state->buffer)) + { + DoRead(state); + } + + while (size > (sizeof(state->buffer) - state->read_offset)) + { + DWORD bytesRead=0; + BOOL res = GetOverlappedResult(file, &state->overlapped, &bytesRead, TRUE); + if ((res && bytesRead != sizeof(state->buffer)) + || (!res && GetLastError() == ERROR_HANDLE_EOF)) + { + state->endOfFile = true; + } + + if (bytesRead > state->read_offset) + { + size_t bytesToCopy = bytesRead-state->read_offset; + memcpy(buffer, state->buffer + state->read_offset, bytesToCopy); + buffer=(uint8_t *)buffer + bytesToCopy; + totalRead+=bytesToCopy; + size-=bytesToCopy; + + + if (state->endOfFile) + return totalRead; + + state->position.QuadPart += bytesToCopy; + DoRead(state); + } + else + break; + } + + while (1) + { + DWORD bytesRead=0; + BOOL res = GetOverlappedResult(file, &state->overlapped, &bytesRead, FALSE); + if ((res && bytesRead != sizeof(state->buffer)) + || (!res && GetLastError() == ERROR_HANDLE_EOF)) + { + state->endOfFile = true; + } + if (bytesRead >= (size + state->read_offset)) + { + memcpy(buffer, state->buffer + state->read_offset, size); + state->read_offset += size; + totalRead+=size; + state->position.QuadPart += size; + break; + } + + if (state->endOfFile) + break; + + WaitForSingleObject(state->event, 10); // wait 10 milliseconds or when buffer is done, whichever is faster + } + + return totalRead; +} + +size_t UnicodeWrite(void *user, void *buffer, size_t size) +{ + Win32_State *state = static_cast<Win32_State *>(user); + DWORD written = 0; + assert(state->handle); + WriteFile(state->handle, buffer, size, &written, NULL); + return 0; +} + +int UnicodeEndOfFile(void *user) +{ + Win32_State *state = static_cast<Win32_State *>(user); + return state->endOfFile; +} + +int UnicodeClose(void *user) +{ + Win32_State *state = static_cast<Win32_State *>(user); + if (state->handle) + CloseHandle(state->handle); + + state->handle=0; + return 0; +} + +Virtual_IO UnicodeIO = +{ + UnicodeGetFileLength, + UnicodeSetPosition, + UnicodeGetPosition, + UnicodeRead, + UnicodeWrite, + UnicodeEndOfFile, + UnicodeClose, +}; + + +void *CreateUnicodeReader(const wchar_t *filename) +{ + HANDLE fileHandle = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); + if (fileHandle == INVALID_HANDLE_VALUE) + return 0; + + Win32_State *state = new Win32_State; + if (!state) + { + CloseHandle(fileHandle); + return 0; + } + state->endOfFile = false; + state->handle = fileHandle; + return state; +} + +void DestroyUnicodeReader(void *reader) +{ + if (reader) // need to check because of the cast + delete (Win32_State *)reader; +} diff --git a/Src/Plugins/Input/in_mp4/VirtualIO.h b/Src/Plugins/Input/in_mp4/VirtualIO.h new file mode 100644 index 00000000..796eff55 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/VirtualIO.h @@ -0,0 +1,17 @@ +#ifndef NULLSOFT_IN_MP4_VIRTUALIO_H +#define NULLSOFT_IN_MP4_VIRTUALIO_H +#include "main.h" +#include <api/service/svcs/svc_fileread.h> +#include <virtual_io.h> + +void *CreateReader(const wchar_t *url, HANDLE killswitch); +void DestroyReader(void *reader); +void StopReader(void *reader); +extern Virtual_IO HTTPIO; +extern Virtual_IO UnicodeIO; + +void *CreateUnicodeReader(const wchar_t *filename); +void DestroyUnicodeReader(void *reader); +int UnicodeClose(void *user); + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/api.cpp b/Src/Plugins/Input/in_mp4/api.cpp new file mode 100644 index 00000000..9014c742 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/api.cpp @@ -0,0 +1,4 @@ +#include "api__in_mp4.h" + +api_downloadManager *WAC_API_DOWNLOADMANAGER =0; +api_threadpool *WASABI_API_THREADPOOL=0;
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/api__in_mp4.h b/Src/Plugins/Input/in_mp4/api__in_mp4.h new file mode 100644 index 00000000..cd83c8ea --- /dev/null +++ b/Src/Plugins/Input/in_mp4/api__in_mp4.h @@ -0,0 +1,22 @@ +#ifndef NULLSOFT_APIH +#define NULLSOFT_APIH + +#include "../Agave/Config/api_config.h" +extern api_config *AGAVE_API_CONFIG; + +#include <api/application/api_application.h> +#define WASABI_API_APP applicationApi + +#include <api/memmgr/api_memmgr.h> +extern api_memmgr *memmgrApi; +#define WASABI_API_MEMMGR memmgrApi + +#include "../Agave/Language/api_language.h" + +#include "../Components/wac_downloadManager/wac_downloadManager_api.h" + +#include "../nu/threadpool/api_threadpool.h" +extern api_threadpool *threadPoolApi; +#define WASABI_API_THREADPOOL threadPoolApi + +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/config.cpp b/Src/Plugins/Input/in_mp4/config.cpp new file mode 100644 index 00000000..27750ccb --- /dev/null +++ b/Src/Plugins/Input/in_mp4/config.cpp @@ -0,0 +1,49 @@ +#include "main.h" +#include "api__in_mp4.h" +#include "../nu/AutoChar.h" +#include "resource.h" + +bool config_show_average_bitrate = true; + +INT_PTR CALLBACK ConfigProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) + { + case WM_INITDIALOG: + { + wchar_t exts[1024] = {0}; + GetPrivateProfileStringW(L"in_mp4", L"extensionlist", defaultExtensions, exts, 1024, m_ini); + SetDlgItemTextW(hwndDlg, IDC_EXTENSIONLIST, exts); + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_DEFAULT: + SetDlgItemTextW(hwndDlg, IDC_EXTENSIONLIST, defaultExtensions); + break; + case IDOK: + { + wchar_t exts[1024] = {0}; + GetDlgItemTextW(hwndDlg, IDC_EXTENSIONLIST, exts, 1024); + if (!_wcsicmp(exts, defaultExtensions)) // same as default? + WritePrivateProfileStringW(L"in_mp4", L"extensionlist", 0, m_ini); + else + WritePrivateProfileStringW(L"in_mp4", L"extensionlist", exts, m_ini); + free(mod.FileExtensions); + mod.FileExtensions = BuildExtensions(AutoChar(exts)); + EndDialog(hwndDlg, 0); + } + break; + case IDCANCEL: + EndDialog(hwndDlg, 1); + break; + } + break; + } + return 0; +} +void config(HWND hwndParent) +{ + WASABI_API_DIALOGBOXW(IDD_CONFIG, hwndParent, ConfigProc); +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/in_mp4.rc b/Src/Plugins/Input/in_mp4/in_mp4.rc new file mode 100644 index 00000000..2ad421b2 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/in_mp4.rc @@ -0,0 +1,129 @@ +// 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 + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_CONFIG DIALOGEX 0, 0, 215, 50 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "MP4 Configuration" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Extension List (semicolon delimited):",IDC_STATIC,4,4,208,10 + EDITTEXT IDC_EXTENSIONLIST,4,16,207,13,ES_AUTOHSCROLL + PUSHBUTTON "Set to Defaults",IDC_DEFAULT,4,33,60,13 + DEFPUSHBUTTON "OK",IDOK,107,33,50,13 + PUSHBUTTON "Cancel",IDCANCEL,161,33,50,13 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_CONFIG, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 211 + TOPMARGIN, 4 + BOTTOMMARGIN, 46 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MPEG4_AUDIO_DECODER "Nullsoft MP4 Demuxer v%s" + 65535 "{F30C75C1-D284-4cd5-9CED-2BD9E7869438}" +END + +STRINGTABLE +BEGIN + IDS_NULLSOFT_MPEG4_AUDIO_DECODER_OLD "Nullsoft MP4 Demuxer" + IDS_CANNOT_UPDATE_THE_FILE "Cannot update the file!" + IDS_ERROR "Error" + IDS_MP4_FILE "MP4 File" + IDS_FAMILY_STRING_M4A "MPEG-4 Audio File Format" + IDS_FAMILY_STRING_MPEG4 "MPEG-4 File Format" + IDS_FAMILY_STRING_M4V "MPEG-4 Video File Format" + IDS_FAMILY_STRING_QUICKTIME "Quicktime Movie" + IDS_FAMILY_STRING_3GPP "3GPP File Format" + IDS_FAMILY_STRING_FLV "Flash MPEG-4 Video" + IDS_ABOUT_TEXT "%s\n© 2005-2023 Winamp SA\n\nBuild date: %s\n\nUses the libmp4v2 library\nfrom the MPEG4IP package\n(http://mpeg4ip.sourceforge.net)" + IDS_AUDIO_INFO "Audio:\n\t%S\n\t%u kbps\n" + IDS_VIDEO_INFO "Video:\n\t%S\n\t%u kbps\n" +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_mp4/in_mp4.sln b/Src/Plugins/Input/in_mp4/in_mp4.sln new file mode 100644 index 00000000..3521de5f --- /dev/null +++ b/Src/Plugins/Input/in_mp4/in_mp4.sln @@ -0,0 +1,67 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29424.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "in_mp4", "in_mp4.vcxproj", "{9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}" + ProjectSection(ProjectDependencies) = postProject + {F6962367-6A6C-4E7D-B661-B767E904F451} = {F6962367-6A6C-4E7D-B661-B767E904F451} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmp4v2", "..\libmp4v2\libmp4v2.vcxproj", "{EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "aacdec-mft", "..\aacdec-mft\aacdec-mft.vcxproj", "{F6962367-6A6C-4E7D-B661-B767E904F451}" + ProjectSection(ProjectDependencies) = postProject + {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}" +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 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Debug|Win32.ActiveCfg = Debug|Win32 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Debug|Win32.Build.0 = Debug|Win32 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Debug|x64.ActiveCfg = Debug|x64 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Debug|x64.Build.0 = Debug|x64 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Release|Win32.ActiveCfg = Release|Win32 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Release|Win32.Build.0 = Release|Win32 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Release|x64.ActiveCfg = Release|x64 + {9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}.Release|x64.Build.0 = Release|x64 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Debug|Win32.ActiveCfg = Debug|Win32 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Debug|Win32.Build.0 = Debug|Win32 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Debug|x64.ActiveCfg = Debug|x64 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Debug|x64.Build.0 = Debug|x64 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Release|Win32.ActiveCfg = Release|Win32 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Release|Win32.Build.0 = Release|Win32 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Release|x64.ActiveCfg = Release|x64 + {EFB9B882-6A8B-463D-A8E3-A2807AFC5D9F}.Release|x64.Build.0 = Release|x64 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Debug|Win32.ActiveCfg = Debug|Win32 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Debug|Win32.Build.0 = Debug|Win32 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Debug|x64.ActiveCfg = Debug|x64 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Debug|x64.Build.0 = Debug|x64 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Release|Win32.ActiveCfg = Release|Win32 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Release|Win32.Build.0 = Release|Win32 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Release|x64.ActiveCfg = Release|x64 + {F6962367-6A6C-4E7D-B661-B767E904F451}.Release|x64.Build.0 = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FC646532-2050-40A5-A2AB-F699F1C071C4} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Input/in_mp4/in_mp4.vcxproj b/Src/Plugins/Input/in_mp4/in_mp4.vcxproj new file mode 100644 index 00000000..ecaf4b55 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/in_mp4.vcxproj @@ -0,0 +1,301 @@ +<?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>{9F6D17FA-6DFD-436F-B54E-1B63D012D1A2}</ProjectGuid> + <RootNamespace>in_mp4</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> + <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> + <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>.;..\..\..\external_dependencies\libmp4v2;..\..\..\Wasabi;..\..\..\external_dependencies\libmp4v2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_DEBUG;WIN32;_WINDOWS;_USRDLL;in_mp4_EXPORTS;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <BrowseInformation>true</BrowseInformation> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4018;4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>libmp4v2.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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>.;..\..\..\libmp4v2;..\..\..\Wasabi;..\..\..\libmp4v2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_DEBUG;WIN64;_WINDOWS;_USRDLL;in_mp4_EXPORTS;UNICODE_INPUT_PLUGIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <BrowseInformation>true</BrowseInformation> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4018;4244;4267;4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <DelayLoadDLLs>libmp4v2.dll;%(DelayLoadDLLs)</DelayLoadDLLs> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\external_dependencies\libmp4v2;..\..\..\Wasabi;..\..\..\external_dependencies\libmp4v2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>NDEBUG;WIN32;_WINDOWS;_USRDLL;in_mp4_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4018;4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\libmp4v2;..\..\..\Wasabi;..\..\..\libmp4v2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>NDEBUG;WIN64;_WINDOWS;_USRDLL;in_mp4_EXPORTS;UNICODE_INPUT_PLUGIN;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <CompileAs>Default</CompileAs> + <DisableSpecificWarnings>4018;4244;4267;4312;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x0409</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>winmm.lib;wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <SubSystem>Windows</SubSystem> + </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> + <ProjectReference Include="..\..\..\external_dependencies\libmp4v2\libmp4v2.vcxproj"> + <Project>{efb9b882-6a8b-463d-a8e3-a2807afc5d9f}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj"> + <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\nu\bitbuffer.cpp" /> + <ClCompile Include="..\..\..\nu\GaplessRingBuffer.cpp" /> + <ClCompile Include="..\..\..\nu\RingBuffer.cpp" /> + <ClCompile Include="..\..\..\nu\SpillBuffer.cpp" /> + <ClCompile Include="AlbumArt.cpp" /> + <ClCompile Include="api.cpp" /> + <ClCompile Include="config.cpp" /> + <ClCompile Include="ExtendedInfo.cpp" /> + <ClCompile Include="ExtendedRead.cpp" /> + <ClCompile Include="infobox.cpp" /> + <ClCompile Include="Main.cpp" /> + <ClCompile Include="mp4.cpp" /> + <ClCompile Include="mpeg4audio.cpp" /> + <ClCompile Include="mpeg4video.cpp" /> + <ClCompile Include="PlayThread.cpp" /> + <ClCompile Include="RawMediaReader.cpp" /> + <ClCompile Include="Stopper.cpp" /> + <ClCompile Include="VideoThread.cpp" /> + <ClCompile Include="VirtualIO.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\nu\GaplessRingBuffer.h" /> + <ClInclude Include="..\..\..\nu\RingBuffer.h" /> + <ClInclude Include="..\..\..\nu\VideoClock.h" /> + <ClInclude Include="AlbumArt.h" /> + <ClInclude Include="api__in_mp4.h" /> + <ClInclude Include="AudioSample.h" /> + <ClInclude Include="Main.h" /> + <ClInclude Include="mpeg4audio.h" /> + <ClInclude Include="mpeg4video.h" /> + <ClInclude Include="RawMediaReader.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="Stopper.h" /> + <ClInclude Include="VideoThread.h" /> + <ClInclude Include="VirtualIO.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_mp4.rc" /> + </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_mp4/in_mp4.vcxproj.filters b/Src/Plugins/Input/in_mp4/in_mp4.vcxproj.filters new file mode 100644 index 00000000..e6d151f1 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/in_mp4.vcxproj.filters @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="AlbumArt.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="api.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="config.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ExtendedRead.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="infobox.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="mp4.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="mpeg4audio.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="mpeg4video.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="PlayThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="RawMediaReader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Stopper.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="VideoThread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="VirtualIO.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\bitbuffer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\nu\GaplessRingBuffer.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="AlbumArt.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="api__in_mp4.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="AudioSample.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Main.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="mpeg4audio.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="mpeg4video.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="RawMediaReader.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Stopper.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="VideoThread.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="VirtualIO.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\GaplessRingBuffer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\RingBuffer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\VideoClock.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{90f60180-1dcd-423f-9b3c-19e90036737f}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{d93a6e62-1a2b-4fe9-bca8-34c3fb1c77d7}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{5f0d4520-0772-4236-af42-4348bc4c5561}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="in_mp4.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/infobox.cpp b/Src/Plugins/Input/in_mp4/infobox.cpp new file mode 100644 index 00000000..c9630997 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/infobox.cpp @@ -0,0 +1,20 @@ +#include "main.h" +#include "resource.h" +#include "../winamp/in2.h" +#include "api__in_mp4.h" + +int infoDlg(const wchar_t *fn, HWND hwnd) +{ // this has been made obsolete by the below. + return 0; +} + +extern "C" +{ + // return 1 if you want winamp to show it's own file info dialogue, 0 if you want to show your own (via In_Module.InfoBox) + // if returning 1, remember to implement winampGetExtendedFileInfo("formatinformation")! + __declspec(dllexport) int winampUseUnifiedFileInfoDlg(const wchar_t * fn) + { + return 1; + } + // no advanced pane in this plugin :) +}; diff --git a/Src/Plugins/Input/in_mp4/mp4.cpp b/Src/Plugins/Input/in_mp4/mp4.cpp new file mode 100644 index 00000000..0c615312 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/mp4.cpp @@ -0,0 +1,98 @@ +#include "main.h" +#include "mpeg4audio.h" +#include "api__in_mp4.h" +#include <api/service/waservicefactory.h> +#include <api/service/services.h> +//#include "JPEGDecoder.h" +//#include "MPEG4VideoDecoder.h" +//#include "AVCDecoder.h" +#include "../nu/bitbuffer.h" +#include <assert.h> + +void ConfigureDecoderASC(MP4FileHandle file, MP4TrackId track, MP4AudioDecoder *decoder) +{ + unsigned char *buffer = NULL; + unsigned __int32 buffer_size = 0; + + // TODO: HUGE hack + const char *location = decoder->GetCodecInfoString(); + if (location) + MP4GetTrackBytesProperty(file, track, location , (unsigned __int8 **)&buffer, &buffer_size); + else + MP4GetTrackESConfiguration(file, track, (unsigned __int8 **)&buffer, &buffer_size); + + if (buffer) + { + decoder->AudioSpecificConfiguration(buffer, buffer_size); + MP4Free(buffer); + } +} + +bool CreateDecoder(MP4FileHandle file, MP4TrackId track, MP4AudioDecoder *&decoder, waServiceFactory *&serviceFactory) +{ + const char *media_data_name = MP4GetTrackMediaDataName(file, track); + u_int8_t audioType = MP4GetTrackEsdsObjectTypeId(file, track); + u_int8_t mpeg4Type = MP4GetTrackAudioMpeg4Type(file, track); + if (!media_data_name) + media_data_name = "mp4a"; // let's assume it's AAC if nothing else is said + waServiceFactory *sf = 0; + + int n = 0; + while (sf = mod.service->service_enumService(WaSvc::MP4AUDIODECODER, n++)) + { + MP4AudioDecoder * dec = static_cast<MP4AudioDecoder *>(sf->getInterface()); + if (dec && dec->CanHandleCodec(media_data_name) + && (!audioType || dec->CanHandleType(audioType)) + && (!mpeg4Type || dec->CanHandleMPEG4Type(mpeg4Type)) + /*&& dec->Open() == MP4_SUCCESS*/) + { + //ConfigureDecoderASC(file, track, dec); + decoder = dec; + serviceFactory = sf; + return true; + } + + sf->releaseInterface(dec); + + } + return false; +} + +bool CreateVideoDecoder(MP4FileHandle file, MP4TrackId track, MP4VideoDecoder *&decoder, waServiceFactory *&serviceFactory) +{ + const char *media_data_name = MP4GetTrackMediaDataName(file, track); + // TODO check this is ok to disable... + //u_int8_t audioType = MP4GetTrackEsdsObjectTypeId(file, track); + //u_int8_t profileLevel = MP4GetVideoProfileLevel(file, track); + if (!media_data_name) + return false; + + waServiceFactory *sf = 0; + int n = 0; + while (sf = mod.service->service_enumService(WaSvc::MP4VIDEODECODER, n++)) + { + MP4VideoDecoder *dec = static_cast<MP4VideoDecoder *>(sf->getInterface()); + if (dec && dec->CanHandleCodec(media_data_name) + /*&& dec->Open() == MP4_SUCCESS*/) + { + decoder = dec; + serviceFactory = sf; + return true; + } + sf->releaseInterface(dec); + } + /* + if (!strcmp(media_data_name, "mp4v")) + { + // TODO: write a way to get the binary data out of an atom + uint8_t *buffer; + uint32_t buffer_size = 0; + MP4GetTrackESConfiguration(file, track, &buffer, &buffer_size); + + decoder = new MPEG4VideoDecoder(0,0);//buffer, buffer_size); + return true; + } +*/ + + return false; +}
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/mpeg4audio.cpp b/Src/Plugins/Input/in_mp4/mpeg4audio.cpp new file mode 100644 index 00000000..d8be9a86 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/mpeg4audio.cpp @@ -0,0 +1 @@ +#include "mpeg4audio.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/mpeg4audio.h b/Src/Plugins/Input/in_mp4/mpeg4audio.h new file mode 100644 index 00000000..ff629b16 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/mpeg4audio.h @@ -0,0 +1,208 @@ +#ifndef NULLSOFT_MPEG4AUDIOH +#define NULLSOFT_MPEG4AUDIOH +#include "../external_dependencies/libmp4v2/mp4.h" +#include <bfc/dispatch.h> +#include <bfc/platform/types.h> +#include <api/service/services.h> +enum +{ + MP4_SUCCESS = 0, + MP4_FAILURE = 1, + + MP4_GETOUTPUTPROPERTIES_NEED_MORE_INPUT = 2, + MP4_NEED_MORE_INPUT = 2, + + MP4_GETCURRENTBITRATE_UNKNOWN = 2, // unable to calculate (e.g. VBR for CT's decoder) + + MP4_OUTPUTFRAMESIZE_VARIABLE = 2, // don't know if any codecs do this + + MP4_TYPE_MPEG1_AUDIO = 0x6B, + MP4_TYPE_MPEG2_AUDIO = 0x69, + MP4_TYPE_MPEG2_AAC_MAIN_AUDIO = 0x66, + MP4_TYPE_MPEG2_AAC_LC_AUDIO = 0x67, + MP4_TYPE_MPEG2_AAC_SSR_AUDIO = 0x68, + MP4_TYPE_MPEG4_AUDIO = 0x40, + MP4_TYPE_PRIVATE_AUDIO = 0xC0, + MP4_TYPE_PCM16_LITTLE_ENDIAN_AUDIO = 0xE0, + MP4_TYPE_VORBIS_AUDIO = 0xE1, + MP4_TYPE_AC3_AUDIO = 0xE2, + MP4_TYPE_ALAW_AUDIO = 0xE3, + MP4_TYPE_ULAW_AUDIO = 0xE4, + MP4_TYPE_G723_AUDIO = 0xE5, + MP4_TYPE_PCM16_BIG_ENDIAN_AUDIO = 0xE6, + + /* MP4 MPEG-4 Audio types from 14496-3 Table 1.5.1 */ + MP4_MPEG4_TYPE_AAC_MAIN_AUDIO = 1, + MP4_MPEG4_TYPE_AAC_LC_AUDIO = 2, + MP4_MPEG4_TYPE_AAC_SSR_AUDIO = 3, + MP4_MPEG4_TYPE_AAC_LTP_AUDIO = 4, + MP4_MPEG4_TYPE_AAC_HE_AUDIO = 5, + MP4_MPEG4_TYPE_AAC_SCALABLE_AUDIO = 6, + MP4_MPEG4_TYPE_CELP_AUDIO = 8, + MP4_MPEG4_TYPE_HVXC_AUDIO = 9, + MP4_MPEG4_TYPE_TTSI_AUDIO = 12, + MP4_MPEG4_TYPE_MAIN_SYNTHETIC_AUDIO = 13, + MP4_MPEG4_TYPE_WAVETABLE_AUDIO = 14, + MP4_MPEG4_TYPE_MIDI_AUDIO = 15, + MP4_MPEG4_TYPE_ALGORITHMIC_FX_AUDIO = 16, + MP4_MPEG4_TYPE_PARAMETRIC_STEREO=29, + MP4_MPEG4_ALS_AUDIO=31, + MP4_MPEG4_LAYER1_AUDIO = 32, + MP4_MPEG4_LAYER2_AUDIO= 33, + MP4_MPEG4_LAYER3_AUDIO= 34, + + MP4_MPEG4_SLS_AUDIO=37, +}; + +class MP4AudioDecoder : public Dispatchable +{ +protected: + MP4AudioDecoder() {} + ~MP4AudioDecoder() {} + +public: + static FOURCC getServiceType() { return WaSvc::MP4AUDIODECODER; } + int Open(); + int OpenEx(size_t bits, size_t maxChannels, bool useFloat); + int OpenMP4(MP4FileHandle mp4_file, MP4TrackId mp4_track, size_t output_bits, size_t maxChannels, bool useFloat); + int AudioSpecificConfiguration(void *buffer, size_t buffer_size); // reads ASC block from mp4 file + int GetCurrentBitrate(unsigned int *bitrate); + int OutputFrameSize(size_t *frameSize); // in Frames + int GetOutputProperties(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample); // can return an error code for "havn't locked to stream yet" + int GetOutputPropertiesEx(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample, bool *isFloat); + int DecodeSample(void *inputBuffer, size_t inputBufferBytes, void *outputBuffer, size_t *outputBufferBytes); + void Flush(); + void Close(); + const char *GetCodecInfoString(); + int CanHandleCodec(const char *codecName); + int CanHandleType(uint8_t type); + int CanHandleMPEG4Type(uint8_t type); + int SetGain(float gain); + int RequireChunks(); // return 1 if your decoder wants to read whole chunks rather than samples + void EndOfStream(); + +public: + DISPATCH_CODES + { + MPEG4_AUDIO_OPEN = 10, + MPEG4_AUDIO_OPEN_EX = 11, + MPEG4_AUDIO_OPENMP4 = 12, + MPEG4_AUDIO_ASC = 20, + MPEG4_AUDIO_BITRATE = 30, + MPEG4_AUDIO_FRAMESIZE = 40, + MPEG4_AUDIO_OUTPUTINFO = 50, + MPEG4_AUDIO_OUTPUTINFO_EX = 51, + MPEG4_AUDIO_DECODE = 60, + MPEG4_AUDIO_FLUSH = 70, + MPEG4_AUDIO_CLOSE = 80, + MPEG4_AUDIO_CODEC_INFO_STRING = 90, + MPEG4_AUDIO_HANDLES_CODEC = 100, + MPEG4_AUDIO_HANDLES_TYPE = 110, + MPEG4_AUDIO_HANDLES_MPEG4_TYPE = 120, + MPEG4_AUDIO_SET_GAIN=130, + MPEG4_AUDIO_REQUIRE_CHUNKS = 140, + MPEG4_END_OF_STREAM = 150, + }; +}; + +inline int MP4AudioDecoder::Open() +{ + return _call(MPEG4_AUDIO_OPEN, (int)MP4_FAILURE); +} + +inline int MP4AudioDecoder::OpenEx(size_t bits, size_t maxChannels, bool useFloat) +{ + void *params[3] = { &bits, &maxChannels, &useFloat}; + int retval; + if (_dispatch(MPEG4_AUDIO_OPEN_EX, &retval, params, 3)) + return retval; + else + return Open(); +} + +inline int MP4AudioDecoder::OpenMP4(MP4FileHandle mp4_file, MP4TrackId mp4_track, size_t output_bits, size_t maxChannels, bool useFloat) +{ + void *params[5] = { &mp4_file, &mp4_track, &output_bits, &maxChannels, &useFloat}; + int retval; + if (_dispatch(MPEG4_AUDIO_OPENMP4, &retval, params, 5)) + return retval; + else + return OpenEx(output_bits, maxChannels, useFloat); +} + +inline int MP4AudioDecoder::AudioSpecificConfiguration(void *buffer, size_t buffer_size) +{ + return _call(MPEG4_AUDIO_ASC, (int)MP4_FAILURE, buffer, buffer_size); +} +inline int MP4AudioDecoder::GetCurrentBitrate(unsigned int *bitrate) +{ + return _call(MPEG4_AUDIO_BITRATE, (int)MP4_FAILURE, bitrate); +} +inline int MP4AudioDecoder::OutputFrameSize(size_t *frameSize) +{ + return _call(MPEG4_AUDIO_FRAMESIZE, (int)MP4_FAILURE, frameSize); +} +inline int MP4AudioDecoder::GetOutputProperties(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample) +{ + return _call(MPEG4_AUDIO_OUTPUTINFO, (int)MP4_FAILURE, sampleRate, channels, bitsPerSample); +} +inline int MP4AudioDecoder::GetOutputPropertiesEx(unsigned int *sampleRate, unsigned int *channels, unsigned int *bitsPerSample, bool *useFloat) +{ + void *params[4] = { &sampleRate, &channels, &bitsPerSample, &useFloat}; + int retval; + if (_dispatch(MPEG4_AUDIO_OUTPUTINFO_EX, &retval, params, 4)) + return retval; + else + { + *useFloat=false; + return GetOutputProperties(sampleRate, channels, bitsPerSample); + } +// return _call(MPEG4_AUDIO_OUTPUTINFO_EX, (int)MP4_FAILURE, sampleRate, channels, bitsPerSample); +} +inline int MP4AudioDecoder::DecodeSample(void *inputBuffer, size_t inputBufferBytes, void *outputBuffer, size_t *outputBufferBytes) +{ + return _call(MPEG4_AUDIO_DECODE, (int)MP4_FAILURE, inputBuffer, inputBufferBytes, outputBuffer, outputBufferBytes); +} + +inline void MP4AudioDecoder::Flush() +{ + _voidcall(MPEG4_AUDIO_FLUSH); +} +inline void MP4AudioDecoder::Close() +{ + _voidcall(MPEG4_AUDIO_CLOSE); +} +inline const char *MP4AudioDecoder::GetCodecInfoString() +{ + return _call(MPEG4_AUDIO_CODEC_INFO_STRING, (const char *)0); +} + +inline int MP4AudioDecoder::CanHandleCodec(const char *codecName) +{ + return _call(MPEG4_AUDIO_HANDLES_CODEC, (int)0, codecName); +} + +inline int MP4AudioDecoder::CanHandleType(uint8_t type) +{ + return _call(MPEG4_AUDIO_HANDLES_TYPE, (int)0, type); +} +inline int MP4AudioDecoder::CanHandleMPEG4Type(uint8_t type) +{ + return _call(MPEG4_AUDIO_HANDLES_MPEG4_TYPE, (int)0, type); +} + +inline int MP4AudioDecoder::SetGain(float gain) +{ + return _call(MPEG4_AUDIO_SET_GAIN, (int)MP4_FAILURE, gain); +} + +inline int MP4AudioDecoder::RequireChunks() +{ + return _call(MPEG4_AUDIO_REQUIRE_CHUNKS, (int)0); +} + +inline void MP4AudioDecoder::EndOfStream() +{ + _voidcall(MPEG4_END_OF_STREAM); +} +#endif diff --git a/Src/Plugins/Input/in_mp4/mpeg4video.cpp b/Src/Plugins/Input/in_mp4/mpeg4video.cpp new file mode 100644 index 00000000..aff2c799 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/mpeg4video.cpp @@ -0,0 +1 @@ +#include "mpeg4video.h"
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/mpeg4video.h b/Src/Plugins/Input/in_mp4/mpeg4video.h new file mode 100644 index 00000000..89b56a96 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/mpeg4video.h @@ -0,0 +1,92 @@ +#ifndef NULLSOFT_MPEG4VIDEO_H +#define NULLSOFT_MPEG4VIDEO_H +#include "../external_dependencies/libmp4v2/mp4.h" +#include <bfc/dispatch.h> +#include <api/service/services.h> + +enum +{ + MP4_VIDEO_SUCCESS = 0, + MP4_VIDEO_FAILURE = 1, + MP4_VIDEO_OUTPUT_FORMAT_CHANGED = -1, // succeeded, but call GetOutputFormat again! + MP4_VIDEO_AGAIN = -2, +}; + +class MP4VideoDecoder : public Dispatchable +{ +protected: + MP4VideoDecoder() {} + ~MP4VideoDecoder() {} + +public: + static FOURCC getServiceType() { return WaSvc::MP4VIDEODECODER; } + int Open(MP4FileHandle mp4_file, MP4TrackId mp4_track); + int GetOutputFormat(int *x, int *y, int *color_format, double *aspect_ratio); + int DecodeSample(const void *inputBuffer, size_t inputBufferBytes, MP4Timestamp timestamp); + void Flush(); + void Close(); + int CanHandleCodec(const char *codecName); // return 0 for no, anything else for yes + int GetPicture(void **data, void **decoder_data, MP4Timestamp *timestamp); + void FreePicture(void *data, void *decoder_data); + void HurryUp(int state); + + DISPATCH_CODES + { + MPEG4_VIDEO_OPEN = 11, + MPEG4_VIDEO_GETOUTPUTFORMAT = 21, + MPEG4_VIDEO_DECODE = 31, + MPEG4_VIDEO_FLUSH = 40, + MPEG4_VIDEO_CLOSE = 50, + MPEG4_VIDEO_HANDLES_CODEC = 60, + MPEG4_VIDEO_GET_PICTURE = 70, + MPEG4_VIDEO_FREE_PICTURE = 80, + MPEG4_VIDEO_HURRY_UP = 90, + }; + +}; + +inline int MP4VideoDecoder::Open(MP4FileHandle mp4_file, MP4TrackId mp4_track) +{ + return _call(MPEG4_VIDEO_OPEN, (int)MP4_VIDEO_FAILURE, mp4_file, mp4_track); +} + +inline int MP4VideoDecoder::GetOutputFormat(int *x, int *y, int *color_format, double *aspect_ratio) +{ + return _call(MPEG4_VIDEO_GETOUTPUTFORMAT, (int)MP4_VIDEO_FAILURE, x, y, color_format, aspect_ratio); +} + +inline int MP4VideoDecoder::DecodeSample(const void *inputBuffer, size_t inputBufferBytes, MP4Timestamp timestamp) +{ + return _call(MPEG4_VIDEO_DECODE, (int)MP4_VIDEO_FAILURE, inputBuffer, inputBufferBytes, timestamp); +} + +inline void MP4VideoDecoder::Flush() +{ + _voidcall(MPEG4_VIDEO_FLUSH); +} + +inline void MP4VideoDecoder::Close() +{ + _voidcall(MPEG4_VIDEO_CLOSE); +} + +inline int MP4VideoDecoder::CanHandleCodec(const char *codecName) +{ + return _call(MPEG4_VIDEO_HANDLES_CODEC, (int)0, codecName); +} + +inline int MP4VideoDecoder::GetPicture(void **data, void **decoder_data, MP4Timestamp *timestamp) +{ + return _call(MPEG4_VIDEO_GET_PICTURE, (int)MP4_VIDEO_FAILURE, data, decoder_data, timestamp); +} + +inline void MP4VideoDecoder::FreePicture(void *data, void *decoder_data) +{ + _voidcall(MPEG4_VIDEO_FREE_PICTURE, data, decoder_data); +} + +inline void MP4VideoDecoder::HurryUp(int state) +{ + _voidcall(MPEG4_VIDEO_HURRY_UP, state); +} +#endif
\ No newline at end of file diff --git a/Src/Plugins/Input/in_mp4/resource.h b/Src/Plugins/Input/in_mp4/resource.h new file mode 100644 index 00000000..b70efb2b --- /dev/null +++ b/Src/Plugins/Input/in_mp4/resource.h @@ -0,0 +1,61 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by in_mp4.rc +// +#define IDS_NULLSOFT_MPEG4_AUDIO_DECODER_OLD 0 +#define IDS_CANNOT_UPDATE_THE_FILE 1 +#define IDS_ERROR 2 +#define IDS_ABOUT_STRING 3 +#define IDS_ABOUT_TITLE 4 +#define IDS_STRING5 5 +#define IDS_MP4_FILE 5 +#define IDS_FAMILY_STRING_M4A 6 +#define IDS_FAMILY_STRING_MPEG4 7 +#define IDS_FAMILY_STRING_M4V 8 +#define IDS_FAMILY_STRING_QUICKTIME 9 +#define IDS_FAMILY_STRING_3GPP 10 +#define IDS_FAMILY_STRING_FLV 11 +#define IDS_ABOUT_TEXT 12 +#define IDS_AUDIO_INFO 13 +#define IDS_VIDEO_INFO 14 +#define IDD_CONFIG 102 +#define IDC_EDIT1 1000 +#define IDC_CHECK2 1002 +#define IDC_DEINT 1002 +#define IDC_SLIDER1 1003 +#define IDC_DVDCACHE 1005 +#define IDC_CACHE 1006 +#define IDD_NAME 1007 +#define IDC_NAME 1008 +#define IDC_ARTIST 1009 +#define IDC_COMPOSER 1010 +#define IDC_ALBUM 1011 +#define IDC_COMMENTS 1012 +#define IDC_GENRE 1013 +#define IDC_YEAR 1014 +#define IDC_TRACK1 1015 +#define IDC_TRACK2 1016 +#define IDC_DISC1 1017 +#define IDC_DISC2 1018 +#define IDC_ALBUM_ARTIST 1019 +#define IDC_INFOTEXT 1023 +#define IDC_COMPILATION 1024 +#define IDC_BPM 1026 +#define IDC_EDIT2 1027 +#define IDC_TRACK_GAIN 1027 +#define IDC_ALBUM_GAIN 1028 +#define IDC_EXTENSIONLIST 1028 +#define IDC_BUTTON1 1029 +#define IDC_DEFAULT 1029 +#define IDS_NULLSOFT_MPEG4_AUDIO_DECODER 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 15 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1030 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Input/in_mp4/version.rc2 b/Src/Plugins/Input/in_mp4/version.rc2 new file mode 100644 index 00000000..a60cbff2 --- /dev/null +++ b/Src/Plugins/Input/in_mp4/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 2,70,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", "2,70,0,0" + VALUE "InternalName", "Nullsoft MP4 Demuxer" + VALUE "LegalCopyright", "Copyright © 2005-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "in_mp4.dll" + VALUE "ProductName", "Winamp" + VALUE "ProductVersion", STR_WINAMP_PRODUCTVER + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END |