FFMPEG使用DrawText滤镜添加字幕,包含ndk编译freetype

该文详细介绍了如何在Ubuntu环境下交叉编译freetype库,并将其用于ffmpeg中启用drawtext滤镜。编译流程包括下载freetype源码,配置编译选项,以及编写和执行编译脚本。然后,文章提供了ffmpeg的编译脚本,使能了drawtext和其他相关编解码库。最后,文章简要提及了如何在代码中使用drawtext滤镜来添加文本到视频帧上。
摘要由CSDN通过智能技术生成

        ffmpeg使用drawtext滤镜需要在编译的时候使能drawtext,要想成功使能必须要先集成编译freetype库,并通知到ffmpeg(交叉编译没有安装到系统库路径)。

        也有看到有的文章说需要集成fribidi,笔者也交叉编译了,但是最终没有用到,可能是ffmpeg版本的原因。

一、交叉编译freetype

freetype:下载地址:https://freetype.org/download.html

笔者选择了红框链接进行下载,下载的2.10版本。

(ps:下载的时候我没注意时间,以为最上面的是最新的【笑哭】)

下载完成后解压文件(解压到当前文件夹)

tar -zxvf freetype-2.10.0.tar.gz .

接下来就是交叉编译了,没有交叉编译环境的朋友可以参考我之前的文章搭建环境,传送门Android FFMPEG编解码实践(一):Ubuntu 22.04 NDK编译FFMPEG+libx264_android编译libx264-CSDN博客

 新建shell脚本文件,文件内容如下。笔者使用的是ubuntu的虚拟机,所以工具链选择的是linux-x86_64,其他的大家根据自己的环境路径修改一下DNK的路径。

#!/bin/bash
export NDK=/home/selivert/ndk/android-ndk-r21e #NDK path
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64
export API=21

function build_android
{
APP_ABI=$1
  echo "======== > Start build $APP_ABI"
  case ${APP_ABI} in
  armeabi-v7a)
    HOST=armv7a-linux-android
    CROSS_PREFIX=$TOOLCHAIN/bin/arm-linux-androideabi-
    ;;
  arm64-v8a)
    HOST=aarch64-linux-android
    CROSS_PREFIX=$TOOLCHAIN/bin/aarch64-linux-android-
    ;;

  esac

./configure \
	--prefix=$PREFIX \
	--disable-cli \
	--enable-shared \
	--with-pic \
	--enable-static \
	--enable-strip \
	--host=$HOST

make clean
make -j4
make install
}

PREFIX=`pwd`/android/armeabi-v7a
SYSROOT=$TOOLCHAIN/sysroot
export TARGET=armv7a-linux-androideabi
export CC=$TOOLCHAIN/bin/$TARGET$API-clang
export CXX=$TOOLCHAIN/bin/$TARGET$API-clang++
#export EXTRA_CFLAGS="-O0 -g"
#export EXTRA_CXXFLAGS="-O0 -g"

build_android armeabi-v7a

PREFIX=`pwd`/android/arm64-v8a
export TARGET=aarch64-linux-android
export CC=$TOOLCHAIN/bin/$TARGET$API-clang
export CXX=$TOOLCHAIN/bin/$TARGET$API-clang++
build_android arm64-v8a

上面的脚本同时编译了32位与64的库。

执行脚本,编译完成后(笔者过程很顺利,一次过),我们到输出目录下查看结果:

 pkgconfig和库文件都有了,编译完成。将结果拷贝到外部文件夹,方便ffmpeg集中引用。

笔者放在用户目录下的out文件夹中,区分了32位与64位。

32位目录结构如下,内部包含了fdk-aac x264 x265等第三方开源库

二、编译ffmpeg 并使能drawtext滤镜

ffmpeg资源下载不再赘述,参考之前的文章。笔者使用的是4.4.2版本(这个版本官网好像找不到了,4.4.3应该也没问题)。

