* update for ax630c

This commit is contained in:
lxowalle
2025-02-14 18:53:33 +08:00
parent 2e78cf7394
commit 0d20a27ef4
24 changed files with 5027 additions and 105 deletions

View File

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

View File

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

View File

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

View File

@@ -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': {}
}
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 <dirent.h>
namespace maix::camera
{
static bool set_regs_flag = false;
std::vector<std::string> list_devices()
{
log::warn("This device is not driven using device files!");
return std::vector<std::string>();
}
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<int> _get_lane_id_from_board_file() {
std::vector<int> 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<int> _get_pn_swap_from_board_file() {
std::vector<int> 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<bool> _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<bool, std::string> _get_sensor_name(void)
{
char name[30];
peripheral::i2c::I2C i2c_obj(4, peripheral::i2c::Mode::MASTER);
std::vector<int> 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<bool, std::string> 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<int> roi) {
if (!this->is_opened()) {
return err::ERR_NOT_OPEN;
}
return err::ERR_NONE;
}
std::vector<int> 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;
}
}

View File

@@ -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 <unistd.h>
#include "maix_pwm.hpp"
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#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<int> 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;
};
}

View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/stat.h>
#include <linux/socket.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <errno.h>
#include <netdb.h>
#define BOUNDARY "frame"
static const char *default_index_str =
"<html>\n"
"<body>\n"
"<h1>JPG Stream</h1>\n"
"<img src='/stream'>\n"
"</body>\n"
"</html>";
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;
}
}

View File

@@ -0,0 +1,791 @@
#include "maix_rtmp.hpp"
#include "maix_video.hpp"
#include "maix_basic.hpp"
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <list>
extern "C" {
#include <unistd.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libswresample/swresample.h>
}
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<std::pair<size_t, Bytes *>> *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<std::pair<size_t, Bytes *>>;
_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<int> get_video_timebase() {
ffmpeg_param_t *ffmpeg = (ffmpeg_param_t *)&_ffmpeg;
if (!_open || !ffmpeg->has_video)
return std::vector<int>();
return {ffmpeg->video_stream->time_base.num, ffmpeg->video_stream->time_base.den};
}
std::vector<int> get_audio_timebase() {
ffmpeg_param_t *ffmpeg = (ffmpeg_param_t *)&_ffmpeg;
if (!_open || !ffmpeg->has_audio)
return std::vector<int>();
return {ffmpeg->audio_stream->time_base.num, ffmpeg->audio_stream->time_base.den};
}
double timebase_to_us(std::vector<int> timebase, uint64_t value) {
if (timebase.size() < 2) return -1;
return value * 1000000 / ((double)timebase[1] / timebase[0]);
}
double timebase_to_ms(std::vector<int> 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<int> 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;
}
}

View File

@@ -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 <dirent.h>
#include "maix_rtsp_server.hpp"
#include <sys/types.h>
#include <ifaddrs.h>
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 &region : 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<bytes_read-5; i++) {
if(m_buf[i] == 0 && m_buf[i+1] == 0 && m_buf[i+2] == 1) {
start_code = 3;
if (start_code > 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<std::string> rtsp_get_server_urls(std::string ip, int port)
{
char new_ip[16] = {0};
std::vector<std::string> 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<std::string> 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 &region) {
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;
}
}

View File

@@ -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 <string>
#include <vector>
#include "maix_basic.hpp"
#include "maix_touchscreen_base.hpp"
#include <linux/input.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/epoll.h>
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<int> 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<int> 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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -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: ''
c_flags: '-ldl'
cxx_flags: '-ldl'