Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top