Merge branch 'main' into full-webrtc
# Conflicts: # src/WebRTCDSP.cpp
This commit is contained in:
commit
45e6daf262
6 changed files with 190 additions and 24 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
#include "AudioProcessor.h"
|
#include "AudioProcessor.h"
|
||||||
|
|
||||||
#include "SpeexDSP.h"
|
#include "SpeexDSP.h"
|
||||||
|
#include "Timer.h"
|
||||||
#include "WebRTCDSP.h"
|
#include "WebRTCDSP.h"
|
||||||
|
|
||||||
#include <QAudioBuffer>
|
#include <QAudioBuffer>
|
||||||
|
|
@ -9,7 +10,7 @@
|
||||||
namespace SpeexWebRTCTest {
|
namespace SpeexWebRTCTest {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
Q_LOGGING_CATEGORY(AudioProcessor, "processor")
|
Q_LOGGING_CATEGORY(processor, "processor")
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<qreal> calculateAudioLevels(const QAudioBuffer& buffer);
|
QVector<qreal> calculateAudioLevels(const QAudioBuffer& buffer);
|
||||||
|
|
@ -78,6 +79,8 @@ void AudioProcessor::process()
|
||||||
{
|
{
|
||||||
while (doWork_)
|
while (doWork_)
|
||||||
{
|
{
|
||||||
|
auto waitUntil = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(5);
|
||||||
|
|
||||||
const std::size_t bytesToRead =
|
const std::size_t bytesToRead =
|
||||||
bufferSize_ * format_.sampleSize() / 8 * format_.channelCount();
|
bufferSize_ * format_.sampleSize() / 8 * format_.channelCount();
|
||||||
const std::size_t monitorToRead =
|
const std::size_t monitorToRead =
|
||||||
|
|
@ -122,12 +125,14 @@ void AudioProcessor::process()
|
||||||
emit readyRead();
|
emit readyRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
std::this_thread::sleep_until(waitUntil);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioProcessor::processBuffer(QAudioBuffer& inputBuffer, const QAudioBuffer& monitorBuffer)
|
void AudioProcessor::processBuffer(QAudioBuffer& inputBuffer, const QAudioBuffer& monitorBuffer)
|
||||||
{
|
{
|
||||||
|
TIMER(qDebug(processor))
|
||||||
|
|
||||||
QVector<qreal> inputLevels = calculateAudioLevels(inputBuffer);
|
QVector<qreal> inputLevels = calculateAudioLevels(inputBuffer);
|
||||||
|
|
||||||
if (dsp_)
|
if (dsp_)
|
||||||
|
|
@ -157,6 +162,10 @@ qint64 AudioProcessor::bytesAvailable() const
|
||||||
|
|
||||||
bool AudioProcessor::open(QIODevice::OpenMode mode)
|
bool AudioProcessor::open(QIODevice::OpenMode mode)
|
||||||
{
|
{
|
||||||
|
std::unique_lock<std::mutex> lock1(inputMutex_);
|
||||||
|
std::unique_lock<std::mutex> lock2(outputMutex_);
|
||||||
|
std::unique_lock<std::mutex> lock3(monitorMutex_);
|
||||||
|
|
||||||
inputBuffer_.clear();
|
inputBuffer_.clear();
|
||||||
outputBuffer_.clear();
|
outputBuffer_.clear();
|
||||||
monitorBuffer_.clear();
|
monitorBuffer_.clear();
|
||||||
|
|
@ -195,6 +204,14 @@ Backend AudioProcessor::getCurrentBackend() const
|
||||||
|
|
||||||
void AudioProcessor::switchBackend(Backend backend)
|
void AudioProcessor::switchBackend(Backend backend)
|
||||||
{
|
{
|
||||||
|
std::unique_lock<std::mutex> lock1(inputMutex_);
|
||||||
|
std::unique_lock<std::mutex> lock2(outputMutex_);
|
||||||
|
std::unique_lock<std::mutex> lock3(monitorMutex_);
|
||||||
|
|
||||||
|
inputBuffer_.clear();
|
||||||
|
outputBuffer_.clear();
|
||||||
|
monitorBuffer_.clear();
|
||||||
|
|
||||||
if (backend == Backend::Speex)
|
if (backend == Backend::Speex)
|
||||||
dsp_.reset(new SpeexDSP(format_, monitorFormat_));
|
dsp_.reset(new SpeexDSP(format_, monitorFormat_));
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ file(GLOB SOURCES *.cpp)
|
||||||
file(GLOB HEADERS *.h)
|
file(GLOB HEADERS *.h)
|
||||||
file(GLOB_RECURSE RESOURCES *.qrc)
|
file(GLOB_RECURSE RESOURCES *.qrc)
|
||||||
|
|
||||||
if (WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Release")
|
#if (WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
add_executable(${TARGET_NAME} WIN32 ${SOURCES} ${HEADERS} ${RESOURCES})
|
# add_executable(${TARGET_NAME} WIN32 ${SOURCES} ${HEADERS} ${RESOURCES})
|
||||||
else()
|
#else()
|
||||||
add_executable(${TARGET_NAME} ${SOURCES} ${HEADERS} ${RESOURCES})
|
add_executable(${TARGET_NAME} ${SOURCES} ${HEADERS} ${RESOURCES})
|
||||||
endif()
|
#endif()
|
||||||
|
|
||||||
target_link_libraries(${TARGET_NAME}
|
target_link_libraries(${TARGET_NAME}
|
||||||
speexdsp
|
speexdsp
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,18 @@ MainWindow::~MainWindow()
|
||||||
stopRecording();
|
stopRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fixFormatForDevice(QAudioFormat& format, const QAudioDeviceInfo& info)
|
||||||
|
{
|
||||||
|
if (!info.isFormatSupported(format))
|
||||||
|
{
|
||||||
|
QAudioFormat newFormat = info.nearestFormat(format);
|
||||||
|
qWarning(Gui).nospace() << "Preferred format " << format << " is not supported by device "
|
||||||
|
<< info.deviceName() << ".";
|
||||||
|
qWarning(Gui) << "Trying to use nearest format" << newFormat;
|
||||||
|
format = newFormat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::initializeAudio(const QAudioDeviceInfo& inputDeviceInfo,
|
void MainWindow::initializeAudio(const QAudioDeviceInfo& inputDeviceInfo,
|
||||||
const QAudioDeviceInfo& outputDeviceInfo,
|
const QAudioDeviceInfo& outputDeviceInfo,
|
||||||
const QAudioDeviceInfo& monitorDeviceInfo)
|
const QAudioDeviceInfo& monitorDeviceInfo)
|
||||||
|
|
@ -94,24 +106,9 @@ void MainWindow::initializeAudio(const QAudioDeviceInfo& inputDeviceInfo,
|
||||||
auto outputFormat = getOutputFormat();
|
auto outputFormat = getOutputFormat();
|
||||||
auto monitorFormat = getMonitorFormat();
|
auto monitorFormat = getMonitorFormat();
|
||||||
|
|
||||||
if (!inputDeviceInfo.isFormatSupported(captureFormat))
|
fixFormatForDevice(captureFormat, inputDeviceInfo);
|
||||||
{
|
fixFormatForDevice(outputFormat, outputDeviceInfo);
|
||||||
qWarning(Gui)
|
fixFormatForDevice(monitorFormat, monitorDeviceInfo);
|
||||||
<< "Preferred format is not supported by input device - trying to use nearest";
|
|
||||||
captureFormat = inputDeviceInfo.nearestFormat(captureFormat);
|
|
||||||
}
|
|
||||||
if (!monitorDeviceInfo.isFormatSupported(monitorFormat))
|
|
||||||
{
|
|
||||||
qWarning(Gui)
|
|
||||||
<< "Preferred format is not supported by monitor device - trying to use nearest";
|
|
||||||
monitorFormat = inputDeviceInfo.nearestFormat(monitorFormat);
|
|
||||||
}
|
|
||||||
if (!outputDeviceInfo.isFormatSupported(outputFormat))
|
|
||||||
{
|
|
||||||
qWarning(Gui)
|
|
||||||
<< "Preferred format is not supported by output device - trying to use nearest";
|
|
||||||
outputFormat = inputDeviceInfo.nearestFormat(outputFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
audioInput_.reset(new QAudioInput(inputDeviceInfo, captureFormat));
|
audioInput_.reset(new QAudioInput(inputDeviceInfo, captureFormat));
|
||||||
audioOutput_.reset(new QAudioOutput(outputDeviceInfo, outputFormat));
|
audioOutput_.reset(new QAudioOutput(outputDeviceInfo, outputFormat));
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#include "SpeexDSP.h"
|
#include "SpeexDSP.h"
|
||||||
|
|
||||||
|
#include "Timer.h"
|
||||||
|
|
||||||
#include <speex/speex_echo.h>
|
#include <speex/speex_echo.h>
|
||||||
#include <speex/speex_preprocess.h>
|
#include <speex/speex_preprocess.h>
|
||||||
|
|
||||||
|
|
@ -36,6 +38,8 @@ SpeexDSP::~SpeexDSP()
|
||||||
|
|
||||||
void SpeexDSP::processFrame(QAudioBuffer& mainBuffer, const QAudioBuffer& auxBuffer)
|
void SpeexDSP::processFrame(QAudioBuffer& mainBuffer, const QAudioBuffer& auxBuffer)
|
||||||
{
|
{
|
||||||
|
TIMER(qDebug(Speex))
|
||||||
|
|
||||||
qDebug(Speex).noquote() << QString(
|
qDebug(Speex).noquote() << QString(
|
||||||
"got %1 near-end samples (%2ms) and %3 far-end samples (%4ms)")
|
"got %1 near-end samples (%2ms) and %3 far-end samples (%4ms)")
|
||||||
.arg(mainBuffer.frameCount())
|
.arg(mainBuffer.frameCount())
|
||||||
|
|
|
||||||
144
src/Timer.h
Normal file
144
src/Timer.h
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
#ifndef _SPEEX_WEBRTC_TEST_TIMER_H_
|
||||||
|
#define _SPEEX_WEBRTC_TEST_TIMER_H_
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace SpeexWebRTCTest {
|
||||||
|
|
||||||
|
namespace _detail {
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
struct is_one_of;
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
struct is_one_of<T, U>
|
||||||
|
{
|
||||||
|
static constexpr bool value = std::is_same<T, U>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename U, typename... Ts>
|
||||||
|
struct is_one_of<T, U, Ts...>
|
||||||
|
{
|
||||||
|
static constexpr bool value = std::is_same<T, U>::value || is_one_of<T, Ts...>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <class Duration,
|
||||||
|
typename = typename std::enable_if<is_one_of<Duration,
|
||||||
|
std::chrono::hours,
|
||||||
|
std::chrono::minutes,
|
||||||
|
std::chrono::seconds,
|
||||||
|
std::chrono::milliseconds,
|
||||||
|
std::chrono::microseconds,
|
||||||
|
std::chrono::nanoseconds>::value>::type>
|
||||||
|
constexpr const char* duration_suffix()
|
||||||
|
{
|
||||||
|
if (std::is_same<Duration, std::chrono::hours>::value)
|
||||||
|
{
|
||||||
|
return "h";
|
||||||
|
}
|
||||||
|
else if (std::is_same<Duration, std::chrono::minutes>::value)
|
||||||
|
{
|
||||||
|
return "min";
|
||||||
|
}
|
||||||
|
else if (std::is_same<Duration, std::chrono::seconds>::value)
|
||||||
|
{
|
||||||
|
return "s";
|
||||||
|
}
|
||||||
|
else if (std::is_same<Duration, std::chrono::milliseconds>::value)
|
||||||
|
{
|
||||||
|
return "ms";
|
||||||
|
}
|
||||||
|
else if (std::is_same<Duration, std::chrono::microseconds>::value)
|
||||||
|
{
|
||||||
|
return "us";
|
||||||
|
}
|
||||||
|
else if (std::is_same<Duration, std::chrono::nanoseconds>::value)
|
||||||
|
{
|
||||||
|
return "ns";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int, class DurationIn>
|
||||||
|
std::string format_impl(DurationIn)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int count, class DurationIn, class FirstDuration, class... RestDurations>
|
||||||
|
std::string format_impl(DurationIn d)
|
||||||
|
{
|
||||||
|
std::string out;
|
||||||
|
|
||||||
|
auto val = std::chrono::duration_cast<FirstDuration>(d);
|
||||||
|
if (val.count() != 0)
|
||||||
|
{
|
||||||
|
out = std::to_string(val.count()) + duration_suffix<FirstDuration>();
|
||||||
|
|
||||||
|
if (sizeof...(RestDurations) > 0)
|
||||||
|
{
|
||||||
|
out += " " + format_impl<count + 1, DurationIn, RestDurations...>(d - val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (sizeof...(RestDurations) > 0)
|
||||||
|
{
|
||||||
|
out = format_impl<count, DurationIn, RestDurations...>(d);
|
||||||
|
}
|
||||||
|
else if (count == 0)
|
||||||
|
{
|
||||||
|
out = std::to_string(0) + duration_suffix<FirstDuration>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace _detail
|
||||||
|
|
||||||
|
template <class... RestDurations, class DurationIn>
|
||||||
|
std::string format(DurationIn d)
|
||||||
|
{
|
||||||
|
return _detail::format_impl<0, DurationIn, RestDurations...>(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Durations>
|
||||||
|
class Timer final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Timer(const QDebug& debug, QString name = "") : debug_(debug), name_(std::move(name))
|
||||||
|
{
|
||||||
|
start_ = std::chrono::high_resolution_clock::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
~Timer()
|
||||||
|
{
|
||||||
|
TimePoint stop = std::chrono::high_resolution_clock::now();
|
||||||
|
if (name_.isEmpty())
|
||||||
|
debug_.nospace().noquote() << "Timer: " << format<Durations...>(stop - start_).c_str();
|
||||||
|
else
|
||||||
|
debug_.nospace().noquote()
|
||||||
|
<< "Timer [" << name_ << "]: " << format<Durations...>(stop - start_).c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using TimePoint = std::chrono::time_point<std::chrono::high_resolution_clock,
|
||||||
|
std::chrono::high_resolution_clock::duration>;
|
||||||
|
QDebug debug_;
|
||||||
|
QString name_;
|
||||||
|
TimePoint start_;
|
||||||
|
};
|
||||||
|
|
||||||
|
using MsTimer = Timer<std::chrono::seconds, std::chrono::milliseconds, std::chrono::microseconds>;
|
||||||
|
|
||||||
|
} // namespace SpeexWebRTCTest
|
||||||
|
|
||||||
|
#define TIMER(debug) MsTimer timer(debug, __func__);
|
||||||
|
|
||||||
|
#endif //_SPEEX_WEBRTC_TEST_TIMER_H_
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#include "WebRTCDSP.h"
|
#include "WebRTCDSP.h"
|
||||||
|
|
||||||
|
#include "Timer.h"
|
||||||
|
|
||||||
#include <webrtc/api/audio/audio_frame.h>
|
#include <webrtc/api/audio/audio_frame.h>
|
||||||
#include <webrtc/modules/audio_processing/include/audio_processing.h>
|
#include <webrtc/modules/audio_processing/include/audio_processing.h>
|
||||||
|
|
||||||
|
|
@ -110,6 +112,8 @@ QString errorDescription(int error)
|
||||||
|
|
||||||
void WebRTCDSP::processFrame(QAudioBuffer& mainBuffer, const QAudioBuffer& auxBuffer)
|
void WebRTCDSP::processFrame(QAudioBuffer& mainBuffer, const QAudioBuffer& auxBuffer)
|
||||||
{
|
{
|
||||||
|
TIMER(qDebug(WebRTC))
|
||||||
|
|
||||||
qDebug(WebRTC).noquote() << QString(
|
qDebug(WebRTC).noquote() << QString(
|
||||||
"got %1 near-end samples at %2 Hz (%3ms) and %4 far-end "
|
"got %1 near-end samples at %2 Hz (%3ms) and %4 far-end "
|
||||||
"samples at %5 (%6ms)")
|
"samples at %5 (%6ms)")
|
||||||
|
|
|
||||||
Reference in a new issue