Skip to content

Commit 478871f

Browse files
committed
Video Pipeline Refactor (part 1): Implement new pipeline.
1 parent 847c701 commit 478871f

39 files changed

+1513
-83
lines changed

Common/Qt/WidgetStackFixedAspectRatio.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ WidgetStackFixedAspectRatio::WidgetStackFixedAspectRatio(
3838
void WidgetStackFixedAspectRatio::add_widget(QWidget& widget){
3939
widget.setParent(m_stack_holder);
4040
m_widgets.insert(&widget);
41+
widget.show();
42+
}
43+
void WidgetStackFixedAspectRatio::remove_widget(QWidget* widget){
44+
m_widgets.erase(widget);
45+
delete widget;
4146
}
4247
double WidgetStackFixedAspectRatio::sanitize_aspect_ratio(double aspect_ratio) const{
4348
if (aspect_ratio == 0 || std::isnan(aspect_ratio)){

Common/Qt/WidgetStackFixedAspectRatio.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class WidgetStackFixedAspectRatio : public QWidget{
3535
void set_all(SizePolicy size_policy, double aspect_ratio);
3636

3737
void add_widget(QWidget& widget);
38+
void remove_widget(QWidget* widget);
3839

3940
virtual void resizeEvent(QResizeEvent* event) override;
4041

SerialPrograms/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,8 @@ file(GLOB MAIN_SOURCES
483483
Source/CommonFramework/VideoPipeline/UI/VideoDisplayWindow.h
484484
Source/CommonFramework/VideoPipeline/UI/VideoOverlayWidget.cpp
485485
Source/CommonFramework/VideoPipeline/UI/VideoOverlayWidget.h
486+
Source/CommonFramework/VideoPipeline/UI/VideoSourceSelectorWidget.cpp
487+
Source/CommonFramework/VideoPipeline/UI/VideoSourceSelectorWidget.h
486488
Source/CommonFramework/VideoPipeline/UI/VideoWidget.h
487489
Source/CommonFramework/VideoPipeline/VideoFeed.h
488490
Source/CommonFramework/VideoPipeline/VideoOverlay.cpp
@@ -495,6 +497,16 @@ file(GLOB MAIN_SOURCES
495497
Source/CommonFramework/VideoPipeline/VideoOverlayTypes.cpp
496498
Source/CommonFramework/VideoPipeline/VideoOverlayTypes.h
497499
Source/CommonFramework/VideoPipeline/VideoPipelineOptions.h
500+
Source/CommonFramework/VideoPipeline/VideoSession.cpp
501+
Source/CommonFramework/VideoPipeline/VideoSession.h
502+
Source/CommonFramework/VideoPipeline/VideoSource.cpp
503+
Source/CommonFramework/VideoPipeline/VideoSource.h
504+
Source/CommonFramework/VideoPipeline/VideoSource_Camera.cpp
505+
Source/CommonFramework/VideoPipeline/VideoSource_Camera.h
506+
Source/CommonFramework/VideoPipeline/VideoSource_Null.cpp
507+
Source/CommonFramework/VideoPipeline/VideoSource_Null.h
508+
Source/CommonFramework/VideoPipeline/VideoSourceDescriptor.cpp
509+
Source/CommonFramework/VideoPipeline/VideoSourceDescriptor.h
498510
Source/CommonFramework/Windows/ButtonDiagram.cpp
499511
Source/CommonFramework/Windows/ButtonDiagram.h
500512
Source/CommonFramework/Windows/DpiScaler.cpp

SerialPrograms/Source/CommonFramework/Recording/StreamHistorySession.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,10 @@ void StreamHistorySession::post_shutdown(){
216216
data.m_has_video = false;
217217
initialize();
218218
}
219-
void StreamHistorySession::post_new_source(const CameraInfo& device, Resolution resolution){
220-
// cout << "post_new_source()" << endl;
219+
void StreamHistorySession::post_startup(VideoSource* source){
221220
Data& data = *m_data;
222221
WriteSpinLock lg(data.m_lock);
223-
data.m_has_video = !device.device_name().empty();
222+
data.m_has_video = source != nullptr;
224223
initialize();
225224
}
226225
void StreamHistorySession::pre_resolution_change(Resolution resolution){

SerialPrograms/Source/CommonFramework/Recording/StreamHistorySession.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#include "Common/Cpp/AbstractLogger.h"
1313
#include "Common/Cpp/Containers/Pimpl.h"
1414
#include "CommonFramework/AudioPipeline/AudioSession.h"
15-
#include "CommonFramework/VideoPipeline/CameraSession.h"
15+
#include "CommonFramework/VideoPipeline/VideoSession.h"
1616

1717
namespace PokemonAutomation{
1818

@@ -21,7 +21,7 @@ class StreamHistorySession
2121
: public AudioFloatStreamListener
2222
, public VideoFrameListener
2323
, public AudioSession::StateListener
24-
, public CameraSession::StateListener
24+
, public VideoSession::StateListener
2525
{
2626
public:
2727
~StreamHistorySession();
@@ -39,7 +39,7 @@ class StreamHistorySession
3939

4040
virtual void pre_shutdown() override;
4141
virtual void post_shutdown() override;
42-
virtual void post_new_source(const CameraInfo& device, Resolution resolution) override;
42+
virtual void post_startup(VideoSource* source) override;
4343
virtual void pre_resolution_change(Resolution resolution) override;
4444
virtual void post_resolution_change(Resolution resolution) override;
4545

SerialPrograms/Source/CommonFramework/Tools/VideoStream.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
*
1515
*/
1616

17-
#ifndef PokemonAutomation_VideoStream_H
18-
#define PokemonAutomation_VideoStream_H
17+
#ifndef PokemonAutomation_VideoPipeline_VideoStream_H
18+
#define PokemonAutomation_VideoPipeline_VideoStream_H
1919

2020
#include <string>
2121
#include "Common/Cpp/AbstractLogger.h"

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

Lines changed: 243 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,11 @@ VideoSnapshot CameraSession::snapshot(){
252252

253253
return VideoSnapshot(m_last_image, m_last_image_timestamp);
254254
}
255-
double CameraSession::fps_source(){
255+
double CameraSession::fps_source() const{
256256
ReadSpinLock lg(m_frame_lock);
257257
return m_fps_tracker_source.events_per_second();
258258
}
259-
double CameraSession::fps_display(){
259+
double CameraSession::fps_display() const{
260260
ReadSpinLock lg(m_frame_lock);
261261
return m_fps_tracker_display.events_per_second();
262262
}
@@ -612,6 +612,247 @@ void VideoDisplayWidget::paintEvent(QPaintEvent* event){
612612

613613

614614

615+
CameraVideoSource::~CameraVideoSource(){
616+
if (!m_capture_session){
617+
return;
618+
}
619+
620+
try{
621+
m_logger.log("Stopping Camera...");
622+
}catch (...){}
623+
624+
m_camera->stop();
625+
m_capture_session.reset();
626+
m_camera.reset();
627+
}
628+
CameraVideoSource::CameraVideoSource(
629+
Logger& logger,
630+
const CameraInfo& info,
631+
Resolution desired_resolution
632+
)
633+
: m_logger(logger)
634+
, m_last_image_timestamp(WallClock::min())
635+
, m_stats_conversion("ConvertFrame", "ms", 1000, std::chrono::seconds(10))
636+
, m_last_frame_seqnum(0)
637+
{
638+
if (!info){
639+
return;
640+
}
641+
m_logger.log("Starting Camera: Backend = CameraQt65QMediaCaptureSession");
642+
643+
auto cameras = QMediaDevices::videoInputs();
644+
const QCameraDevice* device = nullptr;
645+
for (const auto& camera : cameras){
646+
if (camera.id().toStdString() == info.device_name()){
647+
device = &camera;
648+
break;
649+
}
650+
}
651+
if (device == nullptr){
652+
m_logger.log("Camera not found: " + info.device_name(), COLOR_RED);
653+
return;
654+
}
655+
m_logger.log("Camera: " + device->description().toStdString());
656+
657+
QList<QCameraFormat> formats = device->videoFormats();
658+
if (formats.empty()){
659+
m_logger.log("No usable resolutions: " + device->description().toStdString(), COLOR_RED);
660+
return;
661+
}
662+
663+
std::map<Resolution, const QCameraFormat*> resolution_map;
664+
for (const QCameraFormat& format : formats){
665+
QSize resolution = format.resolution();
666+
resolution_map.emplace(
667+
std::piecewise_construct,
668+
std::forward_as_tuple(resolution.width(), resolution.height()),
669+
std::forward_as_tuple(&format)
670+
);
671+
}
672+
673+
const QCameraFormat* format = nullptr;
674+
m_resolutions.clear();
675+
for (const auto& res : resolution_map){
676+
m_resolutions.emplace_back(res.first);
677+
if (res.first == desired_resolution){
678+
format = res.second;
679+
}
680+
}
681+
if (format == nullptr){
682+
format = resolution_map.rbegin()->second;
683+
}
684+
685+
QSize size = format->resolution();
686+
m_resolution = Resolution(size.width(), size.height());
687+
m_logger.log("Resolution: " + m_resolution.to_string());
688+
689+
m_camera.reset(new QCamera(*device));
690+
m_camera->setCameraFormat(*format);
691+
692+
m_capture_session.reset(new QMediaCaptureSession());
693+
m_capture_session->setCamera(m_camera.get());
694+
695+
connect(m_camera.get(), &QCamera::errorOccurred, this, [&](){
696+
if (m_camera->error() == QCamera::NoError){
697+
return;
698+
}
699+
m_logger.log("QCamera error: " + m_camera->errorString().toStdString(), COLOR_RED);
700+
});
701+
702+
m_camera->start();
703+
}
704+
705+
706+
void CameraVideoSource::set_video_output(QGraphicsVideoItem& item){
707+
if (m_capture_session == nullptr){
708+
return;
709+
}
710+
if (m_capture_session->videoSink() == item.videoSink()){
711+
return;
712+
}
713+
m_capture_session->setVideoOutput(&item);
714+
715+
connect(
716+
item.videoSink(), &QVideoSink::videoFrameChanged,
717+
m_camera.get(), [&](const QVideoFrame& frame){
718+
// This will be on the main thread. So we waste as little time as
719+
// possible. Shallow-copy the frame, update the listeners, and
720+
// return immediately to unblock the main thread.
721+
722+
WallClock now = current_time();
723+
{
724+
WriteSpinLock lg(m_frame_lock);
725+
726+
// Skip duplicate frames.
727+
if (frame.startTime() != -1 && frame.startTime() <= m_last_frame.startTime()){
728+
return;
729+
}
730+
731+
m_last_frame = frame;
732+
m_last_frame_timestamp = now;
733+
uint64_t seqnum = m_last_frame_seqnum.load(std::memory_order_relaxed);
734+
seqnum++;
735+
m_last_frame_seqnum.store(seqnum, std::memory_order_relaxed);
736+
}
737+
738+
report_source_frame(std::make_shared<VideoFrame>(now, frame));
739+
},
740+
Qt::DirectConnection
741+
);
742+
}
743+
744+
745+
746+
747+
748+
Resolution CameraVideoSource::current_resolution() const{
749+
// No to lock since it doesn't change after construction.
750+
return m_resolution;
751+
}
752+
std::vector<Resolution> CameraVideoSource::supported_resolutions() const{
753+
// No to lock since it doesn't change after construction.
754+
return m_resolutions;
755+
}
756+
757+
VideoSnapshot CameraVideoSource::snapshot(){
758+
// This will be coming in from random threads. (not the main thread)
759+
// So we efficiently grab the last frame to unblock the main thread.
760+
// Then we can do any expensive post-processing as needed.
761+
762+
std::lock_guard<std::mutex> lg(m_cache_lock);
763+
764+
// Check the cached image frame. If it's not stale, return it immediately.
765+
uint64_t frame_seqnum = m_last_frame_seqnum.load(std::memory_order_relaxed);
766+
if (!m_last_image.isNull() && m_last_image_seqnum == frame_seqnum){
767+
return VideoSnapshot(m_last_image, m_last_image_timestamp);
768+
}
769+
770+
// Cached image is stale. Grab the latest frame.
771+
QVideoFrame frame;
772+
WallClock frame_timestamp;
773+
{
774+
ReadSpinLock lg0(m_frame_lock);
775+
frame_seqnum = m_last_frame_seqnum.load(std::memory_order_relaxed);
776+
frame = m_last_frame;
777+
frame_timestamp = m_last_frame_timestamp;
778+
}
779+
780+
if (!frame.isValid()){
781+
m_logger.log("QVideoFrame is null.", COLOR_RED);
782+
return VideoSnapshot();
783+
}
784+
785+
// Converting the QVideoFrame to QImage is expensive. Time it and
786+
// report performance.
787+
WallClock time0 = current_time();
788+
789+
QImage image = frame.toImage();
790+
QImage::Format format = image.format();
791+
if (format != QImage::Format_ARGB32 && format != QImage::Format_RGB32){
792+
image = image.convertToFormat(QImage::Format_ARGB32);
793+
}
794+
795+
WallClock time1 = current_time();
796+
m_stats_conversion.report_data(m_logger, std::chrono::duration_cast<std::chrono::microseconds>(time1 - time0).count());
797+
798+
// Update the cached image.
799+
m_last_image = std::move(image);
800+
m_last_image_timestamp = frame_timestamp;
801+
m_last_image_seqnum = frame_seqnum;
802+
803+
return VideoSnapshot(m_last_image, m_last_image_timestamp);
804+
}
805+
806+
QWidget* CameraVideoSource::make_QtWidget(QWidget* parent){
807+
return new CameraVideoDisplay(parent, *this);
808+
}
809+
810+
811+
812+
813+
814+
815+
816+
817+
CameraVideoDisplay::CameraVideoDisplay(QWidget* parent, CameraVideoSource& source)
818+
: QWidget(parent)
819+
, m_source(source)
820+
, m_view(new StaticQGraphicsView(this))
821+
, m_sanitizer("CameraVideoDisplay")
822+
{
823+
this->setMinimumSize(80, 45);
824+
m_view->setFixedSize(this->size());
825+
m_view->setScene(&m_scene);
826+
m_video.setSize(this->size());
827+
m_scene.setSceneRect(QRectF(QPointF(0, 0), this->size()));
828+
m_scene.addItem(&m_video);
829+
source.set_video_output(m_video);
830+
831+
connect(
832+
&m_scene, &QGraphicsScene::changed,
833+
this, [&](const QList<QRectF>&){
834+
auto scope_check = m_sanitizer.check_scope();
835+
m_source.report_rendered_frame(current_time());
836+
}
837+
);
838+
}
839+
void CameraVideoDisplay::resizeEvent(QResizeEvent* event){
840+
auto scope_check = m_sanitizer.check_scope();
841+
m_view->setFixedSize(this->size());
842+
m_scene.setSceneRect(QRectF(QPointF(0, 0), this->size()));
843+
m_video.setSize(this->size());
844+
}
845+
846+
847+
848+
849+
850+
851+
852+
853+
854+
855+
615856

616857

617858

0 commit comments

Comments
 (0)