Android音视频开发实战01-环境搭建

一,FFmpeg介绍

FFmpeg 是一款流行的开源多媒体处理工具,它可以用于转换、编辑、录制和流式传输音视频文件。FFmpeg 具有广泛的应用场景,包括视频编解码、格式转换、裁剪、合并、滤镜等等。官网:https://ffmpeg.org/

FFmpeg 支持各种常见的音视频格式,例如 MP4、AVI、FLV、MOV、AAC、MP3、M4A 等等,并且可以通过添加插件支持更多的格式。与其他视频处理软件相比,FFmpeg 优势在于它的跨平台性能好,可以在 Windows、macOS 和 Linux IOS Android等平台上运行。

FFmpeg 提供了一个命令行界面(CLI),可以使用它来执行各种操作。以下是一些常用的 FFmpeg 命令:

  • 裁剪:从视频中截取指定时间段的视频片段。

    ffmpeg -i input.mp4 -ss 00:01:00 -t 00:00:30 -c copy output.mp4
    
  • 视频旋转:将视频顺时针或逆时针旋转指定角度。

    ffmpeg -i input.mp4 -vf "rotate=PI/2" output.mp4
    
  • 视频拼接:将多个视频文件拼接成一个文件。

    ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4
    
  • 视频缩放:按比例缩小或放大视频尺寸。

    ffmpeg -i input.mp4 -vf scale=640:-1 output.mp4
    
  • 音频提取:将视频文件中的音频提取出来。

    ffmpeg -i input.mp4 -vn -acodec copy output.aac
    
  • 音频合并:将多个音频文件合并为一个文件。

    ffmpeg -i "concat:input1.mp3|input2.mp3" -acodec copy output.mp3
    
  • 视频转码:将一个视频文件转换为另一种格式。

    ffmpeg -i input.avi -c:v libx264 -preset slow -crf 22 -c:a libmp3lame -b:a 192k output.mp4
    
  • 视频加减速:将视频的播放速度加快或减慢。

    ffmpeg -i input.mp4 -filter:v "setpts=0.5*PTS" output.mp4
    
  • 音频加减速:将视频中的音频的播放速度加快或减慢。

    ffmpeg -i input.mp4 -vn -af atempo=%.3f output.mp4
    
  • 音频重采样

    ffmpeg -y -i input.mp4 -vn -ar 44100 -ac 2  output.mp4
    
  • 视频转Gif并等比例缩放

    ffmpeg -y -i input.mp4 -vf scale=320:-2 -r 5 output.gif
    
  • 统计I帧数量

    ffprobe -show_frames input.mp4 > frames.txt
    cat frames.txt | grep "pict_type=I" |wc -l
    
  • 视频信息查看 开发过程中最常用的命令,没有之一

    ffprobe -v error -show_format -show_streams input.mp4
    

结果如下:

    [STREAM]
	index=0
	codec_name=h264
	codec_long_name=H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
	profile=Baseline
	codec_type=video
	codec_tag_string=avc1
	codec_tag=0x31637661
	width=1920
	height=1080
	coded_width=1920
	coded_height=1080
	closed_captions=0
	film_grain=0
	has_b_frames=0
	sample_aspect_ratio=1:1
	display_aspect_ratio=16:9
	pix_fmt=yuvj420p
	level=40
	color_range=pc
	color_space=smpte170m
	color_transfer=smpte170m
	color_primaries=smpte170m
	chroma_location=left
	field_order=progressive
	refs=1
	is_avc=true
	nal_length_size=4
	id=0x1
	r_frame_rate=30/1
	avg_frame_rate=40230000/1339297
	time_base=1/90000
	start_pts=0
	start_time=0.000000
	duration_ts=1339297
	duration=14.881078
	bit_rate=20028287
	max_bit_rate=N/A
	bits_per_raw_sample=8
	nb_frames=447
	nb_read_frames=N/A
	nb_read_packets=N/A
	extradata_size=33
	DISPOSITION:default=1
	DISPOSITION:dub=0
	DISPOSITION:original=0
	DISPOSITION:comment=0
	DISPOSITION:lyrics=0
	DISPOSITION:karaoke=0
	DISPOSITION:forced=0
	DISPOSITION:hearing_impaired=0
	DISPOSITION:visual_impaired=0
	DISPOSITION:clean_effects=0
	DISPOSITION:attached_pic=0
	DISPOSITION:timed_thumbnails=0
	DISPOSITION:captions=0
	DISPOSITION:descriptions=0
	DISPOSITION:metadata=0
	DISPOSITION:dependent=0
	DISPOSITION:still_image=0
	TAG:creation_time=2021-02-27T07:35:19.000000Z
	TAG:language=eng
	TAG:handler_name=VideoHandle
	TAG:vendor_id=[0][0][0][0]
	[SIDE_DATA]
	side_data_type=Display Matrix
	displaymatrix=
	00000000:            0       65536           0
	00000001:       -65536           0           0
	00000002:            0           0  1073741824
	rotation=-90
	[/SIDE_DATA]
	[/STREAM]
	[STREAM]
	index=1
	codec_name=aac
	codec_long_name=AAC (Advanced Audio Coding)
	profile=LC
	codec_type=audio
	codec_tag_string=mp4a
	codec_tag=0x6134706d
	sample_fmt=fltp
	sample_rate=48000
	channels=2
	channel_layout=stereo
	bits_per_sample=0
	initial_padding=0
	id=0x2
	r_frame_rate=0/0
	avg_frame_rate=0/0
	time_base=1/48000
	start_pts=0
	start_time=0.000000
	duration_ts=715765
	duration=14.911771
	bit_rate=96041
	max_bit_rate=N/A
	bits_per_raw_sample=N/A
	nb_frames=699
	nb_read_frames=N/A
	nb_read_packets=N/A
	extradata_size=2
	DISPOSITION:default=1
	DISPOSITION:dub=0
	DISPOSITION:original=0
	DISPOSITION:comment=0
	DISPOSITION:lyrics=0
	DISPOSITION:karaoke=0
	DISPOSITION:forced=0
	DISPOSITION:hearing_impaired=0
	DISPOSITION:visual_impaired=0
	DISPOSITION:clean_effects=0
	DISPOSITION:attached_pic=0
	DISPOSITION:timed_thumbnails=0
	DISPOSITION:captions=0
	DISPOSITION:descriptions=0
	DISPOSITION:metadata=0
	DISPOSITION:dependent=0
	DISPOSITION:still_image=0
	TAG:creation_time=2021-02-27T07:35:19.000000Z
	TAG:language=eng
	TAG:handler_name=SoundHandle
	TAG:vendor_id=[0][0][0][0]
	[/STREAM]
	[FORMAT]
	filename=input.mp4
	nb_streams=2
	nb_programs=0
	format_name=mov,mp4,m4a,3gp,3g2,mj2
	format_long_name=QuickTime / MOV
	start_time=0.000000
	duration=14.911771
	size=37839486
	bit_rate=20300465
	probe_score=100
	TAG:major_brand=mp42
	TAG:minor_version=0
	TAG:compatible_brands=isommp42
	TAG:creation_time=2021-02-27T07:35:19.000000Z
	TAG:com.android.version=8.1.0
	[/FORMAT]

