aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Input/in_mp4/Main.cpp
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Input/in_mp4/Main.cpp
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/Plugins/Input/in_mp4/Main.cpp')
-rw-r--r--Src/Plugins/Input/in_mp4/Main.cpp723
1 files changed, 723 insertions, 0 deletions
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