/** * @file winmmshim.cpp * @brief controls volume level of process by intercepting calls to winmm.dll * * $LicenseInfo:firstyear=2010&license=viewergpl$ * * Copyright (c) 2010, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "forwarding_api.h" #include #include #include using std::wstring; static float sVolumeLevel = 1.f; static bool sMute = false; BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { static bool initialized = false; // do this only once if (!initialized) { // bind to original winmm.dll TCHAR system_path[MAX_PATH]; TCHAR dll_path[MAX_PATH]; ::GetSystemDirectory(system_path, MAX_PATH); // grab winmm.dll from system path, where it should live wsprintf(dll_path, "%s\\winmm.dll", system_path); HMODULE winmm_handle = ::LoadLibrary(dll_path); if (winmm_handle != NULL) { // we have a dll, let's get out pointers! initialized = true; init_function_pointers(winmm_handle); return true; } // failed to initialize real winmm.dll return false; } return true; } extern "C" { // tracks the requested format for a given waveout buffer struct WaveOutFormat { WaveOutFormat(int bits_per_sample) : mBitsPerSample(bits_per_sample) {} int mBitsPerSample; }; typedef std::map wave_out_map_t; static wave_out_map_t sWaveOuts; MMRESULT WINAPI waveOutOpen( LPHWAVEOUT phwo, UINT uDeviceID, LPCWAVEFORMATEX pwfx, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen) { if (pwfx->wFormatTag != WAVE_FORMAT_PCM || (pwfx->wBitsPerSample != 8 && pwfx->wBitsPerSample != 16)) { // uncompressed 8 and 16 bit sound are the only types we support return WAVERR_BADFORMAT; } MMRESULT result = waveOutOpen_orig(phwo, uDeviceID, pwfx, dwCallback, dwInstance, fdwOpen); if (result == MMSYSERR_NOERROR && ((fdwOpen & WAVE_FORMAT_QUERY) == 0)) // not just querying for format support { // remember the requested bits per sample, and associate with the given handle WaveOutFormat* wave_outp = new WaveOutFormat(pwfx->wBitsPerSample); sWaveOuts.insert(std::make_pair(*phwo, wave_outp)); } return result; } MMRESULT WINAPI waveOutClose( HWAVEOUT hwo) { wave_out_map_t::iterator found_it = sWaveOuts.find(hwo); if (found_it != sWaveOuts.end()) { // forget what we know about this handle delete found_it->second; sWaveOuts.erase(found_it); } return waveOutClose_orig( hwo); } MMRESULT WINAPI waveOutWrite( HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh) { MMRESULT result = MMSYSERR_NOERROR; if (sMute) { // zero out the audio buffer when muted memset(pwh->lpData, 0, pwh->dwBufferLength); } else if (sVolumeLevel != 1.f) { // need to apply volume level wave_out_map_t::iterator found_it = sWaveOuts.find(hwo); if (found_it != sWaveOuts.end()) { WaveOutFormat* formatp = found_it->second; switch (formatp->mBitsPerSample){ case 8: { char volume = (char)(sVolumeLevel * 127.f); for (unsigned int i = 0; i < pwh->dwBufferLength; i++) { // unsigned multiply doesn't use most significant bit, so shift by 7 bits // to get resulting value back into 8 bits pwh->lpData[i] = (pwh->lpData[i] * volume) >> 7; } break; } case 16: { short volume_16 = (short)(sVolumeLevel * 32767.f); // copy volume level 4 times into 64 bit MMX register __m64 volume_64 = _mm_set_pi16(volume_16, volume_16, volume_16, volume_16); __m64* sample_64; __m64* last_sample_64 = (__m64*)(pwh->lpData + pwh->dwBufferLength - sizeof(__m64)); // for everything that can be addressed in 64 bit multiples... for (sample_64 = (__m64*)pwh->lpData; sample_64 <= last_sample_64; ++sample_64) { //...multiply the samples by the volume... __m64 scaled_sample = _mm_mulhi_pi16(*sample_64, volume_64); // ...and shift left 1 bit since an unsigned multiple loses the most significant bit // 0x7FFF * 0x7FFF = 0x3fff0001 // 0x3fff0001 << 1 = 0x7ffe0002 // notice that the LSB is always 0...should consider dithering *sample_64 = _mm_slli_pi16(scaled_sample, 1); } // the captain has turned off the MMX sign, you are now free to use floating point registers _mm_empty(); // finish remaining samples that didn't fit into 64 bit register for (short* sample_16 = (short*)sample_64; sample_16 < (short*)(pwh->lpData + pwh->dwBufferLength); ++sample_16) { *sample_16 = (*sample_16 * volume_16) >> 15; } break; } default: // don't do anything break; } } } return waveOutWrite_orig( hwo, pwh, cbwh); } void WINAPI setPluginVolume(float volume) { sVolumeLevel = volume; } void WINAPI setPluginMute(bool mute) { sMute = mute; } }