在FFmpeg工具中,ffprobe是一种用于分析媒体文件的命令行工具,参数解释如下:

profile
在H.264/AVC (Advanced Video Coding)视频编码标准中,有以下四种预定义的profile:

  1. Baseline Profile:这是最基本的profile,它定义了一组基本功能,包括单个参考帧、CAVLC (Context-adaptive Variable Length Coding)、8x8变换等。Baseline Profile可用于低比特率、低延迟和低复杂度的应用。

  2. Main Profile:Main Profile扩展了Baseline Profile并增加了一些高级功能,例如B帧、8x8和4x4变换以及熵编码模式自适应技术。Main Profile可以提供更好的视频质量和压缩率,适合于中等比特率的应用。

  3. Extended Profile:Extended Profile扩展了Main Profile,并添加了更多的高级功能,例如8x8和4x4变换、支持更高分辨率、更高的比特率和更丰富的颜色空间。Extended Profile适用于高清视频和广电级别的视频应用。

  4. High Profile:High Profile包含所有的H.264/AVC高级功能,例如无损编码、多层编码、亮度调整等。High Profile通常用于专业级别的视频应用,例如数字电视广播、蓝光光盘和高清视频流媒体服务。
    除了上述四个预定义的profile之外,H.264/AVC还支持用户定义的profile,以根据具体应用需求自定义编解码器的参数设置。

编码level
在H.264/AVC视频编码标准中,Level指的是视频编码器的限制条件,例如最大分辨率、最高比特率以及其他一些技术参数。这些限制条件与profile不同,因为它们通常受到实时处理硬件设备的限制。以下是H.264/AVC定义的level:

  1. Level 1:支持最大352x288像素分辨率和1.5 Mbps的解码速度。

  2. Level 1b:类似于Level 1,但要求使用Baseline Profile。

  3. Level 1.1:支持最大352x480像素分辨率和12 Mbps的解码速度。

  4. Level 1.2:支持最大720x480像素分辨率和30 Mbps的解码速度。

  5. Level 1.3:支持最大1280x720像素分辨率和60 Mbps的解码速度。

  6. Level 2:支持最大1920x1080像素分辨率和60 Mbps的解码速度。

  7. Level 2.1:支持最大1920x1080像素分辨率和120 Mbps的解码速度。

  8. Level 2.2:支持最大1920x1080像素分辨率和120 Mbps的解码速度,并要求使用High Profile。

  9. Level 3:支持最大1920x1080像素分辨率和240 Mbps的解码速度。

  10. Level 3.1:支持最大1920x1080像素分辨率和240 Mbps的解码速度,并要求使用High Profile。

  11. Level 3.2:支持最大1920x1080像素分辨率和240 Mbps的解码速度,并要求使用High Profile和4:2:2色度采样。

  12. Level 4:支持最大2048x2048像素分辨率和480 Mbps的解码速度。

  13. Level 4.1:支持最大2048x2048像素分辨率和1 Gbps的解码速度。

  14. Level 4.2:支持最大2048x2048像素分辨率和1 Gbps的解码速度,并要求使用High Profile和10位色深。

  15. Level 5:支持最大4096x2304像素分辨率和1 Gbps的解码速度。

  16. Level 5.1:支持最大4096x2304像素分辨率和2 Gbps的解码速度。

  17. Level 5.2:支持最大4096x2304像素分辨率和4 Gbps的解码速度。

需要注意的是,level越高,支持的分辨率和比特率就越高,因此所需的处理能力和存储空间也越大。同时,实际可用的level还受到编码器和解码器的硬件限制。

DISPOSITION
DISPOSITION是指多媒体文件流(例如音频、视频)在其容器中的位置以及是否被默认启用的标志。它告诉我们这个特定的流的角色是什么,并且是否应该被自动启用。
以下是一些常见的DISPOSITION标志:
default:表示该流是默认启用的。
dub:表示这个流是一个双语版本的语言流。
original:表示这个流是原始的无损版本。
comment:表示这个流是注释版本的。
lyrics:表示这个流包含歌词信息。
通过查看DISPOSITION标志,我们可以确定每个音频或视频流的作用和启用状态,这对于解析和编辑多媒体文件非常有用。

除了命令行界面之外,FFmpeg 还提供了许多开发库,例如 libavcodec 和 libavformat 等,这些库可以帮助您将 FFmpeg 集成到自己的应用程序中。

总之,FFmpeg 是一款强大的多媒体处理工具,可以让您对音视频文件进行各种操作。如果您需要在自己的应用程序中使用 FFmpeg,可以使用其提供的开发库

二,FFmpeg编译

要想把FFmpeg运行在移动设备上,就需要使用到交叉编译,交叉编译是指在一台计算机上,使用一个编译器将程序或库编译成可以在不同架构的计算机上运行的二进制文件。通常情况下,交叉编译是在开发人员的计算机上完成的,然后将生成的可执行文件或库文件拷贝到目标设备上运行。

2.1 常见的错误

FFmpeg 交叉编译一直依赖都是老大难的问题,新手往往要折腾好几天才能成功编译
为 Android 编译 FFmpeg 时,常见的错误如下:

C compiler test failed.

出现这个问题一般有两种情况

  • NDK版本不对,NDK 的版本非常重要,因为不同版本的 NDK 可能与不同版本的 FFmpeg 不兼容。在下载 NDK 时,请确保使用与您要构建的 FFmpeg 版本相匹配的 NDK 版本。
    -NDK路径写错了

ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol

  1. 在–extra-ldflags 加入-fPIC
  2. 在链接的时候加入-Bsymbolic,慎重使用
  3. 注意链接的时候的顺序 比如libavutil是每个库都需要的,那么就要放在第一个链接

