Thread: Circular Buffer using Virtual Memory

  1. #1
    Registered /usr
    Join Date
    Aug 2001
    Location
    Newport, South Wales, UK
    Posts
    1,273

    Lightbulb Circular Buffer using Virtual Memory

    Hello,

    I'm experimenting with audio decoding and playback using PortAudio at the mo. Initially I found that it isn't a good idea to decode the audio in the same thread as playback (duh ), so I have split the application into two threads: one decodes the audio and the other copies blocks to PortAudio's buffer on callback.

    Each time the playback thread copies decoded audio it signals the decoding thread via an Event. The decoding thread wakes up, decodes another block of audio and goes back to sleep.

    Here's where it gets tasty: I don't intend to have a 50+ MB buffer holding the decoded audio, instead my ideal size is a one second buffer. Newly-decoded blocks overwrite blocks that have been sent for playback, thus creating a circular buffer.

    I have tried a traditional implementation of a circular buffer, that typically involves one to two memcpy()s, depending on how close it is to the end of the buffer. This works well, but for one reason or another I keep hearing pops and clicks as both threads wrap around. It seems as if playback catches up with decoding too quickly. This would make sense, except that I've temporarily disabled the "decoding" element and am just reading raw samples from file into the buffer.

    Reading around, I found out that it is possible to use Windows' virtual memory management to your advantage, effectively mapping the same block of physical memory over and over into consecutive regions of virtual memory. I could have a virtual region the same length as the file, but still only using one second's worth of physical memory.

    This is the code that I devised to do this:-
    Code:
    // nSeconds is the length in seconds of the file, rounded up
    hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_RESERVE, 0, nSeconds * 327680, NULL); // create a map for the whole range
    pSound->pSound = MapViewOfFileEx(hMap, FILE_MAP_WRITE, 0, 0, 0, NULL); // find a virtual address that would accommodate it
    UnmapViewOfFile(pSound->pSound); // clear down
    CloseHandle(hMap);
    hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_RESERVE, 0, 327680, NULL); // create a map for just the second-long buffer
    for (pSound->pEnd=pSound->pSound;nSeconds>0;nSeconds--)
    {
      MapViewOfFileEx(hMap, FILE_MAP_WRITE, 0, 0, 327680, pSound->pEnd); // map the address to the second-long buffer
      pSound->pEnd += 81920; // increment float pointer (327680 bytes)
    }
    
    VirtualAlloc(pSound->pSound, 327680, MEM_COMMIT, PAGE_READWRITE); // "bind" the second-long buffer to memory
    This works brilliantly in the sense that I could dismantle all the pointer arithmetic, etc. and the clicks/pops ceased, but looking at the Task Manager, the memory usage of the process keeps increasing as time goes on. Is this just a count of virtual memory, or have I not done this properly?

  2. #2
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    This works well, but for one reason or another I keep hearing pops and clicks as both threads wrap around.
    O_o

    If this is your problem...

    Reading around, I found out that it is possible to use Windows' virtual memory management to your advantage, effectively mapping the same block of physical memory over and over into consecutive regions of virtual memory.
    ... then that isn't a solution.

    I'm cool with you learning the "Windows" "WinAPI", but if your cache, your circular buffer, is already implemented properly changing how the memory allocation mechanism isn't going to help.

    Do yourself a favor, create a single, large buffer and attempt to use a cache of a single item, a circular buffer of size one, and see if you still get "pops and clicks".

    By the by, I don't use this library, does your decode API automatically create/fix packets or write null samples to an overlarge buffer?

    As for the "WinAPI" stuff, I can't help you really as I try to avoid it.

    Soma

  3. #3
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by SMurf View Post
    Reading around, I found out that it is possible to use Windows' virtual memory management to your advantage, effectively mapping the same block of physical memory over and over into consecutive regions of virtual memory. I could have a virtual region the same length as the file, but still only using one second's worth of physical memory.

    This works brilliantly in the sense that I could dismantle all the pointer arithmetic, etc. and the clicks/pops ceased, but looking at the Task Manager, the memory usage of the process keeps increasing as time goes on. Is this just a count of virtual memory, or have I not done this properly?
    You don't have control over what virtual memory is mapped to what physical memory (that's the kernel's job)!
    VirtualAlloc is not going to help you in the slightest in this regard.
    If you need a circular buffer, then there are plenty of already working solutions which might be a good idea to test with, I think.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  4. #4
    Registered /usr
    Join Date
    Aug 2001
    Location
    Newport, South Wales, UK
    Posts
    1,273
    Aiiieee! I think I misunderstood what was meant by the example given on Wikipedia for this. In the above I thought that I could just map the same region n times to loop as long as required (not possible).
    In fact, the key is thus:-
    Quote Originally Posted by SMurf View Post
    I have tried a traditional implementation of a circular buffer, that typically involves one to two memcpy()s, depending on how close it is to the end of the buffer.
    Using this technique correctly is supposed to make it possible to read/write using exactly one memcpy() call. You map the same region twice and perform the same pointer arithmetic as if it were a single buffer, but don't need to worry about reading/writing past the end any more.

    This kind of stuff never works when I'm sober.

  5. #5
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    You can "walk" the memory with VirtualAlloc, but honestly, it's probably slower than just doing two memcpys.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  6. #6
    Registered /usr
    Join Date
    Aug 2001
    Location
    Newport, South Wales, UK
    Posts
    1,273
    Well after that brain fart and shaking my fist in the air at whoever wrote the Ogg Vorbis library API, I finally have clean, fast and low memory-using streaming audio playback.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 3
    Last Post: 06-27-2011, 11:25 AM
  2. Replies: 3
    Last Post: 11-11-2010, 12:05 PM
  3. Circular buffer - logic difficulty
    By erasm in forum C Programming
    Replies: 2
    Last Post: 10-05-2009, 01:08 PM
  4. Circular Buffer
    By parisframe in forum C Programming
    Replies: 11
    Last Post: 09-16-2009, 11:05 PM
  5. Replies: 2
    Last Post: 09-28-2006, 01:06 PM