iOS集成ijkplayer IJKMediaFramework

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.alibssl.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.tbd3个支持文件

测试地址可以用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.第一次加载播放器时卡主线程

直接修改IJKMediaPlayerIJKSDLGLView.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的那些警告就没有了。这样重新编译生成动态库

解决IJK在子线程执行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 */

源码修改参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345