duplicate symbol

–enable-shared --enable-static 同时打开,在合并so的时候会出现重复的方法

java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol “cos”

在编译以及在把.a文件链接到SO的时候没有把m,z,android库链接上

2.2 编译步骤

2.2.1 下载Android SDK

我们一般通过Android Studio下载SDK,官网地址:https://developer.android.com/?hl=zh-cn
运行之后打开设置,找到Android SDK
SDK Platforms我们一般下最新正式版本就好了,这里最新正式版本为33(Android 13),如图所示:
在这里插入图片描述

SDK Tools一般都需要选择上,如图所示:
在这里插入图片描述

2.2.2下载Android NDK

NDK 我们一般去官网下载对应的版本,地址如下:https://developer.android.com/ndk/downloads?hl=zh-cn
这个地方我们使用r25c就好了

2.2.3 安装必备的软件

MAC需要安装Xcode Command Line Tools 地址:https://developer.apple.com/download/all/?q=xcode
Linux需要安装curl git yasm

2.2.4 Start build

A 基础build

需要下载一个成品脚本,地址:https://github.com/Javernaut/ffmpeg-android-maker 这个脚本我亲测在Mac Linux都可以成功Build但是在实际开发中还需要做修改(修改完的build脚本工程详见这里:https://github.com/bookzhan/ffmpeg-android-build)
cd 到脚本目录,指定SDK目录,如下:

export ANDROID_SDK_HOME='/Users/guaishou/Library/Android/sdk'
export ANDROID_NDK_HOME='/Users/guaishou/Library/Android/sdk/ndk/AndroidNDK9519653.app/Contents/NDK'

执行脚本:
./ffmpeg-android-maker.sh
脚本的执行时间比较长,安静的等待脚本执行完成,脚本执行完成之后,生成的SO在output目录下,如图所示:
在这里插入图片描述
在这里插入图片描述

可以看到,这个脚本构建是不能直接用于开发,主要有以下问题:

  1. 可以看到这个脚本构建的结构有多个SO,这在开发中显得不是很优雅,我们需要做的是把这些SO合并成一个SO
  2. 构建出来的SO很大,包大小遭不住
  3. 在构建的时候我们常用的库libmp3lame,libx264(libx264是GPL协议的,要注意安全合规)没有编译进去

B 多个SO合并成一个SO

要想把多个SO合并成一个SO,那么就需要把FFmpeg编译成静态库,有关静态库与动态库的解释如下:

静态库是编译时链接到应用程序中的,它将库文件的全部内容复制到应用程序中。这意味着在运行时,所有的库函数都已经存在于应用程序中,不需要再进行加载。由于静态库已经被完全链接到应用程序中,因此它们的大小通常比动态库更大,但执行速度更快,因为没有额外的加载和解析操作。同时,使用静态库可以避免版本冲突和依赖问题,因为每个应用程序都会使用自己的一份库文件副本。

相反,动态库是在运行时动态加载的,只有当需要使用库函数时才会被加载到内存中。由于动态库仅在需要时才加载,因此它们的大小通常比静态库小,但执行速度可能稍慢,因为需要进行额外的加载和解析操作。另外,由于多个应用程序可以共享相同的库文件,因此动态库提供了更好的资源利用率。

总之,静态库是在编译时链接到应用程序中的,而动态库是在运行时动态加载的。静态库的优点是执行速度更快,依赖和版本控制更容易,而动态库的优点是更小的库文件大小和更好的资源利用率。可以用文件后缀做简单的区分:静态库文件后缀为.a, 动态库文件后缀为.so

步骤如下:

  1. 首选我们需要修改ffmpeg-android-maker-master/scripts/ffmpeg/build.sh 文件把–disable-static 改成–enable-static 再次build 的时候就可以在ffmpeg-android-maker-master/build目录下看到.a文件已经生成了(在调试脚本的时候可以在后面加-abis=arm64-v8a 参数,只是构建64位的库,这个构建时间就会短很多)
  2. 合并.a文件
${FAM_CC} -shared -o ${STATIC_LIB_DIR}/${OUTPUT_SO_NAME} 
  -Wl,--whole-archive,-Bsymbolic 
  ${STATIC_LIB_DIR}/libavutil.a 
  ${STATIC_LIB_DIR}/libavcodec.a 
  ${STATIC_LIB_DIR}/libavfilter.a 
  ${STATIC_LIB_DIR}/libswresample.a 
  ${STATIC_LIB_DIR}/libavformat.a 
  ${STATIC_LIB_DIR}/libswscale.a 
  -Wl,--no-whole-archive
${FAM_STRIP} --strip-unneeded ${STATIC_LIB_DIR}/${OUTPUT_SO_NAME}
#FAM_CC STATIC_LIB_DIR FAM_STRIP 都是export-build-variables.sh生成的环境变量,FAM_CC其实就是clang(aarch64-linux-android21-clang系列)  --whole-archive指的是把全部代码都写入到so中,不管是否使用到了
#FAM_STRIP --strip-unneeded 标识剔除debug信息,减少so大小,其实Android在打包的时候会对所有so执行这个操作
#-Bsymbolic是解决ld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'ff_tx_tab_32_float'; recompile with -fPIC错误的关键,但是不要轻易使用,尽量去解决编译问题,否则在运行的时候会出问题
#在后面写入-Wl,--no-whole-archive是为了解决重复符号表的问题,表示只对接下来的目标文件执行局部符号绑定。这样,就可以将不必要的符号从最终的符号表中删除,减小输出文件的大小,并避免一些潜在的问题。
#需要主要这个合并的顺序!!!

C FFmpeg裁剪

so合并的文件的问题解决了,但是合并的so会显得很大,这是因为默认是把FFmpeg的代码全部编译了,我们需要做一些裁剪,去掉我们不用的代码
在不知道有那些配置可以选择的时候,可以在FFmpeg源码下执行./configure 这样默认会把所有支持的配置都列出来,然后依次去掉不必要的配置就好
最终scripts/ffmpeg/build.sh文件的样式如下:

#!/usr/bin/env bash

