I'm writing a basic writing app in C# and I wanted to have the program make typewriter sounds as you typed. I've hooked the KeyPress event on my RichTextBox to a function that uses a SoundPlayer to play a short wav file every time a key is pressed, however I've noticed after a while my computer slows to a crawl and checking my processes, audiodlg.exe was using 5 GIGABYTES of RAM.
The code I'm using is as follows:
I initialise the SoundPlayer as a global variable on program start with
SoundPlayer sp = new SoundPlayer("typewriter.wav")
Then on the KeyPress event I simply call
sp.Play();
Does anybody know what's causing the heavy memory usage? The file is less than a second long, so it shouldn't be clogging the thing up too much.
Don't use SoundPlayer
- use the waveOut...
API instead:
http://www.codeproject.com/Articles/4889/A-full-duplex-audio-player-in-C-using-the-waveIn-w
SoundPlayer
is more like a toy than a production-ready component, although I'm sure the MS intern that wrote it meant well. :)
Update: if you use the linked sample and get familiar with the code, you'll see what's probably wrong with the SoundPlayer
implementation. Playing audio with the waveOut...
functions involves two in-memory buffers: one small one for the header, and one potentially large buffer than contains the actual sample data. The hotfix article you linked to mentions the leak of a few hundred bytes each time Play
is called, which means the code is probably instantiating a new header each time and then not disposing of it properly. (This is assuming SoundPlayer
wraps the waveOut...
API - I don't know whether this is the case or not)
Programmers take for granted the maxim "don't reinvent the wheel". Well, sometimes the wheel desperately needs reinventing.
It could be a bug in the SoundPlayer.
Try this article on code project, maybe it will give you some hints.
Try using the Load
method of the sound player to load the sound, and then call play. Play uses a second thread to load(if not loaded already) and play the file.
Maybe the constructor does not load the file initially (which I think is quite possible) , it mearly associates the player with the sound file name.
I've done with this sample. WWFM (aka "Worked Well For Me). Try searching errors in your code (which, i'm almost sure, is pure enough) or another sound file.
Try disposing the SoundPlayer after playing the sound. Then run the Garbage Collector. If it still consumes additional memory, something really nasty is happening and you should run the tests on another computer.
I've used the PlaySound function inside the Win32 API before to do something similar.
Although this isn't in the same language that you're using, below is an example of a program that will play 'mahnamahna.wav' on every 100th keystroke.(Yes, it was quite funny)
format PE GUI 4.0
entry start
;Mahna Mahna.
include 'win32a.inc'
include 'helper.asm'
section '.idata' import data readable writeable
library kernel32,'KERNEL32.DLL',\
user32,'USER32.DLL',\
hook,'HOOK.DLL',\
winmm,'WINMM.DLL'
import hook,\
SetKeyPressedHandler,'SetKeyPressedHandler'
import winmm,\
PlaySound,'PlaySound'
include 'api\kernel32.inc'
include 'api\user32.inc'
section '.data' data readable writeable
szWavFile db "mahnamahna.wav",0
;String saying what the dll is called.
szDllName db "HOOK.DLL",0
;Name of the function in the dll for the keyboard procedure
szf_KeyboardProc db "KeyboardProc",0
;handle to the dll
hDll dd ?
;handle to the keyboard procedure
hKeyboardProc dd ?
;handle to the hook
hHook dd ?
kInput KBINPUT
keyCount dd 0x0 ;
;msg for the message pump
msg MSG
section '.text' code readable executable
start:
;Load the DLL into memory.
invoke LoadLibraryA,szDllName
cmp eax,0x0
je exit
mov [hDll],eax
invoke GetProcAddress,[hDll],szf_KeyboardProc
cmp eax,0x0
je freeLibrary
mov [hKeyboardProc],eax
invoke SetKeyPressedHandler,KeyPressedHandler
hook:
invoke SetWindowsHookEx,WH_KEYBOARD_LL,[hKeyboardProc],[hDll],0x0
cmp eax,0x0
je freeLibrary
mov [hHook],eax
msg_loop:
invoke GetMessage,msg,NULL,0,0
cmp eax,1
jb unhook
jne msg_loop
invoke TranslateMessage,msg
invoke DispatchMessage,msg
jmp msg_loop
proc KeyPressedHandler code,wparam,lparam
;Move the VK Code of the key they pressed into al.
xor eax,eax
mov eax,[lparam]
mov cx,word [eax]
cmp [wparam],WM_KEYDOWN
je .ProcessKeyDown
cmp [wparam],WM_KEYUP
je .ProcessKeyUp
.ProcessKeyDown:
ret ;No need to go any further - we only process characters on key up
.ProcessKeyUp:
mov edx,[keyCount]
inc edx
cmp cx,VK_F12
je unhook
;Hotkeys.
;F12 - Quit.
cmp edx,0x64
jne .done
call MahnaMahna
xor edx,edx
.done:
mov [keyCount],edx
ret
endp
proc MahnaMahna
invoke PlaySound,szWavFile,0x0,0x20000
ret
endp
unhook:
invoke UnhookWindowsHookEx,[hHook]
freeLibrary:
invoke FreeLibrary,[hDll]
exit:
invoke ExitProcess,0
The above will not work without the following dll(hook.dll)
format PE GUI 4.0 DLL
entry _DllMain
include 'win32a.inc'
section '.data' data readable writeable
hKeyPressedHandler dd 0x0
section '.text' code readable executable
proc _DllMain hinstDLL,fdwReason,lpvReserved
mov eax,TRUE
ret
endp
proc SetKeyPressedHandler hProc
mov eax,[hProc]
mov [hKeyPressedHandler],eax
ret
endp
proc KeyboardProc code,wparam,lparam
cmp [code],0x0
jl CallNextHook
cmp [hKeyPressedHandler],0x0;Make sure our event handler is set.
je CallNextHook
;Call our handler.
invoke hKeyPressedHandler,[code],[wparam],[lparam]
CallNextHook:
invoke CallNextHookEx,0x0,[code],[wparam],[lparam]
ret
endp
section '.idata' import data readable writeable
library kernel32,'KERNEL32.DLL',\
user32,'USER32.DLL'
include 'api\kernel32.inc'
include 'api\user32.inc'
section '.edata' export data readable
export 'hook.DLL',\
KeyboardProc,'KeyboardProc',\
SetKeyPressedHandler,'SetKeyPressedHandler'
section '.reloc' fixups data discardable
This isn't strictly speaking an answer, so I won't confirm this as the accepted answer to my question, but it is a solution for those who have had the same problems (and also confirms it's not my system at fault)
I decided to implement the sound using ManagedDirectX's AudioPlayback library, which is about as easy to use as SoundPlayer, but has successfully solved my problem.
For those who want to know, the code is simple:
1) Add a reference to the audioplayback dll.
2) Create an Audio object (I named mine sound), make it a variable on your form so you can refer to it again, use the constructor to set the filename it should play
3) Play the file with sound.Play();
4) If you need to play the file again, use the following line:
sound.SeekCurrentPosition(0, SeekPositionFlags.AbsolutePositioning);
It's fairly fast, and fairly good. There'll be memory issues if you need a lot of different sound effects, because they'll all constantly be in memory, but if you need one sound to play a lot, this one will do it without ballooning your Audiodlg.exe
You should try using()
using(SoundPlayer sp = new SoundPlayer("typewriter.wav")) {
sp.Play();
}
when process finish sp.Play() memory return to your system automatics.