直播类app中推流技术的实现

这里先说一下推流的实现,明天再介绍拉流的实现http://www.jianshu.com/p/4da61fb44441
demo下载地址:https://pan.baidu.com/s/1miSAGre
在说直播推流技术之前,先说一下一些关于直播技术的背景知识。2016年是直播元年,直播技术如此火热,作为一个开发人员应该去学习一下。先说下直播推流的过程:采集——>前处理——>编码-—>推流———>流分发———>播放。
1.采集:音视频采集 pc段屏幕摄像头采集 iOS和安卓端的摄像头和屏幕采集
2.前处理:主要包括美颜,模糊效果,水印。iOS端一般会用到GPUImage处理图像,安卓端一般使用Google的grafika(图形处理库)
3.编码:不经过编码的视频体积会比较庞大。音视频必须经过压缩编辑才能进行存储和传输。
编码方式:硬编码(通过非CPU,如显卡GPU)和软编码(使用CPU),最好使用硬编码。
编码标准:视频:H.265 H.264 VP8 VP9 音频:AAC Opus
4.传输:从推流端到服务器。 常见的传输协议:RTMP RTSP HLS
5.流分发:在服务器端做一些处理。比如鉴黄 转码成不同的格式支持不同协议,以适应各个平台
6.播放 (解协议—解封装—音视频解码—音视频同步—音视频播放)
解协议:取出网络传输过程中一些无用的信息
解封装:获取到的是音频和视频在一起的封装文件

==========================================================
接下来步入正题,这里推流技术的实现我们主要基于bilibili的ijkPlayer第三方开源框架,这个开源框架已经帮我们集成好了FFmpeg。苹果的播放器也是基于FFMpeg实现的,但是不能播放直播类视频。这个第三方开源框架十分强大,安卓移动端的直播很多也是基于这个框架。斗鱼的直播就是基于ijkPlayer实现的,所以说这个开源框架还是很靠谱的,斗鱼这么火的一个直播平都是基于它实现的,我们没有理由不相信它。

==========================================================
先来看一下ijkPlayer的运行效果,在这之前我们要做一些准备操作,才能看到官方提供的demo的运行效果。
1.Github上搜索ijkPlayer,并下载下来。
2.打开终端,输入命令: cd 第一步下载的ijkplayer这个文件名

  1. ./init-ios.sh (下载ffmpeg的过程,可能很漫长)
  2. 第三步骤执行完成后, cd ios
  3. ./compile-ffmpeg.sh clean (编译过程,可能比较耗时间)
  4. ./compile-ffmpeg.sh all (编译过程,可能比较耗时间)
    执行以上步骤后,就可以运行ijkplayer —> ios —>IJKMediaDemo这个Demo了,但是bilibili提供的直播地址貌似不是很好用,可以自己找一个合适的直播地址进行测试。

==========================================================
已经运行了demo,看了项目,接下来看看如何集成到我们自己的项目中。主要有两种方法:1.工程中集成工程 。2.把ijkplayer源码打包成framework,然后将这个framework集成到我们的项目中。第一种方法相对而言比较麻烦,所以这里我们采用生成framework的方式。可以参考我之前写的一篇博客:http://www.jianshu.com/p/c1ea1c249701 如何制作.a 和 .framework静态库,这里我就简单说一下。生成framework的步骤如下:
1.打开ijkplayer —> ios 目录下的IJKMediaPlayer这个工程。
2.调成release版本。(默认是debug版本),操作步骤见下图。
3.然后分别选择模拟器和Generic iOS Device,分别编译一下。
4.合并模拟器和Generic iOS Device编译生成的framework文件。注意:这里合并的内容并不是Bundle文件,而是Bundle文件下的IJKMediaFramework。如果是上线的话,我们可以不合并framework文件,而是直接使用Generic iOS Device生成的framework,这样可以减小项目资源文件大小。之所以执行合并这个步骤,是因为在实际开发中,不仅仅要在真机上测试,还要在模拟器上运行。
5.将上面生成的IJKMediaFramework.framework包文件,直接拉到工程的文件中,然后导入如下依赖框架:
AudioToolbox.framework、AVFoundation.framework、CoreGraphics.framework、CoreMedia.framework、CoreVideo.framework、libbz2.tbd、libz.tbd、MediaPlayer.framework、MobileCoreServices.framework、OpenGLES.framework、QuartzCore.framework、UIKit.framework、VideoToolbox.framework。此时编译一下,应该接没有问题了。

