美文网首页iOS开发攻城狮的集散地iOS开发者
直播类app中推流技术的实现[转]

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

作者: 半岛夏天 | 来源:发表于2018-07-04 15:55 被阅读9次

    这里先说一下推流的实现,关于拉流的实现https://www.jianshu.com/p/7e3dd597cfa5
    demo下载地址:https://pan.baidu.com/s/1fU5SMyEDRAVaGBIBvYzbVw
    在说直播推流技术之前,先说一下一些关于直播技术的背景知识。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
    
    

    原文链接:https://www.jianshu.com/p/4da61fb44441

    相关文章

      网友评论

        本文标题:直播类app中推流技术的实现[转]

        本文链接:https://www.haomeiwen.com/subject/ejneuftx.html