Port FFmpeg based camera view from BambuStudio (#15234)

* Add ffmepg dep

* NEW: reimpl wxMediaCtrl from ffmpeg

Jira: none
Change-Id: I46a47118a7649b2a50fcce8911e2888342ef25de
(cherry picked from commit d6c7f08769c8cfdbbf0e80ad280c9b3408a3c27d)
(cherry picked from commit 94d91be60bfe9bbbcdd21f85b46abc3faf126f17)

* FIX: reset bambu lib after restart network plugin

Change-Id: I4a3a4b7420745835ca3fa00c6edebe9d8d98cbf6
Jira: STUDIO-7571
(cherry picked from commit 28d9c6743fae80bfd40e4ee391e30d62cb16d4ab)

* FIX: ffmpeg decoder memory leak

Change-Id: I997572b5730618a969959f9b24c405d80fa9f83c
Jira: STUDIO-7597
(cherry picked from commit 342cea29bd9593fa89cbb33caff58055b46ebeec)

* FIX: install ffmpeg symbolic sos

Change-Id: Ia4a45182cefcf62a7a4b4a5c89c92251609c5a68
Jira: none
(cherry picked from commit b7f8fa1efdbe0ac2cc896ca24f063f5894fe9f90)

* FIX: ffmpeg swscale & frame_size

Change-Id: I9f4cb8c739b726f7e5cdbe0df7ed06b2eb2154d5
Jira: STUDIO-7624
(cherry picked from commit 5a2c75d835fb437667b590a803eef148baa30875)

* FIX: wxMediaCtrl3 idle image & center pos

Change-Id: Ib9652573e31bfd6229f174c0a1388942d9d98822
Jira: STUDIO-7633
(cherry picked from commit d51247c46e26460b151de79c598d81151280e79c)

* FIX: AVVideoDecoder sws_ctx_ == nullptr on zero size

Change-Id: I9698354bb1f341e276ec9780d4ef4fcd9f8a1028
Jira: STUDIO-7706
(cherry picked from commit ff622e25026a8471c39eb308cf5b115c4a9d84aa)

* fix:cannot open shared object file on linux

Change-Id: Ica66500506cfe8932eac3ae0a58fb7ff30d1da9b
jira:none
(cherry picked from commit febd1aeb4d453bc96571fa5e5727e9e10046cb80)
(cherry picked from commit 5ad579f929154779abd84b01438fd235c647dbf5)

* NEW:add ffmepg build Cmake

buildLinuxImage add ffmpeg so file

jira:nojira

Change-Id: I3e1be53aa58a179b8d9ae048ed7538de3ae8d111
(cherry picked from commit 2d70a1bcb6a5ba601525b08a38e7610f018fe106)

* FIX: ffmpeg cmake install error

jira:nojira

Change-Id: I74cc0f7c86b5364e55cad2af2bd9a82306ee6864
(cherry picked from commit 805df79e3bb044dac29ec1c06736751ccf3675f9)

* FIX: decode video to wxImage on Linux

Change-Id: I5e332a1b0622b3dfc70ac5c4c3bfa62b3411ebdc
Jira: none
(cherry picked from commit c787ba921a31f259e8eb23fd59f178e96279caf9)

* FIX: wxMediaCtrl3 enter Stopped state soon

Change-Id: I120e9d4b9f85599a184650d1d95fe2bec42af171
Jira: STUDIO-8280
(cherry picked from commit 7648d96305d510b9e97f22124961de5115cde830)

* FIX: reset decode buffer zero when scale width changed

Change-Id: Iaa2f99111dd5f7228b7b25e1be0a8cbdbfe982a6
Jira: STUDIO-8422
(cherry picked from commit 659ebc7d07a8f6045ba5443141b44277d7257cec)

* slic3r: Fix missing declarations in wxMediaCtrl3.h

src/slic3r/GUI/wxMediaCtrl3.h:80:10: error: ‘condition_variable’ in namespace ‘std’ does not name a type
   80 |     std::condition_variable m_cond;
      |          ^~~~~~~~~~~~~~~~~~
src/slic3r/GUI/wxMediaCtrl3.h:27:1: note: ‘std::condition_variable’ is defined in header ‘<condition_variable>’; did you forget to ‘#include <condition_variable>’?
   26 | #include "Printer/BambuTunnel.h"
  +++ |+#include <condition_variable>
   27 |
src/slic3r/GUI/wxMediaCtrl3.h:81:10: error: ‘thread’ in namespace ‘std’ does not name a type
   81 |     std::thread m_thread;
      |          ^~~~~~
src/slic3r/GUI/wxMediaCtrl3.h:27:1: note: ‘std::thread’ is defined in header ‘<thread>’; did you forget to ‘#include <thread>’?
   26 | #include "Printer/BambuTunnel.h"
  +++ |+#include <thread>
   27 |

In file included from src/slic3r/GUI/MediaPlayCtrl.h:17,
                 from src/slic3r/GUI/MediaPlayCtrl.cpp:1:
src/slic3r/GUI/wxMediaCtrl3.h:77:13: error: field ‘m_frame’ has incomplete type ‘wxImage’
   77 |     wxImage m_frame;
      |             ^~~~~~~

(cherry picked from commit 727a73333bd67acf5ff2b1c51ff284c2bacdb413)

* slic3r: Fix missing includes in AVVideoDecoder

In file included from src/slic3r/GUI/AVVideoDecoder.cpp:1:
src/slic3r/GUI/AVVideoDecoder.hpp:28:20: error: ‘wxImage’ has not been declared
   28 |     bool toWxImage(wxImage &image, wxSize const &size);
      |                    ^~~~~~~
src/slic3r/GUI/AVVideoDecoder.hpp:28:36: error: ‘wxSize’ has not been declared
   28 |     bool toWxImage(wxImage &image, wxSize const &size);
      |                                    ^~~~~~
src/slic3r/GUI/AVVideoDecoder.hpp:38:10: error: ‘vector’ in namespace ‘std’ does not name a template type
   38 |     std::vector<uint8_t> bits_;
      |          ^~~~~~
src/slic3r/GUI/AVVideoDecoder.hpp:9:1: note: ‘std::vector’ is defined in header ‘<vector>’; did you forget to ‘#include <vector>’?
    8 |     #include <libswscale/swscale.h>
  +++ |+#include <vector>
    9 | }

src/slic3r/GUI/AVVideoDecoder.cpp:145:89: error: invalid use of incomplete type ‘class wxBitmap’
  145 |     bitmap = wxBitmap((char const *) bits_.data(), size.GetWidth(), size.GetHeight(), 32);
      |                                                                                         ^

(cherry picked from commit 781ce14e061366da64fdc2d0d592fa35ee57e67e)

* slic3r: Fix missing includes in wxMediaCtrl2

src/slic3r/GUI/wxMediaCtrl2.cpp: In lambda function:
src/slic3r/GUI/wxMediaCtrl2.cpp:170:13: error: ‘wxMessageBox’ was not declared in this scope; did you mean ‘wxInfoMessageBox’?
  170 |             wxMessageBox(_L("Your system is missing H.264 codecs for GStreamer, which are required to play video.  (Try installing the gstreamer1.0-plugins-bad or gstreamer1.0-libav packages, then restart Bambu Studio?)"), _L("Error"), wxOK);
      |             ^~~~~~~~~~~~
      |             wxInfoMessageBox
src/slic3r/GUI/wxMediaCtrl2.cpp: In member function ‘void wxMediaCtrl2::Load(wxURI)’:
src/slic3r/GUI/wxMediaCtrl2.cpp:179:5: error: ‘wxLog’ has not been declared
  179 |     wxLog::EnableLogging(false);
      |     ^~~~~

(cherry picked from commit 73908d38d8b1f7c8dcae92d55711bc08cbfff23c)

* slic3r: Fix missing wxPaintDC declaration

src/slic3r/GUI/wxMediaCtrl3.cpp: In member function ‘void wxMediaCtrl3::paintEvent(wxPaintEvent&)’:
src/slic3r/GUI/wxMediaCtrl3.cpp:121:5: error: ‘wxPaintDC’ was not declared in this scope; did you mean ‘wxPoint’?
  121 |     wxPaintDC dc(this);
      |     ^~~~~~~~~
      |     wxPoint

(cherry picked from commit 9ab5009235d212699f91e01d7f930f92849ed1e3)

* slic3r: Fix missing BOOST_LOG_TRIVIAL declaration

src/slic3r/GUI/wxMediaCtrl3.cpp:181:23: error: ‘info’ was not declared in this scope
  181 |     BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data();
      |                       ^~~~
src/slic3r/GUI/wxMediaCtrl3.cpp:181:5: error: ‘BOOST_LOG_TRIVIAL’ was not declared in this scope
  181 |     BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data();
      |     ^~~~~~~~~~~~~~~~~

(cherry picked from commit c5c41e20ca2fc7f3b53a4c769961f73df6992008)

* FIX: wxMediaCtrl3 zero size crash

Change-Id: I16a3f7b3afe142bb957a1740b8e8c9820c92b349
Jira: STUDIO-8522
(cherry picked from commit 8cdaea1162ccbcc0bd03ecd99346f3b9cf52cf64)

* FIX: TabCtrl button margin

Change-Id: If8b05a4ef9efb8b57989ee1de6543631e5a3cf90
Jira: STUDIO-8265
(cherry picked from commit 1c5e65707109ad0582b6442cf8e515344f799c27)

* ENH: wxMediaCtrl3 display video frame at pts

Change-Id: I8847236d2307101e5f2befc6477cd20b3691841c
Jira: none
(cherry picked from commit 05328da4612c11d50f6fd90e872b97f5f8f46b1d)

* Fix: fix memory leak caused by ffmpeg decoding

Change-Id: I162ad4ea8d4601c1ffe17a65f292566c9dea6f0b
jira: no-jira
(cherry picked from commit eb20d03186c86b7398b97e3bae0a3c7a7b81c58c)

* ENH: update some missing codes

jira: no-jira
Change-Id: Icb2da53911430ac144b0fb601637a7ad31e7e8db
(cherry picked from commit 13b4213f8a24c76c16e49daf905fa29c0f646a5a)

* Fix build

* Update idle image

* Attempt to fix Windows CI build

* FIX: GTK video window resize ran in a free function without member access

wxMediaCtrl_OnSize referenced wxMediaCtrl2's private m_gtk_video_window,
which does not compile on Linux/GTK. Move the resizing into
wxMediaCtrl2::DoSetSize where the member is in scope.

* Install required tools for Linux

* Install required tools for macOS

* Add ffmpeg to flatpak

* Fix Linux build

* Try fix appimage build

* Fix Linux AppImage bundling of deps-built shared libraries

The AppImage dependency closure resolves each bundled ELF's DT_NEEDED
entries with plain ldd, which cannot resolve the deps-built FFmpeg stack
(libavcodec/libavutil/libswscale) once it is copied into the bundle:
those libs are not installed in any standard loader path and carry no
RUNPATH of their own, so ldd reports the siblings as missing and the
build aborts. Extend the loader path with the bundle directory plus the
source directories of already-bundled files (mirroring
scripts/check_appimage_libs.sh), and key the dedup set on the bundled
file path instead of the source path so dependencies resolved from the
bundle directory are not copied onto themselves.

Co-Authored-By: Claude <noreply@anthropic.com>

* Attempt to fix Linux unit test

* Fix Linux unit tests loading deps-built FFmpeg libraries

The test executables that link libslic3r_gui (which links PkgConfig::LIBAV)
have a load-time dependency on the deps-built FFmpeg shared libraries. The CI
unit-test runner only receives the tests artifact, so those libraries were
unresolvable there (Ubuntu 24.04 ships libavcodec.so.60, not .61). Copy the
libraries next to each affected test executable and give it an $ORIGIN rpath,
mirroring the Windows branch that copies DLLs next to every test executable.
orcaslicer_copy_sos now places the copies in the per-config output directory
for multi-config generators, like orcaslicer_copy_dlls does.

Co-Authored-By: Claude <noreply@anthropic.com>

* Add design doc for macOS FFmpeg player

Co-Authored-By: Claude <noreply@anthropic.com>

* Add implementation plan for macOS FFmpeg player

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: use FFmpeg media player on macOS with static FFmpeg

* build: build static-only FFmpeg for macOS deps

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: remove old BambuPlayer-based media player from macOS

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add static FFmpeg NOTFOUND guard; drop dead wxMediaCtrl2.h include

* build: drop redundant --enable-shared in FFmpeg deps configure

The literal --enable-shared was always overridden by ${_link_cmd}
(--enable-static --disable-shared on Apple, --enable-shared elsewhere)
and FFmpeg configure processes these flags in order, last one wins.
Remove it and the stale comment documenting the workaround.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: remove dead wxMediaCtrl2 player

wxMediaCtrl2 was never instantiated on any platform (USE_WX_MEDIA_CTRL_2
is 0 everywhere); wxMediaCtrl3 replaced it. Delete wxMediaCtrl2.cpp/h,
drop them from the Win/Linux source list and the gettext list.txt, and
collapse the preprocessor-dead #if USE_WX_MEDIA_CTRL_2 gate in
MediaPlayCtrl.h.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: remove dead GStreamer bambusrc plugin and its build dep

gstbambusrc was the GStreamer source element for the old wxMediaCtrl2
Wayland player, its only consumer (deleted in the previous commit).
The new FFmpeg player handles bambu:/// URIs through the Bambu C API
instead. Drop the plugin and the gstreamer-1.0 / gstreamer-base-1.0
REQUIRED pkg-config dependencies that existed solely for it.

Co-Authored-By: Claude <noreply@anthropic.com>

* build: move FFmpeg media player sources to the common GUI list

wxMediaCtrl3 and AVVideoDecoder are platform-neutral C++ compiled on
all three platforms, so list them once in the common SLIC3R_GUI_SOURCES
instead of duplicating them in the APPLE and non-APPLE branches. The
else() branch is now empty and drops out entirely.

Co-Authored-By: Claude <noreply@anthropic.com>

* deps: disable FFmpeg VideoToolbox/AudioToolbox HW-accel on macOS

The static libavcodec.a/avutil.a compiled the auto-detected
videotoolbox/audiotoolbox objects, which reference VideoToolbox
framework symbols (_VTDecompressionSession*). The app link line
happened to satisfy them transitively, but the orca_stubgen module
link (CI-only) failed with undefined symbols. The player decodes in
software (swscale), so disable both HW-accel paths to keep the
static libs self-contained.

Co-Authored-By: Claude <noreply@anthropic.com>

* Copy the decoded frame instead of aliasing the decoder's buffer

wxImage with static_data set stores the pointer and never copies it, so the
frame handed to wxMediaCtrl3 aliased AVVideoDecoder::bits_. That buffer is
rewritten by the next sws_scale with the mutex released, reallocated by
bits_.resize() when the window grows, and freed outright when the decoder
leaves PlayThread's loop body at end of stream, all while the GUI thread may
be painting from it.

Windows is unaffected either way, since toWxBitmap already copies the bits
into GDI.

---------

Co-authored-by: chunmao.guo <chunmao.guo@bambulab.com>
Co-authored-by: BBL\chuan.he <chuan.he@bambulab.com>
Co-authored-by: MackBambu <yongfang.bian@bambulab.com>
Co-authored-by: Bastien Nocera <hadess@hadess.net>
Co-authored-by: chao.zhang <chao.zhang@bambulab.com>
Co-authored-by: lane.wei <lane.wei@bambulab.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
SoftFever
2026-08-30 11:35:24 +08:00
committed by GitHub
37 changed files with 1473 additions and 1824 deletions

View File

