mirror of
https://git.jami.net/savoirfairelinux/jami-daemon.git
synced 2025-08-12 22:09:25 +08:00
audio: implement switch input
Lays the groundwork for file streaming by allowing the audio source to be changed while an audio input is active. Don't notify observers if there's no frame. Renames videoInput_ to mediaInput_ now that it is also used for audio. Change-Id: I0a10d4a339e77b890ee006a5f977383e8942505b
This commit is contained in:
@ -394,11 +394,16 @@ switchInput(const std::string& resource)
|
||||
call->switchInput(resource);
|
||||
return true;
|
||||
} else {
|
||||
bool ret = true;
|
||||
if (auto input = ring::Manager::instance().getVideoManager().videoInput.lock())
|
||||
return input->switchInput(resource).valid();
|
||||
RING_WARN("Video input not initialized");
|
||||
ret = input->switchInput(resource).valid();
|
||||
else
|
||||
RING_WARN("Video input not initialized");
|
||||
|
||||
if (auto input = ring::getAudioInput(ring::RingBufferPool::DEFAULT_ID))
|
||||
ret &= input->switchInput(resource).valid();
|
||||
return ret;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -19,15 +19,15 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "dring/media_const.h"
|
||||
#include "audio_input.h"
|
||||
#include "manager.h"
|
||||
#include "resampler.h"
|
||||
#include "ringbufferpool.h"
|
||||
#include "smartools.h"
|
||||
|
||||
#include <future>
|
||||
#include <chrono>
|
||||
#include "socket_pair.h"
|
||||
#include "audio/ringbufferpool.h"
|
||||
#include "audio/resampler.h"
|
||||
#include "manager.h"
|
||||
#include "smartools.h"
|
||||
|
||||
namespace ring {
|
||||
|
||||
@ -54,6 +54,35 @@ AudioInput::~AudioInput()
|
||||
|
||||
void
|
||||
AudioInput::process()
|
||||
{
|
||||
foundDevOpts(devOpts_);
|
||||
if (switchPending_.exchange(false)) {
|
||||
if (devOpts_.input.empty())
|
||||
RING_DBG() << "Switching to default audio input";
|
||||
else
|
||||
RING_DBG() << "Switching audio input to '" << devOpts_.input << "'";
|
||||
}
|
||||
|
||||
auto frame = std::make_shared<AudioFrame>();
|
||||
if (!nextFromDevice(*frame))
|
||||
return; // no frame
|
||||
|
||||
auto ms = MediaStream("a:local", format_, sent_samples);
|
||||
frame->pointer()->pts = sent_samples;
|
||||
sent_samples += frame->pointer()->nb_samples;
|
||||
|
||||
{
|
||||
auto rec = recorder_.lock();
|
||||
if (rec && rec->isRecording()) {
|
||||
rec->recordData(frame->pointer(), ms);
|
||||
}
|
||||
}
|
||||
|
||||
notify(frame);
|
||||
}
|
||||
|
||||
bool
|
||||
AudioInput::nextFromDevice(AudioFrame& frame)
|
||||
{
|
||||
auto& mainBuffer = Manager::instance().getRingBufferPool();
|
||||
auto bufferFormat = mainBuffer.getInternalAudioFormat();
|
||||
@ -64,7 +93,7 @@ AudioInput::process()
|
||||
|
||||
if (mainBuffer.availableForGet(id_) < samplesToGet
|
||||
&& not mainBuffer.waitForDataAvailable(id_, samplesToGet, MS_PER_PACKET)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// getData resets the format to internal hardware format, will have to be resampled
|
||||
@ -72,7 +101,7 @@ AudioInput::process()
|
||||
micData_.resize(samplesToGet);
|
||||
const auto samples = mainBuffer.getData(micData_, id_);
|
||||
if (samples != samplesToGet)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (muteState_) // audio is muted, set samples to 0
|
||||
micData_.reset();
|
||||
@ -87,20 +116,72 @@ AudioInput::process()
|
||||
}
|
||||
|
||||
auto audioFrame = resampled.toAVFrame();
|
||||
auto frame = audioFrame->pointer();
|
||||
auto ms = MediaStream("a:local", format_, sent_samples);
|
||||
frame->pts = sent_samples;
|
||||
sent_samples += frame->nb_samples;
|
||||
frame.copyFrom(*audioFrame);
|
||||
return true;
|
||||
}
|
||||
|
||||
{
|
||||
auto rec = recorder_.lock();
|
||||
if (rec && rec->isRecording()) {
|
||||
rec->recordData(frame, ms);
|
||||
}
|
||||
bool
|
||||
AudioInput::initDevice(const std::string& device)
|
||||
{
|
||||
devOpts_ = {};
|
||||
devOpts_.input = device;
|
||||
devOpts_.channel = format_.nb_channels;
|
||||
devOpts_.framerate = format_.sample_rate;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_future<DeviceParams>
|
||||
AudioInput::switchInput(const std::string& resource)
|
||||
{
|
||||
if (resource == currentResource_)
|
||||
return futureDevOpts_;
|
||||
|
||||
if (switchPending_) {
|
||||
RING_ERR() << "Audio switch already requested";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<AudioFrame> sharedFrame = std::move(audioFrame);
|
||||
notify(sharedFrame);
|
||||
RING_DBG() << "Switching audio source to match '" << resource << "'";
|
||||
|
||||
currentResource_ = resource;
|
||||
devOptsFound_ = false;
|
||||
|
||||
std::promise<DeviceParams> p;
|
||||
foundDevOpts_.swap(p);
|
||||
|
||||
if (resource.empty()) {
|
||||
devOpts_ = {};
|
||||
switchPending_ = true;
|
||||
futureDevOpts_ = foundDevOpts_.get_future();
|
||||
return futureDevOpts_;
|
||||
}
|
||||
|
||||
static const std::string sep = DRing::Media::VideoProtocolPrefix::SEPARATOR;
|
||||
|
||||
const auto pos = resource.find(sep);
|
||||
if (pos == std::string::npos)
|
||||
return {};
|
||||
|
||||
const auto prefix = resource.substr(0, pos);
|
||||
if ((pos + sep.size()) >= resource.size())
|
||||
return {};
|
||||
|
||||
const auto suffix = resource.substr(pos + sep.size());
|
||||
if (initDevice(suffix))
|
||||
foundDevOpts(devOpts_);
|
||||
|
||||
switchPending_ = true;
|
||||
futureDevOpts_ = foundDevOpts_.get_future().share();
|
||||
return futureDevOpts_;
|
||||
}
|
||||
|
||||
void
|
||||
AudioInput::foundDevOpts(const DeviceParams& params)
|
||||
{
|
||||
if (!devOptsFound_) {
|
||||
devOptsFound_ = true;
|
||||
foundDevOpts_.set_value(params);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -116,13 +197,6 @@ AudioInput::setMuted(bool isMuted)
|
||||
muteState_ = isMuted;
|
||||
}
|
||||
|
||||
std::shared_future<DeviceParams>
|
||||
AudioInput::switchInput(const std::string& resource)
|
||||
{
|
||||
// TODO not implemented yet
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
AudioInput::initRecorder(const std::shared_ptr<MediaRecorder>& rec)
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
* Copyright (C) 2018 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>
|
||||
* Author: Philippe Gorley <philippe.gorley@savoirfairelinux.com>
|
||||
* Author: Philippe Gorley <philippe.gorley@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
|
||||
@ -21,6 +21,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
|
||||
@ -49,6 +50,9 @@ public:
|
||||
void initRecorder(const std::shared_ptr<MediaRecorder>& rec);
|
||||
|
||||
private:
|
||||
bool nextFromDevice(AudioFrame& frame);
|
||||
bool initDevice(const std::string& device);
|
||||
|
||||
std::string id_;
|
||||
AudioBuffer micData_;
|
||||
bool muteState_ = false;
|
||||
@ -59,6 +63,14 @@ private:
|
||||
std::unique_ptr<Resampler> resampler_;
|
||||
std::weak_ptr<MediaRecorder> recorder_;
|
||||
|
||||
std::string currentResource_;
|
||||
std::atomic_bool switchPending_ {false};
|
||||
DeviceParams devOpts_;
|
||||
std::promise<DeviceParams> foundDevOpts_;
|
||||
std::shared_future<DeviceParams> futureDevOpts_;
|
||||
std::atomic_bool devOptsFound_ {false};
|
||||
void foundDevOpts(const DeviceParams& params);
|
||||
|
||||
ThreadLoop loop_;
|
||||
void process();
|
||||
};
|
||||
|
@ -48,6 +48,8 @@
|
||||
|
||||
namespace ring {
|
||||
|
||||
constexpr static auto NEWPARAMS_TIMEOUT = std::chrono::milliseconds(1000);
|
||||
|
||||
AudioRtpSession::AudioRtpSession(const std::string& id)
|
||||
: RtpSession(id)
|
||||
{
|
||||
@ -76,6 +78,22 @@ AudioRtpSession::startSender()
|
||||
if (sender_)
|
||||
RING_WARN("Restarting audio sender");
|
||||
|
||||
// sender sets up input correctly, we just keep a reference in case startSender is called
|
||||
audioInput_ = ring::getAudioInput(callID_);
|
||||
auto newParams = audioInput_->switchInput(input_);
|
||||
try {
|
||||
if (newParams.valid() &&
|
||||
newParams.wait_for(NEWPARAMS_TIMEOUT) == std::future_status::ready) {
|
||||
localAudioParams_ = newParams.get();
|
||||
} else {
|
||||
RING_ERR() << "No valid new audio parameters";
|
||||
return;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
RING_ERR() << "Exception while retrieving audio parameters: " << e.what();
|
||||
return;
|
||||
}
|
||||
|
||||
// be sure to not send any packets before saving last RTP seq value
|
||||
socketPair_->stopSendOp();
|
||||
if (sender_)
|
||||
|
@ -22,20 +22,22 @@
|
||||
#ifndef AUDIO_RTP_SESSION_H__
|
||||
#define AUDIO_RTP_SESSION_H__
|
||||
|
||||
#include "audiobuffer.h"
|
||||
#include "media_device.h"
|
||||
#include "threadloop.h"
|
||||
#include "media/rtp_session.h"
|
||||
#include "media/audio/audiobuffer.h"
|
||||
#include "rtp_session.h"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace ring {
|
||||
|
||||
class RingBuffer;
|
||||
class AudioSender;
|
||||
class AudioInput;
|
||||
class AudioReceiveThread;
|
||||
class AudioSender;
|
||||
class IceSocket;
|
||||
class MediaRecorder;
|
||||
class RingBuffer;
|
||||
|
||||
class AudioRtpSession : public RtpSession {
|
||||
public:
|
||||
@ -48,6 +50,7 @@ class AudioRtpSession : public RtpSession {
|
||||
void stop() override;
|
||||
void setMuted(bool isMuted);
|
||||
|
||||
void switchInput(const std::string& resource) { input_ = resource; }
|
||||
|
||||
void initRecorder(std::shared_ptr<MediaRecorder>& rec) override;
|
||||
|
||||
@ -57,9 +60,12 @@ class AudioRtpSession : public RtpSession {
|
||||
|
||||
std::unique_ptr<AudioSender> sender_;
|
||||
std::unique_ptr<AudioReceiveThread> receiveThread_;
|
||||
std::shared_ptr<AudioInput> audioInput_;
|
||||
std::shared_ptr<RingBuffer> ringbuffer_;
|
||||
uint16_t initSeqVal_ = 0;
|
||||
bool muteState_ = false;
|
||||
DeviceParams localAudioParams_;
|
||||
std::string input_;
|
||||
};
|
||||
|
||||
} // namespace ring
|
||||
|
@ -88,7 +88,7 @@ SIPCall::SIPCall(SIPAccountBase& account, const std::string& id, Call::CallType
|
||||
#ifdef RING_VIDEO
|
||||
// The ID is used to associate video streams to calls
|
||||
, videortp_(new video::VideoRtpSession(id, getVideoSettings()))
|
||||
, videoInput_(Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice())
|
||||
, mediaInput_(Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice())
|
||||
#endif
|
||||
, sdp_(new Sdp(id))
|
||||
{
|
||||
@ -631,8 +631,7 @@ SIPCall::internalOffHold(const std::function<void()>& sdp_cb)
|
||||
void
|
||||
SIPCall::switchInput(const std::string& resource)
|
||||
{
|
||||
#ifdef RING_VIDEO
|
||||
videoInput_ = resource;
|
||||
mediaInput_ = resource;
|
||||
if (isWaitingForIceAndMedia_) {
|
||||
remainingRequest_ = Request::SwitchInput;
|
||||
} else {
|
||||
@ -640,7 +639,6 @@ SIPCall::switchInput(const std::string& resource)
|
||||
isWaitingForIceAndMedia_ = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
@ -901,12 +899,13 @@ SIPCall::startAllMedia()
|
||||
}
|
||||
|
||||
auto new_mtu = transport_->getTlsMtu();
|
||||
if (local.type & MEDIA_AUDIO)
|
||||
avformatrtp_->switchInput(mediaInput_);
|
||||
avformatrtp_->setMtu(new_mtu);
|
||||
|
||||
#ifdef RING_VIDEO
|
||||
if (local.type == MEDIA_VIDEO)
|
||||
videortp_->switchInput(videoInput_);
|
||||
|
||||
if (local.type & MEDIA_VIDEO)
|
||||
videortp_->switchInput(mediaInput_);
|
||||
videortp_->setMtu(new_mtu);
|
||||
#endif
|
||||
rtp->updateMedia(remote, local);
|
||||
@ -925,7 +924,7 @@ SIPCall::startAllMedia()
|
||||
switch (local.type) {
|
||||
#ifdef RING_VIDEO
|
||||
case MEDIA_VIDEO:
|
||||
isVideoMuted_ = videoInput_.empty();
|
||||
isVideoMuted_ = mediaInput_.empty();
|
||||
break;
|
||||
#endif
|
||||
case MEDIA_AUDIO:
|
||||
@ -990,8 +989,8 @@ SIPCall::muteMedia(const std::string& mediaType, bool mute)
|
||||
if (mute == isVideoMuted_) return;
|
||||
RING_WARN("[call:%s] video muting %s", getCallId().c_str(), bool_to_str(mute));
|
||||
isVideoMuted_ = mute;
|
||||
videoInput_ = isVideoMuted_ ? "" : Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice();
|
||||
DRing::switchInput(getCallId(), videoInput_);
|
||||
mediaInput_ = isVideoMuted_ ? "" : Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice();
|
||||
DRing::switchInput(getCallId(), mediaInput_);
|
||||
if (not isSubcall())
|
||||
emitSignal<DRing::CallSignal::VideoMuted>(getCallId(), isVideoMuted_);
|
||||
#endif
|
||||
@ -1152,7 +1151,7 @@ SIPCall::getDetails() const
|
||||
|
||||
#ifdef RING_VIDEO
|
||||
// If Video is not enabled return an empty string
|
||||
details.emplace(DRing::Call::Details::VIDEO_SOURCE, acc.isVideoEnabled() ? videoInput_ : "");
|
||||
details.emplace(DRing::Call::Details::VIDEO_SOURCE, acc.isVideoEnabled() ? mediaInput_ : "");
|
||||
#endif
|
||||
|
||||
#if HAVE_RINGNS
|
||||
|
@ -256,10 +256,10 @@ private:
|
||||
* Video Rtp Session factory
|
||||
*/
|
||||
std::unique_ptr<video::VideoRtpSession> videortp_;
|
||||
|
||||
std::string videoInput_;
|
||||
#endif
|
||||
|
||||
std::string mediaInput_;
|
||||
|
||||
bool srtpEnabled_ {false};
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user