none
Can Wave{In|Out} be run in exclusive mode? & is it safe to play with WASAPI buffers without eventcallback

    Question

  • Hi, i'm new to the core audio (SPECS: 64b intel, 32b vista OS with integrated realtek, 2Gram; Via IsApiSupported, realtek seems to run only 16bit-depth, though the Hz can go up to 192kHz and also in my control panel settings it goes up to 24bit).

    I have 4 questions , 2  pertaining to exclusive mode (as i require lowlatency with audio hardware to feed back and forth with a neuralnet)

    [1] Rather than using WASAPI, can the waveIn/Out functions be set in exclusive mode, or are they already? It was a hassle to go through the WASAPI to get things setup, though i haven't actually tried getting the buffer itself.

    [2] Using WASAPI with everything setup properly, is it safe to just memcpy (to/from) the buffers grabbed from GetBuffer for both IAudioCaptureClient &IAudioRenderClient without using one of the flags for "Initialize"? The exclusive mode example uses EVENTCALLBACK and i'm not sure whether my application requires it?

    My application is devised for this post into 3 threads: (a) AudioIn, (b) audioOut, and (c) the main thread for passing a buffer between the two. 

    AudioIn captures to buffer, that is manipulated by the main thread. And AudioOut renders from buffer used in main thread. 

    [3] Is the data from GetBuffer in PCM format just amplitude values of the waveform that i can feed into a FFT/iFFT if needed before feeding into NNET.

    [4] I tried getting the wavformat from the properties methods in the examples (i setup the wavfmt manually as per someone previous post) using PKEY_AudioEngine_DeviceFormat but i get a duplicate _PKEY_ definitions error and am not sure what #define/undef I need to set so that initguid.h gets defined only once. I have the std WASAPI headers included in an .h file, and from someone's previous post it says u have to ensure that linking happens in only one .cpp. 

     

    Thanks in advanced.

    Jack 



    Friday, April 01, 2011 5:01 PM