@@ -149,7 +149,7 @@ jobs:
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
run: | run: |
if [ -z "${{ vars.SELF_HOSTED }}" ]; then if [ -z "${{ vars.SELF_HOSTED }}" ]; then
brew install automake texinfo libtool brew install automake texinfo libtool pkgconf yasm nasm
fi fi
./build_release_macos.sh -dx ${{ !vars.SELF_HOSTED && '-1' || '' }} -a ${{ inputs.arch }} -t 10.15 ./build_release_macos.sh -dx ${{ !vars.SELF_HOSTED && '-1' || '' }} -a ${{ inputs.arch }} -t 10.15
(cd "${{ github.workspace }}/deps/build/${{ inputs.arch }}" && \ (cd "${{ github.workspace }}/deps/build/${{ inputs.arch }}" && \

View File

@@ -385,6 +385,13 @@ jobs:
dir "C:/Program Files (x86)/Windows Kits/10/Include" dir "C:/Program Files (x86)/Windows Kits/10/Include"
choco install nsis choco install nsis
- name: Install pkg-config
# FFmpeg is discovered via pkg-config (pkg_check_modules LIBAV in
# src/slic3r/CMakeLists.txt); the Windows runners don't ship it.
if: runner.os == 'Windows' && !vars.SELF_HOSTED
run: |
choco install pkgconfiglite -y
- name: Build slicer Win - name: Build slicer Win
if: runner.os == 'Windows' if: runner.os == 'Windows'
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}

View File

@@ -298,6 +298,8 @@ if (APPLE)
SET(CMAKE_XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.orcaslicer.OrcaSlicer") SET(CMAKE_XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.orcaslicer.OrcaSlicer")
message(STATUS "Orca: IS_CROSS_COMPILE: ${IS_CROSS_COMPILE}") message(STATUS "Orca: IS_CROSS_COMPILE: ${IS_CROSS_COMPILE}")
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(CMAKE_INSTALL_RPATH "$ORIGIN")
endif () endif ()
# Proposal for C++ unit tests and sandboxes # Proposal for C++ unit tests and sandboxes
@@ -474,7 +476,8 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory. # WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory.
# We pick it from environment if it is not defined in another way # We pick it from environment if it is not defined in another way
# ORCA: Removed Netfabb STL fixing service support in favor of CGAL. # ORCA: Removed Netfabb STL fixing service support in favor of CGAL.
# if(WIN32) if(WIN32)
find_package(PkgConfig REQUIRED)
# if(NOT DEFINED WIN10SDK_PATH) # if(NOT DEFINED WIN10SDK_PATH)
# if(DEFINED ENV{WIN10SDK_PATH}) # if(DEFINED ENV{WIN10SDK_PATH})
# set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") # set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}")
@@ -510,7 +513,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# else() # else()
# message("Building without Win10 Netfabb STL fixing service support") # message("Building without Win10 Netfabb STL fixing service support")
# endif() # endif()
# endif() endif()
if (APPLE) if (APPLE)
message("OS X SDK Path: ${CMAKE_OSX_SYSROOT}") message("OS X SDK Path: ${CMAKE_OSX_SYSROOT}")
@@ -1065,6 +1068,10 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
${CMAKE_PREFIX_PATH}/bin/occt/TKXDESTEP.dll ${CMAKE_PREFIX_PATH}/bin/occt/TKXDESTEP.dll
${CMAKE_PREFIX_PATH}/bin/occt/TKXSBase.dll ${CMAKE_PREFIX_PATH}/bin/occt/TKXSBase.dll
${CMAKE_PREFIX_PATH}/bin/freetype.dll ${CMAKE_PREFIX_PATH}/bin/freetype.dll
${CMAKE_PREFIX_PATH}/bin/avcodec-61.dll
${CMAKE_PREFIX_PATH}/bin/swresample-5.dll
${CMAKE_PREFIX_PATH}/bin/swscale-8.dll
${CMAKE_PREFIX_PATH}/bin/avutil-59.dll
DESTINATION ${_out_dir}) DESTINATION ${_out_dir})
set(${output_dlls} set(${output_dlls}
@@ -1100,12 +1107,58 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
${_out_dir}/TKXSBase.dll ${_out_dir}/TKXSBase.dll
${_out_dir}/freetype.dll ${_out_dir}/freetype.dll
${_out_dir}/avcodec-61.dll
${_out_dir}/swresample-5.dll
${_out_dir}/swscale-8.dll
${_out_dir}/avutil-59.dll
PARENT_SCOPE PARENT_SCOPE
) )
endfunction() endfunction()
function(orcaslicer_copy_sos target config postfix output_sos)
get_property(_is_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
get_target_property(_alt_out_dir ${target} RUNTIME_OUTPUT_DIRECTORY)
if (_alt_out_dir)
set(_out_dir "${_alt_out_dir}")
elseif (_is_multi)
set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}/${config}")
else ()
set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}")
endif ()
file(COPY ${CMAKE_PREFIX_PATH}/lib/libavcodec.so
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61
${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61.3.100
${CMAKE_PREFIX_PATH}/lib/libavutil.so
${CMAKE_PREFIX_PATH}/lib/libavutil.so.59
${CMAKE_PREFIX_PATH}/lib/libavutil.so.59.8.100
${CMAKE_PREFIX_PATH}/lib/libswscale.so
${CMAKE_PREFIX_PATH}/lib/libswscale.so.8
${CMAKE_PREFIX_PATH}/lib/libswscale.so.8.1.100
${CMAKE_PREFIX_PATH}/lib/libswresample.so
${CMAKE_PREFIX_PATH}/lib/libswresample.so.5
${CMAKE_PREFIX_PATH}/lib/libswresample.so.5.1.100
DESTINATION ${_out_dir})
set(${output_sos}
${_out_dir}/libavcodec.so
${_out_dir}/libavcodec.so.61
${_out_dir}/libavcodec.so.61.3.100
${_out_dir}/libavutil.so
${_out_dir}/libavutil.so.59
${_out_dir}/libavutil.so.59.8.100
${_out_dir}/libswscale.so
${_out_dir}/libswscale.so.8
${_out_dir}/libswscale.so.8.1.100
${_out_dir}/libswresample.so
${_out_dir}/libswresample.so.5
${_out_dir}/libswresample.so.5.1.100
PARENT_SCOPE
)
endfunction()
# Bundled sources set their own warning flags, and a plain -Wall there means /Wall # Bundled sources set their own warning flags, and a plain -Wall there means /Wall
# (= -Weverything) under clang-cl. Target options are applied after the ones a target # (= -Weverything) under clang-cl. Target options are applied after the ones a target
@@ -1217,6 +1270,20 @@ else ()
endif() endif()
endif () endif ()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LIBRARY_FILES
${LIBDIR_BIN}/libavcodec.so.61
${LIBDIR_BIN}/libavcodec.so.61.3.100
${LIBDIR_BIN}/libavutil.so.59
${LIBDIR_BIN}/libavutil.so.59.8.100
${LIBDIR_BIN}/libswresample.so.5
${LIBDIR_BIN}/libswresample.so.5.1.100
${LIBDIR_BIN}/libswscale.so.8
${LIBDIR_BIN}/libswscale.so.8.1.100
)
install(FILES ${LIBRARY_FILES} DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif ()
install(FILES ${CMAKE_SOURCE_DIR}/LICENSE.txt DESTINATION ".") install(FILES ${CMAKE_SOURCE_DIR}/LICENSE.txt DESTINATION ".")
configure_file(${LIBDIR}/dev-utils/platform/unix/fhs.hpp.in ${LIBDIR_BIN}/dev-utils/platform/unix/fhs.hpp) configure_file(${LIBDIR}/dev-utils/platform/unix/fhs.hpp.in ${LIBDIR_BIN}/dev-utils/platform/unix/fhs.hpp)

3
deps/CMakeLists.txt vendored
View File

@@ -375,7 +375,7 @@ include(libnoise/libnoise.cmake)
include(Draco/Draco.cmake) include(Draco/Draco.cmake)
# Assimp: glTF/GLB/FBX import for the texture-to-color feature. include(FFMPEG/FFMPEG.cmake)
include(Assimp/Assimp.cmake) include(Assimp/Assimp.cmake)
@@ -459,6 +459,7 @@ set(_dep_list
dep_libnoise dep_libnoise
dep_python3 dep_python3
dep_wxInspector dep_wxInspector
dep_FFMPEG
dep_Assimp dep_Assimp
) )

87
deps/FFMPEG/FFMPEG.cmake vendored Normal file
View File

