/* * Copyright (c) 2010-2025 Belledonne Communications SARL. * * This file is part of mswasapi library - modular sound and video processing and streaming Windows Audio Session API * sound card plugin for mediastreamer2 (see https://gitlab.linphone.org/BC/public/mswasapi). * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "mswasapi_reader.h" #include "mediastreamer2/mscommon.h" #include "mediastreamer2/msticker.h" #include "utils.h" #define REFTIME_250MS 2500000 static const int minBufferDurationMs = 200; // ms #ifdef MS2_WINDOWS_UNIVERSAL bool MSWASAPIReader::smInstantiated = false; #endif MSWASAPIReader::MSWASAPIReader(MSFilter *filter) : MSWasapi(filter, MS_SND_CARD_CAP_CAPTURE), mAudioCaptureClient(NULL), mAudioClock(NULL) { mTickerSynchronizer = ms_ticker_synchronizer_new(); } MSWASAPIReader::~MSWASAPIReader() { ms_ticker_synchronizer_destroy(mTickerSynchronizer); } int MSWASAPIReader::activate() { int activated = MSWasapi::activate(true, (void **)&mAudioCaptureClient); if (!activated && !mAudioClock) { auto result = mAudioClient->GetService(IID_IAudioClock, (void **)&mAudioClock); REPORT_ERROR_NOGOTO( "mswasapi: Could not get monitor monitor stream's data rate and the current position for the " "input interface [%x]", result); } return activated; } int MSWASAPIReader::deactivate() { SAFE_RELEASE(mAudioClock); SAFE_RELEASE(mAudioCaptureClient) return MSWasapi::deactivate(); } void MSWASAPIReader::start() { MSWasapi::start(); if (mIsStarted) { mSampleTime = (UINT64)-1; ms_ticker_set_synchronizer(mFilter->ticker, mTickerSynchronizer); } } void MSWASAPIReader::stop() { MSWasapi::stop(); ms_ticker_set_synchronizer(mFilter->ticker, nullptr); } int MSWASAPIReader::feed(MSFilter *f) { HRESULT result; DWORD flags; BYTE *pData; UINT32 numFramesAvailable = 0; UINT32 numFramesInNextPacket = 0; UINT64 bufferDevicePosition = (UINT64)-1; // Coming from GetBuffer UINT64 deviceFrequency = 0; UINT64 realDevicePosition = (UINT64)-1; // Coming from AudioClock UINT64 finalDevicePosition = (UINT64)-1; // AudioClock (best precision, based on hardware capability) > Buffer // position (less reliable) > manual counting (in case of error) mblk_t *m; int bytesPerFrame = mNBlockAlign; if (isStarted()) { result = mAudioCaptureClient->GetNextPacketSize(&numFramesInNextPacket); while (numFramesInNextPacket != 0) { REPORT_ERROR("mswasapi: Could not get next packet size for the MSWASAPI audio input interface [0x%X]", result); result = mAudioCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, &bufferDevicePosition, NULL); REPORT_ERROR("mswasapi: Could not get buffer from the MSWASAPI audio input interface [0x%X]", result); if (numFramesAvailable > 0) { finalDevicePosition = bufferDevicePosition; // Ticker has been desynchronized either by not setting the device position, or by being erroneous. // It's supposed that when having an error, the position from Clock/GetBuffer cannot be reliable. // Frame counting is done since then. if (mSampleTime != (UINT64)-1) { mSampleTime += numFramesAvailable; finalDevicePosition = mSampleTime; } else { bool_t haveDevicePosition = FALSE; if (mAudioClock) { // Frequency and Position must be get from Audio Clock both because they are meaning only // between them: The device position from Buffer() can be decorrelated from the AudioClock. result = mAudioClock->GetFrequency(&deviceFrequency); if (result == S_OK && deviceFrequency > 0) { result = mAudioClock->GetPosition(&realDevicePosition, NULL); if (result == S_OK) { finalDevicePosition = (UINT64)(realDevicePosition * mCurrentRate / (long double)deviceFrequency); haveDevicePosition = TRUE; } } } if (!haveDevicePosition && ((flags & AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR) || bufferDevicePosition == (UINT64)-1)) { ms_warning("mswasapi: Timestamp is erroneous. Switch to frame counting."); mSampleTime = numFramesAvailable; finalDevicePosition = mSampleTime; ms_ticker_synchronizer_resync(mTickerSynchronizer); } } m = allocb(numFramesAvailable * bytesPerFrame, 0); if (m == NULL) { ms_error("mswasapi: Could not allocate memory for the captured data from the MSWASAPI audio input " "interface"); goto error; } if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { memset(m->b_wptr, 0, numFramesAvailable * bytesPerFrame); } else { memcpy(m->b_wptr, pData, numFramesAvailable * bytesPerFrame); } result = mAudioCaptureClient->ReleaseBuffer(numFramesAvailable); REPORT_ERROR("mswasapi: Could not release buffer of the MSWASAPI audio input interface [0x%X]", result); m->b_wptr += numFramesAvailable * bytesPerFrame; ms_queue_put(f->outputs[0], m); ms_ticker_synchronizer_update(mTickerSynchronizer, finalDevicePosition, (unsigned int)mCurrentRate); result = mAudioCaptureClient->GetNextPacketSize(&numFramesInNextPacket); } } } else { silence(f); } return 0; error: return -1; } void MSWASAPIReader::silence(MSFilter *f) { mblk_t *om; unsigned int bufsize; unsigned int nsamples; nsamples = (f->ticker->interval * mTargetRate) / 1000; bufsize = nsamples * mTargetNChannels * 2; om = allocb(bufsize, 0); memset(om->b_wptr, 0, bufsize); om->b_wptr += bufsize; ms_queue_put(f->outputs[0], om); } #ifndef MS2_WINDOWS_PHONE // Called from main mswasapi. MSWASAPIReaderPtr MSWASAPIReaderNew(MSFilter *f) { MSWASAPIReaderPtr w = new MSWASAPIReaderWrapper(); #ifdef MS2_WINDOWS_UNIVERSAL w->reader = Make(f); #else w->reader = new MSWASAPIReader(f); #endif return w; } void MSWASAPIReaderDelete(MSWASAPIReaderPtr ptr) { #ifdef MS2_WINDOWS_UNIVERSAL ptr->reader->setInstantiated(false); #endif ptr->reader->unregister(); ptr->reader->Release(); ptr->reader = nullptr; delete ptr; } #else MSWASAPIReaderPtr MSWASAPIReaderNew(MSFilter *f) { return (MSWASAPIReaderPtr) new MSWASAPIReader(f); } void MSWASAPIReaderDelete(MSWASAPIReaderPtr ptr) { ptr->reader->unregister(); delete ptr; } #endif