1. ijkplayer源码编译与环境准备
第一次接触ijkplayer的开发者可能会被它的编译过程吓到,但其实只要掌握几个关键步骤,整个过程就像搭积木一样简单。我最近在开发一款安防监控App时,就经历了从零开始编译ijkplayer的全过程,这里把踩过的坑和最佳实践都分享给你。
首先需要准备Mac开发环境,建议使用Xcode 12以上版本。打开终端执行以下命令安装基础工具:
# 安装Homebrew(如果尚未安装) /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # 安装编译依赖 brew install git yasm接着克隆ijkplayer源码库,这里特别要注意使用0.8.8这个稳定版本分支:
git clone https://github.com/bilibili/ijkplayer.git ijkplayer-ios cd ijkplayer-ios git checkout -B latest k0.8.8在编译前有个重要选择:默认配置为了减小体积禁用了很多编解码器,我们需要修改module-lite.sh来开启必要功能。进入config目录执行:
cd config # 删除默认软连接 rm module.sh # 使用lite配置 ln -s module-lite.sh module.sh然后编辑module-lite.sh文件,找到协议相关配置,修改为以下内容:
# 启用RTSP相关协议 export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-protocol=rtp" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=rtsp" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-protocol=tcp" # 启用HTTPS支持 export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-openssl"接下来初始化子模块和编译环境:
cd .. ./init-ios.sh ./init-ios-openssl.sh这里有个常见坑点:如果遇到"ERROR: openssl not found"错误,需要先确保本地安装了openssl:
brew install openssl2. 解决编译过程中的常见问题
实际编译时可能会遇到各种环境问题,我在M1芯片的MacBook Pro上就遇到了几个典型错误。首先是架构兼容性问题,需要修改compile-ffmpeg.sh:
# 修改前 FF_ALL_ARCHS_IOS8_SDK="armv7 arm64 i386 x86_64" # 修改后(移除armv7) FF_ALL_ARCHS_IOS8_SDK="arm64 i386 x86_64"另一个常见错误是bitcode兼容性问题,解决方法是在compile-ffmpeg.sh中找到CFLAGS参数,添加:
CFLAGS+=" -Wno-error=incompatible-function-pointer-types" CFLAGS+=" -Wno-error=incompatible-pointer-to-integer-type"开始正式编译前,建议先清理之前的编译结果:
./compile-ffmpeg.sh clean然后分步编译openssl和ffmpeg:
# 编译openssl ./compile-openssl.sh all # 编译ffmpeg(耗时较长) ./compile-ffmpeg.sh all编译过程中如果出现文件缺失错误,可能是缓存问题,需要完全清理后重新编译:
./compile-ffmpeg.sh clean ./compile-openssl.sh clean rm -rf ios/build3. 集成RTSP与HTTPS协议支持
要让ijkplayer支持RTSP流,除了前面修改的编译配置外,还需要在代码层面进行适配。在iOS项目中使用时,需要设置额外的格式选项:
IJKFFOptions *options = [IJKFFOptions optionsByDefault]; // 强制使用TCP传输(避免UDP被防火墙拦截) [options setFormatOptionValue:@"tcp" forKey:@"rtsp_transport"]; // 设置超时时间(单位微秒) [options setFormatOptionIntValue:10000000 forKey:@"timeout"];对于HTTPS流媒体,需要确保编译时已启用openssl支持。可以通过以下代码检查:
// 检查协议支持情况 NSLog(@"支持协议: %@", [IJKFFMoviePlayerController supportedProtocols]);在测试RTSP流时,我发现有些摄像头使用特殊的分辨率会导致花屏,这时需要调整解码器参数:
// 设置解码器缓冲大小 [options setPlayerOptionIntValue:256 forKey:@"vol"]; [options setPlayerOptionIntValue:1 forKey:@"videotoolbox"];一个实用的调试技巧是开启ffmpeg的日志输出:
// 设置日志级别 [options setCodecOptionIntValue:IJK_AV_LOG_DEBUG forKey:@"loglevel"];4. 实现视频录制功能
在安防监控场景中,本地录制是刚需功能。我通过修改ijkplayer源码实现了这个功能,下面是关键实现步骤:
首先在ff_ffplay.h中添加函数声明:
// 录制控制接口 int ffp_start_record(FFPlayer *ffp, const char *file_name); int ffp_stop_record(FFPlayer *ffp); int ffp_record_file(FFPlayer *ffp, AVPacket *packet);然后在ff_ffplay.c中实现核心逻辑。关键点是处理时间戳同步:
int ffp_start_record(FFPlayer *ffp, const char *file_name) { // 创建输出上下文 avformat_alloc_output_context2(&ffp->m_ofmt_ctx, NULL, NULL, file_name); // 复制流信息 for (int i = 0; i < is->ic->nb_streams; i++) { AVStream *out_stream = avformat_new_stream(ffp->m_ofmt_ctx, NULL); avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); } // 写入文件头 avformat_write_header(ffp->m_ofmt_ctx, NULL); ffp->is_record = 1; }录制过程中需要特别注意线程安全,我使用了pthread_mutex_t来保护写操作:
int ffp_record_file(FFPlayer *ffp, AVPacket *packet) { pthread_mutex_lock(&ffp->record_mutex); // 调整时间戳 pkt->pts = av_rescale_q_rnd(pkt->pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF); // 写入数据包 av_interleaved_write_frame(ffp->m_ofmt_ctx, pkt); pthread_mutex_unlock(&ffp->record_mutex); }在Objective-C层封装接口:
- (void)startRecordWithFileName:(NSString *)fileName { const char *path = [fileName UTF8String]; ijkmp_start_record(_mediaPlayer, path); } - (void)stopRecord { ijkmp_stop_record(_mediaPlayer); }5. 打包Framework与项目集成
编译完成后,我们需要将ijkplayer打包成可集成的Framework。首先用Xcode打开IJKMediaPlayer工程:
- 选择IJKMediaFramework Scheme
- 修改Build Configuration为Release
- 分别选择Generic iOS Device和模拟器各编译一次
然后使用lipo工具合并二进制文件:
lipo -create \ Release-iphoneos/IJKMediaFramework.framework/IJKMediaFramework \ Release-iphonesimulator/IJKMediaFramework.framework/IJKMediaFramework \ -output IJKMediaFramework合并后需要处理依赖库问题。在项目配置中需要添加以下依赖:
必需框架:
- AudioToolbox.framework
- VideoToolbox.framework
- CoreMedia.framework
- CoreVideo.framework
动态库:
- libz.tbd
- libbz2.tbd
- libc++.tbd
集成到项目后,可以通过这个简单示例测试播放器:
// 初始化播放器 IJKFFOptions *options = [IJKFFOptions optionsByDefault]; NSURL *url = [NSURL URLWithString:@"rtsp://example.com/live.stream"]; _player = [[IJKFFMoviePlayerController alloc] initWithContentURL:url withOptions:options]; // 设置视图 UIView *playerView = [_player view]; playerView.frame = self.view.bounds; [self.view addSubview:playerView]; // 开始播放 [_player prepareToPlay];6. 录制功能的高级应用
在实现基础录制功能后,我又针对安防场景做了些优化。首先是分段录制功能:
// 每5分钟分段录制一次 [NSTimer scheduledTimerWithTimeInterval:300 repeats:YES block:^(NSTimer * _Nonnull timer) { if (_isRecording) { [self stopCurrentRecording]; [self startNewRecording]; } }];然后是视频存储管理,自动清理旧文件:
- (void)cleanOldRecordings { NSFileManager *fm = [NSFileManager defaultManager]; NSArray *files = [fm contentsOfDirectoryAtPath:_recordDirectory error:nil]; for (NSString *file in files) { NSString *path = [_recordDirectory stringByAppendingPathComponent:file]; NSDictionary *attrs = [fm attributesOfItemAtPath:path error:nil]; NSDate *createDate = [attrs objectForKey:NSFileCreationDate]; if ([createDate timeIntervalSinceNow] < -86400) { // 超过1天 [fm removeItemAtPath:path error:nil]; } } }对于视频质量,可以通过参数控制:
// 设置录制参数 [options setPlayerOptionIntValue:2000000 forKey:@"video-bitrate"]; // 2Mbps [options setPlayerOptionIntValue:30 forKey:@"fps"]; [options setPlayerOptionIntValue:1 forKey:@"vf"];7. 性能优化与调试技巧
在实际使用中,我发现几个提升ijkplayer性能的关键点。首先是内存管理:
// 设置最大缓冲大小(单位MB) [options setPlayerOptionIntValue:15 forKey:@"max-buffer-size"]; [options setPlayerOptionIntValue:1 forKey:@"infbuf"];对于直播流,建议开启低延迟模式:
[options setPlayerOptionIntValue:1 forKey:@"framedrop"]; [options setPlayerOptionIntValue:0 forKey:@"packet-buffering"];调试时可以通过监听状态变化来排查问题:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerStateDidChange:) name:IJKMPMoviePlayerLoadStateDidChangeNotification object:_player];遇到播放卡顿时,可以检查以下指标:
NSLog(@"缓冲长度: %f", _player.playableDuration); NSLog(@"网络速度: %f kbps", _player.tcpSpeed);最后分享一个实用技巧 - 在控制台查看ffmpeg日志:
// 设置日志回调 ijkmp_set_log_level(IJK_LOG_DEBUG); ijkmp_set_log_handler(^(IJKLogLevel level, NSString *tag, NSString *msg){ NSLog(@"%@: %@", tag, msg); });