@@ -0,0 +1,87 @@
set(_conf_cmd ./configure)
if (MSVC)
set(_source_dir "${CMAKE_BINARY_DIR}/dep_FFMPEG-prefix/src/dep_FFMPEG")
set(PREBUILD_URL_arm64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-winarm64-orca-shared-7.0.zip")
set(PREBUILD_HASH_arm64 "12f4140279f2f8469885e1b5b2e8be9d788882914c21523cacd56989f3548054")
set(PREBUILD_URL_x64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-win64-orca-shared-7.0.zip")
set(PREBUILD_HASH_x64 "e65916020ddb9ef84b2666dfbcbfc9b1d67f69d15b4a66db53754637bf2d498c")
ExternalProject_Add(dep_FFMPEG
URL ${PREBUILD_URL_${DEPS_ARCH}}
URL_HASH SHA256=${PREBUILD_HASH_${DEPS_ARCH}}
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND
COMMAND ${CMAKE_COMMAND} -E copy_directory "${_source_dir}/bin" "${DESTDIR}/bin"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${_source_dir}/lib" "${DESTDIR}/lib"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${_source_dir}/include" "${DESTDIR}/include"
)
else ()
if (APPLE)
set(_minos_cmd
"--extra-cflags=-mmacosx-version-min=${DEP_OSX_TARGET}"
"--extra-ldflags=-mmacosx-version-min=${DEP_OSX_TARGET}"
)
# Static FFmpeg: nothing to bundle into the .app, no rpath handling.
# Disable the VideoToolbox/AudioToolbox HW-accel paths: the player decodes
# in software (swscale), and the auto-detected HW objects would drag in
# system frameworks that the static libs would then depend on.
set(_link_cmd --enable-static --disable-shared --disable-videotoolbox --disable-audiotoolbox)
if (IS_CROSS_COMPILE)
set(_cross_cmd --enable-cross-compile)
set(_pic_cmd --enable-pic)
if (${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64")
set(_arch_cmd --arch=arm64)
set(_cc_cmd "--cc=clang -arch arm64")
else()
set(_arch_cmd --arch=x86_64)
set(_cc_cmd "--cc=clang -arch x86_64")
endif()
endif()
else ()
set(_link_cmd --enable-shared)
endif ()
set(_build_j -j)
if(DEFINED ENV{CMAKE_BUILD_PARALLEL_LEVEL})
set(_build_j "-j$ENV{CMAKE_BUILD_PARALLEL_LEVEL}")
endif()
ExternalProject_Add(dep_FFMPEG
URL https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.3.tar.gz
URL_HASH SHA256=DEEDCABE339165214A3637DF4C86A507AEF0D793CF8774FF68735F4737E8DDBC
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG
CONFIGURE_COMMAND ${_conf_cmd}
${_cross_cmd}
${_pic_cmd}
${_arch_cmd}
${_cc_cmd}
"--prefix=${DESTDIR}"
${_link_cmd}
${_minos_cmd}
--disable-doc
--enable-small
--disable-outdevs
--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,udp
--disable-muxers
--enable-muxer=rtp
--disable-encoders
--disable-decoders
--enable-decoder=*aac*,h264*,mp3*,mjpeg,rv*
--disable-demuxers
--enable-demuxer=h264,mp3,mov
--disable-zlib
--disable-avdevice
BUILD_IN_SOURCE ON
BUILD_COMMAND make ${_build_j}
INSTALL_COMMAND make install
)
endif()

View File

@@ -0,0 +1,376 @@
# macOS FFmpeg Media Player Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make macOS use the same FFmpeg-based media player (`wxMediaCtrl3` + `AVVideoDecoder`) as Windows/Linux, linking the static FFmpeg libraries from the deps build, and remove the old `wxMediaCtrl2.mm` BambuPlayer-based player.
**Architecture:** The new player is platform-neutral C++ already used on Linux/Windows. Enabling it on macOS is pure build wiring: compile `wxMediaCtrl3.cpp` + `AVVideoDecoder.cpp` on macOS, drop the `__WXMAC__` alias that redirects `wxMediaCtrl3` to the old `wxMediaCtrl2`, and link static FFmpeg (`libavcodec.a`/`libswscale.a`/`libavutil.a`) from the deps install. The Bambu stream API is dlsym'd at runtime from the network plugin (`libBambuSource.dylib`), which already exports it — no plugin changes needed. Rendering reuses the existing `wxImage``DrawBitmap` paint path (same as Linux).
**Tech Stack:** C++17, wxWidgets, CMake, FFmpeg 7.0.3 (libavcodec/libswscale/libavutil), macOS (Xcode generator), `deps/` ExternalProject build system.
## Global Constraints
- Branch: `dev/ffmpeg-player-macos`. Commit after every task.
- **Linux and Windows builds must not change** — the FFmpeg deps flag change is guarded by `APPLE`; Linux keeps `--enable-shared`, Windows keeps its prebuilt DLL zips.
- Static FFmpeg only on macOS: deps produce `libavcodec.a`/`libswscale.a`/`libavutil.a`; the app links those explicitly — the app binary must have **no** `libav*` dylib references (`otool -L` check).
- Follow existing code style: PascalCase classes, snake_case functions, C++17.
- No changes to `StatusPanel.cpp`, `MediaPlayCtrl.*`, or the BambuTunnel interface — the app already creates `wxMediaCtrl3` and uses only its public interface.
- The player cannot be unit-tested (hardware/plugin-dependent GUI code); verification is build-level, link-level, and manual runtime on a Mac.
- `localization/i18n/list.txt` references only `wxMediaCtrl2.cpp` (Win/Linux, stays) — no translation-list changes needed.
- Build dirs on the dev machine: main app = `build_arm64/` (Xcode generator, multi-config), deps = `deps/build/arm64/` (Unix Makefiles). App target name: `OrcaSlicer`. Substitute your own configured build dirs where noted.
---
### Task 1: Enable wxMediaCtrl3 on macOS and link static FFmpeg
**Files:**
- Modify: `src/slic3r/GUI/wxMediaCtrl3.h` (lines 1822: the `#ifdef __WXMAC__` alias branch)
- Modify: `src/slic3r/GUI/wxMediaCtrl3.cpp:13` (uncomment the event define)
- Modify: `src/slic3r/GUI/wxMediaCtrl2.cpp:101` (remove the event define)
- Modify: `src/slic3r/CMakeLists.txt` (APPLE source list ~lines 779792; FFmpeg link block ~lines 905910)
**Interfaces:**
- Consumes: nothing new (all classes already exist).
- Produces: `wxMediaCtrl3` class compiled on macOS with the same interface as Linux/Windows — `Load(wxURI)`, `Play()`, `Stop()`, `SetIdleImage(wxString)`, `GetState()`, `GetLastError()`, `GetVideoSize()`, event `EVT_MEDIA_CTRL_STAT` defined once in the lib (from `wxMediaCtrl3.cpp`).
- [ ] **Step 1: Remove the macOS alias in wxMediaCtrl3.h**
Current (lines 1623 of `src/slic3r/GUI/wxMediaCtrl3.h`):
```cpp
void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height);
#ifdef __WXMAC__
#include "wxMediaCtrl2.h"
#define wxMediaCtrl3 wxMediaCtrl2
#else
#define BAMBU_DYNAMIC
```
New:
```cpp
void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height);
#define BAMBU_DYNAMIC
```
Also remove the matching `#endif` that closed the `#else` branch (the one before the final `#endif /* wxMediaCtrl3_h */`), so the file's `#ifndef`/`#endif` guard pair stays balanced.
- [ ] **Step 2: Move the EVT_MEDIA_CTRL_STAT definition into wxMediaCtrl3.cpp**
In `src/slic3r/GUI/wxMediaCtrl3.cpp:13`, uncomment:
```cpp
//wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
```
becomes:
```cpp
wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
```
In `src/slic3r/GUI/wxMediaCtrl2.cpp:101`, delete:
```cpp
wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
```
(One definition total in the lib — `MediaPlayCtrl.cpp:59` binds this event on the media ctrl.)
- [ ] **Step 3: Update the APPLE source list in CMakeLists.txt**
In `src/slic3r/CMakeLists.txt`, the APPLE branch (currently compiles `wxMediaCtrl2.mm`, which becomes dead on macOS):
```cmake
GUI/wxMediaCtrl2.mm
GUI/wxMediaCtrl2.h
GUI/wxMediaCtrl3.h
)
```
becomes:
```cmake
GUI/AVVideoDecoder.cpp
GUI/AVVideoDecoder.hpp
GUI/wxMediaCtrl3.cpp
GUI/wxMediaCtrl3.h
)
```
(The `else ()` branch — Win/Linux — stays exactly as it is.)
- [ ] **Step 4: Link static FFmpeg on macOS**
In `src/slic3r/CMakeLists.txt`, the FFmpeg block (currently `if (NOT APPLE)`):
```cmake
if (NOT APPLE)
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
libavcodec
libswscale
libavutil
)
target_link_libraries(libslic3r_gui PkgConfig::LIBAV)
endif()
```
becomes:
```cmake
if (APPLE)
# Static FFmpeg from the deps install: nothing to bundle into the .app,
# no rpath/install_name handling. Order matters: avcodec -> swscale -> avutil.
find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
else ()
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
libavcodec
libswscale
libavutil
)
target_link_libraries(libslic3r_gui PkgConfig::LIBAV)
endif()
```
The deps install (`${CMAKE_PREFIX_PATH}/lib`) already contains the three `.a` files from the existing arm64 deps build — no deps rebuild needed for this task.
- [ ] **Step 5: Reconfigure and build the app**
Run (Xcode generator; `cmake` re-runs automatically on build):
```bash
cmake --build build_arm64 --config RelWithDebInfo --target OrcaSlicer
```
Expected: configure succeeds (no `pkg_check_modules` errors on macOS, `find_library` finds all three `.a` files), compile succeeds (`wxMediaCtrl3.cpp` and `AVVideoDecoder.cpp` compile on macOS without changes), link succeeds.
If CMake complains that `wxMediaCtrl3.h` is included but not in the source list or similar IDE-only warnings — ignore; headers in the list are cosmetic.
- [ ] **Step 6: Verify no dynamic FFmpeg dependency**
```bash
otool -L build_arm64/src/RelWithDebInfo/OrcaSlicer.app/Contents/MacOS/OrcaSlicer | grep -i "libav" || echo "OK: no dynamic FFmpeg"
```
Expected: prints `OK: no dynamic FFmpeg` (empty grep output). This is the whole point of static linking — nothing to bundle into the `.app`.
- [ ] **Step 7: Quick sanity — macOS unit tests still pass**
```bash
ctest --test-dir build_arm64/tests/libslic3r --output-on-failure
```
Expected: passes (add `-C RelWithDebInfo` if the multi-config generator requires it). If no tests were built in this build dir, build target `tests` first (`cmake --build build_arm64 --config RelWithDebInfo --target tests`).
- [ ] **Step 8: Commit**
```bash
git add src/slic3r/CMakeLists.txt src/slic3r/GUI/wxMediaCtrl3.h src/slic3r/GUI/wxMediaCtrl3.cpp src/slic3r/GUI/wxMediaCtrl2.cpp
git commit -m "feat: use FFmpeg media player on macOS with static FFmpeg"
```
---
### Task 2: Static-only FFmpeg in the macOS deps build
**Files:**
- Modify: `deps/FFMPEG/FFMPEG.cmake` (non-MSVC branch, APPLE section and CONFIGURE_COMMAND)
**Interfaces:**
- Consumes: nothing.
- Produces: a deps install on macOS containing only `libavcodec.a`, `libswscale.a`, `libavutil.a` (+ headers) — no `libav*` dylibs, so no bundling/rpath machinery is ever needed on macOS. Linux and Windows output are unchanged.
- [ ] **Step 1: Add the static flag variable**
In `deps/FFMPEG/FFMPEG.cmake`, inside the non-MSVC `else ()` branch, in the existing `if (APPLE)` block:
```cmake
if (APPLE)
set(_minos_cmd
"CFLAGS=-mmacosx-version-min=${DEP_OSX_TARGET}"
"LDFLAGS=-mmacosx-version-min=${DEP_OSX_TARGET}"
)
```
add after the `_minos_cmd` set:
```cmake
# Static FFmpeg: nothing to bundle into the .app, no rpath handling.
# Shared flags must come AFTER --enable-shared below so they win.
set(_link_cmd --enable-static --disable-shared)
```
and add a matching `else ()` after the `if (IS_CROSS_COMPILE) ... endif()` block inside that `if (APPLE)`, so non-Apple Unix keeps shared:
```cmake
else ()
set(_link_cmd --enable-shared)
endif ()
```
(If the existing `if (IS_CROSS_COMPILE)` block is the last thing inside `if (APPLE)`, the new `else ()` closes the `if (APPLE)` itself.)
- [ ] **Step 2: Use the variable in CONFIGURE_COMMAND**
In the `ExternalProject_Add(dep_FFMPEG ...)` configure command:
```cmake
"--prefix=${DESTDIR}"
--enable-shared
```
becomes:
```cmake
"--prefix=${DESTDIR}"
--enable-shared
${_link_cmd}
```
Order matters: `--enable-shared` comes first, then `--enable-static --disable-shared` (APPLE) or `--enable-shared` (Linux) — the last flag wins in FFmpeg configure.
- [ ] **Step 3: Rebuild the FFmpeg dep (slow — several minutes, run in background)**
The changed CONFIGURE_COMMAND invalidates the ExternalProject stamp, so this re-configures and rebuilds FFmpeg:
```bash
cmake --build deps/build/arm64 --target dep_FFMPEG
```
For a fully clean static-only check (removes the previous shared build tree, which can leave stale `.dylib` files behind in the in-source build):
```bash
rm -rf deps/build/arm64/dep_FFMPEG-prefix
cmake --build deps/build/arm64 --target dep_FFMPEG
```
- [ ] **Step 4: Verify the artifacts**
```bash
ls deps/build/arm64/dep_FFMPEG-prefix/src/dep_FFMPEG/libavcodec/*.a
ls deps/build/arm64/dep_FFMPEG-prefix/src/dep_FFMPEG/libavcodec/*.dylib 2>/dev/null || echo "OK: no dylibs"
```
Expected: `libavcodec.a` present, second command prints `OK: no dylibs`. Check `libavutil` and `libswscale` the same way.
- [ ] **Step 5: Verify the app still links against the static libs**
```bash
cmake --build build_arm64 --config RelWithDebInfo --target OrcaSlicer
otool -L build_arm64/src/RelWithDebInfo/OrcaSlicer.app/Contents/MacOS/OrcaSlicer | grep -i "libav" || echo "OK: no dynamic FFmpeg"
```
Expected: build succeeds, `OK: no dynamic FFmpeg`.
- [ ] **Step 6: Commit**
```bash
git add deps/FFMPEG/FFMPEG.cmake
git commit -m "build: build static-only FFmpeg for macOS deps"
```
---
### Task 3: Remove the old macOS player
**Files:**
- Delete: `src/slic3r/GUI/wxMediaCtrl2.mm`
- Delete: `src/slic3r/GUI/BambuPlayer/BambuPlayer.h` (and the empty `BambuPlayer/` dir)
- Modify: `src/slic3r/GUI/wxMediaCtrl2.h` (remove the `#ifdef __WXMAC__` section, lines 2260)
**Interfaces:**
- Consumes: Task 1 (macOS no longer references `wxMediaCtrl2` — nothing includes `wxMediaCtrl2.h` on macOS anymore; `wxMediaCtrl2` is never instantiated on any platform).
- Produces: a clean tree where the old BambuPlayer-based player is gone from macOS. The `BambuPlayer` ObjC class itself remains inside the network plugin (external prebuilt binary) — only the GUI-side consumer is removed.
- [ ] **Step 1: Delete the old player files**
```bash
git rm src/slic3r/GUI/wxMediaCtrl2.mm
git rm src/slic3r/GUI/BambuPlayer/BambuPlayer.h
rmdir src/slic3r/GUI/BambuPlayer 2>/dev/null || true
```
- [ ] **Step 2: Strip the __WXMAC__ section from wxMediaCtrl2.h**
In `src/slic3r/GUI/wxMediaCtrl2.h`, remove the entire macOS branch of the `#ifdef __WXMAC__` guard — from `#ifdef __WXMAC__` (line 22) through the closing `};` of the mac class (line 60), and the `#else` marker — leaving only the non-mac `class wxMediaCtrl2 : public wxMediaCtrl { ... };` definition followed by the final `#endif /* wxMediaCtrl2_h */`. The resulting file keeps its `#ifndef`/`#endif` include guard pair balanced.
The file stays on disk because Win/Linux compile `wxMediaCtrl2.cpp`, which includes it.
- [ ] **Step 3: Grep for leftover references**
```bash
grep -rn "wxMediaCtrl2.mm\|BambuPlayer/BambuPlayer.h\|BambuPlayer" src/slic3r --include="*.cpp" --include="*.h" --include="*.mm" --include="*.txt"
```
Expected: no hits in `src/slic3r/GUI` (ignore `localization/i18n/list.txt:196`, which lists the Win/Linux `wxMediaCtrl2.cpp` and stays).
- [ ] **Step 4: Rebuild the app**
```bash
cmake --build build_arm64 --config RelWithDebInfo --target OrcaSlicer
```
Expected: configure + compile + link succeed with the deleted files gone.
- [ ] **Step 5: Commit**
```bash
git add -A src/slic3r/GUI
git commit -m "refactor: remove old BambuPlayer-based media player from macOS"
```
---
### Task 4: Runtime verification on hardware
**Files:** none — manual verification.
**Interfaces:** consumes all prior tasks. Final gate: the new player must actually stream on a Mac.
- [ ] **Step 1: Launch the freshly built app**
```bash
open build_arm64/src/RelWithDebInfo/OrcaSlicer.app
```
Expected: app launches normally; no crash in the network/device subsystem.
- [ ] **Step 2: Load the network plugin and open the Device tab**
Log in / ensure the network plugin (`libBambuSource.dylib`) loads, select a printer, open the Device tab (camera monitoring panel).
Expected: the camera preview area shows the idle image initially (no crash — this exercises `wxMediaCtrl3::SetIdleImage` and the `wxImage` load path on macOS for the first time).
- [ ] **Step 3: Start the stream and watch it render**
Click play / wait for `MediaPlayCtrl` to start the stream.
Expected: live video renders in the panel. Check the console/log output (`BOOST_LOG` goes to the terminal if run from it, or check the log file):
- `stat_log ...` lines appear (the `EVT_MEDIA_CTRL_STAT` path is live — proves the Bambu C API dlsym worked from `libBambuSource.dylib`);
- no repeated decode/error messages like `AVVideoDecoder: ...` or `can not find function ...` (proves `StaticBambuLib::get` resolved all Bambu functions);
- Stop/Play toggle works; idle image reappears on stop;
- window resize keeps aspect ratio (exercises `DoSetSize`/`adjust_frame_size`/`paintEvent`).
- [ ] **Step 4: Confirm the old player is really gone**
Expected: nothing in the logs references `BambuPlayer` (the ObjC class is no longer dlsym'd); the video path is entirely `wxMediaCtrl3` + `AVVideoDecoder`.
If a printer is unavailable, at minimum verify Steps 12 (launch + idle image) and note in the PR that live-stream verification needs hardware.
- [ ] **Step 5: Final review pass**
```bash
git log --oneline -6
git show --stat HEAD # and each of the three task commits
```
Expected: the last 4 commits are the design doc + the 3 implementation tasks (each task commit touches only its listed files). Review the diff for scope: no Linux/Windows changes beyond the two `EVT_MEDIA_CTRL_STAT` lines in Task 1, no `StatusPanel`/`MediaPlayCtrl` changes.

View File

@@ -0,0 +1,110 @@
# FFmpeg Media Player for macOS — Design
Date: 2026-08-14
Branch: `dev/ffmpeg-player-macos`
## Problem
The branch's new FFmpeg-based media player (`wxMediaCtrl3` + `AVVideoDecoder`) is used on
Windows and Linux, but macOS still runs the old player: `wxMediaCtrl2.mm`, an ObjC
`BambuPlayer` class dlsym'd from the Bambu network plugin that renders via CALayer.
On macOS, `wxMediaCtrl3` is currently aliased to `wxMediaCtrl2` and FFmpeg is not linked
into the app at all.
Goal: make macOS use the same FFmpeg player as Windows/Linux, linking the **static**
FFmpeg libraries from the deps build instead of dynamic ones.
## Current state (verified)
- New player (Win/Linux): `GUI/wxMediaCtrl3.cpp` + `GUI/AVVideoDecoder.cpp`. Decodes with
FFmpeg (libavcodec/libswscale/libavutil), renders frames into `wxImage` (non-Windows) /
`wxBitmap` (Windows) drawn in a `paintEvent`, feeds via the `Bambu_*` C API
(`BambuTunnel.h`, `BAMBU_DYNAMIC`) dlsym'd from the network plugin through
`StaticBambuLib::get()` (`GUI/Printer/PrinterFileSystem.cpp`, compiled on all platforms).
- Old player (macOS): `GUI/wxMediaCtrl2.mm` uses the ObjC `BambuPlayer` class found via
`dlsym(module, "OBJC_CLASS_$_BambuPlayer")` in `libBambuSource.dylib`.
- The macOS network plugin `libBambuSource.dylib` already exports the full Bambu C API
(verified with `nm`), so the new player needs zero plugin changes.
- FFmpeg linking in `src/slic3r/CMakeLists.txt` is guarded by `if (NOT APPLE)`
macOS currently does not link FFmpeg.
- `deps/FFMPEG/FFMPEG.cmake`: non-MSVC branch builds FFmpeg from source with
`--enable-shared`. The existing arm64 deps build on the dev machine happened to be
configured with both static and shared enabled, so `libavcodec.a` / `libswscale.a` /
`libavutil.a` are already present at
`deps/build/arm64/OrcaSlicer_dep/usr/local/lib/`.
- `EVT_MEDIA_CTRL_STAT` is `wxDEFINE_EVENT`'d in `wxMediaCtrl2.cpp` (Win/Linux) and
`wxMediaCtrl2.mm` (macOS); the define in `wxMediaCtrl3.cpp` is commented out.
- `wxMediaCtrl2` is never instantiated anywhere on any platform — dead code.
- `StatusPanel` already creates `wxMediaCtrl3`; `MediaPlayCtrl` only uses the
`wxMediaCtrl3` interface (`Load/Play/Stop/GetState/GetVideoSize/GetLastError/SetIdleImage`),
so no UI-side changes are needed.
## Approach (approved)
**Reuse the shared player on macOS.** Compile the existing `wxMediaCtrl3.cpp` +
`AVVideoDecoder.cpp` on macOS so all three platforms run one implementation.
Rendering uses the existing `wxImage``DrawBitmap` paint path, identical to Linux.
Known trade-off: frames are scaled to the widget's logical (1x) size, so Retina is
slightly soft compared to the old CALayer player. Accepted for now; a Retina-aware
scaling follow-up is possible later.
Rejected alternative: a native CGImage/CALayer renderer for macOS — faster and
Retina-crisp, but adds a second render implementation to maintain.
## Changes
### 1. Enable the FFmpeg player on macOS (source)
- `GUI/wxMediaCtrl3.h`: remove the `#ifdef __WXMAC__` branch (lines 1822) that aliases
`wxMediaCtrl3``wxMediaCtrl2`. macOS then compiles the real `wxMediaCtrl3` class,
including the `BAMBU_DYNAMIC` BambuTunnel path used on Linux.
- Event symbol fix: move `wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent)` into
`wxMediaCtrl3.cpp` (uncomment the existing line) and remove it from
`wxMediaCtrl2.cpp`. One definition total in the lib; all three platforms resolve it.
### 2. Static FFmpeg linking (deps + app)
- `deps/FFMPEG/FFMPEG.cmake`: in the non-MSVC branch, pass
`--disable-shared --enable-static` when `APPLE`. Linux keeps `--enable-shared`;
Windows keeps its prebuilt shared DLL zips. Fresh macOS deps builds install only
`libavcodec.a` / `libswscale.a` / `libavutil.a` — no dylibs to bundle, no
rpath/install_name handling. (The existing local arm64 deps build already contains
the `.a` files, so no deps rebuild is strictly needed to try the change locally,
but a fresh CI deps build must produce them.)
- `src/slic3r/CMakeLists.txt`:
- APPLE branch of `SLIC3R_GUI_SOURCES`: add `GUI/wxMediaCtrl3.cpp`,
`GUI/wxMediaCtrl3.h`, `GUI/AVVideoDecoder.cpp`, `GUI/AVVideoDecoder.hpp`;
remove `GUI/wxMediaCtrl2.mm` and `GUI/wxMediaCtrl2.h` (the `.h` stays on
disk for the Win/Linux build of `wxMediaCtrl2.cpp`, but nothing on macOS
includes it after this change).
- Add an APPLE mirror of the `NOT APPLE` FFmpeg block: `find_library` for
`libavcodec.a`, `libswscale.a`, `libavutil.a` under `${CMAKE_PREFIX_PATH}/lib`
with `NO_DEFAULT_PATH`, link them (order avcodec → swscale → avutil), and add
`${CMAKE_PREFIX_PATH}/include` as a SYSTEM include directory. Deps are built with
`--disable-zlib` and no external codecs, so the three static libs link cleanly.
### 3. Remove the old player
- Delete `GUI/wxMediaCtrl2.mm` and `GUI/BambuPlayer/BambuPlayer.h` (header used only
by the `.mm`; the real `BambuPlayer` lives inside the network plugin).
- Remove the now-dead `__WXMAC__` section of `GUI/wxMediaCtrl2.h`.
- `wxMediaCtrl2.cpp` (Win/Linux) stays in the build as-is (dead but harmless; out of
scope to remove on this branch).
### 4. Verification
- Build on macOS: `cmake --build build_arm64` (or `build/arm64`).
- Confirm no dynamic FFmpeg dependency: `otool -L` on the app binary shows no `libav*`
dylib references.
- Runtime: with the network plugin loaded, the Device tab camera preview streams via
the FFmpeg player (check the device page / `MediaPlayCtrl`).
- macOS `ctest` still passes — static linking means no test-executable `.so` copying
hacks (unlike the Linux shared-lib setup).
## Out of scope
- Linux (shared libs, AppImage/flatpak bundling) and Windows (prebuilt DLL zips)
keep their current FFmpeg setup.
- Retina-aware frame scaling / native CGImage rendering (follow-up if visual quality
is judged insufficient).
- Audio streaming (neither player plays audio in this UI path).

View File

@@ -193,7 +193,6 @@ src/slic3r/GUI/ObjColorDialog.cpp
src/slic3r/GUI/SyncAmsInfoDialog.cpp src/slic3r/GUI/SyncAmsInfoDialog.cpp
src/slic3r/GUI/WipeTowerDialog.cpp src/slic3r/GUI/WipeTowerDialog.cpp
src/slic3r/GUI/wxExtensions.cpp src/slic3r/GUI/wxExtensions.cpp
src/slic3r/GUI/wxMediaCtrl2.cpp
src/slic3r/GUI/WebUserLoginDialog.cpp src/slic3r/GUI/WebUserLoginDialog.cpp
src/slic3r/GUI/WebGuideDialog.cpp src/slic3r/GUI/WebGuideDialog.cpp
src/slic3r/GUI/KBShortcutsDialog.hpp src/slic3r/GUI/KBShortcutsDialog.hpp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 579 KiB

View File

@@ -318,6 +318,12 @@ modules:
sha256: 0ba163956f2d468b19a91b96c5aba66ee9610843ea41dda628ea44cdafde7db7 sha256: 0ba163956f2d468b19a91b96c5aba66ee9610843ea41dda628ea44cdafde7db7
dest: external-packages/wxInspector dest: external-packages/wxInspector
# FFmpeg n7.0.3
- type: file
url: https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.3.tar.gz
sha256: deedcabe339165214a3637df4c86a507aef0d793cf8774ff68735f4737e8ddbc
dest: external-packages/FFMPEG
# --------------------------------------------------------------- # ---------------------------------------------------------------
# Fallback archives for deps normally provided by the GNOME SDK. # Fallback archives for deps normally provided by the GNOME SDK.
# These are only used if find_package() fails to locate them. # These are only used if find_package() fails to locate them.

View File

@@ -25,6 +25,9 @@ export REQUIRED_DEV_PACKAGES=(
wayland-protocols wayland-protocols
webkit2gtk-4.1 webkit2gtk-4.1
wget wget
pkgconf
yasm
nasm
) )
if [[ -n "$UPDATE_LIB" ]] if [[ -n "$UPDATE_LIB" ]]

View File

@@ -25,6 +25,9 @@ export REQUIRED_DEV_PACKAGES=(
wayland-protocols wayland-protocols
webkit2gtk webkit2gtk
wget wget
pkgconf
yasm
nasm
) )
if [[ -n "$UPDATE_LIB" ]] if [[ -n "$UPDATE_LIB" ]]

View File

@@ -20,6 +20,7 @@ export REQUIRED_BUNDLES=(
perl-basic perl-basic
texinfo texinfo
wget wget
nasm
) )
if [[ -n "$UPDATE_LIB" ]] if [[ -n "$UPDATE_LIB" ]]

View File

@@ -27,6 +27,9 @@ REQUIRED_DEV_PACKAGES=(
ninja-build ninja-build
texinfo texinfo
wget wget
pkgconf
yasm
nasm
) )
if [[ -n "$UPDATE_LIB" ]] if [[ -n "$UPDATE_LIB" ]]

View File

@@ -31,6 +31,9 @@ REQUIRED_DEV_PACKAGES=(
webkit2gtk4.1-devel webkit2gtk4.1-devel
wget wget
libcurl-devel libcurl-devel
pkgconf
yasm
nasm
) )
if [[ -n "$UPDATE_LIB" ]] if [[ -n "$UPDATE_LIB" ]]

