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

android/video: add video capture support

* allow to enumarate devices using the new video API
* allow to open cameras and capture frames

Issue: #79814
Change-Id: I1b85aa6035ec880f4388479d5a33ae7a5aec8fce
parent 3d9f49c2
......@@ -88,11 +88,10 @@ getSignalHandlers()
exported_callback<DRing::VideoSignal::DecodingStarted>(),
exported_callback<DRing::VideoSignal::DecodingStopped>(),
#ifdef __ANDROID__
exported_callback<DRing::VideoSignal::AcquireCamera>(),
exported_callback<DRing::VideoSignal::ReleaseCamera>(),
exported_callback<DRing::VideoSignal::GetCameraFormats>(),
exported_callback<DRing::VideoSignal::GetCameraSizes>(),
exported_callback<DRing::VideoSignal::GetCameraRates>(),
exported_callback<DRing::VideoSignal::GetCameraInfo>(),
exported_callback<DRing::VideoSignal::SetParameters>(),
exported_callback<DRing::VideoSignal::StartCapture>(),
exported_callback<DRing::VideoSignal::StopCapture>(),
#endif
#endif
};
......
......@@ -159,6 +159,22 @@ removeVideoDevice(const std::string &node)
{
videoManager.videoDeviceMonitor.removeDevice(node);
}
void*
obtainFrame(int length)
{
if (auto input = videoManager.videoInput.lock())
return (*input).obtainFrame(length);
return nullptr;
}
void
releaseFrame(void* frame)
{
if (auto input = videoManager.videoInput.lock())
(*input).releaseFrame(frame);
}
#endif
} // namespace DRing
......
......@@ -73,6 +73,8 @@ void registerSinkTarget(const std::string& sinkId, const SinkTarget& target);
#ifdef __ANDROID__
void addVideoDevice(const std::string &node);
void removeVideoDevice(const std::string &node);
void* obtainFrame(int length);
void releaseFrame(void* frame);
#endif
// Video signal type definitions
......@@ -90,25 +92,21 @@ struct VideoSignal {
using cb_type = void(const std::string& /*id*/, const std::string& /*shm_path*/, bool /*is_mixer*/);
};
#ifdef __ANDROID__
struct AcquireCamera {
constexpr static const char* name = "AcquireCamera";
using cb_type = void(const std::string& device);
};
struct ReleaseCamera {
constexpr static const char* name = "ReleaseCamera";
using cb_type = void(const std::string& device);
struct GetCameraInfo {
constexpr static const char* name = "GetCameraInfo";
using cb_type = void(const std::string& device, std::vector<int> *formats, std::vector<unsigned> *sizes, std::vector<unsigned> *rates);
};
struct GetCameraFormats {
constexpr static const char* name = "GetCameraFormats";
using cb_type = void(const std::string& device, std::vector<int> *formats_);
struct SetParameters {
constexpr static const char* name = "SetParameters";
using cb_type = void(const std::string& device, const int format, const int width, const int height, const int rate);
};
struct GetCameraSizes {
constexpr static const char* name = "GetCameraSizes";
using cb_type = void(const std::string& device, int format, std::vector<std::string> *sizes);
struct StartCapture {
constexpr static const char* name = "StartCapture";
using cb_type = void(const std::string& device);
};
struct GetCameraRates{
constexpr static const char* name = "GetCameraRates";
using cb_type = void(const std::string& device, const int format, const std::string& size, std::vector<float> *rates_);
struct StopCapture {
constexpr static const char* name = "StopCapture";
using cb_type = void(void);
};
#endif
};
......
......@@ -116,6 +116,8 @@ int libav_pixel_format(int fmt)
case video::VIDEO_PIXFMT_BGRA: return PIXEL_FORMAT(BGRA);
case video::VIDEO_PIXFMT_RGBA: return PIXEL_FORMAT(RGBA);
case video::VIDEO_PIXFMT_YUYV422: return PIXEL_FORMAT(YUYV422);
case video::VIDEO_PIXFMT_YUV420P: return PIXEL_FORMAT(YUV420P);
case video::VIDEO_PIXFMT_NV21: return PIXEL_FORMAT(NV21);
}
return fmt;
}
......
......@@ -44,6 +44,12 @@ MediaFrame::reset() noexcept
#ifdef RING_VIDEO
VideoFrame::~VideoFrame()
{
if (releaseBufferCb_)
releaseBufferCb_(ptr_);
}
void
VideoFrame::reset() noexcept
{
......@@ -51,6 +57,7 @@ VideoFrame::reset() noexcept
#if !USE_OLD_AVU
allocated_ = false;
#endif
releaseBufferCb_ = {};
}
size_t
......@@ -107,10 +114,11 @@ VideoFrame::reserve(int format, int width, int height)
if (av_frame_get_buffer(libav_frame, 32))
throw std::bad_alloc();
allocated_ = true;
releaseBufferCb_ = {};
}
void
VideoFrame::setFromMemory(uint8_t* ptr, int format, int width, int height)
VideoFrame::setFromMemory(uint8_t* ptr, int format, int width, int height) noexcept
{
reset();
setGeometry(format, width, height);
......@@ -120,6 +128,17 @@ VideoFrame::setFromMemory(uint8_t* ptr, int format, int width, int height)
(AVPixelFormat)frame_->format, width, height);
}
void
VideoFrame::setFromMemory(uint8_t* ptr, int format, int width, int height,
std::function<void(void*)> cb) noexcept
{
setFromMemory(ptr, format, width, height);
if (cb) {
releaseBufferCb_ = cb;
ptr_ = ptr;
}
}
void
VideoFrame::noise()
{
......
......@@ -57,6 +57,7 @@ class VideoFrame: public MediaFrame {
public:
// Construct an empty VideoFrame
VideoFrame() = default;
~VideoFrame();
// Reset internal buffers (return to an empty VideoFrame)
void reset() noexcept override;
......@@ -78,7 +79,8 @@ class VideoFrame: public MediaFrame {
// Set internal pixel buffers on given memory buffer
// This buffer must follow given specifications.
void setFromMemory(uint8_t* data, int format, int width, int height);
void setFromMemory(uint8_t* data, int format, int width, int height) noexcept;
void setFromMemory(uint8_t* data, int format, int width, int height, std::function<void(void*)> cb) noexcept;
void noise();
......@@ -86,6 +88,8 @@ class VideoFrame: public MediaFrame {
VideoFrame& operator =(const VideoFrame& src);
private:
std::function<void(void*)> releaseBufferCb_ {};
void *ptr_ {nullptr};
bool allocated_ {false};
void setGeometry(int format, int width, int height) noexcept;
};
......
......@@ -33,19 +33,19 @@
namespace ring { namespace video {
class VideoAndroidSize {
public:
VideoAndroidSize(std::string size, std::string camera_id, int format);
std::string str() const;
float getRate(std::string) const;
float getRate(unsigned) const;
std::vector<std::string> getRateList() const;
/*
* Array to match Android formats. List formats in ascending
* preferrence: the format with the lower index will be picked.
*/
struct android_fmt {
int code;
std::string name;
enum VideoPixelFormat ring_format;
};
unsigned width;
unsigned height;
private:
std::vector<float> rates_;
static const struct android_fmt and_formats[] {
{ 17, "NV21", VIDEO_PIXFMT_NV21 },
{ 842094169, "YUV420", VIDEO_PIXFMT_YUV420P },
};
class VideoDeviceImpl {
......@@ -55,193 +55,175 @@ class VideoDeviceImpl {
*/
VideoDeviceImpl(const std::string& path);
std::string camera_id;
std::string name;
const VideoAndroidSize& getSize(const std::string size) const;
VideoSettings getSettings() const;
void applySettings(VideoSettings settings);
DeviceParams getDeviceParams() const;
std::vector<std::string> getSizeList() const;
private:
int format_;
std::string size_;
unsigned rate_;
std::vector<VideoAndroidSize> sizes_;
};
void setDeviceParams(const DeviceParams&);
/* VideoAndroidSize */
VideoAndroidSize::VideoAndroidSize(std::string size, std::string camera_id, int format)
{
sscanf(size.c_str(), "%ux%u", &width, &height);
std::vector<VideoSize> getSizeList() const;
std::vector<FrameRate> getRateList() const;
private:
void selectFormat();
VideoSize getSize(VideoSize size) const;
FrameRate getRate(FrameRate rate) const;
emitSignal<DRing::VideoSignal::GetCameraRates>(camera_id, format, size, &rates_);
}
std::vector<int> formats_ {};
std::vector<VideoSize> sizes_ {};
std::vector<FrameRate> rates_ {};
std::string
VideoAndroidSize::str() const
{
std::stringstream ss;
ss << width << "x" << height;
return ss.str();
}
const struct android_fmt *fmt_ {nullptr};
VideoSize size_ {};
FrameRate rate_ {};
};
float
VideoAndroidSize::getRate(unsigned rate) const
{
for(const auto &r : rates_) {
if (r == rate)
return r;
void
VideoDeviceImpl::selectFormat()
{
/*
* formats_ contains camera parameters as returned by the GetCameraInfo
* signal, find the matching V4L2 formats
*/
unsigned int current, best = UINT_MAX;
for(const auto &fmt : formats_) {
const struct android_fmt *and_fmt;
for(and_fmt = std::begin(and_formats);
and_formats < std::end(and_formats);
and_fmt++) {
if (fmt == and_fmt->code) {
current = and_fmt - std::begin(and_formats);
break;
}
}
/* No match found, we should add it */
if (and_fmt == std::end(and_formats)) {
RING_WARN("AndroidVideo: No format matching %d", fmt);
continue;
}
if (current < best)
best = current;
}
assert(not rates_.empty());
return rates_.back();
}
float
VideoAndroidSize::getRate(std::string rate) const
{
unsigned r;
std::stringstream ss;
ss << rate;
ss >> r;
return getRate(r);
}
std::vector<std::string>
VideoAndroidSize::getRateList() const
{
std::vector<std::string> v;
for(const auto &rate : rates_) {
std::stringstream ss;
ss << rate;
v.push_back(ss.str());
if (best != UINT_MAX) {
fmt_ = &and_formats[best];
RING_DBG("AndroidVideo: picked format %s", fmt_->name.c_str());
}
else {
fmt_ = &and_formats[0];
RING_ERR("AndroidVideo: Could not find a known format to use");
}
return v;
}
/* VideoDeviceImpl */
VideoDeviceImpl::VideoDeviceImpl(const std::string& path) :
camera_id(path), name(path), format_(), rate_()
VideoDeviceImpl::VideoDeviceImpl(const std::string& path) : name(path)
{
emitSignal<DRing::VideoSignal::AcquireCamera>(camera_id);
RING_DBG("### Acquired Camera %s", camera_id.c_str());
std::vector<int> formats;
emitSignal<DRing::VideoSignal::GetCameraFormats>(camera_id, &formats);
assert(not formats.empty());
format_ = formats[0]; /* FIXME: select a real value */
std::vector<unsigned> sizes;
std::vector<unsigned> rates;
emitSignal<DRing::VideoSignal::GetCameraInfo>(name, &formats_, &sizes, &rates);
for (size_t i=0, n=sizes.size(); i<n; i+=2)
sizes_.emplace_back(sizes[i], sizes[i+1]);
for (const auto& r : rates)
rates_.emplace_back(r, 1000);
std::vector<std::string> sizes;
emitSignal<DRing::VideoSignal::GetCameraSizes>(camera_id, format_, &sizes);
for(const auto &iter : sizes) {
sizes_.push_back(VideoAndroidSize(iter, camera_id, format_));
}
emitSignal<DRing::VideoSignal::ReleaseCamera>(camera_id);
// Set default settings
applySettings(VideoSettings());
selectFormat();
}
const VideoAndroidSize&
VideoDeviceImpl::getSize(const std::string size) const
VideoSize
VideoDeviceImpl::getSize(VideoSize size) const
{
for (const auto &iter : sizes_) {
if (iter.str() == size)
if (iter == size)
return iter;
}
assert(not sizes_.empty());
return sizes_.back();
return sizes_.empty() ? VideoSize{0, 0} : sizes_.back();
}
std::vector<std::string>
VideoDeviceImpl::getSizeList() const
FrameRate
VideoDeviceImpl::getRate(FrameRate rate) const
{
std::vector<std::string> v;
for(const auto &iter : sizes_)
v.push_back(iter.str());
for (const auto &iter : rates_) {
if (iter == rate)
return iter;
}
return v;
return rates_.empty() ? FrameRate{0, 0} : rates_.back();
}
void
VideoDeviceImpl::applySettings(VideoSettings settings)
std::vector<VideoSize>
VideoDeviceImpl::getSizeList() const
{
const VideoAndroidSize &s = getSize(settings.video_size);
size_ = s.str();
rate_ = s.getRate(settings.framerate);
return sizes_;
}
VideoSettings
VideoDeviceImpl::getSettings() const
std::vector<FrameRate>
VideoDeviceImpl::getRateList() const
{
VideoSettings settings;
settings.name = name;
settings.channel = "default";
settings.video_size = size_;
settings.framerate = rate_;
return settings;
return rates_;
}
DeviceParams
VideoDeviceImpl::getDeviceParams() const
{
const VideoAndroidSize &s = getSize(size_);
DeviceParams params;
params.input = camera_id;
params.format = "android";
std::stringstream ss1, ss2;
char sep;
ss1 << fmt_->ring_format;
ss1 >> params.format;
params.name = name;
params.input = name;
params.channel = 0;
params.width = s.width;
params.height = s.height;
params.width = size_.first;
params.height = size_.second;
params.framerate = rate_;
return params;
}
void
VideoDeviceImpl::setDeviceParams(const DeviceParams& params)
{
size_ = getSize({params.width, params.height});
rate_ = getRate(params.framerate);
emitSignal<DRing::VideoSignal::SetParameters>(name, fmt_->code, size_.first, size_.second, rate_.real());
}
VideoDevice::VideoDevice(const std::string& path) :
deviceImpl_(new VideoDeviceImpl(path))
{
name = deviceImpl_->name;
}
void
VideoDevice::applySettings(VideoSettings settings)
DeviceParams
VideoDevice::getDeviceParams() const
{
deviceImpl_->applySettings(settings);
return deviceImpl_->getDeviceParams();
}
VideoSettings
VideoDevice::getSettings() const
void
VideoDevice::setDeviceParams(const DeviceParams& params)
{
return deviceImpl_->getSettings();
return deviceImpl_->setDeviceParams(params);
}
DeviceParams
VideoDevice::getDeviceParams() const
std::vector<std::string>
VideoDevice::getChannelList() const
{
return deviceImpl_->getDeviceParams();
return {"default"};
}
DRing::VideoCapabilities
VideoDevice::getCapabilities() const
std::vector<VideoSize>
VideoDevice::getSizeList(const std::string& channel) const
{
DRing::VideoCapabilities cap;
for (const auto &iter : deviceImpl_->getSizeList()) {
const auto &s = deviceImpl_->getSize(iter);
cap["default"][s.str()] = s.getRateList();
}
return deviceImpl_->getSizeList();
}
return cap;
std::vector<FrameRate>
VideoDevice::getRateList(const std::string& channel, VideoSize size) const
{
return deviceImpl_->getRateList();
}
VideoDevice::~VideoDevice()
......
......@@ -50,8 +50,10 @@ namespace ring { namespace video {
enum VideoPixelFormat {
VIDEO_PIXFMT_BGRA = -1,
VIDEO_PIXFMT_YUYV422 = -2,
VIDEO_PIXFMT_RGBA = -3,
VIDEO_PIXFMT_YUV420P = -2,
VIDEO_PIXFMT_YUYV422 = -3,
VIDEO_PIXFMT_RGBA = -4,
VIDEO_PIXFMT_NV21 = -5,
};
template <typename T> class Observer;
......
......@@ -190,7 +190,7 @@ private:
FrameRate closest {0};
double rate_val = 0;
try {
rate_val = rate.empty() ? 0 : std::stod(rate);
rate_val = rate.empty() ? 0 : ring::stod(rate);
} catch (...) {
RING_WARN("Can't read framerate \"%s\"", rate.c_str());
}
......
......@@ -29,8 +29,12 @@
#include "media_const.h"
#include "manager.h"
#include "client/videomanager.h"
#include "client/ring_signal.h"
#include "sinkclient.h"
#include "logger.h"
#include "media/media_buffer.h"
#include <libavformat/avio.h>
#include <string>
#include <sstream>
......@@ -45,16 +49,94 @@ static constexpr unsigned default_grab_height = 480;
VideoInput::VideoInput()
: VideoGenerator::VideoGenerator()
, sink_ {Manager::instance().createSinkClient("local")}
#ifndef __ANDROID__
, loop_(std::bind(&VideoInput::setup, this),
std::bind(&VideoInput::process, this),
std::bind(&VideoInput::cleanup, this))
#else
, loop_(std::bind(&VideoInput::setup, this),
std::bind(&VideoInput::processAndroid, this),
std::bind(&VideoInput::cleanupAndroid, this))
, mutex_(), frame_cv_(), buffers_(8)
#endif
{}
VideoInput::~VideoInput()
{
#ifdef __ANDROID__
/* we need to stop the loop and notify the condition variable
* to unblock the process loop */
loop_.stop();
frame_cv_.notify_one();
#endif
loop_.join();
}
#ifdef __ANDROID__
bool VideoInput::waitForBufferFull()
{
for(auto& buffer : buffers_) {
if (buffer.status == BUFFER_FULL)
return true;
}
/* If the loop is stopped, returned true so we can quit the process loop */
return !isCapturing();
}
void VideoInput::processAndroid()
{
foundDecOpts(decOpts_);
if (switchPending_.exchange(false)) {
RING_DBG("Switching input to '%s'", decOpts_.input.c_str());
if (decOpts_.input.empty()) {
loop_.stop();