record_audio_wasapi.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. #include "record_audio_wasapi.h"
  2. #include <string>
  3. #include "error_define.h"
  4. #include "system_error.h"
  5. #include "utils_string.h"
  6. #include "log_helper.h"
  7. #ifdef _WIN32
  8. #define NS_PER_SEC 1000000000
  9. #define REFTIMES_PER_SEC NS_PER_SEC/100 //100ns per buffer unit
  10. #endif // _WIN32
  11. namespace am {
  12. record_audio_wasapi::record_audio_wasapi()
  13. {
  14. _co_inited = false;
  15. HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
  16. DWORD err = GetLastError();
  17. if (hr != S_OK && err != S_OK)
  18. al_error("%s,error:%s",err2str(AE_CO_INITED_FAILED), system_error::error2str(err).c_str());
  19. _co_inited = (hr == S_OK || hr == S_FALSE);//if already initialize will return S_FALSE
  20. _is_default = false;
  21. _wfex = NULL;
  22. _enumerator = nullptr;
  23. _device = nullptr;
  24. _capture_client = nullptr;
  25. _capture = nullptr;
  26. _render = nullptr;
  27. _render_client = nullptr;
  28. _capture_sample_count = 0;
  29. _render_sample_count = 0;
  30. _ready_event = NULL;
  31. _stop_event = NULL;
  32. _render_event = NULL;
  33. _use_device_ts = false;
  34. _start_time = 0;
  35. }
  36. record_audio_wasapi::~record_audio_wasapi()
  37. {
  38. stop();
  39. clean_wasapi();
  40. if(_co_inited == true)
  41. CoUninitialize();
  42. }
  43. void get_device_info(IMMDevice *device) {
  44. HRESULT resSample;
  45. IPropertyStore *store = nullptr;
  46. PWAVEFORMATEX deviceFormatProperties;
  47. PROPVARIANT prop;
  48. resSample = device->OpenPropertyStore(STGM_READ, &store);
  49. if (!FAILED(resSample)) {
  50. resSample =
  51. store->GetValue(PKEY_AudioEngine_DeviceFormat, &prop);
  52. if (!FAILED(resSample)) {
  53. deviceFormatProperties =
  54. (PWAVEFORMATEX)prop.blob.pBlobData;
  55. std::string device_sample = std::to_string(
  56. deviceFormatProperties->nSamplesPerSec);
  57. }
  58. }
  59. }
  60. #define KSAUDIO_SPEAKER_2POINT1 (KSAUDIO_SPEAKER_STEREO|SPEAKER_LOW_FREQUENCY)
  61. #define OBS_KSAUDIO_SPEAKER_4POINT1 \
  62. (KSAUDIO_SPEAKER_SURROUND | SPEAKER_LOW_FREQUENCY)
  63. int64_t record_audio_wasapi::convert_layout(DWORD layout, WORD channels)
  64. {
  65. switch (layout) {
  66. case KSAUDIO_SPEAKER_2POINT1:
  67. return AV_CH_LAYOUT_SURROUND;
  68. case KSAUDIO_SPEAKER_SURROUND:
  69. return AV_CH_LAYOUT_4POINT0;
  70. case OBS_KSAUDIO_SPEAKER_4POINT1:
  71. return AV_CH_LAYOUT_4POINT1;
  72. case KSAUDIO_SPEAKER_5POINT1_SURROUND:
  73. return AV_CH_LAYOUT_5POINT1_BACK;
  74. case KSAUDIO_SPEAKER_7POINT1_SURROUND:
  75. return AV_CH_LAYOUT_7POINT1;
  76. }
  77. return av_get_default_channel_layout(channels);
  78. }
  79. void record_audio_wasapi::init_format(WAVEFORMATEX * wfex)
  80. {
  81. DWORD layout = 0;
  82. if (wfex->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
  83. WAVEFORMATEXTENSIBLE *ext = (WAVEFORMATEXTENSIBLE *)wfex;
  84. layout = ext->dwChannelMask;
  85. }
  86. _channel_layout = convert_layout(layout, wfex->nChannels);
  87. _sample_rate = wfex->nSamplesPerSec;
  88. _bit_rate = wfex->nAvgBytesPerSec;
  89. _bit_per_sample = wfex->wBitsPerSample;
  90. _channel_num = wfex->nChannels;
  91. //wasapi is always flt
  92. _fmt = AV_SAMPLE_FMT_FLT;
  93. }
  94. int record_audio_wasapi::init_render()
  95. {
  96. int error = AE_NO;
  97. HRESULT res = S_OK;
  98. do {
  99. res = _device->Activate(__uuidof(IAudioClient),
  100. CLSCTX_ALL,
  101. nullptr,
  102. (void **)&_render_client
  103. );
  104. if (FAILED(res)) {
  105. error = AE_CO_ACTIVE_DEVICE_FAILED;
  106. break;
  107. }
  108. WAVEFORMATEX *wfex;
  109. res = _render_client->GetMixFormat(&wfex);
  110. if (FAILED(res)) {
  111. error = AE_CO_GET_FORMAT_FAILED;
  112. break;
  113. }
  114. res = _render_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
  115. AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
  116. REFTIMES_PER_SEC,
  117. 0, wfex, nullptr);
  118. CoTaskMemFree(wfex);
  119. if (FAILED(res)) {
  120. error = AE_CO_AUDIOCLIENT_INIT_FAILED;
  121. break;
  122. }
  123. /* Silent loopback fix. Prevents audio stream from stopping and */
  124. /* messing up timestamps and other weird glitches during silence */
  125. /* by playing a silent sample all over again. */
  126. res = _render_client->GetService(__uuidof(IAudioRenderClient),
  127. (void **)&_render);
  128. if (FAILED(res)) {
  129. error = AE_CO_GET_CAPTURE_FAILED;
  130. break;
  131. }
  132. _render_event = CreateEvent(NULL, FALSE, FALSE, NULL);
  133. if (!_render_event) {
  134. error = AE_CO_CREATE_EVENT_FAILED;
  135. break;
  136. }
  137. res = _render_client->SetEventHandle(_render_event);
  138. if (FAILED(res)) {
  139. error = AE_CO_SET_EVENT_FAILED;
  140. break;
  141. }
  142. //pre fill a single silent buffer
  143. res = _render_client->GetBufferSize(&_render_sample_count);
  144. if (FAILED(res)) {
  145. error = AE_CO_GET_VALUE_FAILED;
  146. break;
  147. }
  148. uint8_t *buffer = NULL;
  149. res = _render->GetBuffer(_render_sample_count, &buffer);
  150. if (FAILED(res)) {
  151. error = AE_CO_GET_VALUE_FAILED;
  152. break;
  153. }
  154. res = _render->ReleaseBuffer(_render_sample_count, AUDCLNT_BUFFERFLAGS_SILENT);
  155. if (FAILED(res)) {
  156. error = AE_CO_RELEASE_BUFFER_FAILED;
  157. break;
  158. }
  159. } while (0);
  160. if (error != AE_NO)
  161. al_error("init render failed(%ld), %s,lasterror:%lu", res, err2str(error), GetLastError());
  162. return error;
  163. }
  164. int record_audio_wasapi::init(const std::string & device_name, const std::string &device_id, bool is_input)
  165. {
  166. int error = AE_NO;
  167. HRESULT hr = S_OK;
  168. al_info("wasapi start to initialize in %s mode with: %s",
  169. is_input ? "input" : "output",
  170. device_name.c_str());
  171. if (_co_inited == false) {
  172. return AE_CO_INITED_FAILED;
  173. }
  174. if (_inited == true) {
  175. return AE_NO;
  176. }
  177. _device_name = device_name;
  178. _device_id = device_id;
  179. _is_input = is_input;
  180. _is_default = (utils_string::ascii_utf8(DEFAULT_AUDIO_INOUTPUT_ID).compare(_device_id) == 0);
  181. do {
  182. hr = CoCreateInstance(
  183. __uuidof(MMDeviceEnumerator),
  184. NULL,
  185. CLSCTX_ALL,
  186. __uuidof(IMMDeviceEnumerator),
  187. (void **)&_enumerator);
  188. if (FAILED(hr)) {
  189. error = AE_CO_CREATE_FAILED;
  190. break;
  191. }
  192. if (_is_default) {
  193. hr = _enumerator->GetDefaultAudioEndpoint(
  194. is_input ? eCapture : eRender,
  195. is_input ? eCommunications : eConsole, &_device);
  196. }
  197. else {
  198. hr = _enumerator->GetDevice(utils_string::utf8_unicode(_device_id).c_str(), &_device);
  199. }
  200. if (hr != S_OK) {
  201. error = AE_CO_GETENDPOINT_FAILED;
  202. break;
  203. }
  204. get_device_info(_device);
  205. hr = _device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&_capture_client);
  206. if (hr != S_OK) {
  207. error = AE_CO_ACTIVE_DEVICE_FAILED;
  208. break;
  209. }
  210. hr = _capture_client->GetMixFormat(&_wfex);
  211. if (hr != S_OK) {
  212. error = AE_CO_GET_FORMAT_FAILED;
  213. break;
  214. }
  215. init_format(_wfex);
  216. DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
  217. if (_is_input == false)
  218. flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
  219. hr = _capture_client->Initialize(
  220. AUDCLNT_SHAREMODE_SHARED,
  221. flags,
  222. REFTIMES_PER_SEC,
  223. 0,
  224. _wfex,
  225. NULL);
  226. // AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED
  227. // https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize
  228. if (hr != S_OK) {
  229. error = AE_CO_AUDIOCLIENT_INIT_FAILED;
  230. break;
  231. }
  232. //For ouotput mode,ready event will not signal when there is nothing rendering
  233. //We run a render thread and render silent pcm data all time
  234. if (!_is_input) {
  235. error = init_render();
  236. if (error != AE_NO)
  237. break;
  238. }
  239. hr = _capture_client->GetBufferSize(&_capture_sample_count);
  240. if (hr != S_OK) {
  241. error = AE_CO_GET_VALUE_FAILED;
  242. break;
  243. }
  244. hr = _capture_client->GetService(__uuidof(IAudioCaptureClient), (void**)&_capture);
  245. if (hr != S_OK) {
  246. error = AE_CO_GET_CAPTURE_FAILED;
  247. break;
  248. }
  249. _ready_event = CreateEvent(NULL, FALSE, FALSE, NULL);
  250. if (!_ready_event) {
  251. error = AE_CO_CREATE_EVENT_FAILED;
  252. break;
  253. }
  254. _stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
  255. if (!_stop_event) {
  256. error = AE_CO_CREATE_EVENT_FAILED;
  257. break;
  258. }
  259. hr = _capture_client->SetEventHandle(_ready_event);
  260. if (hr != S_OK) {
  261. error = AE_CO_SET_EVENT_FAILED;
  262. break;
  263. }
  264. _inited = true;
  265. } while (0);
  266. if (error != AE_NO) {
  267. al_error("wasapi initialize failed,%s,error:%lu,hr:%lld",
  268. err2str(error), GetLastError(), hr);
  269. clean_wasapi();
  270. }
  271. return error;
  272. }
  273. int record_audio_wasapi::start()
  274. {
  275. if (_running == true) {
  276. al_warn("audio record is already running");
  277. return AE_NO;
  278. }
  279. if (_inited == false)
  280. return AE_NEED_INIT;
  281. HRESULT hr = S_OK;
  282. if (!_is_input) {
  283. hr = _render_client->Start();
  284. if (FAILED(hr)) {
  285. al_error("%s,error:%lu", err2str(AE_CO_START_FAILED), GetLastError());
  286. return AE_CO_START_FAILED;
  287. }
  288. }
  289. hr = _capture_client->Start();
  290. if (hr != S_OK) {
  291. al_error("%s,error:%lu", err2str(AE_CO_START_FAILED), GetLastError());
  292. return AE_CO_START_FAILED;
  293. }
  294. _start_time = av_gettime_relative();
  295. _running = true;
  296. if(!_is_input) {
  297. _render_thread = std::thread(std::bind(&record_audio_wasapi::render_loop, this));
  298. }
  299. _thread = std::thread(std::bind(&record_audio_wasapi::record_loop, this));
  300. return AE_NO;
  301. }
  302. int record_audio_wasapi::pause()
  303. {
  304. _paused = true;
  305. return AE_NO;
  306. }
  307. int record_audio_wasapi::resume()
  308. {
  309. _paused = false;
  310. return AE_NO;
  311. }
  312. int record_audio_wasapi::stop()
  313. {
  314. _running = false;
  315. SetEvent(_stop_event);
  316. if (_render_thread.joinable())
  317. _render_thread.join();
  318. if (_thread.joinable())
  319. _thread.join();
  320. if (_capture_client)
  321. _capture_client->Stop();
  322. if (_render_client)
  323. _render_client->Stop();
  324. return AE_NO;
  325. }
  326. const AVRational record_audio_wasapi::get_time_base()
  327. {
  328. if (_use_device_ts)
  329. return{ 1,NS_PER_SEC };
  330. else return{ 1,AV_TIME_BASE };
  331. }
  332. int64_t record_audio_wasapi::get_start_time()
  333. {
  334. return _start_time;
  335. }
  336. void record_audio_wasapi::process_data(AVFrame *frame, uint8_t* data, uint32_t sample_count, uint64_t device_ts)
  337. {
  338. int sample_size = _bit_per_sample / 8 * _channel_num;
  339. //wasapi time unit is 100ns,so time base is NS_PER_SEC
  340. frame->pts = _use_device_ts ? device_ts * 100 : av_gettime_relative();
  341. if (_use_device_ts == false) {
  342. frame->pts -= (int64_t)sample_count * NS_PER_SEC / (int64_t)_sample_rate / 100;
  343. }
  344. frame->pkt_dts = frame->pts;
  345. frame->nb_samples = sample_count;
  346. frame->format = _fmt;
  347. frame->sample_rate = _sample_rate;
  348. frame->channels = _channel_num;
  349. frame->pkt_size = sample_count*sample_size;
  350. av_samples_fill_arrays(frame->data, frame->linesize, data, _channel_num, sample_count, _fmt, 1);
  351. if (_on_data) _on_data(frame, _cb_extra_index);
  352. }
  353. int record_audio_wasapi::do_record(AVFrame *frame)
  354. {
  355. HRESULT res = S_OK;
  356. LPBYTE buffer = NULL;
  357. DWORD flags = 0;
  358. uint32_t sample_count = 0;
  359. UINT64 pos, ts;
  360. int error = AE_NO;
  361. while (_running) {
  362. res = _capture->GetNextPacketSize(&sample_count);
  363. if (FAILED(res)) {
  364. if (res != AUDCLNT_E_DEVICE_INVALIDATED)
  365. al_error("GetNextPacketSize failed: %lX", res);
  366. error = AE_CO_GET_PACKET_FAILED;
  367. break;
  368. }
  369. if (!sample_count)
  370. break;
  371. buffer = NULL;
  372. res = _capture->GetBuffer(&buffer, &sample_count, &flags, &pos, &ts);
  373. if (FAILED(res)) {
  374. if (res != AUDCLNT_E_DEVICE_INVALIDATED)
  375. al_error("GetBuffer failed: %lX",res);
  376. error = AE_CO_GET_BUFFER_FAILED;
  377. break;
  378. }
  379. //input mode do not have silent data flag do nothing here
  380. if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
  381. //al_warn("on slient data %d", sample_count);
  382. }
  383. if (buffer) {
  384. process_data(frame, buffer, sample_count, ts);
  385. }
  386. else {
  387. al_error("buffer invalid is");
  388. }
  389. _capture->ReleaseBuffer(sample_count);
  390. }
  391. return error;
  392. }
  393. void record_audio_wasapi::render_loop()
  394. {
  395. HANDLE events[2] = { _stop_event,_render_event };
  396. HRESULT res = S_OK;
  397. uint8_t *pData = NULL;
  398. uint32_t padding_count = 0;
  399. while (_running &&
  400. WaitForMultipleObjects(2, events, FALSE, INFINITE) != WAIT_OBJECT_0
  401. ) {
  402. res = _render_client->GetCurrentPadding(&padding_count);
  403. if (FAILED(res)) {
  404. break;
  405. }
  406. if (padding_count == _render_sample_count) {
  407. if (_on_error) _on_error(AE_CO_PADDING_UNEXPECTED, _cb_extra_index);
  408. break;
  409. }
  410. res = _render->GetBuffer(_render_sample_count - padding_count, &pData);
  411. if (FAILED(res)) {
  412. if (_on_error) _on_error(AE_CO_GET_BUFFER_FAILED, _cb_extra_index);
  413. break;
  414. }
  415. res = _render->ReleaseBuffer(_render_sample_count - padding_count, AUDCLNT_BUFFERFLAGS_SILENT);
  416. if (FAILED(res)) {
  417. if (_on_error) _on_error(AE_CO_RELEASE_BUFFER_FAILED, _cb_extra_index);
  418. break;
  419. }
  420. }
  421. }
  422. void record_audio_wasapi::record_loop()
  423. {
  424. AVFrame *frame = av_frame_alloc();
  425. HANDLE events[2] = { _stop_event,_ready_event };
  426. // while,sys will never not signal this ready_event in windows7,
  427. // and only signal when something is rendring,so we just wait 10ms for speaker
  428. DWORD dur = _is_input ? INFINITE : 10;
  429. int error = AE_NO;
  430. while (_running)
  431. {
  432. if (WaitForMultipleObjects(2, events, FALSE, dur) == WAIT_OBJECT_0)
  433. break;
  434. if ((error = do_record(frame)) != AE_NO) {
  435. if (_on_error) _on_error(error, _cb_extra_index);
  436. break;
  437. }
  438. }//while(_running)
  439. av_frame_free(&frame);
  440. }
  441. void record_audio_wasapi::clean_wasapi()
  442. {
  443. if (_wfex)
  444. CoTaskMemFree(_wfex);
  445. _wfex = NULL;
  446. if (_enumerator)
  447. _enumerator->Release();
  448. _enumerator = nullptr;
  449. if (_device)
  450. _device->Release();
  451. _device = nullptr;
  452. if (_capture_client)
  453. _capture_client->Release();
  454. _capture_client = nullptr;
  455. if (_render_client)
  456. _render_client->Release();
  457. _render_client = nullptr;
  458. if (_capture)
  459. _capture->Release();
  460. _capture = nullptr;
  461. if (_render)
  462. _render->Release();
  463. _render = nullptr;
  464. if (_ready_event)
  465. CloseHandle(_ready_event);
  466. _ready_event = NULL;
  467. if (_stop_event)
  468. CloseHandle(_stop_event);
  469. _stop_event = NULL;
  470. if (_render_event)
  471. CloseHandle(_render_event);
  472. _render_event = NULL;
  473. if(_co_inited == true)
  474. CoUninitialize();
  475. _co_inited = false;
  476. _inited = false;
  477. }
  478. }