case $ANDROID_ABI in
  x86)
    # Disabling assembler optimizations, because they have text relocations
    EXTRA_BUILD_CONFIGURATION_FLAGS="$EXTRA_BUILD_CONFIGURATION_FLAGS --disable-neon --disable-asm"
    ;;
  x86_64)
    EXTRA_BUILD_CONFIGURATION_FLAGS="$EXTRA_BUILD_CONFIGURATION_FLAGS --disable-neon --disable-asm"
    ;;
  armeabi-v7a)
    EXTRA_BUILD_CONFIGURATION_FLAGS="$EXTRA_BUILD_CONFIGURATION_FLAGS --enable-neon --disable-asm --enable-inline-asm"
    ;;
  arm64-v8a)
    EXTRA_BUILD_CONFIGURATION_FLAGS="$EXTRA_BUILD_CONFIGURATION_FLAGS --enable-neon --disable-asm --enable-inline-asm"
    ;;
esac

if [ "$FFMPEG_GPL_ENABLED" = true ] ; then
    EXTRA_BUILD_CONFIGURATION_FLAGS="$EXTRA_BUILD_CONFIGURATION_FLAGS --enable-gpl"
fi

# Preparing flags for enabling requested libraries
ADDITIONAL_COMPONENTS=
for LIBARY_NAME in ${FFMPEG_EXTERNAL_LIBRARIES[@]}
do
  ADDITIONAL_COMPONENTS+=" --enable-$LIBARY_NAME"
  case $LIBARY_NAME in
        libx264)
          ADDITIONAL_COMPONENTS+=" --enable-encoder=libx264"
          ;;
        libmp3lame)
          ADDITIONAL_COMPONENTS+=" --enable-decoder=mp3"
          ;;
        *)
          echo "Unknown ADDITIONAL_COMPONENTS LIBARY_NAME: $LIBARY_NAME"
          ;;
  esac
done

echo ADDITIONAL_COMPONENTS=${ADDITIONAL_COMPONENTS}

# Referencing dependencies without pkgconfig
DEP_CFLAGS="-I${BUILD_DIR_EXTERNAL}/${ANDROID_ABI}/include"
DEP_LD_FLAGS="-L${BUILD_DIR_EXTERNAL}/${ANDROID_ABI}/lib $FFMPEG_EXTRA_LD_FLAGS"

./configure 
  --prefix=${BUILD_DIR_FFMPEG}/${ANDROID_ABI} 
  --enable-cross-compile 
  --enable-small 
  --target-os=android 
  --arch=${TARGET_TRIPLE_MACHINE_ARCH} 
  --sysroot=${SYSROOT_PATH} 
  --cc=${FAM_CC} 
  --cxx=${FAM_CXX} 
  --ld=${FAM_LD} 
  --ar=${FAM_AR} 
  --as=${FAM_CC} 
  --nm=${FAM_NM} 
  --ranlib=${FAM_RANLIB} 
  --strip=${FAM_STRIP} 
  --extra-cflags="-O3 -fPIC -lm -lz -landroid $DEP_CFLAGS" 
  --extra-ldflags="$DEP_LD_FLAGS" 
  --disable-shared 
  --enable-static 
  --disable-vulkan 
  --disable-symver 
  --disable-doc 
  --disable-htmlpages 
  --disable-manpages 
  --disable-podpages 
  --disable-txtpages 
  --disable-ffplay 
  --disable-ffmpeg 
  --disable-ffprobe 
  --disable-avdevice 
  --disable-bsfs 
  --disable-devices 
  --disable-protocols 
  --disable-postproc 
  --enable-protocol=file 
  --enable-protocol=concat 
  --disable-parsers 
  --disable-demuxers 
  --enable-demuxer=mov 
  --enable-demuxer=mp3 
  --enable-demuxer=image2 
  --enable-demuxer=gif 
  --enable-demuxer=wav 
  --enable-demuxer=asf 
  --enable-demuxer=flv 
  --enable-demuxer=avi 
  --enable-demuxer=webm_dash_manifest 
  --enable-demuxer=matroska 
  --enable-demuxer=mpegts 
  --disable-decoders 
  --enable-decoder=aac 
  --enable-decoder=png 
  --enable-decoder=h264 
  --enable-decoder=mp3 
  --enable-decoder=mjpeg 
  --enable-decoder=mpeg4 
  --enable-decoder=gif 
  --enable-decoder=pcm_s16le 
  --enable-decoder=hevc 
  --enable-decoder=msmpeg4v1 
  --enable-decoder=msmpeg4v2 
  --enable-decoder=msmpeg4v3 
  --enable-decoder=wmav1 
  --enable-decoder=wmav2 
  --enable-decoder=flv 
  --enable-decoder=adpcm_swf 
  --enable-decoder=ac3 
  --enable-decoder=vp8 
  --enable-decoder=vorbis 
  --enable-decoder=mpeg2video 
  --enable-decoder=mp2 
  --enable-decoder=indeo4 
  --enable-decoder=amrnb 
  --disable-muxers 
  --enable-muxer=mov 
  --enable-muxer=mp4 
  --enable-muxer=image2 
  --enable-muxer=mp3 
  --enable-muxer=ipod 
  --enable-muxer=gif 
  --disable-encoders 
  --enable-encoder=aac 
  --enable-encoder=png 
  --enable-encoder=mjpeg 
  --enable-encoder=gif 
  --enable-swscale 
  --disable-filters 
  --enable-filter=crop 
  --enable-filter=scale 
  --enable-filter=afade 
  --enable-filter=atempo 
  --enable-filter=copy 
  --enable-filter=aformat 
  --enable-filter=overlay 
  --enable-filter=vflip 
  --enable-filter=hflip 
  --enable-filter=transpose 
  --enable-filter=volume 
  --enable-filter=rotate 
  --enable-filter=apad 
  --enable-filter=amerge 
  --enable-filter=aresample 
  --enable-filter=setpts 
  --enable-filter=fps 
  --enable-filter=palettegen 
  --enable-filter=paletteuse 
  --enable-filter=trim 
  --enable-filter=null 
  --enable-filter=overlay 
  --enable-filter=format 
  --enable-filter=atrim 
  --enable-filter=split 
  --enable-filter=amix 
  --enable-filter=anull 
  --enable-filter=adelay 
  --enable-zlib 
  --enable-jni 
  --enable-nonfree 
  --enable-mediacodec 
  --enable-version3 
  --pkg-config=${PKG_CONFIG_EXECUTABLE} 
  ${EXTRA_BUILD_CONFIGURATION_FLAGS} 
  ${ADDITIONAL_COMPONENTS} || exit 1

${MAKE_EXECUTABLE} clean
${MAKE_EXECUTABLE} -j${HOST_NPROC}
${MAKE_EXECUTABLE} install


export STATIC_LIB_DIR=${BUILD_DIR_FFMPEG}/${ANDROID_ABI}/lib
export EXTERNAL_LIB_DIR=${INSTALL_DIR}/lib
echo STATIC_LIB_DIR=${STATIC_LIB_DIR}
echo EXTERNAL_LIB_DIR=${EXTERNAL_LIB_DIR}
echo FAM_CC=${FAM_CC}

EXTERNAL_STATIC_LIB_PATH=""
for LIBARY_NAME in ${FFMPEG_EXTERNAL_LIBRARIES[@]}
do
  EXTERNAL_STATIC_LIB_PATH+="${EXTERNAL_LIB_DIR}/${LIBARY_NAME}.a "
done
echo EXTERNAL_STATIC_LIB_PATH=${EXTERNAL_STATIC_LIB_PATH}

${FAM_CC} -shared -o ${STATIC_LIB_DIR}/${OUTPUT_SO_NAME} 
  -Wl,--whole-archive 
  ${EXTERNAL_STATIC_LIB_PATH}
  ${STATIC_LIB_DIR}/libavutil.a 
  ${STATIC_LIB_DIR}/libavcodec.a 
  ${STATIC_LIB_DIR}/libavfilter.a 
  ${STATIC_LIB_DIR}/libswresample.a 
  ${STATIC_LIB_DIR}/libavformat.a 
  ${STATIC_LIB_DIR}/libswscale.a 
  -Wl,--no-whole-archive -lm -lz -landroid

OUTPUT_CONFIG_HEADERS_DIR=${OUTPUT_DIR}/include/${ANDROID_ABI}
mkdir -p ${OUTPUT_CONFIG_HEADERS_DIR}
cp config.h ${OUTPUT_CONFIG_HEADERS_DIR}/config.h

${FAM_STRIP} --strip-unneeded ${STATIC_LIB_DIR}/${OUTPUT_SO_NAME}

脚本说明

  1. 从Android 5.0开始,默认情况下所有设备都支持NEON指令集,在编译的时候尽量打开NEON,这样性能会更好
  2. asm指的是汇编优化,这样代码执行性能会更好,但是在FFmpeg上编译一般使用inline-asm(内联汇编),因为而普通汇编需要单独编写成汇编代码文件,增加了额外的编译链接过程,很容易链接失败,而且内联汇编执行效率更高

D 添加常用的第三方库

在音视频开发中,我们一般会使用一些优秀的第三方库,如对Mp3支持的库libmp3lame,H264软编库libx264(在移动端我们一般不使用H265进行软编,主要原因在于H265软编对性能消耗要比H264大得多,
在执行构建命令的时候启用./ffmpeg-android-maker.sh --enable-libmp3lame --enable-libx264就好了,最后在链接的时候把对应的.a文件链接在一起就好了,如上完整的scripts/ffmpeg/build.sh文件

3.Android 开发环境搭建

3.1 新建工程

新建工程,选择Native c++,然后一直点Next就好了
在这里插入图片描述

在这里插入图片描述

指定ndk版本
ndk版本一般要在各个开发者直接统一,写法为,在build.gradle->android下面写,如下
ndkVersion ‘25.2.9519653’

3.2 cmake简介

其中cpp目录下的CMakeLists.txt文件是构建so的关键文件,俗称cmake文件,如下:

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.18.1)

# Declares and names the project.

project("ffmpegtest")

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        ffmpegtest

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        common/BZLogUtil.cpp
        native-lib.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        ffmpegtest

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

3.2.1 cmake基本语法

cmake的基本语法和C类似,都是通过方法名()的方式来使用的如:

project('ffmpegtest') #指定了工程的名称,非必选

条件语句

if(condition)
  # do something
elseif(condition2)
  # do something else
else()
  # do another thing
endif()

循环语句

foreach(loop_var RANGE start end [step])
  # do something
endforeach()

while(condition)
  # do something
endwhile()

函数

函数由命令组成,通过 function(func_name [args...]) 定义,可以在任意位置调用。

function(hello_world)
  message("Hello World!")
endfunction()

hello_world()

3.2.2 cmake变量的使用与定义

变量在 CMake 中以 $ 开头,有以下几种类型:

  • 内置变量:如 ${CMAKE_CURRENT_LIST_DIR} 表示当前 CMake 文件所在的目录。
  • 环境变量:如 $ENV{PATH} 表示系统环境变量 PATH 的值。
  • 用户定义变量:如 set(VAR_NAME value),通过 ${VAR_NAME} 引用。

3.2.3 常见的用法

