@@ -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+ }
5764std::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