diff options
Diffstat (limited to 'Src/Plugins/Input/in_mp4/Main.cpp')
-rw-r--r-- | Src/Plugins/Input/in_mp4/Main.cpp | 723 |
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 |