View File

@@ -32,6 +32,9 @@ REQUIRED_DEV_PACKAGES=(
sys-devel/m4 sys-devel/m4
virtual/libudev virtual/libudev
x11-libs/gtk+:3 x11-libs/gtk+:3
dev-util/pkgconf
dev-lang/yasm
dev-lang/nasm
) )
if [[ -n "$UPDATE_LIB" ]] if [[ -n "$UPDATE_LIB" ]]

View File

@@ -30,6 +30,9 @@ REQUIRED_DEV_PACKAGES=(
webkit2gtk4-devel webkit2gtk4-devel
wget wget
libcurl-devel libcurl-devel
pkgconf
yasm
nasm
) )
if [[ -n "$UPDATE_LIB" ]] if [[ -n "$UPDATE_LIB" ]]

View File

@@ -294,6 +294,16 @@ if (WIN32)
endif() endif()
else () else ()
if (NOT APPLE)
set(output_sos_Release "")
set(output_sos_Debug "")
add_custom_target(OrcaSlicerSosCopy ALL DEPENDS OrcaSlicer)
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
orcaslicer_copy_sos(OrcaSlicerSosCopy "Debug" "d" output_sos_Debug)
else()
orcaslicer_copy_sos(OrcaSlicerSosCopy "Release" "" output_sos_Release)
endif()
endif()
if (APPLE AND NOT CMAKE_MACOSX_BUNDLE) if (APPLE AND NOT CMAKE_MACOSX_BUNDLE)
# On OSX, the name of the binary matches the name of the Application. # On OSX, the name of the binary matches the name of the Application.
add_custom_command(TARGET OrcaSlicer POST_BUILD add_custom_command(TARGET OrcaSlicer POST_BUILD
@@ -372,5 +382,9 @@ if (WIN32)
install(FILES ${output_dlls_${build_type}} DESTINATION ".") install(FILES ${output_dlls_${build_type}} DESTINATION ".")
install(DIRECTORY "${CMAKE_PREFIX_PATH}/libpython/" DESTINATION "python") install(DIRECTORY "${CMAKE_PREFIX_PATH}/libpython/" DESTINATION "python")
else () else ()
if (APPLE)
else()
install(FILES ${output_sos_${build_type}} DESTINATION "${CMAKE_INSTALL_PREFIX}")
endif()
install(TARGETS OrcaSlicer RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR}) install(TARGETS OrcaSlicer RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR})
endif () endif ()

View File

@@ -83,6 +83,10 @@ copy_shared_object_to_dir() {
src_real="$(readlink -f "$src")" src_real="$(readlink -f "$src")"
dst_name="$(basename "$src_real")" dst_name="$(basename "$src_real")"
mkdir -p "$dst_dir" mkdir -p "$dst_dir"
if [ "$src_real" = "$dst_dir/$dst_name" ]; then
# Already bundled; the dependency resolved from the bundle directory.
return 0
fi
cp -fL "$src_real" "$dst_dir/$dst_name" cp -fL "$src_real" "$dst_dir/$dst_name"
if [ -L "$src" ]; then if [ -L "$src" ]; then
@@ -96,12 +100,23 @@ copy_shared_object_to_dir() {
} }
bundle_dependency_closure() { bundle_dependency_closure() {
local dst_dir="$1" local dst_dir
dst_dir="$(cd -- "$1" && pwd)"
shift shift
local -a queue=("$@") local -a queue=("$@")
local target dep dep_real copied_path local target dep dep_real dep_key copied_path
declare -A seen=() declare -A seen=()
# Dependencies are resolved with ldd, which only searches the default
# loader path. Deps-built shared libraries (e.g. the FFmpeg stack) are not
# installed there and carry no RUNPATH of their own, so once copied into
# the bundle ldd can no longer resolve one sibling from another
# (libavcodec -> libavutil) and reports it as missing. Extend the loader
# path with the bundle directory plus the source directories of files
# already bundled, so every library that was resolved once keeps resolving
# for its own dependencies. The audit script does the same
# (scripts/check_appimage_libs.sh).
local -a search_dirs=("$dst_dir")
while [ ${#queue[@]} -gt 0 ]; do while [ ${#queue[@]} -gt 0 ]; do
target="${queue[0]}" target="${queue[0]}"
@@ -122,17 +137,24 @@ bundle_dependency_closure() {
continue continue
fi fi
if [ -n "${seen[$dep_real]}" ]; then # Key dedup on the bundled file rather than the source path: once
# ldd resolves a library from the bundle directory (via the
# LD_LIBRARY_PATH above) its path is a dst_dir path, which differs
# from the source path the first resolution returned. Keying on
# the source path would re-copy the file onto itself.
dep_key="$dst_dir/$(basename "$dep_real")"
if [ -n "${seen[$dep_key]}" ]; then
continue continue
fi fi
seen[$dep_real]=1 seen[$dep_key]=1
copy_shared_object_to_dir "$dep" "$dst_dir" copy_shared_object_to_dir "$dep" "$dst_dir"
search_dirs+=("$(dirname "$dep_real")")
copied_path="$dst_dir/$(basename "$dep_real")" copied_path="$dst_dir/$(basename "$dep_real")"
if [ -e "$copied_path" ]; then if [ -e "$copied_path" ]; then
queue+=("$copied_path") queue+=("$copied_path")
fi fi
done < <(appimage_list_direct_dependencies "$target") done < <(LD_LIBRARY_PATH="$(IFS=:; printf '%s' "${search_dirs[*]}")${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" appimage_list_direct_dependencies "$target")
done done
} }

View File

@@ -38,6 +38,8 @@ set(SLIC3R_GUI_SOURCES
GUI/AuxiliaryDataViewModel.hpp GUI/AuxiliaryDataViewModel.hpp
GUI/AuxiliaryDialog.cpp GUI/AuxiliaryDialog.cpp
GUI/AuxiliaryDialog.hpp GUI/AuxiliaryDialog.hpp
GUI/AVVideoDecoder.cpp
GUI/AVVideoDecoder.hpp
GUI/Auxiliary.hpp GUI/Auxiliary.hpp
GUI/BackgroundSlicingProcess.cpp GUI/BackgroundSlicingProcess.cpp
GUI/BackgroundSlicingProcess.hpp GUI/BackgroundSlicingProcess.hpp
@@ -611,6 +613,8 @@ set(SLIC3R_GUI_SOURCES
GUI/WipeTowerDialog.cpp GUI/WipeTowerDialog.cpp
GUI/wxExtensions.cpp GUI/wxExtensions.cpp
GUI/wxExtensions.hpp GUI/wxExtensions.hpp
GUI/wxMediaCtrl3.cpp
GUI/wxMediaCtrl3.h
plugin/PythonInterpreter.cpp plugin/PythonInterpreter.cpp
plugin/PythonInterpreter.hpp plugin/PythonInterpreter.hpp
plugin/PythonPluginBridge.cpp plugin/PythonPluginBridge.cpp
@@ -799,21 +803,8 @@ if (APPLE)
GUI/DeepLinkHandlerMac.mm GUI/DeepLinkHandlerMac.mm
GUI/DeepLinkHandlerMac.h GUI/DeepLinkHandlerMac.h
GUI/GUI_UtilsMac.mm GUI/GUI_UtilsMac.mm
GUI/wxMediaCtrl2.mm
GUI/wxMediaCtrl2.h
) )
FIND_LIBRARY(DISKARBITRATION_LIBRARY DiskArbitration) FIND_LIBRARY(DISKARBITRATION_LIBRARY DiskArbitration)
else ()
list(APPEND SLIC3R_GUI_SOURCES
GUI/wxMediaCtrl2.cpp
GUI/wxMediaCtrl2.h
)
endif ()
if (UNIX AND NOT APPLE)
list(APPEND SLIC3R_GUI_SOURCES
GUI/Printer/gstbambusrc.c
)
endif () endif ()
set(ORCA_UPDATER_SIG_KEY_B64 "${ORCA_UPDATER_SIG_KEY}") set(ORCA_UPDATER_SIG_KEY_B64 "${ORCA_UPDATER_SIG_KEY}")
@@ -913,6 +904,26 @@ if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE)
endif () endif ()
if (APPLE)
# Static FFmpeg from the deps install: nothing to bundle into the .app,
# no rpath/install_name handling. Order matters: avcodec -> swscale -> avutil.
find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
message(FATAL_ERROR "Static FFmpeg (libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.")
endif ()
target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
else ()
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
libavcodec
libswscale
libavutil
)
target_link_libraries(libslic3r_gui PkgConfig::LIBAV)
endif()
# We need to implement some hacks for wxWidgets and touch the underlying GTK # We need to implement some hacks for wxWidgets and touch the underlying GTK
# layer and sub-libraries. This forces us to use the include locations and # layer and sub-libraries. This forces us to use the include locations and
# link these libraries. # link these libraries.
@@ -939,11 +950,6 @@ if (UNIX AND NOT APPLE)
target_compile_definitions(libslic3r_gui PRIVATE wxHAVE_GDK_WAYLAND) target_compile_definitions(libslic3r_gui PRIVATE wxHAVE_GDK_WAYLAND)
endif () endif ()
# We add GStreamer for bambu:/// support.
pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0)
pkg_check_modules(GST_BASE REQUIRED gstreamer-base-1.0)
target_link_libraries(libslic3r_gui ${GSTREAMER_LIBRARIES} ${GST_BASE_LIBRARIES})
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GSTREAMER_INCLUDE_DIRS} ${GST_BASE_INCLUDE_DIRS})
endif () endif ()
# Add a definition so that we can tell we are compiling slic3r. # Add a definition so that we can tell we are compiling slic3r.

View File

@@ -0,0 +1,170 @@
#include "AVVideoDecoder.hpp"
#include <assert.h>
extern "C"
{
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
}
AVVideoDecoder::AVVideoDecoder()
{
codec_ctx_ = avcodec_alloc_context3(nullptr);
}
AVVideoDecoder::~AVVideoDecoder()
{
if (sws_ctx_)
sws_freeContext(sws_ctx_);
if (frame_)
av_frame_free(&frame_);
if (codec_ctx_)
avcodec_free_context(&codec_ctx_);
}
int AVVideoDecoder::open(Bambu_StreamInfo const &info)
{
auto codec_id = info.sub_type == AVC1 ? AV_CODEC_ID_H264 : AV_CODEC_ID_MJPEG;
auto codec = avcodec_find_decoder(codec_id);
if (codec == nullptr) {
fprintf(stderr, "AVVideoDecoder: unsupported codec!\n");
return -1; // Codec not found
}
/* open the coderc */
if (avcodec_open2(codec_ctx_, codec, nullptr) < 0) {
fprintf(stderr, "AVVideoDecoder: could not open codec\n");
return -1;
}
// Allocate an AVFrame structure
frame_ = av_frame_alloc();
if (frame_ == nullptr)
return -1;
return 0;
}
int AVVideoDecoder::decode(const Bambu_Sample &sample)
{
int ret = -1;
AVPacket *pkt = av_packet_alloc();
if (!pkt) {
return ret;
}
ret = av_new_packet(pkt, sample.size);
if (ret != 0) {
av_packet_free(&pkt);
return ret;
}
memcpy(pkt->data, sample.buffer, size_t(sample.size));
ret = avcodec_send_packet(codec_ctx_, pkt);
if (ret == 0) {
got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0;
}
av_packet_unref(pkt);
av_packet_free(&pkt);
return ret;
}
int AVVideoDecoder::flush()
{
int ret = avcodec_send_packet(codec_ctx_, nullptr);
got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0;
return ret;
}
void AVVideoDecoder::close()
{
}
bool AVVideoDecoder::toWxImage(wxImage &image, wxSize const &size2)
{
if (!got_frame_)
return false;
auto size1 = size2;
if (!size1.IsFullySpecified())
size1 = {frame_->width, frame_->height };
auto size = size1;
if (size.GetWidth() & 0x0f) {
size.SetWidth((size.GetWidth() & ~0x0f) + 0x10);
if (size.GetWidth() != width_) {
std::fill(bits_.begin(), bits_.end(), 0);
width_ = size.GetWidth();
}
}
AVPixelFormat wxFmt = AV_PIX_FMT_RGB24;
sws_ctx_ = sws_getCachedContext(sws_ctx_,
frame_->width, frame_->height, AVPixelFormat(frame_->format),
size1.GetWidth(), size1.GetHeight(), wxFmt,
SWS_GAUSS,
nullptr, nullptr, nullptr);
if (sws_ctx_ == nullptr)
return false;
int length = size.GetWidth() * size.GetHeight() * 3;
if (bits_.size() < length)
bits_.resize(length);
uint8_t * datas[] = { bits_.data() };
int strides[] = { size.GetWidth() * 3 };
int result_h = sws_scale(sws_ctx_, frame_->data, frame_->linesize, 0, frame_->height, datas, strides);
if (result_h != size.GetHeight()) {
return false;
}
// Copy: the frame outlives this decoder and is painted by the GUI thread while the
// next sws_scale is already overwriting bits_, so it must own its pixels. The Windows
// path below needs no equivalent, wxBitmap copies the bits into GDI.
image = wxImage(size.GetWidth(), size.GetHeight(), bits_.data(), true).Copy();
if (!image.IsOk()) {
fprintf(stderr, "AVVideoDecoder: image not ok %dx%d\n", size.GetWidth(), size.GetHeight());
return false;
}
return true;
}
bool AVVideoDecoder::toWxBitmap(wxBitmap &bitmap, wxSize const &size2)
{
if (!got_frame_)
return false;
auto size1 = size2;
if (!size1.IsFullySpecified())
size1 = {frame_->width, frame_->height };
auto size = size1;
if (size.GetWidth() & 0x0f) {
size.SetWidth((size.GetWidth() & ~0x0f) + 0x10);
if (size.GetWidth() != width_) {
std::fill(bits_.begin(), bits_.end(), 0);
width_ = size.GetWidth();
}
}
AVPixelFormat wxFmt = AV_PIX_FMT_RGB32;
sws_ctx_ = sws_getCachedContext(sws_ctx_,
frame_->width, frame_->height, AVPixelFormat(frame_->format),
size1.GetWidth(), size1.GetHeight(), wxFmt,
SWS_GAUSS,
nullptr, nullptr, nullptr);
if (sws_ctx_ == nullptr)
return false;
int length = size.GetWidth() * size.GetHeight() * 4;
if (bits_.size() < length)
bits_.resize(length);
uint8_t *datas[] = { bits_.data() };
int strides[] = { size.GetWidth() * 4 };
int result_h = sws_scale(sws_ctx_, frame_->data, frame_->linesize, 0, frame_->height, datas, strides);
if (result_h != size.GetHeight()) {
fprintf(stderr, "AVVideoDecoder: result_h %d %d\n", result_h, size.GetHeight());
return false;
}
bitmap = wxBitmap((char const *) bits_.data(), size.GetWidth(), size.GetHeight(), 32);
assert(bitmap.IsOk());
if (!bitmap.IsOk()) {
fprintf(stderr, "AVVideoDecoder: bitmap not ok %dx%d\n", size.GetWidth(), size.GetHeight());
return false;
}
return true;
}

View File

@@ -0,0 +1,46 @@
#ifndef AVVIDEODECODER_HPP
#define AVVIDEODECODER_HPP
#include "Printer/BambuTunnel.h"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
}
#include <vector>
#include <wx/bitmap.h>
#include <wx/gdicmn.h>
#include <wx/image.h>
class wxBitmap;
class AVVideoDecoder
{
public:
AVVideoDecoder();
~AVVideoDecoder();
public:
int open(Bambu_StreamInfo const &info);
int decode(Bambu_Sample const &sample);
int flush();
void close();
bool toWxImage(wxImage &image, wxSize const &size);
bool toWxBitmap(wxBitmap &bitmap, wxSize const & size);
private:
AVCodecContext *codec_ctx_ = nullptr;
AVFrame * frame_ = nullptr;
SwsContext * sws_ctx_ = nullptr;
bool got_frame_ = false;
int width_ { 0 }; // scale result width
std::vector<uint8_t> bits_;
};
#endif // AVVIDEODECODER_HPP

View File

@@ -1,30 +0,0 @@
//
// BambuPlayer.h
// BambuPlayer
//
// Created by cmguo on 2021/12/6.
//
#import <Foundation/Foundation.h>
#import <AVFoundation/AVSampleBufferDisplayLayer.h>
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface BambuPlayer : NSObject
+ (void) initialize;
- (instancetype) initWithDisplayLayer: (AVSampleBufferDisplayLayer*) layer;
- (instancetype) initWithImageView: (NSView*) view;
- (int) open: (char const *) url;
- (NSSize) videoSize;
- (int) play;
- (void) stop;
- (void) close;
- (void) setLogger: (void (*)(void const * context, int level, char const * msg)) logger withContext: (void const *) context;
@end
NS_ASSUME_NONNULL_END

View File

