LCOV - code coverage report
Current view: top level - foo/src/media/audio - audio_input.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 137 249 55.0 %
Date: 2025-12-18 10:07:43 Functions: 25 40 62.5 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2025 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "audio_frame_resizer.h"
      19             : #include "audio_input.h"
      20             : #include "jami/media_const.h"
      21             : #include "fileutils.h" // access
      22             : #include "manager.h"
      23             : #include "media_decoder.h"
      24             : #include "resampler.h"
      25             : #include "ringbuffer.h"
      26             : #include "ringbufferpool.h"
      27             : #include "tracepoint.h"
      28             : 
      29             : #include <future>
      30             : #include <memory>
      31             : 
      32             : namespace jami {
      33             : 
      34             : static constexpr auto MS_PER_PACKET = std::chrono::milliseconds(20);
      35             : 
      36         250 : AudioInput::AudioInput(const std::string& id)
      37         250 :     : id_(id)
      38         250 :     , format_(Manager::instance().getRingBufferPool().getInternalAudioFormat())
      39         250 :     , frameSize_(format_.sample_rate * MS_PER_PACKET.count() / 1000)
      40         250 :     , resampler_(new Resampler)
      41         500 :     , resizer_(new AudioFrameResizer(format_,
      42             :                                      frameSize_,
      43         250 :                                      [this](std::shared_ptr<AudioFrame>&& f) { frameResized(std::move(f)); }))
      44         250 :     , deviceGuard_()
      45       38947 :     , loop_([] { return true; }, [this] { process(); }, [] {})
      46             : {
      47        1000 :     JAMI_DEBUG("Creating audio input with id: {}", id_);
      48         250 :     ringBuf_ = Manager::instance().getRingBufferPool().createRingBuffer(id_);
      49         250 : }
      50             : 
      51           0 : AudioInput::AudioInput(const std::string& id, const std::string& resource)
      52           0 :     : AudioInput(id)
      53             : {
      54           0 :     switchInput(resource);
      55           0 : }
      56             : 
      57         250 : AudioInput::~AudioInput()
      58             : {
      59         250 :     if (playingFile_) {
      60           5 :         Manager::instance().getRingBufferPool().unBindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
      61           5 :         Manager::instance().getRingBufferPool().unBindHalfDuplexOut(id_, id_);
      62             :     }
      63         250 :     ringBuf_.reset();
      64         250 :     loop_.join();
      65             : 
      66         250 :     Manager::instance().getRingBufferPool().flush(id_);
      67         250 : }
      68             : 
      69             : void
      70       37523 : AudioInput::process()
      71             : {
      72       37523 :     readFromDevice();
      73       37523 : }
      74             : 
      75             : void
      76          18 : AudioInput::updateStartTime(int64_t start)
      77             : {
      78          18 :     if (decoder_) {
      79          18 :         decoder_->updateStartTime(start);
      80             :     }
      81          18 : }
      82             : 
      83             : void
      84           0 : AudioInput::frameResized(std::shared_ptr<AudioFrame>&& ptr)
      85             : {
      86           0 :     std::shared_ptr<AudioFrame> frame = std::move(ptr);
      87           0 :     frame->pointer()->pts = sent_samples;
      88           0 :     sent_samples += frame->pointer()->nb_samples;
      89             : 
      90           0 :     notify(std::static_pointer_cast<MediaFrame>(frame));
      91           0 : }
      92             : 
      93             : void
      94           9 : AudioInput::setSeekTime(int64_t time)
      95             : {
      96           9 :     if (decoder_) {
      97           9 :         decoder_->setSeekTime(time);
      98             :     }
      99           9 : }
     100             : 
     101             : void
     102       37523 : AudioInput::readFromDevice()
     103             : {
     104             :     {
     105       37523 :         std::lock_guard lk(resourceMutex_);
     106       37523 :         if (decodingFile_)
     107           0 :             while (ringBuf_ && ringBuf_->isEmpty())
     108           0 :                 readFromFile();
     109       37523 :         if (playingFile_) {
     110          15 :             while (ringBuf_ && ringBuf_->getLength(id_) == 0)
     111          10 :                 readFromQueue();
     112             :         }
     113       37523 :     }
     114             : 
     115             :     // Note: read for device is called in an audio thread and we don't
     116             :     // want to have a loop which takes 100% of the CPU.
     117             :     // Here, we basically want to mix available data without any glitch
     118             :     // and even if one buffer doesn't have audio data (call in hold,
     119             :     // connections issues, etc). So mix every MS_PER_PACKET
     120       37523 :     std::this_thread::sleep_until(wakeUp_);
     121       37523 :     wakeUp_ += MS_PER_PACKET;
     122             : 
     123       37523 :     auto& bufferPool = Manager::instance().getRingBufferPool();
     124       37523 :     auto audioFrame = bufferPool.getData(id_);
     125       37523 :     if (not audioFrame)
     126       37523 :         return;
     127             : 
     128           0 :     if (muteState_) {
     129           0 :         libav_utils::fillWithSilence(audioFrame->pointer());
     130           0 :         audioFrame->has_voice = false; // force no voice activity when muted
     131             :     }
     132             : 
     133           0 :     std::lock_guard lk(fmtMutex_);
     134           0 :     if (bufferPool.getInternalAudioFormat() != format_)
     135           0 :         audioFrame = resampler_->resample(std::move(audioFrame), format_);
     136           0 :     resizer_->enqueue(std::move(audioFrame));
     137             : 
     138           0 :     if (recorderCallback_ && settingMS_.exchange(false)) {
     139           0 :         recorderCallback_(MediaStream("a:local", format_, sent_samples));
     140             :     }
     141             : 
     142             :     jami_tracepoint(audio_input_read_from_device_end, id_.c_str());
     143       37523 : }
     144             : 
     145             : void
     146          10 : AudioInput::readFromQueue()
     147             : {
     148          10 :     if (!decoder_)
     149           0 :         return;
     150          10 :     if (paused_ || !decoder_->emitFrame(true)) {
     151          10 :         std::this_thread::sleep_for(MS_PER_PACKET);
     152             :     }
     153             : }
     154             : 
     155             : void
     156           0 : AudioInput::readFromFile()
     157             : {
     158           0 :     if (!decoder_)
     159           0 :         return;
     160           0 :     const auto ret = decoder_->decode();
     161           0 :     switch (ret) {
     162           0 :     case MediaDemuxer::Status::Success:
     163           0 :         break;
     164           0 :     case MediaDemuxer::Status::EndOfFile:
     165           0 :         createDecoder();
     166           0 :         break;
     167           0 :     case MediaDemuxer::Status::ReadError:
     168           0 :         JAMI_ERR() << "Failed to decode frame";
     169           0 :         break;
     170           0 :     case MediaDemuxer::Status::ReadBufferOverflow:
     171           0 :         JAMI_ERR() << "Read buffer overflow detected";
     172           0 :         break;
     173           0 :     case MediaDemuxer::Status::FallBack:
     174             :     case MediaDemuxer::Status::RestartRequired:
     175           0 :         break;
     176             :     }
     177             : }
     178             : 
     179             : bool
     180         211 : AudioInput::initDevice(const std::string& device)
     181             : {
     182         211 :     devOpts_ = {};
     183         211 :     devOpts_.input = device;
     184         211 :     devOpts_.channel = format_.nb_channels;
     185         211 :     devOpts_.framerate = format_.sample_rate;
     186         211 :     deviceGuard_ = Manager::instance().startAudioStream(AudioDeviceType::CAPTURE);
     187         211 :     playingDevice_ = true;
     188         211 :     return true;
     189             : }
     190             : 
     191             : void
     192           5 : AudioInput::configureFilePlayback(const std::string& path, std::shared_ptr<MediaDemuxer>& demuxer, int index)
     193             : {
     194           5 :     decoder_.reset();
     195           5 :     devOpts_ = {};
     196           5 :     devOpts_.input = path;
     197           5 :     devOpts_.name = path;
     198           0 :     auto decoder = std::make_unique<MediaDecoder>(demuxer, index, [this](std::shared_ptr<MediaFrame>&& frame) {
     199           0 :         if (muteState_)
     200           0 :             libav_utils::fillWithSilence(frame->pointer());
     201           0 :         if (ringBuf_)
     202           0 :             ringBuf_->put(std::static_pointer_cast<AudioFrame>(frame));
     203           5 :     });
     204           5 :     decoder->emulateRate();
     205           5 :     decoder->setInterruptCallback([](void* data) -> int { return not static_cast<AudioInput*>(data)->isCapturing(); },
     206             :                                   this);
     207             : 
     208             :     // have file audio mixed into the local buffer so it gets played
     209           5 :     Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
     210             :     // Bind to itself to be able to read from the ringbuffer
     211           5 :     Manager::instance().getRingBufferPool().bindHalfDuplexOut(id_, id_);
     212             : 
     213           5 :     deviceGuard_ = Manager::instance().startAudioStream(AudioDeviceType::PLAYBACK);
     214             : 
     215           5 :     wakeUp_ = std::chrono::steady_clock::now() + MS_PER_PACKET;
     216           5 :     playingFile_ = true;
     217           5 :     decoder_ = std::move(decoder);
     218           5 :     resource_ = path;
     219           5 :     loop_.start();
     220           5 : }
     221             : 
     222             : void
     223          10 : AudioInput::setPaused(bool paused)
     224             : {
     225          10 :     if (paused) {
     226           8 :         Manager::instance().getRingBufferPool().unBindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
     227           8 :         deviceGuard_.reset();
     228             :     } else {
     229           2 :         Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
     230           2 :         deviceGuard_ = Manager::instance().startAudioStream(AudioDeviceType::PLAYBACK);
     231             :     }
     232          10 :     paused_ = paused;
     233          10 : }
     234             : 
     235             : void
     236           9 : AudioInput::flushBuffers()
     237             : {
     238           9 :     if (decoder_) {
     239           9 :         decoder_->flushBuffers();
     240             :     }
     241           9 : }
     242             : 
     243             : bool
     244           0 : AudioInput::initFile(const std::string& path)
     245             : {
     246           0 :     if (access(path.c_str(), R_OK) != 0) {
     247           0 :         JAMI_ERROR("File '{}' not available", path);
     248           0 :         return false;
     249             :     }
     250             : 
     251           0 :     devOpts_ = {};
     252           0 :     devOpts_.input = path;
     253           0 :     devOpts_.name = path;
     254           0 :     devOpts_.loop = "1";
     255             :     // sets devOpts_'s sample rate and number of channels
     256           0 :     if (!createDecoder()) {
     257           0 :         JAMI_WARN() << "Unable to decode audio from file, switching back to default device";
     258           0 :         return initDevice("");
     259             :     }
     260           0 :     wakeUp_ = std::chrono::steady_clock::now() + MS_PER_PACKET;
     261             : 
     262             :     // have file audio mixed into the local buffer so it gets played
     263           0 :     Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
     264           0 :     decodingFile_ = true;
     265           0 :     deviceGuard_ = Manager::instance().startAudioStream(AudioDeviceType::PLAYBACK);
     266           0 :     return true;
     267             : }
     268             : 
     269             : std::shared_future<DeviceParams>
     270         211 : AudioInput::switchInput(const std::string& resource)
     271             : {
     272             :     // Always switch inputs, even if it's the same resource, so audio will be in sync with video
     273         211 :     std::unique_lock lk(resourceMutex_);
     274             : 
     275         844 :     JAMI_DEBUG("Switching audio source from {} to {}", resource_, resource);
     276             : 
     277         211 :     auto oldGuard = std::move(deviceGuard_);
     278             : 
     279         211 :     decoder_.reset();
     280         211 :     if (decodingFile_) {
     281           0 :         decodingFile_ = false;
     282           0 :         Manager::instance().getRingBufferPool().unBindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
     283             :     }
     284             : 
     285         211 :     playingDevice_ = false;
     286         211 :     resource_ = resource;
     287         211 :     devOptsFound_ = false;
     288             : 
     289         211 :     std::promise<DeviceParams> p;
     290         211 :     foundDevOpts_.swap(p);
     291             : 
     292         211 :     if (resource_.empty()) {
     293         211 :         if (initDevice(""))
     294         211 :             foundDevOpts(devOpts_);
     295             :     } else {
     296           0 :         static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
     297           0 :         const auto pos = resource_.find(sep);
     298           0 :         if (pos == std::string::npos)
     299           0 :             return {};
     300             : 
     301           0 :         const auto prefix = resource_.substr(0, pos);
     302           0 :         if ((pos + sep.size()) >= resource_.size())
     303           0 :             return {};
     304             : 
     305           0 :         const auto suffix = resource_.substr(pos + sep.size());
     306           0 :         bool ready = false;
     307           0 :         if (prefix == libjami::Media::VideoProtocolPrefix::FILE)
     308           0 :             ready = initFile(suffix);
     309             :         else
     310           0 :             ready = initDevice(suffix);
     311             : 
     312           0 :         if (ready)
     313           0 :             foundDevOpts(devOpts_);
     314           0 :     }
     315             : 
     316         211 :     futureDevOpts_ = foundDevOpts_.get_future().share();
     317         211 :     wakeUp_ = std::chrono::steady_clock::now() + MS_PER_PACKET;
     318         211 :     lk.unlock();
     319         211 :     if (not loop_.isRunning())
     320         207 :         loop_.start();
     321         211 :     if (onSuccessfulSetup_)
     322         180 :         onSuccessfulSetup_(MEDIA_AUDIO, 0);
     323         211 :     return futureDevOpts_;
     324         211 : }
     325             : 
     326             : void
     327         211 : AudioInput::foundDevOpts(const DeviceParams& params)
     328             : {
     329         211 :     if (!devOptsFound_) {
     330         211 :         devOptsFound_ = true;
     331         211 :         foundDevOpts_.set_value(params);
     332             :     }
     333         211 : }
     334             : 
     335             : void
     336         181 : AudioInput::setRecorderCallback(const std::function<void(const MediaStream& ms)>& cb)
     337             : {
     338         181 :     settingMS_.exchange(true);
     339         181 :     recorderCallback_ = cb;
     340         181 :     if (decoder_)
     341           0 :         decoder_->setContextCallback([this]() {
     342           0 :             if (recorderCallback_)
     343           0 :                 recorderCallback_(getInfo());
     344           0 :         });
     345         181 : }
     346             : 
     347             : bool
     348           0 : AudioInput::createDecoder()
     349             : {
     350           0 :     decoder_.reset();
     351           0 :     if (devOpts_.input.empty()) {
     352           0 :         foundDevOpts(devOpts_);
     353           0 :         return false;
     354             :     }
     355             : 
     356           0 :     auto decoder = std::make_unique<MediaDecoder>([this](std::shared_ptr<MediaFrame>&& frame) {
     357           0 :         if (ringBuf_)
     358           0 :             ringBuf_->put(std::static_pointer_cast<AudioFrame>(frame));
     359           0 :     });
     360             : 
     361             :     // NOTE don't emulate rate, file is read as frames are needed
     362             : 
     363           0 :     decoder->setInterruptCallback([](void* data) -> int { return not static_cast<AudioInput*>(data)->isCapturing(); },
     364             :                                   this);
     365             : 
     366           0 :     if (decoder->openInput(devOpts_) < 0) {
     367           0 :         JAMI_ERR() << "Unable to open input '" << devOpts_.input << "'";
     368           0 :         foundDevOpts(devOpts_);
     369           0 :         return false;
     370             :     }
     371             : 
     372           0 :     if (decoder->setupAudio() < 0) {
     373           0 :         JAMI_ERR() << "Unable to setup decoder for '" << devOpts_.input << "'";
     374           0 :         foundDevOpts(devOpts_);
     375           0 :         return false;
     376             :     }
     377             : 
     378           0 :     auto ms = decoder->getStream(devOpts_.input);
     379           0 :     devOpts_.channel = ms.nbChannels;
     380           0 :     devOpts_.framerate = ms.sampleRate;
     381           0 :     JAMI_DBG() << "Created audio decoder: " << ms;
     382             : 
     383           0 :     decoder_ = std::move(decoder);
     384           0 :     foundDevOpts(devOpts_);
     385           0 :     decoder_->setContextCallback([this]() {
     386           0 :         if (recorderCallback_)
     387           0 :             recorderCallback_(getInfo());
     388           0 :     });
     389           0 :     return true;
     390           0 : }
     391             : 
     392             : void
     393         180 : AudioInput::setFormat(const AudioFormat& fmt)
     394             : {
     395         180 :     std::lock_guard lk(fmtMutex_);
     396         180 :     format_ = fmt;
     397         180 :     resizer_->setFormat(format_, format_.sample_rate * MS_PER_PACKET.count() / 1000);
     398         180 : }
     399             : 
     400             : void
     401         203 : AudioInput::setMuted(bool isMuted)
     402             : {
     403         203 :     JAMI_WARN("Audio Input muted [%s]", isMuted ? "YES" : "NO");
     404         203 :     muteState_ = isMuted;
     405         203 : }
     406             : 
     407             : MediaStream
     408           1 : AudioInput::getInfo() const
     409             : {
     410           1 :     std::lock_guard lk(fmtMutex_);
     411           2 :     return MediaStream("a:local", format_, sent_samples);
     412           1 : }
     413             : 
     414             : MediaStream
     415           1 : AudioInput::getInfo(const std::string& name) const
     416             : {
     417           1 :     std::lock_guard lk(fmtMutex_);
     418           1 :     auto ms = MediaStream(name, format_, sent_samples);
     419           2 :     return ms;
     420           1 : }
     421             : 
     422             : } // namespace jami

Generated by: LCOV version 1.14