不多说,直接上编译脚本 ffmpeg_build_android.sh,注意ndk路径换成自己的(tag1)。如果不需要编码功能,注释掉(tag2)

#!/bin/bash
# 用于编译android平台的脚本

# NDK所在目录
NDK_PATH=/home/selivert/ndk/android-ndk-r21e # tag1

HOST_PLATFORM=linux-x86_64  
# minSdkVersion
API=21

TOOLCHAINS="$NDK_PATH/toolchains/llvm/prebuilt/$HOST_PLATFORM"
SYSROOT="$NDK_PATH/toolchains/llvm/prebuilt/$HOST_PLATFORM/sysroot"
# 生成 -fpic 与位置无关的代码
CFLAG="-D__ANDROID_API__=$API -Os -fPIC -DANDROID "
LDFLAG="-lc -lm -ldl -llog "

# 输出目录
PREFIX=`pwd`/android
# 日志输出目录
CONFIG_LOG_PATH=${PREFIX}/log

# 公共配置
COMMON_OPTIONS=
# 交叉配置
CONFIGURATION=

build() {
  APP_ABI=$1
  CFLAGTHIRD=-I/home/selivert/out/$APP_ABI/include
  LDFLAGTHIRD=-L/home/selivert/out/$APP_ABI/lib

  echo "======== > Start build $APP_ABI"
  case ${APP_ABI} in
  armeabi-v7a)
    ARCH="arm"
    CPU="armv7-a"
    MARCH="armv7-a"
    TARGET=armv7a-linux-androideabi
    CC="$TOOLCHAINS/bin/$TARGET$API-clang"
    CXX="$TOOLCHAINS/bin/$TARGET$API-clang++"
    LD="$TOOLCHAINS/bin/$TARGET$API-clang"
   
    # 交叉编译工具前缀
    CROSS_PREFIX="$TOOLCHAINS/bin/arm-linux-androideabi-"
    EXTRA_CFLAGS="$CFLAG -mfloat-abi=softfp -mfpu=vfp -marm -march=$MARCH "
    EXTRA_LDFLAGS="$LDFLAG"
    EXTRA_OPTIONS="--enable-neon --cpu=$CPU "
    ;;
  arm64-v8a)
    ARCH="aarch64"
    TARGET=$ARCH-linux-android
    CC="$TOOLCHAINS/bin/$TARGET$API-clang"
    CXX="$TOOLCHAINS/bin/$TARGET$API-clang++"
    LD="$TOOLCHAINS/bin/$TARGET$API-clang"
    CROSS_PREFIX="$TOOLCHAINS/bin/$TARGET-"
    EXTRA_CFLAGS="$CFLAG"
    EXTRA_LDFLAGS="$LDFLAG"
    EXTRA_OPTIONS=""
    ;;
  x86)
    ARCH="x86"
    CPU="i686"
    MARCH="i686"
    TARGET=i686-linux-android
    CC="$TOOLCHAINS/bin/$TARGET$API-clang"
    CXX="$TOOLCHAINS/bin/$TARGET$API-clang++"
    LD="$TOOLCHAINS/bin/$TARGET$API-clang"
    CROSS_PREFIX="$TOOLCHAINS/bin/$TARGET-"
    #EXTRA_CFLAGS="$CFLAG -march=$MARCH -mtune=intel -mssse3 -mfpmath=sse -m32"
    EXTRA_CFLAGS="$CFLAG -march=$MARCH  -mssse3 -mfpmath=sse -m32 "
    EXTRA_LDFLAGS="$LDFLAG"
    EXTRA_OPTIONS="--cpu=$CPU "
    ;;
  x86_64)
    ARCH="x86_64"
    CPU="x86-64"
    MARCH="x86_64"
    TARGET=$ARCH-linux-android
    CC="$TOOLCHAINS/bin/$TARGET$API-clang"
    CXX="$TOOLCHAINS/bin/$TARGET$API-clang++"
    LD="$TOOLCHAINS/bin/$TARGET$API-clang"
    CROSS_PREFIX="$TOOLCHAINS/bin/$TARGET-"
    #EXTRA_CFLAGS="$CFLAG -march=$CPU -mtune=intel -msse4.2 -mpopcnt -m64"
    EXTRA_CFLAGS="$CFLAG -march=$CPU -msse4.2 -mpopcnt -m64 "
    EXTRA_LDFLAGS="$LDFLAG"
    EXTRA_OPTIONS="--cpu=$CPU "
    ;;
  esac

  echo "-------- > Start clean workspace"