@@ -39,13 +39,14 @@ static std::map<int, std::string> error_messages = {
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const wxPoint &pos, const wxSize &size) MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos, const wxSize &size)
: wxPanel(parent, wxID_ANY, pos, size) : wxPanel(parent, wxID_ANY, pos, size)
, m_media_ctrl(media_ctrl) , m_media_ctrl(media_ctrl)
{ {
SetLabel("MediaPlayCtrl"); SetLabel("MediaPlayCtrl");
SetBackgroundColour(*wxWHITE); SetBackgroundColour(*wxWHITE);
m_media_ctrl->Bind(wxEVT_MEDIA_STATECHANGED, &MediaPlayCtrl::onStateChanged, this); m_media_ctrl->Bind(wxEVT_MEDIA_STATECHANGED, &MediaPlayCtrl::onStateChanged, this);
m_media_ctrl->SetIdleImage(from_u8(resources_dir() + "/images/live_stream_default.png"));
m_button_play = new Button(this, "", "media_play", wxBORDER_NONE); m_button_play = new Button(this, "", "media_play", wxBORDER_NONE);
m_button_play->SetCanFocus(false); m_button_play->SetCanFocus(false);
@@ -177,13 +178,6 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
if (machine == m_machine) { if (machine == m_machine) {
if (m_last_state == MEDIASTATE_IDLE && IsEnabled()) if (m_last_state == MEDIASTATE_IDLE && IsEnabled())
Play(); Play();
else if (m_last_state == MEDIASTATE_LOADING && m_tutk_state == "disable"
&& m_last_user_play + wxTimeSpan::Seconds(3) < wxDateTime::Now()) {
// resend ttcode to printer
if (auto agent = wxGetApp().getAgent())
agent->get_camera_url(machine, [](auto) {}, wxGetApp().get_printer_cloud_provider());
m_last_user_play = wxDateTime::Now();
}
return; return;
} }
m_machine = machine; m_machine = machine;
@@ -715,7 +709,9 @@ void MediaPlayCtrl::media_proc()
break; break;
} }
else if (url == "<play>") { else if (url == "<play>") {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start play";
m_media_ctrl->Play(); m_media_ctrl->Play();
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end play";
} }
else { else {
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start load"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start load";
@@ -830,27 +826,16 @@ bool MediaPlayCtrl::get_stream_url(std::string *url)
}} }}
void wxMediaCtrl2::DoSetSize(int x, int y, int width, int height, int sizeFlags) void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height)
{ {
#ifdef __WXMAC__ wxSize size = videoSize;
wxWindow::DoSetSize(x, y, width, height, sizeFlags); if (!size.IsFullySpecified()) size = {16, 9};
#else
wxMediaCtrl::DoSetSize(x, y, width, height, sizeFlags);
#endif
#if defined(__LINUX__) && defined(__WXGTK__)
if (m_gtk_video_window) {
const wxSize client_size = GetClientSize();
m_gtk_video_window->SetSize(0, 0, client_size.GetWidth(), client_size.GetHeight());
}
#endif
if (sizeFlags & wxSIZE_USE_EXISTING) return;
wxSize size = m_video_size;
int maxHeight = (width * size.GetHeight() + size.GetHeight() - 1) / size.GetWidth(); int maxHeight = (width * size.GetHeight() + size.GetHeight() - 1) / size.GetWidth();
if (maxHeight != GetMaxHeight()) { if (maxHeight != ctrl->GetMaxHeight()) {
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2::DoSetSize: width: " << width << ", height: " << height << ", maxHeight: " << maxHeight; // BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl_OnSize: width: " << width << ", height: " << height << ", maxHeight: " << maxHeight;
SetMaxSize({-1, maxHeight}); ctrl->SetMaxSize({-1, maxHeight});
CallAfter([this] { ctrl->CallAfter([ctrl] {
if (auto p = GetParent()) { if (auto p = ctrl->GetParent()) {
p->Layout(); p->Layout();
p->Refresh(); p->Refresh();
} }

View File

@@ -8,7 +8,7 @@
#ifndef MediaPlayCtrl_h #ifndef MediaPlayCtrl_h
#define MediaPlayCtrl_h #define MediaPlayCtrl_h
#include "wxMediaCtrl2.h" #include "wxMediaCtrl3.h"
#include <wx/panel.h> #include <wx/panel.h>
@@ -30,7 +30,7 @@ namespace GUI {
class MediaPlayCtrl : public wxPanel class MediaPlayCtrl : public wxPanel
{ {
public: public:
MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize); MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize);
~MediaPlayCtrl(); ~MediaPlayCtrl();
@@ -75,7 +75,7 @@ private:
// token // token
std::shared_ptr<int> m_token = std::make_shared<int>(0); std::shared_ptr<int> m_token = std::make_shared<int>(0);
wxMediaCtrl2 * m_media_ctrl; wxMediaCtrl3 * m_media_ctrl;
wxMediaState m_last_state = MEDIASTATE_IDLE; wxMediaState m_last_state = MEDIASTATE_IDLE;
std::string m_machine; std::string m_machine;
int m_lan_proto = 0; int m_lan_proto = 0;

View File

@@ -34,7 +34,6 @@
#include "Widgets/AxisCtrlButton.hpp" #include "Widgets/AxisCtrlButton.hpp"
#include "Widgets/TextInput.hpp" #include "Widgets/TextInput.hpp"
#include "Widgets/StaticLine.hpp" #include "Widgets/StaticLine.hpp"
#include "wxMediaCtrl2.h"
#include "MediaPlayCtrl.h" #include "MediaPlayCtrl.h"
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////

View File

@@ -1,657 +0,0 @@
/* bambusrc for gstreamer
* integration with proprietary Bambu Lab blob for getting raw h.264 video
*
* Copyright (C) 2023 Joshua Wise <joshua@accelerated.tech>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Alternatively, the contents of this file may be used under the
* GNU Lesser General Public License Version 2.1 (the "LGPL"), in
* which case the following provisions apply instead of the ones
* mentioned above:
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <gst/gst.h>
#include "gstbambusrc.h"
#include <stdio.h>
#include <unistd.h>
#ifndef EXTERNAL_GST_PLUGIN
#define BAMBU_DYNAMIC
#endif
#include "BambuTunnel.h"
#ifdef BAMBU_DYNAMIC
// From PrinterFileSystem.
#ifdef __cplusplus
extern "C"
#else
extern
#endif
BambuLib *bambulib_get();
BambuLib *_lib = NULL;
#define BAMBULIB(x) (_lib->x)
#else
#define BAMBULIB(x) (x)
#endif
GST_DEBUG_CATEGORY_STATIC (gst_bambusrc_debug);
#define GST_CAT_DEFAULT gst_bambusrc_debug
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
//GST_STATIC_CAPS("video/x-h264,framerate=0/1,parsed=(boolean)false,stream-format=(string)byte-stream"));
enum
{
PROP_0,
PROP_LOCATION,
};
static void gst_bambusrc_uri_handler_init (gpointer g_iface,
gpointer iface_data);
static void gst_bambusrc_finalize (GObject * gobject);
static void gst_bambusrc_dispose (GObject * gobject);
static void gst_bambusrc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_bambusrc_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static GstStateChangeReturn gst_bambusrc_change_state (GstElement *
element, GstStateChange transition);
static GstFlowReturn gst_bambusrc_create (GstPushSrc * psrc,
GstBuffer ** outbuf);
static gboolean gst_bambusrc_start (GstBaseSrc * bsrc);
static gboolean gst_bambusrc_stop (GstBaseSrc * bsrc);
static gboolean gst_bambusrc_is_seekable (GstBaseSrc * bsrc);
static gboolean gst_bambusrc_query (GstBaseSrc * bsrc, GstQuery * query);
static gboolean gst_bambusrc_unlock (GstBaseSrc * bsrc);
static gboolean gst_bambusrc_unlock_stop (GstBaseSrc * bsrc);
static gboolean gst_bambusrc_set_location (GstBambuSrc * src,
const gchar * uri, GError ** error);
#define gst_bambusrc_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstBambuSrc, gst_bambusrc, GST_TYPE_PUSH_SRC,
G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER,
gst_bambusrc_uri_handler_init));
static void
gst_bambusrc_class_init (GstBambuSrcClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
GstBaseSrcClass *gstbasesrc_class;
GstPushSrcClass *gstpushsrc_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gstbasesrc_class = (GstBaseSrcClass *) klass;
gstpushsrc_class = (GstPushSrcClass *) klass;
gobject_class->set_property = gst_bambusrc_set_property;
gobject_class->get_property = gst_bambusrc_get_property;
gobject_class->finalize = gst_bambusrc_finalize;
gobject_class->dispose = gst_bambusrc_dispose;
g_object_class_install_property (gobject_class,
PROP_LOCATION,
g_param_spec_string ("location", "Location",
"URI to pass to Bambu Lab blobs", "",
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
gst_element_class_set_static_metadata (gstelement_class, "Bambu Lab source",
"Source/Network",
"Receive data as a client over the network using the proprietary Bambu Lab blobs",
"Joshua Wise <joshua@accelerated.tech>");
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_bambusrc_change_state);
gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_bambusrc_start);
gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_bambusrc_stop);
gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_bambusrc_unlock);
gstbasesrc_class->unlock_stop =
GST_DEBUG_FUNCPTR (gst_bambusrc_unlock_stop);
gstbasesrc_class->is_seekable =
GST_DEBUG_FUNCPTR (gst_bambusrc_is_seekable);
gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_bambusrc_query);
gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_bambusrc_create);
GST_DEBUG_CATEGORY_INIT (gst_bambusrc_debug, "bambusrc", 0,
"Bambu Lab src");
}
static void
gst_bambusrc_reset (GstBambuSrc * src)
{
gst_caps_replace (&src->src_caps, NULL);
if (src->tnl) {
BAMBULIB(Bambu_Close)(src->tnl);
BAMBULIB(Bambu_Destroy)(src->tnl);
src->tnl = NULL;
}
}
static void
gst_bambusrc_init (GstBambuSrc * src)
{
src->location = NULL;
src->tnl = NULL;
gst_base_src_set_automatic_eos (GST_BASE_SRC (src), FALSE);
gst_base_src_set_live(GST_BASE_SRC(src), TRUE);
gst_bambusrc_reset (src);
}
static void
gst_bambusrc_dispose (GObject * gobject)
{
GstBambuSrc *src = GST_BAMBUSRC (gobject);
GST_DEBUG_OBJECT (src, "dispose");
G_OBJECT_CLASS (parent_class)->dispose (gobject);
}
static void
gst_bambusrc_finalize (GObject * gobject)
{
GstBambuSrc *src = GST_BAMBUSRC (gobject);
GST_DEBUG_OBJECT (src, "finalize");
g_free (src->location);
if (src->tnl) {
BAMBULIB(Bambu_Close)(src->tnl);
BAMBULIB(Bambu_Destroy)(src->tnl);
}
G_OBJECT_CLASS (parent_class)->finalize (gobject);
}
static void
gst_bambusrc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstBambuSrc *src = GST_BAMBUSRC (object);
switch (prop_id) {
case PROP_LOCATION:
{
const gchar *location;
location = g_value_get_string (value);
if (location == NULL) {
GST_WARNING ("location property cannot be NULL");
goto done;
}
if (!gst_bambusrc_set_location (src, location, NULL)) {
GST_WARNING ("badly formatted location");
goto done;
}
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
done:
return;
}
static void
gst_bambusrc_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstBambuSrc *src = GST_BAMBUSRC (object);
switch (prop_id) {
case PROP_LOCATION:
g_value_set_string (value, src->location);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
int gst_bambu_last_error = 0;
static GstFlowReturn
gst_bambusrc_create (GstPushSrc * psrc, GstBuffer ** outbuf)
{
GstBambuSrc *src;
src = GST_BAMBUSRC (psrc);
(void) src;
GST_DEBUG_OBJECT (src, "create()");
int rv;
Bambu_Sample sample;
if (!src->tnl) {
return GST_FLOW_ERROR;
}
while ((rv = BAMBULIB(Bambu_ReadSample)(src->tnl, &sample)) == Bambu_would_block) {
GST_DEBUG_OBJECT(src, "create would block");
usleep(33333); /* 30Hz */
}
if (rv == Bambu_stream_end) {
return GST_FLOW_EOS;
}
if (rv != Bambu_success) {
gst_bambu_last_error = rv;
return GST_FLOW_ERROR;
}
#if GLIB_CHECK_VERSION(2,68,0)
gpointer sbuf = g_memdup2(sample.buffer, sample.size);
#else
gpointer sbuf = g_memdup(sample.buffer, sample.size);
#endif
*outbuf = gst_buffer_new_wrapped_full(0, sbuf, sample.size, 0, sample.size, sbuf, g_free);
/* Synthesize monotonic timestamps at the announced frame rate, anchored
* to the first frame's arrival time. The X1C's RTSPS server emits
* unreliable decode timestamps (wildly non-monotonic jumps, or sometimes
* none at all); forwarding them directly froze the pipeline after a few
* seconds. Pacing on a synthesized clock — the same trick mpv uses when
* it reports "No video PTS! Making something up." — gives smooth
* playback regardless of network jitter, and only drops late frames if
* the printer can't keep up. A snap-back resets the anchor if real
* arrival drifts more than two frame periods from the synthesized
* timeline (e.g. announced framerate was wrong).
*/
GstClock *clock = GST_ELEMENT_CLOCK(psrc);
GstClockTime base_time = gst_element_get_base_time((GstElement *)psrc);
GstClockTime running_now = GST_CLOCK_TIME_NONE;
if (clock) {
GstClockTime now = gst_clock_get_time(clock);
if (now != GST_CLOCK_TIME_NONE && now >= base_time)
running_now = now - base_time;
}
/* Adapt the period to actual inter-arrival time via EWMA. The announced
* frame_rate is unreliable on Bambu printers (X1C announces 30 but
* delivers ~28), so trusting it causes the synthesized timeline to drift
* relative to real time, which makes the sink consider frames late and
* skip pacing entirely. Measuring the real rate keeps PTS in step with
* arrival on average, so the sink can pace inside bursts while still
* tracking the printer's actual frame cadence.
*/
if (src->avg_period == 0) {
int fps = src->frame_rate > 0 ? src->frame_rate : 30;
src->avg_period = GST_SECOND / fps;
}
if (running_now != GST_CLOCK_TIME_NONE && src->last_arrival != 0) {
GstClockTimeDiff delta = GST_CLOCK_DIFF(src->last_arrival, running_now);
/* clamp to plausible video frame periods (5..200 ms) so a one-off
* burst-of-zero or long stall doesn't poison the average */
if (delta > 5 * GST_MSECOND && delta < 200 * GST_MSECOND) {
src->avg_period = (src->avg_period * 15 + (GstClockTime)delta) / 16;
}
}
src->last_arrival = (running_now != GST_CLOCK_TIME_NONE) ? running_now : src->last_arrival;
GstClockTime period = src->avg_period;
/* Lead time: schedule frames a few periods in the future of their
* arrival, so the sink has a small jitter buffer. Without this, frames
* arriving slightly later than expected land behind the running clock
* and the sink renders them immediately, producing visible stutter.
* 100ms is invisible for a live print-monitor view.
*/
const GstClockTime LEAD = 100 * GST_MSECOND;
if (!src->sttime) {
src->sttime = (running_now != GST_CLOCK_TIME_NONE) ? running_now + LEAD : LEAD;
src->frame_count = 0;
}
GstClockTime pts = src->sttime + src->frame_count * period;
/* Safety net: with the lead applied, expected drift is roughly -LEAD
* (pts sits LEAD ns ahead of running_now). Re-anchor only if the
* synthesized timeline diverges from that expectation by several frame
* periods, which indicates a real disturbance (printer paused, stream
* resumed, large fps change) rather than ordinary jitter.
*/
if (running_now != GST_CLOCK_TIME_NONE) {
GstClockTimeDiff drift = GST_CLOCK_DIFF(pts, running_now);
GstClockTimeDiff expected = -(GstClockTimeDiff)LEAD;
GstClockTimeDiff slack = (GstClockTimeDiff)(4 * period);
if (drift > expected + slack || drift < expected - slack) {
GST_DEBUG_OBJECT(src, "ts drift %" G_GINT64_FORMAT " ns; re-anchoring", drift);
src->sttime = running_now + LEAD;
src->frame_count = 0;
pts = src->sttime;
}
}
GST_BUFFER_PTS(*outbuf) = pts;
GST_BUFFER_DTS(*outbuf) = pts;
GST_BUFFER_DURATION(*outbuf) = period;
src->frame_count++;
GST_DEBUG_OBJECT(src,
"sttime:%lu, DTS:%lu, PTS: %lu~",
src->sttime, GST_BUFFER_DTS(*outbuf), GST_BUFFER_PTS(*outbuf));
return GST_FLOW_OK;
}
static void _log(void *ctx, int lvl, const char *msg) {
GstBambuSrc *src = (GstBambuSrc *) ctx;
GST_DEBUG_OBJECT(src, "bambu: %s", msg);
BAMBULIB(Bambu_FreeLogMsg)(msg);
}
static gboolean
gst_bambusrc_start (GstBaseSrc * bsrc)
{
GstBambuSrc *src = GST_BAMBUSRC (bsrc);
GST_DEBUG_OBJECT (src, "start(\"%s\")", src->location);
if (src->tnl) {
BAMBULIB(Bambu_Close)(src->tnl);
BAMBULIB(Bambu_Destroy)(src->tnl);
src->tnl = NULL;
}
#ifdef BAMBU_DYNAMIC
if (!_lib) {
_lib = bambulib_get();
if (!_lib->Bambu_Open) {
return FALSE;
}
}
#endif
if (BAMBULIB(Bambu_Create)(&src->tnl, src->location) != Bambu_success) {
return FALSE;
}
int rv = 0;
BAMBULIB(Bambu_SetLogger)(src->tnl, _log, (void *)src);
if ((rv = BAMBULIB(Bambu_Open)(src->tnl)) != Bambu_success) {
BAMBULIB(Bambu_Destroy)(src->tnl);
src->tnl = NULL;
gst_bambu_last_error = rv;
return FALSE;
}
int n = 0;
while ((rv = BAMBULIB(Bambu_StartStream)(src->tnl, 1 /* video */)) == Bambu_would_block) {
usleep(100000);
}
if (rv != Bambu_success) {
BAMBULIB(Bambu_Close)(src->tnl);
BAMBULIB(Bambu_Destroy)(src->tnl);
src->tnl = NULL;
gst_bambu_last_error = rv;
return FALSE;
}
src->video_type = AVC1;
n = BAMBULIB(Bambu_GetStreamCount)(src->tnl);
GST_INFO_OBJECT (src, "Bambu_GetStreamCount returned stream count=%d",n);
for (int i = 0; i < n; ++i) {
Bambu_StreamInfo info;
BAMBULIB(Bambu_GetStreamInfo)(src->tnl, i, &info);
GST_INFO_OBJECT (src, "stream %d type=%d, sub_type=%d", i, info.type, info.sub_type);
if (info.type == VIDE) {
src->video_type = info.sub_type;
src->frame_rate = info.format.video.frame_rate;
GST_INFO_OBJECT (src, " width %d height=%d, frame_rate=%d",
info.format.video.width, info.format.video.height, info.format.video.frame_rate);
}
}
src->sttime = 0;
src->frame_count = 0;
src->last_arrival = 0;
src->avg_period = 0;
return TRUE;
}
static gboolean
gst_bambusrc_stop (GstBaseSrc * bsrc)
{
GstBambuSrc *src;
src = GST_BAMBUSRC (bsrc);
GST_DEBUG_OBJECT (src, "stop()");
if (src->tnl) {
BAMBULIB(Bambu_Close)(src->tnl);
BAMBULIB(Bambu_Destroy)(src->tnl);
src->tnl = NULL;
}
return TRUE;
}
static GstStateChangeReturn
gst_bambusrc_change_state (GstElement * element, GstStateChange transition)
{
GstStateChangeReturn ret;
GstBambuSrc *src;
src = GST_BAMBUSRC (element);
(void) src;
switch (transition) {
case GST_STATE_CHANGE_READY_TO_NULL:
//gst_bambusrc_session_close (src);
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
return ret;
}
/* Interrupt a blocking request. */
static gboolean
gst_bambusrc_unlock (GstBaseSrc * bsrc)
{
GstBambuSrc *src;
src = GST_BAMBUSRC (bsrc);
GST_DEBUG_OBJECT (src, "unlock()");
return TRUE;
}
/* Interrupt interrupt. */
static gboolean
gst_bambusrc_unlock_stop (GstBaseSrc * bsrc)
{
GstBambuSrc *src;
src = GST_BAMBUSRC (bsrc);
GST_DEBUG_OBJECT (src, "unlock_stop()");
return TRUE;
}
static gboolean
gst_bambusrc_is_seekable (GstBaseSrc * bsrc)
{
GstBambuSrc *src = GST_BAMBUSRC (bsrc);
(void) src;
return FALSE;
}
static gboolean
gst_bambusrc_query (GstBaseSrc * bsrc, GstQuery * query)
{
GstBambuSrc *src = GST_BAMBUSRC (bsrc);
gboolean ret;
GstSchedulingFlags flags;
gint minsize, maxsize, align;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_URI:
gst_query_set_uri (query, src->location);
ret = TRUE;
break;
default:
ret = FALSE;
break;
}
if (!ret)
ret = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_SCHEDULING:
gst_query_parse_scheduling (query, &flags, &minsize, &maxsize, &align);
flags = (GstSchedulingFlags)((int)flags | (int)GST_SCHEDULING_FLAG_SEQUENTIAL);
gst_query_set_scheduling (query, flags, minsize, maxsize, align);
break;
default:
break;
}
return ret;
}
static gboolean
gst_bambusrc_set_location (GstBambuSrc * src, const gchar * uri,
GError ** error)
{
if (src->location) {
g_free (src->location);
src->location = NULL;
}
if (uri == NULL)
return FALSE;
src->location = g_strdup (uri);
return TRUE;
}
static GstURIType
gst_bambusrc_uri_get_type (GType type)
{
return GST_URI_SRC;
}
static const gchar *const *
gst_bambusrc_uri_get_protocols (GType type)
{
static const gchar *protocols[] = { "bambu", NULL };
return protocols;
}
static gchar *
gst_bambusrc_uri_get_uri (GstURIHandler * handler)
{
GstBambuSrc *src = GST_BAMBUSRC (handler);
/* FIXME: make thread-safe */
return g_strdup (src->location);
}
static gboolean
gst_bambusrc_uri_set_uri (GstURIHandler * handler, const gchar * uri,
GError ** error)
{
GstBambuSrc *src = GST_BAMBUSRC (handler);
return gst_bambusrc_set_location (src, uri, error);
}
static void
gst_bambusrc_uri_handler_init (gpointer g_iface, gpointer iface_data)
{
GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
iface->get_type = gst_bambusrc_uri_get_type;
iface->get_protocols = gst_bambusrc_uri_get_protocols;
iface->get_uri = gst_bambusrc_uri_get_uri;
iface->set_uri = gst_bambusrc_uri_set_uri;
}
static gboolean gstbambusrc_init(GstPlugin *plugin)
{
return gst_element_register(plugin, "bambusrc", GST_RANK_PRIMARY, GST_TYPE_BAMBUSRC);
}
#ifndef EXTERNAL_GST_PLUGIN
// for use inside of Bambu Slicer
void gstbambusrc_register()
{
static int did_register = 0;
if (did_register)
return;
did_register = 1;
gst_plugin_register_static(GST_VERSION_MAJOR, GST_VERSION_MINOR, "bambusrc", "Bambu Lab source", gstbambusrc_init, "0.0.1", "GPL", "BambuStudio", "BambuStudio", "https://github.com/bambulab/BambuStudio");
}
#else
#ifndef PACKAGE
#define PACKAGE "bambusrc"
#endif
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, bambusrc, "Bambu Lab source", gstbambusrc_init, "0.0.1", "GPL", "BambuStudio", "https://github.com/bambulab/BambuStudio")
#endif

View File

@@ -1,78 +0,0 @@
/* bambusrc for gstreamer
* integration with proprietary Bambu Lab blob for getting raw h.264 video
*
* Copyright (C) 2023 Joshua Wise <joshua@accelerated.tech>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Alternatively, the contents of this file may be used under the
* GNU Lesser General Public License Version 2.1 (the "LGPL"), in
* which case the following provisions apply instead of the ones
* mentioned above:
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_BAMBUSRC_H__
#define __GST_BAMBUSRC_H__
#include <gst/gst.h>
#include <gst/base/gstpushsrc.h>
#include <glib.h>
G_BEGIN_DECLS
#define GST_TYPE_BAMBUSRC (gst_bambusrc_get_type())
G_DECLARE_FINAL_TYPE (GstBambuSrc, gst_bambusrc,
GST, BAMBUSRC, GstPushSrc)
typedef void *Bambu_Tunnel;
struct _GstBambuSrc
{
GstPushSrc element;
GstCaps *src_caps;
gchar *location;
Bambu_Tunnel tnl;
GstClockTime sttime;
int video_type;
int frame_rate;
guint64 frame_count;
GstClockTime last_arrival;
GstClockTime avg_period;
};
extern void gstbambusrc_register();
G_END_DECLS
#endif /* __GST_BAMBUSRC_H__ */

View File

@@ -1530,7 +1530,7 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
// media_ctrl_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); // media_ctrl_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
// media_ctrl_panel->SetBackgroundColour(*wxBLACK); // media_ctrl_panel->SetBackgroundColour(*wxBLACK);
// wxBoxSizer *bSizer_monitoring = new wxBoxSizer(wxVERTICAL); // wxBoxSizer *bSizer_monitoring = new wxBoxSizer(wxVERTICAL);
m_media_ctrl = new wxMediaCtrl2(this); m_media_ctrl = new wxMediaCtrl3(this);
m_media_ctrl->SetMinSize(wxSize(PAGE_MIN_WIDTH, FromDIP(288))); m_media_ctrl->SetMinSize(wxSize(PAGE_MIN_WIDTH, FromDIP(288)));
m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString); m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString);

View File

@@ -14,7 +14,6 @@
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/gbsizer.h> #include <wx/gbsizer.h>
#include <wx/webrequest.h> #include <wx/webrequest.h>
#include "wxMediaCtrl2.h"
#include "MediaPlayCtrl.h" #include "MediaPlayCtrl.h"
#include "AMSSetting.hpp" #include "AMSSetting.hpp"
#include "Calibration.hpp" #include "Calibration.hpp"
@@ -443,7 +442,7 @@ protected:
wxStaticBitmap* m_camera_switch_button; wxStaticBitmap* m_camera_switch_button;
wxMediaCtrl2 * m_media_ctrl; wxMediaCtrl3 * m_media_ctrl;
MediaPlayCtrl * m_media_play_ctrl; MediaPlayCtrl * m_media_play_ctrl;
Label * m_staticText_printing; Label * m_staticText_printing;

View File

@@ -111,7 +111,7 @@ int TabCtrl::AppendItem(const wxString &item,
btns.push_back(btn); btns.push_back(btn);
if (btns.size() > 1) if (btns.size() > 1)
sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0}); sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0});
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, TAB_BUTTON_SPACE * 2); sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, TAB_BUTTON_SPACE);
sizer->AddStretchSpacer(1); sizer->AddStretchSpacer(1);
relayout(); relayout();
return btns.size() - 1; return btns.size() - 1;
@@ -225,8 +225,9 @@ bool TabCtrl::IsVisible(unsigned int item) const
void TabCtrl::DoSetSize(int x, int y, int width, int height, int sizeFlags) void TabCtrl::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{ {
auto size = GetSize();
wxWindow::DoSetSize(x, y, width, height, sizeFlags); wxWindow::DoSetSize(x, y, width, height, sizeFlags);
if (sizeFlags & wxSIZE_USE_EXISTING) return; if (size == GetSize()) return;
relayout(); relayout();
} }

