8月26日更新: 由于之前编译后得到的包体积过大(增加了40M)左右 , 整个包有很多暂时使用不到的组件 , 因此需要对FFmpeg的编译库进行轻量化配置 , 经过选项配置 , 编译后库的大小减少了30M左右!~
配置CONFIGURE_FLAGS参数 , 把ffprobe、ffplay、avresample、sdl2都给关掉 , 这样在编译时就不会把这些组件增加进去
CONFIGURE_FLAGS="--enable-cross-compile --disable-debug --disable-programs --disable-doc --enable-pic --disable-shared --disable-ffprobe --disable-ffplay --disable-avresample --disable-sdl2"
关于Configure_flags的配置参数可以通过命令行ffmpeg -help
查看支持的组件命令 , 再选择裁剪 , 这里有篇较新的参考文档,比较全面而且分类清晰
前言
之所以要采用FFmpeg , 是因为原本使用系统原生api进行压缩转码时 , 发现某部分视频会转码失败或者转码出来的格式阿里云不支持 , 导致无法正常播放
安装步骤
0.去https://github.com/bigsen/gas-preprocessor.git 下载gas-preprocessor, 编译时需要用到, 然后把它放到/usr/local/bin/ 下面 , 并且执行命令给与权限
sudo chmod 777 /usr/local/bin/gas-preprocessor.pl
- 首先安装下yasm
brew install yasm
- 下载ffmpeg的编译脚本(如果自己编译的话难度很大 , 所幸已经有脚本帮助我们走流程)
(3-5 是编译 x264和 fdk-aac 插件步骤, 为了让FFmpeg支持更多的指令)
- ①从[官网] https://www.videolan.org/developers/x264.html] 直接下载最新版源码。
②从 https://github.com/kewlbear/x264-ios 下载编译脚本build-x264.sh
③把源码解压到x264目录,把编译脚本build-x264.sh放到x264同级目录,根据需要编译的archs修改脚本。(注意脚本里面SOURCE变量的目录名称对不对)
④然后执行 ./build-x264.sh - ①从[官网http://www.linuxfromscratch.org/blfs/view/svn/multimedia/fdk-aac.html] 直接下载最新版源码。
②从https://github.com/kewlbear/fdk-aac-build-script-for-iOS下载编译脚本build-fdk-aac.sh。
③把源码解压,把编译脚本 build-fdk-aac.sh 放到解压出来的fdk-aac-2.0.0同级目录根据需要编译的archs 及fdk-aac源码目录修改脚本。(注意脚本里面SOURCE变量的目录名称对不对)
④然后执行 ./build-x264.sh
!!! 编译x264和fdk-aac的时候, 如果需要编译模拟器架构的话, 有可能会报错
Minimum version is NASM version 2.13
这时候是因为系统自带的nasm汇编编译器太旧了, 直接用brew reinstall nasm
重装, 升级到最新的2.14版本
- 把编译好的x264和fdk-aac 库和头文件放到build-ffmpeg.sh脚本同级目录下, 打开修改 build-ffmpeg.sh 脚本的配置
# 把变量修改成对应的目录名称
X264=`pwd`/x264-iOS
FDK_AAC=`pwd`/fdk-aac-ios
- 编译前可以打开build-ffmpeg.sh脚本, 配置一下裁剪参数CONFIGURE_FLAGS 和支持平台ARCHS 打开文件在根目录下打开终端运行脚本
build-ffmpeg.sh
, 脚本会自动下载ffmpeg4.x(反正会下载最新的) , 然后编译生成各个平台的静态库和对应平台的相关代码
image.png - 为了能真机和模拟器都能使用 , 执行完脚本后再执行一遍 , 会把所有平台的.a文件合并成fat包
./build-ffmpeg.sh lipo
- 将FFmpeg-iOS整个文件夹拖进项目中 , 在build setting 中搜索search path , 配置
Library Search Paths和 Header Search Paths
, 为对应的lib和include路径(一般拖进项目时lib路径会自动填充好) - 添加依赖库
libz.tbd
libbz2.tbd
libiconv.tbd
videoToolbox.framework
- 然后随便找个.m文件导入 , 写几行代码编译看看会不会报错(后面删掉!)
#import <libavformat/avformat.h>
#import <libavcodec/avcodec.h>
#import <libswscale/swscale.h>
#import <libavutil/avutil.h>
#import <libswresample/swresample.h>
#import <libavdevice/avdevice.h>
#import <libavfilter/avfilter.h>
{
av_register_all();
avcodec_register_all();
avformat_network_init();
}
到这里已经可以直接通过c++api使用库了 , 但是如果要用命令则需要添加其他依赖文件
- 打开FFmpeg-4.1(源码目录),在fftools中找到除了config.h的其他8个文件(网上较旧的文章都不需要 ffmpeg_hw.c文件, 但是在4.1中需要 , 不然会报错)
image.png
- 打开
ffmpeg.c , 把main
函数修改一下 , 因为一个程序不能有两个main函数 , 还有在.h把这个方法声明一下
ffmpeg.h ----
int ffmpeg_main(int argc, char **argv);
ffmpeg.m ----
int ffmpeg_main(int argc, char **argv){
}
- 打开
cmdutils.c文件 , 把exit_program函数改一下
, 因为这个函数会退出进程 , 我们先直接啥都不干 , 后面改成其他操作
void exit_program(int ret)
{
//if (program_exit)
// program_exit(ret);
//exit(ret);
}
- 接下就是编译看看报什么错误 , 然后去源码目录中找到相应的头文件,放到include目录下相应的文件夹中
处理完错误之后会发现在真机上已经可以运行了 , 但是在模拟器上还是会报SDL相关错误 , 但是我们已经合并了多平台静态库 , 为什么还会报错呢 ?
既然报SDL错误, 那么我们就给他SDL库
-
下载好后解压 , 在根目录中找到
Xcode-iOS-SDL-SDL.xcodeproj
, 打开项目 , 在Public Headers中找到SDL_main.h , 添加一行宏定义 , 因为SDL本身有main函数 , 如果定义了SDL_MAIN_HANDLED , 就说明自己有main函数, 不需要使用SDL的
#define SDL_MAIN_HANDLED
-
分别选择模拟器和真机编译一下 , 在products目录就有对应的.a文件 , 右键打开Finder , 用lipo命令把他们合并起来
image.png
lipo -create xxx/.a xxx/.a -output xxx/libSDL2.a
-
在项目中新建SDL文件夹 , 再新建lib文件夹 , 把SDL2.a丢进去 , 再从源码目录中找到include头文件拖入SDL目录 , 配置对应的Header Search 路径 , 添加两个需要的库
GameController
CoreMotion
到这里已经可以开始使用了 , 但是有一些地方的代码还需要修改下
-
ffmpeg.c中的 ffmpeg_cleanup
方法 , 需要把一些计数变量清空 , 否则会导致下一次转码崩溃 !!!!后面发现, 把这一部分代码移到exit_program 中更加适合
avformat_network_deinit();
//在👆这行代码之前的逻辑所有内存后 添加清空计数代码
//这里不清空会导致下一个视频转码崩溃
nb_filtergraphs = 0;
nb_input_files = 0;
nb_input_streams = 0;
nb_output_files = 0;
nb_output_streams = 0;
// nb_frames_dup = 0;
// nb_frames_drop = 0;
// run_as_daemon = 0;
- 想获取转码进度的话 , 需要些一个桥接类 , 创建任意一个cocoa touch类,把.h文件中所有东西都删除掉,.m中只留下头文件导入的代码 。 这样在.h 中就可以声明c函数 在点m中调用 就能完成C和OC之间的通信
//声明这几个方法
// 转换停止回调
void stopRuning(void);
// 获取总时间长度
void setDuration(long long int time);
// 获取当前时间
void setCurrentTime(char info[1024]);
- 再新建一个用来管理转码的类
➕(VSFFmpegManager *)sharedManager;
/**
转换视频
@param inputPath 输入视频路径
@param outpath 输出视频路径
@param processBlock 进度回调
@param completionBlock 结束回调
*/
➖ (void)converWithInputPath:(NSString *)inputPath
outputPath:(NSString *)outpath
processBlock:(void (^)(long long spendTime , long long totalTime))processBlock
completionBlock:(void (^)(NSError *error))completionBlock;
// 设置总时长
➕(void)setDuration:(long long)time;
// 设置当前时间
➕(void)setCurrentTime:(long long)time;
// 转换停止
➕(void)stopRuning;
- 监控转码的开始和完成状态 , 在C++函数中调用OC方法,来传递状态信息 ,
cmdutils.c 中的 exit_program
结束线程前调用stopRuning()结束方法 , 并且把原本的结束进程方法改为退出线程
//需要导入
#include <pthread.h>
#include “VSFFmpegConverOC.h"
void exit_program(int ret){
//标记为转换完成
stopRuning();
if (program_exit)
program_exit(ret);
//这个是结束进程的方法,让ffmpeg进行完转码以后不至于退出程序
//exit(ret);
//所以将退出进程的方法改造为退出线程
pthread_exit(NULL);
}
- 要获取视频文件总时间长度 ,
ffmpeg_opt.c
的open_input_file
方法中会有时长信息ic->duration
, 为long long int类型数据
#include “VSFFmpegConverOC.h"
//获取文件总时长信息
setDuration(ic->duration);
//👇在这行代码前 , 其实也就是尽量在加载完文件之后去获取就行了
input_stream_potentially_available = 1;
- 定时获取当前进度时间 ,
ffmpeg.c的print_report
方法中会输出Log,从log中获取当前的进度信息,为char info[1024]类型数据
#include “VSFFmpegConverOC.h”
//4.1中的buf改成了一个结构体 , 里面的str是char *类型
av_log(NULL, AV_LOG_INFO, "%s %c", buf.str, end);
//在打印之后获取转码的当前时间
setCurrentTime(buf.str);
- 转换百分比为当前进度除以总时长。注意事项:更改进度条的时候,是在非主线程,所以无法更改UI,需要在主线程执行更改UI操作
到这里就完成了ffmpeg的编译和静态库的相关配置
然后就可以使用了!!!
网友评论