Ring Daemon 16.0.0
Loading...
Searching...
No Matches
audio_input.cpp
Go to the documentation of this file.
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
32namespace jami {
33
34static constexpr auto MS_PER_PACKET = std::chrono::milliseconds(20);
35
36AudioInput::AudioInput(const std::string& id)
37 : id_(id)
38 , format_(Manager::instance().getRingBufferPool().getInternalAudioFormat())
39 , frameSize_(format_.sample_rate * MS_PER_PACKET.count() / 1000)
40 , resampler_(new Resampler)
41 , resizer_(new AudioFrameResizer(format_,
42 frameSize_,
44 frameResized(std::move(f));
45 }))
46 , deviceGuard_()
47 , loop_([] { return true; }, [this] { process(); }, [] {})
48{
49 JAMI_DEBUG("Creating audio input with id: {}", id_);
51}
52
53AudioInput::AudioInput(const std::string& id, const std::string& resource)
54 : AudioInput(id)
55{
57}
58
70
71void
72AudioInput::process()
73{
74 readFromDevice();
75}
76
77void
79{
80 if (decoder_) {
81 decoder_->updateStartTime(start);
82 }
83}
84
85void
86AudioInput::frameResized(std::shared_ptr<AudioFrame>&& ptr)
87{
88 std::shared_ptr<AudioFrame> frame = std::move(ptr);
89 frame->pointer()->pts = sent_samples;
90 sent_samples += frame->pointer()->nb_samples;
91
92 notify(std::static_pointer_cast<MediaFrame>(frame));
93}
94
95void
97{
98 if (decoder_) {
99 decoder_->setSeekTime(time);
100 }
101}
102
103void
104AudioInput::readFromDevice()
105{
106 {
107 std::lock_guard lk(resourceMutex_);
108 if (decodingFile_)
109 while (ringBuf_ && ringBuf_->isEmpty())
110 readFromFile();
111 if (playingFile_) {
112 while (ringBuf_ && ringBuf_->getLength(id_) == 0)
113 readFromQueue();
114 }
115 }
116
117 // Note: read for device is called in an audio thread and we don't
118 // want to have a loop which takes 100% of the CPU.
119 // Here, we basically want to mix available data without any glitch
120 // and even if one buffer doesn't have audio data (call in hold,
121 // connections issues, etc). So mix every MS_PER_PACKET
122 std::this_thread::sleep_until(wakeUp_);
123 wakeUp_ += MS_PER_PACKET;
124
126 auto audioFrame = bufferPool.getData(id_);
127 if (not audioFrame)
128 return;
129
130 if (muteState_) {
132 audioFrame->has_voice = false; // force no voice activity when muted
133 }
134
135 std::lock_guard lk(fmtMutex_);
136 if (bufferPool.getInternalAudioFormat() != format_)
137 audioFrame = resampler_->resample(std::move(audioFrame), format_);
138 resizer_->enqueue(std::move(audioFrame));
139
140 if (recorderCallback_ && settingMS_.exchange(false)) {
141 recorderCallback_(MediaStream("a:local", format_, sent_samples));
142 }
143
145}
146
147void
148AudioInput::readFromQueue()
149{
150 if (!decoder_)
151 return;
152 if (paused_ || !decoder_->emitFrame(true)) {
153 std::this_thread::sleep_for(MS_PER_PACKET);
154 }
155}
156
157void
158AudioInput::readFromFile()
159{
160 if (!decoder_)
161 return;
162 const auto ret = decoder_->decode();
163 switch (ret) {
165 break;
167 createDecoder();
168 break;
170 JAMI_ERR() << "Failed to decode frame";
171 break;
173 JAMI_ERR() << "Read buffer overflow detected";
174 break;
177 break;
178 }
179}
180
181bool
182AudioInput::initDevice(const std::string& device)
183{
184 devOpts_ = {};
185 devOpts_.input = device;
186 devOpts_.channel = format_.nb_channels;
187 devOpts_.framerate = format_.sample_rate;
189 playingDevice_ = true;
190 return true;
191}
192
193void
194AudioInput::configureFilePlayback(const std::string& path,
195 std::shared_ptr<MediaDemuxer>& demuxer,
196 int index)
197{
198 decoder_.reset();
199 devOpts_ = {};
200 devOpts_.input = path;
201 devOpts_.name = path;
202 auto decoder
203 = std::make_unique<MediaDecoder>(demuxer, index, [this](std::shared_ptr<MediaFrame>&& frame) {
204 if (muteState_)
206 if (ringBuf_)
207 ringBuf_->put(std::static_pointer_cast<AudioFrame>(frame));
208 });
209 decoder->emulateRate();
210 decoder->setInterruptCallback(
211 [](void* data) -> int { return not static_cast<AudioInput*>(data)->isCapturing(); }, this);
212
213 // have file audio mixed into the local buffer so it gets played
215 // Bind to itself to be able to read from the ringbuffer
217
219
220 wakeUp_ = std::chrono::steady_clock::now() + MS_PER_PACKET;
221 playingFile_ = true;
222 decoder_ = std::move(decoder);
223 resource_ = path;
224 loop_.start();
225}
226
227void
239
240void
242{
243 if (decoder_) {
244 decoder_->flushBuffers();
245 }
246}
247
248bool
249AudioInput::initFile(const std::string& path)
250{
251 if (access(path.c_str(), R_OK) != 0) {
252 JAMI_ERROR("File '{}' not available", path);
253 return false;
254 }
255
256 devOpts_ = {};
257 devOpts_.input = path;
258 devOpts_.name = path;
259 devOpts_.loop = "1";
260 // sets devOpts_'s sample rate and number of channels
261 if (!createDecoder()) {
262 JAMI_WARN() << "Unable to decode audio from file, switching back to default device";
263 return initDevice("");
264 }
265 wakeUp_ = std::chrono::steady_clock::now() + MS_PER_PACKET;
266
267 // have file audio mixed into the local buffer so it gets played
269 decodingFile_ = true;
271 return true;
272}
273
274std::shared_future<DeviceParams>
276{
277 // Always switch inputs, even if it's the same resource, so audio will be in sync with video
278 std::unique_lock lk(resourceMutex_);
279
280 JAMI_DEBUG("Switching audio source from {} to {}", resource_, resource);
281
282 auto oldGuard = std::move(deviceGuard_);
283
284 decoder_.reset();
285 if (decodingFile_) {
286 decodingFile_ = false;
288 id_);
289 }
290
291 playingDevice_ = false;
292 resource_ = resource;
293 devOptsFound_ = false;
294
295 std::promise<DeviceParams> p;
296 foundDevOpts_.swap(p);
297
298 if (resource_.empty()) {
299 if (initDevice(""))
300 foundDevOpts(devOpts_);
301 } else {
302 static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
303 const auto pos = resource_.find(sep);
304 if (pos == std::string::npos)
305 return {};
306
307 const auto prefix = resource_.substr(0, pos);
308 if ((pos + sep.size()) >= resource_.size())
309 return {};
310
311 const auto suffix = resource_.substr(pos + sep.size());
312 bool ready = false;
314 ready = initFile(suffix);
315 else
316 ready = initDevice(suffix);
317
318 if (ready)
319 foundDevOpts(devOpts_);
320 }
321
322 futureDevOpts_ = foundDevOpts_.get_future().share();
323 wakeUp_ = std::chrono::steady_clock::now() + MS_PER_PACKET;
324 lk.unlock();
325 loop_.start();
326 if (onSuccessfulSetup_)
327 onSuccessfulSetup_(MEDIA_AUDIO, 0);
328 return futureDevOpts_;
329}
330
331void
332AudioInput::foundDevOpts(const DeviceParams& params)
333{
334 if (!devOptsFound_) {
335 devOptsFound_ = true;
336 foundDevOpts_.set_value(params);
337 }
338}
339
340void
342 const std::function<void(const MediaStream& ms)>&
343 cb)
344{
345 settingMS_.exchange(true);
346 recorderCallback_ = cb;
347 if (decoder_)
348 decoder_->setContextCallback([this]() {
349 if (recorderCallback_)
350 recorderCallback_(getInfo());
351 });
352}
353
354
355bool
356AudioInput::createDecoder()
357{
358 decoder_.reset();
359 if (devOpts_.input.empty()) {
360 foundDevOpts(devOpts_);
361 return false;
362 }
363
364 auto decoder = std::make_unique<MediaDecoder>([this](std::shared_ptr<MediaFrame>&& frame) {
365 if (ringBuf_)
366 ringBuf_->put(std::static_pointer_cast<AudioFrame>(frame));
367 });
368
369 // NOTE don't emulate rate, file is read as frames are needed
370
371 decoder->setInterruptCallback(
372 [](void* data) -> int { return not static_cast<AudioInput*>(data)->isCapturing(); }, this);
373
374 if (decoder->openInput(devOpts_) < 0) {
375 JAMI_ERR() << "Unable to open input '" << devOpts_.input << "'";
376 foundDevOpts(devOpts_);
377 return false;
378 }
379
380 if (decoder->setupAudio() < 0) {
381 JAMI_ERR() << "Unable to setup decoder for '" << devOpts_.input << "'";
382 foundDevOpts(devOpts_);
383 return false;
384 }
385
386 auto ms = decoder->getStream(devOpts_.input);
387 devOpts_.channel = ms.nbChannels;
388 devOpts_.framerate = ms.sampleRate;
389 JAMI_DBG() << "Created audio decoder: " << ms;
390
391 decoder_ = std::move(decoder);
392 foundDevOpts(devOpts_);
393 decoder_->setContextCallback([this]() {
394 if (recorderCallback_)
395 recorderCallback_(getInfo());
396 });
397 return true;
398}
399
400void
402{
403 std::lock_guard lk(fmtMutex_);
404 format_ = fmt;
405 resizer_->setFormat(format_, format_.sample_rate * MS_PER_PACKET.count() / 1000);
406}
407
408void
410{
411 JAMI_WARN("Audio Input muted [%s]", isMuted ? "YES" : "NO");
412 muteState_ = isMuted;
413}
414
417{
418 std::lock_guard lk(fmtMutex_);
419 return MediaStream("a:local", format_, sent_samples);
420}
421
423AudioInput::getInfo(const std::string& name) const
424{
425 std::lock_guard lk(fmtMutex_);
426 auto ms = MediaStream(name, format_, sent_samples);
427 return ms;
428}
429
430} // namespace jami
Buffers extra samples.
void updateStartTime(int64_t start)
void setRecorderCallback(const std::function< void(const MediaStream &ms)> &cb)
void setMuted(bool isMuted)
void setPaused(bool paused)
void setSeekTime(int64_t time)
void configureFilePlayback(const std::string &path, std::shared_ptr< MediaDemuxer > &demuxer, int index)
void setFormat(const AudioFormat &fmt)
std::shared_future< DeviceParams > switchInput(const std::string &resource)
AudioInput(const std::string &id)
MediaStream getInfo() const
bool isCapturing() const
Definition audio_input.h:51
Manager (controller) of daemon.
Definition manager.h:67
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:676
RingBufferPool & getRingBufferPool()
Return a pointer to the instance of the RingBufferPool.
Definition manager.cpp:3157
std::unique_ptr< AudioDeviceGuard > startAudioStream(AudioDeviceType stream)
Definition manager.h:143
void notify(std::shared_ptr< MediaFrame > data)
Definition observer.h:118
Wrapper class for libswresample.
Definition resampler.h:36
std::shared_ptr< RingBuffer > createRingBuffer(const std::string &id)
Create a new ringbuffer with a default readoffset.
void bindHalfDuplexOut(const std::string &readerBufferId, const std::string &sourceBufferId)
Attaches a reader the specified source.
std::shared_ptr< AudioFrame > getData(const std::string &ringbufferId)
static const char *const DEFAULT_ID
void flush(const std::string &ringbufferId)
void unBindHalfDuplexOut(const std::string &readerBufferId, const std::string &sourceBufferId)
Detaches a reader from the specified source.
#define JAMI_ERR(...)
Definition logger.h:218
#define JAMI_ERROR(formatstr,...)
Definition logger.h:228
#define JAMI_DBG(...)
Definition logger.h:216
#define JAMI_DEBUG(formatstr,...)
Definition logger.h:226
#define JAMI_WARN(...)
Definition logger.h:217
void fillWithSilence(AVFrame *frame)
static constexpr auto MS_PER_PACKET
void emitSignal(Args... args)
Definition ring_signal.h:64
@ MEDIA_AUDIO
Definition media_codec.h:47
static constexpr const char * SEPARATOR
Definition media_const.h:33
static constexpr const char * FILE
Definition media_const.h:31
Structure to hold sample rate and channel number associated with audio data.
DeviceParams Parameters used by MediaDecoder and MediaEncoder to open a LibAV device/stream.
rational< double > framerate
std::string input
#define jami_tracepoint(...)
Definition tracepoint.h:53