其实上面的很少用,我们来看一个稍微复杂一点的,工作中掌握这么多就够用了,如下:

	# For more information about using CMake with Android Studio, read the
	# documentation: https://d.android.com/studio/projects/add-native-code.html
	
	# Sets the minimum version of CMake required to build the native library.
	
	#这个是必选的
	cmake_minimum_required(VERSION 3.4.1)
	
	# Creates and names a library, sets it as either STATIC
	# or SHARED, and provides the relative paths to its source code.
	# You can define multiple libraries, and CMake builds them for you.
	# Gradle automatically packages shared libraries with your APK.
	#添加宏
	if (${CMAKE_BUILD_TYPE} STREQUAL Debug)
	    add_definitions(-DDEBUG_TYPE)
	endif()
	
	#添加整个目录的源码作为编译对象
	aux_source_directory(./glprogram DIR_GLPROGRAM_SRCS)
	aux_source_directory(./mediaedit DIR_MEDIAEDIT_SRCS)
	aux_source_directory(./player DIR_PLAYER_SRCS)
	aux_source_directory(./soundtouch DIR_SOUNDTOUCH_SRCS)
	
	add_library( # Sets the name of the library.
	        bzmedia
	
	        # Sets the library as a shared library.
	        SHARED
	
	        # Provides a relative path to your source file(s).
	        ./jni/android_bzmedia.cpp
	        ./jni/ffmpeg_base_info.cpp
	        ./jni/OnActionListener.cpp
	        ./jni/ffmpeg_audio_player.cpp
	        ./jni/video_edit_sdk.cpp
	        ./jni/jni_VideoFrameGetter.cpp
	        ./jni/video_recorder_jni.cpp
	        ./jni/android_gl_program.cpp
	        ./jni/jni_BZRenderEngine.cpp;
	        ./jni/video_player_jni.cpp;
	        ./jni/soundtouch_jni.cpp;
	
	
	        ./common/BZLogUtil.cpp
	        ./common/bz_time.cpp
	        ./common/JvmManager.cpp
	        ./common/GLUtil.cpp
	        ./common/PngReader.cpp
	
	        ${DIR_MEDIAEDIT_SRCS}
	
	        ./glutils/FrameBufferUtils.cpp;
	        ./glutils/GLImageTextureUtil.cpp;
	        ./glutils/GLMatrixUtils.cpp;
	        ./glutils/MatrixVaryTools.cpp;
	        ./glutils/CropTextureUtil.cpp;
	        ./glutils/BZRenderEngine.cpp;
	        ./glutils/VideoTextureManger.cpp;
	        ./glutils/EGLContextUtil.cpp;
	        ./glutils/TextureConvertYUVUtil.cpp;
	        ./glutils/TextureUtil.cpp;
	
	        ${DIR_GLPROGRAM_SRCS}
	        ${DIR_PLAYER_SRCS}
	        ${DIR_SOUNDTOUCH_SRCS}
	
	        ./recorder/VideoRecorder.cpp
	
	        ./utils/AdjustConfigUtil.cpp
	        ./utils/PcmDeque.cpp
	
	        ./permission/base64.c
	        ./permission/PermissionUtil.cpp
	        )
	
	# Searches for a specified prebuilt library and stores the path as a
	# variable. Because CMake includes system libraries in the search path by
	# default, you only need to specify the name of the public NDK library
	# you want to add. CMake verifies that the library exists before
	# completing its build.
	configure_file(${Project_SOURCE_DIR}/lib/${ANDROID_ABI}/libbzffmpeg.so ${Project_BINARY_DIR}/libbzffmpeg.so COPYONLY)
	configure_file(${Project_SOURCE_DIR}/lib/${ANDROID_ABI}/libbzffmpegcmd.so ${Project_BINARY_DIR}/libbzffmpegcmd.so COPYONLY)
	
	#ffmpeg
	add_library(bzffmpeg-lib
	        SHARED
	        IMPORTED)
	set_target_properties(bzffmpeg-lib
	        PROPERTIES IMPORTED_LOCATION
	        libbzffmpeg.so)
	#ffmpegcmd
	add_library(bzffmpegcmd-lib
	        SHARED
	        IMPORTED)
	set_target_properties(bzffmpegcmd-lib
	        PROPERTIES IMPORTED_LOCATION
	        libbzffmpegcmd.so)
	add_library(yuv-lib
	        STATIC
	        IMPORTED)
	set_target_properties(yuv-lib
	        PROPERTIES IMPORTED_LOCATION
	        ${Project_SOURCE_DIR}/lib/${ANDROID_ABI}/libyuv_static.a)
	
	#添加系统库
	find_library(log-lib log)
	find_library(m-lib m)
	find_library(z-lib z)
	find_library(android-lib android)
	find_library(EGL-lib EGL)
	find_library(jnigraphics-lib jnigraphics)
	find_library(GLES-lib GLESv2)
	find_library(OpenSLES-lib OpenSLES)
	
	
	include_directories(
	        ./
	        ./include/
	        ./common
	        ./bean
	        ./cmdutilt
	        ./include
	        ./utils
	        ./permission
	        ./include/libavcodec
	        ./include/libavdevice
	        ./include/libavfilter
	        ./include/libavformat
	        ./include/libavutil
	        ./include/libpostproc
	        ./include/libswresample
	        ./include/libswscale
	        ./include/libyuv
	        ./include/soundtouch
	)
	# Specifies libraries CMake should link to your target library. You
	# can link multiple libraries, such as libraries you define in this
	# build script, prebuilt third-party libraries, or system libraries.
	
	#把所有库连接到最终生成的库中
	target_link_libraries( # Specifies the target library.
	        bzmedia
	
	        # Links the target library to the log library
	        # included in the NDK.
	        bzffmpeg-lib bzffmpegcmd-lib yuv-lib ${log-lib} ${m-lib} ${z-lib} ${android-lib} ${EGL-lib} ${GLES-lib} ${jnigraphics-lib} ${OpenSLES-lib})

3.2.4 配置支持的CPU架构

默认情况下SO会生成armeabi-v7a,arm64-v8a,x86,x86_64,如果我们只想生成指定的SO,那么在build.gradle文件android->defaultConfig下配置ndk来过滤就好了,如下:

	apply plugin: 'com.android.library'
	apply from: '../config.gradle'
	
	android {
	    compileSdkVersion project.ext.compileSdkVersion
	    defaultConfig {
	        minSdkVersion project.ext.minSdkVersion
	        targetSdkVersion project.ext.targetSdkVersion
	
	        ndk {
	            abiFilters 'armeabi-v7a', 'arm64-v8a'
	        }
	    }
	}

3.3验证FFmpeg编译是否成功