View File

@@ -1,633 +0,0 @@
#include "wxMediaCtrl2.h"
#include "libslic3r/Time.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "LinuxDisplayBackend.hpp"
#include <boost/filesystem/operations.hpp>
#include <string>
#ifdef __WIN32__
#include <winuser.h>
#include <versionhelpers.h>
#include <wx/msw/registry.h>
#include <shellapi.h>
#endif
#ifdef __LINUX__
#include "Printer/gstbambusrc.h"
#include <gst/gst.h> // main gstreamer header
#endif
#if defined(__LINUX__) && defined(__WXGTK__)
#include <gtk/gtk.h>
#include <wx/nativewin.h>
namespace {
bool ensure_gstreamer_initialized_for_liveview()
{
GError* error = nullptr;
if (!gst_init_check(nullptr, nullptr, &error)) {
BOOST_LOG_TRIVIAL(error) << "wxMediaCtrl2: gst_init_check failed before native Wayland liveview setup"
<< (error ? std::string(": ") + error->message : std::string());
if (error)
g_error_free(error);
return false;
}
return true;
}
bool is_gstreamer_feature_available(const char* feature)
{
if (!ensure_gstreamer_initialized_for_liveview())
return false;
GstElementFactory* factory = gst_element_factory_find(feature);
if (!factory)
return false;
gst_object_unref(factory);
return true;
}
void set_gstreamer_feature_rank(const char* feature, guint rank)
{
GstElementFactory* factory = gst_element_factory_find(feature);
if (!factory)
return;
gst_plugin_feature_set_rank(GST_PLUGIN_FEATURE(factory), rank);
gst_object_unref(factory);
}
void configure_wayland_gstreamer_liveview_path()
{
static bool configured = false;
if (configured)
return;
configured = true;
if (!ensure_gstreamer_initialized_for_liveview())
return;
// Prefer software decode for Bambu liveview on Wayland/NVIDIA, where
// zero-copy GL/DMABUF display paths can be fragile. Keep hardware
// decoders available as lower-ranked fallbacks for VAAPI/NVDEC/V4L2-only
// installations instead of passing preflight and then blocking autoplug.
set_gstreamer_feature_rank("avdec_h264", GST_RANK_PRIMARY + 300);
set_gstreamer_feature_rank("openh264dec", GST_RANK_PRIMARY + 100);
set_gstreamer_feature_rank("nvh264dec", GST_RANK_MARGINAL);
set_gstreamer_feature_rank("vaapih264dec", GST_RANK_MARGINAL);
set_gstreamer_feature_rank("vah264dec", GST_RANK_MARGINAL);
set_gstreamer_feature_rank("v4l2h264dec", GST_RANK_MARGINAL);
}
}
#endif // defined(__LINUX__) && defined(__WXGTK__)
#ifdef __LINUX__
extern "C" int gst_bambu_last_error;
class WXDLLIMPEXP_MEDIA
wxGStreamerMediaBackend : public wxMediaBackendCommonBase
{
public:
GstElement *m_playbin; // GStreamer media element
};
#endif
wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
wxMediaCtrl2::wxMediaCtrl2(wxWindow *parent)
{
#if defined(__LINUX__) && defined(__WXGTK__)
m_native_wayland = Slic3r::GUI::is_running_on_wayland();
if (m_native_wayland && is_gstreamer_feature_available("gtksink"))
configure_wayland_gstreamer_liveview_path();
else if (m_native_wayland) {
m_gtk_sink_error = _L("Native Wayland liveview requires the GStreamer GTK video sink. Please install the gtksink plugin for GStreamer, then restart OrcaSlicer.");
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: native Wayland liveview disabled because GStreamer gtksink is unavailable";
}
#endif
#ifdef __WIN32__
auto hModExe = GetModuleHandle(NULL);
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: GetModuleHandle " << hModExe;
auto NvOptimusEnablement = (DWORD *) GetProcAddress(hModExe, "NvOptimusEnablement");
auto AmdPowerXpressRequestHighPerformance = (int *) GetProcAddress(hModExe, "AmdPowerXpressRequestHighPerformance");
if (NvOptimusEnablement) {
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: NvOptimusEnablement " << *NvOptimusEnablement;
*NvOptimusEnablement = 0;
}
if (AmdPowerXpressRequestHighPerformance) {
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: AmdPowerXpressRequestHighPerformance " << *AmdPowerXpressRequestHighPerformance;
*AmdPowerXpressRequestHighPerformance = 0;
}
#endif
#if defined(__LINUX__) && defined(__WXGTK__)
if (m_native_wayland)
wxControl::Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize);
else
#endif
wxMediaCtrl::Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxMEDIACTRLPLAYERCONTROLS_NONE);
#ifdef __LINUX__
gstbambusrc_register();
#ifdef __WXGTK__
if (m_native_wayland && m_gtk_sink_error.empty())
m_use_gtk_sink = CreateGtkSinkPlayer();
if (m_native_wayland && !m_use_gtk_sink && m_gtk_sink_error.empty())
m_gtk_sink_error = _L("Failed to initialize the native Wayland GStreamer video sink. Please check your GStreamer GTK plugin installation.");
#endif
if (!m_use_gtk_sink && m_imp) {
auto playbin = reinterpret_cast<wxGStreamerMediaBackend *>(m_imp)->m_playbin;
g_object_set(G_OBJECT(playbin),
"audio-sink", nullptr,
nullptr);
} else if (!m_use_gtk_sink) {
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: wxMediaCtrl backend is unavailable";
}
Bind(wxEVT_MEDIA_LOADED, [this](auto & e) {
m_loaded = true;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(0);
event.SetEventObject(this);
wxPostEvent(this, event);
});
#endif
}
wxMediaCtrl2::~wxMediaCtrl2()
{
#if defined(__LINUX__) && defined(__WXGTK__)
DestroyGtkSinkPlayer();
#endif
}
#if defined(__LINUX__) && defined(__WXGTK__)
bool wxMediaCtrl2::CreateGtkSinkPlayer()
{
GstElement *playbin = gst_element_factory_make("playbin", "orca-wayland-gtk-playbin");
if (!playbin)
return false;
GError *error = nullptr;
GstElement *video_sink = gst_parse_bin_from_description(
"videoconvert ! videoscale ! video/x-raw,format=BGRx ! gtksink name=orca_wayland_gtksink sync=false",
TRUE,
&error);
if (!video_sink) {
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: failed to create gtksink video bin"
<< (error ? std::string(": ") + error->message : std::string());
if (error)
g_error_free(error);
gst_object_unref(playbin);
return false;
}
GstElement *gtk_sink = gst_bin_get_by_name(GST_BIN(video_sink), "orca_wayland_gtksink");
if (!gtk_sink) {
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: failed to find gtksink in video bin";
gst_object_unref(video_sink);
gst_object_unref(playbin);
return false;
}
GtkWidget *gtk_widget = nullptr;
g_object_get(G_OBJECT(gtk_sink), "widget", &gtk_widget, nullptr);
if (!gtk_widget) {
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: gtksink did not expose a GtkWidget";
gst_object_unref(gtk_sink);
gst_object_unref(video_sink);
gst_object_unref(playbin);
return false;
}
gtk_widget_show(gtk_widget);
m_gtk_video_window = new wxNativeWindow(this, wxID_ANY, gtk_widget);
m_gtk_video_window->Show();
g_object_unref(gtk_widget);
g_object_set(G_OBJECT(playbin),
"video-sink", video_sink,
"audio-sink", nullptr,
nullptr);
gst_object_unref(video_sink);
m_gtk_playbin = playbin;
m_gtk_sink = gtk_sink;
GstBus *bus = gst_element_get_bus(playbin);
m_gtk_bus_watch_id = gst_bus_add_watch(bus, [](GstBus *, GstMessage *message, gpointer data) -> gboolean {
auto *self = static_cast<wxMediaCtrl2 *>(data);
if (!self || !self->m_gtk_playbin)
return G_SOURCE_REMOVE;
switch (GST_MESSAGE_TYPE(message)) {
case GST_MESSAGE_ERROR:
{
GError *error = nullptr;
gchar *debug = nullptr;
gst_message_parse_error(message, &error, &debug);
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: gtksink pipeline error"
<< (error ? std::string(": ") + error->message : std::string())
<< (debug ? std::string(" debug: ") + debug : std::string());
if (error)
g_error_free(error);
if (debug)
g_free(debug);
self->m_error = gst_bambu_last_error ? gst_bambu_last_error : 2;
self->m_loaded = false;
self->m_gtk_state = wxMEDIASTATE_STOPPED;
self->PostGtkSinkStateEvent(self->GetId());
break;
}
case GST_MESSAGE_EOS:
self->m_loaded = false;
self->m_gtk_state = wxMEDIASTATE_STOPPED;
self->PostGtkSinkStateEvent(self->GetId());
break;
case GST_MESSAGE_STATE_CHANGED:
if (GST_MESSAGE_SRC(message) == GST_OBJECT(self->m_gtk_playbin)) {
GstState old_state;
GstState new_state;
GstState pending_state;
gst_message_parse_state_changed(message, &old_state, &new_state, &pending_state);
if (new_state == GST_STATE_PLAYING) {
self->m_loaded = true;
self->m_gtk_state = wxMEDIASTATE_PLAYING;
self->PostGtkSinkStateEvent();
} else if (new_state == GST_STATE_PAUSED && old_state < GST_STATE_PAUSED) {
// Treat only upward READY/NULL -> PAUSED as load completion.
// PLAYING -> PAUSED is a normal teardown step before NULL.
self->m_loaded = true;
self->m_gtk_state = wxMEDIASTATE_PAUSED;
self->PostGtkSinkStateEvent();
} else if (new_state <= GST_STATE_READY && old_state >= GST_STATE_PAUSED) {
self->m_loaded = false;
self->m_gtk_state = wxMEDIASTATE_STOPPED;
self->PostGtkSinkStateEvent();
}
}
break;
default:
break;
}
return G_SOURCE_CONTINUE;
}, this);
gst_object_unref(bus);
BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: using GTK native Wayland video sink";
return true;
}
void wxMediaCtrl2::DestroyGtkSinkPlayer()
{
if (m_gtk_bus_watch_id) {
g_source_remove(m_gtk_bus_watch_id);
m_gtk_bus_watch_id = 0;
}
if (m_gtk_playbin) {
gst_element_set_state(m_gtk_playbin, GST_STATE_NULL);
}
if (m_gtk_video_window) {
m_gtk_video_window->Destroy();
m_gtk_video_window = nullptr;
}
if (m_gtk_playbin) {
gst_object_unref(m_gtk_playbin);
m_gtk_playbin = nullptr;
}
if (m_gtk_sink) {
gst_object_unref(m_gtk_sink);
m_gtk_sink = nullptr;
}
}
void wxMediaCtrl2::PostGtkSinkStateEvent(int id)
{
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(id);
event.SetEventObject(this);
wxPostEvent(this, event);
}
#endif // defined(__LINUX__) && defined(__WXGTK__)
#define CLSID_BAMBU_SOURCE L"{233E64FB-2041-4A6C-AFAB-FF9BCF83E7AA}"
void wxMediaCtrl2::Load(wxURI url)
{
#ifdef __WIN32__
InvalidateBestSize();
if (m_imp == nullptr) {
static bool notified = false;
if (!notified) CallAfter([] {
auto res = wxMessageBox(_L("Windows Media Player is required for this task! Do you want to enable 'Windows Media Player' for your operation system?"), _L("Error"), wxOK | wxCANCEL);
if (res == wxOK) {
wxString url = IsWindows10OrGreater()
? "ms-settings:optionalfeatures?activationSource=SMC-Article-14209"
: "https://support.microsoft.com/en-au/windows/get-windows-media-player-81718e0d-cfce-25b1-aee3-94596b658287";
wxExecute("cmd /c start " + url, wxEXEC_HIDE_CONSOLE);
}
});
m_error = 100;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
return;
}
{
wxRegKey key11(wxRegKey::HKCU, L"SOFTWARE\\Classes\\CLSID\\" CLSID_BAMBU_SOURCE L"\\InProcServer32");
wxRegKey key12(wxRegKey::HKCR, L"CLSID\\" CLSID_BAMBU_SOURCE L"\\InProcServer32");
wxString path = key11.Exists() ? key11.QueryDefaultValue()
: key12.Exists() ? key12.QueryDefaultValue() : wxString{};
wxRegKey key2(wxRegKey::HKCR, "bambu");
wxString clsid;
if (key2.Exists())
key2.QueryRawValue("Source Filter", clsid);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": clsid %1% path %2%") % clsid % path;
std::string data_dir_str = Slic3r::data_dir();
boost::filesystem::path data_dir_path(data_dir_str);
auto dll_path = data_dir_path / "plugins" / "BambuSource.dll";
if (path.empty() || !wxFile::Exists(path) || clsid != CLSID_BAMBU_SOURCE) {
if (boost::filesystem::exists(dll_path)) {
CallAfter(
[dll_path] {
int res = wxMessageBox(_L("BambuSource has not correctly been registered for media playing! Press Yes to re-register it. You will be promoted twice"), _L("Error"), wxYES_NO);
if (res == wxYES) {
std::string regContent = R"(Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\bambu]
"Source Filter"="{233E64FB-2041-4A6C-AFAB-FF9BCF83E7AA}"
)";
auto reg_path = (fs::temp_directory_path() / fs::unique_path()).replace_extension(".reg");
std::ofstream temp_reg_file(reg_path.c_str());
if (!temp_reg_file) {
return false;
}
temp_reg_file << regContent;
temp_reg_file.close();
auto sei_params = L"/q /s " + reg_path.wstring();
SHELLEXECUTEINFO sei{sizeof(sei), SEE_MASK_NOCLOSEPROCESS, NULL, L"open",
L"regedit", sei_params.c_str(),SW_HIDE,SW_HIDE};
::ShellExecuteEx(&sei);
wstring quoted_dll_path = L"\"" + dll_path.wstring() + L"\"";
SHELLEXECUTEINFO info{sizeof(info), 0, NULL, L"runas", L"regsvr32", quoted_dll_path.c_str(), SW_HIDE };
::ShellExecuteEx(&info);
fs::remove(reg_path);
}
return true;
});
} else {
CallAfter([] {
wxMessageBox(_L("Missing BambuSource component registered for media playing! Please re-install OrcaSlicer or seek community help."), _L("Error"), wxOK);
});
}
m_error = clsid != CLSID_BAMBU_SOURCE ? 101 : path.empty() ? 102 : 103;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
return;
}
if (path != dll_path) {
static bool notified = false;
if (!notified) CallAfter([dll_path] {
int res = wxMessageBox(_L("Using a BambuSource from a different install, video play may not work correctly! Press Yes to fix it."), _L("Warning"), wxYES_NO | wxICON_WARNING);
if (res == wxYES) {
auto path = dll_path.wstring();
if (path.find(L' ') != std::wstring::npos)
path = L"\"" + path + L"\"";
SHELLEXECUTEINFO info{sizeof(info), 0, NULL, L"open", L"regsvr32", path.c_str(), SW_HIDE};
::ShellExecuteEx(&info);
}
});
notified = true;
}
wxRegKey keyWmp(wxRegKey::HKCU, "SOFTWARE\\Microsoft\\MediaPlayer\\Player\\Extensions\\.");
keyWmp.Create();
long permissions = 0;
if (keyWmp.HasValue("Permissions"))
keyWmp.QueryValue("Permissions", &permissions);
if ((permissions & 32) == 0) {
permissions |= 32;
keyWmp.SetValue("Permissions", permissions);
}
}
url = wxURI(url.BuildURI().append("&hwnd=").append(boost::lexical_cast<std::string>(GetHandle())).append("&tid=").append(
boost::lexical_cast<std::string>(GetCurrentThreadId())));
#endif
#ifdef __WXGTK3__
GstElementFactory *factory;
int hasplugins = 1;
factory = gst_element_factory_find("h264parse");
if (!factory) {
hasplugins = 0;
} else {
gst_object_unref(factory);
}
factory = gst_element_factory_find("openh264dec");
if (!factory) {
factory = gst_element_factory_find("avdec_h264");
}
if (!factory) {
factory = gst_element_factory_find("vaapih264dec");
}
if (!factory) {
factory = gst_element_factory_find("vah264dec");
}
if (!factory) {
factory = gst_element_factory_find("nvh264dec");
}
if (!factory) {
factory = gst_element_factory_find("v4l2h264dec");
}
if (!factory) {
hasplugins = 0;
} else {
gst_object_unref(factory);
}
if (!hasplugins) {
CallAfter([] {
wxMessageBox(_L("Your system is missing H.264 codecs for GStreamer, which are required to play video. (Try installing the gstreamer1.0-plugins-bad or gstreamer1.0-libav packages, then restart Orca Slicer?)"), _L("Error"), wxOK);
});
m_error = 101;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
return;
}
wxLog::EnableLogging(false);
#endif
m_error = 0;
m_loaded = false;
#if defined(__LINUX__) && defined(__WXGTK__)
if (m_use_gtk_sink && m_gtk_playbin) {
const std::string uri = std::string(url.BuildURI().ToUTF8().data());
gst_element_set_state(m_gtk_playbin, GST_STATE_NULL);
g_object_set(G_OBJECT(m_gtk_playbin), "uri", uri.c_str(), nullptr);
m_gtk_state = wxMEDIASTATE_STOPPED;
GstStateChangeReturn state = gst_element_set_state(m_gtk_playbin, GST_STATE_PAUSED);
if (state == GST_STATE_CHANGE_FAILURE) {
m_error = gst_bambu_last_error ? gst_bambu_last_error : 2;
PostGtkSinkStateEvent(GetId());
}
return;
}
if (!m_imp) {
m_error = m_native_wayland && !m_gtk_sink_error.empty() ? 104 : 100;
m_loaded = false;
if (m_native_wayland && !m_gtk_sink_error.empty() && !m_gtk_sink_error_notified) {
m_gtk_sink_error_notified = true;
const wxString message = m_gtk_sink_error;
CallAfter([message] {
wxMessageBox(message, _L("Error"), wxOK);
});
}
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
return;
}
#endif
wxMediaCtrl::Load(url);
}
void wxMediaCtrl2::Play()
{
#if defined(__LINUX__) && defined(__WXGTK__)
if (m_use_gtk_sink && m_gtk_playbin) {
GstStateChangeReturn state = gst_element_set_state(m_gtk_playbin, GST_STATE_PLAYING);
if (state == GST_STATE_CHANGE_FAILURE) {
m_error = gst_bambu_last_error ? gst_bambu_last_error : 2;
m_gtk_state = wxMEDIASTATE_STOPPED;
PostGtkSinkStateEvent(GetId());
}
return;
}
if (!m_imp) {
m_error = m_native_wayland && !m_gtk_sink_error.empty() ? 104 : 100;
if (m_native_wayland && !m_gtk_sink_error.empty() && !m_gtk_sink_error_notified) {
m_gtk_sink_error_notified = true;
const wxString message = m_gtk_sink_error;
CallAfter([message] {
wxMessageBox(message, _L("Error"), wxOK);
});
}
PostGtkSinkStateEvent(GetId());
return;
}
#endif
wxMediaCtrl::Play();
}
void wxMediaCtrl2::Stop()
{
#if defined(__LINUX__) && defined(__WXGTK__)
if (m_use_gtk_sink && m_gtk_playbin) {
gst_element_set_state(m_gtk_playbin, GST_STATE_NULL);
m_gtk_state = wxMEDIASTATE_STOPPED;
m_loaded = false;
PostGtkSinkStateEvent(0);
return;
}
if (!m_imp)
return;
#endif
wxMediaCtrl::Stop();
}
wxMediaState wxMediaCtrl2::GetState()
{
#if defined(__LINUX__) && defined(__WXGTK__)
if (m_use_gtk_sink && m_gtk_playbin)
return m_gtk_state;
if (!m_imp)
return wxMEDIASTATE_STOPPED;
#endif
return wxMediaCtrl::GetState();
}
int wxMediaCtrl2::GetLastError() const
{
#ifdef __LINUX__
#ifdef __WXGTK__
if (m_use_gtk_sink && m_error)
return m_error;
#endif
if (m_error)
return m_error;
return gst_bambu_last_error;
#else
return m_error;
#endif
}
wxSize wxMediaCtrl2::GetVideoSize() const
{
#ifdef __LINUX__
// Gstreamer doesn't give us a VideoSize until we're playing, which
// confuses the MediaPlayCtrl into claiming that it is stuck
// "Loading...". Fake it out for now.
return m_loaded ? wxSize(1280, 720) : wxSize{};
#else
wxSize size = m_imp ? m_imp->GetVideoSize() : wxSize(0, 0);
if (size.GetWidth() > 0)
const_cast<wxSize&>(m_video_size) = size;
return size;
#endif
}
wxSize wxMediaCtrl2::DoGetBestSize() const
{
return {-1, -1};
}
#ifdef __WIN32__
WXLRESULT wxMediaCtrl2::MSWWindowProc(WXUINT nMsg,
WXWPARAM wParam,
WXLPARAM lParam)
{
// The stream source sends WM_USER+1000 with a synchronous SendMessage from its own threads,
// so this runs re-entrantly on the UI thread at whatever message-retrieval point the player
// happens to be in - often nested inside an Orca log statement. Never BOOST_LOG_TRIVIAL here:
// boost::log is not re-entrant on one thread, and doing so corrupted its per-thread record
// state, crashing later in unrelated places (the player, the log filter, a plug-in heap free).
// Post the string out (as the stat branch does) and log it on a clean stack instead.
if (nMsg == WM_USER + 1000) {
wxString msg((wchar_t const *) lParam);
if (wParam == 1) {
if (msg.EndsWith("]")) {
int n = msg.find_last_of('[');
if (n != wxString::npos) {
long val = 0;
if (msg.SubString(n + 1, msg.Length() - 2).ToLong(&val))
m_error = (int) val;
}
} else if (msg.Contains("stat_log")) {
wxCommandEvent evt(EVT_MEDIA_CTRL_STAT);
evt.SetEventObject(this);
evt.SetString(msg.Mid(msg.Find(' ') + 1));
wxPostEvent(this, evt);
}
}
return 0;
}
return wxMediaCtrl::MSWWindowProc(nMsg, wParam, lParam);
}
#endif

