Advertisement
Guest User

WASAPI

a guest
Jun 1st, 2012
1,217
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 12.06 KB | None | 0 0
  1. //-----------------------------------------------------------
  2. // Play an exclusive-mode stream on the default audio
  3. // rendering device. The PlayExclusiveStream function uses
  4. // event-driven buffering and MMCSS to play the stream at
  5. // the minimum latency supported by the device.
  6. //-----------------------------------------------------------
  7.  
  8. // NOTE: Must link against avrt.lib (a pragma could do)
  9.  
  10. #include <ObjBase.h>
  11. #include <mmdeviceapi.h>
  12. #include <Audioclient.h>
  13. #include <avrt.h>
  14. #include <cstring>
  15. #include <iostream>
  16. #include <cmath>
  17. #include <string>
  18. #include <vector>
  19. #include <algorithm>
  20.  
  21. #define PI 3.1415926535897932384626433832795
  22. #define PI2 2 * PI
  23.  
  24. // REFERENCE_TIME time units per second and per millisecond
  25. #define REFTIMES_PER_SEC  10000000
  26. #define REFTIMES_PER_MILLISEC  10000
  27.  
  28. #define EXIT_ON_ERROR(hres)  \
  29.               if (FAILED(hres)) { goto Exit; }
  30. #define SAFE_RELEASE(punk)  \
  31.               if ((punk) != NULL)  \
  32.                 { (punk)->Release(); (punk) = NULL; }
  33.  
  34. const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
  35. const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
  36. const IID IID_IAudioClient = __uuidof(IAudioClient);
  37. const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
  38.  
  39. class MyAudioSource
  40. {
  41. private:
  42.     WAVEFORMATEX    mFormat;
  43.     UINT32          mMilliseconds;
  44.     UINT32          mPlayedFrames;
  45.     WORD            mCurrentChannel;
  46.  
  47. public:
  48.     MyAudioSource()
  49.     :   mMilliseconds(0),
  50.         mPlayedFrames(0),
  51.         mCurrentChannel(0)
  52.     {
  53.         ZeroMemory(&mFormat, sizeof(WAVEFORMATEX));
  54.     }
  55.  
  56.     HRESULT SetFormat(const WAVEFORMATEX *pwfx)
  57.     {
  58.         memcpy(&mFormat, pwfx, sizeof(WAVEFORMATEX));
  59.  
  60.         return S_OK;
  61.     }
  62.  
  63.     HRESULT LoadData(UINT32 bufferFrameCount, BYTE *pData, DWORD *pFlags)
  64.     {
  65.         // Rough code to play for at least cMaxMilliseconds per channel
  66.         const UINT32 cMaxMilliseconds = 2000;
  67.         const double cTestFrequencyHz = 400;
  68.         const UINT32 cBytesPerSample = mFormat.wBitsPerSample / 8;
  69.  
  70.         if (mMilliseconds >= cMaxMilliseconds)
  71.         {
  72.             ++mCurrentChannel;
  73.  
  74.             if (mCurrentChannel >= mFormat.nChannels)
  75.             {
  76.                 ZeroMemory(pData, bufferFrameCount * mFormat.nChannels * cBytesPerSample);
  77.                 *pFlags = AUDCLNT_BUFFERFLAGS_SILENT;
  78.  
  79.                 return S_OK;
  80.             }
  81.  
  82.             mMilliseconds = 0;
  83.             mPlayedFrames = 0;
  84.         }
  85.  
  86.         const double cAmplitude = 0.9 * std::pow(2.0, static_cast<double>(mFormat.wBitsPerSample - 1));
  87.  
  88.         for (UINT32 i = 0; i < bufferFrameCount; ++i)
  89.         {
  90.             const UINT32 cSampleValue = static_cast<UINT32>(cAmplitude * std::sin(cTestFrequencyHz * PI2 * (mPlayedFrames + i) / mFormat.nSamplesPerSec));
  91.  
  92.             for (WORD c = 0; c < mFormat.nChannels; ++c)
  93.             {
  94.                 if (c == mCurrentChannel) std::memcpy(pData, &cSampleValue, cBytesPerSample);
  95.                 else ZeroMemory(pData, cBytesPerSample);
  96.  
  97.                 pData += cBytesPerSample;
  98.             }
  99.         }
  100.  
  101.         *pFlags = 0;
  102.         mMilliseconds += 1000 * bufferFrameCount / mFormat.nSamplesPerSec;
  103.         mPlayedFrames += bufferFrameCount;
  104.        
  105.         return S_OK;
  106.     }
  107. };
  108.  
  109. HRESULT GetStreamFormat(IAudioClient *pAudioClient, WAVEFORMATEX **ppwfx)
  110. {
  111.     WAVEFORMATEXTENSIBLE wfx;
  112.  
  113.     ZeroMemory(&wfx, sizeof(WAVEFORMATEXTENSIBLE));
  114.     wfx.Format.cbSize = 22; // minimum required, and all that is needed (skips the SubFormat)
  115.     wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
  116.     wfx.Format.nChannels = 6;
  117.     wfx.Format.nSamplesPerSec = 96000;
  118.     wfx.Format.wBitsPerSample = 24;
  119.     wfx.Format.nBlockAlign = 18;
  120.     wfx.Format.nAvgBytesPerSec = 1728000;
  121.     wfx.Samples.wValidBitsPerSample = 24;
  122.     wfx.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
  123.     wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
  124.  
  125.     std::cout << "Trying WAVEFORMAT\n"
  126.         << "\tChannels: " << wfx.Format.nChannels << "\n"
  127.         << "\tSamples per second: " << wfx.Format.wBitsPerSample << "\n"
  128.         << "\tBits per sample: " << wfx.Format.wBitsPerSample << "\n"
  129.         << "\tBlock align: " << wfx.Format.nBlockAlign << "\n"
  130.         << "\tAverage bytes per sec: " << wfx.Format.nAvgBytesPerSec << "\n";
  131.  
  132.     HRESULT hr = pAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfx.Format, ppwfx);
  133.  
  134.     if (hr < 0)
  135.     {
  136.         std::cout << "IAudioClient::IsFormatSupported failed: ";
  137.  
  138.         switch (hr)
  139.         {
  140.         case E_POINTER: std::cout << "E_POINTER"; break;
  141.         case E_INVALIDARG: std::cout << "E_INVALIDARG"; break;
  142.         case AUDCLNT_E_DEVICE_INVALIDATED: std::cout << "AUDCLNT_E_DEVICE_INVALIDATED"; break;
  143.         case AUDCLNT_E_SERVICE_NOT_RUNNING: std::cout << "AUDCLNT_E_SERVICE_NOT_RUNNING"; break;
  144.         case AUDCLNT_E_UNSUPPORTED_FORMAT: std::cout << "AUDCLNT_E_UNSUPPORTED_FORMAT"; break;
  145.         default: std::cout << "HRESULT(" << std::hex << hr << ")"; break;
  146.         }
  147.  
  148.         std::cout << "\n";
  149.     }
  150.  
  151.     if (*ppwfx == nullptr)
  152.     {
  153.         if (hr != S_OK)
  154.         {
  155.             std::cout << "Returning the proposed WAVEFORMAT anyway\n";
  156.         }
  157.  
  158.         // Force it
  159.         *ppwfx = reinterpret_cast<WAVEFORMATEX *>(CoTaskMemAlloc(sizeof(wfx)));
  160.  
  161.         std::memcpy(*ppwfx, &wfx, sizeof(wfx));
  162.  
  163.         hr = S_OK;
  164.     }
  165.  
  166.     return hr;
  167. }
  168.  
  169. template <typename V>
  170. void ClearDeviceVector(V &v)
  171. {
  172.     for (auto i = v.begin(); i != v.end(); ++i)
  173.     {
  174.         SAFE_RELEASE(*i);
  175.     }
  176.  
  177.     v.clear();
  178. }
  179.  
  180. HRESULT PlayExclusiveStream(MyAudioSource *pMySource)
  181. {
  182.     HRESULT hr;
  183.     REFERENCE_TIME hnsRequestedDuration = 0;
  184.     IMMDeviceEnumerator *pEnumerator = NULL;
  185.     IMMDeviceCollection *pCollection = NULL;
  186.     IMMDevice *pDevice = NULL;
  187.     IAudioClient *pAudioClient = NULL;
  188.     IAudioRenderClient *pRenderClient = NULL;
  189.     WAVEFORMATEX *pwfx = NULL;
  190.     HANDLE hEvent = NULL;
  191.     HANDLE hTask = NULL;
  192.     UINT cDevices;
  193.     UINT32 bufferFrameCount;
  194.     BYTE *pData;
  195.     PWSTR strId;
  196.     DWORD flags = 0;
  197.     std::vector<IMMDevice *> vDevices;
  198.     std::string lInput;
  199.  
  200.     std::cout << "CoCreateInstance\n";
  201.     hr = CoCreateInstance(
  202.            CLSID_MMDeviceEnumerator, NULL,
  203.            CLSCTX_ALL, IID_IMMDeviceEnumerator,
  204.            (void**)&pEnumerator);
  205.     EXIT_ON_ERROR(hr)
  206.  
  207.     std::cout << "IMMDeviceEnumerator::EnumAudioEndpoints\n";
  208.     hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pCollection);
  209.     EXIT_ON_ERROR(hr)
  210.  
  211.     hr = pCollection->GetCount(&cDevices);
  212.     EXIT_ON_ERROR(hr)
  213.  
  214.     for (UINT i = 0; i < cDevices; ++i)
  215.     {
  216.         hr = pCollection->Item(i, &pDevice);
  217.         EXIT_ON_ERROR(hr)
  218.  
  219.         hr = pDevice->GetId(&strId);
  220.         EXIT_ON_ERROR(hr)
  221.  
  222.         vDevices.push_back(pDevice);
  223.  
  224.         if (strId != nullptr)
  225.         {
  226.             std::wcout << L"\tDevice " << i << ": " << strId << std::endl;
  227.  
  228.             CoTaskMemFree(strId);
  229.             strId = nullptr;
  230.         }
  231.  
  232.         pDevice = nullptr;
  233.     }
  234.  
  235.     std::cout << "Select the device to use (just press ENTER for the default): ";
  236.     std::getline(std::cin, lInput);
  237.  
  238.     while (!lInput.empty() && (lInput.back() == '\r' || lInput.back() == '\n'))
  239.     {
  240.         lInput.pop_back();
  241.     }
  242.  
  243.     if (lInput.empty())
  244.     {
  245.         hr = pEnumerator->GetDefaultAudioEndpoint(
  246.                         eRender, eConsole, &pDevice);
  247.         EXIT_ON_ERROR(hr)
  248.     }
  249.     else
  250.     {
  251.         long iDevice = std::strtol(lInput.c_str(), nullptr, 10);
  252.  
  253.         if (iDevice < 0 || iDevice >= static_cast<long>(vDevices.size()))
  254.         {
  255.             std::cout << "Wrong choice\n";
  256.            
  257.             EXIT_ON_ERROR(E_INVALIDARG)
  258.         }
  259.  
  260.         std::swap(pDevice, vDevices[iDevice]);
  261.     }
  262.  
  263.     hr = pDevice->GetId(&strId);
  264.     EXIT_ON_ERROR(hr)
  265.  
  266.     if (strId != nullptr)
  267.     {
  268.         std::wcout << L"Default device: " << strId << std::endl;
  269.  
  270.         CoTaskMemFree(strId);
  271.         strId = nullptr;
  272.     }
  273.  
  274.     std::cout << "IMMDevice::Activate\n";
  275.     hr = pDevice->Activate(
  276.                     IID_IAudioClient, CLSCTX_ALL,
  277.                     NULL, (void**)&pAudioClient);
  278.     EXIT_ON_ERROR(hr)
  279.  
  280.     // Call a helper function to negotiate with the audio
  281.     // device for an exclusive-mode stream format.
  282.     hr = GetStreamFormat(pAudioClient, &pwfx);
  283.     EXIT_ON_ERROR(hr)
  284.  
  285.     // Initialize the stream to play at the minimum latency.
  286.     std::cout << "IAudioClient::GetDevicePeriod\n";
  287.     hr = pAudioClient->GetDevicePeriod(NULL, &hnsRequestedDuration);
  288.     EXIT_ON_ERROR(hr)
  289.  
  290.     std::cout << "IAudioDevice::Initialize\n";
  291.     hr = pAudioClient->Initialize(
  292.                          AUDCLNT_SHAREMODE_EXCLUSIVE,
  293.                          AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
  294.                          hnsRequestedDuration,
  295.                          hnsRequestedDuration,
  296.                          pwfx,
  297.                          NULL);
  298.     EXIT_ON_ERROR(hr)
  299.  
  300.     // Tell the audio source which format to use.
  301.     hr = pMySource->SetFormat(pwfx);
  302.     EXIT_ON_ERROR(hr)
  303.  
  304.     // Create an event handle and register it for
  305.     // buffer-event notifications.
  306.     hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  307.     if (hEvent == NULL)
  308.     {
  309.         hr = E_FAIL;
  310.         goto Exit;
  311.     }
  312.  
  313.     hr = pAudioClient->SetEventHandle(hEvent);
  314.     EXIT_ON_ERROR(hr);
  315.  
  316.     // Get the actual size of the two allocated buffers.
  317.     hr = pAudioClient->GetBufferSize(&bufferFrameCount);
  318.     EXIT_ON_ERROR(hr)
  319.  
  320.     hr = pAudioClient->GetService(
  321.                          IID_IAudioRenderClient,
  322.                          (void**)&pRenderClient);
  323.     EXIT_ON_ERROR(hr)
  324.  
  325.     // To reduce latency, load the first buffer with data
  326.     // from the audio source before starting the stream.
  327.     hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);
  328.     EXIT_ON_ERROR(hr)
  329.  
  330.     hr = pMySource->LoadData(bufferFrameCount, pData, &flags);
  331.     EXIT_ON_ERROR(hr)
  332.  
  333.     hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags);
  334.     EXIT_ON_ERROR(hr)
  335.  
  336.     // Ask MMCSS to temporarily boost the thread priority
  337.     // to reduce glitches while the low-latency stream plays.
  338.     DWORD taskIndex = 0;
  339.     hTask = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex);
  340.     if (hTask == NULL)
  341.     {
  342.         hr = E_FAIL;
  343.         EXIT_ON_ERROR(hr)
  344.     }
  345.  
  346.     std::cout << "IAudioDevice::Start\n";
  347.     hr = pAudioClient->Start();  // Start playing.
  348.     EXIT_ON_ERROR(hr)
  349.  
  350.     // Each loop fills one of the two buffers.
  351.     while (flags != AUDCLNT_BUFFERFLAGS_SILENT)
  352.     {
  353.         // Wait for next buffer event to be signaled.
  354.         DWORD retval = WaitForSingleObject(hEvent, 2000);
  355.         if (retval != WAIT_OBJECT_0)
  356.         {
  357.             // Event handle timed out after a 2-second wait.
  358.             pAudioClient->Stop();
  359.             hr = ERROR_TIMEOUT;
  360.             goto Exit;
  361.         }
  362.  
  363.         // Grab the next empty buffer from the audio device.
  364.         hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);
  365.         EXIT_ON_ERROR(hr)
  366.  
  367.         // Load the buffer with data from the audio source.
  368.         hr = pMySource->LoadData(bufferFrameCount, pData, &flags);
  369.         EXIT_ON_ERROR(hr)
  370.  
  371.         hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags);
  372.         EXIT_ON_ERROR(hr)
  373.     }
  374.  
  375.     // Wait for the last buffer to play before stopping.
  376.     Sleep((DWORD)(hnsRequestedDuration/REFTIMES_PER_MILLISEC));
  377.  
  378.     std::cout << "IAudioDevice::Stop\n";
  379.     hr = pAudioClient->Stop();  // Stop playing.
  380.     EXIT_ON_ERROR(hr)
  381.  
  382. Exit:
  383.     if (hEvent != NULL)
  384.     {
  385.         CloseHandle(hEvent);
  386.     }
  387.     if (hTask != NULL)
  388.     {
  389.         AvRevertMmThreadCharacteristics(hTask);
  390.     }
  391.     CoTaskMemFree(pwfx);
  392.     ClearDeviceVector(vDevices);
  393.     SAFE_RELEASE(pEnumerator)
  394.     SAFE_RELEASE(pCollection)
  395.     SAFE_RELEASE(pDevice)
  396.     SAFE_RELEASE(pAudioClient)
  397.     SAFE_RELEASE(pRenderClient)
  398.  
  399.     return hr;
  400. }
  401.  
  402. int main()
  403. {
  404.     if (CoInitializeEx(nullptr, COINIT_MULTITHREADED) != S_OK)
  405.     {
  406.         return 1;
  407.     }
  408.  
  409.     MyAudioSource mas;
  410.     PlayExclusiveStream(&mas);
  411.  
  412.     CoUninitialize();
  413. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement