美文网首页业余整理的工作笔记
Android视音频开发初探【一】(clang编译FFmpeg+

Android视音频开发初探【一】(clang编译FFmpeg+

作者: 北野青阳 | 来源:发表于2020-06-15 15:16 被阅读0次

    demo地址https://github.com/ColorfulHorse/learnFFmpeg, 包含编译脚本

    本文主要参考https://github.com/byhook/ffmpeg4android以及雷霄骅博客

    下一篇Android视音频开发初探【二】(简单的相机推流器)

    一些概念

    什么是音视频开发?

    简单点说分为两个方面,一方面是播放视频的时候,要经历:解协议(网络视频,rtmp,rtsp)-> 解封装(flv, mp4) -> 解码(h.264,h.265) -> 得到rgb/yuv原始数据,音频得到pcm原数据,然后分别进行绘制和播放;
    另一方面是录制视频的时候,流程刚好反过来:采集原数据 -> 原始数据编码 -> 音视频封装到一起 -> 协议封装传输(网络视频)。

    关于各种格式和协议介绍更详细的知识可以看雷霄骅的这篇博客 视音频编解码技术零基础学习方法

    为什么要进行编解码?

    归根结底还是因为原数据太大,必须进行压缩。就拿我们熟悉的rgb565(一个像素2byte)来说,1080x1920 30fps的视频一秒钟需要1080x1920x2x30 = 118MB,这种量级是完全无法接受的,所以必然要进行压缩;对应图片有图片压缩算法,而视频由于动态连续性可以有更高压缩比的方法,这就是视频编码了。

    比如h.264编码格式,就定义了I帧P帧B帧,I帧可以解析成一个完整的图像;P帧需要根据它之前的帧来补充自己的信息,本身存的信息比较少;B帧则需要通过前后帧来补充自己。关于H.264更详细一点的知识可以看这篇博客https://www.jianshu.com/p/1b3f8187b271

    FFmpeg能做什么?

    FFmpeg是一个纯c编写的开源库,提供了对音视频进行编解码、变换、采样、格式封装、滤镜后处理的一系列接口,由于统一了上层接口,开发者能更容易基于它开发兼容多平台的应用。视频编码标准迄今为止已经发展了好几代,每一个标准都有若干编解码器的实现,比如x264是最好的h.264编码器,把它接入到FFmpeg以后我们无需再关心x264的api,只需要通过FFmpeg提供的统一编码接口就可以使用它。

    另外,编解码分为软编解码和硬编解码两种,软对应cpu,硬对应gpu,硬编解码虽然速度更快,但是并非所有设备都支持;软编解码效率较低占用资源也比较大,但是胜在兼容性,综合来说软硬结合比较合理。android中提供了硬编解码的api,java层对应MediaCodec类,使用FFmpeg添加mediacodec支持也可以在native层调用它,不过目前仅限于解码,还不支持编码。

    编译FFmpeg

    上面说了这么多,我们来开始第一步,编译FFmpeg。我的编译环境是虚拟机Ubuntu 20.04 LTS 桌面版,
    ndkr21,ffmpeg4.2.3,x264最新版,fdk-aac2.0.1(音频编码),openssl1.1.1(用于添加https支持),对应的源码到官网去下载就好,需要注意的是ndk要下载linux版本。高版本的ndk已经移除了gcc,所以我们用clang来编译,同时arm架构只需要适配armv7和armv8就可以了。

    目录图 编译的时候我们可以选择将所有库编译成.a静态库,然后把他们合并成一个.so动态库,但是这样的话最后的体积会比较大,我这里选择的是折衷的方式,将x264、fdkaac、openssl编译成静态库,然后动态编译FFmpeg生成多个so库。下面来编写编译脚本,这需要一点点shell知识。

    定义公共变量

    config.sh 用于初始化一些公用变量

    
    #NDK路径
    export ANDROID_NDK_ROOT=/home/lyj/dev/android-ndk-r21
    
    export AOSP_API="21"
    
    #cpu架构
    if [ "$#" -lt 1 ]; then
        THE_ARCH=armv7
    else
        THE_ARCH=$(tr [A-Z] [a-z] <<< "$1")
    fi
    
    #根据不同架构配置变量
    case "$THE_ARCH" in
      armv7a|armeabi-v7a)
        TOOLNAME_BASE="arm-linux-androideabi"
        COMPILER_BASE="armv7a-linux-androideabi"
        AOSP_ABI="armeabi-v7a"
        AOSP_ARCH="armeabi-v7a"
        OPENSSL_ARCH="android-arm"
        HOST="arm-linux-androideabi"
        FF_EXTRA_CFLAGS="-DANDROID -Wall -fPIC"
        FF_CFLAGS="-DANDROID -Wall -fPIC"
        ;;
      armv8|armv8a|aarch64|arm64|arm64-v8a)
        TOOLNAME_BASE="aarch64-linux-android"
        COMPILER_BASE="aarch64-linux-android"
        AOSP_ABI="arm64-v8a"
        AOSP_ARCH="arm64"
        OPENSSL_ARCH="android-arm64"
        HOST="aarch64-linux-android"
    
        FF_EXTRA_CFLAGS="-DANDROID -Wall -fPIC"
        FF_CFLAGS="-DANDROID -Wall -fPIC"
        ;;
      x86)
        TOOLNAME_BASE="i686-linux-android"
        COMPILER_BASE="i686-linux-android"
        AOSP_ABI="x86"
        AOSP_ARCH="x86"
        OPENSSL_ARCH="android-x86"
        HOST="i686-linux-android"
        FF_EXTRA_CFLAGS="-DANDROID -Wall -fPIC"
        FF_CFLAGS="-DANDROID -Wall -fPIC"
        ;;
      x86_64|x64)
        TOOLNAME_BASE="x86_64-linux-android"
        COMPILER_BASE="x86_64-linux-android"
        AOSP_ABI="x86_64"
        AOSP_ARCH="x86_64"
        OPENSSL_ARCH="android-x86_64"
        HOST="x86_64-linux-android"
        FF_EXTRA_CFLAGS="-DANDROID -Wall -fPIC"
        FF_CFLAGS="-DANDROID -Wall -fPIC"
        ;;
      *)
        echo "ERROR: Unknown architecture $1"
        [ "$0" = "$BASH_SOURCE" ] && exit 1 || return 1
        ;;
    esac
    # 工具链
    TOOLCHAIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64
    SYS_ROOT=$TOOLCHAIN/sysroot
    # 交叉编译路径
    CROSS_PREFIX=$TOOLCHAIN/bin/$TOOLNAME_BASE-
    # 编译器
    CC=$TOOLCHAIN/bin/$COMPILER_BASE$AOSP_API-clang
    CXX=$TOOLCHAIN/bin/$COMPILER_BASE$AOSP_API-clang++
    
    echo "TOOLNAME_BASE="$TOOLNAME_BASE
    echo "COMPILER_BASE="$COMPILER_BASE
    echo "AOSP_ABI="$AOSP_ABI
    echo "AOSP_ARCH="$AOSP_ARCH
    echo "HOST="$HOST
    

    编译x264

    build_x264.sh

    #!/bin/bash
    
    ARCH=$1
    
    # 导入配置文件
    source config.sh $ARCH
    # 输出路径
    LIBS_DIR=$(cd `dirname $0`; pwd)/libs/libx264
    echo "LIBS_DIR="$LIBS_DIR
    
    # x264源码路径
    cd x264
    
    export CC=$CC
    export CXX=$CXX
    export CXXFLAGS=$FF_EXTRA_CFLAGS
    export CFLAGS=$FF_CFLAGS
    export AR="${CROSS_PREFIX}ar"
    export LD="${CROSS_PREFIX}ld"
    export AS="${CROSS_PREFIX}as"
    export NM="${CROSS_COMPILE}nm"
    export STRIP="${CROSS_COMPILE}strip"
    export RANLIB="${CROSS_COMPILE}ranlib"
    
    PREFIX=$LIBS_DIR/$AOSP_ABI
    
    ./configure --prefix=$PREFIX \
    --enable-static \
    --enable-pic \
    --disable-cli \
    --disable-asm \
    --host=$HOST \
    --cross-prefix=$CROSS_PREFIX \
    --sysroot=$SYS_ROOT \
    --extra-cflags="$FF_CFLAGS" \
    --extra-ldflags=""
    
    make clean
    make -j2
    make install
    
    cd ..
    

    命令行sudo bash buid_x264.sh armv7a 编译对应平台,同时我们也可以写一个shell一次性编译所有平台,其他的脚本也可以参照这个做法
    build_x264_all.sh

    for arch in armeabi-v7a arm64-v8a x86 x86_64
    do
        bash build_x264.sh $arch
    done
    

    编译完以后输出如下

    image

    编译fdk-aac

    build_fdkaac.sh

    #!/bin/bash
    
    ARCH=$1
    
    source config.sh $ARCH
    LIBS_DIR=$(cd `dirname $0`; pwd)/libs/libfdk-aac
    
    cd fdk-aac-2.0.1
    
    
    PREFIX=$LIBS_DIR/$AOSP_ABI
    echo "PREFIX="$PREFIX
    
    export CC="$CC"
    export CXX="$CXX"
    export CFLAGS="$FF_CFLAGS"
    export CXXFLAGS="$FF_EXTRA_CFLAGS"
    # x86架构源码中使用了math库所以必须链接
    export LDFLAGS="-lm"
    export AR="${CROSS_PREFIX}ar"
    export LD="${CROSS_PREFIX}ld"
    export AS="${CROSS_PREFIX}as"
    
    
    ./configure \
    --prefix=$PREFIX \
    --target=android \
    --with-sysroot=$SYS_ROOT \
    --enable-static \
    --disable-shared \
    --host=$HOST 
    
    
    make clean
    make -j2
    make install
    
    cd ..
    

    编译fdkaac的时候你可能会遇到一些问题,比如下面这样

    fdkaac-error 这是因为fdk-aac已经作为android的一部分被构建了,所以直接引入了android才有的log库打印一些日志,我们可以到/libSBRdec/src/llp_tran.cpp源码中把log相关代码删掉,有几个地方需要删。本方法来自issue区作者的回答 tag1

    编译openssl

    openssl脚本: build_openssl.sh

    #!/bin/bash
    ARCH=$1
    source config.sh $ARCH
    LIBS_DIR=$(cd `dirname $0`; pwd)/libs/openssl
    PREFIX=$LIBS_DIR/$AOSP_ABI
    echo "PREFIX"=$PREFIX
    
    cd openssl
    export ANDROID_NDK_HOME=$ANDROID_NDK_ROOT
    export PATH=$TOOLCHAIN/bin:$PATH
    export CC="$CC"
    export CXX="$CXX"
    export AR="${CROSS_PREFIX}ar"
    export LD="${CROSS_PREFIX}ld"
    export AS="${CROSS_PREFIX}as"
    export NM="${CROSS_COMPILE}nm"
    
    ./Configure $OPENSSL_ARCH \
    -D__ANDROID_API__=$AOSP_API \
    --prefix=$PREFIX \
    no-shared \
    no-engine \
    no-dtls \
    no-hw
    
    make clean
    make -j2
    make install
    
    cd ..
    
    

    编译FFmpeg

    源码包里面有一个configure文件,我们可以./configure --help来查看编译配置,也可以去看文档
    FFmpeg wiki
    下面的脚本这么多配置是为了对库进行按需裁剪,可以根据自己的实际情况改动

    注意编译的时候要将 \ 后面的注释全部去掉,不然无法解析

    build_ffmpeg.sh

    #!/bin/bash
    # 编译ffmpeg,链接x264和fdkaac
    ARCH=$1
    
    source config.sh $ARCH
    NOW_DIR=$(cd `dirname $0`; pwd)
    LIBS_DIR=$NOW_DIR/libs
    
    # 源码目录,自行更改
    cd ffmpeg-4.2.3
    
    
    # 输出路径
    PREFIX=$LIBS_DIR/ffmpeg/$AOSP_ABI
    
    # 头文件目录
    FDK_INCLUDE=$LIBS_DIR/libfdk-aac/$AOSP_ABI/include
    # 库文件目录
    FDK_LIB=$LIBS_DIR/libfdk-aac/$AOSP_ABI/lib
    X264_INCLUDE=$LIBS_DIR/libx264/$AOSP_ABI/include
    X264_LIB=$LIBS_DIR/libx264/$AOSP_ABI/lib
    OPENSSL_INCLUDE=$LIBS_DIR/openssl/$AOSP_ABI/include
    OPENSSL_LIB=$LIBS_DIR/openssl/$AOSP_ABI/lib
    
    ./configure \
    --target-os=android \
    --prefix=$PREFIX \
    --enable-cross-compile \
    --disable-runtime-cpudetect \
    --disable-asm \
    --arch=$AOSP_ARCH \
    --cc=$CC \
    --cxx=$CXX \
    --cross-prefix=$CROSS_PREFIX \
    # 链接头文件路径
    --extra-cflags="-I$X264_INCLUDE  -I$FDK_INCLUDE -I$OPENSSL_INCLUDE $FF_CFLAGS" \
    --extra-cxxflags="$FF_EXTRA_CFLAGS" \
    # 链接库文件路径
    --extra-ldflags="-L$X264_LIB -L$FDK_LIB -L$OPENSSL_LIB" \
    --extra-libs=-lm \
    --sysroot=$SYS_ROOT \
    --disable-static \
    --enable-shared \
    --enable-jni \
    --enable-mediacodec \
    --enable-pthreads \
    --enable-pic \
    --disable-iconv \
    --enable-libx264 \
    --enable-libfdk_aac \
    --enable-openssl \
    --enable-gpl \
    --enable-nonfree \
    # 编复用器,用于格式封装
    --disable-muxers \
    --enable-muxer=mov \
    --enable-muxer=mp4 \
    --enable-muxer=h264 \
    --enable-muxer=avi \
    --enable-muxer=flv \
    --enable-muxer=hls \
    --enable-muxer=rtp \
    --enable-muxer=rtsp \
    # 解复用器
    --disable-demuxers \
    --enable-demuxer=mov \
    --enable-demuxer=h264 \
    --enable-demuxer=avi \
    --enable-demuxer=flv \
    --enable-demuxer=hls \
    --enable-demuxer=rtp \
    --enable-demuxer=rtsp \
    # 编码器
    --disable-encoders \
    --enable-encoder=aac \
    --enable-encoder=libfdk_aac \
    --enable-encoder=libx264 \
    --enable-encoder=mpeg4 \
    --enable-encoder=mjpeg \
    --enable-encoder=png \
    # 解码器
    --disable-decoders \
    --enable-decoder=aac \
    --enable-decoder=aac_latm \
    --enable-decoder=libfdk_aac \
    --enable-decoder=h264 \
    --enable-decoder=h264_mediacodec \
    --enable-decoder=mpeg4 \
    --enable-decoder=mjpeg \
    --enable-decoder=png \
    #解析器
    --disable-parsers \
    --enable-parser=aac \
    --enable-parser=aac_latm \
    --enable-parser=h264 \
    --enable-parser=mjpeg \
    --enable-parser=png \
    # 传输协议
    --disable-protocols \
    --enable-protocol=file \
    --enable-protocol=crypto \
    --enable-protocol=http \
    --enable-protocol=https \
    --enable-protocol=tls \
    --enable-protocol=tcp \
    --enable-protocol=udp \
    --enable-protocol=rtp \
    --enable-protocol=rtmp \
    --enable-protocol=rtmps \
    --enable-protocol=hls \
    # 其他
    --enable-zlib \
    --enable-small \
    --enable-postproc \
    --disable-outdevs \
    --disable-indevs \
    --disable-ffprobe \
    --disable-ffplay \
    --disable-ffmpeg \
    --disable-debug \
    --disable-symver 
    make clean
    make -j2
    make install
    
    cd ..
    
    

    如果加入了openssl也许你会碰到下图的情况,提示openssl not found

    image
    我们到FFmpeg源码目录/ffbuild/config.log看一下编译日志
    image
    FFmpeg会去调用openssl的一个函数来检查是否正确加入了openssl库,然而日志显示这个函数未定义。
    这是因为openssl自1.1.0版本以后将此函数改为了OPENSSL_init_ssl。所以我们需要手动到FFmpeg的configure文件里面做一些改动,直接到configure中搜索SSL_library_init添加下面三行。
    image
    check_lib openssl openssl/ssl.h OPENSSL_init_ssl -lssl -lcrypto ||
    check_lib openssl openssl/ssl.h OPENSSL_init_ssl -lssl32 -leay32 ||
    check_lib openssl openssl/ssl.h OPENSSL_init_ssl -lssl -lcrypto -lws2_32 -lgdi32 ||
    

    之后再重新编译就没有问题了,之后将FFmpeg输出目录中各个平台的头文件和库文件拷贝到项目中就可以了,下一篇博客我们来学习如何使用FFmpeg。

    image

    相关文章

      网友评论

        本文标题:Android视音频开发初探【一】(clang编译FFmpeg+

        本文链接:https://www.haomeiwen.com/subject/vvthxktx.html