View File

@@ -1,117 +0,0 @@
//
// wxMediaCtrl2.h
// libslic3r_gui
//
// Created by cmguo on 2021/12/7.
//
#ifndef wxMediaCtrl2_h
#define wxMediaCtrl2_h
#include "wx/uri.h"
#include "wx/mediactrl.h"
wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
#if defined(__LINUX__) && defined(__WXGTK__)
typedef struct _GstElement GstElement;
#endif
#ifdef __WXMAC__
class wxMediaCtrl2 : public wxWindow
{
public:
wxMediaCtrl2(wxWindow * parent);
~wxMediaCtrl2();
void Load(wxURI url);
void Play();
void Stop();
void SetIdleImage(wxString const & image);
wxMediaState GetState() const;
wxSize GetVideoSize() const;
int GetLastError() const { return m_error; }
static inline const wxMediaState MEDIASTATE_BUFFERING = static_cast<wxMediaState>(6);
protected:
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
static void bambu_log(void const * ctx, int level, char const * msg);
void NotifyStopped();
private:
void create_player();
void * m_player = nullptr;
wxMediaState m_state = wxMEDIASTATE_STOPPED;
int m_error = 0;
wxSize m_video_size{16, 9};
};
#else
class wxMediaCtrl2 : public wxMediaCtrl
{
public:
wxMediaCtrl2(wxWindow *parent);
~wxMediaCtrl2();
void Load(wxURI url);
void Play();
void Stop();
void SetIdleImage(wxString const & image);
wxMediaState GetState();
int GetLastError() const;
wxSize GetVideoSize() const;
protected:
wxSize DoGetBestSize() const override;
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
#ifdef __WIN32__
WXLRESULT MSWWindowProc(WXUINT nMsg,
WXWPARAM wParam,
WXLPARAM lParam) override;
#endif
private:
#if defined(__LINUX__) && defined(__WXGTK__)
bool CreateGtkSinkPlayer();
void DestroyGtkSinkPlayer();
void PostGtkSinkStateEvent(int id = 0);
bool m_native_wayland = false;
bool m_use_gtk_sink = false;
wxString m_gtk_sink_error;
bool m_gtk_sink_error_notified = false;
GstElement *m_gtk_playbin = nullptr;
GstElement *m_gtk_sink = nullptr;
unsigned int m_gtk_bus_watch_id = 0;
wxWindow *m_gtk_video_window = nullptr;
wxMediaState m_gtk_state = wxMEDIASTATE_STOPPED;
#endif
wxString m_idle_image;
int m_error = 0;
bool m_loaded = false;
wxSize m_video_size{16, 9};
};
#endif
#endif /* wxMediaCtrl2_h */

View File

