feat: use ffmpeg to render http camera stream

This commit is contained in:
Ian Chua
2026-09-04 16:42:36 +08:00
parent 2f82cfe40f
commit dbcb82075f
5 changed files with 48 additions and 23 deletions
+2 -2
View File
@@ -69,14 +69,14 @@ else ()
--disable-filters --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* --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 --disable-protocols
--enable-protocol=file,fd,pipe,rtp,tcp,udp --enable-protocol=file,fd,pipe,http,rtp,tcp,udp
--disable-muxers --disable-muxers
--enable-muxer=rtp --enable-muxer=rtp
--disable-encoders --disable-encoders
--disable-decoders --disable-decoders
--enable-decoder=*aac*,h264*,mp3*,mjpeg,rv* --enable-decoder=*aac*,h264*,mp3*,mjpeg,rv*
--disable-demuxers --disable-demuxers
--enable-demuxer=h264,mp3,mov,rtsp,sdp --enable-demuxer=h264,mp3,mov,mpjpeg,rtsp,sdp
--disable-zlib --disable-zlib
--disable-avdevice --disable-avdevice
BUILD_IN_SOURCE ON BUILD_IN_SOURCE ON
+3 -4
View File
@@ -290,7 +290,6 @@ void refresh_agora_url(char const* device, char const* dev_ver, char const* chan
void MediaPlayCtrl::Play() void MediaPlayCtrl::Play()
{ {
switch (current_mode()) { switch (current_mode()) {
case CameraStreamMode::http:
case CameraStreamMode::http_snapshot: case CameraStreamMode::http_snapshot:
if (!m_next_retry.IsValid() || wxDateTime::Now() < m_next_retry) if (!m_next_retry.IsValid() || wxDateTime::Now() < m_next_retry)
return; return;
@@ -300,14 +299,13 @@ void MediaPlayCtrl::Play()
Stop(_L("Please confirm if the printer is connected.")); Stop(_L("Please confirm if the printer is connected."));
return; return;
} }
if (auto agent = wxGetApp().getAgent())
agent->command_start_camera(m_machine);
m_button_play->SetIcon("media_stop"); m_button_play->SetIcon("media_stop");
m_web_ctrl->Load(wxURI(m_url), current_mode()); m_web_ctrl->Load(wxURI(m_url), current_mode());
m_web_ctrl->Play(); m_web_ctrl->Play();
m_last_state = wxMEDIASTATE_PLAYING; m_last_state = wxMEDIASTATE_PLAYING;
SetStatus(_L("Playing..."), false); SetStatus(_L("Playing..."), false);
return; return;
case CameraStreamMode::http:
case CameraStreamMode::rtsp: case CameraStreamMode::rtsp:
if (m_next_retry.IsValid() && wxDateTime::Now() < m_next_retry) if (m_next_retry.IsValid() && wxDateTime::Now() < m_next_retry)
return; return;
@@ -769,7 +767,8 @@ void MediaPlayCtrl::load()
{ {
m_last_state = MEDIASTATE_LOADING; m_last_state = MEDIASTATE_LOADING;
SetStatus(_L("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_h264 = data_dir() + "/video.h264";
std::string file_info = data_dir() + "/video.info"; std::string file_info = data_dir() + "/video.info";
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl dump video to " << file_h264; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl dump video to " << file_h264;
+11 -7
View File
@@ -2308,8 +2308,8 @@ void StatusPanel::update_camera_state(MachineObject* obj)
auto agent = wxGetApp().getAgent(); auto agent = wxGetApp().getAgent();
const auto camera_mode = agent ? agent->get_camera_stream_mode() : CameraStreamMode::none; 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; const bool use_webview = camera_mode == CameraStreamMode::http_snapshot;
if (has_printer_webcam) { if (use_webview) {
//m_camera_switch_button->Hide(); //m_camera_switch_button->Hide();
if (!m_custom_camera_view->IsShown()) { if (!m_custom_camera_view->IsShown()) {
// why: do not reload the WebView URL per tick, or redirects can cause a reload loop. // 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_custom_camera_view->Show();
m_media_ctrl->Hide(); m_media_ctrl->Hide();
} }
} else if (m_custom_camera_view->IsShown()) { } else {
m_custom_camera_view->Hide(); 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_ctrl->Show();
m_media_play_ctrl->StopWebStream();
} }
//sdcard //sdcard
@@ -2354,7 +2358,7 @@ void StatusPanel::update_camera_state(MachineObject* obj)
m_last_recording = obj->is_recording() ? 1 : 0; m_last_recording = obj->is_recording() ? 1 : 0;
} }
if (has_printer_webcam) { if (use_webview) {
if (m_bitmap_recording_img->IsShown()) { if (m_bitmap_recording_img->IsShown()) {
m_bitmap_recording_img->Hide(); m_bitmap_recording_img->Hide();
m_panel_monitoring_title->Layout(); m_panel_monitoring_title->Layout();
@@ -2417,7 +2421,7 @@ void StatusPanel::update_camera_state(MachineObject* obj)
m_camera_popup->update(show_vcamera); 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) StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxString &name)
+28 -8
View File
@@ -188,14 +188,14 @@ void wxMediaCtrl3::bambu_log(void *ctx, int level, tchar const *msg2)
BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data(); 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<wxMediaCtrl3 *>(opaque); auto *ctrl = static_cast<wxMediaCtrl3 *>(opaque);
std::lock_guard<std::mutex> lock(ctrl->m_mutex); std::lock_guard<std::mutex> lock(ctrl->m_mutex);
return ctrl->m_url != ctrl->m_active_url; return ctrl->m_url != ctrl->m_active_url;
} }
int wxMediaCtrl3::PlayRtsp(std::shared_ptr<wxURI> const &url, std::unique_lock<std::mutex> &lock) int wxMediaCtrl3::PlayFfmpeg(std::shared_ptr<wxURI> const &url, std::unique_lock<std::mutex> &lock)
{ {
if (avformat_network_init() < 0) if (avformat_network_init() < 0)
return 2; return 2;
@@ -206,7 +206,9 @@ int wxMediaCtrl3::PlayRtsp(std::shared_ptr<wxURI> const &url, std::unique_lock<s
return 2; return 2;
} }
format_context->interrupt_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; m_active_url = url;
auto finish = [&](int error) { auto finish = [&](int error) {
@@ -219,8 +221,20 @@ int wxMediaCtrl3::PlayRtsp(std::shared_ptr<wxURI> const &url, std::unique_lock<s
}; };
const std::string uri = url->BuildURI().ToUTF8().data(); const std::string uri = url->BuildURI().ToUTF8().data();
const wxString scheme = url->GetScheme();
const bool http_stream = scheme.CmpNoCase("http") == 0 || scheme.CmpNoCase("https") == 0;
AVDictionary *options = nullptr; 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(); lock.unlock();
int error = avformat_open_input(&format_context, uri.c_str(), nullptr, &options); int error = avformat_open_input(&format_context, uri.c_str(), nullptr, &options);
av_dict_free(&options); av_dict_free(&options);
@@ -278,7 +292,12 @@ int wxMediaCtrl3::PlayRtsp(std::shared_ptr<wxURI> const &url, std::unique_lock<s
break; break;
if (frame.IsOk()) if (frame.IsOk())
m_frame = frame; m_frame = frame;
CallAfter([this] { Refresh(); }); if (!m_refresh_pending.exchange(true)) {
CallAfter([this] {
m_refresh_pending.store(false);
Refresh();
});
}
} }
} }
av_packet_unref(packet); av_packet_unref(packet);
@@ -301,10 +320,11 @@ void wxMediaCtrl3::PlayThread()
if (!url->HasScheme()) if (!url->HasScheme())
break; break;
const wxString scheme = url->GetScheme(); 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; int error = 0;
if (generic_rtsp) { if (generic_ffmpeg) {
error = PlayRtsp(url, lk); error = PlayFfmpeg(url, lk);
} else { } else {
lk.unlock(); lk.unlock();
Bambu_Tunnel tunnel = nullptr; Bambu_Tunnel tunnel = nullptr;
+4 -2
View File
@@ -16,6 +16,7 @@ wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height); void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height);
#define BAMBU_DYNAMIC #define BAMBU_DYNAMIC
#include <atomic>
#include <condition_variable> #include <condition_variable>
#include <thread> #include <thread>
#ifndef _WIN32 #ifndef _WIN32
@@ -56,10 +57,10 @@ protected:
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override; 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 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(); void PlayThread();
int PlayRtsp(std::shared_ptr<wxURI> const &url, std::unique_lock<std::mutex> &lock); int PlayFfmpeg(std::shared_ptr<wxURI> const &url, std::unique_lock<std::mutex> &lock);
void NotifyStopped(); void NotifyStopped();
@@ -83,6 +84,7 @@ private:
std::mutex m_mutex; std::mutex m_mutex;
std::condition_variable m_cond; std::condition_variable m_cond;
std::thread m_thread; std::thread m_thread;
std::atomic_bool m_refresh_pending{false};
}; };
#endif /* wxMediaCtrl3_h */ #endif /* wxMediaCtrl3_h */