| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610 |
- #include "record_audio_wasapi.h"
- #include "headers_ffmpeg.h"
- #include <string>
- #include "error_define.h"
- #include "system_error.h"
- #include "utils_string.h"
- #include "log_helper.h"
- #ifdef _WIN32
- #define NS_PER_SEC 1000000000
- #define REFTIMES_PER_SEC NS_PER_SEC / 100 //100ns per buffer unit
- #endif // _WIN32
- namespace am {
- record_audio_wasapi::record_audio_wasapi()
- {
- _co_inited = false;
- HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
- DWORD err = GetLastError();
- if (hr != S_OK && err != S_OK)
- al_error("%s,error:%s", err2str(AE_CO_INITED_FAILED), system_error::error2str(err).c_str());
- _co_inited = (hr == S_OK || hr == S_FALSE); //if already initialize will return S_FALSE
- _is_default = false;
- _wfex = NULL;
- _enumerator = nullptr;
- _device = nullptr;
- _capture_client = nullptr;
- _capture = nullptr;
- _render = nullptr;
- _render_client = nullptr;
- _capture_sample_count = 0;
- _render_sample_count = 0;
- _ready_event = NULL;
- _stop_event = NULL;
- _render_event = NULL;
- _use_device_ts = false;
- _start_time = 0;
- }
- record_audio_wasapi::~record_audio_wasapi()
- {
- stop();
- clean_wasapi();
- if (_co_inited == true)
- CoUninitialize();
- }
- void get_device_info(IMMDevice *device)
- {
- HRESULT resSample;
- IPropertyStore *store = nullptr;
- PWAVEFORMATEX deviceFormatProperties;
- PROPVARIANT prop;
- resSample = device->OpenPropertyStore(STGM_READ, &store);
- if (!FAILED(resSample)) {
- resSample = store->GetValue(PKEY_AudioEngine_DeviceFormat, &prop);
- if (!FAILED(resSample)) {
- deviceFormatProperties = (PWAVEFORMATEX) prop.blob.pBlobData;
- std::string device_sample = std::to_string(deviceFormatProperties->nSamplesPerSec);
- }
- }
- }
- #define KSAUDIO_SPEAKER_2POINT1 (KSAUDIO_SPEAKER_STEREO | SPEAKER_LOW_FREQUENCY)
- #define OBS_KSAUDIO_SPEAKER_4POINT1 (KSAUDIO_SPEAKER_SURROUND | SPEAKER_LOW_FREQUENCY)
- int64_t record_audio_wasapi::convert_layout(DWORD layout, WORD channels)
- {
- switch (layout) {
- case KSAUDIO_SPEAKER_2POINT1:
- return AV_CH_LAYOUT_SURROUND;
- case KSAUDIO_SPEAKER_SURROUND:
- return AV_CH_LAYOUT_4POINT0;
- case OBS_KSAUDIO_SPEAKER_4POINT1:
- return AV_CH_LAYOUT_4POINT1;
- case KSAUDIO_SPEAKER_5POINT1_SURROUND:
- return AV_CH_LAYOUT_5POINT1_BACK;
- case KSAUDIO_SPEAKER_7POINT1_SURROUND:
- return AV_CH_LAYOUT_7POINT1;
- }
- return ffmpeg_get_default_channel_layout(channels);
- }
- void record_audio_wasapi::init_format(WAVEFORMATEX *wfex)
- {
- DWORD layout = 0;
- if (wfex->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
- WAVEFORMATEXTENSIBLE *ext = (WAVEFORMATEXTENSIBLE *) wfex;
- layout = ext->dwChannelMask;
- }
- _channel_layout = convert_layout(layout, wfex->nChannels);
- _sample_rate = wfex->nSamplesPerSec;
- _bit_rate = wfex->nAvgBytesPerSec;
- _bit_per_sample = wfex->wBitsPerSample;
- _channel_num = wfex->nChannels;
- //wasapi is always flt
- _fmt = AV_SAMPLE_FMT_FLT;
- }
- int record_audio_wasapi::init_render()
- {
- int error = AE_NO;
- HRESULT res = S_OK;
- do {
- res = _device->Activate(__uuidof(IAudioClient),
- CLSCTX_ALL,
- nullptr,
- (void **) &_render_client);
- if (FAILED(res)) {
- error = AE_CO_ACTIVE_DEVICE_FAILED;
- break;
- }
- WAVEFORMATEX *wfex;
- res = _render_client->GetMixFormat(&wfex);
- if (FAILED(res)) {
- error = AE_CO_GET_FORMAT_FAILED;
- break;
- }
- res = _render_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
- AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
- REFTIMES_PER_SEC,
- 0,
- wfex,
- nullptr);
- CoTaskMemFree(wfex);
- if (FAILED(res)) {
- error = AE_CO_AUDIOCLIENT_INIT_FAILED;
- break;
- }
- /* Silent loopback fix. Prevents audio stream from stopping and */
- /* messing up timestamps and other weird glitches during silence */
- /* by playing a silent sample all over again. */
- res = _render_client->GetService(__uuidof(IAudioRenderClient), (void **) &_render);
- if (FAILED(res)) {
- error = AE_CO_GET_CAPTURE_FAILED;
- break;
- }
- _render_event = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (!_render_event) {
- error = AE_CO_CREATE_EVENT_FAILED;
- break;
- }
- res = _render_client->SetEventHandle(_render_event);
- if (FAILED(res)) {
- error = AE_CO_SET_EVENT_FAILED;
- break;
- }
- //pre fill a single silent buffer
- res = _render_client->GetBufferSize(&_render_sample_count);
- if (FAILED(res)) {
- error = AE_CO_GET_VALUE_FAILED;
- break;
- }
- uint8_t *buffer = NULL;
- res = _render->GetBuffer(_render_sample_count, &buffer);
- if (FAILED(res)) {
- error = AE_CO_GET_VALUE_FAILED;
- break;
- }
- res = _render->ReleaseBuffer(_render_sample_count, AUDCLNT_BUFFERFLAGS_SILENT);
- if (FAILED(res)) {
- error = AE_CO_RELEASE_BUFFER_FAILED;
- break;
- }
- } while (0);
- if (error != AE_NO)
- al_error("init render failed(%ld), %s,lasterror:%lu", res, err2str(error), GetLastError());
- return error;
- }
- int record_audio_wasapi::init(const std::string &device_name,
- const std::string &device_id,
- bool is_input)
- {
- int error = AE_NO;
- HRESULT hr = S_OK;
- al_info("wasapi start to initialize in %s mode with: %s",
- is_input ? "input" : "output",
- device_name.c_str());
- if (_co_inited == false) {
- return AE_CO_INITED_FAILED;
- }
- if (_inited == true) {
- return AE_NO;
- }
- _device_name = device_name;
- _device_id = device_id;
- _is_input = is_input;
- _is_default = (utils_string::ascii_utf8(DEFAULT_AUDIO_INOUTPUT_ID).compare(_device_id) == 0);
- do {
- hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
- NULL,
- CLSCTX_ALL,
- __uuidof(IMMDeviceEnumerator),
- (void **) &_enumerator);
- if (FAILED(hr)) {
- error = AE_CO_CREATE_FAILED;
- break;
- }
- if (_is_default) {
- hr = _enumerator->GetDefaultAudioEndpoint(is_input ? eCapture : eRender,
- is_input ? eCommunications : eConsole,
- &_device);
- } else {
- hr = _enumerator->GetDevice(utils_string::utf8_unicode(_device_id).c_str(), &_device);
- }
- if (hr != S_OK) {
- error = AE_CO_GETENDPOINT_FAILED;
- break;
- }
- get_device_info(_device);
- hr = _device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void **) &_capture_client);
- if (hr != S_OK) {
- error = AE_CO_ACTIVE_DEVICE_FAILED;
- break;
- }
- hr = _capture_client->GetMixFormat(&_wfex);
- if (hr != S_OK) {
- error = AE_CO_GET_FORMAT_FAILED;
- break;
- }
- init_format(_wfex);
- DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
- if (_is_input == false)
- flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
- hr = _capture_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
- flags,
- REFTIMES_PER_SEC,
- 0,
- _wfex,
- NULL);
- // AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED
- // https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize
- if (hr != S_OK) {
- error = AE_CO_AUDIOCLIENT_INIT_FAILED;
- break;
- }
- //For ouotput mode,ready event will not signal when there is nothing rendering
- //We run a render thread and render silent pcm data all time
- if (!_is_input) {
- error = init_render();
- if (error != AE_NO)
- break;
- }
- hr = _capture_client->GetBufferSize(&_capture_sample_count);
- if (hr != S_OK) {
- error = AE_CO_GET_VALUE_FAILED;
- break;
- }
- hr = _capture_client->GetService(__uuidof(IAudioCaptureClient), (void **) &_capture);
- if (hr != S_OK) {
- error = AE_CO_GET_CAPTURE_FAILED;
- break;
- }
- _ready_event = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (!_ready_event) {
- error = AE_CO_CREATE_EVENT_FAILED;
- break;
- }
- _stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
- if (!_stop_event) {
- error = AE_CO_CREATE_EVENT_FAILED;
- break;
- }
- hr = _capture_client->SetEventHandle(_ready_event);
- if (hr != S_OK) {
- error = AE_CO_SET_EVENT_FAILED;
- break;
- }
- _inited = true;
- } while (0);
- if (error != AE_NO) {
- al_error("wasapi initialize failed,%s,error:%lu,hr:%lld",
- err2str(error),
- GetLastError(),
- hr);
- clean_wasapi();
- }
- return error;
- }
- int record_audio_wasapi::start()
- {
- if (_running == true) {
- al_warn("audio record is already running");
- return AE_NO;
- }
- if (_inited == false)
- return AE_NEED_INIT;
- HRESULT hr = S_OK;
- if (!_is_input) {
- hr = _render_client->Start();
- if (FAILED(hr)) {
- al_error("%s,error:%lu", err2str(AE_CO_START_FAILED), GetLastError());
- return AE_CO_START_FAILED;
- }
- }
- hr = _capture_client->Start();
- if (hr != S_OK) {
- al_error("%s,error:%lu", err2str(AE_CO_START_FAILED), GetLastError());
- return AE_CO_START_FAILED;
- }
- _start_time = av_gettime_relative();
- _running = true;
- if (!_is_input) {
- _render_thread = std::thread(std::bind(&record_audio_wasapi::render_loop, this));
- }
- _thread = std::thread(std::bind(&record_audio_wasapi::record_loop, this));
- return AE_NO;
- }
- int record_audio_wasapi::pause()
- {
- _paused = true;
- return AE_NO;
- }
- int record_audio_wasapi::resume()
- {
- _paused = false;
- return AE_NO;
- }
- int record_audio_wasapi::stop()
- {
- _running = false;
- SetEvent(_stop_event);
- if (_render_thread.joinable())
- _render_thread.join();
- if (_thread.joinable())
- _thread.join();
- if (_capture_client)
- _capture_client->Stop();
- if (_render_client)
- _render_client->Stop();
- return AE_NO;
- }
- const AVRational record_audio_wasapi::get_time_base()
- {
- if (_use_device_ts)
- return {1, NS_PER_SEC};
- else
- return {1, AV_TIME_BASE};
- }
- int64_t record_audio_wasapi::get_start_time()
- {
- return _start_time;
- }
- void record_audio_wasapi::process_data(AVFrame *frame,
- uint8_t *data,
- uint32_t sample_count,
- uint64_t device_ts)
- {
- int sample_size = _bit_per_sample / 8 * _channel_num;
- //wasapi time unit is 100ns,so time base is NS_PER_SEC
- frame->pts = _use_device_ts ? device_ts * 100 : av_gettime_relative();
- if (_use_device_ts == false) {
- frame->pts -= (int64_t) sample_count * NS_PER_SEC / (int64_t) _sample_rate / 100;
- }
- frame->pkt_dts = frame->pts;
- frame->nb_samples = sample_count;
- frame->format = _fmt;
- frame->sample_rate = _sample_rate;
- ffmpeg_set_frame_channels(frame, _channel_num);
- frame->pkt_size = sample_count * sample_size;
- av_samples_fill_arrays(frame->data, frame->linesize, data, _channel_num, sample_count, _fmt, 1);
- if (_on_data)
- _on_data(frame, _cb_extra_index);
- }
- int record_audio_wasapi::do_record(AVFrame *frame)
- {
- HRESULT res = S_OK;
- LPBYTE buffer = NULL;
- DWORD flags = 0;
- uint32_t sample_count = 0;
- UINT64 pos, ts;
- int error = AE_NO;
- while (_running) {
- res = _capture->GetNextPacketSize(&sample_count);
- if (FAILED(res)) {
- if (res != AUDCLNT_E_DEVICE_INVALIDATED)
- al_error("GetNextPacketSize failed: %lX", res);
- error = AE_CO_GET_PACKET_FAILED;
- break;
- }
- if (!sample_count)
- break;
- buffer = NULL;
- res = _capture->GetBuffer(&buffer, &sample_count, &flags, &pos, &ts);
- if (FAILED(res)) {
- if (res != AUDCLNT_E_DEVICE_INVALIDATED)
- al_error("GetBuffer failed: %lX", res);
- error = AE_CO_GET_BUFFER_FAILED;
- break;
- }
- //input mode do not have silent data flag do nothing here
- if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
- //al_warn("on slient data %d", sample_count);
- }
- if (buffer) {
- process_data(frame, buffer, sample_count, ts);
- } else {
- al_error("buffer invalid is");
- }
- _capture->ReleaseBuffer(sample_count);
- }
- return error;
- }
- void record_audio_wasapi::render_loop()
- {
- HANDLE events[2] = {_stop_event, _render_event};
- HRESULT res = S_OK;
- uint8_t *pData = NULL;
- uint32_t padding_count = 0;
- while (_running && WaitForMultipleObjects(2, events, FALSE, INFINITE) != WAIT_OBJECT_0) {
- res = _render_client->GetCurrentPadding(&padding_count);
- if (FAILED(res)) {
- break;
- }
- if (padding_count == _render_sample_count) {
- if (_on_error)
- _on_error(AE_CO_PADDING_UNEXPECTED, _cb_extra_index);
- break;
- }
- res = _render->GetBuffer(_render_sample_count - padding_count, &pData);
- if (FAILED(res)) {
- if (_on_error)
- _on_error(AE_CO_GET_BUFFER_FAILED, _cb_extra_index);
- break;
- }
- res = _render->ReleaseBuffer(_render_sample_count - padding_count,
- AUDCLNT_BUFFERFLAGS_SILENT);
- if (FAILED(res)) {
- if (_on_error)
- _on_error(AE_CO_RELEASE_BUFFER_FAILED, _cb_extra_index);
- break;
- }
- }
- }
- void record_audio_wasapi::record_loop()
- {
- AVFrame *frame = av_frame_alloc();
- HANDLE events[2] = {_stop_event, _ready_event};
- // while,sys will never not signal this ready_event in windows7,
- // and only signal when something is rendring,so we just wait 10ms for speaker
- DWORD dur = _is_input ? INFINITE : 10;
- int error = AE_NO;
- while (_running) {
- if (WaitForMultipleObjects(2, events, FALSE, dur) == WAIT_OBJECT_0)
- break;
- if ((error = do_record(frame)) != AE_NO) {
- if (_on_error)
- _on_error(error, _cb_extra_index);
- break;
- }
- } //while(_running)
- av_frame_free(&frame);
- }
- void record_audio_wasapi::clean_wasapi()
- {
- if (_wfex)
- CoTaskMemFree(_wfex);
- _wfex = NULL;
- if (_enumerator)
- _enumerator->Release();
- _enumerator = nullptr;
- if (_device)
- _device->Release();
- _device = nullptr;
- if (_capture_client)
- _capture_client->Release();
- _capture_client = nullptr;
- if (_render_client)
- _render_client->Release();
- _render_client = nullptr;
- if (_capture)
- _capture->Release();
- _capture = nullptr;
- if (_render)
- _render->Release();
- _render = nullptr;
- if (_ready_event)
- CloseHandle(_ready_event);
- _ready_event = NULL;
- if (_stop_event)
- CloseHandle(_stop_event);
- _stop_event = NULL;
- if (_render_event)
- CloseHandle(_render_event);
- _render_event = NULL;
- if (_co_inited == true)
- CoUninitialize();
- _co_inited = false;
- _inited = false;
- }
- } // namespace am
|