@@ -1,170 +0,0 @@
//
// wxMediaCtrl2.m
// OrcaSlicer
//
// Created by cmguo on 2021/12/7.
//
#import "wxMediaCtrl2.h"
#import "wx/mediactrl.h"
#include <boost/log/trivial.hpp>
#import <Foundation/Foundation.h>
#import "BambuPlayer/BambuPlayer.h"
#import "../Utils/NetworkAgent.hpp"
#include <stdlib.h>
#include <dlfcn.h>
wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
#define BAMBU_DYNAMIC
void wxMediaCtrl2::bambu_log(void const * ctx, int level, char const * msg)
{
if (level == 1) {
wxString msg2(msg);
if (msg2.EndsWith("]")) {
int n = msg2.find_last_of('[');
if (n != wxString::npos) {
long val = 0;
wxMediaCtrl2 * ctrl = (wxMediaCtrl2 *) ctx;
if (msg2.SubString(n + 1, msg2.Length() - 2).ToLong(&val))
ctrl->m_error = (int) val;
}
} else if (strstr(msg, "stat_log")) {
wxMediaCtrl2 * ctrl = (wxMediaCtrl2 *) ctx;
wxCommandEvent evt(EVT_MEDIA_CTRL_STAT);
evt.SetEventObject(ctrl);
evt.SetString(strchr(msg, ' ') + 1);
wxPostEvent(ctrl, evt);
}
} else if (level < 0) {
wxMediaCtrl2 * ctrl = (wxMediaCtrl2 *) ctx;
ctrl->NotifyStopped();
}
BOOST_LOG_TRIVIAL(info) << msg;
}
wxMediaCtrl2::wxMediaCtrl2(wxWindow * parent)
: wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
{
NSView * imageView = (NSView *) GetHandle();
imageView.layer = [[CALayer alloc] init];
CGColorRef color = CGColorCreateGenericRGB(0, 0, 0, 1.0f);
imageView.layer.backgroundColor = color;
CGColorRelease(color);
imageView.wantsLayer = YES;
create_player();
}
wxMediaCtrl2::~wxMediaCtrl2()
{
BambuPlayer * player = (BambuPlayer *) m_player;
[player dealloc];
}
void wxMediaCtrl2::create_player()
{
auto module = Slic3r::NetworkAgent::get_bambu_source_entry();
if (!module) {
//not ready yet
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Network plugin not ready currently!";
return;
}
Class cls = (__bridge Class) dlsym(module, "OBJC_CLASS_$_BambuPlayer");
if (cls == nullptr) {
m_error = -2;
return;
}
NSView * imageView = (NSView *) GetHandle();
BambuPlayer * player = [cls alloc];
[player initWithImageView: imageView];
[player setLogger: bambu_log withContext: this];
m_player = player;
}
void wxMediaCtrl2::Load(wxURI url)
{
if (!m_player) {
create_player();
if (!m_player) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create_player failed currently!";
return;
}
}
BambuPlayer * player = (BambuPlayer *) m_player;
if (player) {
[player close];
m_error = 0;
m_error = [player open: url.BuildURI().ToUTF8()];
}
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
}
void wxMediaCtrl2::Play()
{
if (!m_player) {
create_player();
if (!m_player) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create_player failed currently!";
return;
}
}
BambuPlayer * player2 = (BambuPlayer *) m_player;
[player2 play];
if (m_state != wxMEDIASTATE_PLAYING) {
m_state = wxMEDIASTATE_PLAYING;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
}
}
void wxMediaCtrl2::Stop()
{
if (!m_player) {
create_player();
if (!m_player) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create_player failed currently!";
return;
}
}
BambuPlayer * player2 = (BambuPlayer *) m_player;
[player2 close];
NotifyStopped();
}
void wxMediaCtrl2::NotifyStopped()
{
if (m_state != wxMEDIASTATE_STOPPED) {
m_state = wxMEDIASTATE_STOPPED;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
}
}
wxMediaState wxMediaCtrl2::GetState() const
{
return m_state;
}
wxSize wxMediaCtrl2::GetVideoSize() const
{
BambuPlayer * player2 = (BambuPlayer *) m_player;
if (player2) {
NSSize size = [player2 videoSize];
if (size.width > 0)
const_cast<wxSize&>(m_video_size) = {(int) size.width, (int) size.height};
return {(int) size.width, (int) size.height};
} else {
return {0, 0};
}
}

View File

@@ -0,0 +1,313 @@
#include "wxMediaCtrl3.h"
#include "AVVideoDecoder.hpp"
#include "I18N.hpp"
#include "libslic3r/Utils.hpp"
#include <boost/log/trivial.hpp>
#include <wx/dcclient.h>
#ifdef __WIN32__
#include <versionhelpers.h>
#include <wx/msw/registry.h>
#include <shellapi.h>
#endif
wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
BEGIN_EVENT_TABLE(wxMediaCtrl3, wxWindow)
// catch paint events
EVT_PAINT(wxMediaCtrl3::paintEvent)
END_EVENT_TABLE()
struct StaticBambuLib : BambuLib
{
static StaticBambuLib &get(BambuLib *);
};
wxMediaCtrl3::wxMediaCtrl3(wxWindow *parent)
: wxWindow(parent, wxID_ANY)
, BambuLib(StaticBambuLib::get(this))
, m_thread([this] { PlayThread(); })
{
SetBackgroundColour("#000001ff");
}
wxMediaCtrl3::~wxMediaCtrl3()
{
{
std::unique_lock<std::mutex> lk(m_mutex);
m_url.reset(new wxURI);
m_frame = wxImage(m_idle_image);
m_cond.notify_all();
}
m_thread.join();
}
void wxMediaCtrl3::Load(wxURI url)
{
std::unique_lock<std::mutex> lk(m_mutex);
m_video_size = wxDefaultSize;
m_error = 0;
m_url.reset(new wxURI(url));
m_cond.notify_all();
}
void wxMediaCtrl3::Play()
{
std::unique_lock<std::mutex> lk(m_mutex);
if (m_state != wxMEDIASTATE_PLAYING) {
m_state = wxMEDIASTATE_PLAYING;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
}
}
void wxMediaCtrl3::Stop()
{
std::unique_lock<std::mutex> lk(m_mutex);
m_url.reset();
m_frame = wxImage(m_idle_image);
NotifyStopped();
m_cond.notify_all();
Refresh();
}
void wxMediaCtrl3::SetIdleImage(wxString const &image)
{
if (m_idle_image == image)
return;
m_idle_image = image;
if (m_url == nullptr) {
std::unique_lock<std::mutex> lk(m_mutex);
m_frame = wxImage(m_idle_image);
assert(m_frame.IsOk());
Refresh();
}
}
wxMediaState wxMediaCtrl3::GetState()
{
std::unique_lock<std::mutex> lk(m_mutex);
return m_state;
}
int wxMediaCtrl3::GetLastError()
{
std::unique_lock<std::mutex> lk(m_mutex);
return m_error;
}
wxSize wxMediaCtrl3::GetVideoSize()
{
std::unique_lock<std::mutex> lk(m_mutex);
return m_video_size;
}
wxSize wxMediaCtrl3::DoGetBestSize() const
{
return {-1, -1};
}
static void adjust_frame_size(wxSize & frame, wxSize const & video, wxSize const & window)
{
if (video.x * window.y < video.y * window.x)
frame = { video.x * window.y / video.y, window.y };
else
frame = { window.x, video.y * window.x / video.x };
}
void wxMediaCtrl3::paintEvent(wxPaintEvent &evt)
{
wxPaintDC dc(this);
auto size = GetSize();
if (size.x <= 0 || size.y <= 0)
return;
std::unique_lock<std::mutex> lk(m_mutex);
if (!m_frame.IsOk())
return;
auto size2 = m_frame.GetSize();
if (size2.x != m_frame_size.x && size2.y == m_frame_size.y)
size2.x = m_frame_size.x;
auto size3 = (size - size2) / 2;
if (size2.x != size.x && size2.y != size.y) {
double scale = 1.;
if (size.x * size2.y > size.y * size2.x) {
size3 = {size.x * size2.y / size.y, size2.y};
scale = double(size.y) / size2.y;
} else {
size3 = {size2.x, size.y * size2.x / size.x};
scale = double(size.x) / size2.x;
}
dc.SetUserScale(scale, scale);
size3 = (size3 - size2) / 2;
}
dc.DrawBitmap(m_frame, size3.x, size3.y);
}
void wxMediaCtrl3::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
wxWindow::DoSetSize(x, y, width, height, sizeFlags);
if (sizeFlags == wxSIZE_USE_EXISTING) return;
wxMediaCtrl_OnSize(this, m_video_size, width, height);
std::unique_lock<std::mutex> lk(m_mutex);
adjust_frame_size(m_frame_size, m_video_size, GetSize());
Refresh();
}
void wxMediaCtrl3::bambu_log(void *ctx, int level, tchar const *msg2)
{
#ifdef _WIN32
wxString msg(msg2);
#else
wxString msg = wxString::FromUTF8(msg2);
#endif
if (level == 1) {
if (msg.EndsWith("]")) {
int n = msg.find_last_of('[');
if (n != wxString::npos) {
long val = 0;
wxMediaCtrl3 *ctrl = (wxMediaCtrl3 *) ctx;
if (msg.SubString(n + 1, msg.Length() - 2).ToLong(&val)) {
std::unique_lock<std::mutex> lk(ctrl->m_mutex);
ctrl->m_error = (int) val;
}
}
} else if (msg.Contains("stat_log")) {
wxCommandEvent evt(EVT_MEDIA_CTRL_STAT);
wxMediaCtrl3 *ctrl = (wxMediaCtrl3 *) ctx;
evt.SetEventObject(ctrl);
evt.SetString(msg.Mid(msg.Find(' ') + 1));
wxPostEvent(ctrl, evt);
}
}
BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data();
}
void wxMediaCtrl3::PlayThread()
{
using namespace std::chrono_literals;
std::shared_ptr<wxURI> url;
std::unique_lock<std::mutex> lk(m_mutex);
while (true) {
m_cond.wait(lk, [this, &url] { return m_url != url; });
url = m_url;
if (url == nullptr)
continue;
if (!url->HasScheme())
break;
lk.unlock();
Bambu_Tunnel tunnel = nullptr;
int error = Bambu_Create(&tunnel, m_url->BuildURI().ToUTF8());
if (error == 0) {
Bambu_SetLogger(tunnel, &wxMediaCtrl3::bambu_log, this);
error = Bambu_Open(tunnel);
if (error == 0)
error = Bambu_would_block;
}
lk.lock();
while (error == int(Bambu_would_block)) {
m_cond.wait_for(lk, 100ms);
if (m_url != url) {
error = 1;
break;
}
lk.unlock();
error = Bambu_StartStream(tunnel, true);
lk.lock();
}
Bambu_StreamInfo info;
if (error == 0)
error = Bambu_GetStreamInfo(tunnel, 0, &info);
AVVideoDecoder decoder;
int minFrameDuration = 0;
if (error == 0) {
decoder.open(info);
m_video_size = { info.format.video.width, info.format.video.height };
adjust_frame_size(m_frame_size, m_video_size, GetSize());
minFrameDuration = 800 / info.format.video.frame_rate; // 80%
NotifyStopped();
}
Bambu_Sample sample;
while (error == 0) {
lk.unlock();
error = Bambu_ReadSample(tunnel, &sample);
lk.lock();
while (error == int(Bambu_would_block)) {
m_cond.wait_for(lk, 100ms);
if (m_url != url) {
error = 1;
break;
}
lk.unlock();
error = Bambu_ReadSample(tunnel, &sample);
lk.lock();
}
if (error == 0) {
auto frame_size = m_frame_size;
lk.unlock();
decoder.decode(sample);
#ifdef _WIN32
wxBitmap bm;
decoder.toWxBitmap(bm, frame_size);
#else
wxImage bm;
decoder.toWxImage(bm, frame_size);
#endif
lk.lock();
if (m_url != url) {
error = 1;
break;
}
if (bm.IsOk()) {
auto now = std::chrono::system_clock::now();
if (m_last_PTS && (sample.decode_time - m_last_PTS) < 30000000ULL) { // 3s
auto next_PTS_expected = m_last_PTS_expected + std::chrono::milliseconds((sample.decode_time - m_last_PTS) / 10000ULL);
// The frame is late, catch up a little
auto next_PTS_practical = m_last_PTS_practical + std::chrono::milliseconds(minFrameDuration);
auto next_PTS = std::max(next_PTS_expected, next_PTS_practical);
if(now < next_PTS)
std::this_thread::sleep_until(next_PTS);
else
next_PTS = now;
//auto text = wxString::Format(L"wxMediaCtrl3 pts diff %ld\n", std::chrono::duration_cast<std::chrono::milliseconds>(next_PTS - next_PTS_expected).count());
//OutputDebugString(text);
m_last_PTS = sample.decode_time;
m_last_PTS_expected = next_PTS_expected;
m_last_PTS_practical = next_PTS;
} else {
// Resync
m_last_PTS = sample.decode_time;
m_last_PTS_expected = now;
m_last_PTS_practical = now;
}
m_frame = bm;
}
CallAfter([this] { Refresh(); });
}
}
if (tunnel) {
lk.unlock();
Bambu_Close(tunnel);
Bambu_Destroy(tunnel);
tunnel = nullptr;
lk.lock();
}
if (m_url == url)
m_error = error;
m_frame_size = wxDefaultSize;
m_video_size = wxDefaultSize;
NotifyStopped();
}
}
void wxMediaCtrl3::NotifyStopped()
{
m_state = wxMEDIASTATE_STOPPED;
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
event.SetId(GetId());
event.SetEventObject(this);
wxPostEvent(this, event);
}

View File

@@ -0,0 +1,85 @@
//
// wxMediaCtrl3.h
// libslic3r_gui
//
// Created by cmguo on 2024/6/22.
//
#ifndef wxMediaCtrl3_h
#define wxMediaCtrl3_h
#include "wx/uri.h"
#include "wx/mediactrl.h"
wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height);
#define BAMBU_DYNAMIC
#include <condition_variable>
#include <thread>
#ifndef _WIN32
#include <wx/image.h>
#endif
#include "Printer/BambuTunnel.h"
class AVVideoDecoder;
class wxMediaCtrl3 : public wxWindow, BambuLib
{
public:
wxMediaCtrl3(wxWindow *parent);
~wxMediaCtrl3();
void Load(wxURI url);
void Play();
void Stop();
void SetIdleImage(wxString const & image);
wxMediaState GetState();
int GetLastError();
wxSize GetVideoSize();
protected:
DECLARE_EVENT_TABLE()
void paintEvent(wxPaintEvent &evt);
wxSize DoGetBestSize() const 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);
void PlayThread();
void NotifyStopped();
private:
wxString m_idle_image;
wxMediaState m_state = wxMEDIASTATE_STOPPED;
int m_error = 0;
wxSize m_video_size = wxDefaultSize;
wxSize m_frame_size = wxDefaultSize;
#ifdef _WIN32
wxBitmap m_frame;
#else
wxImage m_frame;
#endif
std::shared_ptr<wxURI> m_url;
std::uint64_t m_last_PTS{0};
std::chrono::system_clock::time_point m_last_PTS_expected;
std::chrono::system_clock::time_point m_last_PTS_practical;
std::mutex m_mutex;
std::condition_variable m_cond;
std::thread m_thread;
};
#endif /* wxMediaCtrl3_h */

View File

@@ -30,25 +30,47 @@ if (APPLE)
target_link_libraries(test_common INTERFACE "-liconv -framework IOKit" "-framework CoreFoundation" -lc++) target_link_libraries(test_common INTERFACE "-liconv -framework IOKit" "-framework CoreFoundation" -lc++)
endif() endif()
# Copies runtime DLLs next to each test executable. Handles both single-config # Copies runtime shared libraries next to each test executable. Handles both
# generators (CMAKE_BUILD_TYPE set) and multi-config generators (Ninja # single-config generators (CMAKE_BUILD_TYPE set) and multi-config generators
# Multi-Config, Visual Studio) where CMAKE_BUILD_TYPE is empty and DLLs must # (Ninja Multi-Config, Visual Studio) where CMAKE_BUILD_TYPE is empty and DLLs
# land in every per-config output directory. # must land in every per-config output directory. On Windows the loader finds
# DLLs in the executable's directory; the Linux branch below does the same for
# the deps-built FFmpeg libraries and adds an $ORIGIN rpath, since the ELF
# loader does not search the executable's directory and the CI unit-test runner
# only receives the tests artifact (no deps install).
function(orcaslicer_copy_test_dlls) function(orcaslicer_copy_test_dlls)
if (NOT WIN32) if (WIN32)
return() set(_configs ${CMAKE_CONFIGURATION_TYPES})
endif() if (NOT _configs)
set(_configs ${CMAKE_CONFIGURATION_TYPES}) set(_configs "${CMAKE_BUILD_TYPE}")
if (NOT _configs)
set(_configs "${CMAKE_BUILD_TYPE}")
endif()
foreach(_cfg IN LISTS _configs)
if (_cfg STREQUAL "Debug")
orcaslicer_copy_dlls(COPY_DLLS "Debug" "d" _unused_dlls)
else()
orcaslicer_copy_dlls(COPY_DLLS "${_cfg}" "" _unused_dlls)
endif() endif()
endforeach() foreach(_cfg IN LISTS _configs)
if (_cfg STREQUAL "Debug")
orcaslicer_copy_dlls(COPY_DLLS "Debug" "d" _unused_dlls)
else()
orcaslicer_copy_dlls(COPY_DLLS "${_cfg}" "" _unused_dlls)
endif()
endforeach()
elseif (UNIX AND NOT APPLE)
# Only test executables that link libslic3r_gui pull in the FFmpeg
# shared libraries (src/slic3r/CMakeLists.txt links PkgConfig::LIBAV
# into it). Copy them next to the executable and give it an $ORIGIN
# rpath so the loader finds them when the tests run on the CI unit-test
# runner, which only receives this build/tests tree.
get_target_property(_linked_libs ${_TEST_NAME}_tests LINK_LIBRARIES)
if (NOT "libslic3r_gui" IN_LIST _linked_libs)
return()
endif()
set_property(TARGET ${_TEST_NAME}_tests PROPERTY BUILD_RPATH "$ORIGIN")
set(_configs ${CMAKE_CONFIGURATION_TYPES})
if (NOT _configs)
set(_configs "${CMAKE_BUILD_TYPE}")
endif()
foreach(_cfg IN LISTS _configs)
orcaslicer_copy_sos(${_TEST_NAME}_tests "${_cfg}" "" _unused_sos)
endforeach()
endif()
endfunction() endfunction()
# Register Catch2 tags as CTest labels so `ctest -L`/`-LE` can filter by tag. # Register Catch2 tags as CTest labels so `ctest -L`/`-LE` can filter by tag.