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

View File

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

View File

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

View File

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

View File

@@ -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<wxMediaCtrl3 *>(opaque);
std::lock_guard<std::mutex> lock(ctrl->m_mutex);
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)
return 2;
@@ -206,7 +206,9 @@ int wxMediaCtrl3::PlayRtsp(std::shared_ptr<wxURI> const &url, std::unique_lock<s
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;
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 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<wxURI> const &url, std::unique_lock<s
break;
if (frame.IsOk())
m_frame = frame;
CallAfter([this] { Refresh(); });
if (!m_refresh_pending.exchange(true)) {
CallAfter([this] {
m_refresh_pending.store(false);
Refresh();
});
}
}
}
av_packet_unref(packet);
@@ -301,10 +320,11 @@ void wxMediaCtrl3::PlayThread()
if (!url->HasScheme())
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;

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);
#define BAMBU_DYNAMIC
#include <atomic>
#include <condition_variable>
#include <thread>
#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<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();
@@ -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 */