==========================================================
接下来就可以开始代码实现部分了,注释会在代码中说明。以下代码是我之前写过的代码,所以在这里就说明一些事项。创建一个PlayerViewController类,在.h文件中添加一个属性@property (nonatomic, strong) Live * live;其中Live是上一界面传入的一个模型对象,live.streamAddr属性是播放的地址,live.creator.portrait是毛玻璃特效显示模糊处理的图片地址。另外以下代码中有些代码会和界面布局有关系,有刚进入播放界面的毛玻璃特效。另外,还添加了一个子控制器,并将子控制器的view显示到PlayerViewController控制器的view上。这个控制器主要是起到控制面板的作用,类似直播界面中的一些发送小礼物,聊天等控能,都在这个控制器面板中实现。

#import "PlayerViewController.h"
//导入头文件
#import <IJKMediaFramework/IJKMediaFramework.h>
#import "AppDelegate.h"
#import "SXTLiveChatViewController.h"

@interface PlayerViewController ()
//注意这里是atomic**************************
@property(atomic, retain) id<IJKMediaPlayback> player;

@property(nonatomic,strong)UIImageView *blurImageView;
@property (nonatomic, strong) UIButton * closeBtn;
//添加直播控制面板
@property (nonatomic, strong) SXTLiveChatViewController * liveChatVC;

@end

@implementation SXTPlayerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self initPlayer];
    [self initUI];
    [self addChildVC];
}

- (void)initUI{
    //这和毛玻璃效果有关系
    self.view.backgroundColor = [UIColor blackColor];
    self.blurImageView = [[UIImageView alloc]initWithFrame:self.view.bounds];
    [self.blurImageView downloadImage:[NSString stringWithFormat:@"%@%@",IMAGE_HOST,self.live.creator.portrait] placeholder:@"default_room"];
    [self.view addSubview:self.blurImageView];
    //  创建需要的毛玻璃特效类型
    UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
    // 创建毛玻璃view 视图
    UIVisualEffectView *effectView = [[UIVisualEffectView alloc]initWithEffect:blurEffect];
    effectView.frame = self.blurImageView.bounds;
    //添加到要有毛玻璃特效的控件中
    [self.blurImageView addSubview:effectView];
    //[self.view addSubview:self.closeBtn];
}

