aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Input/in_mp4
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Plugins/Input/in_mp4')
-rw-r--r--Src/Plugins/Input/in_mp4/AlbumArt.cpp232
-rw-r--r--Src/Plugins/Input/in_mp4/AlbumArt.h39
-rw-r--r--Src/Plugins/Input/in_mp4/AudioSample.h80
-rw-r--r--Src/Plugins/Input/in_mp4/ExtendedInfo.cpp1022
-rw-r--r--Src/Plugins/Input/in_mp4/ExtendedRead.cpp239
-rw-r--r--Src/Plugins/Input/in_mp4/Main.cpp723
-rw-r--r--Src/Plugins/Input/in_mp4/Main.h76
-rw-r--r--Src/Plugins/Input/in_mp4/PlayThread.cpp417
-rw-r--r--Src/Plugins/Input/in_mp4/RawMediaReader.cpp145
-rw-r--r--Src/Plugins/Input/in_mp4/RawMediaReader.h40
-rw-r--r--Src/Plugins/Input/in_mp4/Stopper.cpp47
-rw-r--r--Src/Plugins/Input/in_mp4/Stopper.h11
-rw-r--r--Src/Plugins/Input/in_mp4/VideoThread.cpp215
-rw-r--r--Src/Plugins/Input/in_mp4/VideoThread.h10
-rw-r--r--Src/Plugins/Input/in_mp4/VirtualIO.cpp716
-rw-r--r--Src/Plugins/Input/in_mp4/VirtualIO.h17
-rw-r--r--Src/Plugins/Input/in_mp4/api.cpp4
-rw-r--r--Src/Plugins/Input/in_mp4/api__in_mp4.h22
-rw-r--r--Src/Plugins/Input/in_mp4/config.cpp49
-rw-r--r--Src/Plugins/Input/in_mp4/in_mp4.rc129
-rw-r--r--Src/Plugins/Input/in_mp4/in_mp4.sln67
-rw-r--r--Src/Plugins/Input/in_mp4/in_mp4.vcxproj301
-rw-r--r--Src/Plugins/Input/in_mp4/in_mp4.vcxproj.filters122
-rw-r--r--Src/Plugins/Input/in_mp4/infobox.cpp20
-rw-r--r--Src/Plugins/Input/in_mp4/mp4.cpp98
-rw-r--r--Src/Plugins/Input/in_mp4/mpeg4audio.cpp1
-rw-r--r--Src/Plugins/Input/in_mp4/mpeg4audio.h208
-rw-r--r--Src/Plugins/Input/in_mp4/mpeg4video.cpp1
-rw-r--r--Src/Plugins/Input/in_mp4/mpeg4video.h92
-rw-r--r--Src/Plugins/Input/in_mp4/resource.h61
-rw-r--r--Src/Plugins/Input/in_mp4/version.rc239
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, &timestamp))
+ {
+ 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