/** * @brief Loads the OpenAL extension methods needed for echo cancellation * @param dev OpenAL device used for the output * @return True when functions successfully loaded, false otherwise */ bool OpenAL2::loadOpenALExtensions(ALCdevice* dev) { // load OpenAL extension functions GET_PROC_ADDR(dev, alcLoopbackOpenDeviceSOFT); checkAlcError(dev); if (!alcLoopbackOpenDeviceSOFT) { qDebug() << "Failed to load alcLoopbackOpenDeviceSOFT function!"; return false; } GET_PROC_ADDR(dev, alcIsRenderFormatSupportedSOFT); checkAlcError(dev); if (!alcIsRenderFormatSupportedSOFT) { qDebug() << "Failed to load alcIsRenderFormatSupportedSOFT function!"; return false; } GET_PROC_ADDR(dev, alGetSourcedvSOFT); checkAlcError(dev); if (!alGetSourcedvSOFT) { qDebug() << "Failed to load alGetSourcedvSOFT function!"; return false; } GET_PROC_ADDR(dev, alcRenderSamplesSOFT); checkAlcError(dev); if (!alcRenderSamplesSOFT) { qDebug() << "Failed to load alcRenderSamplesSOFT function!"; return false; } return true; }
/** * @brief Open an audio output device */ bool OpenAL2::initOutput(const QString& deviceName) { peerSources.clear(); outputInitialized = false; if (!Settings::getInstance().getAudioOutDevEnabled()) { return false; } qDebug() << "Opening audio output" << deviceName; assert(!alOutDev); const QByteArray qDevName = deviceName.toUtf8(); const ALchar* tmpDevName = qDevName.isEmpty() ? nullptr : qDevName.constData(); alOutDev = alcOpenDevice(tmpDevName); if (!alOutDev) { qWarning() << "Cannot open output audio device" << deviceName; return false; } qDebug() << "Opened audio output" << deviceName; alOutContext = alcCreateContext(alOutDev, nullptr); checkAlcError(alOutDev); if (!alcMakeContextCurrent(alOutContext)) { qWarning() << "Cannot create output audio context"; return false; } // try to init echo cancellation echoCancelSupported = initOutputEchoCancel(); if (!echoCancelSupported) { // fallback to normal, no proxy device needed qDebug() << "Echo cancellation disabled"; alProxyDev = alOutDev; alProxyContext = alOutContext; } alGenSources(1, &alMainSource); checkAlError(); // init master volume alListenerf(AL_GAIN, Settings::getInstance().getOutVolume() * 0.01f); checkAlError(); Core* core = Core::getInstance(); if (core) { // reset each call's audio source core->getAv()->invalidateCallSources(); } // ensure alProxyContext is active alcMakeContextCurrent(alProxyContext); outputInitialized = true; return true; }
/** * @brief Handle audio output */ void OpenAL2::doOutput() { alcMakeContextCurrent(alOutContext); ALuint bufids[PROXY_BUFFER_COUNT]; ALint processed = 0, queued = 0; alGetSourcei(alProxySource, AL_BUFFERS_PROCESSED, &processed); alGetSourcei(alProxySource, AL_BUFFERS_QUEUED, &queued); if (processed > 0) { // unqueue all processed buffers alSourceUnqueueBuffers(alProxySource, processed, bufids); // delete all but the first buffer, reuse first for new data alDeleteBuffers(processed - 1, bufids + 1); } else if (queued < PROXY_BUFFER_COUNT) { // create new buffer until the maximum is reached alGenBuffers(1, bufids); } else { alcMakeContextCurrent(alProxyContext); return; } ALdouble latency[2] = {0}; alGetSourcedvSOFT(alProxySource, AL_SEC_OFFSET_LATENCY_SOFT, latency); checkAlError(); ALshort outBuf[AUDIO_FRAME_SAMPLE_COUNT] = {0}; alcMakeContextCurrent(alProxyContext); alcRenderSamplesSOFT(alProxyDev, outBuf, AUDIO_FRAME_SAMPLE_COUNT); checkAlcError(alProxyDev); alcMakeContextCurrent(alOutContext); alBufferData(bufids[0], AL_FORMAT_MONO16, outBuf, AUDIO_FRAME_SAMPLE_COUNT * 2, AUDIO_SAMPLE_RATE); alSourceQueueBuffers(alProxySource, 1, bufids); // initialize echo canceler if supported if (!filterer) { filterer = new_filter_audio(AUDIO_SAMPLE_RATE); int16_t filterLatency = latency[1] * 1000 * 2 + AUDIO_FRAME_DURATION; qDebug() << "Setting filter delay to: " << filterLatency << "ms"; set_echo_delay_ms(filterer, filterLatency); enable_disable_filters(filterer, 1, 1, 1, 0); } // do echo cancel pass_audio_output(filterer, outBuf, AUDIO_FRAME_SAMPLE_COUNT); ALint state; alGetSourcei(alProxySource, AL_SOURCE_STATE, &state); if (state != AL_PLAYING) { qDebug() << "Proxy source underflow detected"; alSourcePlay(alProxySource); } alcMakeContextCurrent(alProxyContext); }
/** * @brief Open an audio output device */ bool Audio::initOutput(const QString& deviceName) { outSources.clear(); outputInitialized = false; if (!Settings::getInstance().getAudioOutDevEnabled()) return false; qDebug() << "Opening audio output" << deviceName; assert(!alOutDev); const QByteArray qDevName = deviceName.toUtf8(); const ALchar* tmpDevName = qDevName.isEmpty() ? nullptr : qDevName.constData(); alOutDev = alcOpenDevice(tmpDevName); if (!alOutDev) { qWarning() << "Cannot open output audio device" << deviceName; return false; } qDebug() << "Opened audio output" << deviceName; alOutContext = alcCreateContext(alOutDev, nullptr); checkAlcError(alOutDev); if (!alcMakeContextCurrent(alOutContext)) { qWarning() << "Cannot create output audio context"; return false; } alGenSources(1, &alMainSource); checkAlError(); // init master volume alListenerf(AL_GAIN, Settings::getInstance().getOutVolume() * 0.01f); checkAlError(); Core* core = Core::getInstance(); if (core) { // reset each call's audio source core->getAv()->invalidateCallSources(); } outputInitialized = true; return true; }
/** * @brief Initializes the output with echo cancelling enabled * @return true on success, false otherwise * Creates a loopback device and a proxy source on the main output device. * If this function returns true only the proxy source should be used for * audio output. */ bool OpenAL2::initOutputEchoCancel() { // check for the needed extensions for echo cancelation if (alcIsExtensionPresent(alOutDev, "ALC_SOFT_loopback") != AL_TRUE) { qDebug() << "Device doesn't support loopback"; return false; } if (alIsExtensionPresent("AL_SOFT_source_latency") != AL_TRUE) { qDebug() << "Device doesn't support source latency"; return false; } if (!loadOpenALExtensions(alOutDev)) { qDebug() << "Couldn't load needed OpenAL extensions"; return false; } // source for proxy output alGenSources(1, &alProxySource); checkAlError(); // configuration for the loopback device ALCint attrs[] = {ALC_FORMAT_CHANNELS_SOFT, ALC_MONO_SOFT, ALC_FORMAT_TYPE_SOFT, ALC_SHORT_SOFT, ALC_FREQUENCY, Audio::AUDIO_SAMPLE_RATE, 0}; // End of List alProxyDev = alcLoopbackOpenDeviceSOFT(NULL); checkAlcError(alProxyDev); if (!alProxyDev) { qDebug() << "Couldn't create proxy device"; alDeleteSources(1, &alProxySource); // cleanup source return false; } if (!alcIsRenderFormatSupportedSOFT(alProxyDev, attrs[5], attrs[1], attrs[3])) { qDebug() << "Unsupported format for loopback"; alcCloseDevice(alProxyDev); // cleanup loopback dev alDeleteSources(1, &alProxySource); // cleanup source return false; } alProxyContext = alcCreateContext(alProxyDev, attrs); checkAlcError(alProxyDev); if (!alProxyContext) { qDebug() << "Couldn't create proxy context"; alcCloseDevice(alProxyDev); // cleanup loopback dev alDeleteSources(1, &alProxySource); // cleanup source return false; } if (!alcMakeContextCurrent(alProxyContext)) { qDebug() << "Cannot activate proxy context"; alcDestroyContext(alProxyContext); alcCloseDevice(alProxyDev); // cleanup loopback dev alDeleteSources(1, &alProxySource); // cleanup source return false; } qDebug() << "Echo cancelation enabled"; return true; }