测试工程:https://github.com/bookzhan/ffmpegtest
成功标准:

  1. 执行System.loadLibrary没有任何报错
  2. 能够成功获取到编码器,解码器,avformat信息,验证代码如下:
	#include <common/BZLogUtil.h>
	#include <cstring>
	#include "ffmpeg_base_info.h"
	
	
	void printFFmpegBaseInfo() {
	    char buffer[4096];
	    getFFmpegConfigure(buffer);
	    BZLogUtil::logD(buffer);
	
	    memset(buffer, 0, 4096);
	    getFFmpegSupportProtocol(buffer);
	    BZLogUtil::logD(buffer);
	
	    memset(buffer, 0, 4096);
	    getFFmpegSupportAVFormat(buffer);
	    BZLogUtil::logD(buffer);
	
	    memset(buffer, 0, 4096);
	    getFFmpegSupportAVCodec(buffer);
	    BZLogUtil::logD(buffer);
	
	    memset(buffer, 0, 4096);
	    getFFmpegSupportAVFilter(buffer);
	    BZLogUtil::logD(buffer);
	
	    testLib();
	}
	
	int getFFmpegConfigure(char *info) {
	    return sprintf(info, "%sn", avcodec_configuration());;
	}
	
	int getFFmpegSupportProtocol(char *info) {
	    struct URLProtocol *pup = nullptr;
	    int ret = 0;
	    //Input
	    struct URLProtocol **p_temp = &pup;
	    avio_enum_protocols((void **) p_temp, 0);
	    while ((*p_temp) != nullptr) {
	        ret = sprintf(info, "%s[In ][%10s]n", info, avio_enum_protocols((void **) p_temp, 0));
	    }
	    pup = nullptr;
	    //Output
	    avio_enum_protocols((void **) p_temp, 1);
	    while ((*p_temp) != nullptr) {
	        ret = sprintf(info, "%s[Out][%10s]n", info, avio_enum_protocols((void **) p_temp, 1));
	    }
	    return ret;
	}
	
	int getFFmpegSupportAVFormat(char *info) {
	    int ret = 0;
	    void *opaque_in = nullptr;
	    void *opaque_out = nullptr;
	    const AVInputFormat *if_temp = av_demuxer_iterate(&opaque_in);
	    const AVOutputFormat *of_temp = av_muxer_iterate(&opaque_out);
	    //Input
	    while (if_temp != nullptr) {
	        ret = sprintf(info, "%s[In ][%10s]n", info, if_temp->name);
	        if_temp = av_demuxer_iterate(&opaque_in);;
	    }
	    //Output
	    while (of_temp != nullptr) {
	        ret = sprintf(info, "%s[Out][%10s]n", info, of_temp->name);
	        of_temp = av_muxer_iterate(&opaque_out);
	    }
	    return ret;
	}
	
	int getFFmpegSupportAVCodec(char *info) {
	    int ret = 0;
	    void *opaque = nullptr;
	    const AVCodec *c_temp = av_codec_iterate(&opaque);
	
	    while (c_temp != nullptr) {
	        if (av_codec_is_decoder(c_temp) != 0) {
	            ret = sprintf(info, "%s[Dec]", info);
	        } else {
	            ret = sprintf(info, "%s[Enc]", info);
	        }
	        switch (c_temp->type) {
	            case AVMEDIA_TYPE_VIDEO:
	                sprintf(info, "%s[Video]", info);
	                break;
	            case AVMEDIA_TYPE_AUDIO:
	                sprintf(info, "%s[Audio]", info);
	                break;
	            default:
	                sprintf(info, "%s[Other]", info);
	                break;
	        }
	        ret = sprintf(info, "%s[%10s]n", info, c_temp->name);
	        c_temp = av_codec_iterate(&opaque);
	    }
	    return ret;
	}
	
	int getFFmpegSupportAVFilter(char *info) {
	    int ret = 0;
	    void *opaque = nullptr;
	    const AVFilter *f_temp = av_filter_iterate(&opaque);
	    while (nullptr != f_temp) {
	        ret = sprintf(info, "%s[%10s]n", info, f_temp->name);
	        f_temp = av_filter_iterate(&opaque);
	    }
	    return ret;
	}
	
	int testLib() {
	    const AVCodec *avCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
	    if (nullptr == avCodec) {
	        BZLogUtil::logD("avcodec_find_decoder AV_CODEC_ID_H264 未发现");
	    } else {
	        BZLogUtil::logD("avcodec_find_decoder AV_CODEC_ID_H264 正常");
	    }
	    const AVCodec *avCodecAAC = avcodec_find_decoder(AV_CODEC_ID_AAC);
	    if (nullptr == avCodecAAC) {
	        BZLogUtil::logD("avcodec_find_decoder AV_CODEC_ID_AAC 未发现");
	    } else {
	        BZLogUtil::logD("avcodec_find_decoder AV_CODEC_ID_AAC 正常");
	    }
	
	
	    const AVCodec *avCodecEncoder = avcodec_find_encoder(AV_CODEC_ID_H264);
	    if (nullptr == avCodecEncoder) {
	        BZLogUtil::logD("avcodec_find_encoder AV_CODEC_ID_H264 未发现");
	    } else {
	        BZLogUtil::logD("avcodec_find_encoder AV_CODEC_ID_H264 正常");
	    }
	
	
	    const AVCodec *avCodecDecoderMp3 = avcodec_find_decoder(AV_CODEC_ID_MP3);
	    if (nullptr == avCodecDecoderMp3) {
	        BZLogUtil::logD("avcodec_find_decoder AV_CODEC_ID_MP3 未发现");
	    } else {
	        BZLogUtil::logD("avcodec_find_decoder AV_CODEC_ID_MP3 正常");
	    }
	
	
	    const AVCodec *avCodecAACEncoder = avcodec_find_encoder(AV_CODEC_ID_AAC);
	    if (nullptr == avCodecAACEncoder) {
	        BZLogUtil::logD("avcodec_find_encoder AV_CODEC_ID_AAC 未发现");
	    } else {
	        BZLogUtil::logD("avcodec_find_encoder AV_CODEC_ID_AAC 正常");
	    }
	    return 0;
	}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/29258.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机视觉-目标检测(二):从R-FCN到YOLO-v3

文章目录 1. R-FCN1.1 动机1.2. R-FCN 网络结构1.3. R-FCN 的损失函数1.4. R-FCN的训练及性能 2. YoLO-v12.1 简介2.2 YOLO-v1网络结构2.3 目标函数2.4 YOLO-v1的优缺点 3. YOLO-v23.1 YOLO-v2相比v1的优化 4. YOLO-v3参考 1. R-FCN 论文链接&#xff1a;R-FCN:Object Detecti…

在提交代码时有哪些注意事项

分享 10 种适合初学者的技术&#xff0c;这些技术将帮助您立即编写更好的代码。因此&#xff0c;如果您准备好将您的编码技能提升到一个新的水平&#xff0c;请继续阅读&#xff01; 1. 从计划开始 编写更好代码的最佳方法之一是从计划开始。在开始编码之前&#xff0c;请花几…

Nginx+Tomcat负载均衡、动静分离

Nginx应用 Nginx是一款非常优秀的HTTP服务器软件 支持高达50000个并发连接数的响应拥有强大的静态资源处理能力运行稳定内存、CPU等系统资源消耗非常低 目前很多大型网站都应用Nginx服务器作为后端网站程序的反向代理及负载均衡器&#xff0c;提升整个站点的负载并发能力 反向…

STM32 Proteu直流电机正反转控制系统限位开关-0035

STM32 Proteu直流电机正反转控制系统限位开关-0035 Proteus仿真小实验&#xff1a; STM32 Proteu直流电机正反转控制系统限位开关-0035 功能&#xff1a; 硬件组成&#xff1a;STM32F103C6单片机 L298N电机控制电路直流电机3个按键&#xff08;正转、反转、停止&#xff09;L…

Java企业级信息系统开发学习笔记(4.3) Spring Boot两种全局配置和两种注解

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/Z2viP】 文章目录 一、Application.properites配置文件1.1 创建Spring Boot的Web项目ProperitesDemo1.2 在应用属性文件里添加相关配置1. 配置服务器端口号和web虚拟路径2. 对象类型的配置与使用&…

【6.09 代随_52day】 最长递增子序列、最长连续递增序列、最长重复子数组

最长递增子序列、最长连续递增序列、最长重复子数组 最长递增子序列1.方法图解步骤递归代码 最长连续递增序列1.动态规划的方法图解步骤代码 最长重复子数组图解步骤代码 最长递增子序列 力扣连接&#xff1a;300. 最长递增子序列&#xff08;中等&#xff09; 1.方法 dp[i]…

【计算机网络自顶向下】如何学好计网-第二章应用层

第二章 应用层 应用层协议原理 网络应用程序体系结构 客户机/服务器体系结构&#xff1a;至少有一个服务器&#xff0c;一个客户机&#xff0c;其中服务器总是打开的&#xff0c;具有固定的众所周知的IP地址&#xff0c;主机群集常被用于创建强大的虚拟服务器&#xff0c;而客…

开发新项目看过来,这3款基于 Vue 的免费开源的 admin 管理后台框架非常好用

三款 admin 框架&#xff0c;分别基于热门的前端 UI 组件库 ElementPlus / Ant Design / Naive UI 打造&#xff0c;开箱即用。 新项目的开始&#xff0c;一般是搭建 admin 系统&#xff0c;今天盘点一下3个好的选择。 Vue vben admin 了解详细&#xff1a;https://www.thos…

数据建模学习2--作业-利用matlab解决实际问题

文章目录 Malthus模型问题用最小儿二乘法估计阻滞增长模型自来水运输问题利用 Dijkstra 算法计算下图中起点 D 至各顶点得最短距离&#xff0c;需要给出 仅供参考&#xff0c;代码注意修改 Malthus模型问题 1790-1980年间美国每隔10年的人口数量记录如下表所示。 表1 1790-1…

虚拟机(VMware )部署

一、VMware 概述&#xff1a; VMware是一家提供虚拟化解决方案的领先公司&#xff0c;其产品被广泛应用于企业和个人用户的计算环境中。VMware的虚拟化技术可以将物理计算资源&#xff08;如服务器、存储和网络&#xff09;抽象成虚拟化的资源&#xff0c;从而提供更高的灵活性…

kali学习笔记(二)

一、关闭自动锁屏 关闭自动锁屏对于测试人员来说&#xff0c;可以按照自己的习惯来设置&#xff0c;不然kali会过十分钟就锁屏&#xff0c;有的时候会比较不方便。 1、使用root账号登录&#xff0c;在display设置选项中做如下设置。 2、把休眠选项关掉。 二、创建快照 关机创…

算法刷题-数组-长度最小的子数组

209.长度最小的子数组 力扣题目链接 给定一个含有 n 个正整数的数组和一个正整数 s &#xff0c;找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组&#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0。 示例&#xff1a; 输入&#xff1a;s 7, …

React-Redux 对Todolist修改

在单独使用redux的时候 需要手动订阅store里面 感觉特别麻烦 不错的是react有一个组件可以帮我们解决这个问题, 那就是react-redux。 react-redux提供了Provider 和 connent给我们使用。 先说一下几个重点知道的知识 Provider 就是用来提供store里面的状态 自动getState()co…

MySQL——视图(VIEW)详解

今天我们一起来学起视图(VIEW)&#xff0c;那么视图是什么呢&#xff1f;视图有什么作用呢&#xff1f;视图一方面可以帮我们使用表的一部分而不是所有的表&#xff0c;另一方面也可以针对不同的用户制定不同的查询视图&#xff01;带着问题一起来寻找答案吧~~~ 1. 常见的数据库…

Zookeeper概述

​ ZooKeeper概述 ZooKeeper是什么 zookeeper是一个为分布式应用程序提供的一个分布式开源协调服务框架。是Google的Chubby的一个开源实现&#xff0c;是Hadoop和Hbase的重要组件。主要用于解决分布式集群中应用系统的一致性问题。提供了基于类似Unix系统的目录节点树方式的数…

python3 爬虫相关学习9:BeautifulSoup 官方文档学习

目录 1 BeautifulSoup 官方文档 2 用bs 和 requests 打开 本地html的区别&#xff1a;代码里的一段html内容 2.1 代码和运行结果 2.2 用beautiful 打开 本地 html 文件 2.2.1 本地html文件 2.2.2 soup1BeautifulSoup(html1,"lxml") 2.3 用requests打开 本地 h…

【默认端口】市面上各种中间件、软件、服务的默认端口汇总

常用软件&#xff0c;中间件&#xff0c;服务的默认端口汇总 常用软件默认端口汇总 市面上各种中间件、软件和服务的默认端口众多&#xff0c;下面列举一些常见的默认端口&#xff1a; SSH&#xff08;Secure Shell&#xff09;&#xff1a;22 Telnet&#xff1a;23 FTP…

赛宁网安助力智能网联汽车发展 | “饶派杯”XCTF车联网安全挑战赛圆满收官

​​ 2023年5月31日&#xff0c;“饶派杯”XCTF车联网安全挑战赛在江西省上饶市圆满落幕。本次大赛特邀国内21支精英战队参与比拼&#xff0c;参赛选手覆盖全国知名高校、自动驾驶汽车和科研院所等车联网安全人才。最终&#xff0c;经过9个小时激烈角逐&#xff0c;来自南京邮电…

后端(二):Servlet

我们上一张聊的是Tomcat&#xff0c;它其实就是一个 HTTP 服务器&#xff0c;而Servlet 是基于 Tomcat 的 原生api &#xff0c;除了 Servlet&#xff0c;后面还有聊到很多 api 。 Servlet 是什么 Servlet&#xff08;Server Applet&#xff09;是Java Servlet的简称&#xf…

动态规划算法(多状态dp1)

动态规划算法专辑之多状态dp问题&#xff08;1&#xff09; 一、什么是多状态 多状态dp问题&#xff0c;指一个规模问题下存在多种状态&#xff0c;我们需要联合关注多种状态间的相互转移&#xff0c;才可以求解目的问题。 多状态问题可以理解为有限状态机&#xff0c;在有限…