@@ -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