OpenCV默認並不支持安卓端FFMPEG,也就是說,在給了編譯選項WITH_FFMPEG的情況下也無法成功調用VideoCapture獲取流,因此我們需要修改OpenCV的CMAKE文件,手動設置一下FFMPEG庫的路徑,然后重新編譯即可。
編譯環境
NDK:android-ndk-r16
Android-ABI:arm64-v8
Android-API:android-21
OpenCV:3.4.5
FFMPEG:4.1
編譯工具:clang
編譯步驟
- 交叉編譯OpenCV並保證無FFMPEG support情況下編譯鏈接正常
- 交叉編譯FFMPEG並保證拉流正常
- 修改OpenCV配置文件聯合編譯With FFMPEG support情況下編譯鏈接正常。
FFMPEG交叉編譯
配置好NDK之后,先生成以下獨立的編譯鏈工具,講道理我這個版本的NDK是不需要單獨生成的,但為了保險期間還是單獨生成一下:
ffmpeg的編譯需要生成獨立的編譯鏈工具
./make-standalone-toolchain.sh \
--arch=arm64 \
--platform=android-21 \
--install-dir=TARGET_DIR/stand-alone-toolchain \
--use-llvm \
--stl=libc++ \
--force
生成編譯鏈工具之后,需要編譯FFMPEG,這個編譯腳本是我嘗試了各種ndk版本,獨立遍歷鏈工具和配置項后確定的,因為我們這邊需要、rtsp視頻流的解析支持,所以加了一些network、protocol的編譯選項。編譯成靜態庫也是為了后面編譯集成。
#!/bin/bash
export MY_TOOLCHAIN=DIR_TO_REPLACE/stand-alone-toolchain
mkdir ffmjpeg-lib
export PREFIX=ffmjpeg-lib
BUILD_FFMJPEG(){
./configure --target-os=android --prefix=$PREFIX \
--enable-cross-compile \
--enable-runtime-cpudetect \
--disable-asm \
--disable-x86asm \
--enable-protocol=tcp \
--enable-network \
--enable-protocol=udp \
--enable-demuxer=rtsp \
--enable-demuxer=rtp \
--disable-doc \
--arch=aarch64 \
--cc=$MY_TOOLCHAIN/bin/aarch64-linux-android-clang \
--cxx=$MY_TOOLCHAIN/bin/aarch64-linux-android-clang++ \
--disable-stripping \
--nm=$MY_TOOLCHAIN/aarch64-linux-android/bin/nm \
--sysroot=$MY_TOOLCHAIN/sysroot \
--enable-jni \
--enable-mediacodec \
--enable-avresample \
--enable-gpl --disable-shared --enable-small \
--disable-ffprobe --disable-ffplay --disable-debug \
--extra-cflags="-DBIONIC_IOCTL_NO_SIGNEDNESS_OVERLOAD -fPIC -D__thumb__ -mthumb -Wfatal-errors -Wno-deprecated -mfloat-abi=softfp -marm -D__ANDROID_API__=21 -march=armv8-a"
}
BUILD_FFMJPEG
make -j8
make install
編譯可能出現的錯誤:
- member reference base type '__be32' (aka 'unsigned int') is not a structure or union,這個似乎是我用ndk16編譯的時候出現的,改為21修正了。
- libavdevice/v4l2.c:135:9: fatal error: assigning to,這個錯誤似乎有時候會出現,有時候不會,我出現這個錯誤之后,添加編譯選項-DBIONIC_IOCTL_NO_SIGNEDNESS_OVERLOAD。
具體其他錯誤忘記了,大概都是通過編譯選項和NDK版本控制修正的。
FFMPEG測試
./ffmpeg -i rtsp://USER:PASS@IP/h264/ch1/main/av_stream \
-r 1/60 \
-f image2 \
/data/test/tmp/images%05d.png \
-c copy \
-map 0 \
-f segment \
-segment_time 60 \
"/data/test/tmp/out%03d.mkv"
把二進制文件push到板子上測試,出現過這些錯誤:
- 完全無法解析給定的RTSP地址,這個原因是編譯的時候沒有開rtsp支持
- 出現Network不可達之類的,是因為沒連wifi。。。
- Could not find tag for codec pcm_mulaw in stream,存儲格式mp4寫的有問題,要改為mkv。
OpenCV編譯
編譯腳本:
cmake ..\
-DCMAKE_TOOLCHAIN_FILE=TOOLCHAIN_ROOT/android.toolchain.cmake \
-DCMAKE_ANDROID_NDK=NDK_ROOT\
-DANDROID_NATIVE_API_LEVEL=21\
-DBUILD_ANDROID_PROJECTS=OFF\
-DBUILD_ANDROID_EXAMPLES=OFF\
-DCMAKE_BUILD_TYPE=Release\
-DBUILD_JAVA=OFF\
-DCMAKE_ANDROID_ARCH_ABI=arm64-v8a\
-DCMAKE_INSTALL_PREFIX=INSTALL_ROOT/opencv_install \
-DANDROID_ABI="arm64-v8a" \
-DBUILD_JASPER=ON \
-DBUILD_JPEG=ON \
-DBUILD_PERF_TESTS=OFF \
-DBUILD_SHARED_LIBS=NO \
-DBUILD_TESTS=OFF \
-DBUILD_TIFF=ON \
-DBUILD_ZLIB=ON \
-DBUILD_WEBP=ON \
-DBUILD_opencv_apps=OFF \
-DBUILD_opencv_core=ON \
-DBUILD_opencv_calib3d=ON \
-DBUILD_opencv_dnn=ON \
-DBUILD_opencv_features2d=ON \
-DBUILD_opencv_flann=ON \
-DBUILD_opencv_gapi=OFF \
-DBUILD_opencv_highgui=ON \
-DBUILD_opencv_imgcodecs=ON \
-DBUILD_opencv_imgproc=ON \
-DBUILD_opencv_java_bindings_generator=OFF \
-DBUILD_opencv_js=OFF \
-DBUILD_opencv_ml=ON \
-DBUILD_opencv_objdetect=OFF \
-DBUILD_opencv_photo=OFF \
-DBUILD_opencv_python2=OFF \
-DBUILD_opencv_python3=OFF \
-DBUILD_opencv_python_bindings_generator=OFF \
-DBUILD_opencv_stitching=OFF \
-DBUILD_opencv_ts=OFF \
-DBUILD_opencv_video=ON \
-DBUILD_opencv_videoio=ON \
-DWITH_GTK=OFF \
-DWITH_GTK_2_X=OFF \
-DWITH_LAPACK=OFF \
-DANDROID_STL=c++_static \
-DANDROID_TOOLCHAIN=clang \
-DANDROID_ARM_NEON=ON \
-DWITH_FFMPEG=ON
這個腳本也是嘗試了一些,最終都是我們需要用到的,最后編譯選項DWITH_FFMPEG=ON打不打開沒影響,因為最終也不會編進去。。
這一塊遇到的報錯就不說了,基本OpenCV的編譯。
聯合編譯OpenCV和FFMPEG
主要修改兩個文件:
- OpenCV根目錄CMakeLists.txt
OCV_OPTION(WITH_FFMPEG "Include FFMPEG support" ON
VISIBLE_IF NOT ANDROID AND NOT IOS AND NOT WINRT
VERIFY HAVE_FFMPEG)
改為:
OCV_OPTION(WITH_FFMPEG "Include FFMPEG support" ON IF (NOT IOS AND NOT WINRT) )
if(WITH_FFMPEG OR HAVE_FFMPEG)
if(OPENCV_FFMPEG_USE_FIND_PACKAGE)
status(" FFMPEG:" HAVE_FFMPEG THEN "YES (find_package)" ELSE "NO (find_package)")
elseif(WIN32)
status(" FFMPEG:" HAVE_FFMPEG THEN "YES (prebuilt binaries)" ELSE NO)
else()
status(" FFMPEG:" HAVE_FFMPEG THEN YES ELSE NO)
endif()
status(" avcodec:" FFMPEG_libavcodec_FOUND THEN "YES (ver ${FFMPEG_libavcodec_VERSION})" ELSE NO)
status(" avformat:" FFMPEG_libavformat_FOUND THEN "YES (ver ${FFMPEG_libavformat_VERSION})" ELSE NO)
status(" avutil:" FFMPEG_libavutil_FOUND THEN "YES (ver ${FFMPEG_libavutil_VERSION})" ELSE NO)
status(" swscale:" FFMPEG_libswscale_FOUND THEN "YES (ver ${FFMPEG_libswscale_VERSION})" ELSE NO)
status(" avresample:" FFMPEG_libavresample_FOUND THEN "YES (ver ${FFMPEG_libavresample_VERSION})" ELSE NO)
endif()
改為:
if(WITH_FFMPEG OR HAVE_FFMPEG)
if(OPENCV_FFMPEG_USE_FIND_PACKAGE)
status(" FFMPEG:" HAVE_FFMPEG THEN "YES (find_package)" ELSE "NO (find_package)")
#elseif(WIN32)
elseif(WIN32 OR ANDROID)
status(" FFMPEG:" HAVE_FFMPEG THEN "YES (prebuilt binaries)" ELSE NO)
else()
status(" FFMPEG:" HAVE_FFMPEG THEN YES ELSE NO)
endif()
status(" avcodec:" FFMPEG_libavcodec_FOUND THEN "YES (ver ${FFMPEG_libavcodec_VERSION})" ELSE NO)
status(" avformat:" FFMPEG_libavformat_FOUND THEN "YES (ver ${FFMPEG_libavformat_VERSION})" ELSE NO)
status(" avutil:" FFMPEG_libavutil_FOUND THEN "YES (ver ${FFMPEG_libavutil_VERSION})" ELSE NO)
status(" swscale:" FFMPEG_libswscale_FOUND THEN "YES (ver ${FFMPEG_libswscale_VERSION})" ELSE NO)
status(" avresample:" FFMPEG_libavresample_FOUND THEN "YES (ver ${FFMPEG_libavresample_VERSION})" ELSE NO)
endif()
- cmake/OpenCVFindLibsVideo.cmake
找到# --- FFMPEG ---的段落
整個替換:
# --- FFMPEG ---
ocv_clear_vars(HAVE_FFMPEG)
if(WITH_FFMPEG) # try FFmpeg autodetection
if(ANDROID)
set(HAVE_FFMPEG TRUE)
set(FFMPEG_DIR FFMJPEG_INSTALL_DIR/ffmjpeg-lib)
set(FFMPEG_INCLUDE_DIRS ${FFMPEG_DIR}/include)
set(FFMPEG_LIBRARY_DIRS ${FFMPEG_DIR}/lib)
set(FFMPEG_LIBRARIES avcodec avformat avutil swscale z)
message(STATUS "FFMPEG_INCLUDE_DIR: ${FFMPEG_INCLUDE_DIRS}")
message(STATUS "FFMPEG_LIBRARY_DIRS: ${FFMPEG_LIBRARY_DIRS}")
message(STATUS "FFMPEG_LIBRARIES: ${FFMPEG_LIBRARIES}")
else()
message(STATUS "Can't find ffmpeg - 'pkg-config' utility is missing")
endif()
endif()
if(HAVE_FFMPEG
AND NOT HAVE_FFMPEG_WRAPPER
)
try_compile(__VALID_FFMPEG
"${OpenCV_BINARY_DIR}"
"${OpenCV_SOURCE_DIR}/cmake/checks/ffmpeg_test.cpp"
CMAKE_FLAGS "-DINCLUDE_DIRECTORIES:STRING=${FFMPEG_INCLUDE_DIRS}"
"-DLINK_DIRECTORIES:STRING=${FFMPEG_LIBRARY_DIRS}"
"-DLINK_LIBRARIES:STRING=${FFMPEG_LIBRARIES}"
OUTPUT_VARIABLE TRY_OUT
)
if(False)
#message(FATAL_ERROR "FFMPEG: test check build log:\n${TRY_OUT}")
message(STATUS "WARNING: Can't build ffmpeg test code")
set(HAVE_FFMPEG FALSE)
else()
ocv_append_build_options(VIDEOIO FFMPEG)
endif()
endif()
這一塊,一來將FFMPEG相關的變量都手動設置了,二來去掉了編譯測試,這一塊編譯測試老失敗導致最后編不進去FFMPEG。
然后重新編譯一下OpenCV。
過程中可能遇到的問題:
- error: function-like macro '__GNUC_PREREQ' is not defined,這個可能是NDK版本不對吧
- 主要出現的問題是編不上去,經過幾次修改CMAKE文件才編上去。。
最后去寫個文件上板測試一下:
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
int main(int argc, char *argv[])
{
cv::VideoCapture cap;
cap.open("RTSP_ADDRESS");
cv::Mat frame;
cout << "open: " << cap.isOpened() << endl;
for (int i = 0; i < 10; i++)
{
cap >> frame;
cout << frame.cols << endl;
}
return 0;
}
沒問題: