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
|