diff options
Diffstat (limited to 'Src/Plugins/Output/out_wasapi/main.cpp')
-rw-r--r-- | Src/Plugins/Output/out_wasapi/main.cpp | 393 |
1 files changed, 393 insertions, 0 deletions
diff --git a/Src/Plugins/Output/out_wasapi/main.cpp b/Src/Plugins/Output/out_wasapi/main.cpp new file mode 100644 index 00000000..8878cedb --- /dev/null +++ b/Src/Plugins/Output/out_wasapi/main.cpp @@ -0,0 +1,393 @@ +#include "../Winamp/OUT.H" +#include "api.h" +#include "resource.h" +#include <Mmdeviceapi.h> +#include <Audioclient.h> +#include <Audiosessiontypes.h> +#include "../winamp/wa_ipc.h" +#include <api/service/waServiceFactory.h> +#include <strsafe.h> + +#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<api_language*>(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<void **>( & 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) + { + } + +} |