All replies

  • waveIn/waveOut are shared-mode for PCM and floating-point formats, and exclusive-mode for other formats.
    Matthew van Eerde
    Friday, April 01, 2011 8:34 PM
  • (2) You can access the capture buffer only between calls to IAudioCaptureClient::GetBuffer and IAudioCaptureClient::ReleaseBuffer, and the render buffer only between calls to IAudioRenderClient::GetBuffer and IAudioRenderClient::ReleaseBuffer.  You should not access either buffer outside of these calls.

    What are you using to control your loop? You can wait on an event that you passed to IAudioClient::SetEventHandle (event callback mode) or you can create your own waitable timer (or even use Sleep().)


    Matthew van Eerde
    Friday, April 01, 2011 8:37 PM
  • (3) the audio samples are in whatever format you passed to IAudioClient::Initialize.  Usually this is the same format you got from IAudioClient::GetMixFormat.

    Matthew van Eerde
    Friday, April 01, 2011 8:38 PM
  • (4) I usually create an initguid.cpp file which looks like this:

    #include <initguid.h>
    #include <windows.h>
    #include <header-that-includes-guids-1.h>
    #include <header-that-includes-guids-2.h>
    ...
    #include <header-that-includes-guids-n.h>

    I then make sure NOT to include initguid.h in any other .cpp file in the project.


    Matthew van Eerde
    Friday, April 01, 2011 8:41 PM
  • thank you for the replies

    for (1) is there away to get PCM format in exclusive mode for the waveXXX functions? I just need basic mic capture/speaker render functionality.

    for (2) both capture/render threads will be in a "while(fOn) { }" loop till some error, device disconnection or the main thread sets the boolean fOn=false. The mic thread should be capturing CONSTANTLY while the speaker thread depends on when the neuralnet wants to make a sound.  

    for (4) I think I read in the docs that "initguid.h" must precede "mmdeviceapi.h", is this correct? I use a class defn with the following (that requires the latter header)

     

    #include "mmdeviceapi.h"

     

    #include <audioclient.h>

    #include <audiopolicy.h>

     

    class CAudioHdwrDev {

      ...

      IMMDeviceCollection  *devSet;  

      IMMDevice            *dev;

      IAudioClient         *client;   //adaptor?

      WAVEFORMATEXTENSIBLE wavFmt;

      EDataFlow type;

      ... //utilized ERole some where.

    }

    Does that mean that "mmdeviceapi.h" cannot be included in a header if i require "initguid.h"?

     







    Saturday, April 02, 2011 5:48 AM
  • (1) is there away to get PCM format in exclusive mode for the waveXXX functions

    No, but shared mode should work.  Why do you need exclusive mode?

    (2) the mic thread should be capturing CONSTANTLY

    Sure, but once you're done processing a packet, how do you wait for the next one?  There are a couple of WASAPI samples in the Windows SDK:

    http://msdn.microsoft.com/en-us/windows/bb980924

    There are, in general, two answers: (a) pass an event to the IAudioClient and have it wake me up when it's time to process the next packet, or (b) use Sleep() or my own waitable timer

    (4) DEFINE_GUID(X) either translates into "guid X exists and is defined in some other .obj file" or "guid X is {...}", based on whether INITGUID is defined.  If all DEFINE_GUID(X)s are of the first form for a particular X, you'll get a compile error to the effect of "I can't find a definition for GUID X.".  If you have two DEFINE_GUID(X)s of the second form for a particular X, you'll get a different compile error to the effect of "foo.obj defines X but it's already defined in bar.obj."

    There are various ways to make sure that every X is defined in exactly one place.  One way is to put all your code in one .cpp file (I can't recommend this.)  Another way is the way I suggested - to make an initguid.cpp file which exists for the sole purpose of getting all the GUID definitions out of the way.


    Matthew van Eerde
    Monday, April 04, 2011 3:21 PM
  • thx again

    for (1): i wasn't sure whether there were latency issues with using waveXXX/PCM format/shared mode ... sorry i may have been miscomprehending the msdn docs

    for (4): ah i see it now...u ensure initguid.cpp gets built before other mmdevice-dependent files . That was the part i couldn't wrap my brain around within the MSVC IDE. Thx for this suggestion...it was really bugging me.

    for (2): with my OpenAL  code(i'm working on openal/winaudio concurrently), i have the following

          if (nDataCap>nGrabSamp) 

          { alcCaptureSamples(devIn,buf,nGrabSamp); ...//other stuff

          }

    so i thought i would do the same with winaudio, but i'm running to issues in openal with race conditions...

    so i'll take a look at the event method. Is there an msdn doc comparing the advantages of (a) event-driven vs (b) timer-driven.  Is there a preference for low latency with the mic? or is that app-dependent?

     

    regards, Jack

    Monday, April 04, 2011 6:32 PM
  • It is possible to get lower latency with exclusive mode than with shared mode.  But you can't get exclusive-mode PCM through the winMM APIs (waveIn, waveOut), only exclusive-mode compressed audio.

    Going through exclusive mode means no other apps will be able to play sound via that device.  It also means your app will be limited to a single stream, so you'll need to do your own mixing if you want to play multiple sounds (and this mixing will add latency.)  This is less of an issue for capture than for render.

    If you still want to use exclusive mode you can use WASAPI directly.

    Event-driven mode is perhaps slighly better than timer-driven mode from a latency perspective because you get notified at just the right time.

    In addition to the SDK samples, there's an exclusive-mode playback app on my blog:

    http://blogs.msdn.com/b/matthew_van_eerde/archive/2009/04/03/sample-wasapi-exclusive-mode-event-driven-playback-app-including-the-hd-audio-alignment-dance.aspx

    Capture would look similar.


    Matthew van Ee
    Monday, April 04, 2011 6:54 PM
  • that was very informative, thank you.

    I should have mentioned it in the first post, that the application will be the only active app (a robotic app):  In{Mic,Cam}/Out{Spkr} <--> Neuralnet  , each with its own thread

     

    ... a side question: when an app is using exclusive mode, should I be closing other audio apps first (Winamp just crashed for me.)? or should (a) Window be reviving the other apps automatically after the exclusive app has terminated (b) is it up to the other software to handle another app using exclusive mode; or (c) my app should be signalling to other audio apps that it is using exclusive mode? 

    Monday, April 04, 2011 9:46 PM
  • It's fine for your app to use exclusive mode.

    If WinAmp is streaming, and then your app takes over the device, then WinAmp's calls to the audio APIs will start failing with "device in use" errors.  WinAmp is expected to handle this gracefully and not crash; it sounds like they have a bug.

    Generally well-behaved apps will respond to something like this by popping up a "there was an error" dialog; they will usually not attempt to resume playback "later".


    Matthew van Eerde
    Monday, April 04, 2011 10:36 PM
  • This might be a very basic question.

     

    What is the difference between #frames retrieved from 

    (a)IAudioClient::GetBufferSize(&nFrame) and

    (b)IAudioCaptureClient::GetBuffer(&buf,&nFrame,...)

     

    The docs say (a) is the maximum #frames the device can capture. 

    But i'm getting a value from (b) [#=576] double the size of (a) [#=288] , 

    is this because its double buffering? Shouldn't (b) still only return max value?

    Thursday, April 07, 2011 6:34 PM
  • That's very odd.  What are the values you're passing to Initialize() ?


    Matthew van Eerde
    Thursday, April 07, 2011 7:15 PM
  • wavFmt is default PCM for my realtek onboard (24/32, 96000 , 2ch
      hres=client->GetDevicePeriod (NULL,& dura);
      
      hres = client->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE
            ,0 ,dura ,dura ,(WAVEFORMATEX*)wavFmt ,NULL);   
      
      hres = client->GetService(__uuidof(IAudioCaptureClient), (void**)&clientCapture);
      
      UINT32 bufLn=0; hres=client->GetBufferSize(&bufLn); WIN_EXITERR(hres ,"Thread:Capture - bufln")
      hres = client->Start();   WIN_EXITERR(hres ,"Thread:Capture -client start") 
     
    i fixed it though, imoved the buffersize call too after the start.
    Its weird though that before start its set to a different value
    Thursday, April 07, 2011 9:46 PM
  • It is OK to call GetBufferSize() before Start().  What disturbs me is that the buffer size is 288 frames but you're getting 576-frame buffers.
    Matthew van Eerde
    Thursday, April 07, 2011 10:29 PM
  • I ran some tests and I'm seeing it the other way around: GetBufferSize() is 896 frames but the most frames I ever actually get handed in a GetBuffer() call is 448.

    You're doing timer-driven capture; the control flow for that should look something like:

    for (...) {
        while (pAudioCaptureClient->GetNextPacketSize() > 0) {
            pAudioCaptureClient->GetBuffer();
            // process the buffer
            pAudioCaptureClient->ReleaseBuffer();
            // note the lack of any sleep or wait here
        }
        Sleep or wait on a periodic waitable timer
    }


    Matthew van Eerde
    Thursday, April 07, 2011 11:22 PM
  • i tried event driven but i kept getting some error....i need to look at this more deeply. But i had this as my init code

     

      hres=client->GetDevicePeriod (NULL,& dura);

     hres = client->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE

            ,AUDCLNT_STREAMFLAGS_EVENTCALLBACK ,dura ,dura ,(WAVEFORMATEX*)wavFmt ,NULL); 

      WIN_EXITERR(hres,"Thread:Capture - client init")

     hres = client->SetEventHandle(hEvtAudioCap);

     

    Though the erro might have been caused by this packet size error i posted previously.

     

     

    Does the GetNextPacketSize work in exclusive mode? the docs say it doesn't.

    I'm currently using this instead of nextpacket

     

     

    hres=clientCapture->GetBuffer(&dataBuf,&nframeIn,&flags,NULL,NULL);     

    if(nframeIn>0) memcpy(audioDataIn, dataBuf, nframeIn*frameSz);

     

     

     

     

     



    Thursday, April 07, 2011 11:55 PM
  • You're correct that GetNextPacketSize() is documented as not working in exclusive mode - I retract that.

    "Use this method only with shared-mode streams. It does not work with exclusive-mode streams."


    Matthew van Eerde
    Friday, April 08, 2011 12:07 AM
  • when i have the iaudioclient::initialize in EventCallback mode...., the GetBufferSize(&n) is n=288 [where i get the error i posted above]. But when i set the mode to 0 (timermode?) GetBufferSize(&n)  is n= 512. Should this happen?

     

    Also in several of the examples and in your blog on aligning this value pops up

     10000.0 * // (hns / ms); 

    Is this an arbitrary value?...and does the hns abbrev refer to 100ns(nano) as per the doc on GetDevicePeriod?


    And i think the last 2 questions I'll have for a while (I finally have thigns working for timer-driven and event-driven, the latter i had to set both threads to event-driven).

    (1) When your WAVEFORMATEXTENSIBLE has non-equal precision bitd pairing 24/32 ... is the 24bit precsion the first 24b or the last 24b of the 32bit depth.

    (2) after awhile of running flawlessly, the app flatlines to a constant buzz.  Since i played with the Process/Thread Priority, would this account for the buzz. It usually happens when I doing some other UI task in another app.

     

    Thanks again for all the help in understnading winAudio. Very much appreciated it.

    regards, Jack


    Friday, April 08, 2011 12:35 AM
  • hns stands for "hundred nanoseconds."  1 hns is 1/10 of a microsecond, or 1/10,000 of a millisecond.

    The 24 "valid bits" are the most significant bits of the 32.  You could treat it as a 32 bit (signed) int and ignore the bottom eight bits, or divide it by 256 to see only the valid bits.

    Re process and thread priority: registering with the Multimedia Class Scheduler Service (MMCSS) will ensure that your thread receives "bursts" of very high priority with intervals of low priority in between.


    Matthew van Eerde
    Friday, April 08, 2011 3:47 PM
  • back again =[

    The capturing works fine sometimes in "exclusive+event mode" ...then it ends up with AUDCLNT_E_BUFFER_OPERATION_PENDING and either the discontinuity flag or timestamp.  Is there any way to restart teh device so that it captures again? 

    I tried "client->stop(); client->reset(); client->start();" to no avail.

     

    ===================

     For the initialize i have set (should the buffersize/periodicity be greater than the DevicePeriod?)

    client->GetDevicePeriod (NULL,&duraHNS);

    hres = client->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE,AUDCLNT_STREAMFLAGS_EVENTCALLBACK         ,duraHNS, duraHNS ,(WAVEFORMATEX*)&wavFmt ,NULL); 

        WIN_EXITERR(hres,"ClientInit - client init")

        hres = client->SetEventHandle(hEvt);

        WIN_EXITERR(hres,"ClientInit - set eventhandle")

    ===================

    Then in the loop i have

    client->Start();

    while(On)

    {  WaitForSingleObject(hEvt,INFINITE);  

        hres=cap->GetBuffer(&dataBuf,&nframeIn,&flags,NULL,NULL);            

     if(hres==S_OK&&dataBuf&&0<nframeIn)

    { devMgr->nframeSoundIn=nframeIn;

    memcpy(devMgr->soundBufIn, dataBuf , nframeIn*frameSz);

    hres=cap->ReleaseBuffer(nframeIn);

    }

          ... some handlers for the various errors 

     

    }

     

     





    Thursday, April 14, 2011 6:50 PM
  • Hmmm... Are you getting failing HRESULTs, NULL buffers, or buffers of size 0 back from GetBuffer()? That is, if you change your error handling from "wait and call buffer again" to "report failure and exit", what failure do you see?
    Matthew van Eerde
    Thursday, April 14, 2011 9:52 PM
  • the 2errors i seem to be getting repeatedly are

    [1] hresult=0x8890001(EMPTY BUFFER) with either valid or null ptr and flag=4 (TIMESTAMP error)

    [2] hresult=0(or S_OK) with a  NULL buffer with and flag = 4 (TIMESTAMP error)

    #1 occurs quite frequently and usually at the first call to getbuffer, and i use a "continue" on this error to get the device capturing for some duration of time. This sometimes doesn't occur when i'm talking as the app is launched.

    #2 usually occurs after #1 which seems  logical...but it has also occured by itself. 

     

    ----------------------------

    it also returns back for #frames the full double buffer size of the multiple of the deviceperiod i set for buffer/periodicity in the INITIALIZE function. 

    ie.   my realtek devperiod is 3ms (which corresponds to 288 frames), and i've been trying different factors of it (2x,4x,10x) so for the 4x its returns  2304 = (288*4*2) frames. 

     

    The larger the factor(currently at 20x) of devperiod I use for the INIT: buffer/periodicity, the longer before the secoond error appears.

    --------------------------

    So i guess the first thing i need to solve is how to not to get the 

    AUDCLNT_S_BUFFER_EMPTY off the start.

     

    Oh yah i'm using SDK6.1 on vista


    Friday, April 15, 2011 3:19 AM
  • Event-driven capture is not supported prior to Windows 7.

    http://msdn.microsoft.com/en-us/library/dd370875(v=VS.85).aspx

    > The initial release of Windows Vista supports event-driven buffering (that is, the use of the AUDCLNT_STREAMFLAGS_EVENTCALLBACK flag) for rendering streams only.


    Matthew van Eerde
    Friday, April 15, 2011 8:11 PM
  • In the paragraph following the one u cited:

    "In the Windows Vista Service Pack 1 release, this flag is functional in shared-mode and exclusive mode; an application can set this flag to enable event-buffering for capture streams. For more information about capturing an audio stream, see Capturing a Stream."

    i have sp2 for vista....though i'll try to use shared-mode....should the loop be teh same AND just the initialization code is different?

    Its nice though that it works for a couple of seconds and doesn't have the noise that timer-driven has.

    Saturday, April 16, 2011 4:54 AM