PageRenderTime 102ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/Samples/WindowsAudioSession/cpp/WASAPICapture.cpp

https://github.com/Microsoft/Windows-universal-samples
C++ | 834 lines | 518 code | 124 blank | 192 comment | 83 complexity | 94629e00d2fe4689813c9ab16f7d05a8 MD5 | raw file
  1. //*********************************************************
  2. //
  3. // Copyright (c) Microsoft. All rights reserved.
  4. // THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
  5. // ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
  6. // IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
  7. // PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
  8. //
  9. //*********************************************************
  10. //
  11. // WASAPICapture.h
  12. //
  13. #include "pch.h"
  14. #include "WASAPICapture.h"
  15. using namespace Windows::Storage;
  16. using namespace Windows::System::Threading;
  17. using namespace SDKSample::WASAPIAudio;
  18. #define BITS_PER_BYTE 8
  19. //
  20. // WASAPICapture()
  21. //
  22. WASAPICapture::WASAPICapture() :
  23. m_BufferFrames( 0 ),
  24. m_cbDataSize( 0 ),
  25. m_cbHeaderSize( 0 ),
  26. m_cbFlushCounter( 0 ),
  27. m_dwQueueID( 0 ),
  28. m_DeviceStateChanged( nullptr ),
  29. m_AudioClient( nullptr ),
  30. m_AudioCaptureClient( nullptr ),
  31. m_SampleReadyAsyncResult( nullptr ),
  32. m_ContentStream( nullptr ),
  33. m_OutputStream( nullptr ),
  34. m_WAVDataWriter( nullptr ),
  35. m_PlotData( nullptr ),
  36. m_fWriting( false )
  37. {
  38. // Create events for sample ready or user stop
  39. m_SampleReadyEvent = CreateEventEx( nullptr, nullptr, 0, EVENT_ALL_ACCESS );
  40. if (nullptr == m_SampleReadyEvent)
  41. {
  42. ThrowIfFailed( HRESULT_FROM_WIN32( GetLastError() ) );
  43. }
  44. if (!InitializeCriticalSectionEx( &m_CritSec, 0, 0 ))
  45. {
  46. ThrowIfFailed( HRESULT_FROM_WIN32( GetLastError() ) );
  47. }
  48. m_DeviceStateChanged = ref new DeviceStateChangedEvent();
  49. if (nullptr == m_DeviceStateChanged)
  50. {
  51. ThrowIfFailed( E_OUTOFMEMORY );
  52. }
  53. // Register MMCSS work queue
  54. HRESULT hr = S_OK;
  55. DWORD dwTaskID = 0;
  56. hr = MFLockSharedWorkQueue( L"Capture", 0, &dwTaskID, &m_dwQueueID );
  57. if (FAILED( hr ))
  58. {
  59. ThrowIfFailed( hr );
  60. }
  61. // Set the capture event work queue to use the MMCSS queue
  62. m_xSampleReady.SetQueueID( m_dwQueueID );
  63. }
  64. //
  65. // ~WASAPICapture()
  66. //
  67. WASAPICapture::~WASAPICapture()
  68. {
  69. SAFE_RELEASE( m_AudioClient );
  70. SAFE_RELEASE( m_AudioCaptureClient );
  71. SAFE_RELEASE( m_SampleReadyAsyncResult );
  72. if (INVALID_HANDLE_VALUE != m_SampleReadyEvent)
  73. {
  74. CloseHandle( m_SampleReadyEvent );
  75. m_SampleReadyEvent = INVALID_HANDLE_VALUE;
  76. }
  77. MFUnlockWorkQueue( m_dwQueueID );
  78. m_DeviceStateChanged = nullptr;
  79. m_ContentStream = nullptr;
  80. m_OutputStream = nullptr;
  81. m_WAVDataWriter = nullptr;
  82. m_PlotData = nullptr;
  83. DeleteCriticalSection( &m_CritSec );
  84. }
  85. //
  86. // InitializeAudioDeviceAsync()
  87. //
  88. // Activates the default audio capture on a asynchronous callback thread. This needs
  89. // to be called from the main UI thread.
  90. //
  91. HRESULT WASAPICapture::InitializeAudioDeviceAsync()
  92. {
  93. ComPtr<IActivateAudioInterfaceAsyncOperation> asyncOp;
  94. HRESULT hr = S_OK;
  95. // Get a string representing the Default Audio Capture Device
  96. m_DeviceIdString = MediaDevice::GetDefaultAudioCaptureId( Windows::Media::Devices::AudioDeviceRole::Default );
  97. // This call must be made on the main UI thread. Async operation will call back to
  98. // IActivateAudioInterfaceCompletionHandler::ActivateCompleted, which must be an agile interface implementation
  99. hr = ActivateAudioInterfaceAsync( m_DeviceIdString->Data(), __uuidof(IAudioClient3), nullptr, this, &asyncOp );
  100. if (FAILED( hr ))
  101. {
  102. m_DeviceStateChanged->SetState( DeviceState::DeviceStateInError, hr, true );
  103. }
  104. return hr;
  105. }
  106. //
  107. // ActivateCompleted()
  108. //
  109. // Callback implementation of ActivateAudioInterfaceAsync function. This will be called on MTA thread
  110. // when results of the activation are available.
  111. //
  112. HRESULT WASAPICapture::ActivateCompleted( IActivateAudioInterfaceAsyncOperation *operation )
  113. {
  114. HRESULT hr = S_OK;
  115. HRESULT hrActivateResult = S_OK;
  116. ComPtr<IUnknown> punkAudioInterface;
  117. // Check for a successful activation result
  118. hr = operation->GetActivateResult( &hrActivateResult, &punkAudioInterface );
  119. if (FAILED( hr ))
  120. {
  121. goto exit;
  122. }
  123. hr = hrActivateResult;
  124. if (FAILED( hr ))
  125. {
  126. goto exit;
  127. }
  128. // Get the pointer for the Audio Client
  129. punkAudioInterface.CopyTo( &m_AudioClient );
  130. if (nullptr == m_AudioClient)
  131. {
  132. hr = E_NOINTERFACE;
  133. goto exit;
  134. }
  135. hr = m_AudioClient->GetMixFormat( &m_MixFormat );
  136. if (FAILED( hr ))
  137. {
  138. goto exit;
  139. }
  140. // convert from Float to 16-bit PCM
  141. switch ( m_MixFormat->wFormatTag )
  142. {
  143. case WAVE_FORMAT_PCM:
  144. // nothing to do
  145. break;
  146. case WAVE_FORMAT_IEEE_FLOAT:
  147. m_MixFormat->wFormatTag = WAVE_FORMAT_PCM;
  148. m_MixFormat->wBitsPerSample = 16;
  149. m_MixFormat->nBlockAlign = m_MixFormat->nChannels * m_MixFormat->wBitsPerSample / BITS_PER_BYTE;
  150. m_MixFormat->nAvgBytesPerSec = m_MixFormat->nSamplesPerSec * m_MixFormat->nBlockAlign;
  151. break;
  152. case WAVE_FORMAT_EXTENSIBLE:
  153. {
  154. WAVEFORMATEXTENSIBLE *pWaveFormatExtensible = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(m_MixFormat);
  155. if ( pWaveFormatExtensible->SubFormat == KSDATAFORMAT_SUBTYPE_PCM )
  156. {
  157. // nothing to do
  158. }
  159. else if ( pWaveFormatExtensible->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT )
  160. {
  161. pWaveFormatExtensible->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
  162. pWaveFormatExtensible->Format.wBitsPerSample = 16;
  163. pWaveFormatExtensible->Format.nBlockAlign =
  164. pWaveFormatExtensible->Format.nChannels *
  165. pWaveFormatExtensible->Format.wBitsPerSample /
  166. BITS_PER_BYTE;
  167. pWaveFormatExtensible->Format.nAvgBytesPerSec =
  168. pWaveFormatExtensible->Format.nSamplesPerSec *
  169. pWaveFormatExtensible->Format.nBlockAlign;
  170. pWaveFormatExtensible->Samples.wValidBitsPerSample =
  171. pWaveFormatExtensible->Format.wBitsPerSample;
  172. // leave the channel mask as-is
  173. }
  174. else
  175. {
  176. // we can only handle float or PCM
  177. hr = HRESULT_FROM_WIN32( ERROR_NOT_FOUND );
  178. }
  179. break;
  180. }
  181. default:
  182. // we can only handle float or PCM
  183. hr = HRESULT_FROM_WIN32( ERROR_NOT_FOUND );
  184. break;
  185. }
  186. if (FAILED( hr ))
  187. {
  188. goto exit;
  189. }
  190. // The wfx parameter below is optional (Its needed only for MATCH_FORMAT clients). Otherwise, wfx will be assumed
  191. // to be the current engine format based on the processing mode for this stream
  192. hr = m_AudioClient->GetSharedModeEnginePeriod(m_MixFormat, &m_DefaultPeriodInFrames, &m_FundamentalPeriodInFrames, &m_MinPeriodInFrames, &m_MaxPeriodInFrames);
  193. if (FAILED(hr))
  194. {
  195. return hr;
  196. }
  197. // Initialize the AudioClient in Shared Mode with the user specified buffer
  198. if (m_DeviceProps.IsLowLatency == false)
  199. {
  200. hr = m_AudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,
  201. AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
  202. 200000,
  203. 0,
  204. m_MixFormat,
  205. nullptr);
  206. }
  207. else
  208. {
  209. hr = m_AudioClient->InitializeSharedAudioStream(
  210. AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
  211. m_MinPeriodInFrames,
  212. m_MixFormat,
  213. nullptr);
  214. }
  215. if (FAILED( hr ))
  216. {
  217. goto exit;
  218. }
  219. // Get the maximum size of the AudioClient Buffer
  220. hr = m_AudioClient->GetBufferSize( &m_BufferFrames );
  221. if (FAILED( hr ))
  222. {
  223. goto exit;
  224. }
  225. // Get the capture client
  226. hr = m_AudioClient->GetService( __uuidof(IAudioCaptureClient), (void**) &m_AudioCaptureClient );
  227. if (FAILED( hr ))
  228. {
  229. goto exit;
  230. }
  231. // Create Async callback for sample events
  232. hr = MFCreateAsyncResult( nullptr, &m_xSampleReady, nullptr, &m_SampleReadyAsyncResult );
  233. if (FAILED( hr ))
  234. {
  235. goto exit;
  236. }
  237. // Sets the event handle that the system signals when an audio buffer is ready to be processed by the client
  238. hr = m_AudioClient->SetEventHandle( m_SampleReadyEvent );
  239. if (FAILED( hr ))
  240. {
  241. goto exit;
  242. }
  243. // Create the visualization array
  244. hr = InitializeScopeData();
  245. if (FAILED( hr ))
  246. {
  247. goto exit;
  248. }
  249. // Creates the WAV file. If successful, will set the Initialized event
  250. hr = CreateWAVFile();
  251. if (FAILED( hr ))
  252. {
  253. goto exit;
  254. }
  255. exit:
  256. if (FAILED( hr ))
  257. {
  258. m_DeviceStateChanged->SetState( DeviceState::DeviceStateInError, hr, true );
  259. SAFE_RELEASE( m_AudioClient );
  260. SAFE_RELEASE( m_AudioCaptureClient );
  261. SAFE_RELEASE( m_SampleReadyAsyncResult );
  262. }
  263. // Need to return S_OK
  264. return S_OK;
  265. }
  266. //
  267. // CreateWAVFile()
  268. //
  269. // Creates a WAV file in KnownFolders::MusicLibrary
  270. //
  271. HRESULT WASAPICapture::CreateWAVFile()
  272. {
  273. // Create the WAV file, appending a number if file already exists
  274. concurrency::task<StorageFile^>( KnownFolders::MusicLibrary->CreateFileAsync( AUDIO_FILE_NAME, CreationCollisionOption::GenerateUniqueName )).then(
  275. [this]( StorageFile^ file )
  276. {
  277. if (nullptr == file)
  278. {
  279. ThrowIfFailed( E_INVALIDARG );
  280. }
  281. return file->OpenAsync( FileAccessMode::ReadWrite );
  282. })
  283. // Then create a RandomAccessStream
  284. .then([this]( IRandomAccessStream^ stream )
  285. {
  286. if (nullptr == stream)
  287. {
  288. ThrowIfFailed( E_INVALIDARG );
  289. }
  290. // Get the OutputStream for the file
  291. m_ContentStream = stream;
  292. m_OutputStream = m_ContentStream->GetOutputStreamAt(0);
  293. // Create the DataWriter
  294. m_WAVDataWriter = ref new DataWriter( m_OutputStream );
  295. if (nullptr == m_WAVDataWriter)
  296. {
  297. ThrowIfFailed( E_OUTOFMEMORY );
  298. }
  299. // Create the WAV header
  300. DWORD header[] = {
  301. FCC('RIFF'), // RIFF header
  302. 0, // Total size of WAV (will be filled in later)
  303. FCC('WAVE'), // WAVE FourCC
  304. FCC('fmt '), // Start of 'fmt ' chunk
  305. sizeof(WAVEFORMATEX) + m_MixFormat->cbSize // Size of fmt chunk
  306. };
  307. DWORD data[] = { FCC('data'), 0 }; // Start of 'data' chunk
  308. auto headerBytes = ref new Platform::Array<BYTE>( reinterpret_cast<BYTE*>(header), sizeof(header) );
  309. auto formatBytes = ref new Platform::Array<BYTE>( reinterpret_cast<BYTE*>(m_MixFormat), sizeof(WAVEFORMATEX) + m_MixFormat->cbSize );
  310. auto dataBytes = ref new Platform::Array<BYTE>( reinterpret_cast<BYTE*>(data), sizeof(data) );
  311. if ( (nullptr == headerBytes) || (nullptr == formatBytes) || (nullptr == dataBytes) )
  312. {
  313. ThrowIfFailed( E_OUTOFMEMORY );
  314. }
  315. // Write the header
  316. m_WAVDataWriter->WriteBytes( headerBytes );
  317. m_WAVDataWriter->WriteBytes( formatBytes );
  318. m_WAVDataWriter->WriteBytes( dataBytes );
  319. return m_WAVDataWriter->StoreAsync();
  320. })
  321. // Wait for file data to be written to file
  322. .then([this]( unsigned int BytesWritten )
  323. {
  324. m_cbHeaderSize = BytesWritten;
  325. return m_WAVDataWriter->FlushAsync();
  326. })
  327. // Our file is ready to go, so we can now signal that initialization is finished
  328. .then([this]( bool f )
  329. {
  330. try
  331. {
  332. m_DeviceStateChanged->SetState( DeviceState::DeviceStateInitialized, S_OK, true );
  333. }
  334. catch (Platform::Exception ^e)
  335. {
  336. m_DeviceStateChanged->SetState( DeviceState::DeviceStateInError, e->HResult, true );
  337. }
  338. });
  339. return S_OK;
  340. }
  341. //
  342. // FixWAVHeader()
  343. //
  344. // The size values were not known when we originally wrote the header, so now go through and fix the values
  345. //
  346. HRESULT WASAPICapture::FixWAVHeader()
  347. {
  348. auto DataSizeByte = ref new Platform::Array<BYTE>( reinterpret_cast<BYTE*>( &m_cbDataSize ), sizeof(DWORD) );
  349. // Write the size of the 'data' chunk first
  350. IOutputStream^ OutputStream = m_ContentStream->GetOutputStreamAt( m_cbHeaderSize - sizeof(DWORD) );
  351. m_WAVDataWriter = ref new DataWriter( OutputStream );
  352. m_WAVDataWriter->WriteBytes( DataSizeByte );
  353. concurrency::task<unsigned int>( m_WAVDataWriter->StoreAsync()).then(
  354. [this]( unsigned int BytesWritten )
  355. {
  356. DWORD cbTotalSize = m_cbDataSize + m_cbHeaderSize - 8;
  357. auto TotalSizeByte = ref new Platform::Array<BYTE>( reinterpret_cast<BYTE*>( &cbTotalSize ), sizeof(DWORD) );
  358. // Write the total file size, minus RIFF chunk and size
  359. IOutputStream^ OutputStream = m_ContentStream->GetOutputStreamAt( sizeof(DWORD) ); // sizeof(DWORD) == sizeof(FOURCC)
  360. m_WAVDataWriter = ref new DataWriter( OutputStream );
  361. m_WAVDataWriter->WriteBytes( TotalSizeByte );
  362. concurrency::task<unsigned int>( m_WAVDataWriter->StoreAsync()).then(
  363. [this]( unsigned int BytesWritten )
  364. {
  365. return m_WAVDataWriter->FlushAsync();
  366. })
  367. .then(
  368. [this]( bool f )
  369. {
  370. m_DeviceStateChanged->SetState( DeviceState::DeviceStateStopped, S_OK, true );
  371. });
  372. });
  373. return S_OK;
  374. }
  375. //
  376. // InitializeScopeData()
  377. //
  378. // Setup data structures for sample visualizations
  379. //
  380. HRESULT WASAPICapture::InitializeScopeData()
  381. {
  382. HRESULT hr = S_OK;
  383. m_cPlotDataFilled = 0;
  384. m_cPlotDataMax = (MILLISECONDS_TO_VISUALIZE * m_MixFormat->nSamplesPerSec) / 1000;
  385. m_PlotData = ref new Platform::Array<int, 1>( m_cPlotDataMax + 1 );
  386. if (nullptr == m_PlotData)
  387. {
  388. return E_OUTOFMEMORY;
  389. }
  390. // Only Support 16 bit Audio for now
  391. if (m_MixFormat->wBitsPerSample == 16)
  392. {
  393. m_PlotData[ m_cPlotDataMax ] = -32768; // INT16_MAX
  394. }
  395. else
  396. {
  397. m_PlotData = nullptr;
  398. hr = S_FALSE;
  399. }
  400. return hr;
  401. }
  402. //
  403. // StartCaptureAsync()
  404. //
  405. // Starts asynchronous capture on a separate thread via MF Work Item
  406. //
  407. HRESULT WASAPICapture::StartCaptureAsync()
  408. {
  409. HRESULT hr = S_OK;
  410. // We should be in the initialzied state if this is the first time through getting ready to capture.
  411. if (m_DeviceStateChanged->GetState() == DeviceState::DeviceStateInitialized)
  412. {
  413. m_DeviceStateChanged->SetState( DeviceState::DeviceStateStarting, S_OK, true );
  414. return MFPutWorkItem2( MFASYNC_CALLBACK_QUEUE_MULTITHREADED, 0, &m_xStartCapture, nullptr );
  415. }
  416. // We are in the wrong state
  417. return E_NOT_VALID_STATE;
  418. }
  419. //
  420. // OnStartCapture()
  421. //
  422. // Callback method to start capture
  423. //
  424. HRESULT WASAPICapture::OnStartCapture( IMFAsyncResult* pResult )
  425. {
  426. HRESULT hr = S_OK;
  427. // Start the capture
  428. hr = m_AudioClient->Start();
  429. if (SUCCEEDED( hr ))
  430. {
  431. m_DeviceStateChanged->SetState( DeviceState::DeviceStateCapturing, S_OK, true );
  432. MFPutWaitingWorkItem( m_SampleReadyEvent, 0, m_SampleReadyAsyncResult, &m_SampleReadyKey );
  433. }
  434. else
  435. {
  436. m_DeviceStateChanged->SetState( DeviceState::DeviceStateInError, hr, true );
  437. }
  438. return S_OK;
  439. }
  440. //
  441. // StopCaptureAsync()
  442. //
  443. // Stop capture asynchronously via MF Work Item
  444. //
  445. HRESULT WASAPICapture::StopCaptureAsync()
  446. {
  447. if ( (m_DeviceStateChanged->GetState() != DeviceState::DeviceStateCapturing) &&
  448. (m_DeviceStateChanged->GetState() != DeviceState::DeviceStateInError) )
  449. {
  450. return E_NOT_VALID_STATE;
  451. }
  452. m_DeviceStateChanged->SetState( DeviceState::DeviceStateStopping, S_OK, true );
  453. return MFPutWorkItem2( MFASYNC_CALLBACK_QUEUE_MULTITHREADED, 0, &m_xStopCapture, nullptr );
  454. }
  455. //
  456. // OnStopCapture()
  457. //
  458. // Callback method to stop capture
  459. //
  460. HRESULT WASAPICapture::OnStopCapture( IMFAsyncResult* pResult )
  461. {
  462. // Stop capture by cancelling Work Item
  463. // Cancel the queued work item (if any)
  464. if (0 != m_SampleReadyKey)
  465. {
  466. MFCancelWorkItem( m_SampleReadyKey );
  467. m_SampleReadyKey = 0;
  468. }
  469. m_AudioClient->Stop();
  470. SAFE_RELEASE( m_SampleReadyAsyncResult );
  471. // If this is set, it means we writing from the memory buffer to the actual file asynchronously
  472. // Since a second call to StoreAsync() can cause an exception, don't queue this now, but rather
  473. // let the async operation completion handle the call.
  474. if (!m_fWriting)
  475. {
  476. m_DeviceStateChanged->SetState( DeviceState::DeviceStateFlushing, S_OK, true );
  477. concurrency::task<unsigned int>( m_WAVDataWriter->StoreAsync()).then(
  478. [this]( unsigned int BytesWritten )
  479. {
  480. FinishCaptureAsync();
  481. });
  482. }
  483. return S_OK;
  484. }
  485. //
  486. // FinishCaptureAsync()
  487. //
  488. // Finalizes WAV file on a separate thread via MF Work Item
  489. //
  490. HRESULT WASAPICapture::FinishCaptureAsync()
  491. {
  492. // We should be flushing when this is called
  493. if (m_DeviceStateChanged->GetState() == DeviceState::DeviceStateFlushing)
  494. {
  495. return MFPutWorkItem2( MFASYNC_CALLBACK_QUEUE_MULTITHREADED, 0, &m_xFinishCapture, nullptr );
  496. }
  497. // We are in the wrong state
  498. return E_NOT_VALID_STATE;
  499. }
  500. //
  501. // OnFinishCapture()
  502. //
  503. // Because of the asynchronous nature of the MF Work Queues and the DataWriter, there could still be
  504. // a sample processing. So this will get called to finalize the WAV header.
  505. //
  506. HRESULT WASAPICapture::OnFinishCapture( IMFAsyncResult* pResult )
  507. {
  508. // FixWAVHeader will set the DeviceStateStopped when all async tasks are complete
  509. return FixWAVHeader();
  510. }
  511. //
  512. // OnSampleReady()
  513. //
  514. // Callback method when ready to fill sample buffer
  515. //
  516. HRESULT WASAPICapture::OnSampleReady( IMFAsyncResult* pResult )
  517. {
  518. HRESULT hr = S_OK;
  519. hr = OnAudioSampleRequested( false );
  520. if (SUCCEEDED( hr ))
  521. {
  522. // Re-queue work item for next sample
  523. if (m_DeviceStateChanged->GetState() == DeviceState::DeviceStateCapturing)
  524. {
  525. hr = MFPutWaitingWorkItem( m_SampleReadyEvent, 0, m_SampleReadyAsyncResult, &m_SampleReadyKey );
  526. }
  527. }
  528. else
  529. {
  530. m_DeviceStateChanged->SetState( DeviceState::DeviceStateInError, hr, true );
  531. }
  532. return hr;
  533. }
  534. //
  535. // OnAudioSampleRequested()
  536. //
  537. // Called when audio device fires m_SampleReadyEvent
  538. //
  539. HRESULT WASAPICapture::OnAudioSampleRequested( Platform::Boolean IsSilence )
  540. {
  541. HRESULT hr = S_OK;
  542. UINT32 FramesAvailable = 0;
  543. BYTE *Data = nullptr;
  544. DWORD dwCaptureFlags;
  545. UINT64 u64DevicePosition = 0;
  546. UINT64 u64QPCPosition = 0;
  547. DWORD cbBytesToCapture = 0;
  548. EnterCriticalSection( &m_CritSec );
  549. // If this flag is set, we have already queued up the async call to finialize the WAV header
  550. // So we don't want to grab or write any more data that would possibly give us an invalid size
  551. if ( (m_DeviceStateChanged->GetState() == DeviceState::DeviceStateStopping) ||
  552. (m_DeviceStateChanged->GetState() == DeviceState::DeviceStateFlushing) )
  553. {
  554. goto exit;
  555. }
  556. // A word on why we have a loop here;
  557. // Suppose it has been 10 milliseconds or so since the last time
  558. // this routine was invoked, and that we're capturing 48000 samples per second.
  559. //
  560. // The audio engine can be reasonably expected to have accumulated about that much
  561. // audio data - that is, about 480 samples.
  562. //
  563. // However, the audio engine is free to accumulate this in various ways:
  564. // a. as a single packet of 480 samples, OR
  565. // b. as a packet of 80 samples plus a packet of 400 samples, OR
  566. // c. as 48 packets of 10 samples each.
  567. //
  568. // In particular, there is no guarantee that this routine will be
  569. // run once for each packet.
  570. //
  571. // So every time this routine runs, we need to read ALL the packets
  572. // that are now available;
  573. //
  574. // We do this by calling IAudioCaptureClient::GetNextPacketSize
  575. // over and over again until it indicates there are no more packets remaining.
  576. for
  577. (
  578. hr = m_AudioCaptureClient->GetNextPacketSize(&FramesAvailable);
  579. SUCCEEDED(hr) && FramesAvailable > 0;
  580. hr = m_AudioCaptureClient->GetNextPacketSize(&FramesAvailable)
  581. )
  582. {
  583. cbBytesToCapture = FramesAvailable * m_MixFormat->nBlockAlign;
  584. // WAV files have a 4GB (0xFFFFFFFF) size limit, so likely we have hit that limit when we
  585. // overflow here. Time to stop the capture
  586. if ( (m_cbDataSize + cbBytesToCapture) < m_cbDataSize )
  587. {
  588. StopCaptureAsync();
  589. goto exit;
  590. }
  591. // Get sample buffer
  592. hr = m_AudioCaptureClient->GetBuffer( &Data, &FramesAvailable, &dwCaptureFlags, &u64DevicePosition, &u64QPCPosition );
  593. if (FAILED( hr ))
  594. {
  595. goto exit;
  596. }
  597. if (dwCaptureFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY)
  598. {
  599. // Pass down a discontinuity flag in case the app is interested and reset back to capturing
  600. m_DeviceStateChanged->SetState( DeviceState::DeviceStateDiscontinuity, S_OK, true );
  601. m_DeviceStateChanged->SetState( DeviceState::DeviceStateCapturing, S_OK, false );
  602. }
  603. // Zero out sample if silence
  604. if ( (dwCaptureFlags & AUDCLNT_BUFFERFLAGS_SILENT) || IsSilence )
  605. {
  606. memset( Data, 0, FramesAvailable * m_MixFormat->nBlockAlign );
  607. }
  608. // Store data in array
  609. auto dataByte = ref new Platform::Array<BYTE, 1>( Data, cbBytesToCapture );
  610. // Release buffer back
  611. m_AudioCaptureClient->ReleaseBuffer( FramesAvailable );
  612. // Update plotter data
  613. ProcessScopeData( dataByte->Data, dataByte->Length );
  614. // Write File and async store
  615. m_WAVDataWriter->WriteBytes( dataByte );
  616. // Increase the size of our 'data' chunk and flush counter. m_cbDataSize needs to be accurate
  617. // Its OK if m_cbFlushCounter is an approximation
  618. m_cbDataSize += cbBytesToCapture;
  619. m_cbFlushCounter += cbBytesToCapture;
  620. if ( (m_cbFlushCounter > ( m_MixFormat->nAvgBytesPerSec * FLUSH_INTERVAL_SEC )) && !m_fWriting )
  621. {
  622. // Set this flag when about to commit the async storage operation. We don't want to
  623. // accidently call stop and finalize the WAV header or run into a scenario where the
  624. // store operation takes longer than FLUSH_INTERVAL_SEC as multiple concurrent calls
  625. // to StoreAsync() can cause an exception
  626. m_fWriting = true;
  627. // Reset the counter now since we can process more samples during the async callback
  628. m_cbFlushCounter = 0;
  629. concurrency::task<unsigned int>( m_WAVDataWriter->StoreAsync()).then(
  630. [this]( unsigned int BytesWritten )
  631. {
  632. m_fWriting = false;
  633. // We need to check for StopCapture while we are flusing the file. If it has come through, then we
  634. // can go ahead and call FinisheCaptureAsync() to write the WAV header
  635. if (m_DeviceStateChanged->GetState() == DeviceState::DeviceStateStopping)
  636. {
  637. m_DeviceStateChanged->SetState( DeviceState::DeviceStateFlushing, S_OK, true );
  638. FinishCaptureAsync();
  639. }
  640. });
  641. }
  642. }
  643. exit:
  644. LeaveCriticalSection( &m_CritSec );
  645. return hr;
  646. }
  647. //
  648. // ProcessScopeData()
  649. //
  650. // Copies sample data to the buffer array and fires the event
  651. //
  652. HRESULT WASAPICapture::ProcessScopeData( BYTE* pData, DWORD cbBytes )
  653. {
  654. HRESULT hr = S_OK;
  655. // We don't have a valid pointer array, so return. This could be the case if we aren't
  656. // dealing with 16-bit audio
  657. if (m_PlotData == nullptr)
  658. {
  659. return S_FALSE;
  660. }
  661. DWORD dwNumPoints = cbBytes / m_MixFormat->nChannels / (m_MixFormat->wBitsPerSample / 8);
  662. // Read the 16-bit samples from channel 0
  663. INT16 *pi16 = (INT16*)pData;
  664. for ( DWORD i = 0; m_cPlotDataFilled < m_cPlotDataMax && i < dwNumPoints; i++ )
  665. {
  666. m_PlotData[ m_cPlotDataFilled ] = *pi16;
  667. pi16 += m_MixFormat->nChannels;
  668. m_cPlotDataFilled++;
  669. }
  670. // Send off the event and get ready for the next set of samples
  671. if (m_cPlotDataFilled == m_cPlotDataMax)
  672. {
  673. ComPtr<IUnknown> spUnknown;
  674. ComPtr<CAsyncState> spState = Make<CAsyncState>( m_PlotData, m_cPlotDataMax + 1 );
  675. hr = spState.As( &spUnknown );
  676. if (SUCCEEDED( hr ))
  677. {
  678. MFPutWorkItem2( MFASYNC_CALLBACK_QUEUE_MULTITHREADED, 0, &m_xSendScopeData, spUnknown.Get() );
  679. }
  680. m_cPlotDataFilled = 0;
  681. }
  682. return hr;
  683. }
  684. //
  685. // OnSendScopeData()
  686. //
  687. // Callback method to stop capture
  688. //
  689. HRESULT WASAPICapture::OnSendScopeData( IMFAsyncResult* pResult )
  690. {
  691. HRESULT hr = S_OK;
  692. CAsyncState *pState = nullptr;
  693. hr = pResult->GetState( reinterpret_cast<IUnknown**>(&pState) );
  694. if (SUCCEEDED( hr ))
  695. {
  696. PlotDataReadyEvent::SendEvent( reinterpret_cast<Platform::Object^>(this), pState->m_Data , pState->m_Size );
  697. }
  698. SAFE_RELEASE( pState );
  699. return S_OK;
  700. }
  701. //
  702. // SetProperties()
  703. //
  704. // Sets various properties that the user defines in the scenario
  705. //
  706. HRESULT WASAPICapture::SetProperties(CAPTUREDEVICEPROPS props)
  707. {
  708. m_DeviceProps = props;
  709. return S_OK;
  710. }