一:编码介绍
软编码:使用cpu进行编码。
硬编码:使用显卡GPU专用的DSP\FPGA\ASIC芯片等硬件进行编码。
软编码的特点是升级方便,实现简单,但是cpu负载中,性能低。硬编码性能高。
在H.265之前,H.264一直是新一代的编码标准。H265主要是用来满足4
k,8k视频的需求,H.264以高压缩高质量和支持多种网络流媒体传输而闻名。经过h264标准对视频进行压缩编码,能够有效减小视频的文件大小,这主要是受益于他的低码率。与MPEG-2和MPEG-4 ASP等压缩技术相比,H.264压缩技术节省来用户的下载时间和流量。
H.264只是一个标准,具体实现包括JM,T264,x264。而由于种种原因,JM,T264等实现工具,并不能很好地继续完成我们的需求,因此x264便成了互联网世界中主流的实现H.264标准的编码工具。
FFMPEG默认只支持对H.264的解码,并不支持对H.264的编码,因此就需要我们手动配置FFMPEG来实现对H.264的编码。首先我们需要下载x264
二:编译x264
下载完成之后,我们需要首先编译x264为静态库,这是因为FFMPEG默认是通过静态链接方式来链接其他功能库。我们在编译ffmpeg时,修改了configure文件,这样方便生成可以让android识别的*.so文件,但是我们编译x264是生成的静态库,因此不需要修改x264的configure文件。下面我们直接放一个网上的编译脚本,build_x264_arm.sh:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #!/bin/bash NDK=/Users/xiaguangcheng/android/android-sdk-macosx/ndk-bundle SYSROOT=$NDK/platforms/android-18/arch-arm/ TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 CPU=arm PREFIX=/Users/xiaguangcheng/x264/arm ADDI_CFLAGS="" ADDI_LDFLAGS="" function build_x264 { ./configure \ --prefix=$PREFIX \ --disable-shared \ --disable-asm \ --enable-static \ --enable-pic \ --enable-strip \ --host=arm-linux-androideabi \ --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \ --sysroot=$SYSROOT --extra-cflags="-Os -fpic $ADDI_CFLAGS $OPTIMIZE_CFLAGS" \ --extra-ldflags="$ADDI_LDFLAGS" \ $ADDITIONAL_CONFIGURE_FLAG make clean make -j4 make install } build_x264 |
下面的几行需要根据你的电脑来完成配置。同时在x264文件夹下新建一个arm的文件夹用来存放编译之后的文件。
1 2 3 4 5 | NDK=/Users/xiaguangcheng/android/android-sdk-macosx/ndk-bundle SYSROOT=$NDK/platforms/android-18/arch-arm/ TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 CPU=arm PREFIX=/Users/xiaguangcheng/x264/arm |
接着我们通过命令cd x264进入到x264文件夹内,查看新建的build_x264_arm.sh文件的权限
1 2 | ls -l build_x264_arm.sh -rwxrwxrwx 1 xiaguangcheng staff 733 6 26 20:46 build_x264_arm.sh |
如果你的该文件不具备可执行权限,那么就需要手动添加可执行权限,添加完权限之后,记得再查看一下是否成功。
1 2 | chmod 777 build_x264_arm.sh ls -l build_x264_arm.sh |
如果还没有安装yasm那么可以通过homebrew安装
1 | brew install yasm |
否则可能会报一个错误
1 | if you really want to compile without asm, configure with --disable-asm. make: *** [config.mak] Error 1 Makefile:3: config.mak: No such file or directory |
接下来我们在命令行中就可以运行该文件进行编译
1 | ./build_x264_arm.sh |
待编译结束后,我们在刚刚创建的arm文件夹中的就可以找到一个.a静态库。
image.png
三:编译含有x264静态库的ffmpeg动态库
在第一篇中我们讲解来如何编译一个ffmpeg动态库,但是那个动态库并没有包含x264,这里我们将x264包含进来。因此在编译ffmpeg动态库的编译文件基础之上进行略微的修改即可,buildffmpeg.sh文件内容更新如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #!/bin/bash export TMPDIR=/Users/xiaguangcheng/ffmpeg/ffmpeg/ffmpegtemp NDK=/Users/xiaguangcheng/android/android-sdk-macosx/ndk-bundle SYSROOT=$NDK/platforms/android-18/arch-arm/ TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 CPU=arm PREFIX=/Users/xiaguangcheng/ffmpeg/ffmpeg/android ADDI_CFLAGS="-marm" function build_one { ./configure \ --prefix=$PREFIX \ --enable-shared \ --enable-gpl \ --disable-static \ --disable-doc \ --disable-ffmpeg \ --disable-ffplay \ --disable-ffprobe \ --disable-ffserver \ --disable-doc \ --disable-symver \ --enable-small \ --enable-gpl \ --enable-libx264 \ --enable-yasm \ --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \ --target-os=linux \ --arch=arm \ --enable-cross-compile \ --sysroot=$SYSROOT \ --extra-cflags="-I/Users/xiaguangcheng/x264/arm/include" \ --extra-ldflags="-L/Users/xiaguangcheng/x264/arm/lib" \ $ADDITIONAL_CONFIGURE_FLAG make clean make make install } build_one |
主要修改包括以下几点
1 2 3 4 5 | --enable-gpl \ --enable-libx264 \ --enable-yasm \ --extra-cflags="-I/Users/xiaguangcheng/x264/arm/include" \ --extra-ldflags="-L/Users/xiaguangcheng/x264/arm/lib" \ |
- -I:编译阶段生效的参数,用于指定头文件的搜索路径。
- -L:链接阶段生效的参数,用于指定链接库的搜索路径,-l用于指定链接库的名称,一般两者一起使用的话,就可以指定动态链接库。
查看该文件是否有执行权限,如果有执行权限
1 | ./buildffmpeg.sh |
运行该命令后即可得到最终的包含x264的ffmpeg的so动态库。我们也可以通过在编译过程中打印的日志来查看
image.png
四:使用包含x264的ffmpeg动态库在android中压缩视频
在android中使用ffmpeg执行命令,并通过命令来编码压缩视频,利用FFmpeg玩转Android视频录制与压缩(一),这篇博客已经说的差不多了,但是根据我们项目中上传视频的需求,我们还需要获取在编码压缩过程中的进度,具体思路是通过process来执行命令,而不是通过jni调用ffmpeg来执行命令。而上面提到的这篇博客就是通过jni调用ffmpeg来执行命令的。通过android.jar中的Process来执行命令,可以获取到执行命令的结果,由于在执行ffmpeg的过程中,会出现诸如下面这样的结果输出
1 | frame= 125 fps=0.0 q=38.0 size= 176kB time=00:00:08.54 bitrate= 168.7kbits/frame= 298 fps=297 q=35.0 size= 471kB time=00:00:20.03 bitrate= 192.6kbits/frame= 454 fps=302 q=35.0 size= 727kB time=00:00:30.46 bitrate= 195.4kbits/frame= 662 fps=331 q=33.0 size= 1067kB time=00:00:44.37 bitrate= 197.0kbits/frame= 853 fps=341 q=36.0 size= 1386kB time=00:00:57.05 bitrate= 199.0kbits/frame= 1041 fps=347 q=37.0 size= 1719kB time=00:01:09.56 bitrate= 202.4kbits/frame= 1241 fps=354 q=37.0 size= 2063kB time=00:01:22.91 bitrate= 203.8kbits/frame= 1478 fps=369 q=37.0 size= 2475kB time=00:01:38.73 bitrate= 205.3kbits/frame= 1677 fps=372 q=38.0 size= 2815kB time=00:01:51.96 bitrate= 206.0kbits/frame= 1889 fps=378 q=38.0 size= 3181kB time=00:02:06.13 bitrate= 206.6kbits/frame= 2101 fps=382 q=36.0 size= 3535kB time=00:02:20.29 bitrate= 206.4kbits/frame= 2341 fps=390 q=32.0 size= 3931kB time=00:02:36.22 bitrate= 206.1kbits/frame= 2602 fps=400 q=36.0 size= 4393kB time=00:02:53.63 bitrate= 207.3kbits/frame= 2809 fps=401 q=27.0 size= 4728kB time=00:03:07.47 bitrate= 206.6kbits/frame= 3016 fps=406 q=-1.0 Lsize= 5098kB time=00:03:21.01 bitrate= 207.7kbits/s speed= 27x |
那么我们就可以通过先获取本地视频的时长,然后再拿到输出结果中的转换时长来查看编码进度。上面输出中的size并不是原视频的size,而是编码之后的新视频已经生成的size,因此不可以参考。但是我们编码只是压缩了大小,并没有改变时长,因此时间可以用来参考
1:获取本地视频时长
1 2 3 4 5 6 | Cursor mCursor = mContentResolver.query( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.MediaColumns.DATE_ADDED + " DESC"); int columnIndex1 = mCursor.getColumnIndex(MediaStore.Video.Media.DURATION); long millSeconds = mCursor.getLong(columnIndex1); |
2:获取ffmpeg命令输出结果
1 2 3 4 5 6 7 8 9 10 11 12 13 | String [] cmd =new String []{"ffmpeg","-i","xiaugangcheng.mp4","xia.mp4"}; Process process = new ProcessBuilder() .command(cmd) .redirectErrorStream(true) .start(); try { InputStream in = process.getInputStream(); OutputStream out = process.getOutputStream(); readStream(in); } finally { process.destroy(); } |
3:根据输出结果获取已转换的视频时长
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | String s=""frame=125 fps=0.0 q=38.0 size=176kB time=01:11:08.54 bitrate= 168.7kbits""; int index = s.indexOf("time="); if(index==-1){ return; } if(index+5>s.length()||index+13>s.length()){ return; } String cmd = s.substring(index+5, index+13); System.out.println(cmd); String[] my =cmd.split(":"); int hour =Integer.parseInt(my[0]); int min =Integer.parseInt(my[1]); int sec =Integer.parseInt(my[2]); int totalSec =hour*3600+min*60+sec; int percent =String totalSec *100/ 202; progressDialog.setMessage("Processing\n"+percent+"%"); |
拿到输入输出流,就可以转化为string来提取时间了