Skip to content

Commit

Permalink
Merge pull request #712 from drowe67/ms-win-audio-error
Browse files Browse the repository at this point in the history
Revert PR #689 due to Windows audio problems.
  • Loading branch information
tmiw authored Apr 13, 2024
2 parents 7b24817 + 0964b2b commit 5898c82
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 115 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ endif()
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.11" CACHE STRING "Minimum OS X deployment version")

set(PROJECT_NAME FreeDV)
set(PROJECT_VERSION 1.9.9)
set(PROJECT_VERSION 1.9.9.1)
set(PROJECT_DESCRIPTION "HF Digital Voice for Radio Amateurs")
set(PROJECT_HOMEPAGE_URL "https://freedv.org")

Expand Down Expand Up @@ -417,7 +417,7 @@ if(NOT USE_STATIC_PORTAUDIO AND NOT USE_PULSEAUDIO)
message(FATAL_ERROR "portaudio library not found.
On Linux systems try installing:
portaudio-devel (RPM based systems)
libportaudio-dev (DEB based systems)
portaudio19-dev (DEB based systems)
On Windows it's easiest to use the cmake option: USE_STATIC_PORTAUDIO"
)
endif()
Expand Down
5 changes: 5 additions & 0 deletions USER_MANUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,11 @@ LDPC | Low Density Parity Check Codes - a family of powerful FEC codes

# Release Notes

## V1.9.9.1 April 2024

1. Bugfixes:
* Revert PR #689 and reimplement fix for original startup delay issue. (PR #712)

## V1.9.9 April 2024

1. Bugfixes:
Expand Down
7 changes: 6 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,16 @@ endif(APPLE)

# Custom commands to build OSX images.
if(APPLE)
if(NOT LPCNET_DISABLE)
set(LPCNET_DYLIBBUNDLER_PATH ${LPCNET_BUILD_DIR}/src:)
set(LPCNET_DYLIBBUNDLER_ARG -s ${LPCNET_BUILD_DIR}/src)
endif(NOT LPCNET_DISABLE)

add_custom_command(
TARGET FreeDV
POST_BUILD
COMMAND rm -rf dist_tmp FreeDV.dmg || true
COMMAND DYLD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src:${LPCNET_BUILD_DIR}/src:${portaudio_BINARY_DIR}:${samplerate_BINARY_DIR}/src:${DYLD_LIBRARY_PATH} ${CMAKE_SOURCE_DIR}/macdylibbundler/dylibbundler ARGS -od -b -x FreeDV.app/Contents/MacOS/FreeDV -d FreeDV.app/Contents/libs -p @loader_path/../libs/ -i /usr/lib -s ${LPCNET_BUILD_DIR}/src -s ${CODEC2_BUILD_DIR}/src -s ${CMAKE_BINARY_DIR}/LPCNet_build/src -s ${CMAKE_BINARY_DIR}/codec2_build/src -s ${portaudio_BINARY_DIR} -s ${samplerate_BINARY_DIR}/src
COMMAND DYLD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src:${LPCNET_DYLIBBUNDLER_PATH}${portaudio_BINARY_DIR}:${samplerate_BINARY_DIR}/src:${DYLD_LIBRARY_PATH} ${CMAKE_SOURCE_DIR}/macdylibbundler/dylibbundler ARGS -od -b -x FreeDV.app/Contents/MacOS/FreeDV -d FreeDV.app/Contents/libs -p @loader_path/../libs/ -i /usr/lib ${LPCNET_DYLIBBUNDLER_ARG} -s ${CODEC2_BUILD_DIR}/src -s ${CMAKE_BINARY_DIR}/LPCNet_build/src -s ${CMAKE_BINARY_DIR}/codec2_build/src -s ${portaudio_BINARY_DIR} -s ${samplerate_BINARY_DIR}/src
COMMAND cp ARGS ${CMAKE_CURRENT_SOURCE_DIR}/freedv.icns FreeDV.app/Contents/Resources
COMMAND codesign --force --options runtime --timestamp --entitlements ${CMAKE_CURRENT_SOURCE_DIR}/entitlements.plist --sign - ${CMAKE_CURRENT_BINARY_DIR}/FreeDV.app
COMMAND mkdir dist_tmp
Expand Down
136 changes: 55 additions & 81 deletions src/audio/PortAudioEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@
//=========================================================================

#include <sstream>
#include <iomanip>
#include <mutex>
#include <condition_variable>

#include <wx/string.h>
#include "portaudio.h"
#include "PortAudioDevice.h"
Expand Down Expand Up @@ -65,66 +61,17 @@ void PortAudioEngine::start()
}
else
{
if (cachePopulationThread_.joinable())
{
cachePopulationThread_.join();
}

std::mutex theadStartMtx;
std::condition_variable threadStartCV;
std::unique_lock<std::mutex> threadStartLock(theadStartMtx);

cachePopulationThread_ = std::thread([&]() {
std::unique_lock<std::recursive_mutex> lock(deviceCacheMutex_);

{
std::unique_lock<std::mutex> innerThreadStartLock(theadStartMtx);
threadStartCV.notify_one();
}

deviceListCache_[AUDIO_ENGINE_IN] = getAudioDeviceList_(AUDIO_ENGINE_IN);
deviceListCache_[AUDIO_ENGINE_OUT] = getAudioDeviceList_(AUDIO_ENGINE_OUT);
});

threadStartCV.wait(threadStartLock);

initialized_ = true;
}
}

void PortAudioEngine::stop()
{
if (cachePopulationThread_.joinable())
{
cachePopulationThread_.join();
}

Pa_Terminate();

initialized_ = false;
deviceListCache_.clear();
sampleRateList_.clear();
}

std::vector<AudioDeviceSpecification> PortAudioEngine::getAudioDeviceList(AudioDirection direction)
{
std::unique_lock<std::recursive_mutex> lock(deviceCacheMutex_);
return deviceListCache_[direction];
}

std::vector<int> PortAudioEngine::getSupportedSampleRates(wxString deviceName, AudioDirection direction)
{
std::unique_lock<std::recursive_mutex> lock(deviceCacheMutex_);

auto key = std::pair<wxString, AudioDirection>(deviceName, direction);
if (sampleRateList_.count(key) == 0)
{
sampleRateList_[key] = getSupportedSampleRates_(deviceName, direction);
}
return sampleRateList_[key];
}

std::vector<AudioDeviceSpecification> PortAudioEngine::getAudioDeviceList_(AudioDirection direction)
{
int numDevices = Pa_GetDeviceCount();
std::vector<AudioDeviceSpecification> result;
Expand Down Expand Up @@ -155,27 +102,17 @@ std::vector<AudioDeviceSpecification> PortAudioEngine::getAudioDeviceList_(Audio
// on Windows.
PaStreamParameters streamParameters;
streamParameters.device = index;
streamParameters.channelCount = 1;
streamParameters.sampleFormat = paInt16;
streamParameters.suggestedLatency = Pa_GetDeviceInfo(index)->defaultHighInputLatency;
streamParameters.hostApiSpecificStreamInfo = NULL;
streamParameters.channelCount = 1;

// On Linux, the below logic causes the device lookup process to take MUCH
// longer than it does on other platforms, mainly because of the special devices
// it provides to PortAudio. For these, we're just going to assume the minimum
// valid channels is 1.
// On Linux, the below logic causes the device lookup process to take MUCH
// longer than it does on other platforms, mainly because of the special devices
// it provides to PortAudio. For these, we're just going to assume the minimum
// valid channels is 1.
int maxChannels = direction == AUDIO_ENGINE_IN ? deviceInfo->maxInputChannels : deviceInfo->maxOutputChannels;
bool isDeviceWithKnownMinimum =
!strcmp(deviceInfo->name, "sysdefault") ||
!strcmp(deviceInfo->name, "front") ||
!strcmp(deviceInfo->name, "surround") ||
!strcmp(deviceInfo->name, "samplerate") ||
!strcmp(deviceInfo->name, "speexrate") ||
!strcmp(deviceInfo->name, "pulse") ||
!strcmp(deviceInfo->name, "upmix") ||
!strcmp(deviceInfo->name, "vdownmix") ||
!strcmp(deviceInfo->name, "dmix") ||
!strcmp(deviceInfo->name, "default");
bool isDeviceWithKnownMinimum = IsDeviceWhitelisted_(deviceInfo->name);
if (!isDeviceWithKnownMinimum)
{
while (streamParameters.channelCount < maxChannels)
Expand All @@ -185,7 +122,7 @@ std::vector<AudioDeviceSpecification> PortAudioEngine::getAudioDeviceList_(Audio
direction == AUDIO_ENGINE_OUT ? &streamParameters : NULL,
deviceInfo->defaultSampleRate);

if (err != paFormatIsSupported)
if (err == paFormatIsSupported)
{
break;
}
Expand All @@ -199,8 +136,18 @@ std::vector<AudioDeviceSpecification> PortAudioEngine::getAudioDeviceList_(Audio
device.deviceId = index;
device.name = wxString::FromUTF8(deviceInfo->name);
device.apiName = hostApiName;
device.minChannels = streamParameters.channelCount;
device.maxChannels = maxChannels;

// For "whitelisted" devices, also assume channel counts
if (IsDeviceWhitelisted_(deviceInfo->name))
{
device.maxChannels = 2;
}
else
{
device.minChannels = streamParameters.channelCount;
device.maxChannels =
direction == AUDIO_ENGINE_IN ? deviceInfo->maxInputChannels : deviceInfo->maxOutputChannels;
}
device.defaultSampleRate = deviceInfo->defaultSampleRate;

result.push_back(device);
Expand All @@ -210,7 +157,7 @@ std::vector<AudioDeviceSpecification> PortAudioEngine::getAudioDeviceList_(Audio
return result;
}

std::vector<int> PortAudioEngine::getSupportedSampleRates_(wxString deviceName, AudioDirection direction)
std::vector<int> PortAudioEngine::getSupportedSampleRates(wxString deviceName, AudioDirection direction)
{
std::vector<int> result;
auto devInfo = getAudioDeviceList(direction);
Expand All @@ -230,20 +177,32 @@ std::vector<int> PortAudioEngine::getSupportedSampleRates_(wxString deviceName,
int rateIndex = 0;
while (IAudioEngine::StandardSampleRates[rateIndex] != -1)
{
PaError err = Pa_IsFormatSupported(
direction == AUDIO_ENGINE_IN ? &streamParameters : NULL,
direction == AUDIO_ENGINE_OUT ? &streamParameters : NULL,
IAudioEngine::StandardSampleRates[rateIndex]);

PaError err = paFormatIsSupported;

bool isDeviceWithKnownMinimum = IsDeviceWhitelisted_(deviceName);
if (!isDeviceWithKnownMinimum)
{
err = Pa_IsFormatSupported(
direction == AUDIO_ENGINE_IN ? &streamParameters : NULL,
direction == AUDIO_ENGINE_OUT ? &streamParameters : NULL,
IAudioEngine::StandardSampleRates[rateIndex]);
}

if (err == paFormatIsSupported)
{
result.push_back(IAudioEngine::StandardSampleRates[rateIndex]);
}

rateIndex++;
}

break;

// If we can't find a supported sample rate, just assume that the
// default sample rate is supported. If that can't actually be used,
// we can deal with it later.
if (result.size() == 0)
{
result.push_back(device.defaultSampleRate);
}
}
}

Expand Down Expand Up @@ -294,7 +253,7 @@ std::shared_ptr<IAudioDevice> PortAudioEngine::getAudioDevice(wxString deviceNam

for (auto& dev : deviceList)
{
if (dev.name.Find(deviceName) == 0)
if (dev.name.IsSameAs(deviceName))
{
// Ensure that the passed-in number of channels is within the allowed range.
numChannels = std::max(numChannels, dev.minChannels);
Expand All @@ -308,3 +267,18 @@ std::shared_ptr<IAudioDevice> PortAudioEngine::getAudioDevice(wxString deviceNam

return nullptr;
}

bool PortAudioEngine::IsDeviceWhitelisted_(const char* devName)
{
return
!strcmp(devName, "sysdefault") ||
!strcmp(devName, "front") ||
!strcmp(devName, "surround") ||
!strcmp(devName, "samplerate") ||
!strcmp(devName, "speexrate") ||
!strcmp(devName, "pulse") ||
!strcmp(devName, "upmix") ||
!strcmp(devName, "vdownmix") ||
!strcmp(devName, "dmix") ||
!strcmp(devName, "default");
}
12 changes: 1 addition & 11 deletions src/audio/PortAudioEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@
#ifndef PORT_AUDIO_ENGINE_H
#define PORT_AUDIO_ENGINE_H

#include <map>
#include <thread>
#include <mutex>
#include "IAudioEngine.h"

class PortAudioEngine : public IAudioEngine
Expand All @@ -44,14 +41,7 @@ class PortAudioEngine : public IAudioEngine
private:
bool initialized_;

// Device cache and associated management.
std::map<AudioDirection, std::vector<AudioDeviceSpecification> > deviceListCache_;
std::map<std::pair<wxString, AudioDirection>, std::vector<int> > sampleRateList_;
std::recursive_mutex deviceCacheMutex_;
std::thread cachePopulationThread_;

std::vector<AudioDeviceSpecification> getAudioDeviceList_(AudioDirection direction);
std::vector<int> getSupportedSampleRates_(wxString deviceName, AudioDirection direction);
static bool IsDeviceWhitelisted_(const char* devName);
};

#endif // PORT_AUDIO_ENGINE_H
27 changes: 24 additions & 3 deletions src/gui/dialogs/dlg_audiooptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ extern wxConfigBase *pConfig;
void AudioOptsDialog::audioEngineInit(void)
{
m_isPaInitialized = true;

auto engine = AudioEngineFactory::GetAudioEngine();
engine->setOnEngineError([this](IAudioEngine&, std::string error, void*)
{
CallAfter([&]() {
wxMessageBox(wxT("Sound engine failed to initialize"), wxT("Error"), wxOK);
});

m_isPaInitialized = false;
}, nullptr);

engine->start();
}


Expand Down Expand Up @@ -344,6 +356,8 @@ AudioOptsDialog::~AudioOptsDialog()
wxGetApp().appConfiguration.audioConfigWindowWidth = sz.GetWidth();
wxGetApp().appConfiguration.audioConfigWindowHeight = sz.GetHeight();

AudioEngineFactory::GetAudioEngine()->stop();

// Disconnect Events
this->Disconnect(wxEVT_HIBERNATE, wxActivateEventHandler(AudioOptsDialog::OnHibernate));
this->Disconnect(wxEVT_ICONIZE, wxIconizeEventHandler(AudioOptsDialog::OnIconize));
Expand Down Expand Up @@ -385,7 +399,7 @@ bool AudioOptsDialog::setTextCtrlIfDevNameValid(wxTextCtrl *textCtrl, wxListCtrl
// ignore last list entry as it is the "none" entry
for(int i = 0; i < listCtrl->GetItemCount() - 1; i++)
{
if (listCtrl->GetItemText(i, 0).Find(devName) == 0)
if (listCtrl->GetItemText(i, 0).IsSameAs(devName))
{
textCtrl->SetValue(listCtrl->GetItemText(i, 0));
if (g_verbose) fprintf(stderr,"setting focus of %d\n", i);
Expand Down Expand Up @@ -839,7 +853,7 @@ void AudioOptsDialog::plotDeviceInputForAFewSecs(wxString devName, PlotScalar *p
auto devList = engine->getAudioDeviceList(IAudioEngine::AUDIO_ENGINE_IN);
for (auto& devInfo : devList)
{
if (devInfo.name.Find(devName) == 0)
if (devInfo.name.IsSameAs(devName))
{
int sampleCount = 0;
int sampleRate = wxAtoi(m_cbSampleRateRxIn->GetValue());
Expand Down Expand Up @@ -964,7 +978,7 @@ void AudioOptsDialog::plotDeviceOutputForAFewSecs(wxString devName, PlotScalar *
auto devList = engine->getAudioDeviceList(IAudioEngine::AUDIO_ENGINE_OUT);
for (auto& devInfo : devList)
{
if (devInfo.name.Find(devName) == 0)
if (devInfo.name.IsSameAs(devName))
{
int sampleCount = 0;
int sampleRate = wxAtoi(m_cbSampleRateRxIn->GetValue());
Expand Down Expand Up @@ -1043,6 +1057,7 @@ void AudioOptsDialog::plotDeviceOutputForAFewSecs(wxString devName, PlotScalar *
}

device->stop();

codec2_fifo_destroy(callbackFifo);
}
break;
Expand Down Expand Up @@ -1134,6 +1149,9 @@ void AudioOptsDialog::OnCancelAudioParameters(wxCommandEvent& event)
{
if(m_isPaInitialized)
{
auto engine = AudioEngineFactory::GetAudioEngine();
engine->stop();
engine->setOnEngineError(nullptr, nullptr);
m_isPaInitialized = false;
}
EndModal(wxCANCEL);
Expand All @@ -1152,6 +1170,9 @@ void AudioOptsDialog::OnOkAudioParameters(wxCommandEvent& event)
if (status == 0) {
if(m_isPaInitialized)
{
auto engine = AudioEngineFactory::GetAudioEngine();
engine->stop();
engine->setOnEngineError(nullptr, nullptr);
m_isPaInitialized = false;
}
EndModal(wxOK);
Expand Down
Loading

0 comments on commit 5898c82

Please sign in to comment.