Skip to content

Commit 9dc87ce

Browse files
committed
Extend video refactor to VideoSink.
1 parent 3525a1f commit 9dc87ce

File tree

6 files changed

+334
-5
lines changed

6 files changed

+334
-5
lines changed

SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraImplementations.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <vector>
1111
#include "Common/Cpp/Options/EnumDropdownOption.h"
1212
#include "CommonFramework/Logging/Logger.h"
13+
#include "CommonFramework/VideoPipeline/VideoSourceDescriptor.h"
1314
#include "CommonFramework/VideoPipeline/CameraInfo.h"
1415
#include "CommonFramework/VideoPipeline/CameraSession.h"
1516
#include "CommonFramework/VideoPipeline/UI/VideoWidget.h"
@@ -31,6 +32,11 @@ class CameraBackend{
3132
virtual std::vector<CameraInfo> get_all_cameras() const = 0;
3233
virtual std::string get_camera_name(const CameraInfo& info) const = 0;
3334

35+
virtual std::unique_ptr<VideoSource> make_video_source(
36+
Logger& logger,
37+
const CameraInfo& info,
38+
Resolution resolution
39+
) const = 0;
3440
virtual std::unique_ptr<CameraSession> make_camera(Logger& logger, Resolution default_resolution) const = 0;
3541
};
3642

SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraWidgetQt6.5.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ std::string CameraBackend::get_camera_name(const CameraInfo& info) const{
6464
std::cout << "Error: no such camera for CameraInfo: " << info.device_name() << std::endl;
6565
return "";
6666
}
67+
std::unique_ptr<VideoSource> CameraBackend::make_video_source(
68+
Logger& logger,
69+
const CameraInfo& info,
70+
Resolution resolution
71+
) const{
72+
return std::make_unique<CameraVideoSource>(logger, info, resolution);
73+
}
6774
std::unique_ptr<PokemonAutomation::CameraSession> CameraBackend::make_camera(Logger& logger, Resolution default_resolution) const{
6875
return std::make_unique<CameraSession>(logger, default_resolution);
6976
}
@@ -616,7 +623,6 @@ CameraVideoSource::~CameraVideoSource(){
616623
if (!m_capture_session){
617624
return;
618625
}
619-
620626
try{
621627
m_logger.log("Stopping Camera...");
622628
}catch (...){}
@@ -734,7 +740,6 @@ void CameraVideoSource::set_video_output(QGraphicsVideoItem& item){
734740
seqnum++;
735741
m_last_frame_seqnum.store(seqnum, std::memory_order_relaxed);
736742
}
737-
738743
report_source_frame(std::make_shared<VideoFrame>(now, frame));
739744
},
740745
Qt::DirectConnection

SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraWidgetQt6.5.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ class CameraBackend : public PokemonAutomation::CameraBackend{
4949
virtual std::vector<CameraInfo> get_all_cameras() const override;
5050
virtual std::string get_camera_name(const CameraInfo& info) const override;
5151

52+
virtual std::unique_ptr<VideoSource> make_video_source(
53+
Logger& logger,
54+
const CameraInfo& info,
55+
Resolution resolution
56+
) const override;
5257
virtual std::unique_ptr<PokemonAutomation::CameraSession> make_camera(Logger& logger, Resolution default_resolution) const override;
5358
};
5459

SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraWidgetQt6.cpp

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ std::string CameraBackend::get_camera_name(const CameraInfo& info) const{
5454
std::cout << "Error: no such camera for CameraInfo: " << info.device_name() << std::endl;
5555
return "";
5656
}
57+
std::unique_ptr<VideoSource> CameraBackend::make_video_source(
58+
Logger& logger,
59+
const CameraInfo& info,
60+
Resolution resolution
61+
) const{
62+
return std::make_unique<CameraVideoSource>(logger, info, resolution);
63+
}
5764
std::unique_ptr<PokemonAutomation::CameraSession> CameraBackend::make_camera(Logger& logger, Resolution default_resolution) const{
5865
return std::make_unique<CameraSession>(logger, default_resolution);
5966
}
@@ -433,6 +440,216 @@ void VideoWidget::paintEvent(QPaintEvent* event){
433440

434441

435442

443+
CameraVideoSource::~CameraVideoSource(){
444+
if (!m_capture){
445+
return;
446+
}
447+
try{
448+
m_logger.log("Stopping Camera...");
449+
}catch (...){}
450+
451+
m_camera->stop();
452+
m_capture.reset();
453+
m_video_sink.reset();
454+
m_camera.reset();
455+
}
456+
CameraVideoSource::CameraVideoSource(
457+
Logger& logger,
458+
const CameraInfo& info,
459+
Resolution desired_resolution
460+
)
461+
: m_logger(logger)
462+
, m_last_image_timestamp(WallClock::min())
463+
, m_stats_conversion("ConvertFrame", "ms", 1000, std::chrono::seconds(10))
464+
, m_last_frame_seqnum(0)
465+
{
466+
if (!info){
467+
return;
468+
}
469+
m_logger.log("Starting Camera: Backend = CameraQt6QVideoSink");
470+
471+
auto cameras = QMediaDevices::videoInputs();
472+
const QCameraDevice* device = nullptr;
473+
for (const auto& camera : cameras){
474+
if (camera.id().toStdString() == info.device_name()){
475+
device = &camera;
476+
break;
477+
}
478+
}
479+
if (device == nullptr){
480+
m_logger.log("Camera not found: " + info.device_name(), COLOR_RED);
481+
return;
482+
}
483+
484+
QList<QCameraFormat> formats = device->videoFormats();
485+
if (formats.empty()){
486+
m_logger.log("No usable resolutions: " + device->description().toStdString(), COLOR_RED);
487+
return;
488+
}
489+
490+
std::map<Resolution, const QCameraFormat*> resolution_map;
491+
for (const QCameraFormat& format : formats){
492+
QSize resolution = format.resolution();
493+
resolution_map.emplace(
494+
std::piecewise_construct,
495+
std::forward_as_tuple(resolution.width(), resolution.height()),
496+
std::forward_as_tuple(&format)
497+
);
498+
}
499+
500+
const QCameraFormat* format = nullptr;
501+
m_resolutions.clear();
502+
for (const auto& res : resolution_map){
503+
m_resolutions.emplace_back(res.first);
504+
if (res.first == desired_resolution){
505+
format = res.second;
506+
}
507+
}
508+
if (format == nullptr){
509+
format = resolution_map.rbegin()->second;
510+
}
511+
512+
QSize size = format->resolution();
513+
m_resolution = Resolution(size.width(), size.height());
514+
m_logger.log("Resolution: " + m_resolution.to_string());
515+
516+
m_camera.reset(new QCamera(*device));
517+
m_camera->setCameraFormat(*format);
518+
m_video_sink.reset(new QVideoSink());
519+
m_capture.reset(new QMediaCaptureSession());
520+
m_capture->setCamera(m_camera.get());
521+
m_capture->setVideoSink(m_video_sink.get());
522+
523+
connect(m_camera.get(), &QCamera::errorOccurred, this, [&](){
524+
if (m_camera->error() != QCamera::NoError){
525+
m_logger.log("QCamera error: " + m_camera->errorString().toStdString());
526+
}
527+
});
528+
connect(
529+
m_video_sink.get(), &QVideoSink::videoFrameChanged,
530+
m_camera.get(), [&](const QVideoFrame& frame){
531+
// This will be on the main thread. So we waste as little time as
532+
// possible. Shallow-copy the frame, update the listeners, and
533+
// return immediately to unblock the main thread.
534+
535+
WallClock now = current_time();
536+
{
537+
WriteSpinLock lg(m_frame_lock);
538+
m_last_frame = frame;
539+
m_last_frame_timestamp = now;
540+
uint64_t seqnum = m_last_frame_seqnum.load(std::memory_order_relaxed);
541+
seqnum++;
542+
m_last_frame_seqnum.store(seqnum, std::memory_order_relaxed);
543+
}
544+
report_source_frame(std::make_shared<VideoFrame>(now, frame));
545+
}
546+
);
547+
548+
m_camera->start();
549+
}
550+
551+
Resolution CameraVideoSource::current_resolution() const{
552+
// No to lock since it doesn't change after construction.
553+
return m_resolution;
554+
}
555+
std::vector<Resolution> CameraVideoSource::supported_resolutions() const{
556+
// No to lock since it doesn't change after construction.
557+
return m_resolutions;
558+
}
559+
560+
VideoSnapshot CameraVideoSource::snapshot(){
561+
// Prevent multiple concurrent screenshots from entering here.
562+
std::lock_guard<std::mutex> lg(m_snapshot_lock);
563+
564+
if (m_camera == nullptr){
565+
return VideoSnapshot();
566+
}
567+
568+
// Frame is already cached and is not stale.
569+
QVideoFrame frame;
570+
WallClock frame_timestamp;
571+
uint64_t frame_seqnum;
572+
{
573+
SpinLockGuard lg0(m_frame_lock);
574+
frame_seqnum = m_last_frame_seqnum;
575+
if (!m_last_image.isNull() && m_last_image_seqnum == frame_seqnum){
576+
return VideoSnapshot(m_last_image, m_last_image_timestamp);
577+
}
578+
frame = m_last_frame;
579+
frame_timestamp = m_last_frame_timestamp;
580+
}
581+
582+
if (!frame.isValid()){
583+
global_logger_tagged().log("QVideoFrame is null.", COLOR_RED);
584+
return VideoSnapshot();
585+
}
586+
587+
WallClock time0 = current_time();
588+
589+
QImage image = frame.toImage();
590+
QImage::Format format = image.format();
591+
if (format != QImage::Format_ARGB32 && format != QImage::Format_RGB32){
592+
image = image.convertToFormat(QImage::Format_ARGB32);
593+
}
594+
595+
// No lock needed here since this the only place that touches it.
596+
m_last_image = std::move(image);
597+
m_last_image_timestamp = frame_timestamp;
598+
m_last_image_seqnum = frame_seqnum;
599+
600+
WallClock time1 = current_time();
601+
m_stats_conversion.report_data(m_logger, std::chrono::duration_cast<std::chrono::microseconds>(time1 - time0).count());
602+
603+
return VideoSnapshot(m_last_image, m_last_image_timestamp);
604+
}
605+
606+
QWidget* CameraVideoSource::make_QtWidget(QWidget* parent){
607+
return new CameraVideoDisplay(parent, *this);
608+
}
609+
610+
611+
612+
613+
614+
CameraVideoDisplay::~CameraVideoDisplay(){
615+
m_source.remove_source_frame_listener(*this);
616+
}
617+
CameraVideoDisplay::CameraVideoDisplay(QWidget* parent, CameraVideoSource& source)
618+
: QWidget(parent)
619+
, m_source(source)
620+
{
621+
source.add_source_frame_listener(*this);
622+
}
623+
void CameraVideoDisplay::on_frame(std::shared_ptr<const VideoFrame> frame){
624+
m_last_frame = frame;
625+
this->update();
626+
}
627+
void CameraVideoDisplay::paintEvent(QPaintEvent* event){
628+
QWidget::paintEvent(event);
629+
630+
if (!m_last_frame){
631+
return;
632+
}
633+
634+
QVideoFrame frame = m_last_frame->frame;
635+
if (!frame.isValid()){
636+
return;
637+
}
638+
639+
QRect rect(0, 0, this->width(), this->height());
640+
QVideoFrame::PaintOptions options;
641+
QPainter painter(this);
642+
643+
frame.paint(&painter, rect, options);
644+
m_source.report_rendered_frame(current_time());
645+
}
646+
647+
648+
649+
650+
651+
652+
436653

437654

438655

0 commit comments

Comments
 (0)