/** * @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 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 Set the master output volume. * * @param[in] volume the master volume (between 0 and 1) */ void OpenAL::setOutputVolume(qreal volume) { QMutexLocker locker(&audioLock); volume = std::max(0.0, std::min(volume, 1.0)); alListenerf(AL_GAIN, static_cast<ALfloat>(volume)); checkAlError(); }
/** * @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); }
void OpenALRenderableSource::updateImpl(int flags, const Listener& sceneListener) throw(Exception) { Source *source = getSource(); ALSourceHandle als = getALSource(); clearAlError(); if (flags & UPDATE_ATTRIBUTES) { // Distance attenuation if (source->isAttenuated()) { alSourcef(als, AL_REFERENCE_DISTANCE, source->getRadius()); alSourcef(als, AL_ROLLOFF_FACTOR, 1.f); } else { alSourcef(als, AL_ROLLOFF_FACTOR, 0.f); } // Cone { Range<Scalar> angleRange = source->getAngleRange(); alSourcef(als, AL_CONE_INNER_ANGLE, float(angleRange.min) * M_1_PI * 360.f); alSourcef(als, AL_CONE_OUTER_ANGLE, float(angleRange.max) * M_1_PI * 360.f); alSourcef(als, AL_CONE_OUTER_GAIN , 0.f); } // Relativity alSourcei(als, AL_SOURCE_RELATIVE, source->isRelative() ? AL_TRUE : AL_FALSE); // Looping alSourcei(als, AL_LOOPING, source->isLooping() ? AL_TRUE : AL_FALSE); } if (flags & UPDATE_GAIN) { // Gain alSourcef(als, AL_GAIN, source->getGain()); } if (flags & UPDATE_LOCATION) { if (source->isRelative()) { alSource3f(als, AL_POSITION, source->getPosition()); alSource3f(als, AL_VELOCITY, source->getVelocity()); alSource3f(als, AL_DIRECTION, source->getDirection()); } else { alSource3f(als, AL_POSITION, source->getPosition() - sceneListener.getPosition() ); alSource3f(als, AL_VELOCITY, sceneListener.toLocalDirection( source->getVelocity() - sceneListener.getVelocity() ) ); alSource3f(als, AL_DIRECTION, sceneListener.toLocalDirection( source->getDirection() ) ); } } checkAlError(); }
/** * @brief Returns the current output volume (between 0 and 1) */ qreal OpenAL::outputVolume() const { QMutexLocker locker(&audioLock); ALfloat volume = 0.0; if (alOutDev) { alGetListenerf(AL_GAIN, &volume); checkAlError(); } return volume; }
void OpenALRenderableSource::startPlayingImpl(Timestamp start) throw(Exception) { if (!isPlayingImpl()) { // Make sure we have an attached sound attachALBuffers(); // Tell the AL to start playing (from the specified position) clearAlError(); ALuint als = getALSource(); alSourcePlay(als); checkAlError(); if (start != 0) seekImpl(start); } }
void OpenALRenderableSource::attachALBuffers() throw(Exception) { if (!alBuffersAttached) { SharedPtr<Sound> sound = getSource()->getSound(); if (!sound->isLoaded()) sound->load(); assert(!sound->isStreaming() && "OpenALRenderableSource can only handle streaming sounds"); // Attachment to a simple sound, just assign the AL buffer to this AL source ALBufferHandle alBuffer = dynamic_cast<OpenALSimpleSound*>(sound.get())->getAlBuffer(); ALSourceHandle alSource = getALSource(); alSourcei(alSource, AL_BUFFER, alBuffer); alBuffersAttached = true; checkAlError(); } }
/** * @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; }