转载iOS直播视频iOS直播技术

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

2017-01-07  本文已影响3336人  ZhengYaWei

这里先说一下推流的实现,明天再介绍拉流的实现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

上一篇下一篇

猜你喜欢

热点阅读