Commit 10126454 authored by Philippe Gorley's avatar Philippe Gorley

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
parent f9bdc9f2
......@@ -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};
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment