blob: c5a1ba49072299c52cb8e3b6a5a6901d4fbfec7b [file] [log] [blame]
/*
* Copyright (C) 2004-2021 Savoir-faire Linux Inc.
*
* Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
* Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "audiolayer.h"
#include "audio/dcblocker.h"
#include "logger.h"
#include "manager.h"
#include "audio/ringbufferpool.h"
#include "audio/resampler.h"
#include "tonecontrol.h"
#include "client/ring_signal.h"
extern "C" {
#include <speex/speex_echo.h>
}
#include <ctime>
#include <algorithm>
namespace jami {
struct AudioLayer::EchoState
{
EchoState(AudioFormat format, unsigned frameSize, unsigned)
: state(speex_echo_state_init_mc(frameSize,
frameSize * 16,
format.nb_channels,
format.nb_channels),
&speex_echo_state_destroy)
, playbackQueue(format, frameSize)
, recordQueue(format, frameSize)
{
int sr = format.sample_rate;
speex_echo_ctl(state.get(), SPEEX_ECHO_SET_SAMPLING_RATE, &sr);
}
void putRecorded(std::shared_ptr<AudioFrame>&& in)
{
// JAMI_DBG("putRecorded %s %d", in->getFormat().toString().c_str(), in->getFrameSize());
recordQueue.enqueue(std::move(in));
}
void putPlayback(const std::shared_ptr<AudioFrame>& in)
{
// JAMI_DBG("putPlayback %s %d", in->getFormat().toString().c_str(), in->getFrameSize());
auto c = in;
playbackQueue.enqueue(std::move(c));
}
std::shared_ptr<AudioFrame> getRecorded()
{
if (playbackQueue.samples() < playbackQueue.frameSize()
or recordQueue.samples() < recordQueue.frameSize()) {
JAMI_DBG("getRecorded underflow %d / %d, %d / %d",
playbackQueue.samples(),
playbackQueue.frameSize(),
recordQueue.samples(),
recordQueue.frameSize());
return {};
}
if (recordQueue.samples() > 2 * recordQueue.frameSize() && playbackQueue.samples() == 0) {
JAMI_DBG("getRecorded PLAYBACK underflow");
return recordQueue.dequeue();
}
while (playbackQueue.samples() > 10 * playbackQueue.frameSize()) {
JAMI_DBG("getRecorded record underflow");
playbackQueue.dequeue();
}
while (recordQueue.samples() > 4 * recordQueue.frameSize()) {
JAMI_DBG("getRecorded playback underflow");
recordQueue.dequeue();
}
auto playback = playbackQueue.dequeue();
auto record = recordQueue.dequeue();
if (playback and record) {
auto ret = std::make_shared<AudioFrame>(record->getFormat(), record->getFrameSize());
speex_echo_cancellation(state.get(),
(const int16_t*) record->pointer()->data[0],
(const int16_t*) playback->pointer()->data[0],
(int16_t*) ret->pointer()->data[0]);
return ret;
}
return {};
}
private:
using SpeexEchoStatePtr = std::unique_ptr<SpeexEchoState, void (*)(SpeexEchoState*)>;
SpeexEchoStatePtr state;
AudioFrameResizer playbackQueue;
AudioFrameResizer recordQueue;
};
AudioLayer::AudioLayer(const AudioPreference& pref)
: isCaptureMuted_(pref.getCaptureMuted())
, isPlaybackMuted_(pref.getPlaybackMuted())
, captureGain_(pref.getVolumemic())
, playbackGain_(pref.getVolumespkr())
, mainRingBuffer_(
Manager::instance().getRingBufferPool().getRingBuffer(RingBufferPool::DEFAULT_ID))
, audioFormat_(Manager::instance().getRingBufferPool().getInternalAudioFormat())
, audioInputFormat_(Manager::instance().getRingBufferPool().getInternalAudioFormat())
, urgentRingBuffer_("urgentRingBuffer_id", SIZEBUF, audioFormat_)
, resampler_(new Resampler)
, lastNotificationTime_()
{
urgentRingBuffer_.createReadOffset(RingBufferPool::DEFAULT_ID);
}
AudioLayer::~AudioLayer() {}
void
AudioLayer::hardwareFormatAvailable(AudioFormat playback, size_t bufSize)
{
JAMI_DBG("Hardware audio format available : %s %zu", playback.toString().c_str(), bufSize);
std::unique_lock<std::mutex> lk(mutex_);
audioFormat_ = Manager::instance().hardwareAudioFormatChanged(playback);
urgentRingBuffer_.setFormat(audioFormat_);
nativeFrameSize_ = bufSize;
}
void
AudioLayer::hardwareInputFormatAvailable(AudioFormat capture)
{
JAMI_DBG("Hardware input audio format available : %s", capture.toString().c_str());
}
void
AudioLayer::devicesChanged()
{
emitSignal<DRing::AudioSignal::DeviceEvent>();
}
void
AudioLayer::flushMain()
{
Manager::instance().getRingBufferPool().flushAllBuffers();
}
void
AudioLayer::flushUrgent()
{
std::lock_guard<std::mutex> lock(mutex_);
urgentRingBuffer_.flushAll();
}
void
AudioLayer::flush()
{
Manager::instance().getRingBufferPool().flushAllBuffers();
urgentRingBuffer_.flushAll();
}
void
AudioLayer::playbackChanged(bool started)
{
playbackStarted_ = started;
checkAEC();
}
void
AudioLayer::recordChanged(bool started)
{
recordStarted_ = started;
checkAEC();
}
void
AudioLayer::setHasNativeAEC(bool hasEAC)
{
hasNativeAEC_ = hasEAC;
checkAEC();
}
void
AudioLayer::checkAEC()
{
bool shouldSoftAEC = not hasNativeAEC_ and playbackStarted_ and recordStarted_;
if (not echoState_ and shouldSoftAEC) {
JAMI_WARN("Starting AEC");
echoState_.reset(
new EchoState(audioFormat_, nativeFrameSize_, audioFormat_.sample_rate / 4));
} else if (echoState_ and not shouldSoftAEC) {
JAMI_WARN("Stopping AEC");
echoState_.reset();
}
}
void
AudioLayer::putUrgent(AudioBuffer& buffer)
{
std::lock_guard<std::mutex> lock(mutex_);
urgentRingBuffer_.put(buffer.toAVFrame());
}
// Notify (with a beep) an incoming call when there is already a call in progress
void
AudioLayer::notifyIncomingCall()
{
if (!Manager::instance().incomingCallsWaiting())
return;
auto now = std::chrono::system_clock::now();
// Notify maximum once every 5 seconds
if ((now - lastNotificationTime_) < std::chrono::seconds(5))
return;
lastNotificationTime_ = now;
// Enable notification only if more than one call
if (!Manager::instance().hasCurrentCall())
return;
Tone tone("440/160", getSampleRate());
size_t nbSample = tone.getSize();
AudioBuffer buf(nbSample, AudioFormat::MONO());
tone.getNext(buf, 1.0);
/* Put the data in the urgent ring buffer */
flushUrgent();
putUrgent(buf);
}
std::shared_ptr<AudioFrame>
AudioLayer::getToRing(AudioFormat format, size_t writableSamples)
{
ringtoneBuffer_.resize(0);
if (auto fileToPlay = Manager::instance().getTelephoneFile()) {
auto fileformat = fileToPlay->getFormat();
bool resample = format != fileformat;
size_t readableSamples = resample ? (rational<size_t>(writableSamples, format.sample_rate)
* (size_t) fileformat.sample_rate)
.real<size_t>()
: writableSamples;
ringtoneBuffer_.setFormat(fileformat);
ringtoneBuffer_.resize(readableSamples);
fileToPlay->getNext(ringtoneBuffer_, isRingtoneMuted_ ? 0. : 1.);
return resampler_->resample(ringtoneBuffer_.toAVFrame(), format);
}
return {};
}
std::shared_ptr<AudioFrame>
AudioLayer::getToPlay(AudioFormat format, size_t writableSamples)
{
notifyIncomingCall();
auto& bufferPool = Manager::instance().getRingBufferPool();
if (not playbackQueue_)
playbackQueue_.reset(new AudioFrameResizer(format, writableSamples));
else
playbackQueue_->setFrameSize(writableSamples);
std::shared_ptr<AudioFrame> playbackBuf {};
while (!(playbackBuf = playbackQueue_->dequeue())) {
std::shared_ptr<AudioFrame> resampled;
if (auto urgentSamples = urgentRingBuffer_.get(RingBufferPool::DEFAULT_ID)) {
bufferPool.discard(1, RingBufferPool::DEFAULT_ID);
resampled = resampler_->resample(std::move(urgentSamples), format);
} else if (auto toneToPlay = Manager::instance().getTelephoneTone()) {
resampled = resampler_->resample(toneToPlay->getNext(), format);
} else if (auto buf = bufferPool.getData(RingBufferPool::DEFAULT_ID)) {
resampled = resampler_->resample(std::move(buf), format);
} else {
break;
}
if (resampled) {
if (echoState_) {
echoState_->putPlayback(resampled);
}
playbackQueue_->enqueue(std::move(resampled));
} else
break;
}
return playbackBuf;
}
void
AudioLayer::putRecorded(std::shared_ptr<AudioFrame>&& frame)
{
// if (isCaptureMuted_)
// libav_utils::fillWithSilence(frame->pointer());
if (echoState_) {
echoState_->putRecorded(std::move(frame));
while (auto rec = echoState_->getRecorded())
mainRingBuffer_->put(std::move(rec));
} else {
mainRingBuffer_->put(std::move(frame));
}
}
} // namespace jami