| /* |
| * 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 |