Commit bdd139c9 authored by Adrien Béraud's avatar Adrien Béraud Committed by Guillaume Roguez

video_device: add default settings selection logic

* use default settings if no preference exists
* logic requires unformated parameters (added related methods)
* current logic: largest resolution <= 720P, framerate >= 10 FPS
* support new pixel format
* report pixel format to libav/ffmpeg for decoding

Tuleap: #294
Change-Id: Ia9b61814b49e7057ae4eb5fef9d0b814706cafff
parent 450390a0
......@@ -90,8 +90,11 @@ int MediaDecoder::openInput(const DeviceParams& params)
av_dict_set(&options_, "reorder_queue_size",ring::to_string(jitterBufferMaxSize_).c_str(), 0);
av_dict_set(&options_, "max_delay",ring::to_string(jitterBufferMaxDelay_).c_str(), 0);
RING_DBG("Trying to open device %s with format %s", params.input.c_str(),
params.format.c_str());
if(!params.pixel_format.empty()){
av_dict_set(&options_, "pixel_format", params.pixel_format.c_str(), 0);
}
RING_DBG("Trying to open device %s with format %s, pixel format %s, size %dx%d, rate %lf", params.input.c_str(),
params.format.c_str(), params.pixel_format.c_str(), params.width, params.height, params.framerate.real());
int ret = avformat_open_input(
&inputCtx_,
params.input.c_str(),
......
......@@ -33,10 +33,13 @@ namespace ring {
* to open a LibAV device/stream
*/
struct DeviceParams {
std::string name {};
std::string input {}; // Device path (e.g. /dev/video0)
std::string format {};
unsigned width {}, height {};
rational<double> framerate {};
std::string pixel_format {};
std::string channel_name {};
unsigned channel {}; // Channel number
std::string loop {};
std::string sdp_flags {};
......
......@@ -35,13 +35,6 @@
namespace ring { namespace video {
class OSXVideoSize {
public:
OSXVideoSize(const unsigned width, const unsigned height);
unsigned width;
unsigned height;
};
class VideoDeviceImpl {
public:
/**
......@@ -53,22 +46,19 @@ class VideoDeviceImpl {
std::string name;
std::vector<std::string> getChannelList() const;
std::vector<std::string> getSizeList(const std::string& channel) const;
std::vector<std::string> getSizeList() const;
std::vector<std::string> getRateList(const std::string& channel, const std::string& size) const;
float getRate(unsigned rate) const;
VideoSettings getSettings() const;
void applySettings(VideoSettings settings);
std::vector<VideoSize> getSizeList(const std::string& channel) const;
std::vector<VideoSize> getSizeList() const;
std::vector<FrameRate> getRateList(const std::string& channel, VideoSize size) const;
DeviceParams getDeviceParams() const;
void setDeviceParams(const DeviceParams&);
private:
const OSXVideoSize extractSize(const std::string &name) const;
VideoSize extractSize(VideoSize) const;
AVCaptureDevice* avDevice_;
std::vector<OSXVideoSize> available_sizes_;
OSXVideoSize current_size_;
std::vector<VideoSize> available_sizes_;
VideoSize current_size_;
};
VideoDeviceImpl::VideoDeviceImpl(const std::string& uniqueID)
......@@ -79,34 +69,18 @@ VideoDeviceImpl::VideoDeviceImpl(const std::string& uniqueID)
{
name = [[avDevice_ localizedName] UTF8String];
available_sizes_.reserve(avDevice_.formats.count);
for (AVCaptureDeviceFormat* format in avDevice_.formats) {
std::stringstream ss;
auto dimensions = CMVideoFormatDescriptionGetDimensions(format.formatDescription);
OSXVideoSize size(dimensions.width, dimensions.height);
available_sizes_.push_back(size);
available_sizes_.emplace_back(dimensions.width, dimensions.height);
}
// Set default settings
applySettings(VideoSettings());
}
OSXVideoSize::OSXVideoSize(const unsigned width, const unsigned height) :
width(width), height(height) {}
void
VideoDeviceImpl::applySettings(VideoSettings settings)
{
//TODO: add framerate
// rate_ = size_.getRate(settings["rate"]);
current_size_ = extractSize(settings.video_size);
}
const OSXVideoSize
VideoDeviceImpl::extractSize(const std::string &name) const
VideoSize
VideoDeviceImpl::extractSize(VideoSize size) const
{
for (const auto item : available_sizes_) {
std::stringstream ss;
ss << item.width << "x" << item.height;
if (ss.str() == name)
if (item.first == size.first && item.second == size.second)
return item;
}
......@@ -114,19 +88,19 @@ VideoDeviceImpl::extractSize(const std::string &name) const
if (!available_sizes_.empty()) {
return available_sizes_.back();
}
return OSXVideoSize(-1, -1);
return VideoSize(0, 0);
}
DeviceParams
VideoDeviceImpl::getDeviceParams() const
{
DeviceParams params;
params.name = [[avDevice_ localizedName] UTF8String];
params.input = "[" + device + "]";
params.format = "avfoundation";
params.width = current_size_.width;
params.height = current_size_.height;
params.width = current_size_.first;
params.height = current_size_.second;
auto format = [avDevice_ activeFormat];
auto frameRate = (AVFrameRateRange*)
......@@ -135,21 +109,41 @@ VideoDeviceImpl::getDeviceParams() const
return params;
}
VideoSettings
VideoDeviceImpl::getSettings() const
void
VideoDeviceImpl::setDeviceParams(const DeviceParams& params)
{
VideoSettings settings;
//TODO: add framerate
// rate_ = size_.getRate(settings["rate"]);
current_size_ = extractSize({params.width, params.height});
}
settings.name = [[avDevice_ localizedName] UTF8String];
std::vector<VideoSize>
VideoDeviceImpl::getSizeList() const
{
return getSizeList("default");
}
std::vector<FrameRate>
VideoDeviceImpl::getRateList(const std::string& channel, VideoSize size) const
{
auto format = [avDevice_ activeFormat];
auto frameRate = (AVFrameRateRange*)
[format.videoSupportedFrameRateRanges objectAtIndex:0];
settings.framerate = frameRate.maxFrameRate;
settings.video_size = std::to_string(current_size_.width) +
"x" + std::to_string(current_size_.height);
std::vector<FrameRate> v;
v.reserve(format.videoSupportedFrameRateRanges.count);
for (AVFrameRateRange* frameRateRange in format.videoSupportedFrameRateRanges)
v.emplace_back(frameRateRange.maxFrameRate);
return v;
}
return settings;
std::vector<VideoSize>
VideoDeviceImpl::getSizeList(const std::string& channel) const
{
return available_sizes_;
}
std::vector<std::string>
VideoDeviceImpl::getChannelList() const
{
return {"default"};
}
VideoDevice::VideoDevice(const std::string& path) :
......@@ -166,66 +160,27 @@ VideoDevice::getDeviceParams() const
}
void
VideoDevice::applySettings(VideoSettings settings)
{
deviceImpl_->applySettings(settings);
}
VideoSettings
VideoDevice::getSettings() const
{
return deviceImpl_->getSettings();
}
std::vector<std::string>
VideoDeviceImpl::getSizeList() const
{
return getSizeList("default");
}
std::vector<std::string>
VideoDeviceImpl::getRateList(const std::string& channel, const std::string& size) const
VideoDevice::setDeviceParams(const DeviceParams& params)
{
auto format = [avDevice_ activeFormat];
std::vector<std::string> v;
for (AVFrameRateRange* frameRateRange in format.videoSupportedFrameRateRanges) {
std::stringstream ss;
ss << frameRateRange.maxFrameRate;
v.push_back(ss.str());
}
return v;
return deviceImpl_->setDeviceParams(params);
}
std::vector<std::string>
VideoDeviceImpl::getSizeList(const std::string& channel) const
VideoDevice::getChannelList() const
{
std::vector<std::string> v;
for (const auto &item : available_sizes_) {
std::stringstream ss;
ss << item.width << "x" << item.height;
v.push_back(ss.str());
}
return v;
return deviceImpl_->getChannelList();
}
std::vector<std::string> VideoDeviceImpl::getChannelList() const
std::vector<VideoSize>
VideoDevice::getSizeList(const std::string& channel) const
{
return {"default"};
return deviceImpl_->getSizeList(channel);
}
DRing::VideoCapabilities
VideoDevice::getCapabilities() const
std::vector<FrameRate>
VideoDevice::getRateList(const std::string& channel, VideoSize size) const
{
DRing::VideoCapabilities cap;
for (const auto& chan : deviceImpl_->getChannelList())
for (const auto& size : deviceImpl_->getSizeList(chan))
cap[chan][size] = deviceImpl_->getRateList(chan, size);
return cap;
return deviceImpl_->getRateList(channel, size);
}
VideoDevice::~VideoDevice()
......
This diff is collapsed.
......@@ -78,8 +78,13 @@ extractString(const std::map<std::string, std::string>& settings, const std::str
static unsigned
extractInt(const std::map<std::string, std::string>& settings, const std::string& key) {
auto i = settings.find(key);
if (i != settings.cend())
return ring::stoi(i->second);
if (i != settings.cend()) {
try {
return ring::stoi(i->second);
} catch(...) {
RING_ERR("Can't parse int: %s", i->second.c_str());
}
}
return 0;
}
......@@ -88,7 +93,7 @@ VideoSettings::VideoSettings(const std::map<std::string, std::string>& settings)
name = extractString(settings, "name");
channel = extractString(settings, "channel");
video_size = extractString(settings, "size");
framerate = extractInt(settings, "rate");
framerate = extractString(settings, "rate");
}
std::map<std::string, std::string>
......@@ -98,7 +103,7 @@ VideoSettings::to_map() const
{"name", name},
{"size", video_size},
{"channel", channel},
{"rate", ring::to_string(framerate)}
{"rate", framerate}
};
}
......@@ -112,7 +117,7 @@ convert<ring::video::VideoSettings>::encode(const ring::video::VideoSettings& rh
node["name"] = rhs.name;
node["video_size"] = rhs.video_size;
node["channel"] = rhs.channel;
node["framerate"] = ring::to_string(rhs.framerate);
node["framerate"] = rhs.framerate;
return node;
}
......@@ -125,7 +130,7 @@ convert<ring::video::VideoSettings>::decode(const Node& node, ring::video::Video
rhs.name = node["name"].as<std::string>();
rhs.video_size = node["video_size"].as<std::string>();
rhs.channel = node["channel"].as<std::string>();
rhs.framerate = node["framerate"].as<unsigned>();
rhs.framerate = node["framerate"].as<std::string>();
return true;
}
......
......@@ -154,9 +154,9 @@ struct VideoSettings
std::map<std::string, std::string> to_map() const;
std::string name {};
std::string video_size {};
std::string channel {};
unsigned framerate {};
std::string video_size {};
std::string framerate {};
};
}} // namespace ring::video
......
......@@ -2,6 +2,7 @@
* Copyright (C) 2014-2015 Savoir-faire Linux Inc.
*
* Author: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
* Author: Adrien Béraud <adrien.beraud@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
......@@ -18,20 +19,27 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __VIDEO_DEVICE_H__
#define __VIDEO_DEVICE_H__
#pragma once
#include "media/media_device.h"
#include "video_base.h"
#include "rational.h"
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <algorithm>
#include "videomanager_interface.h"
#include "string_utils.h"
#include "logger.h"
namespace ring { namespace video {
using VideoSize = std::pair<unsigned, unsigned>;
using FrameRate = rational<double>;
class VideoDeviceImpl;
class VideoDevice {
......@@ -44,7 +52,7 @@ public:
* The device name, e.g. "Integrated Camera",
* actually used as the identifier.
*/
std::string name = "";
std::string name {};
const std::string& getNode() const { return node_; }
......@@ -63,12 +71,74 @@ public:
* '800x448': ['15'],
* '960x540': ['10']}}
*/
DRing::VideoCapabilities getCapabilities() const;
DRing::VideoCapabilities getCapabilities() const {
DRing::VideoCapabilities cap;
for (const auto& chan : getChannelList())
for (const auto& size : getSizeList(chan)) {
std::stringstream sz;
sz << size.first << "x" << size.second;
auto rates = getRateList(chan, size);
std::vector<std::string> rates_str {rates.size()};
std::transform(rates.begin(), rates.end(), rates_str.begin(),
[](FrameRate r) { return ring::to_string(r.real()); });
cap[chan][sz.str()] = std::move(rates_str);
}
return cap;
}
/* Default setting is found by using following rules:
* - frame height <= 720 pixels
* - frame rate >= 10 fps
*/
VideoSettings getDefaultSettings() const {
auto settings = getSettings();
settings.channel = getChannelList().front();
VideoSize max_size {0, 0};
FrameRate max_size_rate {0};
auto sizes = getSizeList(settings.channel);
for (auto& s : sizes) {
if (s.second > 720)
continue;
auto rates = getRateList(settings.channel, s);
if (rates.empty())
continue;
auto max_rate = *std::max_element(rates.begin(), rates.end());
if (max_rate < 10)
continue;
if (s.second > max_size.second ||
(s.second == max_size.second && s.first > max_size.first)) {
max_size = s;
max_size_rate = max_rate;
}
}
if (max_size.second > 0) {
std::stringstream video_size;
video_size << max_size.first << "x" << max_size.second;
settings.video_size = video_size.str();
settings.framerate = ring::to_string(max_size_rate.real());
RING_WARN("Default video settings: %s, %s FPS", settings.video_size.c_str(),
settings.framerate.c_str());
}
return settings;
}
/*
* Get the settings for the device.
*/
VideoSettings getSettings() const;
VideoSettings getSettings() const {
auto params = getDeviceParams();
VideoSettings settings;
settings.name = params.name;
settings.channel = params.channel_name;
settings.video_size = sizeToString(params.width, params.height);
settings.framerate = ring::to_string(params.framerate.real());
return settings;
}
/*
* Setup the device with the preferences listed in the "settings" map.
......@@ -77,7 +147,16 @@ public:
* If a key is missing, a valid default value is choosen. Thus, calling
* this function with an empty map will reset the device to default.
*/
void applySettings(VideoSettings settings);
void applySettings(VideoSettings settings) {
DeviceParams params {};
params.name = settings.name;
params.channel_name = settings.channel;
auto size = sizeFromString(settings.channel, settings.video_size);
params.width = size.first;
params.height = size.second;
params.framerate = rateFromString(settings.channel, size, settings.framerate);
setDeviceParams(params);
}
/**
* Returns the parameters needed for actual use of the device
......@@ -86,10 +165,55 @@ public:
private:
std::vector<std::string> getChannelList() const;
std::vector<VideoSize> getSizeList(const std::string& channel) const;
std::vector<FrameRate> getRateList(const std::string& channel, VideoSize size) const;
VideoSize sizeFromString(const std::string& channel, const std::string& size) const {
auto size_list = getSizeList(channel);
for (const auto& s : size_list) {
if (sizeToString(s.first, s.second) == size)
return s;
}
return {0, 0};
}
std::string sizeToString(unsigned w, unsigned h) const {
std::stringstream video_size;
video_size << w << "x" << h;
return video_size.str();
}
FrameRate rateFromString(const std::string& channel, VideoSize size,
const std::string& rate) const {
FrameRate closest {0};
double rate_val = 0;
try {
rate_val = rate.empty() ? 0 : std::stod(rate);
} catch (...) {
RING_WARN("Can't read framerate \"%s\"", rate.c_str());
}
// fallback to framerate closest to 30 FPS
if (rate_val == 0)
rate_val = 30;
double closest_dist = std::numeric_limits<double>::max();
auto rate_list = getRateList(channel, size);
for (const auto& r : rate_list) {
double dist = std::fabs(r.real() - rate_val);
if (dist < closest_dist) {
closest = r;
closest_dist = dist;
}
}
return closest;
}
void setDeviceParams(const DeviceParams&);
/*
* The device node, e.g. "/dev/video0".
*/
std::string node_ = "";
std::string node_ {};
/*
* Device specific implementation.
......@@ -102,5 +226,3 @@ private:
};
}} // namespace ring::video
#endif // __VIDEO_DEVICE_H__
......@@ -70,12 +70,12 @@ VideoDeviceMonitor::getCapabilities(const string& name) const
VideoSettings
VideoDeviceMonitor::getSettings(const string& name)
{
const auto itd = findDeviceByName(name);
const auto itd = findPreferencesByName(name);
if (itd == devices_.end())
if (itd == preferences_.end())
return VideoSettings();
return itd->getSettings();
return *itd;
}
void
......@@ -182,24 +182,26 @@ VideoDeviceMonitor::addDevice(const string& node)
// instantiate a new unique device
try {
VideoDevice dev(node);
VideoDevice dev {node};
giveUniqueName(dev, devices_);
// restore its preferences if any, or store the defaults
auto it = findPreferencesByName(dev.name);
if (it != preferences_.end())
dev.applySettings(*it);
else
preferences_.push_back(dev.getSettings());
else {
dev.applySettings(dev.getDefaultSettings());
preferences_.emplace_back(dev.getSettings());
}
// in case there is no default device on a fresh run
if (defaultDevice_.empty())
defaultDevice_ = dev.name;
devices_.push_back(dev);
devices_.emplace_back(std::move(dev));
notify();
} catch (const std::runtime_error& e) {
RING_ERR("%s", e.what());
} catch (const std::exception& e) {
RING_ERR("Failed to add device %s: %s", node.c_str(), e.what());
return;
}
}
......
......@@ -113,9 +113,12 @@ bool VideoInput::captureFrame()
switch (ret) {
case MediaDecoder::Status::ReadError:
case MediaDecoder::Status::DecodeError:
return false;
// try to keep decoding
case MediaDecoder::Status::DecodeError:
return true;
// End of streamed file
case MediaDecoder::Status::EOFError:
createDecoder();
......
......@@ -48,24 +48,22 @@ class VideoDeviceImpl {
unsigned int id;
std::vector<std::string> getChannelList() const;
std::vector<std::string> getSizeList(const std::string& channel) const;