diff --git a/components/3rd_party/FFmpeg/CMakeLists.txt b/components/3rd_party/FFmpeg/CMakeLists.txt index 3477058f..d6e08296 100644 --- a/components/3rd_party/FFmpeg/CMakeLists.txt +++ b/components/3rd_party/FFmpeg/CMakeLists.txt @@ -1,6 +1,10 @@ set(ffmpeg_version_str "${CONFIG_FFMPEG_VERSION_MAJOR}.${CONFIG_FFMPEG_VERSION_MINOR}.${CONFIG_FFMPEG_VERSION_PATCH}") set(ffmpeg_unzip_path "${DL_EXTRACTED_PATH}/ffmpeg_srcs") -set(src_path "${ffmpeg_unzip_path}/ffmpeg") +if(PLATFORM_MAIXCAM2) + set(src_path "${ffmpeg_unzip_path}/ffmpeg_ax630c_libs") +else() + set(src_path "${ffmpeg_unzip_path}/ffmpeg") +endif() ############### Add include ################### set(ffmpeg_include_dir "${src_path}/include" "." @@ -62,12 +66,13 @@ set(ffmpeg_dynamic_lib_file ${src_path}/lib/libavcodec.so ${src_path}/lib/libavformat.so ${src_path}/lib/libavresample.so ${src_path}/lib/libavutil.so - ${src_path}/lib/libpostproc.so ${src_path}/lib/libswresample.so ${src_path}/lib/libswscale.so ) list(APPEND ADD_DYNAMIC_LIB ${ffmpeg_dynamic_lib_file}) -list(APPEND ADD_DIST_LIB_IGNORE ${ffmpeg_dynamic_lib_file}) +if (PLATFORM_MAIXCAM) + list(APPEND ADD_DYNAMIC_LIB ${src_path}/lib/libpostproc.so) +endif() set_property(SOURCE ${ffmpeg_dynamic_lib_file} PROPERTY GENERATED 1) ############################################### diff --git a/components/3rd_party/FFmpeg/component.py b/components/3rd_party/FFmpeg/component.py index 91132f3b..8069c3c8 100644 --- a/components/3rd_party/FFmpeg/component.py +++ b/components/3rd_party/FFmpeg/component.py @@ -5,32 +5,60 @@ def add_file_downloads(confs : dict) -> list: @return list type, items is dict type ''' version = f"{confs['CONFIG_FFMPEG_VERSION_MAJOR']}.{confs['CONFIG_FFMPEG_VERSION_MINOR']}.{confs['CONFIG_FFMPEG_VERSION_PATCH']}.{confs['CONFIG_FFMPEG_COMPILED_VERSION']}" - url = f"https://github.com/sipeed/MaixCDK/releases/download/v0.0.0/ffmpeg_libs_n{version}.tar.xz" - if version == "4.4.4.1": - sha256sum = "d4b8eb70a7864c855e8e286fef39b9b90faf72c1b77491148c451da252cad6c0" - else: - raise Exception(f"version {version} not support") - sites = ["https://github.com/sipeed/MaixCDK/releases/tag/v0.0.0"] - filename = f"ffmpeg_libs_n{version}.tar.xz" - path = f"ffmpeg_srcs" - check_file = 'ffmpeg' - rename = { - f'ffmpeg_libs_n{version}': 'ffmpeg' - } - - return [ - { - 'url': f'{url}', - 'urls': [], - 'sites': sites, - 'sha256sum': sha256sum, - 'filename': filename, - 'path': path, - 'check_files': [ - check_file - ], - 'rename': rename + if confs.get('PLATFORM_MAIXCAM', None): + url = f"https://github.com/sipeed/MaixCDK/releases/download/v0.0.0/ffmpeg_libs_n{version}.tar.xz" + if version == "4.4.4.1": + sha256sum = "d4b8eb70a7864c855e8e286fef39b9b90faf72c1b77491148c451da252cad6c0" + else: + raise Exception(f"version {version} not support") + sites = ["https://github.com/sipeed/MaixCDK/releases/tag/v0.0.0"] + filename = f"ffmpeg_libs_n{version}.tar.xz" + path = f"ffmpeg_srcs" + check_file = 'ffmpeg' + rename = { + f'ffmpeg_libs_n{version}': 'ffmpeg' } - ] + return [ + { + 'url': f'{url}', + 'urls': [], + 'sites': sites, + 'sha256sum': sha256sum, + 'filename': filename, + 'path': path, + 'check_files': [ + check_file + ], + 'rename': rename + } + ] + elif confs.get('PLATFORM_MAIXCAM2', None): + url = f"https://github.com/sipeed/MaixCDK/releases/download/v0.0.0/ffmpeg_ax630c_libs_n{version}.tar.xz" + if version == "4.4.4.1": + sha256sum = "3fcc394fad917127dd9503396dd84ce29fe47f33e76ebfd86dce6a73031e3a4b." + else: + raise Exception(f"version {version} not support") + sites = ["https://github.com/sipeed/MaixCDK/releases/tag/v0.0.0"] + filename = f"ffmpeg_ax630c_libs_n{version}.tar.xz" + path = f"ffmpeg_srcs" + check_file = 'ffmpeg_ax630c_libs' + rename = { + f'ffmpeg_ax630c_libs_n{version}': 'ffmpeg_ax630c_libs' + } + + return [ + { + 'url': f'{url}', + 'urls': [], + 'sites': sites, + 'sha256sum': sha256sum, + 'filename': filename, + 'path': path, + 'check_files': [ + check_file + ], + 'rename': rename + } + ] diff --git a/components/3rd_party/ax620e_bsp_sdk/CMakeLists.txt b/components/3rd_party/ax620e_bsp_sdk/CMakeLists.txt new file mode 100644 index 00000000..1472ec64 --- /dev/null +++ b/components/3rd_party/ax620e_bsp_sdk/CMakeLists.txt @@ -0,0 +1,121 @@ +set(srcs_path "${DL_EXTRACTED_PATH}/ax620e_bsp_sdk_srcs/ax620e_bsp_sdk") +################# Add include ################# +set(bsp_glibc_path "${srcs_path}/msp/out/arm64_glibc") +set(bsp_sample_path "${srcs_path}/msp/sample") +set(bsp_app_path "${srcs_path}/app") +list(APPEND ADD_INCLUDE "${bsp_glibc_path}/include" + "${bsp_sample_path}/common" + ) +list(APPEND ADD_PRIVATE_INCLUDE "${bsp_app_path}/component/3rd/inc/appweb") +############################################### + +############## Add source files ############### +# list(APPEND ADD_SRCS "src/lib1.c" +# ) +# aux_source_directory("inifile2/src" ADD_SRCS) # collect all source file in src dir, will set var ADD_SRCS +append_srcs_dir(ADD_SRCS "${bsp_sample_path}/common") # append source file in src dir to var ADD_SRCS +# list(REMOVE_ITEM COMPONENT_SRCS "src/test.c") +############################################### + +###### Add required/dependent components ###### +# list(APPEND ADD_REQUIREMENTS component1) +############################################### + +###### Add link search path for requirements/libs ###### +# list(APPEND ADD_LINK_SEARCH_PATH "${CONFIG_TOOLCHAIN_PATH}/lib") +# list(APPEND ADD_REQUIREMENTS m) # add system libs, pthread or m(math) lib for example +############################################### + +############ Add static libs ################## +# if(CONFIG_COMPONENT1_INCLUDE_STATIC_LIB) + +# endif() +############################################### + +############ Add dynamic libs ################## +list(APPEND ADD_DYNAMIC_LIB "${bsp_glibc_path}/lib/libax_skel.so" +"${bsp_glibc_path}/lib/libax_skel.so" +"${bsp_glibc_path}/lib/libax_avs.so" +"${bsp_glibc_path}/lib/libax_utility.so" +"${bsp_glibc_path}/lib/libax_af.so" +"${bsp_glibc_path}/lib/libax_venc.so" +# "${bsp_glibc_path}/lib/libax_faac.so" +"${bsp_glibc_path}/lib/libax_proton.so" +"${bsp_glibc_path}/lib/libsns_sc500ai.so" +"${bsp_glibc_path}/lib/libax_nt_stream.so" +"${bsp_glibc_path}/lib/libax_audio_3a.so" +"${bsp_glibc_path}/lib/libax_interpreter.so" +"${bsp_glibc_path}/lib/libsns_sc231ai.so" +"${bsp_glibc_path}/lib/libax_ivps.so" +"${bsp_glibc_path}/lib/libax_ives.so" +# "${bsp_glibc_path}/lib/libax_audio.so" +"${bsp_glibc_path}/lib/libax_ae.so" +"${bsp_glibc_path}/lib/libax_skel_tiny.so" +"${bsp_glibc_path}/lib/libsns_imx664.so" +"${bsp_glibc_path}/lib/libax_efuse.so" +"${bsp_glibc_path}/lib/libax_vo.so" +"${bsp_glibc_path}/lib/libsns_sc430ai.so" +"${bsp_glibc_path}/lib/libsns_s5kjn1sq03.so" +# "${bsp_glibc_path}/lib/libax_fdk.so" +"${bsp_glibc_path}/lib/libsns_sc450ai.so" +"${bsp_glibc_path}/lib/libax_sys.so" +"${bsp_glibc_path}/lib/libax_vdec.so" +"${bsp_glibc_path}/lib/libax_awb.so" +"${bsp_glibc_path}/lib/libax_mipi.so" +"${bsp_glibc_path}/lib/liblens_t5719.so" +"${bsp_glibc_path}/lib/libsns_imx678.so" +"${bsp_glibc_path}/lib/libax_ive.so" +"${bsp_glibc_path}/lib/libax_engine.so" +"${bsp_glibc_path}/lib/libsns_os04d10.so" +"${bsp_glibc_path}/lib/libsns_mis2032.so" +"${bsp_glibc_path}/lib/libax_nt_ctrl.so" +"${bsp_glibc_path}/lib/libsns_os08c10.so" +"${bsp_glibc_path}/lib/libax_avscali.so" +# "${bsp_glibc_path}/lib/libax_opus.so" +"${bsp_glibc_path}/lib/libsns_sc200ai.so" +"${bsp_glibc_path}/lib/libsns_dummy.so" +"${bsp_glibc_path}/lib/libsns_os04a10.so" +"${bsp_glibc_path}/lib/libsns_sc530ai.so" +"${bsp_glibc_path}/lib/libax_engine_tiny.so" +"${bsp_glibc_path}/lib/libsns_gc4653.so" +"${bsp_glibc_path}/lib/libax_gzipd.so" +"${bsp_glibc_path}/lib/libax_thermal.so" +"${bsp_glibc_path}/lib/libsns_sc4210.so" +"${bsp_glibc_path}/lib/libsns_gc4663.so" +"${bsp_glibc_path}/lib/libsns_sc830ai.so" +"${bsp_glibc_path}/lib/libsns_c4395.so" +"${bsp_glibc_path}/lib/libax_cipher.so" +# "${bsp_glibc_path}/lib/libax_opal.so" +"${bsp_glibc_path}/lib/libsns_sc850sl.so" +"${bsp_glibc_path}/lib/libax_syslog.so" +"${bsp_glibc_path}/lib/libsns_dummy_bittrue.so" +"${bsp_glibc_path}/lib/liblens_dciris.so" +# "${srcs_path}/third-party/faac/lib/arm64/glibc/libfaac.so.0" +"${srcs_path}/third-party/fdk-aac/lib/arm64/glibc/libfdk-aac.so.2" +"${srcs_path}/third-party/opus/lib/arm64/glibc/libopus.so.0" +"${bsp_app_path}/component/resource/lib/arm64/glibc/appweb/libmpr.so" + +) + +############################################### + +#### Add compile option for this component #### +#### Just for this component, won't affect other +#### modules, including component that depend +#### on this component +# list(APPEND ADD_DEFINITIONS_PRIVATE -DAAAAA=1) + +#### Add compile option for this component +#### Add components that depend on this component +# list(APPEND ADD_DEFINITIONS -DAAAAA222=1 +# -DAAAAA333=1) +############################################### + +############ Add static libs ################## +#### Update parent's variables like CMAKE_C_LINK_FLAGS +set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,-rpath,${srcs_path}/third-party/faac/lib/arm/glibc/libfaac.so.0" PARENT_SCOPE) +############################################### + +# register component, DYNAMIC or SHARED flags will make component compiled to dynamic(shared) lib +register_component() + diff --git a/components/3rd_party/ax620e_bsp_sdk/component.py b/components/3rd_party/ax620e_bsp_sdk/component.py new file mode 100644 index 00000000..88109c0d --- /dev/null +++ b/components/3rd_party/ax620e_bsp_sdk/component.py @@ -0,0 +1,36 @@ + +def add_file_downloads(confs : dict) -> list: + ''' + @param confs kconfig vars, dict type + @return list type, items is dict type + ''' + return [ + { + 'url': f"https://github.com/AXERA-TECH/ax620e_bsp_sdk/archive/refs/tags/v2.0.0_P7.tar.gz", + 'urls': [], + 'sites': ["https://github.com/AXERA-TECH/ax620e_bsp_sdk"], + 'sha256sum': "adfe5fda41b1aac131a2b2618a823b9c5041c486dfa725ce7fc0e9c1331cca65", + 'filename': f"ax620e_bsp_sdk-2.0.0_P7.tar.gz", + 'path': f"ax620e_bsp_sdk_srcs", + 'check_files': [ + 'ax620e_bsp_sdk' + ], + 'rename': { + f'ax620e_bsp_sdk-2.0.0_P7': 'ax620e_bsp_sdk' + } + }, + { + 'url': f"https://github.com/sipeed/MaixCDK/releases/download/v0.0.0/ax630c-third-party_2.0.0.tar.gz", + 'urls': [], + 'sites': ["https://github.com/AXERA-TECH/ax620e_bsp_sdk"], + 'sha256sum': "9c9b25aec80e1195fabe35b29f74ca588f6dec0370dfa7f2c6f1c5020ee89547", + 'filename': f"ax630c-third-party_2.0.0.tar.gz", + 'path': f"ax620e_bsp_sdk_srcs/ax620e_bsp_sdk", + 'check_files': [ + 'ax620e_bsp_sdk_srcs/ax620e_bsp_sdk/third-party' + ], + 'rename': {} + } + ] + + diff --git a/components/3rd_party/brotli/CMakeLists.txt b/components/3rd_party/brotli/CMakeLists.txt index 948ea986..86d54da8 100644 --- a/components/3rd_party/brotli/CMakeLists.txt +++ b/components/3rd_party/brotli/CMakeLists.txt @@ -96,7 +96,7 @@ endif() if(PLATFORM_M2DOCK) set(brotli_toolchain_file ${CMAKE_CURRENT_LIST_DIR}/toolchain_m2dock.cmake) -elseif(PLATFORM_MAIXCAM) +elseif(PLATFORM_MAIXCAM OR PLATFORM_MAIXCAM2) set(brotli_toolchain_file ${CMAKE_CURRENT_LIST_DIR}/toolchain_linux_cross.cmake) elseif(PLATFORM_LINUX) set(brotli_toolchain_file ${CMAKE_CURRENT_LIST_DIR}/toolchain_linux.cmake) diff --git a/components/3rd_party/freetype/CMakeLists.txt b/components/3rd_party/freetype/CMakeLists.txt index 7740cef3..7115e7fd 100644 --- a/components/3rd_party/freetype/CMakeLists.txt +++ b/components/3rd_party/freetype/CMakeLists.txt @@ -91,7 +91,7 @@ set(freetype_unzip_path "${DL_EXTRACTED_PATH}/freetype_srcs") set(freetype_src_path "${freetype_unzip_path}/freetype-${freetype_version_str}") if(PLATFORM_M2DOCK) set(freetype_toolchain_file ${CMAKE_CURRENT_LIST_DIR}/toolchain_m2dock.cmake) -elseif(PLATFORM_MAIXCAM) +elseif(PLATFORM_MAIXCAM OR PLATFORM_MAIXCAM2) set(freetype_toolchain_file ${CMAKE_CURRENT_LIST_DIR}/toolchain_linux_cross.cmake) elseif(PLATFORM_LINUX) set(freetype_toolchain_file ${CMAKE_CURRENT_LIST_DIR}/toolchain_linux.cmake) diff --git a/components/3rd_party/harfbuzz/CMakeLists.txt b/components/3rd_party/harfbuzz/CMakeLists.txt index f884b447..ed070d84 100644 --- a/components/3rd_party/harfbuzz/CMakeLists.txt +++ b/components/3rd_party/harfbuzz/CMakeLists.txt @@ -83,6 +83,14 @@ if(PLATFORM_MAIXCAM AND NOT CONFIG_COMPONENTS_COMPILE_FROM_SOURCE) list(APPEND ADD_INCLUDE ${harfbuzz_include_dir}) list(APPEND ADD_STATIC_LIB ${harfbuzz_lib_file}) endif() +elseif(PLATFORM_MAIXCAM2 AND NOT CONFIG_COMPONENTS_COMPILE_FROM_SOURCE) + set(harfbuzz_lib_dir "${DL_EXTRACTED_PATH}/harfbuzz/harfbuzz_maixcam2_glibc_v8.2.1") + set(harfbuzz_include_dir "${harfbuzz_lib_dir}/include/harfbuzz" "${harfbuzz_lib_dir}/include") + set(harfbuzz_lib_file "${harfbuzz_lib_dir}/lib/libharfbuzz.a") + set_property(SOURCE ${harfbuzz_lib_file} PROPERTY GENERATED 1) + set_property(SOURCE ${harfbuzz_include_dir} PROPERTY GENERATED 1) + list(APPEND ADD_INCLUDE ${harfbuzz_include_dir}) + list(APPEND ADD_STATIC_LIB ${harfbuzz_lib_file}) endif() if(NOT ADD_INCLUDE) @@ -103,7 +111,7 @@ if(NOT ADD_INCLUDE) if(PLATFORM_M2DOCK) set(harfbuzz_toolchain_file ${CMAKE_CURRENT_LIST_DIR}/toolchain_m2dock.cmake) - elseif(PLATFORM_MAIXCAM) + elseif(PLATFORM_MAIXCAM OR PLATFORM_MAIXCAM2) set(harfbuzz_toolchain_file ${CMAKE_CURRENT_LIST_DIR}/toolchain_linux_cross.cmake) elseif(PLATFORM_LINUX) set(harfbuzz_toolchain_file ${CMAKE_CURRENT_LIST_DIR}/toolchain_linux.cmake) diff --git a/components/3rd_party/harfbuzz/component.py b/components/3rd_party/harfbuzz/component.py index b705c66a..8d1d6c0c 100644 --- a/components/3rd_party/harfbuzz/component.py +++ b/components/3rd_party/harfbuzz/component.py @@ -4,7 +4,7 @@ def add_file_downloads(confs : dict) -> list: @param confs kconfig vars, dict type @return list type, items is dict type ''' - if (not confs.get("PLATFORM_MAIXCAM", None)) or confs.get("CONFIG_COMPONENTS_COMPILE_FROM_SOURCE", None): + if not (bool(confs.get("PLATFORM_MAIXCAM", None)) or bool(confs.get('PLATFORM_MAIXCAM2', None))) or confs.get("CONFIG_COMPONENTS_COMPILE_FROM_SOURCE", None): # harfbuzz src version = f"{confs['CONFIG_HARFBUZZ_VERSION_MAJOR']}.{confs['CONFIG_HARFBUZZ_VERSION_MINOR']}.{confs['CONFIG_HARFBUZZ_VERSION_PATCH']}" url = f"https://github.com/harfbuzz/harfbuzz/releases/download/{version}/harfbuzz-{version}.tar.xz" @@ -16,6 +16,56 @@ def add_file_downloads(confs : dict) -> list: filename = f"harfbuzz-{version}.tar.xz" path = "harfbuzz_srcs" check_file = f'harfbuzz-{version}' + return [ + { + 'url': f'{url}', + 'urls': [], + 'sites': sites, + 'sha256sum': sha256sum, + 'filename': filename, + 'path': path, + 'check_files': [ + check_file + ] + } + ] + elif confs.get('PLATFORM_MAIXCAM', None): + if "musl" not in confs["CONFIG_TOOLCHAIN_PATH"]: + return [] + version = "8.2.1" + url = "https://github.com/sipeed/MaixCDK/releases/download/v0.0.0/harfbuzz_maixcam_musl_v8.2.1.tar.xz" + if version == "8.2.1": + sha256sum = "0ee057914aeabd4d44c23f776e3dde79a3ec0a34cfe5e0a9c09737d0bf34e7fb" + else: + raise Exception(f"version {version} not support") + sites = ["https://github.com/sipeed/MaixCDK/releases/tag/v0.0.0"] + filename = "harfbuzz_maixcam_musl_v8.2.1.tar.xz" + path = "harfbuzz" + check_file = 'harfbuzz_maixcam_musl_v8.2.1' + return [ + { + 'url': f'{url}', + 'urls': [], + 'sites': sites, + 'sha256sum': sha256sum, + 'filename': filename, + 'path': path, + 'check_files': [ + check_file + ] + } + ] + elif confs.get('PLATFORM_MAIXCAM2', None): + version = "8.2.1" + url = "https://github.com/sipeed/MaixCDK/releases/download/v0.0.0/harfbuzz_maixcam2_glibc_v8.2.1.tar.xz" + if version == "8.2.1": + sha256sum = "16cb4a733b17370cbb012cca16867a57a6080a40251be51f18f40164b60ff8b3" + else: + raise Exception(f"version {version} not support") + sites = ["https://github.com/sipeed/MaixCDK/releases/tag/v0.0.0"] + filename = "harfbuzz_maixcam2_glibc_v8.2.1.tar.xz" + path = "harfbuzz" + check_file = 'harfbuzz_maixcam2_glibc_v8.2.1' return [ { @@ -31,32 +81,4 @@ def add_file_downloads(confs : dict) -> list: } ] - # maixcam precompiled lib - if "musl" not in confs["CONFIG_TOOLCHAIN_PATH"]: - return [] - version = "8.2.1" - url = "https://github.com/sipeed/MaixCDK/releases/download/v0.0.0/harfbuzz_maixcam_musl_v8.2.1.tar.xz" - if version == "8.2.1": - sha256sum = "0ee057914aeabd4d44c23f776e3dde79a3ec0a34cfe5e0a9c09737d0bf34e7fb" - else: - raise Exception(f"version {version} not support") - sites = ["https://github.com/sipeed/MaixCDK/releases/tag/v0.0.0"] - filename = "harfbuzz_maixcam_musl_v8.2.1.tar.xz" - path = "harfbuzz" - check_file = 'harfbuzz_maixcam_musl_v8.2.1' - - return [ - { - 'url': f'{url}', - 'urls': [], - 'sites': sites, - 'sha256sum': sha256sum, - 'filename': filename, - 'path': path, - 'check_files': [ - check_file - ] - } - ] - diff --git a/components/3rd_party/omv/CMakeLists.txt b/components/3rd_party/omv/CMakeLists.txt index 7d4dd9c4..ae18d5b5 100644 --- a/components/3rd_party/omv/CMakeLists.txt +++ b/components/3rd_party/omv/CMakeLists.txt @@ -108,7 +108,9 @@ set_property(SOURCE "${src_path}/alloc" PROPERTY GENERATED 1) #### modules, including component that depend #### on this component list(APPEND ADD_DEFINITIONS_PRIVATE -w) - +if(PLATFORM_MAIXCAM2) + list(APPEND ADD_DEFINITIONS_PRIVATE -D__ARM_ARCH=0) +endif() #### Add compile option for this component #### Add components that depend on this component # list(APPEND ADD_DEFINITIONS -DAAAAA222=1 diff --git a/components/3rd_party/opencv/CMakeLists.txt b/components/3rd_party/opencv/CMakeLists.txt index a2129dd2..3e12a7fe 100644 --- a/components/3rd_party/opencv/CMakeLists.txt +++ b/components/3rd_party/opencv/CMakeLists.txt @@ -18,7 +18,7 @@ if((NOT CONFIG_OPENCV_COMPILE_FROM_SOURCE) AND PLATFORM_LINUX AND NOT CONFIG_COM message(FATAL_ERROR "can not find OpenCV locally, will download source code to compile, you can install it by 'sudo apt install libopencv-dev libopencv-contrib-dev', or select OPENCV_COMPILE_FROM_SOURCE option to compile it from source code") endif() endif() -if(PLATFORM_MAIXCAM AND NOT CONFIG_OPENCV_COMPILE_FROM_SOURCE AND NOT CONFIG_COMPONENTS_COMPILE_FROM_SOURCE) +if((PLATFORM_MAIXCAM OR PLATFORM_MAIXCAM2) AND NOT CONFIG_OPENCV_COMPILE_FROM_SOURCE AND NOT CONFIG_COMPONENTS_COMPILE_FROM_SOURCE) if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/opencv-mobile") list(APPEND ADD_INCLUDE "opencv-mobile/include/opencv4") list(APPEND ADD_STATIC_LIB @@ -32,10 +32,14 @@ if(PLATFORM_MAIXCAM AND NOT CONFIG_OPENCV_COMPILE_FROM_SOURCE AND NOT CONFIG_COM list(APPEND ADD_REQUIREMENTS pthread dl) list(APPEND ADD_DEFINITIONS -fopenmp) else() - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/opencv4_lib_maixcam") - set(opencv_lib_dir "${CMAKE_CURRENT_LIST_DIR}/opencv4_lib_maixcam") - elseif(CONFIG_TOOLCHAIN_PATH MATCHES "musl") - set(opencv_lib_dir "${DL_EXTRACTED_PATH}/opencv/opencv4/opencv4_lib_maixcam_musl_4.9.0") + if (PLATFORM_MAIXCAM) + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/opencv4_lib_maixcam") + set(opencv_lib_dir "${CMAKE_CURRENT_LIST_DIR}/opencv4_lib_maixcam") + elseif(CONFIG_TOOLCHAIN_PATH MATCHES "musl") + set(opencv_lib_dir "${DL_EXTRACTED_PATH}/opencv/opencv4/opencv4_lib_maixcam_musl_4.9.0") + endif() + elseif(PLATFORM_MAIXCAM2) + set(opencv_lib_dir "${DL_EXTRACTED_PATH}/opencv/opencv4/opencv4_lib_maixcam2_glibc_4.9.0") endif() if(opencv_lib_dir) list(APPEND ADD_INCLUDE "${opencv_lib_dir}/include/opencv4") @@ -96,7 +100,7 @@ if(NOT ADD_INCLUDE) set(opencv_toolchain_file ${CMAKE_CURRENT_LIST_DIR}/toolchain_m2dock.cmake) elseif(PLATFORM_LINUX) set(opencv_toolchain_file ${CMAKE_CURRENT_LIST_DIR}/toolchain_linux.cmake) - elseif(PLATFORM_MAIXCAM) + elseif(PLATFORM_MAIXCAM OR PLATFORM_MAIXCAM2) set(opencv_toolchain_file ${CMAKE_CURRENT_LIST_DIR}/toolchain_linux_cross.cmake) else() message(FATAL_ERROR "No opencv toolchain config for this board, please edit to add opencv support for this board") diff --git a/components/3rd_party/opencv/component.py b/components/3rd_party/opencv/component.py index c6d719cc..3881b664 100644 --- a/components/3rd_party/opencv/component.py +++ b/components/3rd_party/opencv/component.py @@ -4,7 +4,7 @@ def add_file_downloads(confs : dict) -> list: @param confs kconfig vars, dict type @return list type, items is dict type ''' - if (not confs.get("PLATFORM_MAIXCAM", None)) or confs.get("CONFIG_COMPONENTS_COMPILE_FROM_SOURCE", None) or confs.get("CONFIG_OPENCV_COMPILE_FROM_SOURCE", None): + if not (bool(confs.get("PLATFORM_MAIXCAM", None)) or bool(confs.get('PLATFORM_MAIXCAM2', None))) or confs.get("CONFIG_COMPONENTS_COMPILE_FROM_SOURCE", None) or confs.get("CONFIG_OPENCV_COMPILE_FROM_SOURCE", None): # version = f"{confs['CONFIG_PYTHON_VERSION_MAJOR']}.{confs['CONFIG_PYTHON_VERSION_MINOR']}.{confs['CONFIG_PYTHON_VERSION_PATCH']}" version = "4.9.0" url = f"https://github.com/opencv/opencv/archive/{version}.zip" @@ -32,7 +32,7 @@ def add_file_downloads(confs : dict) -> list: 'rename': rename } ] - if confs.get("PLATFORM_MAIXCAM", None): + if confs.get("PLATFORM_MAIXCAM", None) or confs.get("PLATFORM_MAIXCAM2", None): version = "0.1.2a" url = f"https://github.com/opencv/ade/archive/v0.1.2a.zip" if version == "0.1.2a": @@ -91,34 +91,61 @@ def add_file_downloads(confs : dict) -> list: raise Exception("No opencv config for this board, please edit to add opencv support for this board") return files + elif confs.get("PLATFORM_MAIXCAM", None): + if "musl" not in confs["CONFIG_TOOLCHAIN_PATH"]: + return [] + # version = f"{confs['CONFIG_OMV_VERSION_MAJOR']}.{confs['CONFIG_OMV_VERSION_MINOR']}.{confs['CONFIG_OMV_VERSION_PATCH']}" + version = "4.9.0" + url = f"https://github.com/sipeed/MaixCDK/releases/download/v0.0.0/opencv4_lib_maixcam_musl_4.9.0.tar.xz" + if version == "4.9.0": + sha256sum = "c4553fe39212b271724d2bb9394e8944678f846f4e58a578da1db3013cbc3dcf" + else: + raise Exception(f"version {version} not support") + sites = ["https://github.com/sipeed/MaixCDK/releases/tag/v0.0.0"] + filename = f"opencv4_lib_maixcam_musl_4.9.0.tar.xz" + path = f"opencv/opencv4" + check_file = f'opencv4_lib_maixcam_musl_4.9.0' + rename = {} - if "musl" not in confs["CONFIG_TOOLCHAIN_PATH"]: - return [] - # version = f"{confs['CONFIG_OMV_VERSION_MAJOR']}.{confs['CONFIG_OMV_VERSION_MINOR']}.{confs['CONFIG_OMV_VERSION_PATCH']}" - version = "4.9.0" - url = f"https://github.com/sipeed/MaixCDK/releases/download/v0.0.0/opencv4_lib_maixcam_musl_4.9.0.tar.xz" - if version == "4.9.0": - sha256sum = "c4553fe39212b271724d2bb9394e8944678f846f4e58a578da1db3013cbc3dcf" - else: - raise Exception(f"version {version} not support") - sites = ["https://github.com/sipeed/MaixCDK/releases/tag/v0.0.0"] - filename = f"opencv4_lib_maixcam_musl_4.9.0.tar.xz" - path = f"opencv/opencv4" - check_file = f'opencv4_lib_maixcam_musl_4.9.0' - rename = {} - - return [ - { - 'url': f'{url}', - 'urls': [], - 'sites': sites, - 'sha256sum': sha256sum, - 'filename': filename, - 'path': path, - 'check_files': [ - check_file - ], - 'rename': rename - } - ] + return [ + { + 'url': f'{url}', + 'urls': [], + 'sites': sites, + 'sha256sum': sha256sum, + 'filename': filename, + 'path': path, + 'check_files': [ + check_file + ], + 'rename': rename + } + ] + elif confs.get("PLATFORM_MAIXCAM2", None): + # version = f"{confs['CONFIG_OMV_VERSION_MAJOR']}.{confs['CONFIG_OMV_VERSION_MINOR']}.{confs['CONFIG_OMV_VERSION_PATCH']}" + version = "4.9.0" + url = f"https://github.com/sipeed/MaixCDK/releases/download/v0.0.0/opencv4_lib_maixcam2_glibc_4.9.0.tar.xz" + if version == "4.9.0": + sha256sum = "42e2827b632cdfcd168e4a3d77414ce57e0d9c3398893ac899635124d1579a53" + else: + raise Exception(f"version {version} not support") + sites = ["https://github.com/sipeed/MaixCDK/releases/tag/v0.0.0"] + filename = f"opencv4_lib_maixcam2_glibc_4.9.0.tar.xz" + path = f"opencv/opencv4" + check_file = f'opencv4_lib_maixcam2_glibc_4.9.0' + rename = {} + return [ + { + 'url': f'{url}', + 'urls': [], + 'sites': sites, + 'sha256sum': sha256sum, + 'filename': filename, + 'path': path, + 'check_files': [ + check_file + ], + 'rename': rename + } + ] diff --git a/components/3rd_party/sdl/CMakeLists.txt b/components/3rd_party/sdl/CMakeLists.txt index b37b4196..e9e66975 100644 --- a/components/3rd_party/sdl/CMakeLists.txt +++ b/components/3rd_party/sdl/CMakeLists.txt @@ -95,6 +95,11 @@ if(NOT ADD_INCLUDE) string(LENGTH "${CONFIG_TOOLCHAIN_PREFIX}" toolchain_prefix_len) math(EXPR toolchain_prefix_len "${toolchain_prefix_len} - 1") string(SUBSTRING "${CONFIG_TOOLCHAIN_PREFIX}" 0 ${toolchain_prefix_len} host_prefix) + elseif(PLATFORM_MAIXCAM2) + set(arch "aarch64") + string(LENGTH "${CONFIG_TOOLCHAIN_PREFIX}" toolchain_prefix_len) + math(EXPR toolchain_prefix_len "${toolchain_prefix_len} - 1") + string(SUBSTRING "${CONFIG_TOOLCHAIN_PREFIX}" 0 ${toolchain_prefix_len} host_prefix) else() message(FATAL_ERROR "please edit here to add support for this board") endif() diff --git a/components/peripheral/CMakeLists.txt b/components/peripheral/CMakeLists.txt index 6dcd3515..1b9df5cc 100755 --- a/components/peripheral/CMakeLists.txt +++ b/components/peripheral/CMakeLists.txt @@ -19,7 +19,7 @@ append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_S if (PLATFORM_LINUX) append_srcs_dir(ADD_SRCS "port/linux_common") append_srcs_dir(ADD_SRCS "port/linux") -elseif (PLATFORM_MAIXCAM) +elseif (PLATFORM_MAIXCAM OR PLATFORM_MAIXCAM2) append_srcs_dir(ADD_SRCS "port/linux_common") append_srcs_dir(ADD_SRCS "port/maixcam") endif() diff --git a/components/vision/CMakeLists.txt b/components/vision/CMakeLists.txt index 7448467e..ba78314f 100644 --- a/components/vision/CMakeLists.txt +++ b/components/vision/CMakeLists.txt @@ -9,6 +9,9 @@ if(PLATFORM_LINUX) elseif(PLATFORM_MAIXCAM) list(APPEND ADD_PRIVATE_INCLUDE "port/maixcam") list(APPEND ADD_PRIVATE_INCLUDE "port/linux_common") +elseif(PLATFORM_MAIXCAM2) + list(APPEND ADD_PRIVATE_INCLUDE "port/maixcam2") + list(APPEND ADD_PRIVATE_INCLUDE "port/linux_common") endif() list(APPEND ADD_PRIVATE_INCLUDE "include_private") ############################################### @@ -28,6 +31,9 @@ if(PLATFORM_LINUX) elseif(PLATFORM_MAIXCAM) append_srcs_dir(ADD_SRCS "port/maixcam") append_srcs_dir(ADD_SRCS "port/linux_common") + elseif(PLATFORM_MAIXCAM2) + append_srcs_dir(ADD_SRCS "port/maixcam2") + append_srcs_dir(ADD_SRCS "port/linux_common") endif() # list(REMOVE_ITEM COMPONENT_SRCS "src/test.c") # set(ADD_ASM_SRCS "src/asm.S") @@ -48,6 +54,8 @@ elseif(PLATFORM_MAIXCAM) set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,--start-group vision/libvision.a -lmaixcam_lib -Wl,--end-group" PARENT_SCOPE) set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -Wl,--start-group vision/libvision.a -lmaixcam_lib -Wl,--end-group" PARENT_SCOPE) endif() +elseif(PLATFORM_MAIXCAM2) +list(APPEND ADD_REQUIREMENTS FFmpeg RtspServer) endif() list(APPEND ADD_REQUIREMENTS eigen voice) ############################################### diff --git a/components/vision/port/maixcam2/maix_camera_maixcam2.cpp b/components/vision/port/maixcam2/maix_camera_maixcam2.cpp new file mode 100644 index 00000000..bac985be --- /dev/null +++ b/components/vision/port/maixcam2/maix_camera_maixcam2.cpp @@ -0,0 +1,656 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + + +#include "maix_camera.hpp" +#include "maix_basic.hpp" +#include "maix_i2c.hpp" +#include + +namespace maix::camera +{ + static bool set_regs_flag = false; + + std::vector list_devices() + { + log::warn("This device is not driven using device files!"); + return std::vector(); + } + + void set_regs_enable(bool enable) { + log::warn("This operation is not supported!"); + } + + err::Err Camera::show_colorbar(bool enable) + { + // only set variable now + // should control camera to show colorbar + _show_colorbar = enable; + return err::ERR_NONE; + } + + static void generate_colorbar(image::Image &img) + { + int width = img.width(); + int height = img.height(); + int step = width / 8; + int color_step = 255 / 7; + int color = 0; + uint8_t colors[8][3] = { + {255, 255, 255}, + {255, 0, 0}, + {255, 127, 0}, + {255, 255, 0}, + {0, 255, 0}, + {0, 0, 255}, + {143, 0, 255}, + {0, 0, 0}, + }; + for (int i = 0; i < 8; i++) + { + image::Color _color(colors[i][0], colors[i][1], colors[i][2], 0, image::FMT_RGB888); + img.draw_rect(i * step, 0, step, height, _color, -1); + color += color_step; + } + } + + typedef struct { + int dev; + int i2c_addr; + bool raw; + double fps; + int exptime_max; // unit:us + int exptime_min; + } camera_priv_t; + + Camera::Camera(int width, int height, image::Format format, const char *device, double fps, int buff_num, bool open, bool raw) + { + err::Err e; + err::check_bool_raise(_check_format(format), "Format not support"); + + _width = (width == -1) ? 640 : width; + _height = (height == -1) ? 480 : height; + _format = format; + _buff_num = buff_num; + _show_colorbar = false; + _open_set_regs = set_regs_flag; + _device = ""; + _last_read_us = time::ticks_us(); + _invert_flip = false; + _invert_mirror = false; + _is_opened = false; + + camera_priv_t *priv = (camera_priv_t *)malloc(sizeof(camera_priv_t)); + err::check_null_raise(priv, "camera_priv_t malloc error"); + memset(priv, 0, sizeof(camera_priv_t)); + priv->dev = 0; + priv->raw = raw; + priv->fps = fps; + _param = priv; + + + if (format == image::Format::FMT_RGB888 && _width * _height * 3 > 640 * 640 * 3) { + log::warn("Note that we do not recommend using large resolution RGB888 images, which can take up a lot of memory!\r\n"); + } + + // config fps + if (fps == -1 && _width <= 1280 && _height <= 720) { + _fps = 60; + } else if (fps == -1) { + _fps = 30; + } else { + _fps = fps; + } + + if ((_width > 1280 || _height > 720) && _fps > 30) { + log::warn("Current fps is too high, will be be updated to 30fps! Currently only supported up to 720p 60fps or 1440p 30fps.\r\n"); + _fps = 30; + } else if (_width <= 1280 && _height <= 720 && _fps > 60 && _fps != 80) { + log::warn("Currently only supports fixed 30,60 and 80fps in 720p configuration, current configuration will be updated to 80fps.\r\n"); + _fps = 80; + } else if (_width <= 1280 && _height <= 720 && _fps < 80 && _fps > 30 && _fps != 60) { + log::warn("Currently only supports fixed 30,60 and 80fps in 720p configuration, current configuration will be updated to 60fps.\r\n"); + _fps = 60; + } + + // open camera + if (open) { + e = this->open(_width, _height, _format, _fps, _buff_num); + err::check_raise(e, "camera open failed"); + } + } + + Camera::~Camera() + { + if (this->is_opened()) { + this->close(); + } + + if (_param) { + free(_param); + } + } + + int Camera::get_ch_nums() + { + return 2; + } + + int Camera::get_channel() + { + return this->_ch; + } + + bool Camera::_check_format(image::Format format) { + if (format == image::FMT_RGB888 || format == image::FMT_BGR888 + || format == image::FMT_RGBA8888 || format == image::FMT_BGRA8888 + || format == image::FMT_YVU420SP || format == image::FMT_GRAYSCALE) { + return true; + } else { + return false; + } + } + + static bool _get_board_config_path(char *path, int path_size) + { + if (fs::exists("/boot/board")) { + snprintf(path, path_size, "/boot/board"); + return true; + } + return false; + } + + static int _get_mclk_id(void) { + char path[64]; + int mclk_id = 0; + + err::check_bool_raise(_get_board_config_path(path, sizeof(path)), "Can't find board config file"); + + std::string mclk_id_str; + auto device_configs = sys::device_configs(); + auto it = device_configs.find("cam_mclk"); + if (it != device_configs.end()) { + mclk_id_str = it->second; + } + if (!mclk_id_str.empty()) { + mclk_id = atoi(mclk_id_str.c_str()); + } else { + std::string board_id = sys::device_id(); + if (board_id == "maixcam_pro") { + mclk_id = 0; + } else { + mclk_id = 1; + } + } + return mclk_id; + } + + static std::vector _get_lane_id_from_board_file() { + std::vector lane_id; + auto device_configs = sys::device_configs(); + auto it = device_configs.find("lane_id"); + if (it != device_configs.end()) { + auto lane_id_str = it->second; + std::string item; + std::stringstream ss(lane_id_str); + while (std::getline(ss, item, ',')) { + lane_id.push_back(std::stoi(item)); + } + } + + return lane_id; + } + + static std::vector _get_pn_swap_from_board_file() { + std::vector pn_swap; + auto device_configs = sys::device_configs(); + auto it = device_configs.find("pn_swap"); + if (it != device_configs.end()) { + auto pn_swap_str = it->second; + std::string item; + std::stringstream ss(pn_swap_str); + while (std::getline(ss, item, ',')) { + pn_swap.push_back(std::stoi(item)); + } + } + + return pn_swap; + } + + static std::vector _get_cam_flip_mirror(void) { + char path[64]; + bool flip = 0, mirror = 0; + bool flip_is_found = false, mirror_is_found = false; + + err::check_bool_raise(_get_board_config_path(path, sizeof(path)), "Can't find board config file"); + + std::string flip_str; + std::string mirror_str; + auto device_configs = sys::device_configs(); + auto it = device_configs.find("cam_flip"); + if (it != device_configs.end()) { + flip_str = it->second; + flip_is_found = true; + } + auto it2 = device_configs.find("cam_mirror"); + if (it2 != device_configs.end()) { + mirror_str = it2->second; + mirror_is_found = true; + } + + std::string board_id = sys::device_id(); + // log::info("cam flip=%s, cam mirror=%s", flip_str, mirror_str); + if (flip_is_found && !flip_str.empty()) { + flip = atoi(flip_str.c_str()); + } else { + if (board_id == "maixcam_pro") { + flip = true; + } else { + flip = false; + } + } + + if (mirror_is_found && !mirror_str.empty()) { + mirror = atoi(mirror_str.c_str()); + } else { + std::string board_id = sys::device_id(); + if (board_id == "maixcam_pro") { + mirror = true; + } else { + mirror = false; + } + } + + return {flip, mirror}; + } + + static std::pair _get_sensor_name(void) + { + char name[30]; + peripheral::i2c::I2C i2c_obj(4, peripheral::i2c::Mode::MASTER); + + std::vector addr_list = i2c_obj.scan(); + for (size_t i = 0; i < addr_list.size(); i++) { + // log::info("i2c4 addr: 0x%02x", addr_list[i]); + switch (addr_list[i]) { + case 0x29: + // log::info("find gcore_gc4653, addr %#x", addr_list[i]); + snprintf(name, sizeof(name), "gcore_gc4653"); + return {true, name}; + case 0x30: + // log::info("find sms_sc035gs, addr %#x", addr_list[i]); + snprintf(name, sizeof(name), "sms_sc035gs"); + return {true, name}; + case 0x2b: + // log::info("find lt6911, addr %#x", addr_list[i]); + snprintf(name, sizeof(name), "lt6911"); + return {true, name}; + case 0x36: + // log::info("find ov_os04a10, addr %#x", addr_list[i]); + snprintf(name, sizeof(name), "ov_os04a10"); + return {true, name}; + case 0x37: + // log::info("find ov_os04a10, addr %#x", addr_list[i]); + snprintf(name, sizeof(name), "gcore_gc02m1"); + return {true, name}; + case 0x48:// fall through + case 0x3c: + // log::info("find ov_ov2685, addr %#x", addr_list[i]); + snprintf(name, sizeof(name), "ov_ov2685"); + return {true, name}; + default: break; + } + } + + // log::info("sensor address not found , use gcore_gc4653\n" ); + snprintf(name, sizeof(name), "gcore_gc4653"); + return {false, name}; + } + + std::string get_device_name() + { + std::string device_name; + std::pair res = _get_sensor_name(); + if (res.first == false) { + device_name = ""; + return device_name; + } else { + device_name = res.second; + } + return device_name; + } + + + static int _is_module_in_use(const char *module_name) { + FILE *fp; + char buffer[256]; + + fp = fopen("/proc/modules", "r"); + if (fp == NULL) { + perror("fopen"); + return -1; + } + + while (fgets(buffer, sizeof(buffer), fp) != NULL) { + char mod_name[256]; + int usage_count; + + sscanf(buffer, "%255s %*s %d", mod_name, &usage_count); + + if (strcmp(mod_name, module_name) == 0) { + fclose(fp); + return usage_count > 0; + } + } + + fclose(fp); + return 0; + } + + err::Err Camera::open(int width, int height, image::Format format, double fps, int buff_num) + { + // int width_tmp = (width == -1) ? _width : width; + // int height_tmp = (height == -1) ? _height : height; + // image::Format format_tmp = (format == image::FMT_INVALID) ? _format : format; + // double fps_tmp = (fps == -1) ? _fps : fps; + // int buff_num_tmp =( buff_num == -1) ? _buff_num : buff_num; + // camera_priv_t *priv = (camera_priv_t *)_param; + + // check format + // err::check_bool_raise(_check_format(format_tmp), "Format not support"); + + _is_opened = true; + return err::ERR_NONE; + } + + void Camera::close() + { + if (this->is_closed()) + return; + } + + camera::Camera *Camera::add_channel(int width, int height, image::Format format, double fps, int buff_num, bool open) + { + err::check_bool_raise(_check_format(format), "Format not support"); + + int width_tmp = (width == -1) ? _width : width; + int height_tmp = (height == -1) ? _height : height; + image::Format format_tmp = (format == image::Format::FMT_INVALID) ? _format : format; + double fps_tmp = (fps == -1) ? _fps : fps; + int buff_num_tmp = buff_num == -1 ? _buff_num : buff_num; + + Camera *cam = new Camera(width_tmp, height_tmp, format_tmp, _device.c_str(), fps_tmp, buff_num_tmp, true); + return cam; + } + + bool Camera::is_opened() + { + return _is_opened; + } + + image::Image *Camera::read(void *buff, size_t buff_size, bool block, int block_ms) + { + if (!this->is_opened()) { + err::Err e = open(_width, _height, _format, _fps, _buff_num); + err::check_raise(e, "open camera failed"); + } + return nullptr; + } + + // static image::Format _get_raw_format_with_size(int w, int h, int total_size, BAYER_FORMAT_E bayer_format) { + // image::Format format = image::FMT_INVALID; + // int size = w * h; + // if (total_size == size * 0.75) { + // switch (bayer_format) { + // case BAYER_FORMAT_BG: + // format = image::FMT_BGGR6; + // break; + // case BAYER_FORMAT_GB: + // format = image::FMT_GBRG6; + // break; + // case BAYER_FORMAT_GR: + // format = image::FMT_GRBG6; + // break; + // case BAYER_FORMAT_RG: + // format = image::FMT_RGGB6; + // break; + // default: + // return image::FMT_INVALID; + // } + // } else if (total_size == size * 1) { + // switch (bayer_format) { + // case BAYER_FORMAT_BG: + // format = image::FMT_BGGR8; + // break; + // case BAYER_FORMAT_GB: + // format = image::FMT_GBRG8; + // break; + // case BAYER_FORMAT_GR: + // format = image::FMT_GRBG8; + // break; + // case BAYER_FORMAT_RG: + // format = image::FMT_RGGB8; + // break; + // default: + // return image::FMT_INVALID; + // } + // } else if (total_size == size * 1.25) { + // switch (bayer_format) { + // case BAYER_FORMAT_BG: + // format = image::FMT_BGGR10; + // break; + // case BAYER_FORMAT_GB: + // format = image::FMT_GBRG10; + // break; + // case BAYER_FORMAT_GR: + // format = image::FMT_GRBG10; + // break; + // case BAYER_FORMAT_RG: + // format = image::FMT_RGGB10; + // break; + // default: + // return image::FMT_INVALID; + // } + // } else if (total_size == size * 1.5) { + // switch (bayer_format) { + // case BAYER_FORMAT_BG: + // format = image::FMT_BGGR12; + // break; + // case BAYER_FORMAT_GB: + // format = image::FMT_GBRG12; + // break; + // case BAYER_FORMAT_GR: + // format = image::FMT_GRBG12; + // break; + // case BAYER_FORMAT_RG: + // format = image::FMT_RGGB12; + // break; + // default: + // return image::FMT_INVALID; + // } + // } else { + // return image::FMT_INVALID; + // } + + // return format; + // } + + image::Image *Camera::read_raw() { + if (!this->is_opened()) { + err::Err e = open(_width, _height, _format, _fps, _buff_num); + err::check_raise(e, "open camera failed"); + } + + camera_priv_t *priv = (camera_priv_t *)this->_param; + if (!priv->raw) { + err::check_raise(err::ERR_NOT_READY, "you need to enable the raw parameter when constructing the Camera object."); + } + + return nullptr; + } + + void Camera::clear_buff() + { + log::warn("This operation is not supported!"); + } + + void Camera::skip_frames(int num) + { + for(int i = 0; i < num; i++) + { + image::Image *img = this->read(); + delete img; + } + } + + err::Err Camera::set_resolution(int width, int height) + { + + return err::ERR_NONE; + } + + static void _config_extern_register_of_os04a10(int exptime_ms) + { + char cmd[128]; + if (exptime_ms >= 1 * 1000) { + double scale = 1.587; // (0x0c68 - 0x0635) / 1000 + double exp_time_ms = exptime_ms; + uint64_t reg_val = (uint16_t)((exp_time_ms - 1000) * scale) + 0x0635; + reg_val = reg_val > 0xffff ? 0xffff : reg_val; + snprintf(cmd, sizeof(cmd), "i2ctransfer -fy 4 w4@0x36 0x38 0x0c %#.2x %#.2x", (uint8_t)((reg_val >> 8) & 0xff), (uint8_t)(reg_val & 0xff)); + system(cmd); + } else { + uint64_t reg_val = 0x05cc; + snprintf(cmd, sizeof(cmd), "i2ctransfer -fy 4 w4@0x36 0x38 0x0c %#.2x %#.2x", (uint8_t)((reg_val >> 8) & 0xff), (uint8_t)(reg_val & 0xff)); + system(cmd); + } + } + + err::Err Camera::set_fps(double fps) { + + return err::ERR_NONE; + } + + int Camera::exposure(int value) { + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + + return 0; + } + + int Camera::gain(int value) { + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + + return 0; + } + + int Camera::hmirror(int value) { + + return 0; + } + + int Camera::vflip(int value) { + + return 0; + } + + int Camera::luma(int value) { + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + return 0; + } + + int Camera::constrast(int value) { + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + return 0; + } + + int Camera::saturation(int value) { + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + return 0; + } + + int Camera::awb_mode(int value) { + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + return 0; + } + + int Camera::set_awb(int value) { + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + + if (value == 0) { + value = 1; + } else if (value > 0) { + value = 0; + } + + return this->awb_mode(value) == 0 ? 1 : 0; + } + + int Camera::exp_mode(int value) { + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + + return value; + } + + err::Err Camera::set_windowing(std::vector roi) { + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + + return err::ERR_NONE; + } + + std::vector Camera::get_sensor_size() { + camera_priv_t *priv = (camera_priv_t *)_param; + + return {640, 480}; + } + + err::Err Camera::write_reg(int addr, int data, int bit_width) + { + camera_priv_t *priv = (camera_priv_t *)_param; + (void)bit_width; + peripheral::i2c::I2C i2c_obj(4, peripheral::i2c::Mode::MASTER); + uint8_t temp[1]; + temp[0] = (uint8_t)data; + i2c_obj.writeto_mem(priv->i2c_addr, addr, temp, sizeof(temp), 16); + return err::ERR_NONE; + } + + int Camera::read_reg(int addr, int bit_width) + { + camera_priv_t *priv = (camera_priv_t *)_param; + (void)bit_width; + peripheral::i2c::I2C i2c_obj(4, peripheral::i2c::Mode::MASTER); + Bytes *data = i2c_obj.readfrom_mem(priv->i2c_addr, addr, 1, 16); + int out = -1;log::info("addr:%#x", priv->i2c_addr); + if (data) { + if (data->size() > 0) { + out = data->data[0]; + } + delete data; + } + + return out; + } +} + diff --git a/components/vision/port/maixcam2/maix_display_maixcam2.hpp b/components/vision/port/maixcam2/maix_display_maixcam2.hpp new file mode 100644 index 00000000..68b79bf4 --- /dev/null +++ b/components/vision/port/maixcam2/maix_display_maixcam2.hpp @@ -0,0 +1,276 @@ +/** + * @author lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + + +#pragma once + +#include "maix_display_base.hpp" +#include "maix_thread.hpp" +#include "maix_image.hpp" +#include "maix_time.hpp" +#include +#include "maix_pwm.hpp" +#include +#include +#include +#include +#include +#include "maix_fs.hpp" + +using namespace maix::peripheral; + +namespace maix::display +{ + enum class PanelType { + NORMAL, + LT9611, + UNKNOWN, + }; + inline static PanelType __g_panel_type = PanelType::UNKNOWN; + + __attribute__((unused)) static int _get_vo_max_size(int *width, int *height, int rotate) + { + int w = 0, h = 0; + + if (rotate) { + *width = h; + *height = w; + } else { + *width = w; + *height = h; + } + return 0; + } + + + static void _get_disp_configs(bool &flip, bool &mirror, float &max_backlight) { + std::string flip_str; + bool flip_is_found = false; + + auto device_configs = sys::device_configs(); + auto it = device_configs.find("disp_flip"); + if (it != device_configs.end()) { + flip_str = it->second; + flip_is_found = true; + } + auto it2 = device_configs.find("disp_mirror"); + if (it2 != device_configs.end()) { + if (it2->second.size() > 0) + mirror = atoi(it2->second.c_str()); + } + auto it3 = device_configs.find("disp_max_backlight"); + if (it3 != device_configs.end()) { + if (it3->second.size() > 0) + max_backlight = atof(it3->second.c_str()); + } + + if (flip_is_found && flip_str.size() > 0) { + flip = atoi(flip_str.c_str()); + } else { + std::string board_id = sys::device_id(); + if (board_id == "maixcam_pro") { + flip = true; + } + } + + // log::info("disp config flip: %d, mirror: %d, max_backlight: %.1f", flip, mirror, max_backlight); + } + + + class DisplayAx final : public DisplayBase + { + public: + DisplayAx(const string &device, int width, int height, image::Format format) + { + err::check_bool_raise(!_get_vo_max_size(&_max_width, &_max_height, 1), "get vo max size failed"); + width = width <= 0 ? _max_width : width; + height = height <= 0 ? _max_height : height; + this->_width = width > _max_width ? _max_width : width; + this->_height = height > _max_height ? _max_height : height; + this->_format = format; + this->_opened = false; + this->_format = format; + this->_invert_flip = false; + this->_invert_mirror = false; + this->_layer = 0; // layer 0 means vedio layer + err::check_bool_raise(_format == image::FMT_RGB888 + || _format == image::FMT_YVU420SP + || _format == image::FMT_BGRA8888, "Format not support"); + + int pwm_id = 10; + _bl_pwm = new pwm::PWM(pwm_id, 100000, 20); + } + + DisplayAx(int layer, int width, int height, image::Format format) + { + err::check_bool_raise(!_get_vo_max_size(&_max_width, &_max_height, 1), "get vo max size failed"); + width = width <= 0 ? _max_width : width; + height = height <= 0 ? _max_height : height; + this->_width = width > _max_width ? _max_width : width; + this->_height = height > _max_height ? _max_height : height; + this->_format = format; + this->_opened = false; + this->_format = format; + this->_invert_flip = false; + this->_invert_mirror = false; + this->_max_backlight = 50.0; + this->_layer = layer; // layer 0 means vedio layer + // layer 1 means osd layer + err::check_bool_raise(_format == image::FMT_BGRA8888, "Format not support"); + + int pwm_id = 10; + _bl_pwm = new pwm::PWM(pwm_id, 100000, 20); + } + + ~DisplayAx() + { + if(_bl_pwm && this->_layer == 0) // _layer = 0, means video layer + { + delete _bl_pwm; + } + } + + int width() + { + return this->_width; + } + + int height() + { + return this->_height; + } + + std::vector size() + { + return {this->_width, this->_height}; + } + + image::Format format() + { + return this->_format; + } + + err::Err open(int width, int height, image::Format format) + { + width = width > _max_width ? _max_width : width; + height = height > _max_height ? _max_height : height; + if(this->_opened) + { + return err::ERR_NONE; + } + + this->_opened = true; + return err::ERR_NONE; + } + + err::Err close() + { + if (!this->_opened) + return err::ERR_NONE; + + this->_opened = false; + return err::ERR_NONE; + } + + display::DisplayAx *add_channel(int width, int height, image::Format format) + { + int new_width = 0; + int new_height = 0; + image::Format new_format = format; + if (width == -1) { + new_width = this->_width; + } else { + new_width = width > this->_width ? this->_width : width; + } + if (height == -1) { + new_height = this->_height; + } else { + new_height = height > this->_height ? this->_height : height; + } + + _format = new_format; + DisplayAx *disp = new DisplayAx(1, new_width, new_height, new_format); + return disp; + } + + bool is_opened() + { + return this->_opened; + } + + err::Err show(image::Image &img, image::Fit fit) + { + err::check_bool_raise((img.width() % 2 == 0 && img.height() % 2 == 0), "Image width and height must be a multiple of 2."); + int format = img.format(); + + return err::ERR_NONE; + } + + void set_backlight(float value) + { + _bl_pwm->duty(value * _max_backlight / 100.0); + _bl_pwm->disable(); + if(value == 0) + return; + _bl_pwm->enable(); + } + + float get_backlight() + { + return _bl_pwm->duty() / _max_backlight * 100; + } + + int get_ch_nums() + { + return 2; + } + + err::Err set_hmirror(bool en) { + en = _invert_mirror ? !en : en; + + bool need_open = false; + if (this->_opened) { + this->close(); + need_open = true; + } + + if (need_open) { + err::check_raise(this->open(this->_width, this->_height, this->_format), "Open failed"); + } + return err::ERR_NONE; + } + + err::Err set_vflip(bool en) { + en = _invert_flip ? !en : en; + + bool need_open = false; + if (this->_opened) { + this->close(); + need_open = true; + } + + if (need_open) { + err::check_raise(this->open(this->_width, this->_height, this->_format), "Open failed"); + } + return err::ERR_NONE; + } + + private: + int _width; + int _height; + int _max_width; + int _max_height; + image::Format _format; + int _layer; + int _ch; + bool _opened; + bool _invert_flip; + bool _invert_mirror; + float _max_backlight; + pwm::PWM *_bl_pwm; + }; +} diff --git a/components/vision/port/maixcam2/maix_jpg_stream.cpp b/components/vision/port/maixcam2/maix_jpg_stream.cpp new file mode 100644 index 00000000..14721e6e --- /dev/null +++ b/components/vision/port/maixcam2/maix_jpg_stream.cpp @@ -0,0 +1,620 @@ +/** + * @author lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2024.5.17: Add framework, create this file. + */ + +#include "maix_jpg_stream.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BOUNDARY "frame" + +static const char *default_index_str = +"\n" +"\n" +"

