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: 138 248 55.6 %
Date: 2026-01-22 10:39:23 Functions: 25 40 62.5 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2026 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     1300449 :     , 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     1299029 : AudioInput::process()
      71             : {
      72     1299029 :     readFromDevice();
      73     1299137 : }
      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     1299076 : AudioInput::readFromDevice()
     103             : {
     104             :     {
     105     1299076 :         std::lock_guard lk(resourceMutex_);
     106     1299129 :         if (decodingFile_)
     107           0 :             while (ringBuf_ && ringBuf_->isEmpty())
     108           0 :                 readFromFile();
     109     1298826 :         if (playingFile_) {
     110          15 :             while (ringBuf_ && ringBuf_->getLength(id_) == 0)
     111          10 :                 readFromQueue();
     112             :         }
     113     1298710 :     }
     114             : 
     115     1298832 :     auto& bufferPool = Manager::instance().getRingBufferPool();
     116     1298690 :     bufferPool.waitForDataAvailable(id_, MS_PER_PACKET);
     117             :     
     118     1299118 :     auto audioFrame = bufferPool.getData(id_);
     119     1299151 :     if (not audioFrame)
     120     1299134 :         return;
     121             : 
     122          18 :     if (muteState_) {
     123           0 :         libav_utils::fillWithSilence(audioFrame->pointer());
     124           0 :         audioFrame->has_voice = false; // force no voice activity when muted
     125             :     }
     126             : 
     127          18 :     std::lock_guard lk(fmtMutex_);
     128           0 :     if (bufferPool.getInternalAudioFormat() != format_)
     129           0 :         audioFrame = resampler_->resample(std::move(audioFrame), format_);
     130           0 :     resizer_->enqueue(std::move(audioFrame));
     131             : 
     132           0 :     if (recorderCallback_ && settingMS_.exchange(false)) {
     133           0 :         recorderCallback_(MediaStream("a:local", format_, sent_samples));
     134             :     }
     135             : 
     136             :     jami_tracepoint(audio_input_read_from_device_end, id_.c_str());
     137     1299134 : }
     138             : 
     139             : void
     140          10 : AudioInput::readFromQueue()
     141             : {
     142          10 :     if (!decoder_)
     143           0 :         return;
     144          10 :     if (paused_ || !decoder_->emitFrame(true)) {
     145          10 :         std::this_thread::sleep_for(MS_PER_PACKET);
     146             :     }
     147             : }
     148             : 
     149             : void
     150           0 : AudioInput::readFromFile()
     151             : {
     152           0 :     if (!decoder_)
     153           0 :         return;
     154           0 :     const auto ret = decoder_->decode();
     155           0 :     switch (ret) {
     156           0 :     case MediaDemuxer::Status::Success:
     157           0 :         break;
     158           0 :     case MediaDemuxer::Status::EndOfFile:
     159           0 :         createDecoder();
     160           0 :         break;
     161           0 :     case MediaDemuxer::Status::ReadError:
     162           0 :         JAMI_ERR() << "Failed to decode frame";
     163           0 :         break;
     164           0 :     case MediaDemuxer::Status::ReadBufferOverflow:
     165           0 :         JAMI_ERR() << "Read buffer overflow detected";
     166           0 :         break;
     167           0 :     case MediaDemuxer::Status::FallBack:
     168             :     case MediaDemuxer::Status::RestartRequired:
     169           0 :         break;
     170             :     }
     171             : }
     172             : 
     173             : bool
     174         212 : AudioInput::initDevice(const std::string& device)
     175             : {
     176         212 :     devOpts_ = {};
     177         212 :     devOpts_.input = device;
     178         212 :     devOpts_.channel = format_.nb_channels;
     179         212 :     devOpts_.framerate = format_.sample_rate;
     180         212 :     deviceGuard_ = Manager::instance().startAudioStream(AudioDeviceType::CAPTURE);
     181         212 :     playingDevice_ = true;
     182         212 :     return true;
     183             : }
     184             : 
     185             : void
     186           5 : AudioInput::configureFilePlayback(const std::string& path, std::shared_ptr<MediaDemuxer>& demuxer, int index)
     187             : {
     188           5 :     decoder_.reset();
     189           5 :     devOpts_ = {};
     190           5 :     devOpts_.input = path;
     191           5 :     devOpts_.name = path;
     192           0 :     auto decoder = std::make_unique<MediaDecoder>(demuxer, index, [this](std::shared_ptr<MediaFrame>&& frame) {
     193           0 :         if (muteState_)
     194           0 :             libav_utils::fillWithSilence(frame->pointer());
     195           0 :         if (ringBuf_)
     196           0 :             ringBuf_->put(std::static_pointer_cast<AudioFrame>(frame));
     197           5 :     });
     198           5 :     decoder->emulateRate();
     199           5 :     decoder->setInterruptCallback([](void* data) -> int { return not static_cast<AudioInput*>(data)->isCapturing(); },
     200             :                                   this);
     201             : 
     202             :     // have file audio mixed into the local buffer so it gets played
     203           5 :     Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
     204             :     // Bind to itself to be able to read from the ringbuffer
     205           5 :     Manager::instance().getRingBufferPool().bindHalfDuplexOut(id_, id_);
     206             : 
     207           5 :     deviceGuard_ = Manager::instance().startAudioStream(AudioDeviceType::PLAYBACK);
     208             : 
     209           5 :     wakeUp_ = std::chrono::steady_clock::now() + MS_PER_PACKET;
     210           5 :     playingFile_ = true;
     211           5 :     decoder_ = std::move(decoder);
     212           5 :     resource_ = path;
     213           5 :     loop_.start();
     214           5 : }
     215             : 
     216             : void
     217          10 : AudioInput::setPaused(bool paused)
     218             : {
     219          10 :     if (paused) {
     220           8 :         Manager::instance().getRingBufferPool().unBindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
     221           8 :         deviceGuard_.reset();
     222             :     } else {
     223           2 :         Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
     224           2 :         deviceGuard_ = Manager::instance().startAudioStream(AudioDeviceType::PLAYBACK);
     225             :     }
     226          10 :     paused_ = paused;
     227          10 : }
     228             : 
     229             : void
     230           9 : AudioInput::flushBuffers()
     231             : {
     232           9 :     if (decoder_) {
     233           9 :         decoder_->flushBuffers();
     234             :     }
     235           9 : }
     236             : 
     237             : bool
     238           0 : AudioInput::initFile(const std::string& path)
     239             : {
     240           0 :     if (access(path.c_str(), R_OK) != 0) {
     241           0 :         JAMI_ERROR("File '{}' not available", path);
     242           0 :         return false;
     243             :     }
     244             : 
     245           0 :     devOpts_ = {};
     246           0 :     devOpts_.input = path;
     247           0 :     devOpts_.name = path;
     248           0 :     devOpts_.loop = "1";
     249             :     // sets devOpts_'s sample rate and number of channels
     250           0 :     if (!createDecoder()) {
     251           0 :         JAMI_WARN() << "Unable to decode audio from file, switching back to default device";
     252           0 :         return initDevice("");
     253             :     }
     254           0 :     wakeUp_ = std::chrono::steady_clock::now() + MS_PER_PACKET;
     255             : 
     256             :     // have file audio mixed into the local buffer so it gets played
     257           0 :     Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
     258           0 :     decodingFile_ = true;
     259           0 :     deviceGuard_ = Manager::instance().startAudioStream(AudioDeviceType::PLAYBACK);
     260           0 :     return true;
     261             : }
     262             : 
     263             : std::shared_future<DeviceParams>
     264         212 : AudioInput::switchInput(const std::string& resource)
     265             : {
     266             :     // Always switch inputs, even if it's the same resource, so audio will be in sync with video
     267         212 :     std::unique_lock lk(resourceMutex_);
     268             : 
     269         848 :     JAMI_DEBUG("Switching audio source from {} to {}", resource_, resource);
     270             : 
     271         212 :     auto oldGuard = std::move(deviceGuard_);
     272             : 
     273         212 :     decoder_.reset();
     274         212 :     if (decodingFile_) {
     275           0 :         decodingFile_ = false;
     276           0 :         Manager::instance().getRingBufferPool().unBindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_);
     277             :     }
     278             : 
     279         212 :     playingDevice_ = false;
     280         212 :     resource_ = resource;
     281         212 :     devOptsFound_ = false;
     282             : 
     283         212 :     std::promise<DeviceParams> p;
     284         212 :     foundDevOpts_.swap(p);
     285             : 
     286         212 :     if (resource_.empty()) {
     287         212 :         if (initDevice(""))
     288         212 :             foundDevOpts(devOpts_);
     289             :     } else {
     290           0 :         static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
     291           0 :         const auto pos = resource_.find(sep);
     292           0 :         if (pos == std::string::npos)
     293           0 :             return {};
     294             : 
     295           0 :         const auto prefix = resource_.substr(0, pos);
     296           0 :         if ((pos + sep.size()) >= resource_.size())
     297           0 :             return {};
     298             : 
     299           0 :         const auto suffix = resource_.substr(pos + sep.size());
     300           0 :         bool ready = false;
     301           0 :         if (prefix == libjami::Media::VideoProtocolPrefix::FILE)
     302           0 :             ready = initFile(suffix);
     303             :         else
     304           0 :             ready = initDevice(suffix);
     305             : 
     306           0 :         if (ready)
     307           0 :             foundDevOpts(devOpts_);
     308           0 :     }
     309             : 
     310         212 :     futureDevOpts_ = foundDevOpts_.get_future().share();
     311         212 :     wakeUp_ = std::chrono::steady_clock::now() + MS_PER_PACKET;
     312         212 :     lk.unlock();
     313         212 :     if (not loop_.isRunning())
     314         208 :         loop_.start();
     315         212 :     if (onSuccessfulSetup_)
     316         180 :         onSuccessfulSetup_(MEDIA_AUDIO, 0);
     317         212 :     return futureDevOpts_;
     318         212 : }
     319             : 
     320             : void
     321         212 : AudioInput::foundDevOpts(const DeviceParams& params)
     322             : {
     323         212 :     if (!devOptsFound_) {
     324         212 :         devOptsFound_ = true;
     325         212 :         foundDevOpts_.set_value(params);
     326             :     }
     327         212 : }
     328             : 
     329             : void
     330         181 : AudioInput::setRecorderCallback(const std::function<void(const MediaStream& ms)>& cb)
     331             : {
     332         181 :     settingMS_.exchange(true);
     333         181 :     recorderCallback_ = cb;
     334         181 :     if (decoder_)
     335           0 :         decoder_->setContextCallback([this]() {
     336           0 :             if (recorderCallback_)
     337           0 :                 recorderCallback_(getInfo());
     338           0 :         });
     339         181 : }
     340             : 
     341             : bool
     342           0 : AudioInput::createDecoder()
     343             : {
     344           0 :     decoder_.reset();
     345           0 :     if (devOpts_.input.empty()) {
     346           0 :         foundDevOpts(devOpts_);
     347           0 :         return false;
     348             :     }
     349             : 
     350           0 :     auto decoder = std::make_unique<MediaDecoder>([this](std::shared_ptr<MediaFrame>&& frame) {
     351           0 :         if (ringBuf_)
     352           0 :             ringBuf_->put(std::static_pointer_cast<AudioFrame>(frame));
     353           0 :     });
     354             : 
     355             :     // NOTE don't emulate rate, file is read as frames are needed
     356             : 
     357           0 :     decoder->setInterruptCallback([](void* data) -> int { return not static_cast<AudioInput*>(data)->isCapturing(); },
     358             :                                   this);
     359             : 
     360           0 :     if (decoder->openInput(devOpts_) < 0) {
     361           0 :         JAMI_ERR() << "Unable to open input '" << devOpts_.input << "'";
     362           0 :         foundDevOpts(devOpts_);
     363           0 :         return false;
     364             :     }
     365             : 
     366           0 :     if (decoder->setupAudio() < 0) {
     367           0 :         JAMI_ERR() << "Unable to setup decoder for '" << devOpts_.input << "'";
     368           0 :         foundDevOpts(devOpts_);
     369           0 :         return false;
     370             :     }
     371             : 
     372           0 :     auto ms = decoder->getStream(devOpts_.input);
     373           0 :     devOpts_.channel = ms.nbChannels;
     374           0 :     devOpts_.framerate = ms.sampleRate;
     375           0 :     JAMI_DBG() << "Created audio decoder: " << ms;
     376             : 
     377           0 :     decoder_ = std::move(decoder);
     378           0 :     foundDevOpts(devOpts_);
     379           0 :     decoder_->setContextCallback([this]() {
     380           0 :         if (recorderCallback_)
     381           0 :             recorderCallback_(getInfo());
     382           0 :     });
     383           0 :     return true;
     384           0 : }
     385             : 
     386             : void
     387         180 : AudioInput::setFormat(const AudioFormat& fmt)
     388             : {
     389         180 :     std::lock_guard lk(fmtMutex_);
     390         180 :     format_ = fmt;
     391         180 :     resizer_->setFormat(format_, format_.sample_rate * MS_PER_PACKET.count() / 1000);
     392         180 : }
     393             : 
     394             : void
     395         203 : AudioInput::setMuted(bool isMuted)
     396             : {
     397         203 :     JAMI_WARN("Audio Input muted [%s]", isMuted ? "YES" : "NO");
     398         203 :     muteState_ = isMuted;
     399         203 : }
     400             : 
     401             : MediaStream
     402           1 : AudioInput::getInfo() const
     403             : {
     404           1 :     std::lock_guard lk(fmtMutex_);
     405           2 :     return MediaStream("a:local", format_, sent_samples);
     406           1 : }
     407             : 
     408             : MediaStream
     409           1 : AudioInput::getInfo(const std::string& name) const
     410             : {
     411           1 :     std::lock_guard lk(fmtMutex_);
     412           1 :     auto ms = MediaStream(name, format_, sent_samples);
     413           2 :     return ms;
     414           1 : }
     415             : 
     416             : } // namespace jami

Generated by: LCOV version 1.14