#include "../Winamp/OUT.H" #include "api.h" #include "resource.h" #include #include #include #include "../winamp/wa_ipc.h" #include #include #define WASAPI_PLUGIN_VERSION L"0.3" constexpr auto VolumeLevelMultiplier = 255; static wchar_t plugin_name[256]; // wasabi based services for localisation support api_service *WASABI_API_SVC = 0; api_language *WASABI_API_LNG = 0; HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; static const UINT32 REFTIMES_PER_SEC = 10000000; static const UINT32 REFTIMES_PER_MILLISEC = 10000; // TODO(benski) is there some library that has this static const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); static const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); static const IID IID_IAudioClient = __uuidof(IAudioClient); static const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient); static const IID IID_IAudioClock = __uuidof(IAudioClock); static bool InitializedCOM; extern Out_Module plugin; static IAudioClient *client=0; static IAudioRenderClient *render_client=0; static IAudioClock *clock=0; static ISimpleAudioVolume *audio_volume = 0; static IChannelAudioVolume *channel_volume = 0; static UINT32 bufferFrameCount; static WORD bytes_per_frame; static UINT64 frequency=0; static UINT32 sample_rate; static double start_time_ms = 0; static bool paused=false; static float start_volume = 1.0; static float start_pan = 0; WAVEFORMATEXTENSIBLE WaveFormatForParameters(int samplerate, int numchannels, int bitspersamp); static void SetVolume(int volume); static void SetPan(int pan); static CRITICAL_SECTION ThreadSync; static void Config(HWND hwndParent) { } static int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message) { MSGBOXPARAMSW msgbx = {sizeof(MSGBOXPARAMSW),0}; msgbx.lpszText = message; msgbx.lpszCaption = title; msgbx.lpszIcon = MAKEINTRESOURCEW(102); msgbx.hInstance = GetModuleHandle(0); msgbx.dwStyle = MB_USERICON; msgbx.hwndOwner = parent; return MessageBoxIndirectW(&msgbx); } static void About(HWND hwndParent) { wchar_t message[1024], text[1024] =L""; WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_WASAPI_OLD,text,1024); StringCchPrintfW(message, 1024, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), plugin.description, TEXT(__DATE__)); DoAboutMessageBox(hwndParent,text,message); } static void Init() { /* HRESULT hr; hr=CoInitializeEx(0, COINIT_MULTITHREADED); if (SUCCEEDED(hr)) { InitializedCOM = true; } else { InitializedCOM = false; } */ // loader so that we can get the localisation service api for use WASABI_API_SVC = (api_service*)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GET_API_SERVICE); if (WASABI_API_SVC == (api_service*)1) WASABI_API_SVC = NULL; waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid(languageApiGUID); if (sf) WASABI_API_LNG = reinterpret_cast(sf->getInterface()); // need to have this initialised before we try to do anything with localisation features WASABI_API_START_LANG(plugin.hDllInstance,OutWasapiLangGUID); StringCbPrintfW(plugin_name,sizeof(plugin_name),WASABI_API_LNGSTRINGW(IDS_NULLSOFT_WASAPI), WASAPI_PLUGIN_VERSION); plugin.description = (char *)plugin_name; } static void Quit() { /* if (InitializedCOM) { CoUninitialize(); }*/ } static int Open(int samplerate, int numchannels, int bitspersamp, int bufferlenms, int prebufferms) { CoInitialize(0); IMMDeviceEnumerator *enumerator=0; IMMDevice *device=0; sample_rate = samplerate; HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&enumerator); hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &device); hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&client); if (FAILED(hr)) { wchar_t temp[1234]; wsprintf(temp, L"device->Activate: %x", hr); ::MessageBox(NULL, temp, L"error", MB_OK); return -1; } WAVEFORMATEXTENSIBLE wave_format = WaveFormatForParameters(samplerate, numchannels, bitspersamp); bytes_per_frame = wave_format.Format.nBlockAlign; hr = client->Initialize( AUDCLNT_SHAREMODE_SHARED, 0x80000000, 1 * REFTIMES_PER_SEC, 0, (WAVEFORMATEX *)&wave_format, NULL); if (FAILED(hr)) { wchar_t temp[1234]; wsprintf(temp, L"client->Initialize: %x", hr); ::MessageBox(NULL, temp, L"error", MB_OK); return -1; } // Get the actual size of the allocated buffer. hr = client->GetBufferSize(&bufferFrameCount); if (FAILED(hr)) { wchar_t temp[1234]; wsprintf(temp, L"client->GetBufferSize: %x", hr); ::MessageBox(NULL, temp, L"error", MB_OK); return -1; } hr = client->GetService( IID_IAudioRenderClient, (void**)&render_client); if (FAILED(hr)) { wchar_t temp[1234]; wsprintf(temp, L"client->GetService(IID_IAudioRenderClient): %x", hr); ::MessageBox(NULL, temp, L"error", MB_OK); return -1; } hr = client->GetService( IID_IAudioClock, (void**)&clock); if (FAILED(hr)) { wchar_t temp[1234]; wsprintf(temp, L"client->GetService(IID_IAudioClock): %x", hr); ::MessageBox(NULL, temp, L"error", MB_OK); return -1; } hr = clock->GetFrequency(&frequency); hr = client->GetService(__uuidof(ISimpleAudioVolume), reinterpret_cast( & audio_volume)); hr = client->GetService(__uuidof(IChannelAudioVolume), (void **)&channel_volume); start_time_ms = 0; paused=false; client->Start(); // Start volume is in range 0.0 to 1.0, should be converted SetVolume((int)(start_volume * VolumeLevelMultiplier)); SetPan((int)start_pan); return 1000; } static void Close() { if (client) { client->Stop(); client->Release(); client=0; } if (render_client) { render_client->Release(); render_client=0; } if (clock) { clock->Release(); clock=0; } if (audio_volume) { audio_volume->Release(); audio_volume=0; } if (channel_volume) { channel_volume->Release(); channel_volume=0; } } static int CanWrite() { if (client) { UINT32 numFramesPadding; HRESULT hr = client->GetCurrentPadding(&numFramesPadding); return (bufferFrameCount - numFramesPadding) * bytes_per_frame; } else { return 0; } } static int Write(char* buf, int len) { if (!render_client) { return -1; } else { int LenghtToWrite = CanWrite(); if (LenghtToWrite > 0 && LenghtToWrite >= len) { BYTE* data; render_client->GetBuffer(len / bytes_per_frame, &data); memcpy(data, buf, len); render_client->ReleaseBuffer(len / bytes_per_frame, 0); } } return 0; } static int IsPlaying() { return CanWrite() == 0; } static int Pause(int pause) { int old_paused = paused?1:0; if (client) { if (pause) { client->Stop(); paused=true; } else { client->Start(); paused=false; } } return old_paused; } static void SetVolume(int volume) { float fVolume = (float)volume / (float)VolumeLevelMultiplier; if (volume >= 0) { start_volume = fVolume; if (audio_volume) { audio_volume->SetMasterVolume(fVolume, 0); } } } static void SetPan(int pan) { float fPan = (float)pan/128.0f; if (channel_volume) { start_pan = fPan; if (fPan < 0) { channel_volume->SetChannelVolume(0, 1.0f, NULL); channel_volume->SetChannelVolume(1, 1.0f-fPan, NULL); } else if (fPan > 0) { channel_volume->SetChannelVolume(1, 1.0f, NULL); channel_volume->SetChannelVolume(0, 1.0f-fPan, NULL); } } } static void Flush(int t) { if (client) { client->Stop(); client->Reset(); start_time_ms = t; client->Start(); } } static double GetOutputTimeAsDouble() { if (clock) { UINT64 position; HRESULT hr = clock->GetPosition(&position, NULL); double output_time = (double)position * 1000.0 / (double)frequency; return output_time + start_time_ms; } else { return 0; } } static int GetOutputTime() { return (int)GetOutputTimeAsDouble(); } static int GetWrittenTime() { double time_in_buffer = (1000.0 * (double)CanWrite()) / ((double)bytes_per_frame * (double)sample_rate); return (int)(GetOutputTimeAsDouble() + time_in_buffer); } Out_Module plugin = { OUT_VER_U, 0, 70, NULL, NULL, Config, About, Init, Quit, Open, Close, Write, CanWrite, IsPlaying, Pause, SetVolume, SetPan, Flush, GetOutputTime, GetWrittenTime, }; extern "C" { __declspec(dllexport) int __cdecl winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) { return OUT_PLUGIN_UNINSTALL_REBOOT; } __declspec(dllexport) Out_Module * __cdecl winampGetOutModule(){ return &plugin; } __declspec(dllexport) void __cdecl winampGetOutModeChange(int mode) { } }