make clean

  echo "-------- > Start build configuration"
  CONFIGURATION="$COMMON_OPTIONS"
  CONFIGURATION="$CONFIGURATION --logfile=$CONFIG_LOG_PATH/config_$APP_ABI.log"
  CONFIGURATION="$CONFIGURATION --prefix=$PREFIX"
  CONFIGURATION="$CONFIGURATION --libdir=$PREFIX/libs/$APP_ABI"
  CONFIGURATION="$CONFIGURATION --incdir=$PREFIX/includes/$APP_ABI"
  CONFIGURATION="$CONFIGURATION --pkgconfigdir=$PREFIX/pkgconfig/$APP_ABI"
  CONFIGURATION="$CONFIGURATION --cross-prefix=$CROSS_PREFIX"
  CONFIGURATION="$CONFIGURATION --arch=$ARCH"
  CONFIGURATION="$CONFIGURATION --sysroot=$SYSROOT"
  CONFIGURATION="$CONFIGURATION --cc=$CC"
  CONFIGURATION="$CONFIGURATION --cxx=$CXX"
  CONFIGURATION="$CONFIGURATION --ld=$LD"
  # nm 和 strip
  CONFIGURATION="$CONFIGURATION --nm=$TOOLCHAINS/bin/llvm-nm"
  CONFIGURATION="$CONFIGURATION --strip=$TOOLCHAINS/bin/llvm-strip"
  CONFIGURATION="$CONFIGURATION $EXTRA_OPTIONS"

  echo "-------- > Start config makefile with $CONFIGURATION --extra-cflags=${EXTRA_CFLAGS}${CFLAG264} --extra-ldflags=${EXTRA_LDFLAGS}${LDFLAG264}"
  
  ./configure ${CONFIGURATION} \
  --pkg-config="pkg-config --static" \
  --extra-cflags="$EXTRA_CFLAGS$CFLAGTHIRD" \
  --extra-ldflags="$EXTRA_LDFLAGS$LDFLAGTHIRD"
  
  #exit

  echo "-------- > Start make $APP_ABI with -j1"
  make -j1

  echo "-------- > Start install $APP_ABI"
  make install
  echo "++++++++ > make and install $APP_ABI complete."

}