JPG Stream

\n" +"\n" +"\n" +""; + +typedef struct { + int socket; + int thread; + int idx; + uint8_t is_inited; + uint8_t is_running; + uint8_t try_update_buffer; + uint8_t try_exit; + pthread_mutex_t lock; +} client_info_t; + +typedef struct { + void *p; + size_t size; + uint8_t valid; +} buffer_info_t; + +typedef struct { + int socket_fd; + struct sockaddr_in address; + int addrlen; + + pthread_mutex_t lock; + int thread; + uint8_t thread_is_started; + uint8_t try_exit_thread; + + int client_cnt; + int client_max; + client_info_t *client; + + buffer_info_t buffer[2]; + int new_buffer_idx; + + char *index_str; +} priv_t; + +priv_t priv; + + +static char *get_ip_str(char *hostname) { + struct addrinfo hints, *res, *p; + int status; + char *ipstr = (char *)malloc(INET6_ADDRSTRLEN); + if (!ipstr) return NULL; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if ((status = getaddrinfo(hostname, NULL, &hints, &res)) != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); + return NULL; + } + + for (p = res; p != NULL; p = p->ai_next) { + void *addr; + if (p->ai_family == AF_INET) { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr; + addr = &(ipv4->sin_addr); + } else { // IPv6 + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr; + addr = &(ipv6->sin6_addr); + } + inet_ntop(p->ai_family, addr, ipstr, INET6_ADDRSTRLEN); + } + + freeaddrinfo(res); + + return ipstr; +} + +static int socket_is_connected(int sockfd) { + char buffer; + int result = recv(sockfd, &buffer, 1, MSG_PEEK | MSG_DONTWAIT); + if (result == 0) { + return 0; + } else if (result < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 1; + } else { + return 0; + } + } + return 1; +} + +static size_t socket_write(int socket, void *data, size_t size) +{ + if (!socket_is_connected(socket)) { + return -1; + } + return write(socket, data, size); +} + +static size_t socket_read(int socket, void *data, size_t size) +{ + return read(socket, data, size); +} + +void send_response(int client_socket, char *header, char *body) { + socket_write(client_socket, header, strlen(header)); + socket_write(client_socket, body, strlen(body)); +} + +static int find_unused_idx(client_info_t *client, int client_max) +{ + for (int i = 0; i < client_max; i ++) { + client_info_t *c = (client_info_t *)&client[i]; + if (!c->is_inited) { + return i; + } + } + return -1; +} + +static int notify_client_update_buffer() +{ + for (int i = 0; i < priv.client_max; i ++) { + client_info_t *c = (client_info_t *)&priv.client[i]; + if (c->is_inited) { + pthread_mutex_lock(&c->lock); + c->try_update_buffer = 1; + pthread_mutex_unlock(&c->lock); + } + } + + return 0; +} + +static int get_global_buffer_not_safe(void **buffer, size_t *size) +{ + buffer_info_t *info = (buffer_info_t *)&priv.buffer[priv.new_buffer_idx]; + if (info->valid) { + if (!info->p) return -1; + + if (buffer) *buffer = info->p; + if (size) *size = info->size; + return 0; + } + + return -1; +} + +static void on_stream(client_info_t *client) { + int client_socket = client->socket; + char header[1024]; + sprintf(header, "HTTP/1.1 200 OK\r\n"); + sprintf(header + strlen(header), "Content-Type: multipart/x-mixed-replace; boundary=%s\r\n", BOUNDARY); + sprintf(header + strlen(header), "\r\n"); + socket_write(client_socket, header, strlen(header)); + + int update = 0; + while (1) { + pthread_mutex_lock(&client->lock); + if (client->try_exit || !socket_is_connected(client->socket)) { + pthread_mutex_unlock(&client->lock); + break; + } + + if (client->try_update_buffer) { + update = 1; + client->try_update_buffer = 0; + } + pthread_mutex_unlock(&client->lock); + + size_t image_size; + char *image_data; + if (update) { + update = 0; + + pthread_mutex_lock(&priv.lock); + void *buffer; + size_t buffer_size; + if (0 != get_global_buffer_not_safe(&buffer, &buffer_size)) { + printf("get global buffer failed!\r\n"); + usleep(100*1000); + continue; + } + + image_size = buffer_size; + image_data = (char *)malloc(image_size); + if (!image_data) { + printf("malloc failed!\r\n"); + usleep(100*1000); + continue; + } + memcpy(image_data, buffer, image_size); + pthread_mutex_unlock(&priv.lock); + + sprintf(header, "--%s\r\n", BOUNDARY); + sprintf(header + strlen(header), "Content-Type: image/jpeg\r\n"); + sprintf(header + strlen(header), "Content-Length: %ld\r\n", image_size); + sprintf(header + strlen(header), "\r\n"); + socket_write(client_socket, header, strlen(header)); + socket_write(client_socket, image_data, image_size); + socket_write(client_socket, (char *)"\r\n", 2); + free(image_data); + } else { + usleep(10 * 1000); + } + } +} + +int http_jpeg_server_create(char *host, int port, int client_num) { + int server_fd; + struct sockaddr_in address; + int opt = 1; + int addrlen = sizeof(address); + + if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { + perror("socket failed"); + return -1; + } + + if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { + perror("setsockopt"); + close(server_fd); + return -1; + } + + address.sin_family = AF_INET; + if (host == 0 || strlen(host) == 0) { + address.sin_addr.s_addr = INADDR_ANY; + } else { + char *ip = (char *)get_ip_str(host); + if (!ip) { + printf("can not parse ip:%s\r\n", host); + close(server_fd); + return -1; + } + address.sin_addr.s_addr = inet_addr(ip); + free(ip); + } + address.sin_port = htons(port); + + if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { + perror("bind failed"); + close(server_fd); + return -1; + } + + if (listen(server_fd, 3) < 0) { + perror("listen"); + close(server_fd); + return -1; + } + + if (0 != pthread_mutex_init(&priv.lock, NULL)) { + printf("create lock failed!\r\n"); + close(server_fd); + return -1; + } + + priv.socket_fd = server_fd; + memcpy(&priv.address, &address, sizeof(struct sockaddr_in)); + priv.addrlen = addrlen; + priv.client_max = client_num; + priv.client = (client_info_t *)malloc(priv.client_max * sizeof(client_info_t)); + if (priv.client == NULL) { + printf("create client info failed!\r\n"); + return -1; + } + memset(priv.client, 0, priv.client_max * sizeof(client_info_t)); + memset(priv.buffer, 0, sizeof(priv.buffer)); + priv.client_cnt = 0; + priv.index_str = (char *)default_index_str; + return 0; +} + +static void *client_thread_handle(void *param) +{ + int res; + pthread_mutex_lock(&priv.lock); + client_info_t *client = (client_info_t *)param; + pthread_mutex_t *lock = &client->lock; + pthread_mutex_unlock(&priv.lock); + + pthread_mutex_lock(lock); + int client_socket = client->socket; + client->is_running = 1; + pthread_mutex_unlock(lock); + + fd_set readfds; + while (1) { + pthread_mutex_lock(lock); + if (client->try_exit || !socket_is_connected(client->socket)) { + pthread_mutex_unlock(lock); + break; + } + pthread_mutex_unlock(lock); + + FD_ZERO(&readfds); + FD_SET(client_socket, &readfds); + + char buffer[1024] = {0}; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(client_socket, &fds); + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100 * 1000; + int ret = select(client_socket + 1, &fds, NULL, NULL, &tv); + if (ret == -1) { + printf("select error! ret:%d\r\n", ret); + continue; + } else if (ret == 0) { + continue; // timeout + } else { + res = socket_read(client_socket, buffer, 1024); + if (res < 0) { + printf("socket_read failed! res: %d\r\n", res); + break; + } + } + + if (strstr(buffer, "GET /stream") != NULL) { + on_stream(client); + } else { + char *response = priv.index_str; + socket_write(client_socket, (char *)"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n", strlen("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")); + socket_write(client_socket, response, strlen(response)); + } + } + + pthread_mutex_lock(&priv.lock); + if (client->is_inited) { + close(client->socket); + client->socket = -1; + client->thread = -1; // will exit + client->is_running = 0; + client->is_inited = 0; + priv.client_cnt --; + pthread_mutex_destroy(&client->lock); + } + pthread_mutex_unlock(&priv.lock); + pthread_exit(NULL); +} + +static void *thread_handle(void *param) +{ + int res; + priv_t *priv = (priv_t *)param; + int server_fd; + + pthread_mutex_lock(&priv->lock); + server_fd = priv->socket_fd; + pthread_mutex_unlock(&priv->lock); + while (1) { + pthread_mutex_lock(&priv->lock); + if (priv->try_exit_thread) { + pthread_mutex_unlock(&priv->lock); + break; + } + pthread_mutex_unlock(&priv->lock); + + int client_socket; + if ((client_socket = accept(server_fd, (struct sockaddr *)&priv->address, (socklen_t*)&priv->addrlen)) < 0) { + perror("accept"); + sleep(1); + continue; + } + + pthread_mutex_lock(&priv->lock); + int idx = find_unused_idx(priv->client, priv->client_max); + if (idx < 0) { + printf("can not create more client! curr:%d max:%d\r\n", priv->client_cnt, priv->client_max); + continue;; + } + + client_info_t *client = (client_info_t *)&priv->client[idx]; + client->socket = client_socket; + client->try_exit = 0; + client->idx = idx; + if (0 != pthread_mutex_init(&client->lock, NULL)) { + printf("create client lock failed!\r\n"); + continue; + } + + if (0 != (res = pthread_create((pthread_t *)&client->thread, NULL, client_thread_handle, client))) { + fprintf(stderr, "create client thread error:%s\n", strerror(res)); + pthread_mutex_destroy(&client->lock); + continue; + } + + // if (0 != (res = pthread_detach(&client->thread))) { + // fprintf(stderr, "client thread detach error:%s\n", strerror(res)); + // pthread_mutex_destroy(&client->lock); + // continue; + // } + client->is_inited = 1; + priv->client_cnt ++; + pthread_mutex_unlock(&priv->lock); + } + + return NULL; +} + +static int http_jpeg_server_set_index_str(char *index) +{ + if (priv.index_str != default_index_str) { + if (priv.index_str) { + free(priv.index_str); + priv.index_str = NULL; + } + } + + priv.index_str = (char *)malloc(strlen(index) + 1); + if (!priv.index_str) { + printf("malloc failed!\r\n"); + return -1; + } + strcpy(priv.index_str, index); + return 0; +} + +static int http_jpeg_server_start() { + pthread_t thread; + + pthread_mutex_lock(&priv.lock); + if (priv.thread_is_started) { + return 0; + } + + priv.try_exit_thread = 0; + if (0 != pthread_create(&thread, NULL, thread_handle, &priv)) { + printf("create thread failed!\r\n"); + return -1; + } + + priv.thread = thread; + priv.thread_is_started = 1; + pthread_mutex_unlock(&priv.lock); + + return 0; +} + +static int http_jpeg_server_stop() { + pthread_mutex_lock(&priv.lock); + if (!priv.thread_is_started) { + return 0; + } + priv.try_exit_thread = 1; + pthread_mutex_unlock(&priv.lock); + + for (int i = 0; i < priv.client_max; i ++) { + client_info_t *c = (client_info_t *)&priv.client[i]; + if (c->is_inited) { + pthread_mutex_lock(&c->lock); + c->try_exit = 1; + pthread_mutex_unlock(&c->lock); + } + } + + // pthread_join(priv.thread, NULL); + priv.thread_is_started = 0; + return 0; +} + + +static int http_jpeg_server_destory() { + http_jpeg_server_stop(); + + if (priv.client) { + free(priv.client); + priv.client = NULL; + } + + if (priv.socket_fd > 0) { + close(priv.socket_fd); + priv.socket_fd = -1; + } + + pthread_mutex_destroy(&priv.lock); + + return 0; +} + +static int http_jpeg_server_send(void *data, size_t size) +{ + pthread_mutex_lock(&priv.lock); + int next_buffer_idx = !priv.new_buffer_idx; + if (priv.buffer[next_buffer_idx].p) { + free(priv.buffer[next_buffer_idx].p); + priv.buffer[next_buffer_idx].p = NULL; + } + priv.buffer[next_buffer_idx].p = malloc(size); + if (priv.buffer[next_buffer_idx].p == NULL) { + printf("create new buffer failed!\r\n"); + return -1; + } + memcpy(priv.buffer[next_buffer_idx].p, data, size); + priv.buffer[next_buffer_idx].size = size; + priv.buffer[next_buffer_idx].valid = 1; + priv.new_buffer_idx = next_buffer_idx; + pthread_mutex_unlock(&priv.lock); + + notify_client_update_buffer(); + + return 0; +} + +namespace maix::http +{ + JpegStreamer::JpegStreamer(std::string host, int port, int client_number) { + int res = 0; + if (host.size() == 0) { + host = "0.0.0.0"; + } + + if (0 != (res = http_jpeg_server_create((char *)host.c_str(), port, client_number))) { + err::check_raise(err::ERR_RUNTIME, "http_jpeg_server_create failed!"); + } + + _host = host; + _port = port; + } + + JpegStreamer::~JpegStreamer() { + int res = 0; + if (0 != (res = http_jpeg_server_destory())) { + err::check_raise(err::ERR_RUNTIME, "http_jpeg_server_start failed!"); + } + } + + err::Err JpegStreamer::start() { + int res = 0; + if (0 != (res = http_jpeg_server_start())) { + log::error("http_jpeg_server_start failed! res:%d\r\n", res); + return err::ERR_RUNTIME; + } + + return err::ERR_NONE; + } + + err::Err JpegStreamer::stop() { + int res = 0; + if (0 != (res = http_jpeg_server_stop())) { + log::error("http_jpeg_server_stop failed! res:%d\r\n", res); + return err::ERR_RUNTIME; + } + + return err::ERR_NONE; + } + + err::Err JpegStreamer::write(image::Image *img) { + int res = 0; + image::Image *jpg = NULL; + + if (img->format() != image::Format::FMT_JPEG) { + jpg = img->to_jpeg(); + if (jpg == NULL) { + log::error("invert to jpeg failed!\r\n"); + return err::ERR_RUNTIME; + } + } else { + jpg = img; + } + + if (0 != (res = http_jpeg_server_send(jpg->data(), jpg->data_size()))) { + log::error("http_jpeg_server_send failed! res:%d\r\n", res); + return err::ERR_RUNTIME; + } + + if (img->format() != image::Format::FMT_JPEG) { + delete jpg; + } + return err::ERR_NONE; + } + + err::Err JpegStreamer::set_html(std::string data) { + int res = 0; + if (data.size() == 0) { + log::error("html code is none!\r\n"); + return err::ERR_RUNTIME; + } + + if (0 != (res = http_jpeg_server_set_index_str(&data[0]))) { + log::error("http_jpeg_server_set_index_str failed! res:%d\r\n", res); + return err::ERR_RUNTIME; + } + + return err::ERR_NONE; + } +} \ No newline at end of file diff --git a/components/vision/port/maixcam2/maix_rtmp_maixcam2.cpp b/components/vision/port/maixcam2/maix_rtmp_maixcam2.cpp new file mode 100644 index 00000000..1bf06351 --- /dev/null +++ b/components/vision/port/maixcam2/maix_rtmp_maixcam2.cpp @@ -0,0 +1,791 @@ +#include "maix_rtmp.hpp" + +#include "maix_video.hpp" +#include "maix_basic.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +} + +using namespace maix; + +class RTMPClient +{ + typedef struct { + std::string url; + AVFormatContext *format_context = nullptr; + bool has_video = true; + AVStream *video_stream = nullptr; + enum AVCodecID video_codec_id; + int video_width; + int video_height; + enum AVPixelFormat vidoe_pixel_format; + uint32_t video_bitrate; + AVRational video_timebase; + uint8_t *video_sps_pps = nullptr; + size_t video_sps_pps_size; + size_t video_frame_count = 0; + size_t video_last_pts = 0; + + bool has_audio = false; + AVStream *audio_stream = nullptr; + AVCodec *audio_codec = nullptr; + AVFrame *audio_frame = nullptr; + AVCodecContext *audio_codec_ctx = NULL; + SwrContext *audio_swr_ctx = nullptr; + std::list> *pcm_list; + int audio_sample_rate; + int audio_channels; + int audio_bitrate; + enum AVSampleFormat audio_format; + } ffmpeg_param_t; + + ffmpeg_param_t _ffmpeg; + bool _open; +public: + static int ffmpeg_init(ffmpeg_param_t *ffmpeg) + { + avformat_network_init(); + AVFormatContext *format_context = NULL; + avformat_alloc_output_context2(&format_context, NULL, "flv", ffmpeg->url.c_str()); + if (!format_context) { + log::info("Can't create output context"); + return -1; + } + + if (ffmpeg->has_video) { + AVStream *video_stream = avformat_new_stream(format_context, NULL); + if (!video_stream) { + log::info("Can't create video stream"); + goto _free_format_context; + } + video_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + video_stream->codecpar->codec_id = ffmpeg->video_codec_id; + video_stream->codecpar->width = ffmpeg->video_width; + video_stream->codecpar->height = ffmpeg->video_height; + video_stream->codecpar->format = ffmpeg->vidoe_pixel_format; + video_stream->codecpar->bit_rate = ffmpeg->video_bitrate; + video_stream->codecpar->codec_tag = 0; + video_stream->codecpar->extradata = ffmpeg->video_sps_pps; + video_stream->codecpar->extradata_size = ffmpeg->video_sps_pps_size; + video_stream->time_base = ffmpeg->video_timebase; + if (video_stream->codecpar->extradata == NULL || + video_stream->codecpar->extradata_size == 0) { + log::info("video sps pps is null, size is 0"); + goto _free_format_context; + } + + ffmpeg->video_stream = video_stream; + } + + if (ffmpeg->has_audio) { + int sample_rate = 48000; + int channels = 1; + int bitrate = 128000; + enum AVSampleFormat format = AV_SAMPLE_FMT_S16; + + AVStream *audio_stream = avformat_new_stream(format_context, NULL); + if (!audio_stream) { + log::info("Can't create audio stream"); + goto _free_format_context; + } + + AVCodec *audio_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); + if (!audio_codec) { + log::info("Can't find audio encoder"); + goto _free_format_context; + } + + AVCodecContext *audio_codec_ctx = avcodec_alloc_context3(audio_codec); + if (!audio_codec_ctx) { + log::info("Can't alloc audio codec context"); + goto _free_format_context; + } + audio_codec_ctx->codec_id = AV_CODEC_ID_AAC; + audio_codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO; + audio_codec_ctx->sample_rate = sample_rate; + audio_codec_ctx->channels = channels; + audio_codec_ctx->channel_layout = av_get_default_channel_layout(audio_codec_ctx->channels); + audio_codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP; + audio_codec_ctx->time_base = (AVRational){1, sample_rate}; + audio_codec_ctx->bit_rate = bitrate; + audio_stream->time_base = audio_codec_ctx->time_base; + + if (0 > avcodec_open2(audio_codec_ctx, audio_codec, NULL)) { + log::info("Can't open audio codec"); + goto _free_audio_codec_ctx; + } + + if (0 > avcodec_parameters_from_context(audio_stream->codecpar, audio_codec_ctx)) { + log::info("Can't copy audio codec parameters"); + goto _free_audio_codec_ctx; + } + + SwrContext *swr_ctx = swr_alloc(); + if (!swr_ctx) { + log::info("Can't alloc swr context"); + goto _free_audio_codec_ctx; + } + + av_opt_set_int(swr_ctx, "in_channel_layout", audio_codec_ctx->channel_layout, 0); + av_opt_set_int(swr_ctx, "out_channel_layout", audio_codec_ctx->channel_layout, 0); + av_opt_set_int(swr_ctx, "in_sample_rate", audio_codec_ctx->sample_rate, 0); + av_opt_set_int(swr_ctx, "out_sample_rate", audio_codec_ctx->sample_rate, 0); + av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", format, 0); + av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); + swr_init(swr_ctx); + + AVFrame *audio_frame = av_frame_alloc(); + if (!audio_frame) { + log::info("Can't alloc audio frame"); + swr_free(&swr_ctx); + swr_ctx = NULL; + goto _free_swr_ctx; + } + audio_frame->nb_samples = audio_codec_ctx->frame_size; + audio_frame->channel_layout = audio_codec_ctx->channel_layout; + audio_frame->format = AV_SAMPLE_FMT_FLTP; + audio_frame->sample_rate = audio_codec_ctx->sample_rate; + av_frame_get_buffer(audio_frame, 0); + + ffmpeg->audio_stream = audio_stream; + ffmpeg->audio_codec = audio_codec; + ffmpeg->audio_codec_ctx = audio_codec_ctx; + ffmpeg->audio_swr_ctx = swr_ctx; + ffmpeg->audio_frame = audio_frame; + ffmpeg->audio_sample_rate = sample_rate; + ffmpeg->audio_channels = channels; + ffmpeg->audio_bitrate = bitrate; + ffmpeg->audio_format = format; + } + + if (avio_open(&format_context->pb, ffmpeg->url.c_str(), AVIO_FLAG_WRITE) < 0) { + log::info("rtmp connect failed!"); + goto _free_audio_frame; + } + + if (avformat_write_header(format_context, NULL) < 0) { + log::info("rtmp write header failed!"); + goto _close_io; + } + + ffmpeg->format_context = format_context; + return 0; +_close_io: + if (ffmpeg->format_context && ffmpeg->format_context->pb) { + avio_closep(&ffmpeg->format_context->pb); + ffmpeg->format_context->pb = nullptr; + } +_free_audio_frame: + if (ffmpeg->audio_frame) { + av_frame_free(&ffmpeg->audio_frame); + ffmpeg->audio_frame = nullptr; + } +_free_swr_ctx: + if (ffmpeg->audio_swr_ctx) { + swr_free(&ffmpeg->audio_swr_ctx); + ffmpeg->audio_swr_ctx = nullptr; + } +_free_audio_codec_ctx: + if (ffmpeg->audio_codec_ctx) { + avcodec_free_context(&ffmpeg->audio_codec_ctx); + ffmpeg->audio_codec_ctx = nullptr; + } +_free_format_context: + if (ffmpeg->format_context) { + avformat_free_context(ffmpeg->format_context); + ffmpeg->format_context = nullptr; + } + + avformat_network_deinit(); + return -1; + } + + static int ffmpeg_deinit(ffmpeg_param_t *ffmpeg) + { + if (!ffmpeg) return 0; + + /* free sps pps in avformat_free_context */ + /* + if (ffmpeg->video_sps_pps) { + free(ffmpeg->video_sps_pps); + ffmpeg->video_sps_pps = NULL; + ffmpeg->video_sps_pps_size = 0; + } + */ + + if (ffmpeg->audio_frame) { + av_frame_free(&ffmpeg->audio_frame); + ffmpeg->audio_frame = nullptr; + } + + if (ffmpeg->audio_swr_ctx) { + swr_free(&ffmpeg->audio_swr_ctx); + ffmpeg->audio_swr_ctx = nullptr; + } + + if (ffmpeg->audio_codec_ctx) { + avcodec_free_context(&ffmpeg->audio_codec_ctx); + ffmpeg->audio_codec_ctx = nullptr; + } + + if (ffmpeg->format_context) { + // av_write_trailer(ffmpeg->format_context); + if (ffmpeg->format_context && ffmpeg->format_context->pb) { + avio_closep(&ffmpeg->format_context->pb); + } + avformat_free_context(ffmpeg->format_context); + ffmpeg->format_context = NULL; + } + + avformat_network_deinit(); + return 0; + } + + RTMPClient() { + _ffmpeg.has_video = true; + _ffmpeg.pcm_list = new std::list>; + _ffmpeg.video_last_pts = 0; + err::check_null_raise(_ffmpeg.pcm_list, "create pcm list failed!"); + _open = false; + } + + ~RTMPClient() { + if (_ffmpeg.pcm_list) { + for (auto it = _ffmpeg.pcm_list->begin(); it != _ffmpeg.pcm_list->end(); ++it) { + auto &item = *it; + Bytes *pcm = item.second; + delete pcm; + it = _ffmpeg.pcm_list->erase(it); + } + delete _ffmpeg.pcm_list; + _ffmpeg.pcm_list = nullptr; + } + + if (_open) { + close(); + } + } + + err::Err open() + { + if (_open) return err::ERR_NONE; + + if (ffmpeg_init(&_ffmpeg) < 0) { + return err::ERR_RUNTIME; + } + + _open = true; + return err::ERR_NONE; + } + + err::Err close() + { + if (!_open) return err::ERR_NONE; + + if (ffmpeg_deinit(&_ffmpeg) < 0) { + return err::ERR_RUNTIME; + } + + _open = false; + return err::ERR_NONE; + } + + err::Err config(std::string cmd, int data) + { + ffmpeg_param_t *ffmpeg = (ffmpeg_param_t *)&_ffmpeg; + + if (cmd == "has_video") { + ffmpeg->has_video = data ? true : false; + return err::ERR_NONE; + } else if (cmd == "video_codec_id") { + ffmpeg->video_codec_id = (AVCodecID)data; + return err::ERR_NONE; + } else if (cmd == "video_width") { + ffmpeg->video_width = data; + return err::ERR_NONE; + } else if (cmd == "video_height") { + ffmpeg->video_height = data; + return err::ERR_NONE; + } else if (cmd == "video_bitrate") { + ffmpeg->video_bitrate = data; + return err::ERR_NONE; + } else if (cmd == "video_fps") { + ffmpeg->video_timebase = (AVRational){1, data}; + return err::ERR_NONE; + } else if (cmd == "video_pixel_format") { + ffmpeg->vidoe_pixel_format = (AVPixelFormat)data; + return err::ERR_NONE; + } else if (cmd == "has_audio") { + ffmpeg->has_audio = data ? true : false; + return err::ERR_NONE; + } else { + return err::ERR_RUNTIME; + } + return err::ERR_RUNTIME; + } + + err::Err config_url(std::string url) { + _ffmpeg.url = url; + return err::ERR_NONE; + } + + err::Err config_sps_pps(uint8_t *sps_pps, int sps_pps_size) + { + ffmpeg_param_t *ffmpeg = (ffmpeg_param_t *)&_ffmpeg; + + if (ffmpeg->video_sps_pps) { + free(ffmpeg->video_sps_pps); + ffmpeg->video_sps_pps = NULL; + } + ffmpeg->video_sps_pps = (uint8_t *)malloc(sps_pps_size); + if (!ffmpeg->video_sps_pps) { + return err::ERR_NO_MEM; + } + memcpy(ffmpeg->video_sps_pps, sps_pps, sps_pps_size); + ffmpeg->video_sps_pps_size = sps_pps_size; + return err::ERR_NONE; + } + + err::Err push(uint8_t *frame, size_t frame_size, uint64_t pts, bool is_audio = false) + { + ffmpeg_param_t *ffmpeg = (ffmpeg_param_t *)&_ffmpeg; + if (!_open) { + return err::ERR_NOT_OPEN; + } + + if (!is_audio) { + if (ffmpeg->has_video) { + AVPacket *pkt = av_packet_alloc(); + if (!pkt) { + fprintf(stderr, "Can't malloc avpacket\r\n"); + return err::ERR_RUNTIME; + } + + pkt->data = frame; + pkt->size = frame_size; + pkt->stream_index = ffmpeg->video_stream->index; + pkt->duration = pts - _ffmpeg.video_last_pts; + _ffmpeg.video_last_pts = pts; + pkt->pts = pkt->dts = pts; + pkt->flags |= AV_PKT_FLAG_KEY; + ffmpeg->video_frame_count ++; + // log::info("[VIDEO] frame:%p frame_size:%d pts:%ld(%f s)", frame, frame_size, pkt->pts, this->timebase_to_ms(this->get_video_timebase(), pkt->pts) / 1000); + if (av_interleaved_write_frame(ffmpeg->format_context, pkt) < 0) { + fprintf(stderr, "send frame failed!\r\n"); + av_packet_free(&pkt); + return err::ERR_RUNTIME; + } + + av_packet_unref(pkt); + av_packet_free(&pkt); + } + } else { + if (ffmpeg->has_audio) { + AVPacket *pkt = av_packet_alloc(); + if (!pkt) { + fprintf(stderr, "Can't malloc avpacket\r\n"); + return err::ERR_RUNTIME; + } + + if (frame && frame_size > 0) { + auto pcm_list = ffmpeg->pcm_list; + AVFrame *audio_frame = ffmpeg->audio_frame; + AVStream *audio_stream = ffmpeg->audio_stream; + AVCodecContext *audio_codec_ctx = ffmpeg->audio_codec_ctx; + SwrContext *swr_ctx = ffmpeg->audio_swr_ctx; + AVFormatContext *outputFormatContext = ffmpeg->format_context; + AVPacket *audio_packet = pkt; + size_t buffer_size = av_samples_get_buffer_size(NULL, ffmpeg->audio_channels, audio_frame->nb_samples, ffmpeg->audio_format, 1); + size_t pcm_remain_len = frame_size; + + // fill last pcm to buffer_size + size_t next_pts = pts; + if (!pcm_list->empty()) { + auto last_item = pcm_list->back(); + Bytes *last_pcm = last_item.second; + if (last_pcm && last_pcm->data_len < buffer_size) { + int temp_size = pcm_remain_len + last_pcm->data_len >= buffer_size ? buffer_size : pcm_remain_len + last_pcm->data_len; + uint8_t *temp = (uint8_t *)malloc(temp_size); + err::check_null_raise(temp, "malloc failed!"); + memcpy(temp, last_pcm->data, last_pcm->data_len); + if (pcm_remain_len + last_pcm->data_len < buffer_size) { + memcpy(temp + last_pcm->data_len, frame, pcm_remain_len); + pcm_remain_len = 0; + } else { + memcpy(temp + last_pcm->data_len, frame, buffer_size - last_pcm->data_len); + pcm_remain_len -= (buffer_size - last_pcm->data_len); + } + + Bytes *new_pcm = new Bytes(temp, temp_size, true, false); + pcm_list->pop_back(); + delete last_pcm; + + size_t new_pts = last_item.first; + next_pts = new_pts + get_audio_pts_from_pcm_size(new_pcm->data_len); + pcm_list->push_back(std::make_pair(new_pts, new_pcm)); + } + } + + // fill other pcm + while (pcm_remain_len > 0) { + int temp_size = pcm_remain_len >= buffer_size ? buffer_size : pcm_remain_len; + uint8_t *temp = (uint8_t *)malloc(temp_size); + err::check_null_raise(temp, "malloc failed!"); + memcpy(temp, frame + frame_size - pcm_remain_len, temp_size); + pcm_remain_len -= temp_size; + + Bytes *new_pcm = new Bytes(temp, temp_size, true, false); + pcm_list->push_back(std::make_pair(next_pts, new_pcm)); + next_pts += get_audio_pts_from_pcm_size(temp_size); + } + + // for (auto it = _ffmpeg.pcm_list->begin(); it != _ffmpeg.pcm_list->end(); ++it) { + // auto &item = *it; + // log::info("PTS:%d PCM:%p PCM_SIZE:%d", item.first, item.second->data, item.second->data_len); + // } + + // audio process + while (pcm_list->size() > 0) { + auto item = pcm_list->front(); + auto next_pts = item.first; + Bytes *pcm = item.second; + if (pcm) { + if (pcm->data_len == buffer_size) { + const uint8_t *in[] = {pcm->data}; + uint8_t *out[] = {audio_frame->data[0]}; + swr_convert(swr_ctx, out, audio_codec_ctx->frame_size, in, audio_codec_ctx->frame_size); + audio_frame->pts = next_pts; + if (avcodec_send_frame(audio_codec_ctx, audio_frame) < 0) { + printf("Error sending audio_frame to encoder.\n"); + break; + } + + while (avcodec_receive_packet(audio_codec_ctx, audio_packet) == 0) { + audio_packet->stream_index = audio_stream->index; + audio_packet->pts = audio_packet->dts = next_pts; + audio_packet->duration = get_audio_pts_from_pcm_size(pcm->data_len); + + av_interleaved_write_frame(outputFormatContext, audio_packet); + av_packet_unref(audio_packet); + } + pcm_list->pop_front(); + delete pcm; + } else { + break; + } + } else { + err::check_raise(err::ERR_RUNTIME, "pcm data error"); + } + } + // log::info("pcm process time:%d", time::ticks_ms() - t); + } + + av_packet_unref(pkt); + av_packet_free(&pkt); + } + } + + return err::ERR_NONE; + } + + void reset_pts() + { + ffmpeg_param_t *ffmpeg = (ffmpeg_param_t *)&_ffmpeg; + ffmpeg->video_frame_count = 0; + } + + bool is_opened() + { + return _open; + } + + int get_frame_size_per_second() { + if (!_open) + return 0; + ffmpeg_param_t *ffmpeg = (ffmpeg_param_t *)&_ffmpeg; + return ffmpeg->audio_frame->sample_rate * ffmpeg->audio_frame->channels * av_get_bytes_per_sample(ffmpeg->audio_format); + } + + std::vector get_video_timebase() { + ffmpeg_param_t *ffmpeg = (ffmpeg_param_t *)&_ffmpeg; + if (!_open || !ffmpeg->has_video) + return std::vector(); + return {ffmpeg->video_stream->time_base.num, ffmpeg->video_stream->time_base.den}; + } + + std::vector get_audio_timebase() { + ffmpeg_param_t *ffmpeg = (ffmpeg_param_t *)&_ffmpeg; + if (!_open || !ffmpeg->has_audio) + return std::vector(); + return {ffmpeg->audio_stream->time_base.num, ffmpeg->audio_stream->time_base.den}; + } + + double timebase_to_us(std::vector timebase, uint64_t value) { + if (timebase.size() < 2) return -1; + return value * 1000000 / ((double)timebase[1] / timebase[0]); + } + + double timebase_to_ms(std::vector timebase, uint64_t value) { + if (timebase.size() < 2) return -1; + return value * 1000 / ((double)timebase[1] / timebase[0]); + } + + uint64_t ms_to_pts(std::vector timebase, uint64_t value) { + return value * (timebase[1] / timebase[0]) / 1000; + } + + uint64_t get_audio_pts_from_pcm_size(size_t pcm_length) { + ffmpeg_param_t *ffmpeg = (ffmpeg_param_t *)&_ffmpeg; + if (!_open || !ffmpeg->has_audio) + return 0; + return pcm_length * (ffmpeg->audio_stream->time_base.den / ffmpeg->audio_stream->time_base.num) / get_frame_size_per_second(); + } +}; + +namespace maix::rtmp { + class PrivateParam { + public: + enum { + RTMP_IDLE = 0, + RTMP_STOP, + RTMP_RUNNING, + } status; + RTMPClient *rtmp_client = nullptr; + rtmp::Rtmp *rtmp; + std::string url; + int bitrate; + camera::Camera **camera; + audio::Recorder **recorder; + video::Encoder **encoder; + display::Display **display; + }; + + Rtmp::Rtmp(std::string host, int port, std::string app, std::string stream, int bitrate) { + _host = host; + _port = port; + _app = app; + _stream = stream; + _bitrate = bitrate; + + RTMPClient *rtmp_client = new RTMPClient(); + PrivateParam *param = new PrivateParam(); + param->rtmp = this; + param->rtmp_client = rtmp_client; + param->status = PrivateParam::RTMP_IDLE; + param->bitrate = bitrate; + param->camera = &_camera; + param->recorder = &_audio_recorder; + param->encoder = &_video_encoder; + param->display = &_display; + /* rtmp://host:prot/app/stream */ + param->url = "rtmp://" + host + ":" + std::to_string(port) + "/" + app + "/" + stream; + if (0 != pthread_mutex_init(&_lock, NULL)) { + throw std::runtime_error("create lock failed!"); + } + // log::info("url:%s", param->url.c_str()); + + _param = param; + _thread = nullptr; + _camera = nullptr; + _start = false; + _capture_image = nullptr; + _need_capture = false; + _path = std::string(); + } + + Rtmp::~Rtmp() { + if (_param) { + PrivateParam *param = (PrivateParam *)_param; + if (param->status != PrivateParam::RTMP_IDLE) { + this->stop(); + } + + if (param->rtmp_client) { + delete param->rtmp_client; + param->rtmp_client = nullptr; + } + delete param; + _param = nullptr; + } + + pthread_mutex_destroy(&_lock); + } + + // return 0 ok, other error + int Rtmp::push_video(void *data, size_t data_size, uint32_t timestamp) { + PrivateParam *param = (PrivateParam *)_param; + if (param && param->rtmp_client) { + auto pts = param->rtmp_client->ms_to_pts(param->rtmp_client->get_audio_timebase(), timestamp); + param->rtmp_client->push((uint8_t *)data, data_size, pts); + return 0; + } else { + return -1; + } + } + + // return 0 ok, other error + int Rtmp::push_audio(void *data, size_t data_size, uint32_t timestamp) { + PrivateParam *param = (PrivateParam *)_param; + if (param && param->rtmp_client) { + auto pts = param->rtmp_client->ms_to_pts(param->rtmp_client->get_audio_timebase(), timestamp); + param->rtmp_client->push((uint8_t *)data, data_size, pts, true); + return 0; + } else { + return -1; + } + } + + // return 0 ok, other error + int Rtmp::push_script(void *data, size_t data_size, uint32_t timestamp) { + err::check_raise(err::ERR_NOT_IMPL, "This interface is not supported"); + return -1; + } + + err::Err Rtmp::lock(uint32_t time) { + uint32_t count = 0; + while (0 != pthread_mutex_trylock(&_lock)) { + count ++; + if (count >= time) { + break; + } + time::sleep_ms(1); + } + + if (count >= time) + return err::ERR_BUSY; + else + return err::ERR_NONE; + } + + err::Err Rtmp::unlock() { + if (0 == pthread_mutex_unlock(&_lock)) { + return err::ERR_NONE; + } + return err::ERR_RUNTIME; + } + + static void _push_camera_thread(void *priv) + { + PrivateParam *param = (PrivateParam *)priv; + camera::Camera *camera = *param->camera; + audio::Recorder *audio_recorder = *param->recorder; + + param->status = PrivateParam::RTMP_IDLE; + } + + err::Err Rtmp::start(std::string path) { + lock(-1); + err::Err ret = err::ERR_NONE; + PrivateParam *param = (PrivateParam *)_param; + auto video_encoder_width = 0; + auto video_encoder_height = 0; + auto video_encoder_format = 0; + auto fps = 30; + bool has_audio = false; + RTMPClient *rtmp_client = nullptr; + + if (param == nullptr) { + ret = err::ERR_RUNTIME; + goto _error; + } + + rtmp_client = (RTMPClient *)param->rtmp_client; + if (param->status != PrivateParam::RTMP_IDLE) { + ret = err::ERR_BUSY; + goto _error; + } + + if (_camera == nullptr) { + log::info("You must use the bind_camera interface to bind a Camera object."); + ret = err::ERR_NOT_READY; + goto _error; + } + + if (_audio_recorder == nullptr) { + has_audio = false; + } else { + has_audio = true; + _audio_recorder->reset(true); + } + + video_encoder_width = _camera->width(); + video_encoder_height = _camera->height(); + video_encoder_format = _camera->format(); + _camera->set_fps(fps); + // log::info("Create encoder, width:%d height:%d format:%s fps:%d bitrate:%d", video_encoder_width, video_encoder_height, image::fmt_names[video_encoder_format].c_str(), fps, param->bitrate); + _video_encoder = new video::Encoder("", video_encoder_width, video_encoder_height, (image::Format)video_encoder_format, video::VIDEO_H264, fps, 50, param->bitrate); + if (!_video_encoder) { + log::info("Create video encoder failed!"); + ret = err::ERR_RUNTIME; + goto _error; + } + + err::check_raise(rtmp_client->config("has_audio", has_audio), "rtmp config failed!"); + err::check_raise(rtmp_client->config("video_codec_id", AV_CODEC_ID_H264), "rtmp config failed!"); + err::check_raise(rtmp_client->config("video_width", video_encoder_width), "rtmp config failed!"); + err::check_raise(rtmp_client->config("video_height", video_encoder_height), "rtmp config failed!"); + err::check_raise(rtmp_client->config("video_bitrate", _bitrate), "rtmp config failed!"); + err::check_raise(rtmp_client->config("video_fps", fps), "rtmp config failed!"); + err::check_raise(rtmp_client->config("video_pixel_format", AV_PIX_FMT_NV21), "rtmp config failed!"); + err::check_raise(rtmp_client->config_url(param->url), "rtmp config failed!"); + + _thread = new thread::Thread(_push_camera_thread, param); + if (this->_thread == NULL) { + log::error("create camera thread failed!\r\n"); + ret = err::ERR_RUNTIME; + goto _error; + } + + unlock(); + return err::ERR_NONE; +_error: + unlock(); + return ret; + } + + err::Err Rtmp::stop() { + PrivateParam *param = (PrivateParam *)_param; + lock(-1); + if (param->status != PrivateParam::RTMP_IDLE) { + param->status = PrivateParam::RTMP_STOP; + } + + unlock(); + + if (_thread) { + _thread->join(); + _thread = nullptr; + } + + while (param->status != PrivateParam::RTMP_IDLE) { + time::sleep_ms(100); + log::info("wait rtmp thread exit.."); + } + + if (_video_encoder) { + delete _video_encoder; + _video_encoder = nullptr; + } + + if (_audio_recorder) { + _audio_recorder->reset(false); + } + + return err::ERR_NONE; + } +} \ No newline at end of file diff --git a/components/vision/port/maixcam2/maix_rtsp_maixcam2.cpp b/components/vision/port/maixcam2/maix_rtsp_maixcam2.cpp new file mode 100644 index 00000000..cb1226bb --- /dev/null +++ b/components/vision/port/maixcam2/maix_rtsp_maixcam2.cpp @@ -0,0 +1,642 @@ +/** + * @author lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#include "maix_rtsp.hpp" +#include "maix_err.hpp" +#include "maix_basic.hpp" +#include "maix_audio.hpp" +#include "maix_video.hpp" +#include "maix_ffmpeg.hpp" +#include +#include "maix_rtsp_server.hpp" +#include +#include + +namespace maix::rtsp +{ + Region::Region(int x, int y, int width, int height, image::Format format, camera::Camera *camera) + { + if (format != image::Format::FMT_BGRA8888) { + err::check_raise(err::ERR_RUNTIME, "region support FMT_BGRA8888 only!"); + } + + if (camera == NULL) { + err::check_raise(err::ERR_RUNTIME, "region bind a NULL camera!"); + } + + this->_width = width; + this->_height = height; + this->_x = x; + this->_y = y; + this->_format = format; + this->_camera = camera; + } + + Region::~Region() { + + } + + image::Image *Region::get_canvas() { + void *data; + + + + return nullptr; + } + + err::Err Region::update_canvas() { + image::Image *img = this->_image; + if (img->format() == image::Format::FMT_BGRA8888) { + uint32_t *data_u32 = (uint32_t *)img->data(); + int width = img->width(); + int height = img->height(); + + if (this->_flip) { + for (int h = 0; h < height; h ++) { + for (int w = 0; w < width / 2; w ++) { + int left_idx = h * width + w; + int right_idx = h * width + (width - 1 - w); + uint32_t tmp = data_u32[left_idx]; + data_u32[left_idx] = data_u32[right_idx]; + data_u32[right_idx] = tmp; + } + } + } + + if (this->_mirror) { + for (int h = 0; h < height / 2; h ++) { + for (int w = 0; w < width; w ++) { + int left_idx = h * width + w; + int right_idx = (height - 1 - h) * width + w; + uint32_t tmp = data_u32[left_idx]; + data_u32[left_idx] = data_u32[right_idx]; + data_u32[right_idx] = tmp; + } + } + } + } else { + log::error("support FMT_BGRA888 only!\r\n"); + return err::ERR_RUNTIME; + } + + return err::ERR_NONE; + } + + enum RtspStatus{ + RTSP_IDLE = 0, + RTSP_RUNNING, + RTSP_STOP, + }; + + typedef struct { + MaixRtspServer *rtsp_server; + enum RtspStatus status; + int *clients; + camera::Camera *camera; + video::Encoder *encoder; + audio::Recorder *audio_recorder; + ffmpeg::FFmpegPacker *ffmpeg_packer; + bool bind_camera; + bool bind_audio_recorder; + int encoder_bitrate; + int fps; + int audio_bitrate; + } rtsp_param_t; + + Rtsp::Rtsp(std::string ip, int port, int fps, rtsp::RtspStreamType stream_type, int bitrate) { + rtsp_param_t *param = (rtsp_param_t *)malloc(sizeof(rtsp_param_t)); + err::check_null_raise(param, "malloc failed!"); + memset(param, 0, sizeof(rtsp_param_t)); + + this->_ip = ip; + this->_port = port; + this->_fps = fps; + this->_stream_type = stream_type; + this->_is_start = false; + this->_thread = NULL; + this->_param = param; + this->_region_max_number = 16; + for (int i = 0; i < this->_region_max_number; i ++) { + this->_region_list.push_back(NULL); + this->_region_type_list.push_back(0); + this->_region_used_list.push_back(false); + } + + if (_ip.size() == 0) { + _ip = "0.0.0.0"; + } + + this->_timestamp = 0; + this->_last_ms = 0; + + param->status = RTSP_IDLE; + param->camera = nullptr; + param->audio_recorder = nullptr; + param->bind_camera = false; + param->bind_audio_recorder = false; + param->encoder_bitrate = bitrate; + param->fps = fps; + param->audio_bitrate = 128000; + } + + Rtsp::~Rtsp() { + rtsp_param_t *param = (rtsp_param_t *)_param; + if (param) { + if (param->status != RTSP_IDLE) { + this->stop(); + } + + if (param->encoder) { + delete param->encoder; + param->encoder = nullptr; + } + + if (param->rtsp_server) { + delete param->rtsp_server; + param->rtsp_server = nullptr; + } + + free(_param); + _param = nullptr; + } + + for (auto ®ion : this->_region_list) { + delete region; + } + } +#if 0 + static void dump_type(uint8_t *m_buf, int bytes_read) + { + int i = 0, start_code = 0; + for (i=0; i 0) { + printf("[%d] nalu type:%d\r\n", i, m_buf[i+start_code]&0x1F); + } + + i += start_code; + continue; + } + else if(m_buf[i] == 0 && m_buf[i+1] == 0 && m_buf[i+2] == 0 && m_buf[i+3] == 1) { + start_code = 4; + + if (start_code > 0) { + printf("[%d] nalu type:%d\r\n", i, m_buf[i+start_code]&0x1F); + } + + i += start_code; + continue; + } + else { + continue; + } + } + } +#endif + + static uint64_t get_video_timestamp() { + return time::ticks_ms() * 90; + } + + static uint64_t get_audio_timestamp() { + return get_video_timestamp(); + } + + static void _camera_push_thread(void *args) { + rtsp_param_t *param = (rtsp_param_t *)args; + MaixRtspServer *rtsp_server = param->rtsp_server; + int vi_ch = param->camera->get_channel(); + int enc_ch = 1; + size_t video_pts = 0, audio_pts = 0; + uint64_t last_ms = time::ticks_ms(); + bool found_first_pps_sps = false; + uint64_t last_read_pcm_ms = time::ticks_ms(); + + typedef struct { + MaixRtspServer *rtsp_server; + bool is_i_or_b; + } temp_param_t; + + while (param->status == RTSP_RUNNING && !app::need_exit()) { + void *frame = NULL; + bool found_camera_frame = false; + bool found_venc_stream = false; + bool found_audio_data = false; + + // if (found_audio_data) { + // delete pcm; + // } + } + + param->status = RTSP_IDLE; + } + + err::Err Rtsp::start() { + err::Err err = err::ERR_NONE; + rtsp_param_t *param = (rtsp_param_t *)_param; + if (!param) { + return err::ERR_RUNTIME; + } + + if (param->status != RTSP_IDLE) { + return err::ERR_BUSY; + } + + // check camera + if (!param->bind_camera || !param->camera) { + log::error("You need bind a camera!"); + return err::ERR_RUNTIME; + } + + if (param->camera->width() % 32 != 0) { + log::error("camera width must be multiple of 32!\r\n"); + return err::ERR_RUNTIME; + } + + // create rtsp server + MaixRtspServerBuilder rtsp_builder = MaixRtspServerBuilder() + .set_ip(_ip) + .set_port(this->_port) + .set_session_name("live"); + if (param->bind_audio_recorder && param->audio_recorder) { + rtsp_builder = rtsp_builder.set_audio(true) + .set_audio_channels(param->audio_recorder->channel()) + .set_audio_sample_rate(param->audio_recorder->sample_rate()); + } + param->rtsp_server = rtsp_builder.build(); + + // create encoder + if (param->encoder) { + delete param->encoder; + param->encoder = nullptr; + } + param->encoder = new video::Encoder("", param->camera->width(), param->camera->height(), image::Format::FMT_YVU420SP, video::VIDEO_H264, param->fps, 50, param->encoder_bitrate); + err::check_null_raise(param->encoder, "Create video encoder failed!"); + + // create frame package + param->ffmpeg_packer = new ffmpeg::FFmpegPacker(); + err::check_null_raise(param->ffmpeg_packer, "ffmpeg packer init failed"); + err::check_bool_raise(!param->ffmpeg_packer->config2("context_format_name", "flv"), "rtmp config failed!"); + if (param->bind_audio_recorder && param->audio_recorder) { + if (param->audio_recorder->format() != audio::FMT_S16_LE) { + log::error("Only support audio::FMT_S16_LE format!"); + return err::ERR_RUNTIME; + } + err::check_bool_raise(!param->ffmpeg_packer->config("has_audio", true), "rtmp config failed!"); + err::check_bool_raise(!param->ffmpeg_packer->config("audio_sample_rate", param->audio_recorder->sample_rate()), "rtmp config failed!"); + err::check_bool_raise(!param->ffmpeg_packer->config("audio_channels", param->audio_recorder->channel()), "rtmp config failed!"); + err::check_bool_raise(!param->ffmpeg_packer->config("audio_bitrate", param->audio_bitrate), "rtmp config failed!"); + err::check_bool_raise(!param->ffmpeg_packer->config("audio_format", AV_SAMPLE_FMT_S16), "rtmp config failed!"); + } + param->ffmpeg_packer->open(); + + // start audio + if (param->bind_audio_recorder) { + param->audio_recorder->reset(true); + } + + // create runtime thread + param->status = RTSP_RUNNING; + _thread = new thread::Thread(_camera_push_thread, param); + if (_thread == NULL) { + log::error("create camera thread failed!\r\n"); + return err::ERR_RUNTIME; + } + return err; + } + + err::Err Rtsp::stop() { + err::Err err = err::ERR_NONE; + rtsp_param_t *param = (rtsp_param_t *)_param; + if (param->status != RTSP_RUNNING) { + return err::ERR_NONE; + } + + param->status = RTSP_STOP; + if (_thread) { + _thread->join(); + _thread = nullptr; + } + + while (param->status != RTSP_IDLE) { + time::sleep_ms(100); + log::info("wait rtsp thread exit.."); + } + + // stop audio + if (param->bind_audio_recorder) { + param->audio_recorder->reset(false); + } + + if (param->ffmpeg_packer) { + delete param->ffmpeg_packer; + param->ffmpeg_packer = nullptr; + } + + if (param->encoder) { + delete param->encoder; + param->encoder = nullptr; + } + + if (param->rtsp_server) { + delete param->rtsp_server; + param->rtsp_server = nullptr; + } + + return err; + } + + err::Err Rtsp::bind_camera(camera::Camera *camera) { + err::Err err = err::ERR_NONE; + rtsp_param_t *param = (rtsp_param_t *)_param; + if (!param) { + return err::ERR_RUNTIME; + } + + if (camera->format() != image::Format::FMT_YVU420SP) { + err::check_raise(err::ERR_RUNTIME, "bind camera failed! support FMT_YVU420SP only!\r\n"); + return err::ERR_RUNTIME; + } + + param->camera = camera; + param->bind_camera = true; + return err; + } + + err::Err Rtsp::bind_audio_recorder(audio::Recorder *recorder) { + err::Err err = err::ERR_NONE; + rtsp_param_t *param = (rtsp_param_t *)_param; + if (!param) { + return err::ERR_RUNTIME; + } + + param->audio_recorder = recorder; + param->bind_audio_recorder = true; + return err; + } + + err::Err Rtsp::write(video::Frame &frame) { + err::Err err = err::ERR_NONE; + err::check_raise(err::ERR_NOT_IMPL, "write frame not impl!"); + return err; + } + + camera::Camera *Rtsp::to_camera() { + rtsp_param_t *param = (rtsp_param_t *)_param; + err::check_null_raise(param->camera, "camera is null!"); + return param->camera; + } + + std::string Rtsp::get_url() { + return "rtsp://" + _ip + ":" + std::to_string(_port) + "/live"; + } + + + static int get_ip(char *hw, char ip[16]) + { + struct ifaddrs *ifaddr, *ifa; + int family, s; + char host[NI_MAXHOST]; + + if (getifaddrs(&ifaddr) == -1) { + perror("getifaddrs"); + return -1; + } + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) { + continue; + } + + family = ifa->ifa_addr->sa_family; + + if (family == AF_INET) { + s = getnameinfo(ifa->ifa_addr, (family == AF_INET) ? sizeof(struct sockaddr_in) : + sizeof(struct sockaddr_in6), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + if (s != 0) { + printf("getnameinfo() failed: %s\n", gai_strerror(s)); + return -1; + } + + if (!strcmp(ifa->ifa_name, hw)) { + strncpy(ip, host, 16); + freeifaddrs(ifaddr); + return 0; + } + } + } + + freeifaddrs(ifaddr); + return -1; + } + + static std::vector rtsp_get_server_urls(std::string ip, int port) + { + char new_ip[16] = {0}; + + std::vector ip_list; + + if (!strcmp("0.0.0.0", ip.c_str())) { + if (!get_ip((char *)"eth0", new_ip)) { + ip_list.push_back("rtsp://" + std::string(new_ip) + ":" + std::to_string(port) + "/live"); + } + if (!get_ip((char *)"usb0", new_ip)) { + ip_list.push_back("rtsp://" + std::string(new_ip) + ":" + std::to_string(port) + "/live"); + } + if (!get_ip((char *)"wlan0", new_ip)) { + ip_list.push_back("rtsp://" + std::string(new_ip) + ":" + std::to_string(port) + "/live"); + } + } else { + ip_list.push_back("rtsp://" + ip + ":" + std::to_string(port) + "/live"); + } + + return ip_list; + } + + std::vector Rtsp::get_urls() + { + return rtsp_get_server_urls(_ip, _port); + } + + rtsp::Region *Rtsp::add_region(int x, int y, int width, int height, image::Format format) { + rtsp_param_t *param = (rtsp_param_t *)_param; + if (!param) { + return nullptr; + } + + if (format != image::Format::FMT_BGRA8888) { + log::error("region support FMT_BGRA8888 only!\r\n"); + return NULL; + } + + if (!param->bind_camera) { + log::error("You must use bind camera firstly!\r\n"); + return NULL; + } + + // Find unused idx + int unused_idx = -1; + for (int i = 0; i < this->_region_max_number; i ++) { + if (this->_region_used_list[i] == false) { + unused_idx = i; + break; + } + } + err::check_bool_raise(unused_idx != -1, "Unused region not found"); + + // Create region + rtsp::Region *region = new rtsp::Region(x, y, width, height, format, param->camera); + err::check_null_raise(region, "Create region failed!"); + this->_region_list[unused_idx] = region; + this->_region_used_list[unused_idx] = true; + this->_region_type_list[unused_idx] = 0; + + return region; + } + + err::Err Rtsp::update_region(rtsp::Region ®ion) { + return region.update_canvas(); + } + + err::Err Rtsp::del_region(rtsp::Region *region) { + err::check_null_raise(region, "The region object is NULL"); + + for (int i = 0; i < this->_region_max_number; i ++) { + if (this->_region_list[i] == region) { + this->_region_list[i] = NULL; + this->_region_used_list[i] = false; + this->_region_type_list[i] = 0; + delete region; + return err::ERR_NONE; + } + } + + return err::ERR_NONE; + } + + err::Err Rtsp::draw_rect(int id, int x, int y, int width, int height, image::Color color, int thickness) + { + rtsp_param_t *param = (rtsp_param_t *)_param; + if (!param) { + return err::ERR_RUNTIME; + } + + // Check id + if (id < 0 || id > 3) { + log::error("region id is invalid! range is [0, 3"); + err::check_raise(err::ERR_RUNTIME, "invalid parameter"); + } + + if (x < 0) { + width = width + x < 0 ? 0 : width + x; + x = 0; + } + + if (y < 0) { + height = height + y < 0 ? 0 : height + y; + y = 0; + } + + if (x > param->camera->width()) { + x = 0; + width = 0; + } + + if (y > param->camera->height()) { + y = 0; + height = 0; + } + + if (x + width > param->camera->width()) { + width = param->camera->width() - x; + } + + if (y + height > param->camera->height()) { + height = param->camera->height() - y; + } + + // Check if the region [id, id + 4) is used for other functions + for (size_t i = id; i < this->_region_used_list.size() && (int)i < id + 4; i ++) { + if (_region_used_list[i] == true && _region_type_list[i] != 2) { + log::error("In areas %d - %d, %d is used for other functions(%d)", id, id + 4, i, _region_type_list[i]); + err::check_raise(err::ERR_RUNTIME, "invalid parameter"); + } + } + + // Delete last region + for (size_t i = id; i < this->_region_used_list.size() && (int)i < id + 4; i ++) { + if (_region_used_list[i] == true && _region_type_list[i] == 2) { + delete _region_list[i]; + _region_list[i] = NULL; + _region_used_list[i] = false; + _region_type_list[i] = -1; + } + } + + // Create upper region + int upper_lower_height = 0; + upper_lower_height = thickness > 0 ? thickness : height; + upper_lower_height = upper_lower_height > height ? height : upper_lower_height; + rtsp::Region *region_upper = add_region(x, y, width, upper_lower_height); + err::check_null_raise(region_upper); + + // Create lower region + rtsp::Region *region_lower = add_region(x, y + height - upper_lower_height, width, upper_lower_height); + err::check_null_raise(region_upper); + + // Create left region + int left_right_width = 0; + left_right_width = thickness > 0 ? thickness : width; + left_right_width = left_right_width > width ? width : left_right_width; + rtsp::Region *region_left = add_region(x, y + upper_lower_height, left_right_width, height - 2 * upper_lower_height); + err::check_null_raise(region_left); + + // Create right region + rtsp::Region *region_right = add_region(x + width - left_right_width, y + upper_lower_height, left_right_width, height - 2 * upper_lower_height); + err::check_null_raise(region_right); + + // Config region list + _region_list[id] = region_upper; + _region_list[id + 1] = region_lower; + _region_list[id + 2] = region_left; + _region_list[id + 3] = region_right; + _region_used_list[id] = true; + _region_used_list[id + 1] = true; + _region_used_list[id + 2] = true; + _region_used_list[id + 3] = true; + _region_type_list[id] = 2; + _region_type_list[id + 1] = 2; + _region_type_list[id + 2] = 2; + _region_type_list[id + 3] = 2; + + // Draw all of region + uint32_t color_hex = color.hex(); + for (int i = id; i < id + 4; i ++) { + rtsp::Region *region = _region_list[i]; + image::Image *img = region->get_canvas(); + err::check_null_raise(img, "Get canvas image failed!"); + uint32_t *data = (uint32_t *)img->data(); + int width = img->width(); + int height = img->height(); + for (int i = 0; i < height; i ++) { + for (int j = 0; j < width; j ++) { + data[i * width + j] = color_hex; + } + } + update_region(*region); + } + + return err::ERR_NONE; + } + + err::Err Rtsp::draw_string(int id, int x, int y, const char *str, image::Color color, int size, int thickness) + { + return err::ERR_NOT_IMPL; + } +} diff --git a/components/vision/port/maixcam2/maix_touchscreen_maixcam2.hpp b/components/vision/port/maixcam2/maix_touchscreen_maixcam2.hpp new file mode 100644 index 00000000..3388c462 --- /dev/null +++ b/components/vision/port/maixcam2/maix_touchscreen_maixcam2.hpp @@ -0,0 +1,236 @@ +/** + * @author neucrack@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#pragma once + +#include +#include +#include "maix_basic.hpp" +#include "maix_touchscreen_base.hpp" +#include +#include +#include +#include + +namespace maix::touchscreen +{ + + class TouchScreen_MaixCam final : public TouchScreen_Base + { + public: + TouchScreen_MaixCam(const std::string &device = "/dev/input/event1") + { + _opened = false; + _fd = -1; + _device = device; + } + + err::Err open() + { + if(_fd > 0) + { + return err::ERR_NONE; + } + _opened = false; + _fd = ::open(_device.c_str(), O_RDONLY); + if(_fd < 0) + { + log::error("open touch screen failed: %s", _device.c_str()); + return err::ERR_IO; + } + + _x = 0; + _y = 0; + _pressed = false; + + // set unblock read + int non_blocking = 1; + ioctl(_fd, FIONBIO, &non_blocking); + + // char * absval[6] = { "Value", "Min", "Max", "Fuzz", "Flat", "Resolution" }; + int absX[6] = {}; + int absY[6] = {}; + + ioctl(_fd, EVIOCGABS(ABS_MT_POSITION_X), absX); + ioctl(_fd, EVIOCGABS(ABS_MT_POSITION_Y), absY); + + _x_max = absX[2]; + _y_max = absY[2]; + if(_x_max <= 0 || _y_max <= 0) + { + log::error("get touchscreen resolution failed"); + _x_max = 368; + _y_max = 552; + } + + _init_epoll(_fd); + + _opened = true; + return err::ERR_NONE; + } + + err::Err close() + { + if(_fd > 0) + { + ::close(_fd); + _fd = -1; + } + _opened = false; + return err::ERR_NONE; + } + + bool is_opened() { + return _opened; + } + + err::Err read(int &x, int &y, bool &pressed) + { + err::Err e = _read(true); + if(e != err::ERR_NONE) + return e; + x = _x; + y = _y; + pressed = _pressed; + return err::ERR_NONE; + } + + std::vector read() + { + _read(true); + return {_x, _y, _pressed ? 1 : 0}; + } + + err::Err read0(int &x, int &y, bool &pressed) + { + err::Err e = _read(false); + if(e != err::ERR_NONE) + return e; + x = _x; + y = _y; + pressed = _pressed; + return err::ERR_NONE; + } + + std::vector read0() + { + _read(false); + return {_x, _y, _pressed ? 1 : 0}; + } + + bool available(int timeout) + { + return _available(timeout); + } + + + private: + int _x; + int _y; + bool _pressed; + int _width; + int _height; + bool _opened; + int _fd; + std::string _device; + int _x_max; + int _y_max; + int _epoll_fd; + + void _init_epoll(int fd) + { + _epoll_fd = epoll_create(1); + if(_epoll_fd < 0) + { + log::error("create epoll failed: %s", strerror(errno)); + return; + } + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.fd = fd; + if(epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) + { + log::error("epoll_ctl add failed: %s", strerror(errno)); + return; + } + } + + bool _available(int timeout) + { + struct epoll_event events[1]; + int nfds = epoll_wait(_epoll_fd, events, 1, timeout); + return nfds > 0; + } + + err::Err _read(bool empty_buffer) + { + struct input_event event; + // struct epoll_event events[1]; + bool event_press = false; + bool event_move = false; + while(1) + { + // int nfds = epoll_wait(_epoll_fd, events, 1, 0); + // if(nfds <= 0) + // { + // return err::ERR_NOT_READY; + // } + int ret = ::read(_fd, &event, sizeof(event)); + if(ret != sizeof(event)) + { + if(event_move) + return err::ERR_NONE; + if(errno == EAGAIN || errno == EWOULDBLOCK) + return err::ERR_NOT_READY; + log::error("read touch screen failed: %s, %d", strerror(errno), ret); + return err::ERR_IO; + } + // printf("event %d %d\n", event.type, event.code); + if(event.type == EV_ABS) + { + if(event.code == ABS_MT_POSITION_X || event.code == ABS_X) + { + // _x = event.value; + // anti-clockwise 90 degree + _y = event.value; + event_move = true; + } + else if(event.code == ABS_MT_POSITION_Y || event.code == ABS_Y) + { + // _y = event.value; + // anti-clockwise 90 degree + _x = _y_max - event.value - 1; + event_move = true; + } + } + else if(event.type == EV_KEY) + { + if(event.code == BTN_TOUCH) + { + _pressed = event.value == 1; + event_press = true; + } + } + else if(event.type == EV_SYN) + { + if(event_press) + { + break; + } + if(event_move && !empty_buffer) + { + break; + } + } + } + return err::ERR_NONE; + } + }; +} // namespace maix::touchscreen + + + diff --git a/components/vision/port/maixcam2/maix_video_maixcam2.cpp b/components/vision/port/maixcam2/maix_video_maixcam2.cpp new file mode 100644 index 00000000..2e043bfb --- /dev/null +++ b/components/vision/port/maixcam2/maix_video_maixcam2.cpp @@ -0,0 +1,1431 @@ +/** + * @author lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +extern "C" { +#include +#include +#include +#include +#include +#include +} + +#include +#include "maix_basic.hpp" +#include "maix_image.hpp" +#include "maix_time.hpp" +#include "maix_video.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace maix::video +{ +#if CONFIG_BUILD_WITH_MAIXPY + maix::image::Image *Video::NoneImage = new maix::image::Image(); + maix::image::Image *Encoder::NoneImage = new maix::image::Image(); + maix::Bytes *Encoder::NoneBytes = new maix::Bytes(); +#else + maix::image::Image *Video::NoneImage = NULL; + maix::image::Image *Encoder::NoneImage = NULL; + maix::Bytes *Encoder::NoneBytes = NULL; +#endif + static void custom_log_callback(void* ptr, int level, const char* fmt, va_list vargs) { + if (level > AV_LOG_ERROR) { + return; + } + } + + static video::VideoType _get_video_type(const char *filename, video::VideoType type) { + video::VideoType video_type = type; + const char *suffix = strrchr(filename, '.'); + if (!suffix) { + return type; + } + + if (!strcmp(suffix, ".h264")) { + video_type = video::VIDEO_H264; + } else if (!strcmp(suffix, ".h265")) { + video_type = video::VIDEO_H264; + } else if (!strcmp(suffix, ".mp4")) { + switch (type) { + case video::VIDEO_H264: // fall through + case video::VIDEO_H264_MP4: // fall through + case video::VIDEO_H264_FLV: + video_type = video::VIDEO_H264_MP4; + break; + case video::VIDEO_H265: // fall through + case video::VIDEO_H265_MP4: + video_type = video::VIDEO_H265_MP4; + break; + default: + err::check_raise(err::ERR_RUNTIME, "Unsupported video type!"); + break; + } + } else if (!strcmp(suffix, ".flv")) { + switch (type) { + case video::VIDEO_H264: // fall through + case video::VIDEO_H264_FLV: + video_type = video::VIDEO_H264_FLV; + break; + default: + err::check_raise(err::ERR_RUNTIME, "Unsupported video type!"); + break; + } + } else { + video_type = type; + } + return video_type; + } + + + static enum AVCodecID _video_type_to_ffmpeg(VideoType video_type) { + enum AVCodecID codec_id = AV_CODEC_ID_NONE; + switch (video_type) { + case VIDEO_H264: + case VIDEO_H264_MP4: + case VIDEO_H264_FLV: + codec_id = AV_CODEC_ID_H264; + break; + case VIDEO_H265: + case VIDEO_H265_MP4: + codec_id = AV_CODEC_ID_HEVC; + break; + default: + err::check_raise(err::ERR_RUNTIME, "Unsupported video type!"); + } + return codec_id; + } + + static enum AVPixelFormat _image_format_to_ffmpeg(image::Format format) { + enum AVPixelFormat codec_fmt = AV_PIX_FMT_NONE; + switch (format) { + case image::Format::FMT_YVU420SP: + codec_fmt = AV_PIX_FMT_NV21; + break; + default: + err::check_raise(err::ERR_RUNTIME, "Unsupported video type!"); + } + return codec_fmt; + } + + double timebase_to_us(std::vector timebase, uint64_t value) { + return value * 1000000 / ((double)timebase[1] / timebase[0]); + } + + double timebase_to_ms(std::vector timebase, uint64_t value) { + return value * 1000 / ((double)timebase[1] / timebase[0]); + } + + uint64_t ms_to_pts(std::vector timebase, uint64_t ms) { + return ms * ((double)timebase[1] / timebase[0]) / 1000; + } + + typedef struct { + AVFormatContext *outputFormatContext; + AVStream *outputStream; + AVPacket *pPacket; + bool find_sps_pps; + bool copy_sps_pps_per_iframe; + uint64_t frame_index; + uint64_t video_frame_last_ms; + uint64_t last_encode_ms; + AVPacket *video_last_packet; + std::list *video_packet_list; + video::VideoType video_type; + int venc_ch; + + AVStream *audio_stream = NULL; + AVCodecContext *audio_codec_ctx = NULL; + AVCodec *audio_codec = NULL; + AVFrame *audio_frame = NULL; + SwrContext *swr_ctx = NULL; + AVPacket *audio_packet = NULL; + uint64_t audio_last_pts = 0; + std::list *pcm_list; + int audio_sample_rate; + int audio_channels; + int audio_bitrate; + enum AVSampleFormat audio_format; + } encoder_param_t; + + Encoder::Encoder(std::string path, int width, int height, image::Format format, VideoType type, int framerate, int gop, int bitrate, int time_base, bool capture, bool block) { + _path = path; + _width = width; + _height = height; + _format = format; + _type = type; + _framerate = framerate; + _gop = gop; + _bitrate = bitrate; + _time_base = time_base; + _need_capture = capture; + _capture_image = NULL; + _camera = NULL; + _bind_camera = NULL; + _start_encode_ms = 0; + _encode_started = false; + _block = block; + + err::check_bool_raise(format == image::Format::FMT_YVU420SP, "Encoder only support FMT_YVU420SP format!"); + video::VideoType video_type = _get_video_type(path.c_str(), type); + + if (_path.size() == 0) { + switch (_type) { + case VIDEO_H264: + { + + break; + } + case VIDEO_H265: + { + + break; + } + default: + std::string err_str = "Encoder not support type: " + std::to_string(_type); + err::check_raise(err::ERR_RUNTIME, err_str); + } + } else { + // ffmpeg init + av_log_set_callback(custom_log_callback); + _param = (encoder_param_t *)malloc(sizeof(encoder_param_t)); + err::check_null_raise(_param, "malloc failed!"); + memset(_param, 0, sizeof(encoder_param_t)); + + AVFormatContext *outputFormatContext = NULL; + if (0 != avformat_alloc_output_context2(&outputFormatContext, NULL, NULL, _path.c_str())) { + log::error("Count not open file: %s", _path.c_str()); + err::check_raise(err::ERR_RUNTIME, "Could not open file"); + } + + /* video init */ + AVStream *outputStream = avformat_new_stream(outputFormatContext, NULL); + err::check_null_raise(outputStream, "create new stream failed"); + + outputStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + outputStream->codecpar->codec_id = _video_type_to_ffmpeg(video_type); + outputStream->codecpar->width = _width; + outputStream->codecpar->height = _height; + outputStream->codecpar->format = _image_format_to_ffmpeg(_format); + outputStream->time_base = (AVRational){1, _framerate}; + outputStream->codecpar->bit_rate = _bitrate; + + if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) { + if (avio_open(&outputFormatContext->pb, _path.c_str(), AVIO_FLAG_WRITE) < 0) { + log::error("Count not open file: %s", _path.c_str()); + err::check_raise(err::ERR_RUNTIME, "Could not open file"); + } + } + + AVPacket *pPacket = av_packet_alloc(); + err::check_null_raise(pPacket, "malloc failed!"); + + // venc init + + /* audio init */ + AVStream *audio_stream = NULL; + AVCodecContext *audio_codec_ctx = NULL; + AVCodec *audio_codec = NULL; + AVFrame *audio_frame = NULL; + SwrContext *swr_ctx = NULL; + AVPacket *audio_packet = NULL; + + audio_packet = av_packet_alloc(); + err::check_null_raise(audio_packet, "av_packet_alloc"); + audio_packet->data = NULL; + audio_packet->size = 0; + + int sample_rate = 48000; + int channels = 1; + int bitrate = 128000; + enum AVSampleFormat format = AV_SAMPLE_FMT_S16; + err::check_null_raise(audio_codec = avcodec_find_encoder(AV_CODEC_ID_AAC), "Could not find aac encoder"); + err::check_null_raise(audio_stream = avformat_new_stream(outputFormatContext, NULL), "Could not allocate stream"); + err::check_null_raise(audio_codec_ctx = avcodec_alloc_context3(audio_codec), "Could not allocate audio codec context"); + audio_codec_ctx->codec_id = AV_CODEC_ID_AAC; + audio_codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO; + audio_codec_ctx->sample_rate = sample_rate; + audio_codec_ctx->channels = channels; + audio_codec_ctx->channel_layout = av_get_default_channel_layout(audio_codec_ctx->channels); + audio_codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP; // AAC编码需要浮点格式 + audio_codec_ctx->time_base = (AVRational){1, sample_rate}; + audio_codec_ctx->bit_rate = bitrate; + audio_stream->time_base = audio_codec_ctx->time_base; + err::check_bool_raise(avcodec_parameters_from_context(audio_stream->codecpar, audio_codec_ctx) >= 0, "avcodec_parameters_to_context"); + err::check_bool_raise(avcodec_open2(audio_codec_ctx, audio_codec, NULL) >= 0, "audio_codec open failed"); + + swr_ctx = swr_alloc(); + av_opt_set_int(swr_ctx, "in_channel_layout", audio_codec_ctx->channel_layout, 0); + av_opt_set_int(swr_ctx, "out_channel_layout", audio_codec_ctx->channel_layout, 0); + av_opt_set_int(swr_ctx, "in_sample_rate", audio_codec_ctx->sample_rate, 0); + av_opt_set_int(swr_ctx, "out_sample_rate", audio_codec_ctx->sample_rate, 0); + av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", format, 0); + av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); + swr_init(swr_ctx); + + int frame_size = audio_codec_ctx->frame_size; + audio_frame = av_frame_alloc(); + audio_frame->nb_samples = frame_size; + audio_frame->channel_layout = audio_codec_ctx->channel_layout; + audio_frame->format = AV_SAMPLE_FMT_FLTP; + audio_frame->sample_rate = audio_codec_ctx->sample_rate; + av_frame_get_buffer(audio_frame, 0); + + err::check_bool_raise(avformat_write_header(outputFormatContext, NULL) >= 0, "avformat_write_header failed!"); + + /* param init */ + encoder_param_t *param = (encoder_param_t *)_param; + param->outputFormatContext = outputFormatContext; + param->outputStream = outputStream; + + // video init + param->pPacket = pPacket; + param->video_type = video_type; + param->find_sps_pps = false; + param->frame_index = 0; + param->last_encode_ms = time::ticks_ms(); + param->video_packet_list = new std::list; + switch (video_type) { + case VIDEO_H264: + param->copy_sps_pps_per_iframe = true; + break; + case VIDEO_H264_MP4: + param->copy_sps_pps_per_iframe = false; + break; + case VIDEO_H264_FLV: + param->copy_sps_pps_per_iframe = false; + break; + case VIDEO_H265: + param->copy_sps_pps_per_iframe = true; + break; + case VIDEO_H265_MP4: + param->copy_sps_pps_per_iframe = false; + break; + default: + err::check_raise(err::ERR_RUNTIME, "Unsupported video type!"); + } + + // audio init + param->audio_stream = audio_stream; + param->audio_codec_ctx = audio_codec_ctx; + param->audio_codec = audio_codec; + param->audio_frame = audio_frame; + param->swr_ctx = swr_ctx; + param->audio_packet = audio_packet; + param->audio_last_pts = 0; + param->pcm_list = new std::list; + param->audio_sample_rate = 48000; + param->audio_channels = 1; + param->audio_bitrate = 128000; + param->audio_format = AV_SAMPLE_FMT_S16; + } + } + + Encoder::~Encoder() { + if (_path.size() == 0) { + switch (_type) { + case VIDEO_H264: + { + + break; + } + case VIDEO_H265: + { + + break; + } + default: + std::string err_str = "Encoder not support type: " + std::to_string(_type); + err::check_raise(err::ERR_RUNTIME, err_str); + } + + if (_capture_image && _capture_image->data()) { + delete _capture_image; + _capture_image = nullptr; + } + } else { + encoder_param_t *param = (encoder_param_t *)_param; + if (param) { + + av_write_trailer(param->outputFormatContext); + + for (auto it = param->pcm_list->begin(); it != param->pcm_list->end(); ++it) { + Bytes *pcm = *it; + delete pcm; + it = param->pcm_list->erase(it); + } + delete param->pcm_list; + av_frame_free(¶m->audio_frame); + swr_free(¶m->swr_ctx); + avcodec_free_context(¶m->audio_codec_ctx); + + avformat_close_input(¶m->outputFormatContext); + if (param->outputFormatContext && !(param->outputFormatContext->oformat->flags & AVFMT_NOFILE)) { + avio_closep(¶m->outputFormatContext->pb); + } + avformat_free_context(param->outputFormatContext); + av_packet_unref(param->pPacket); + av_packet_free(¶m->pPacket); + + free(_param); + _param = NULL; + } + + if (_capture_image && _capture_image->data()) { + delete _capture_image; + _capture_image = nullptr; + } + + system("sync"); + } + } + + err::Err Encoder::bind_camera(camera::Camera *camera) { + err::Err err = err::ERR_NONE; + if (camera->format() != image::Format::FMT_YVU420SP) { + err::check_raise(err::ERR_RUNTIME, "bind camera failed! support FMT_YVU420SP only!\r\n"); + return err::ERR_RUNTIME; + } + + this->_camera = camera; + this->_bind_camera = true; + return err; + } + + video::Frame *Encoder::encode(image::Image *img, Bytes *pcm) { + + return nullptr; + } + + typedef enum { + VIDEO_FORMAT_NONE, + VIDEO_FORMAT_H264, + VIDEO_FORMAT_H264_MP4, + VIDEO_FORMAT_H264_FLV, + VIDEO_FORMAT_MAX + } video_format_e; + + typedef struct { + AVFormatContext *pFormatContext; + + // video + bool find_video; + AVPacket *pPacket; + AVBSFContext * bsfc; + AVCodecContext *codec_ctx; + int video_stream_index; + video_format_e video_format; + int vdec_ch; + std::list *ctx_list; + uint64_t next_pts; + + // audio + bool find_audio; + int audio_stream_index; + int sample_rate; + int channels; + enum AVSampleFormat sample_fmt; + int resample_sample_rate; + int resample_channels; + enum AVSampleFormat resample_sample_format; + AVCodecContext *audio_codec_ctx = NULL; + // AVPacket *audio_packet = NULL; + AVFrame *audio_frame = NULL; + SwrContext *swr_ctx = NULL; + } decoder_param_t; + + // static video_format_e _get_video_format(const char *filename, PAYLOAD_TYPE_E type) { + // video_format_e format = VIDEO_FORMAT_NONE; + // const char *suffix = strrchr(filename, '.'); + // err::check_null_raise((void *)suffix, "Try a file format with a suffix, e.g. video.h264/video.mp4/video.flv"); + + // if (!strcmp(suffix, ".h264")) { + // format = VIDEO_FORMAT_H264; + // } else if (!strcmp(suffix, ".mp4")) { + // // if (type == PT_H264) { + // // format = VIDEO_FORMAT_H264_MP4; + // // } + // } else if (!strcmp(suffix, ".flv")) { + // // if (type == PT_H264) { + // // format = VIDEO_FORMAT_H264_FLV; + // // } + // } else { + // err::check_raise(err::ERR_RUNTIME, "Currently only support avc/avc-mp4/avc-flv format!"); + // } + + // err::check_bool_raise(format != VIDEO_FORMAT_NONE, "Not found a valid video format!"); + // return format; + // } + + + static int check_stream_specifier(AVFormatContext *s, AVStream *st, const char *spec) + { + int ret = avformat_match_stream_specifier(s, st, spec); + if (ret < 0) + av_log(s, AV_LOG_ERROR, "Invalid stream specifier: %s.\n", spec); + return ret; + } + + static AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id, + AVFormatContext *s, AVStream *st, const AVCodec *codec) + { + AVDictionary *ret = NULL; + AVDictionaryEntry *t = NULL; + int flags = s->oformat ? AV_OPT_FLAG_ENCODING_PARAM + : AV_OPT_FLAG_DECODING_PARAM; + char prefix = 0; + const AVClass *cc = avcodec_get_class(); + + if (!codec) + codec = s->oformat ? avcodec_find_encoder(codec_id) + : avcodec_find_decoder(codec_id); + + switch (st->codecpar->codec_type) { + case AVMEDIA_TYPE_VIDEO: + prefix = 'v'; + flags |= AV_OPT_FLAG_VIDEO_PARAM; + break; + case AVMEDIA_TYPE_AUDIO: + prefix = 'a'; + flags |= AV_OPT_FLAG_AUDIO_PARAM; + break; + case AVMEDIA_TYPE_SUBTITLE: + prefix = 's'; + flags |= AV_OPT_FLAG_SUBTITLE_PARAM; + break; + default: + break; + } + + while ((t = av_dict_get(opts, "", t, AV_DICT_IGNORE_SUFFIX))) { + const AVClass *priv_class; + char *p = strchr(t->key, ':'); + + /* check stream specification in opt name */ + if (p) + switch (check_stream_specifier(s, st, p + 1)) { + case 1: *p = 0; break; + case 0: continue; + default: return NULL; + } + + if (av_opt_find(&cc, t->key, NULL, flags, AV_OPT_SEARCH_FAKE_OBJ) || + !codec || + ((priv_class = codec->priv_class) && + av_opt_find(&priv_class, t->key, NULL, flags, + AV_OPT_SEARCH_FAKE_OBJ))) + av_dict_set(&ret, t->key, t->value, 0); + else if (t->key[0] == prefix && + av_opt_find(&cc, t->key + 1, NULL, flags, + AV_OPT_SEARCH_FAKE_OBJ)) + av_dict_set(&ret, t->key + 1, t->value, 0); + + if (p) + *p = ':'; + } + return ret; + } + + static AVDictionary **setup_find_stream_info_opts(AVFormatContext *s, + AVDictionary *codec_opts) + { + unsigned int i; + AVDictionary **opts; + + if (!s->nb_streams) + return NULL; + opts = (AVDictionary **)av_mallocz_array(s->nb_streams, sizeof(*opts)); + if (!opts) { + av_log(NULL, AV_LOG_ERROR, + "Could not alloc memory for stream options.\n"); + return NULL; + } + for (i = 0; i < s->nb_streams; i++) + opts[i] = filter_codec_opts(codec_opts, s->streams[i]->codecpar->codec_id, + s, s->streams[i], NULL); + return opts; + } + static audio::Format _audio_format_from_alsa(enum AVSampleFormat format) { + switch (format) { + case AV_SAMPLE_FMT_NONE: return audio::FMT_NONE; + case AV_SAMPLE_FMT_U8: return audio::FMT_S8; + case AV_SAMPLE_FMT_S16: return audio::FMT_S16_LE; + case AV_SAMPLE_FMT_S32: return audio::FMT_S32_LE; + default: { + log::error("Not support format %s", av_get_sample_fmt_name(format)); + err::check_raise(err::ERR_NOT_IMPL); + } + } + return audio::FMT_NONE; + } + + static enum AVSampleFormat _audio_format_to_alsa(audio::Format format) { + switch (format) { + case audio::FMT_NONE: return AV_SAMPLE_FMT_NONE; + case audio::FMT_S8: return AV_SAMPLE_FMT_U8; + case audio::FMT_S16_LE: return AV_SAMPLE_FMT_S16; + case audio::FMT_S32_LE: return AV_SAMPLE_FMT_S32; + default: { + err::check_raise(err::ERR_NOT_IMPL); + } + } + return AV_SAMPLE_FMT_NONE; + } + + Decoder::Decoder(std::string path, image::Format format) { + + } + + static int _release_video_ctx_list(std::list *list) + { + if (list == NULL) return -1; + + std::list::iterator iter; + for(iter=list->begin();iter!=list->end();iter++) { + video::Context *ctx = *iter; + delete ctx; + iter = list->erase(iter); + } + + return 0; + } + + Decoder::~Decoder() { + + } + + + typedef struct { + AVPacket *pPacket; + uint8_t *data; + uint8_t *end; + } find_frame_iterator_t; + + static int _create_find_frame_iterator(find_frame_iterator_t *handler, AVPacket *pPacket) + { + if (handler == NULL || pPacket == NULL) return -1; + + memset(handler, 0, sizeof(find_frame_iterator_t)); + handler->pPacket = pPacket; + handler->data = pPacket->data; + handler->end = handler->data + pPacket->size; + return 0; + } + + // return 0: success, -1: failed + static int _find_frame_iterator(find_frame_iterator_t *handler, uint8_t *type, uint8_t **data, size_t *data_size) + { + if (handler == NULL || type == NULL || data == NULL || data_size == NULL) return -1; + bool found_start = false; + uint8_t *start = NULL; + uint8_t nal_type = 0; + while (handler->data < handler->end) { + if (handler->data + 3 < handler->end && handler->data[0] == 0 && handler->data[1] == 0 && handler->data[2] == 1) { + if (!found_start) { + start = handler->data; + handler->data += 3; + nal_type = handler->data[0] & 0x1F; + found_start = true; + } else { + break; + } + } else if (handler->data + 4 < handler->end && handler->data[0] == 0 && handler->data[1] == 0 && handler->data[2] == 0 && handler->data[3] == 1) { + if (!found_start) { + start = handler->data; + handler->data += 4; + nal_type = handler->data[0] & 0x1F; + found_start = true; + } else { + break; + } + } + handler->data++; + } + + if (found_start) { + *type = nal_type; + *data = start; + *data_size = handler->data - start; + return 0; + } + + return -1; + } + + video::Context *Decoder::decode_video(bool block) { + return nullptr; + } + + video::Context *Decoder::decode_audio() { + return nullptr; + } + + video::Context *Decoder::decode(bool block) { + return nullptr; + } + + video::Context *Decoder::unpack() { + return nullptr; + } + + double Decoder::seek(double time) { + return 0; + } + + double Decoder::duration() { + decoder_param_t *param = (decoder_param_t *)_param; + AVFormatContext *pFormatContext = param->pFormatContext; + video_format_e video_format = param->video_format; + if (video_format != VIDEO_FORMAT_H264_FLV && video_format != VIDEO_FORMAT_H264_MP4) { + return 0; + } + int64_t duration = pFormatContext->duration; + double duration_in_seconds = (double)duration / AV_TIME_BASE; + return duration_in_seconds; + } + + Video::Video(std::string path, int width, int height, image::Format format, int time_base, int framerate, bool capture, bool open) + { + this->_pre_path = path; + this->_video_type = VIDEO_NONE; + this->_bind_camera = false; + this->_is_recording = false; + this->_camera = NULL; + this->_fd = -1; + this->_time_base = time_base; + this->_framerate = framerate; + this->_need_auto_config = true; + this->_pre_width = width; + this->_pre_height = height; + this->_last_pts = 0; + this->_capture_image = nullptr; + this->_need_capture = capture; + this->_is_opened = false; + + if (open) { + err::check_bool_raise(err::ERR_NONE == this->open(), "Video open failed!\r\n"); + } + } + + Video::~Video() { + if (this->_is_opened) { + this->close(); + } + } + + err::Err Video::open(std::string path, double fps) + { + if (this->_is_opened) { + return err::ERR_NONE; + } + + if (path == std::string()) { + this->_path = this->_pre_path; + } else { + this->_path = path; + } + + if (fps == 30.0) { + this->_fps = this->_pre_fps; + } else { + this->_fps = fps; + } + + this->_is_opened = true; + return err::ERR_NONE; + } + + void Video::close() + { + + if (_capture_image && _capture_image->data()) { + delete _capture_image; + _capture_image = nullptr; + } + this->_is_opened = false; + } + + err::Err Video::bind_camera(camera::Camera *camera) { + err::Err err = err::ERR_NONE; + + if (camera->format() != image::Format::FMT_YVU420SP) { + err::check_raise(err::ERR_RUNTIME, "bind camera failed! support FMT_YVU420SP only!\r\n"); + return err::ERR_RUNTIME; + } + + this->_camera = camera; + this->_bind_camera = true; + return err; + } + + static video::VideoType get_video_type(std::string &path, bool encode) + { + video::VideoType video_type; + std::string ext; + size_t pos = path.rfind('.'); + if (pos != std::string::npos) { + ext = path.substr(pos); + } + + if (ext.find(".h265") != std::string::npos) { + if (encode) { + video_type = VIDEO_ENC_H265_CBR; + } else { + video_type = VIDEO_DEC_H265_CBR; + } + } else if (ext.find(".mp4") != std::string::npos) { + if (encode) { + video_type = VIDEO_ENC_MP4_CBR; + } else { + video_type = VIDEO_DEC_MP4_CBR; + } + } else { + log::error("Video not support %s!\r\n", ext.c_str()); + video_type = VIDEO_NONE; + } + + return video_type; + } + + video::Packet *Video::encode(image::Image *img) { + return nullptr; + } + + image::Image *Video::decode(video::Frame *frame) { + return NULL; + } + + err::Err Video::finish() { + if (this->_fd > 2) { + ::close(this->_fd); + + switch (this->_video_type) { + case VIDEO_ENC_H265_CBR: + // do nothing + break; + case VIDEO_ENC_MP4_CBR: + { + char cmd[128]; + snprintf(cmd, sizeof(cmd), "ffmpeg -loglevel quiet -i %s -c:v copy -c:a copy %s -y", this->_tmp_path.c_str(), this->_path.c_str()); + system(cmd); + + snprintf(cmd, sizeof(cmd), "rm %s", this->_tmp_path.c_str()); + system(cmd); + } + break; + default: + log::error("%s not support\r\n", this->_video_type); + } + system("sync"); + } + return err::ERR_NONE; + } + + typedef enum { + VIDEO_RECORDER_IDLE = 0, + VIDEO_RECORDER_RECORD, // record and display + VIDEO_RECORDER_UNKNOWN, + VIDEO_RECORDER_DISPLAY_ONLY, + } video_recoder_state_t; + + class rect_info { + public: + int id; + int x; + int y; + int w; + int h; + image::Color color = image::Color(255); + int thickness; + bool show = false; + }; + + typedef struct { + VideoRecorder *obj; + pthread_mutex_t lock; + video_recoder_state_t state; + std::string path; + bool snapshot_en; + std::vector snapshot_res; + image::Format snapshot_fmt; + image::Image *snapshot_img; + int64_t seek_start_ms; + int64_t seek_ms; + + encoder_param_t packager; + pthread_t thread; + bool thread_exit_flag; + + struct { + int ch; + int fps; + int bitrate; + std::vector resolution; + } venc; + + struct { + camera::Camera *obj; + camera::Camera *obj2; + } camera; + + struct { + display::Display *obj; + int fit; + } display; + + struct { + audio::Recorder *obj; + bool mute; + } audio; + + struct { + void *obj; + void *gcsv; + std::string gcsv_path; + pthread_t imu_thread; + } imu; + + std::vector rect; + } video_recoder_param_t; + + static void _video_recoder_config_default(video_recoder_param_t *param) + { + + } + + VideoRecorder::VideoRecorder(bool open) + { + video_recoder_param_t *param = (video_recoder_param_t *)calloc(1, sizeof(video_recoder_param_t)); + err::check_null_raise(param, "malloc param failed"); + + _is_opened = false; + _param = param; + + if (open) { + this->open(); + } + } + + VideoRecorder::~VideoRecorder() + { + close(); + + if (_param) { + free(_param); + _param = nullptr; + } + } + + + static void nv21_fill_rect(uint8_t *yuv, int width, int height, int x1, int y1, int rect_w, int rect_h, uint8_t y_value, uint8_t u_value, uint8_t v_value) + { + for (int h = y1; h < y1 + rect_h; h++) { + for (int w = x1; w < x1 + rect_w; w++) { + int index = h * width + w; + yuv[index] = y_value; + } + } + + for (int h = y1; h < y1 + rect_h; h+=2) { + for (int w = x1; w < x1 + rect_w; w+=2) { + int uv_index = width * height + (h / 2) * width + w; + yuv[uv_index] = v_value; + yuv[uv_index + 1] = u_value; + } + } + } + + static void nv21_draw_rectangle(uint8_t *yuv, int width, int height, int x, int y, int rect_width, int rect_height, uint8_t y_value, uint8_t u_value, uint8_t v_value, int thickness) { + + int tmp_x = 0, tmp_y = 0, tmp_rect_width = 0, tmp_rect_height = 0; + x = (x % 2 == 0) ? x : x - 1; + x = x < 0 ? 0 : x; + x = x >= width ? width - 2 : x; + y = (y % 2 == 0) ? y : y - 1; + y = y < 0 ? 0 : y; + y = y >= height ? height - 2 : y; + rect_width = (rect_width % 2 == 0) ? rect_width : rect_width - 1; + rect_width = (rect_width + x) < width ? rect_width : width - x; + rect_width = rect_width < 0 ? 0 : rect_width; + rect_height = (rect_height % 2 == 0) ? rect_height : rect_height - 1; + rect_height = (rect_height + y) < height ? rect_height : height - y; + rect_height = rect_height < 0 ? 0 : rect_height; + + if (thickness < 0) { + tmp_x = x; + tmp_y = y; + tmp_rect_width = rect_width; + tmp_rect_height = rect_height; + nv21_fill_rect(yuv, width, height, x, y, tmp_rect_width, tmp_rect_height, y_value, u_value, v_value); // upper + } else { + thickness = (thickness % 2 == 0) ? thickness : thickness + 1; + thickness = thickness < 0 ? 0 : thickness; + + tmp_x = x; + tmp_y = y; + tmp_rect_width = rect_width; + tmp_rect_height = thickness; + nv21_fill_rect(yuv, width, height, tmp_x, tmp_y, tmp_rect_width, tmp_rect_height, y_value, u_value, v_value); // upper + + tmp_x = x; + tmp_y = (y + rect_height - thickness) >= 0 ? (y + rect_height - thickness) : 0; + tmp_rect_width = rect_width; + tmp_rect_height = thickness; + nv21_fill_rect(yuv, width, height, tmp_x, tmp_y, tmp_rect_width, tmp_rect_height, y_value, u_value, v_value); // lower + + tmp_x = x; + tmp_y = (y + thickness) < height ? (y + thickness) : height - thickness; + tmp_rect_width = thickness; + tmp_rect_height = rect_height - thickness * 2; + nv21_fill_rect(yuv, width, height, tmp_x, tmp_y, tmp_rect_width, tmp_rect_height, y_value, u_value, v_value); // left + + + tmp_x = (x + rect_width - thickness) < width ? (x + rect_width - thickness) : width - thickness; + tmp_y = (y + thickness) < height ? (y + thickness) : height - thickness; + tmp_rect_width = thickness; + tmp_rect_height = rect_height - thickness * 2; + nv21_fill_rect(yuv, width, height, tmp_x, tmp_y, tmp_rect_width, tmp_rect_height, y_value, u_value, v_value); // right + } + } + + static void _draw_rect_on_nv21(uint8_t *yuv, int width, int height, video_recoder_param_t *param) + { + for (size_t i = 0; i < param->rect.size(); i++) { + if (!param->rect[i].show) { + continue; + } + + int x = param->rect[i].x; + int y = param->rect[i].y; + int w = param->rect[i].w; + int h = param->rect[i].h; + int thickness = param->rect[i].thickness; + + int r = param->rect[i].color.r; + int g = param->rect[i].color.g; + int b = param->rect[i].color.b; + int y_value = (int)(0.299 * r + 0.587 * g + 0.114 * b); + int u_value = (int)(-0.14713 * r - 0.28886 * g + 0.436 * b) + 128; + int v_value = (int)(0.615 * r - 0.51499 * g - 0.10001 * b) + 128; + nv21_draw_rectangle(yuv, width, height, x, y, w, h, y_value, u_value, v_value, thickness); + } + } + + static void *record_thread_handle(void *par) + { + return NULL; + } + + err::Err VideoRecorder::open() + { + + return err::ERR_NONE; + } + + err::Err VideoRecorder::close() + { + + return err::ERR_NONE; + } + + err::Err VideoRecorder::lock(int64_t timeout) + { + video_recoder_param_t *param = (video_recoder_param_t *)_param; + + int res = -1; + if (timeout < 0) { + while (0 != (res = pthread_mutex_lock(¶m->lock))); + } else { + uint64_t start_ms = time::ticks_ms(); + while (0 != (res = pthread_mutex_lock(¶m->lock))) { + if ((int64_t)(time::ticks_ms() - start_ms) >= timeout) { + break; + } else { + time::sleep_ms(1); + } + } + } + + return res == 0 ? err::ERR_NONE : err::ERR_TIMEOUT; + } + + err::Err VideoRecorder::unlock() + { + video_recoder_param_t *param = (video_recoder_param_t *)_param; + int res = pthread_mutex_unlock(¶m->lock); + return res == 0 ? err::ERR_NONE : err::ERR_RUNTIME; + } + + err::Err VideoRecorder::bind_display(display::Display *display, image::Fit fit) + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + param->display.obj = display; + int new_fit = 0; + switch (fit) { + case image::Fit::FIT_FILL: new_fit = 0; break; + case image::Fit::FIT_CONTAIN: new_fit = 1; break; + case image::Fit::FIT_COVER: new_fit = 2; break; + default: new_fit = 2; break; + } + param->display.fit = new_fit; + unlock(); + return param->display.obj ? err::ERR_NONE : err::ERR_ARGS; + } + + err::Err VideoRecorder::bind_camera(camera::Camera *camera) + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + param->camera.obj = camera; + + if (camera) { + err::check_bool_raise(camera->format() == image::FMT_YVU420SP, "camera format must be FMTYVU420SP"); + } + + unlock(); + return param->camera.obj ? err::ERR_NONE : err::ERR_ARGS; + } + + err::Err VideoRecorder::bind_audio(audio::Recorder *audio) + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + param->audio.obj = audio; + unlock(); + return param->audio.obj ? err::ERR_NONE : err::ERR_ARGS; + } + + err::Err VideoRecorder::bind_imu(void *imu) + { + err::check_raise(err::Err::ERR_NOT_IMPL, "Not supported"); + // lock(); + // video_recoder_param_t *param = (video_recoder_param_t *)_param; + // param->imu.obj = imu; + // param->imu.gcsv = new ext_dev::imu::Gcsv(); + // err::check_null_raise(param->imu.gcsv, "create gcsv failed!"); + // unlock(); + // return param->imu.obj ? err::ERR_NONE : err::ERR_ARGS; + return err::ERR_NOT_IMPL; + } + + err::Err VideoRecorder::reset() + { + record_finish(); + + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + _video_recoder_config_default(param); + unlock(); + return err::ERR_NONE; + } + + err::Err VideoRecorder::config_path(std::string path) + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + if (param->state != VIDEO_RECORDER_IDLE) { + unlock(); + return err::ERR_BUSY; + } + + param->path = path; + unlock(); + return err::ERR_NONE; + } + + std::string VideoRecorder::get_path() + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + std::string path = param->path; + unlock(); + return path; + } + + err::Err VideoRecorder::config_snapshot(bool enable, std::vector resolution, image::Format format) + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + if (param->state != VIDEO_RECORDER_IDLE) { + unlock(); + return err::ERR_BUSY; + } + + camera::Camera *cam = param->camera.obj; + if (!cam) { + log::error("You must use the bind_camera interface to bind a Camera object."); + return err::ERR_RUNTIME; + } + + if (enable) { + if (resolution.size() < 2) { + param->snapshot_res = {cam->width(), cam->height()}; + } else { + param->snapshot_res = resolution; + } + param->snapshot_fmt = format; + + if (param->snapshot_en) { + if (param->camera.obj2) { + delete param->camera.obj2; + param->camera.obj2 = NULL; + } + } + param->camera.obj2 = cam->add_channel(param->snapshot_res[0], param->snapshot_res[1], param->snapshot_fmt); + err::check_null_raise(param->camera.obj2, "camera add channel failed!"); + + param->snapshot_en = true; + } else { + if (param->camera.obj2) { + delete param->camera.obj2; + param->camera.obj2 = NULL; + } + + if (param->snapshot_img) { + delete param->snapshot_img; + param->snapshot_img = NULL; + } + param->snapshot_en = false; + } + + unlock(); + return err::ERR_NONE; + } + + err::Err VideoRecorder::config_resolution(std::vector resolution) + { + if (resolution.size() < 2) return err::ERR_ARGS; + + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + if (param->state != VIDEO_RECORDER_IDLE) { + unlock(); + return err::ERR_BUSY; + } + + camera::Camera *cam = param->camera.obj; + if (!cam) { + log::error("You must use the bind_camera interface to bind a Camera object."); + return err::ERR_RUNTIME; + } + + cam->set_resolution(resolution[0], resolution[1]); + + param->venc.resolution = resolution; + unlock(); + return err::ERR_NONE; + } + + std::vector VideoRecorder::get_resolution() + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + std::vector resolution; + if (param) { + resolution = param->venc.resolution; + if (resolution.size() == 0) { + if (param->camera.obj) { + resolution = {param->camera.obj->width(), param->camera.obj->height()}; + } + } + + err::check_bool_raise(resolution.size() == 2, "You need config resolution!"); + } + unlock(); + + return resolution; + } + + err::Err VideoRecorder::config_fps(int fps) + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + if (param->state != VIDEO_RECORDER_IDLE) { + unlock(); + return err::ERR_BUSY; + } + + camera::Camera *cam = param->camera.obj; + if (!cam) { + log::error("You must use the bind_camera interface to bind a Camera object."); + return err::ERR_RUNTIME; + } + + cam->set_fps(fps); + + param->venc.fps = fps; + unlock(); + return err::ERR_NONE; + } + + int VideoRecorder::get_fps() + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + int fps = param->venc.fps; + unlock(); + return fps; + } + + err::Err VideoRecorder::config_bitrate(int bitrate) + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + if (param->state != VIDEO_RECORDER_IDLE) { + unlock(); + return err::ERR_BUSY; + } + + param->venc.bitrate = bitrate; + unlock(); + return err::ERR_NONE; + } + + int VideoRecorder::get_bitrate() + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + int bitrate = param->venc.bitrate; + unlock(); + return bitrate; + } + + int VideoRecorder::mute(int data) + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + int current_mute = 0; + if (param->audio.obj) { + if (data >= 0) { + current_mute = param->audio.obj->mute(data); + } else { + current_mute = param->audio.obj->mute(); + } + } + unlock(); + + return current_mute; + } + + int VideoRecorder::volume(int data) + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + int current_volume = 0; + if (data >= 0) { + data = data >= 100 ? 100 : data; + param->audio.obj->volume(data); + } else { + current_volume = param->audio.obj->volume(); + } + unlock(); + + return current_volume; + } + + int64_t VideoRecorder::seek() + { + // Only read param, so don't lock + video_recoder_param_t *param = (video_recoder_param_t *)_param; + return param->seek_ms; + } + + // static void *_imu_thread_process(void *arg) { + // video_recoder_param_t *param = (video_recoder_param_t *)arg; + // ext_dev::imu::IMU *imu = param->imu.obj; + // ext_dev::imu::Gcsv *gcsv = param->imu.gcsv; + // uint64_t first_write_ms = time::ticks_ms(); + // while (!app::need_exit()) { + // if (param->state == VIDEO_RECORDER_IDLE) { + // break; + // } + + // if (imu && gcsv) { + // auto res = imu->read(); + // // log::info("------------------------"); + // // printf("acc x: %f\t", res[0]); + // // printf("acc y: %f\t", res[1]); + // // printf("acc z: %f\n", res[2]); + // // printf("gyro x: %f\t", res[3]); + // // printf("gyro y: %f\t", res[4]); + // // printf("gyro z: %f\n", res[5]); + // // printf("temp: %f\n", res[6]); + // // log::info("------------------------\n"); + + // double t = (double)(time::ticks_ms() - first_write_ms); + // std::vector g = {res[3] * M_PI / 180, res[4] * M_PI / 180, res[5] * M_PI / 180}; + // std::vector a = {res[0], res[1], res[2]}; + // gcsv->write(t, g, a); + // } + + // time::sleep_ms(1); + // } + // return NULL; + // } + + err::Err VideoRecorder::record_start() + { + + return err::ERR_NONE; + } + + image::Image *VideoRecorder::snapshot() + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + image::Image *new_image = NULL; + if (param->snapshot_img) { + new_image = param->snapshot_img->copy(); + delete param->snapshot_img; + param->snapshot_img = NULL; + } + unlock(); + return new_image; + } + + err::Err VideoRecorder::record_finish() + { + + + return err::ERR_NONE; + } + + err::Err VideoRecorder::draw_rect(int id, int x, int y, int w, int h, image::Color color, int thickness, bool hidden) + { + lock(); + video_recoder_param_t *param = (video_recoder_param_t *)_param; + if (id < 0 || id >= (int)param->rect.size()) { + log::error("draw_rect id %d out of range", id); + return err::ERR_ARGS; + } + + if (!param->camera.obj || param->camera.obj->format() != image::Format::FMT_YVU420SP) { + log::error("Drawing the box is only effective in NV21 format. Ensure that the camera's image format is set to image::Format::FMT_YVU420SP"); + return err::ERR_ARGS; + } + + param->rect[id].id = id; + param->rect[id].x = x; + param->rect[id].y = y; + param->rect[id].w = w; + param->rect[id].h = h; + param->rect[id].color = color; + param->rect[id].thickness = thickness; + param->rect[id].show = !hidden; + + unlock(); + return err::ERR_NOT_IMPL; + } +} // namespace maix::video diff --git a/components/vision/src/maix_display.cpp b/components/vision/src/maix_display.cpp index daac9108..1e54dcb3 100644 --- a/components/vision/src/maix_display.cpp +++ b/components/vision/src/maix_display.cpp @@ -18,6 +18,10 @@ #include "maix_display_mmf.hpp" #include "maix_display_fb.hpp" #endif +#ifdef PLATFORM_MAIXCAM2 + #include "maix_display_maixcam2.hpp" + #include "maix_display_fb.hpp" +#endif namespace maix::display { diff --git a/components/voice/CMakeLists.txt b/components/voice/CMakeLists.txt index 70d3b01a..1ed26dc9 100644 --- a/components/voice/CMakeLists.txt +++ b/components/voice/CMakeLists.txt @@ -5,7 +5,7 @@ list(APPEND ADD_INCLUDE "include" ) if(PLATFORM_LINUX) list(APPEND ADD_PRIVATE_INCLUDE "port/linux") -elseif(PLATFORM_MAIXCAM) +elseif(PLATFORM_MAIXCAM OR PLATFORM_MAIXCAM2) list(APPEND ADD_PRIVATE_INCLUDE "port/maixcam") endif() ############################################### @@ -20,7 +20,7 @@ endif() append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_SRCS if(PLATFORM_LINUX) append_srcs_dir(ADD_SRCS "port/linux") -elseif(PLATFORM_MAIXCAM) +elseif(PLATFORM_MAIXCAM OR PLATFORM_MAIXCAM2) append_srcs_dir(ADD_SRCS "port/maixcam") endif() # list(REMOVE_ITEM COMPONENT_SRCS "src/test.c") @@ -35,7 +35,7 @@ endif() list(APPEND ADD_REQUIREMENTS basic) if(PLATFORM_LINUX) list(APPEND ADD_REQUIREMENTS ) -elseif(PLATFORM_MAIXCAM) +elseif(PLATFORM_MAIXCAM OR PLATFORM_MAIXCAM2) list(APPEND ADD_REQUIREMENTS tinyalsa) endif() ############################################### diff --git a/platforms/maixcam2.yaml b/platforms/maixcam2.yaml index 9cd935c8..d9a656e8 100644 --- a/platforms/maixcam2.yaml +++ b/platforms/maixcam2.yaml @@ -10,5 +10,5 @@ toolchain: path: toolchains/maixcam2 bin_path: toolchains/maixcam2/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin prefix: aarch64-none-linux-gnu- - c_flags: '' - cxx_flags: '' \ No newline at end of file + c_flags: '-ldl' + cxx_flags: '-ldl' \ No newline at end of file