From dbcb82075f364526c4b49c4e2ce33a58a6234063 Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Fri, 4 Sep 2026 16:42:36 +0800 Subject: [PATCH] feat: use ffmpeg to render http camera stream --- deps/FFMPEG/FFMPEG.cmake | 4 ++-- src/slic3r/GUI/MediaPlayCtrl.cpp | 7 +++---- src/slic3r/GUI/StatusPanel.cpp | 18 +++++++++------- src/slic3r/GUI/wxMediaCtrl3.cpp | 36 +++++++++++++++++++++++++------- src/slic3r/GUI/wxMediaCtrl3.h | 6 ++++-- 5 files changed, 48 insertions(+), 23 deletions(-) diff --git a/deps/FFMPEG/FFMPEG.cmake b/deps/FFMPEG/FFMPEG.cmake index 3c582f0682..2b148ae3f7 100644 --- a/deps/FFMPEG/FFMPEG.cmake +++ b/deps/FFMPEG/FFMPEG.cmake @@ -69,14 +69,14 @@ else () --disable-filters --enable-filter=*null*,afade,*fifo,*format,*resample,aeval,allrgb,allyuv,atempo,pan,*bars,color,*key,crop,draw*,eq*,framerate,*_qsv,*_vaapi,*v4l2*,hw*,scale,volume,test* --disable-protocols - --enable-protocol=file,fd,pipe,rtp,tcp,udp + --enable-protocol=file,fd,pipe,http,rtp,tcp,udp --disable-muxers --enable-muxer=rtp --disable-encoders --disable-decoders --enable-decoder=*aac*,h264*,mp3*,mjpeg,rv* --disable-demuxers - --enable-demuxer=h264,mp3,mov,rtsp,sdp + --enable-demuxer=h264,mp3,mov,mpjpeg,rtsp,sdp --disable-zlib --disable-avdevice BUILD_IN_SOURCE ON diff --git a/src/slic3r/GUI/MediaPlayCtrl.cpp b/src/slic3r/GUI/MediaPlayCtrl.cpp index 77a183f090..49259bdf5b 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.cpp +++ b/src/slic3r/GUI/MediaPlayCtrl.cpp @@ -290,7 +290,6 @@ void refresh_agora_url(char const* device, char const* dev_ver, char const* chan void MediaPlayCtrl::Play() { switch (current_mode()) { - case CameraStreamMode::http: case CameraStreamMode::http_snapshot: if (!m_next_retry.IsValid() || wxDateTime::Now() < m_next_retry) return; @@ -300,14 +299,13 @@ void MediaPlayCtrl::Play() Stop(_L("Please confirm if the printer is connected.")); return; } - if (auto agent = wxGetApp().getAgent()) - agent->command_start_camera(m_machine); m_button_play->SetIcon("media_stop"); m_web_ctrl->Load(wxURI(m_url), current_mode()); m_web_ctrl->Play(); m_last_state = wxMEDIASTATE_PLAYING; SetStatus(_L("Playing..."), false); return; + case CameraStreamMode::http: case CameraStreamMode::rtsp: if (m_next_retry.IsValid() && wxDateTime::Now() < m_next_retry) return; @@ -769,7 +767,8 @@ void MediaPlayCtrl::load() { m_last_state = MEDIASTATE_LOADING; SetStatus(_L("Loading...")); - if (current_mode() != CameraStreamMode::rtsp) { + const auto mode = current_mode(); + if (mode != CameraStreamMode::rtsp && mode != CameraStreamMode::http) { std::string file_h264 = data_dir() + "/video.h264"; std::string file_info = data_dir() + "/video.info"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl dump video to " << file_h264; diff --git a/src/slic3r/GUI/StatusPanel.cpp b/src/slic3r/GUI/StatusPanel.cpp index b00dee3dca..7099f3fea9 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -2308,8 +2308,8 @@ void StatusPanel::update_camera_state(MachineObject* obj) auto agent = wxGetApp().getAgent(); const auto camera_mode = agent ? agent->get_camera_stream_mode() : CameraStreamMode::none; - const bool has_printer_webcam = camera_mode == CameraStreamMode::http || camera_mode == CameraStreamMode::http_snapshot; - if (has_printer_webcam) { + const bool use_webview = camera_mode == CameraStreamMode::http_snapshot; + if (use_webview) { //m_camera_switch_button->Hide(); if (!m_custom_camera_view->IsShown()) { // why: do not reload the WebView URL per tick, or redirects can cause a reload loop. @@ -2317,10 +2317,14 @@ void StatusPanel::update_camera_state(MachineObject* obj) m_custom_camera_view->Show(); m_media_ctrl->Hide(); } - } else if (m_custom_camera_view->IsShown()) { - m_custom_camera_view->Hide(); + } else { + if (m_custom_camera_view->IsShown()) { + m_custom_camera_view->Hide(); + // Stop the snapshot WebView before switching to native playback + // or leaving the camera mode. + m_media_play_ctrl->StopWebStream(); + } m_media_ctrl->Show(); - m_media_play_ctrl->StopWebStream(); } //sdcard @@ -2354,7 +2358,7 @@ void StatusPanel::update_camera_state(MachineObject* obj) m_last_recording = obj->is_recording() ? 1 : 0; } - if (has_printer_webcam) { + if (use_webview) { if (m_bitmap_recording_img->IsShown()) { m_bitmap_recording_img->Hide(); m_panel_monitoring_title->Layout(); @@ -2417,7 +2421,7 @@ void StatusPanel::update_camera_state(MachineObject* obj) m_camera_popup->update(show_vcamera); } - m_setting_button->Show(!has_printer_webcam); + m_setting_button->Show(!use_webview); } StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxString &name) diff --git a/src/slic3r/GUI/wxMediaCtrl3.cpp b/src/slic3r/GUI/wxMediaCtrl3.cpp index d0d53072d6..c4b114bb9b 100644 --- a/src/slic3r/GUI/wxMediaCtrl3.cpp +++ b/src/slic3r/GUI/wxMediaCtrl3.cpp @@ -188,14 +188,14 @@ void wxMediaCtrl3::bambu_log(void *ctx, int level, tchar const *msg2) BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data(); } -int wxMediaCtrl3::rtsp_interrupt_callback(void *opaque) +int wxMediaCtrl3::ffmpeg_interrupt_callback(void *opaque) { auto *ctrl = static_cast(opaque); std::lock_guard lock(ctrl->m_mutex); return ctrl->m_url != ctrl->m_active_url; } -int wxMediaCtrl3::PlayRtsp(std::shared_ptr const &url, std::unique_lock &lock) +int wxMediaCtrl3::PlayFfmpeg(std::shared_ptr const &url, std::unique_lock &lock) { if (avformat_network_init() < 0) return 2; @@ -206,7 +206,9 @@ int wxMediaCtrl3::PlayRtsp(std::shared_ptr const &url, std::unique_lockinterrupt_callback = {&wxMediaCtrl3::rtsp_interrupt_callback, this}; + format_context->interrupt_callback = {&wxMediaCtrl3::ffmpeg_interrupt_callback, this}; + format_context->flags |= AVFMT_FLAG_NOBUFFER; + format_context->max_delay = 0; m_active_url = url; auto finish = [&](int error) { @@ -219,8 +221,20 @@ int wxMediaCtrl3::PlayRtsp(std::shared_ptr const &url, std::unique_lockBuildURI().ToUTF8().data(); + const wxString scheme = url->GetScheme(); + const bool http_stream = scheme.CmpNoCase("http") == 0 || scheme.CmpNoCase("https") == 0; AVDictionary *options = nullptr; - av_dict_set(&options, "rtsp_transport", "tcp", 0); + if (http_stream) { + // This is a live multipart MJPEG stream. Keep FFmpeg from building a + // read-ahead buffer, otherwise the UI can display frames several + // seconds behind the camera. + av_dict_set(&options, "fflags", "nobuffer", 0); + av_dict_set(&options, "avioflags", "direct", 0); + av_dict_set(&options, "probesize", "32", 0); + av_dict_set(&options, "analyzeduration", "0", 0); + } else { + av_dict_set(&options, "rtsp_transport", "tcp", 0); + } lock.unlock(); int error = avformat_open_input(&format_context, uri.c_str(), nullptr, &options); av_dict_free(&options); @@ -278,7 +292,12 @@ int wxMediaCtrl3::PlayRtsp(std::shared_ptr const &url, std::unique_lockHasScheme()) break; const wxString scheme = url->GetScheme(); - const bool generic_rtsp = scheme.CmpNoCase("rtsp") == 0 || scheme.CmpNoCase("rtsps") == 0; + const bool generic_ffmpeg = scheme.CmpNoCase("http") == 0 || scheme.CmpNoCase("https") == 0 || + scheme.CmpNoCase("rtsp") == 0 || scheme.CmpNoCase("rtsps") == 0; int error = 0; - if (generic_rtsp) { - error = PlayRtsp(url, lk); + if (generic_ffmpeg) { + error = PlayFfmpeg(url, lk); } else { lk.unlock(); Bambu_Tunnel tunnel = nullptr; diff --git a/src/slic3r/GUI/wxMediaCtrl3.h b/src/slic3r/GUI/wxMediaCtrl3.h index bcc94a17ef..5160030983 100644 --- a/src/slic3r/GUI/wxMediaCtrl3.h +++ b/src/slic3r/GUI/wxMediaCtrl3.h @@ -16,6 +16,7 @@ wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height); #define BAMBU_DYNAMIC +#include #include #include #ifndef _WIN32 @@ -56,10 +57,10 @@ protected: void DoSetSize(int x, int y, int width, int height, int sizeFlags) override; static void bambu_log(void *ctx, int level, tchar const *msg); - static int rtsp_interrupt_callback(void *opaque); + static int ffmpeg_interrupt_callback(void *opaque); void PlayThread(); - int PlayRtsp(std::shared_ptr const &url, std::unique_lock &lock); + int PlayFfmpeg(std::shared_ptr const &url, std::unique_lock &lock); void NotifyStopped(); @@ -83,6 +84,7 @@ private: std::mutex m_mutex; std::condition_variable m_cond; std::thread m_thread; + std::atomic_bool m_refresh_pending{false}; }; #endif /* wxMediaCtrl3_h */