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)、8×8变换等。Baseline Profile可用于低比特率、低延迟和低复杂度的应用。

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

  3. Extended Profile:Extended Profile扩展了Main Profile,并添加了更多的高级功能,例如8×8和4×4变换、支持更高分辨率、更高的比特率和更丰富的颜色空间。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:支持最大352×288像素分辨率和1.5 Mbps的解码速度。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  17. Level 5.2:支持最大4096×2304像素分辨率和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但是在实际开发中还需要做修改
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文件

修改完的build脚本工程详见这里:https://github.com/bookzhan/ffmpeg-android-build

3.Android 开发环境搭建

3.1 新建工程

新建工程,选择Native c++,然后一直点Next就好了

生成的目录如下:

3.1.1 指定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 
#include 
#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;
}
此条目发表在Android, FFmpeg分类目录。将固定链接加入收藏夹。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注