- (void)initPlayer {
    IJKFFOptions *options = [IJKFFOptions optionsByDefault];
    self.player = [[IJKFFMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:self.live.streamAddr] withOptions:options];
    self.player.view.frame = self.view.bounds;
    //设置自动播放
    self.player.shouldAutoplay = YES;
    
    /******************************************/
    //因为本视图控制器添加了视图,为了退出按钮可以点击,应该添加到window上
    //添加player的view到self.view上
    [self.view addSubview:self.player.view];
    
}
//添加控制面板
- (void)addChildVC{
    //添加子控制器
    [self addChildViewController:self.liveChatVC];
    //添加子控制器视图
    [self.view addSubview:self.liveChatVC.view];
    [self.liveChatVC.view mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    self.liveChatVC.live = self.live;
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    self.navigationController.navigationBarHidden = YES;
    //注册直播需要的通知
    [self installMovieNotificationObservers];
    //准备播放
     [self.player prepareToPlay];
    
    /******************************************/
    //因为本视图控制器添加了视图,为了退出按钮可以点击,应该添加到window上。但是要注意:视图离开时要移除退出按钮
    UIWindow * window = [(AppDelegate *)[UIApplication sharedApplication].delegate window];
    [window addSubview:self.closeBtn];
}

- (UIButton *)closeBtn {
    if (!_closeBtn) {
        UIImage * image = [UIImage imageNamed:@"mg_room_btn_guan_h"];
        _closeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        [_closeBtn setImage:image forState:UIControlStateNormal];
        _closeBtn.frame = CGRectMake(SCREEN_WIDTH - image.size.width - 10, SCREEN_HEIGHT - image.size.height - 10, image.size.width, image.size.height);
        [_closeBtn addTarget:self action:@selector(closeLive:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _closeBtn;
}
- (void)closeLive:(UIButton *)button {
    [self.navigationController popViewControllerAnimated:YES];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
     self.navigationController.navigationBarHidden = NO;
    //关闭直播
    [self.player shutdown];
    //移除直播通知
    [self removeMovieNotificationObservers];
    /******************************************/
    //因为本视图控制器添加了视图,为了退出按钮可以点击,应该添加到window上。但是要注意:视图离开时要移除退出按钮
    [self.closeBtn removeFromSuperview];
}


#pragma mark -通知要实现的四个方法
- (void)loadStateDidChange:(NSNotification*)notification
{
    //    MPMovieLoadStateUnknown        = 0,  未知
    //    MPMovieLoadStatePlayable       = 1 << 0, 缓冲结束可以播放
    //    MPMovieLoadStatePlaythroughOK  = 1 << 1, // Playback will be automatically started in this state when shouldAutoplay is YES 缓冲结束自动播放
    //    MPMovieLoadStateStalled        = 1 << 2, // Playback will be automatically paused in this state, if started  暂停
    
    IJKMPMovieLoadState loadState = _player.loadState;
    
    if ((loadState & IJKMPMovieLoadStatePlaythroughOK) != 0) {
        NSLog(@"loadStateDidChange: IJKMPMovieLoadStatePlaythroughOK: %d\n", (int)loadState);
    } else if ((loadState & IJKMPMovieLoadStateStalled) != 0) {
        NSLog(@"loadStateDidChange: IJKMPMovieLoadStateStalled: %d\n", (int)loadState);
    } else {
        NSLog(@"loadStateDidChange: ???: %d\n", (int)loadState);
    }
}

- (void)moviePlayBackDidFinish:(NSNotification*)notification
{
    //    MPMovieFinishReasonPlaybackEnded,  直播结束
    //    MPMovieFinishReasonPlaybackError,  直播错误
    //    MPMovieFinishReasonUserExited   用户退出
    int reason = [[[notification userInfo] valueForKey:IJKMPMoviePlayerPlaybackDidFinishReasonUserInfoKey] intValue];
    
    switch (reason)
    {
            case IJKMPMovieFinishReasonPlaybackEnded:
            NSLog(@"playbackStateDidChange: IJKMPMovieFinishReasonPlaybackEnded: %d\n", reason);
            break;
            
            case IJKMPMovieFinishReasonUserExited:
            NSLog(@"playbackStateDidChange: IJKMPMovieFinishReasonUserExited: %d\n", reason);
            break;
            
            case IJKMPMovieFinishReasonPlaybackError:
            NSLog(@"playbackStateDidChange: IJKMPMovieFinishReasonPlaybackError: %d\n", reason);
            break;
            
        default:
            NSLog(@"playbackPlayBackDidFinish: ???: %d\n", reason);
            break;
    }
}

- (void)mediaIsPreparedToPlayDidChange:(NSNotification*)notification
{
    NSLog(@"mediaIsPreparedToPlayDidChange\n");
}

- (void)moviePlayBackStateDidChange:(NSNotification*)notification
{
    
    //    MPMoviePlaybackStateStopped,  停止
    //    MPMoviePlaybackStatePlaying, 播放
    //    MPMoviePlaybackStatePaused, 暂停
    //    MPMoviePlaybackStateInterrupted,
    //    MPMoviePlaybackStateSeekingForward, 前进
    //    MPMoviePlaybackStateSeekingBackward 后退
    
    switch (_player.playbackState)
    {
            case IJKMPMoviePlaybackStateStopped: {
                NSLog(@"IJKMPMoviePlayBackStateDidChange %d: stoped", (int)_player.playbackState);
                break;
            }
            case IJKMPMoviePlaybackStatePlaying: {
                NSLog(@"IJKMPMoviePlayBackStateDidChange %d: playing", (int)_player.playbackState);
                break;
            }
            case IJKMPMoviePlaybackStatePaused: {
                NSLog(@"IJKMPMoviePlayBackStateDidChange %d: paused", (int)_player.playbackState);
                break;
            }
            case IJKMPMoviePlaybackStateInterrupted: {
                NSLog(@"IJKMPMoviePlayBackStateDidChange %d: interrupted", (int)_player.playbackState);
                break;
            }
            case IJKMPMoviePlaybackStateSeekingForward:
            case IJKMPMoviePlaybackStateSeekingBackward: {
                NSLog(@"IJKMPMoviePlayBackStateDidChange %d: seeking", (int)_player.playbackState);
                break;
            }
        default: {
            NSLog(@"IJKMPMoviePlayBackStateDidChange %d: unknown", (int)_player.playbackState);
            break;
        }
    }
    /*****************************************/
    //只要有变化就移除掉毛玻璃效果
    //开始播放直播的时候要移除毛玻璃效果
    self.blurImageView.hidden = YES;
    [self.blurImageView removeFromSuperview];
}


#pragma mark Install Movie Notifications

/* Register observers for the various movie object notifications. */
-(void)installMovieNotificationObservers
{
    //监听网络环境,监听缓冲方法
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(loadStateDidChange:)
                                                 name:IJKMPMoviePlayerLoadStateDidChangeNotification
                                               object:_player];
    //监听直播完成回调
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(moviePlayBackDidFinish:)
                                                 name:IJKMPMoviePlayerPlaybackDidFinishNotification
                                               object:_player];
    //
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(mediaIsPreparedToPlayDidChange:)
                                                 name:IJKMPMediaPlaybackIsPreparedToPlayDidChangeNotification
                                               object:_player];
    //监听用户的主动操作
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(moviePlayBackStateDidChange:)
                                                 name:IJKMPMoviePlayerPlaybackStateDidChangeNotification
                                               object:_player];
}

