record_audio_wasapi.cpp 16 KB

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