winamp/Src/Plugins/Input/in_mp4/Main.cpp
2024-09-24 14:54:57 +02:00

723 lines
18 KiB
C++

#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;
}
}