build_all() {
  #配置开源协议声明
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-gpl"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-nonfree"
  #目标android平台
  COMMON_OPTIONS="$COMMON_OPTIONS --target-os=android"
  #配置动态库  静态库静
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-static"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-shared"
  #COMMON_OPTIONS="$COMMON_OPTIONS --disable-static"
  #COMMON_OPTIONS="$COMMON_OPTIONS --disable-shared"
  
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-protocols"
  #开启交叉编译
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-cross-compile"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-optimizations"
  COMMON_OPTIONS="$COMMON_OPTIONS --disable-debug"
  #尽可能小
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-small"
  COMMON_OPTIONS="$COMMON_OPTIONS --disable-doc"
  #不要命令(执行文件)
  COMMON_OPTIONS="$COMMON_OPTIONS --disable-programs"  # do not build command line programs
  COMMON_OPTIONS="$COMMON_OPTIONS --disable-ffmpeg"    # disable ffmpeg build
  COMMON_OPTIONS="$COMMON_OPTIONS --disable-ffplay"    # disable ffplay build
  COMMON_OPTIONS="$COMMON_OPTIONS --disable-ffprobe"   # disable ffprobe build
  COMMON_OPTIONS="$COMMON_OPTIONS --disable-symver"
  #COMMON_OPTIONS="$COMMON_OPTIONS --disable-network"
  COMMON_OPTIONS="$COMMON_OPTIONS --disable-x86asm"
  COMMON_OPTIONS="$COMMON_OPTIONS --disable-asm"
  #启用
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-pthreads"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-mediacodec"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-jni"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-zlib"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-pic"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-libx264"             #tag2
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-libfdk-aac"          #tag2
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-libfreetype"         
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-encoder=libfdk_aac"  #tag2
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-libx265"             #tag2
  #COMMON_OPTIONS="$COMMON_OPTIONS --enable-avresample"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-encoder=libx264"     #tag2
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-muxer=flv"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-decoder=mpeg4"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-decoder=mjpeg"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-decoder=png"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-decoder=vorbis"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-decoder=opus"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-decoder=flac"
  COMMON_OPTIONS="$COMMON_OPTIONS --enable-filter=drawtext"
  

 
  echo "COMMON_OPTIONS=$COMMON_OPTIONS"
  echo "PREFIX=$PREFIX"
  echo "CONFIG_LOG_PATH=$CONFIG_LOG_PATH"
  mkdir -p ${CONFIG_LOG_PATH}
  #build "armeabi-v7a"
  build "arm64-v8a"
  #build "x86"
  #build "x86_64"
}

#export PKG_CONFIG_PATH=/home/selivert/out/armeabi-v7a/lib/pkgconfig

echo "-------- Start --------"
build_all
echo "-------- End --------"

需要注意的是,笔者shell脚本的基本功基本等于0,也没有将32位与64位分成两个脚本,编译32位与64的库时需要手动修改脚本。

编译32位:

上述脚本注释build "arm64-v8a",放开build "armeabi-v7a"保存后,在命令行执行如下命令

export PKG_CONFIG_PATH=/home/selivert/out/armeabi-v7a/lib/pkgconfig

./ffmpeg_build_android.sh

编译64位:

 上述脚本放开build "arm64-v8a", 注释build "armeabi-v7a"保存后,在命令行执行如下命令

export PKG_CONFIG_PATH=/home/selivert/out/arm64-v8a/lib/pkgconfig

./ffmpeg_build_android.sh

编译完成,到android目录下,将32位与64位的库与头文件拷贝出来,为android项目集成做准备。

PS:头文件拷贝ffmpeg的即可,库文件除了ffmpeg的so文件,还需拷贝依赖的第三方库文件(如libfreetype.so)。

三、代码使用drawtext

        使用drawtext主要是初始化滤镜与使用滤镜。直接上伪代码。

初始化:需要注意的是字体文件(fontfile)需要给全路径。

AVFilterGraph * filter_graph = NULL;
AVFilterContext *bufferSinkCtx = NULL;
AVFilterContext *bufferSrcCtx = NULL;
AVFilterContext* filterCtx = NULL;