#pragma mark Remove Movie Notification Handlers

/* Remove the movie notification observers from the movie object. */
-(void)removeMovieNotificationObservers
{
    [[NSNotificationCenter defaultCenter]removeObserver:self name:IJKMPMoviePlayerLoadStateDidChangeNotification object:_player];
    [[NSNotificationCenter defaultCenter]removeObserver:self name:IJKMPMoviePlayerPlaybackDidFinishNotification object:_player];
    [[NSNotificationCenter defaultCenter]removeObserver:self name:IJKMPMediaPlaybackIsPreparedToPlayDidChangeNotification object:_player];
    [[NSNotificationCenter defaultCenter]removeObserver:self name:IJKMPMoviePlayerPlaybackStateDidChangeNotification object:_player];
}


- (SXTLiveChatViewController *)liveChatVC {
    if (!_liveChatVC) {
        _liveChatVC = [[SXTLiveChatViewController alloc] init];
    }
    return _liveChatVC;
}

@end

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,068评论 4 62
  • Swift版本点击这里欢迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh阅读 25,345评论 7 249
  • 01 长这么大,第一次深深地觉得自己确实错过了一个好时机是在去年6月份。当牛市如火如荼地一路高歌猛进直奔5000点...
    两蓑烟雨阅读 1,153评论 0 1
  • 凌晨六点二十三分,一个多小时以前被媳妇叫醒后再睡不着了,床上翻来覆去。看着媳妇产后宫缩疼痛掉眼泪的样子,真心疼!握...
    一只北方的狼阅读 162评论 1 1
  • 前几天考完执医操作,想起当时考试的情形,就有点不 舒服,毫无逻辑可言,被外界干扰,可能分数极低,也可能考试不...
    小小猫妖阅读 270评论 0 2