From a774e0f4b36fbad1169069570274d21766069d27 Mon Sep 17 00:00:00 2001 From: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com> Date: Fri, 10 Aug 2018 13:22:24 -0400 Subject: [PATCH] audio: implement audio_input class The audioInput class allows easy retrieval of local audio stream without being in a running call. For now it is only used to record audio data but might be employed for other purposes with little design changes. Change-Id: Icdf6a9799f341367b0c50aaff76b8642f42d0d1c --- src/media/audio/Makefile.am | 4 +- src/media/audio/audio_input.cpp | 124 ++++++++++++++++++++++++++ src/media/audio/audio_input.h | 60 +++++++++++++ src/media/audio/audio_rtp_session.cpp | 1 + 4 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 src/media/audio/audio_input.cpp create mode 100644 src/media/audio/audio_input.h diff --git a/src/media/audio/Makefile.am b/src/media/audio/Makefile.am index 904df3c81d..12bcbf2189 100644 --- a/src/media/audio/Makefile.am +++ b/src/media/audio/Makefile.am @@ -39,6 +39,7 @@ endif libaudio_la_SOURCES = \ audiobuffer.cpp \ + audio_input.cpp \ audioloop.cpp \ ringbuffer.cpp \ ringbufferpool.cpp \ @@ -59,13 +60,14 @@ endif noinst_HEADERS = \ audiobuffer.h \ + audio_input.h \ audioloop.h \ ringbuffer.h \ ringbufferpool.h \ audiolayer.h \ + resampler.h \ $(RING_SPEEXDSP_HEAD) \ dcblocker.h \ - resampler.h \ audio_rtp_session.h \ tonecontrol.h diff --git a/src/media/audio/audio_input.cpp b/src/media/audio/audio_input.cpp new file mode 100644 index 0000000000..295f1a9502 --- /dev/null +++ b/src/media/audio/audio_input.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2018 Savoir-faire Linux Inc. + * + * Author: Hugo Lefeuvre <hugo.lefeuvre@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 + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "audio_input.h" + +#include <future> +#include <chrono> +#include "socket_pair.h" +#include "audio/ringbufferpool.h" +#include "manager.h" +#include "smartools.h" + +namespace ring { namespace audio { + +AudioInput::AudioInput(const std::string& id) : + id_(id), + loop_([] {return true;}, // setup() + [this] {return process();}, + [this] {return cleanup();}) +{ + loop_.start(); +} + +AudioInput::~AudioInput() +{ + if (auto rec = recorder_.lock()) + rec->stopRecording(); + loop_.join(); +} + +// seq: frame number for video, sent samples audio +// sampleFreq: fps for video, sample rate for audio +// clock: stream time base (packetization interval times) +// FIXME duplicate code from media encoder +int64_t +getNextTimestamp(int64_t seq, rational<int64_t> sampleFreq, rational<int64_t> clock) +{ + return (seq / (sampleFreq * clock)).real<int64_t>(); +} + +void +AudioInput::process() +{ + auto& mainBuffer = Manager::instance().getRingBufferPool(); + auto ringBuffer = mainBuffer.getRingBuffer(id_); + auto bufferFormat = ringBuffer->getFormat(); + + // compute number of bytes contained in a frame with duration msPerPacket_ + auto bytesPerPacket = msPerPacket_ * bufferFormat.sample_rate; + const std::size_t bytesToGet = std::chrono::duration_cast<std::chrono::seconds>(bytesPerPacket).count(); + + if (ringBuffer->availableForGet(id_) < bytesToGet + && not ringBuffer->waitForDataAvailable(id_, bytesToGet)) { + return; + } + + // get data + micData_.setFormat(bufferFormat); + micData_.resize(bytesToGet); + const auto samples = ringBuffer->get(micData_, id_); + if (samples != bytesToGet) + return; + + if (muteState_) // audio is muted, set samples to 0 + micData_.reset(); + + // record frame + AVFrame* frame = micData_.toAVFrame(); + auto ms = MediaStream("a:local", micData_.getFormat()); + frame->pts = getNextTimestamp(sent_samples, ms.sampleRate, static_cast<rational<int64_t>>(ms.timeBase)); + sent_samples += frame->nb_samples; + + { + auto rec = recorder_.lock(); + if (rec && rec->isRecording()) { + rec->recordData(frame, ms); + } + } +} + +void +AudioInput::cleanup() +{ + micData_.clear(); +} + +void +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) +{ + rec->incrementExpectedStreams(1); + recorder_ = rec; +} + +}} // namespace ring::audio diff --git a/src/media/audio/audio_input.h b/src/media/audio/audio_input.h new file mode 100644 index 0000000000..040d68e9ce --- /dev/null +++ b/src/media/audio/audio_input.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 Savoir-faire Linux Inc. + * + * Author: Hugo Lefeuvre <hugo.lefeuvre@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 + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include <future> +#include "audio/audiobuffer.h" +#include "media/media_recorder.h" +#include "audio/resampler.h" + +namespace ring { +class MediaRecorder; +} + +namespace ring { namespace audio { + +class AudioInput +{ +public: + AudioInput(const std::string& id); + ~AudioInput(); + + std::shared_future<DeviceParams> switchInput(const std::string& resource); + + void setMuted(bool isMuted); + void initRecorder(const std::shared_ptr<MediaRecorder>& rec); + +private: + std::weak_ptr<MediaRecorder> recorder_; + uint64_t sent_samples = 0; + + std::string id_; + AudioBuffer micData_; + bool muteState_ = false; + + const std::chrono::milliseconds msPerPacket_ {20}; + + ThreadLoop loop_; + void process(); + void cleanup(); +}; + +}} // namespace ring::audio diff --git a/src/media/audio/audio_rtp_session.cpp b/src/media/audio/audio_rtp_session.cpp index aa996f8801..e354fd0681 100644 --- a/src/media/audio/audio_rtp_session.cpp +++ b/src/media/audio/audio_rtp_session.cpp @@ -222,6 +222,7 @@ AudioSender::process() if (audioEncoder_->encodeAudio(frame) < 0) RING_ERR("encoding failed"); } + void AudioSender::setMuted(bool isMuted) { -- GitLab