int initFilter(AVCodecContext * codecContext)
{
    char args[512] = {0};
    int ret = 0;
    int nb_filters = 0;

    const AVFilter *buffersrc = avfilter_get_by_name("buffer");
    const AVFilter *buffersink = avfilter_get_by_name("buffersink");
    AVFilterInOut *outputs = avfilter_inout_alloc();
    AVFilterInOut *inputs = avfilter_inout_alloc();
    char filters_descr[2048] = {0};
    char font[] = {"/system/fonts/NotoSansCJK-Regular.ttc"};  // 绘制文字的字体,安卓系统中文字体(中日韩)
    int fontSize = 36;                                        // 绘制文字的大小 pix
    int yLocation = codecContext->height - fontSize * 3.5;
    sprintf(filters_descr,"drawtext=fontfile='%s':fontcolor=white:fontsize=%d:text='hello ffmpeg':x=(w-text_w)/2:y=%d",font, fontSize, yLocation);  // 文字居中在视频底部


    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };

    filter_graph = avfilter_graph_alloc();
    if (!outputs || !inputs || !filter_graph) {
        ret = AVERROR(ENOMEM);
        LOGE("initFilter ENOMEM \n");
        goto end;
    }

    /* buffer video source: the decoded frames from the decoder will be inserted here. */
    sprintf(args,
              "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
              codecContext->width, codecContext->height, codecContext->pix_fmt,
              codecContext->time_base.num, codecContext->time_base.den,
              codecContext->sample_aspect_ratio.num, codecContext->sample_aspect_ratio.den);

    LOGE("initFilter:  %s\n", args);

    ret = avfilter_graph_create_filter(&bufferSrcCtx, buffersrc, "in",
                                       args, NULL, filter_graph);
    if (ret < 0) {
        LOGE("Cannot create buffer source\n");
        goto end;
    }

    /* buffer video sink: to terminate the filter chain. */
    ret = avfilter_graph_create_filter(&bufferSinkCtx, buffersink, "out",
                                       NULL, NULL, filter_graph);
    if (ret < 0) {
        LOGE("Cannot create buffer sink\n");
        goto end;
    }

    ret = av_opt_set_int_list(bufferSinkCtx, "pix_fmts", pix_fmts,
                              AV_PIX_FMT_YUV420P, AV_OPT_SEARCH_CHILDREN);
    if (ret < 0) {
        LOGE("Cannot set output pixel format\n");
        goto end;
    }

    /* Endpoints for the filter graph. */
    outputs->name = av_strdup("in");
    outputs->filter_ctx = bufferSrcCtx;
    outputs->pad_idx = 0;
    outputs->next = NULL;

    inputs->name = av_strdup("out");
    inputs->filter_ctx = bufferSinkCtx;
    inputs->pad_idx = 0;
    inputs->next = NULL;


    nb_filters = filter_graph->nb_filters;

    if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,
                                        &inputs, &outputs, NULL)) < 0) {
        LOGE("avfilter_graph_parse_ptr failed  %s %x\n", filters_descr, AVERROR(ret));
        goto end;
    }

    filterCtx = filter_graph->filters[nb_filters];

    if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0) {
        LOGE("avfilter_graph_config failed\n");
        goto end;
    }

    return ret;
end:
    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);
    avfilter_graph_free(&filter_graph);
    filter_graph = NULL;
    return ret;
}

视频内容绘制文字:



...
// picFrame 存放视频数据的frame,像素格式为yuv420
//添加字幕
int ret = -1;
if (filter_graph && openDrawText) {
	ret = av_buffersrc_add_frame_flags(bufferSrcCtx, picFrame, AV_BUFFERSRC_FLAG_PUSH);
	if (ret < 0) {
		LOGE("Error while feeding the filtergraph  ret:%x\n", AVERROR(ret));
	}
}

if (ret < 0) {
	... // 后续逻辑,如编码,渲染
} else {
	index++;

	if (filterCtx && index == 200) {
        // 修改绘制的文字
		int  ft = av_opt_set_int(filterCtx->priv, "fontsize", 18, 0 );
		LOGE("=========== av_opt_set  fontsize ret:%x    \n", AVERROR(ft));
		av_opt_set(filterCtx->priv, "text", "胜多负少多发发所发一所大所大所大所大所二大所大所大所大所大三所大所大所大所大所四大所大所大\n所大所水五电费水电费水电费水电费水电费水电费", 0 );
	}

	AVFrame*  filterFrame = av_frame_alloc();
	/* pull filtered frames from the filtergraph */
	while (1) {
		ret = av_buffersink_get_frame(bufferSinkCtx, filterFrame);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			break;
		if (ret < 0) {
			LOGE("Error while get the filtergraph  error:%x\n", AVERROR(ret));
			break;
		}
		
		... // 后续逻辑,如编码,渲染
	}
	av_frame_free(&filterFrame);
}

上效果图:(图像数据是全0的yuv420数据,所以是绿的)

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值