Move call device handling out of WebRTCSession
This commit is contained in:
parent
375e20462b
commit
f1bc3ba587
8 changed files with 509 additions and 396 deletions
433
src/CallDevices.cpp
Normal file
433
src/CallDevices.cpp
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include "CallDevices.h"
|
||||
#include "ChatPage.h"
|
||||
#include "Logging.h"
|
||||
#include "UserSettingsPage.h"
|
||||
|
||||
#ifdef GSTREAMER_AVAILABLE
|
||||
extern "C"
|
||||
{
|
||||
#include "gst/gst.h"
|
||||
}
|
||||
#endif
|
||||
|
||||
CallDevices::CallDevices()
|
||||
: QObject()
|
||||
{}
|
||||
|
||||
#ifdef GSTREAMER_AVAILABLE
|
||||
namespace {
|
||||
|
||||
struct AudioSource
|
||||
{
|
||||
std::string name;
|
||||
GstDevice *device;
|
||||
};
|
||||
|
||||
struct VideoSource
|
||||
{
|
||||
struct Caps
|
||||
{
|
||||
std::string resolution;
|
||||
std::vector<std::string> frameRates;
|
||||
};
|
||||
std::string name;
|
||||
GstDevice *device;
|
||||
std::vector<Caps> caps;
|
||||
};
|
||||
|
||||
std::vector<AudioSource> audioSources_;
|
||||
std::vector<VideoSource> videoSources_;
|
||||
|
||||
using FrameRate = std::pair<int, int>;
|
||||
std::optional<FrameRate>
|
||||
getFrameRate(const GValue *value)
|
||||
{
|
||||
if (GST_VALUE_HOLDS_FRACTION(value)) {
|
||||
gint num = gst_value_get_fraction_numerator(value);
|
||||
gint den = gst_value_get_fraction_denominator(value);
|
||||
return FrameRate{num, den};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void
|
||||
addFrameRate(std::vector<std::string> &rates, const FrameRate &rate)
|
||||
{
|
||||
constexpr double minimumFrameRate = 15.0;
|
||||
if (static_cast<double>(rate.first) / rate.second >= minimumFrameRate)
|
||||
rates.push_back(std::to_string(rate.first) + "/" + std::to_string(rate.second));
|
||||
}
|
||||
|
||||
void
|
||||
setDefaultDevice(bool isVideo)
|
||||
{
|
||||
auto settings = ChatPage::instance()->userSettings();
|
||||
if (isVideo && settings->camera().isEmpty()) {
|
||||
const VideoSource &camera = videoSources_.front();
|
||||
settings->setCamera(QString::fromStdString(camera.name));
|
||||
settings->setCameraResolution(
|
||||
QString::fromStdString(camera.caps.front().resolution));
|
||||
settings->setCameraFrameRate(
|
||||
QString::fromStdString(camera.caps.front().frameRates.front()));
|
||||
} else if (!isVideo && settings->microphone().isEmpty()) {
|
||||
settings->setMicrophone(QString::fromStdString(audioSources_.front().name));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
addDevice(GstDevice *device)
|
||||
{
|
||||
if (!device)
|
||||
return;
|
||||
|
||||
gchar *name = gst_device_get_display_name(device);
|
||||
gchar *type = gst_device_get_device_class(device);
|
||||
bool isVideo = !std::strncmp(type, "Video", 5);
|
||||
g_free(type);
|
||||
nhlog::ui()->debug("WebRTC: {} device added: {}", isVideo ? "video" : "audio", name);
|
||||
if (!isVideo) {
|
||||
audioSources_.push_back({name, device});
|
||||
g_free(name);
|
||||
setDefaultDevice(false);
|
||||
return;
|
||||
}
|
||||
|
||||
GstCaps *gstcaps = gst_device_get_caps(device);
|
||||
if (!gstcaps) {
|
||||
nhlog::ui()->debug("WebRTC: unable to get caps for {}", name);
|
||||
g_free(name);
|
||||
return;
|
||||
}
|
||||
|
||||
VideoSource source{name, device, {}};
|
||||
g_free(name);
|
||||
guint nCaps = gst_caps_get_size(gstcaps);
|
||||
for (guint i = 0; i < nCaps; ++i) {
|
||||
GstStructure *structure = gst_caps_get_structure(gstcaps, i);
|
||||
const gchar *name = gst_structure_get_name(structure);
|
||||
if (!std::strcmp(name, "video/x-raw")) {
|
||||
gint widthpx, heightpx;
|
||||
if (gst_structure_get(structure,
|
||||
"width",
|
||||
G_TYPE_INT,
|
||||
&widthpx,
|
||||
"height",
|
||||
G_TYPE_INT,
|
||||
&heightpx,
|
||||
nullptr)) {
|
||||
VideoSource::Caps caps;
|
||||
caps.resolution =
|
||||
std::to_string(widthpx) + "x" + std::to_string(heightpx);
|
||||
const GValue *value =
|
||||
gst_structure_get_value(structure, "framerate");
|
||||
if (auto fr = getFrameRate(value); fr)
|
||||
addFrameRate(caps.frameRates, *fr);
|
||||
else if (GST_VALUE_HOLDS_FRACTION_RANGE(value)) {
|
||||
addFrameRate(
|
||||
caps.frameRates,
|
||||
*getFrameRate(gst_value_get_fraction_range_min(value)));
|
||||
addFrameRate(
|
||||
caps.frameRates,
|
||||
*getFrameRate(gst_value_get_fraction_range_max(value)));
|
||||
} else if (GST_VALUE_HOLDS_LIST(value)) {
|
||||
guint nRates = gst_value_list_get_size(value);
|
||||
for (guint j = 0; j < nRates; ++j) {
|
||||
const GValue *rate =
|
||||
gst_value_list_get_value(value, j);
|
||||
if (auto fr = getFrameRate(rate); fr)
|
||||
addFrameRate(caps.frameRates, *fr);
|
||||
}
|
||||
}
|
||||
if (!caps.frameRates.empty())
|
||||
source.caps.push_back(std::move(caps));
|
||||
}
|
||||
}
|
||||
}
|
||||
gst_caps_unref(gstcaps);
|
||||
videoSources_.push_back(std::move(source));
|
||||
setDefaultDevice(true);
|
||||
}
|
||||
|
||||
#if GST_CHECK_VERSION(1, 18, 0)
|
||||
template<typename T>
|
||||
bool
|
||||
removeDevice(T &sources, GstDevice *device, bool changed)
|
||||
{
|
||||
if (auto it = std::find_if(sources.begin(),
|
||||
sources.end(),
|
||||
[device](const auto &s) { return s.device == device; });
|
||||
it != sources.end()) {
|
||||
nhlog::ui()->debug(std::string("WebRTC: device ") +
|
||||
(changed ? "changed: " : "removed: ") + "{}",
|
||||
it->name);
|
||||
gst_object_unref(device);
|
||||
sources.erase(it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
removeDevice(GstDevice *device, bool changed)
|
||||
{
|
||||
if (device) {
|
||||
if (removeDevice(audioSources_, device, changed) ||
|
||||
removeDevice(videoSources_, device, changed))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data G_GNUC_UNUSED)
|
||||
{
|
||||
switch (GST_MESSAGE_TYPE(msg)) {
|
||||
case GST_MESSAGE_DEVICE_ADDED: {
|
||||
GstDevice *device;
|
||||
gst_message_parse_device_added(msg, &device);
|
||||
addDevice(device);
|
||||
emit CallDevices::instance().devicesChanged();
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_DEVICE_REMOVED: {
|
||||
GstDevice *device;
|
||||
gst_message_parse_device_removed(msg, &device);
|
||||
removeDevice(device, false);
|
||||
emit CallDevices::instance().devicesChanged();
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_DEVICE_CHANGED: {
|
||||
GstDevice *device;
|
||||
GstDevice *oldDevice;
|
||||
gst_message_parse_device_changed(msg, &device, &oldDevice);
|
||||
removeDevice(oldDevice, true);
|
||||
addDevice(device);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
std::vector<std::string>
|
||||
deviceNames(T &sources, const std::string &defaultDevice)
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
ret.reserve(sources.size());
|
||||
for (const auto &s : sources)
|
||||
ret.push_back(s.name);
|
||||
|
||||
// move default device to top of the list
|
||||
if (auto it = std::find(ret.begin(), ret.end(), defaultDevice); it != ret.end())
|
||||
std::swap(ret.front(), *it);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<VideoSource>
|
||||
getVideoSource(const std::string &cameraName)
|
||||
{
|
||||
if (auto it = std::find_if(videoSources_.cbegin(),
|
||||
videoSources_.cend(),
|
||||
[&cameraName](const auto &s) { return s.name == cameraName; });
|
||||
it != videoSources_.cend()) {
|
||||
return *it;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::pair<int, int>
|
||||
tokenise(std::string_view str, char delim)
|
||||
{
|
||||
std::pair<int, int> ret;
|
||||
ret.first = std::atoi(str.data());
|
||||
auto pos = str.find_first_of(delim);
|
||||
ret.second = std::atoi(str.data() + pos + 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
CallDevices::init()
|
||||
{
|
||||
#if GST_CHECK_VERSION(1, 18, 0)
|
||||
static GstDeviceMonitor *monitor = nullptr;
|
||||
if (!monitor) {
|
||||
monitor = gst_device_monitor_new();
|
||||
GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw");
|
||||
gst_device_monitor_add_filter(monitor, "Audio/Source", caps);
|
||||
gst_caps_unref(caps);
|
||||
caps = gst_caps_new_empty_simple("video/x-raw");
|
||||
gst_device_monitor_add_filter(monitor, "Video/Source", caps);
|
||||
gst_caps_unref(caps);
|
||||
|
||||
GstBus *bus = gst_device_monitor_get_bus(monitor);
|
||||
gst_bus_add_watch(bus, newBusMessage, nullptr);
|
||||
gst_object_unref(bus);
|
||||
if (!gst_device_monitor_start(monitor)) {
|
||||
nhlog::ui()->error("WebRTC: failed to start device monitor");
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
CallDevices::refresh()
|
||||
{
|
||||
#if !GST_CHECK_VERSION(1, 18, 0)
|
||||
|
||||
static GstDeviceMonitor *monitor = nullptr;
|
||||
if (!monitor) {
|
||||
monitor = gst_device_monitor_new();
|
||||
GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw");
|
||||
gst_device_monitor_add_filter(monitor, "Audio/Source", caps);
|
||||
gst_caps_unref(caps);
|
||||
caps = gst_caps_new_empty_simple("video/x-raw");
|
||||
gst_device_monitor_add_filter(monitor, "Video/Source", caps);
|
||||
gst_caps_unref(caps);
|
||||
}
|
||||
|
||||
auto clearDevices = [](auto &sources) {
|
||||
std::for_each(
|
||||
sources.begin(), sources.end(), [](auto &s) { gst_object_unref(s.device); });
|
||||
sources.clear();
|
||||
};
|
||||
clearDevices(audioSources_);
|
||||
clearDevices(videoSources_);
|
||||
|
||||
GList *devices = gst_device_monitor_get_devices(monitor);
|
||||
if (devices) {
|
||||
for (GList *l = devices; l != nullptr; l = l->next)
|
||||
addDevice(GST_DEVICE_CAST(l->data));
|
||||
g_list_free(devices);
|
||||
}
|
||||
emit devicesChanged();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
CallDevices::haveMic() const
|
||||
{
|
||||
return !audioSources_.empty();
|
||||
}
|
||||
|
||||
bool
|
||||
CallDevices::haveCamera() const
|
||||
{
|
||||
return !videoSources_.empty();
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
CallDevices::names(bool isVideo, const std::string &defaultDevice) const
|
||||
{
|
||||
return isVideo ? deviceNames(videoSources_, defaultDevice)
|
||||
: deviceNames(audioSources_, defaultDevice);
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
CallDevices::resolutions(const std::string &cameraName) const
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
if (auto s = getVideoSource(cameraName); s) {
|
||||
ret.reserve(s->caps.size());
|
||||
for (const auto &c : s->caps)
|
||||
ret.push_back(c.resolution);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
CallDevices::frameRates(const std::string &cameraName, const std::string &resolution) const
|
||||
{
|
||||
if (auto s = getVideoSource(cameraName); s) {
|
||||
if (auto it =
|
||||
std::find_if(s->caps.cbegin(),
|
||||
s->caps.cend(),
|
||||
[&](const auto &c) { return c.resolution == resolution; });
|
||||
it != s->caps.cend())
|
||||
return it->frameRates;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
GstDevice *
|
||||
CallDevices::audioDevice() const
|
||||
{
|
||||
std::string name = ChatPage::instance()->userSettings()->microphone().toStdString();
|
||||
if (auto it = std::find_if(audioSources_.cbegin(),
|
||||
audioSources_.cend(),
|
||||
[&name](const auto &s) { return s.name == name; });
|
||||
it != audioSources_.cend()) {
|
||||
nhlog::ui()->debug("WebRTC: microphone: {}", name);
|
||||
return it->device;
|
||||
} else {
|
||||
nhlog::ui()->error("WebRTC: unknown microphone: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
GstDevice *
|
||||
CallDevices::videoDevice(std::pair<int, int> &resolution, std::pair<int, int> &frameRate) const
|
||||
{
|
||||
auto settings = ChatPage::instance()->userSettings();
|
||||
std::string name = settings->camera().toStdString();
|
||||
if (auto s = getVideoSource(name); s) {
|
||||
nhlog::ui()->debug("WebRTC: camera: {}", name);
|
||||
resolution = tokenise(settings->cameraResolution().toStdString(), 'x');
|
||||
frameRate = tokenise(settings->cameraFrameRate().toStdString(), '/');
|
||||
nhlog::ui()->debug(
|
||||
"WebRTC: camera resolution: {}x{}", resolution.first, resolution.second);
|
||||
nhlog::ui()->debug(
|
||||
"WebRTC: camera frame rate: {}/{}", frameRate.first, frameRate.second);
|
||||
return s->device;
|
||||
} else {
|
||||
nhlog::ui()->error("WebRTC: unknown camera: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void
|
||||
CallDevices::refresh()
|
||||
{}
|
||||
|
||||
bool
|
||||
CallDevices::haveMic() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
CallDevices::haveCamera() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
CallDevices::names(bool, const std::string &) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
CallDevices::resolutions(const std::string &) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
CallDevices::frameRates(const std::string &, const std::string &) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue