1.源码下载
git clone https://github.com/Bilibili/ijkplayer.git ijkplayer-ios
cd ijkplayer-ios
git checkout -B latest k0.8.8
接着是./init-ios.sh
,
这一步很慢,主要是要从github
上下载ffmpeg
等依赖,所以把init-ios.sh
文件改一下,把github
的源替换成gitee
的,如下所示:
# IJK_FFMPEG_UPSTREAM=https://github.com/Bilibili/FFmpeg.git
IJK_FFMPEG_UPSTREAM=https://gitee.com/yuazhen/FFmpeg.git
# IJK_FFMPEG_FORK=https://github.com/Bilibili/FFmpeg.git
IJK_FFMPEG_FORK=https://gitee.com/yuazhen/FFmpeg.git
# IJK_GASP_UPSTREAM=https://github.com/Bilibili/gas-preprocessor.git
IJK_GASP_UPSTREAM=https://gitee.com/yuazhen/gas-preprocessor.git
#FF_ALL_ARCHS_IOS8_SDK="armv7 arm64 i386 x86_64"
FF_ALL_ARCHS_IOS8_SDK="arm64 x86_64"#移除32位支持,i386 x86_64是模拟器的
然后执行./init-ios.sh
2.解码器配置
$ cd config
module-default.sh 更多的编解码器/格式
module-lite-hevc.sh 较少的编解码器/格式(包括hevc)
module-lite.sh 较少的编解码器/格式(默认情况)
添加rtsp
解码支持
编辑module-lite.sh文件,添加下面2个配置
export·COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS·--enable-protocol=rtp"
export·COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS·--enable-demuxer=rtsp"
删除当前的module.sh 文件
rm module.sh
创建软链接 module.sh 指向 module-lite.sh
ln -s module-lite.sh module.sh
3.添加 https 支持
最后会生成支持 https 的静态文件 libcrypto.a 和 libssl.a, 如果不需要可以跳过这一步
获取 openssl 并初始化,也要先替换成gitee源,pull_fork只保留arm64和x86_64
./init-ios-openssl.sh
在模块文件中添加一行配置 以启用 openssl 组件
echo 'export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-openssl"' >> ../config/module.sh
添加完后重新生成软链接
删除当前的module.sh 文件
rm module.sh
创建软链接 module.sh 指向 module-lite.sh
ln -s module-lite.sh module.sh
4.编译
cd ios
编辑 compile-openssl.sh compile-ffmpeg.sh,移除32位支持
FF_ALL_ARCHS_IOS8_SDK="armv7 arm64 i386 x86_64" 改为
FF_ALL_ARCHS_IOS8_SDK="arm64 x86_64"
编译openssl, 如果不需要https可以跳过这一步
./compile-openssl.sh clean
./compile-openssl.sh all
编译ffmpeg
./compile-ffmpeg.sh clean
./compile-ffmpeg.sh all
5.合并动态库
如果使用 https, 那么需要手动给IJKMediaFramework添加 libcrypto.a 和 libssl.a 文件, 默认不会添加
ps: 这两个依赖库的目录为ijkplayer-ios/ios/build/universal/lib
, 只有进行了上面跟 openssl
相关的操作, 才会在这个目录下有生成libcrypto.a
和libssl.a
大家会发现除了
IJKMediaFramework
这个target
, 还有一个叫IJKMediaFrameworkWithSSL
, 但是不推荐使用这个, 因为大部分基于ijkplayer
的第三方框架都是使用的前者, 你把后者导入项目还是会报找不到包的错误, 就算你要支持https
也推荐使用前者, 然后按照上一步添加openssl
即可支持
打开ios/IJKMediaPlayer并运行,分别真机和模拟器编译,生成动态库
合并IJKMediaFramework
lipo -create 真机framework路径 模拟器framework路径 -output 合并的文件路径
lipo -create Release-iphoneos/IJKMediaFramework.framework/IJKMediaFramework Release-iphonesimulator/IJKMediaFramework.framework/IJKMediaFramework -output IJKMediaFramework
直接替换掉Release-iphoneos中的IJKMediaFramework就行
6.测试
新建工程并导入IJKMediaFramework.framework 添加libc++ libz.tbd libbz2.tbd
3个支持文件
测试地址可以用live555MediaServer文件把本地视频映射成rtsp
协议的视频。执行./live555MediaServer
,按提示操作就行
import UIKit
class playerViewController: UIViewController {
var iPlayer:IJKFFMoviePlayerController?
override func viewDidLoad() {
super.viewDidLoad()
let options:IJKFFOptions = IJKFFOptions.byDefault()
let url:URL = URL.init(string: "rtmp://live.hkstv.hk.lxdns.com/live/hks")!
self.iPlayer = IJKFFMoviePlayerController.init(contentURL: url, with: options)
var arm1 = UIViewAutoresizing.init(rawValue: 0)
arm1.insert(UIViewAutoresizing.flexibleWidth)
arm1.insert(UIViewAutoresizing.flexibleHeight)
self.iPlayer?.view.autoresizingMask = arm1
self.iPlayer?.view.backgroundColor = UIColor.white
self.iPlayer?.view.frame = CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 300)
self.iPlayer?.scalingMode = .aspectFit
self.iPlayer?.shouldAutoplay = true
self.view.autoresizesSubviews = true
self.view.addSubview((self.iPlayer?.view)!)
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.iPlayer?.prepareToPlay() //准备
self.iPlayer?.play() //播放
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.iPlayer?.pause()//暂停
// self.iPlayer?.shutdown() //销毁
}
}
7.遇到的问题&解决办法
1.第一次加载播放器时卡主线程
直接修改IJKMediaPlayer
中IJKSDLGLView.m
的源码
可以在github直接下载修改好的源码文件
首先,增加个只在主线程操作的方法,这个方法里面就是先判断当前是否在主线程,如果是就直接执行,否则同步切换到主队列进行执行。
static void IJKHanleInMainThread(dispatch_block_t mainThreadblock) {
if ([NSThread currentThread] == [NSThread mainThread]){
mainThreadblock();
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
mainThreadblock();
});
}
}
其次将方法- (BOOL)isApplicationActive里面改成这样:
__block UIApplicationState appState = 0;
IJKHanleInMainThread(^{
appState = [UIApplication sharedApplication].applicationState;
});
将方法- (void)displayInternal: (SDL_VoutOverlay *) overlay里面改成这样
IJKHanleInMainThread(^{
[[self eaglLayer] setContentsScale:_scaleFactor];
});
IJKHanleInMainThread(^{
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];
});
最后执行IJKMediaPlayer
的demo,发现子线程操作UI的那些警告就没有了。这样重新编译生成动态库
或者直接下载poholo/ijkplayer里面的IJKSDLGLView.m
文件
2.当视频地址相同,只有端口不同时,视频重复
修改源码,使用url+端口判断是否同一个链接
修改如下3个文件源码,修改后,其他架构的可以直接替换
可以在github下载修改好的直接替换
ios/ffmpeg-arm64/libavformat/tcp.c
ios/ffmpeg-arm64/libavutil/dns_cache.c
ios/ffmpeg-arm64/libavutil/dns_cache.h
下面是修改的diff
文件
---
libavformat/tcp.c | 16 ++++++++--------
libavutil/dns_cache.c | 28 +++++++++++++++++++---------
libavutil/dns_cache.h | 6 +++---
3 files changed, 30 insertions(+), 20 deletions(-)
diff --git a/libavformat/tcp.c b/libavformat/tcp.c
index d2de743..6515309 100644
--- a/libavformat/tcp.c
+++ b/libavformat/tcp.c
@@ -405,9 +405,9 @@ static int tcp_open(URLContext *h, const char *uri, int flags)
memcpy(hostname_bak, hostname, 1024);
if (s->dns_cache_clear) {
av_log(NULL, AV_LOG_INFO, "will delete cache entry, hostname = %s\n", hostname);
- remove_dns_cache_entry(hostname);
+ remove_dns_cache_entry(hostname, portstr);
} else {
- dns_entry = get_dns_cache_reference(hostname);
+ dns_entry = get_dns_cache_reference(hostname, portstr);
}
}
@@ -496,7 +496,7 @@ static int tcp_open(URLContext *h, const char *uri, int flags)
av_log(NULL, AV_LOG_WARNING, "terminated by application in AVAPP_CTRL_DID_TCP_OPEN");
goto fail1;
} else if (!dns_entry && strcmp(control.ip, hostname_bak)) {
- add_dns_cache_entry(hostname_bak, cur_ai, s->dns_cache_timeout);
+ add_dns_cache_entry(hostname_bak, portstr, cur_ai, s->dns_cache_timeout);
av_log(NULL, AV_LOG_INFO, "Add dns cache hostname = %s, ip = %s\n", hostname_bak , control.ip);
}
}
@@ -528,7 +528,7 @@ static int tcp_open(URLContext *h, const char *uri, int flags)
if (dns_entry) {
av_log(NULL, AV_LOG_ERROR, "Hit dns cache but connect fail hostname = %s, ip = %s\n", hostname , control.ip);
release_dns_cache_reference(hostname_bak, &dns_entry);
- remove_dns_cache_entry(hostname_bak);
+ remove_dns_cache_entry(hostname_bak, portstr);
} else {
freeaddrinfo(ai);
}
@@ -592,9 +592,9 @@ static int tcp_fast_open(URLContext *h, const char *http_request, const char *ur
memcpy(hostname_bak, hostname, 1024);
if (s->dns_cache_clear) {
av_log(NULL, AV_LOG_INFO, "will delete cache entry, hostname = %s\n", hostname);
- remove_dns_cache_entry(hostname);
+ remove_dns_cache_entry(hostname, portstr);
} else {
- dns_entry = get_dns_cache_reference(hostname);
+ dns_entry = get_dns_cache_reference(hostname, portstr);
}
}
@@ -686,7 +686,7 @@ static int tcp_fast_open(URLContext *h, const char *http_request, const char *ur
av_log(NULL, AV_LOG_WARNING, "terminated by application in AVAPP_CTRL_DID_TCP_OPEN");
goto fail1;
} else if (!dns_entry && strcmp(control.ip, hostname_bak)) {
- add_dns_cache_entry(hostname_bak, cur_ai, s->dns_cache_timeout);
+ add_dns_cache_entry(hostname_bak, portstr, cur_ai, s->dns_cache_timeout);
av_log(NULL, AV_LOG_INFO, "Add dns cache hostname = %s, ip = %s\n", hostname_bak , control.ip);
}
}
@@ -718,7 +718,7 @@ static int tcp_fast_open(URLContext *h, const char *http_request, const char *ur
if (dns_entry) {
av_log(NULL, AV_LOG_ERROR, "Hit dns cache but connect fail hostname = %s, ip = %s\n", hostname , control.ip);
release_dns_cache_reference(hostname_bak, &dns_entry);
- remove_dns_cache_entry(hostname_bak);
+ remove_dns_cache_entry(hostname_bak, portstr);
} else {
freeaddrinfo(ai);
}
diff --git a/libavutil/dns_cache.c b/libavutil/dns_cache.c
index c705401..70d71a4 100644
--- a/libavutil/dns_cache.c
+++ b/libavutil/dns_cache.c
@@ -115,10 +115,13 @@ fail:
return NULL;
}
-DnsCacheEntry *get_dns_cache_reference(char *hostname) {
+DnsCacheEntry *get_dns_cache_reference(char *hostname, char *portstr) {
AVDictionaryEntry *elem = NULL;
DnsCacheEntry *dns_cache_entry = NULL;
int64_t cur_time = av_gettime_relative();
+ char hostnameWithPort[1024];
+ strcat(hostnameWithPort, hostname);
+ strcat(hostnameWithPort, portstr);
if (cur_time < 0 || !hostname || strlen(hostname) == 0) {
return NULL;
@@ -130,14 +133,15 @@ DnsCacheEntry *get_dns_cache_reference(char *hostname) {
#endif
}
+
if (context && context->initialized) {
pthread_mutex_lock(&context->dns_dictionary_mutex);
- elem = av_dict_get(context->dns_dictionary, hostname, NULL, AV_DICT_MATCH_CASE);
+ elem = av_dict_get(context->dns_dictionary, hostnameWithPort, NULL, AV_DICT_MATCH_CASE);
if (elem) {
dns_cache_entry = (DnsCacheEntry *) (intptr_t) strtoll(elem->value, NULL, 10);
if (dns_cache_entry) {
if (dns_cache_entry->expired_time < cur_time) {
- inner_remove_dns_cache(hostname, dns_cache_entry);
+ inner_remove_dns_cache(hostnameWithPort, dns_cache_entry);
dns_cache_entry = NULL;
} else {
dns_cache_entry->ref_count++;
@@ -169,9 +173,12 @@ int release_dns_cache_reference(char *hostname, DnsCacheEntry **p_entry) {
return 0;
}
-int remove_dns_cache_entry(char *hostname) {
+int remove_dns_cache_entry(char *hostname, char *portstr) {
AVDictionaryEntry *elem = NULL;
DnsCacheEntry *dns_cache_entry = NULL;
+ char hostnameWithPort[1024];
+ strcat(hostnameWithPort, hostname);
+ strcat(hostnameWithPort, portstr);
if (!hostname || strlen(hostname) == 0) {
return -1;
@@ -179,11 +186,11 @@ int remove_dns_cache_entry(char *hostname) {
if (context && context->initialized) {
pthread_mutex_lock(&context->dns_dictionary_mutex);
- elem = av_dict_get(context->dns_dictionary, hostname, NULL, AV_DICT_MATCH_CASE);
+ elem = av_dict_get(context->dns_dictionary, hostnameWithPort, NULL, AV_DICT_MATCH_CASE);
if (elem) {
dns_cache_entry = (DnsCacheEntry *) (intptr_t) strtoll(elem->value, NULL, 10);
if (dns_cache_entry) {
- inner_remove_dns_cache(hostname, dns_cache_entry);
+ inner_remove_dns_cache(hostnameWithPort, dns_cache_entry);
}
}
pthread_mutex_unlock(&context->dns_dictionary_mutex);
@@ -192,10 +199,13 @@ int remove_dns_cache_entry(char *hostname) {
return 0;
}
-int add_dns_cache_entry(char *hostname, struct addrinfo *cur_ai, int64_t timeout) {
+int add_dns_cache_entry(char *hostname, char* portstr, struct addrinfo *cur_ai, int64_t timeout) {
DnsCacheEntry *new_entry = NULL;
DnsCacheEntry *old_entry = NULL;
AVDictionaryEntry *elem = NULL;
+ char hostnameWithPort[1024];
+ strcat(hostnameWithPort, hostname);
+ strcat(hostnameWithPort, portstr);
if (!hostname || strlen(hostname) == 0 || timeout <= 0) {
goto fail;
@@ -207,7 +217,7 @@ int add_dns_cache_entry(char *hostname, struct addrinfo *cur_ai, int64_t timeout
if (context && context->initialized) {
pthread_mutex_lock(&context->dns_dictionary_mutex);
- elem = av_dict_get(context->dns_dictionary, hostname, NULL, AV_DICT_MATCH_CASE);
+ elem = av_dict_get(context->dns_dictionary, hostnameWithPort, NULL, AV_DICT_MATCH_CASE);
if (elem) {
old_entry = (DnsCacheEntry *) (intptr_t) strtoll(elem->value, NULL, 10);
if (old_entry) {
@@ -217,7 +227,7 @@ int add_dns_cache_entry(char *hostname, struct addrinfo *cur_ai, int64_t timeout
}
new_entry = new_dns_cache_entry(hostname, cur_ai, timeout);
if (new_entry) {
- av_dict_set_int(&context->dns_dictionary, hostname, (int64_t) (intptr_t) new_entry, 0);
+ av_dict_set_int(&context->dns_dictionary, hostnameWithPort, (int64_t) (intptr_t) new_entry, 0);
}
pthread_mutex_unlock(&context->dns_dictionary_mutex);
diff --git a/libavutil/dns_cache.h b/libavutil/dns_cache.h
index a2ed92e..0ea7e2d 100644
--- a/libavutil/dns_cache.h
+++ b/libavutil/dns_cache.h
@@ -30,9 +30,9 @@ typedef struct DnsCacheEntry {
struct addrinfo *res; // construct by private function, not support ai_next and ai_canonname, can only be released using free_private_addrinfo
} DnsCacheEntry;
-DnsCacheEntry *get_dns_cache_reference(char *hostname);
+DnsCacheEntry *get_dns_cache_reference(char *hostname, char* portstr);
int release_dns_cache_reference(char *hostname, DnsCacheEntry **p_entry);
-int remove_dns_cache_entry(char *hostname);
-int add_dns_cache_entry(char *hostname, struct addrinfo *cur_ai, int64_t timeout);
+int remove_dns_cache_entry(char *hostname, char* portstr);
+int add_dns_cache_entry(char *hostname, char* portstr, struct addrinfo *cur_ai, int64_t timeout);
#endif /* AVUTIL_DNS_CACHE_H */