record_audio_wasapi.cpp 16 KB

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