參考:https://blog.csdn.net/leixiaohua1020/article/details/15811977/
《基于 FFmpeg + SDL 的視頻播放器的制作》課程的視頻:https://blog.csdn.net/leixiaohua1020/article/details/47068015
參考:iOS中集成ijkplayer視頻直播框架:http://www.reibang.com/p/1f06b27b3ac0
參考直播demo:http://www.reibang.com/p/b8db6c142aad
參考:http://www.reibang.com/u/b09c3959ab3b
參考:http://www.cnblogs.com/fusheng-it/p/7911000.html(直播)
參考:http://www.reibang.com/p/bd42bacbe4cc(直播原理)
視頻直播脱衙,可以分為 采集,前處理,編碼谬擦,傳輸, 服務(wù)器處理,解碼,渲染
直播前期準(zhǔn)備為:
1.推流用優(yōu)酷開源的LFLiveKit框架。
2.拉流(實(shí)際上就是一個(gè)播放器)用ijkplayer 框架庆杜,當(dāng)然這個(gè)也是開源的。
3.創(chuàng)建本地rtmp服務(wù)器碟摆。
推流:
推流用的是一個(gè)第三方的IFliveKit框架晃财。這個(gè)框架基于rtmp協(xié)議。IFLiveKit內(nèi)部集成了GPUIImage典蜕。內(nèi)部實(shí)現(xiàn)了圖片渲染等美艷效果断盛。減少了開發(fā)時(shí)候美艷效果的調(diào)試。
推流端工作將它細(xì)分為以下幾個(gè)部分(基本上是依次執(zhí)行的):
一:相機(jī)相冊權(quán)限檢查并作出相應(yīng)的處理方法愉舔。
二:音頻視頻信息配置(碼率钢猛,采樣率,質(zhì)量等信息)
三:音頻視頻采集及編碼前的濾鏡等效果(GPUIImage)
四:音頻視頻編碼轩缤。這里需要注意的是iOS8以上支持硬件編碼命迈,如果不能適配iOS8以上是需要做處理的(參考LFLiveKit)
五:上傳數(shù)據(jù)(rtmp)
一個(gè)簡單的推流頁面應(yīng)該包含以下幾個(gè)功能:
1.推流狀態(tài)監(jiān)聽。2.切換攝像頭典奉。3.切換美艷效果躺翻。4.開關(guān)推流丧叽。
一.推流之前需要檢查攝像頭和麥克風(fēng)等權(quán)限是否開啟卫玖,并啟動(dòng)攝像頭,核心代碼如下:
//判斷是否有攝像頭if(![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]){
? ? ? ? [self showInfo:@"您的設(shè)備沒有攝像頭或者相關(guān)的驅(qū)動(dòng), 不能進(jìn)行直播"];
? ? ? ? return;
? ? }
? ? //判斷是否有攝像頭權(quán)限AVAuthorizationStatus? authorizationStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
? ? if(authorizationStatus == AVAuthorizationStatusRestricted|| authorizationStatus == AVAuthorizationStatusDenied) {
? ? ? ? [self showInfo:@"app需要訪問您的攝像頭踊淳。\n請(qǐng)啟用攝像頭-設(shè)置/隱私/攝像頭"];
? ? ? ? return ;
? ? }AVAudioSession *audioSession = [AVAudioSession sharedInstance];
? ? if ([audioSession respondsToSelector:@selector(requestRecordPermission:)]) {
? ? ? ? [audioSession performSelector:@selector(requestRecordPermission:) withObject:^(BOOL granted) {
? ? ? ? ? ? if (granted) {
? ? ? ? ? ? ? ? return YES;
? ? ? ? ? ? }
? ? ? ? ? ? else {
? ? ? ? ? ? ? ? [self showInfo:@"app需要訪問您的麥克風(fēng)假瞬。\n請(qǐng)啟用麥克風(fēng)-設(shè)置/隱私/麥克風(fēng)"];
? ? ? ? ? ? ? ? return NO;
? ? ? ? ? ? }
? ? ? ? }];
? ? }
//檢查麥克風(fēng)權(quán)限- (void)checkCaptureAudioDeviceEnableCheckCaptureVideo:(void(^)(BOOL isAutioSucc,NSString * err))succ{
? ? if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]==AVAuthorizationStatusNotDetermined) {
? ? ? ? [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
? ? ? ? ? ? if (granted) {succ(granted,nil);}
? ? ? ? ? ? else{succ(NO,@"app需要訪問您的麥克風(fēng)陕靠。\n請(qǐng)啟用麥克風(fēng)-設(shè)置/隱私/麥克風(fēng)");}
? ? ? ? }];
? ? }elseif([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]==AVAuthorizationStatusAuthorized){
? ? ? ? succ(YES,nil);
? ? }else{
? ? ? ? succ(NO,@"app需要訪問您的麥克風(fēng)。\n請(qǐng)啟用麥克風(fēng)-設(shè)置/隱私/麥克風(fēng)");
? ? }
}
二.創(chuàng)建一個(gè)按鈕.點(diǎn)擊開始推流代碼如下:
- (LFLiveSession*)session{
? ? if(!_session){
? ? ? ? /***? ?默認(rèn)分辨率368 * 640? 音頻:44.1 iphone6以上48? 雙聲道? 方向豎屏 ***/? ? ? ? _session = [[LFLiveSession alloc] initWithAudioConfiguration:[LFLiveAudioConfiguration defaultConfiguration] videoConfiguration:[LFLiveVideoConfiguration defaultConfigurationForQuality:LFLiveVideoQuality_Medium2]];
? ? ? ? /**? ??自己定制高質(zhì)量音頻128K 分辨率設(shè)置為720*1280 方向豎屏 *//*? ? ? ? LFLiveAudioConfiguration *audioConfiguration = [LFLiveAudioConfiguration new];
? ? ? ? audioConfiguration.numberOfChannels = 2;
? ? ? ? audioConfiguration.audioBitrate = LFLiveAudioBitRate_128Kbps;
? ? ? ? audioConfiguration.audioSampleRate = LFLiveAudioSampleRate_44100Hz;
? ? ? ? LFLiveVideoConfiguration *videoConfiguration = [LFLiveVideoConfiguration new];
? ? ? ? videoConfiguration.videoSize = CGSizeMake(720, 1280);
? ? ? ? videoConfiguration.videoBitRate = 800*1024;
? ? ? ? videoConfiguration.videoMaxBitRate = 1000*1024;
? ? ? ? videoConfiguration.videoMinBitRate = 500*1024;
? ? ? ? videoConfiguration.videoFrameRate = 15;
? ? ? ? videoConfiguration.videoMaxKeyframeInterval = 30;
? ? ? ? videoConfiguration.orientation = UIInterfaceOrientationPortrait;
? ? ? ? videoConfiguration.sessionPreset = LFCaptureSessionPreset720x1280;
? ? ? ? _session = [[LFLiveSession alloc] initWithAudioConfiguration:audioConfiguration videoConfiguration:videoConfiguration liveType:LFLiveRTMP];
? ? ? ? */// 設(shè)置代理_session.delegate= self;
? ? ? ? _session.running = YES;
? ? ? ? _session.preView = self.livingPreView;
? ? }
? ? return _session;
}
//給服務(wù)器推流
- (IBAction)startTouched:(id)sender {
? ? LFLiveStreamInfo *stream = [LFLiveStreamInfonew];
? ? // 本地推流地址stream.url =@"rtmp://192.168.199.131:1935/rtmplive/room";
? ? self.rtmpUrl = stream.url;
? ? [self.session startLive:stream];
}
3.創(chuàng)建一個(gè)按鈕點(diǎn)擊關(guān)閉推流脱茉,代碼如下:
- (IBAction)endTouched:(id)sender {
? ? // 結(jié)束直播? ? [self.session stopLive];
? ? self.stateLable.text = [NSString stringWithFormat:@"狀態(tài): 直播被關(guān)閉\nRTMP: %@", self.rtmpUrl];
}
4.創(chuàng)建一個(gè)按鈕點(diǎn)擊切換前后攝像頭剪芥,代碼如下:
- (IBAction)camaBtnTouched:(id)sender {
? ? AVCaptureDevicePosition devicePositon = self.session.captureDevicePosition;
? ? self.session.captureDevicePosition = (devicePositon == AVCaptureDevicePositionBack) ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack;
? ? NSLog(@"切換前置/后置攝像頭");
}
5.創(chuàng)建一個(gè)按鈕設(shè)置美艷功能,代碼如下:
- (IBAction)beautiyBtnTouched:(id)sender {
? ? ((UIButton*)sender).selected = !((UIButton*)sender).selected;
? ? // 默認(rèn)是開啟了美顏功能的self.session.beautyFace = !self.session.beautyFace;
}
6.推流狀態(tài)監(jiān)聽琴许,接受代理税肪,代碼如下:
#pragmamark -- LFStreamingSessionDelegate/** live status changed will callback */- (void)liveSession:(nullable LFLiveSession *)session liveStateDidChange:(LFLiveState)state{
? ? NSString *tempStatus;
? ? switch (state) {
? ? ? ? case LFLiveReady:
? ? ? ? ? ? tempStatus =@"準(zhǔn)備中";
? ? ? ? ? ? break;
? ? ? ? case LFLivePending:
? ? ? ? ? ? tempStatus =@"連接中";
? ? ? ? ? ? break;
? ? ? ? case LFLiveStart:
? ? ? ? ? ? tempStatus =@"已連接";
? ? ? ? ? ? break;
? ? ? ? case LFLiveStop:
? ? ? ? ? ? tempStatus =@"已斷開";
? ? ? ? ? ? break;
? ? ? ? case LFLiveError:
? ? ? ? ? ? tempStatus =@"連接出錯(cuò)";
? ? ? ? ? ? break;
? ? ? ? default:
? ? ? ? ? ? break;
? ? }
? ? self.stateLable.text = [NSString stringWithFormat:@"狀態(tài): %@\nRTMP: %@", tempStatus, self.rtmpUrl];
}/** live debug info callback */- (void)liveSession:(nullable LFLiveSession *)session debugInfo:(nullable LFLiveDebug*)debugInfo{
}/** callback socket errorcode */- (void)liveSession:(nullable LFLiveSession*)session errorCode:(LFLiveSocketErrorCode)errorCode{
}
6.自己也需要看到自己的推流畫面,并觀察美艷效果榜田,代碼如下:
- (UIView *)livingPreView
{
? ? if(!_livingPreView) {
? ? ? ? UIView *livingPreView = [[UIView alloc] initWithFrame:self.view.bounds];
? ? ? ? livingPreView.backgroundColor = [UIColor clearColor];
? ? ? ? livingPreView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
? ? ? ? [self.view insertSubview:livingPreView atIndex:0];
? ? ? ? _livingPreView = livingPreView;
? ? }
? ? return _livingPreView;
}
至此益兄,推流完成。
拉流:
推流完成后箭券,需要拉流才能進(jìn)行完整的直播净捅。拉流我們采用的也是一個(gè)開源的第三方庫IJKMediaFramework。
這個(gè)庫本質(zhì)是一個(gè)播放器辩块,能播放flv格式的播放器蛔六。用起來和ios自帶的AVPlayer很相似。
實(shí)現(xiàn)功能:1.拉流播放废亭。2.監(jiān)聽国章。
一.創(chuàng)建占位圖和卡頓占位動(dòng)效,代碼如下:
//直播前的占位圖片
- (UIImageView *)placeHolderView
{
? ? if(!_placeHolderView) {
? ? ? ? _placeHolderView = [[UIImageView alloc] init];
? ? ? ? _placeHolderView.frame = self.view.bounds;
? ? ? ? _placeHolderView.image = [UIImage imageNamed:@"profile_user_414x414"];
? ? ? ? // 強(qiáng)制布局? ? ? ? [_placeHolderView layoutIfNeeded];
? ? }
? ? return_placeHolderView;}
//卡頓占位動(dòng)效
- (void)showActivityView{
? ? if(!_activity) {
? ? ? ? _activity= [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
? ? ? ? _activity.frame = CGRectMake((SCREAM_WEIGHT-100)*0.5, (SCREAM_HIGHT-100)*0.5,100,100);
? ? }
? ? [self.activity startAnimating];
? ? [self.view addSubview:self.activity];
}
//關(guān)閉卡頓占位動(dòng)效- (void)stopActivityView{
? ? if ([_activity isAnimating]) {
? ? ? ? [_activity startAnimating];
? ? }
? ? [_activity removeFromSuperview];
? ? _activity = nil;
}
二.拉流播放(創(chuàng)建播放器播放)豆村,代碼如下:
- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? [self.view addSubview:self.placeHolderView];
? ? [self showActivityView];
? ? IJKFFOptions *options = [IJKFFOptions optionsByDefault];
? ? [options setPlayerOptionIntValue:1forKey:@"videotoolbox"];
? ? // 幀速率(fps) (可以改捉腥,確認(rèn)非標(biāo)準(zhǔn)楨率會(huì)導(dǎo)致音畫不同步,所以只能設(shè)定為15或者29.97)[options setPlayerOptionIntValue:29.97forKey:@"r"];
? ? // -vol——設(shè)置音量大小你画,256為標(biāo)準(zhǔn)音量抵碟。(要設(shè)置成兩倍音量時(shí)則輸入512,依此類推[options setPlayerOptionIntValue:512forKey:@"vol"];
? ? IJKFFMoviePlayerController *moviePlayer = [[IJKFFMoviePlayerController alloc] initWithContentURLString:PLAY_URL withOptions:options];
? ? moviePlayer.view.frame = self.view.bounds;
? ? moviePlayer.scalingMode = IJKMPMovieScalingModeAspectFill;
? ? // 設(shè)置自動(dòng)播放(必須設(shè)置為NO, 防止自動(dòng)播放, 才能更好的控制直播的狀態(tài))moviePlayer.shouldAutoplay = NO;
? ? // 默認(rèn)不顯示moviePlayer.shouldShowHudView = NO;
? ? [self.view insertSubview:moviePlayer.view atIndex:0];
? ? [moviePlayer prepareToPlay];
? ? self.moviePlayer = moviePlayer;
? ? // 設(shè)置監(jiān)聽? ? [self addObserver];
? ? [self.view addSubview:self.outBtn];
}
三.設(shè)置監(jiān)聽(主要是監(jiān)聽緩存情況)坏匪,代碼如下:
- (void)addObserver
{
? ? //監(jiān)聽加載狀態(tài)改變通知[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadStateDidChange:) name:IJKMPMoviePlayerLoadStateDidChangeNotificationobject:self.moviePlayer];
}- (void)loadStateDidChange:(NSNotification *) notification
{
? ? //狀態(tài)為緩沖幾乎完成拟逮,可以連續(xù)播放if((self.moviePlayer.loadState & IJKMPMovieLoadStatePlaythroughOK) !=0) {
? ? ? ? if(!self.moviePlayer.isPlaying) {
? ? ? ? ? ? //開始播放? ? ? ? ? ? [self.moviePlayer play];
? ? ? ? ? ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
? ? ? ? ? ? ? ? if (_placeHolderView) {
? ? ? ? ? ? ? ? ? ? [_placeHolderView removeFromSuperview];
? ? ? ? ? ? ? ? ? ? _placeHolderView = nil;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? [self stopActivityView];
? ? ? ? ? ? });
? ? ? ? }else{
? ? ? ? ? ? // 如果是網(wǎng)絡(luò)狀態(tài)不好, 斷開后恢復(fù), 也需要去掉加載if ([_activity isAnimating]) {
? ? ? ? ? ? ? ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
? ? ? ? ? ? ? ? ? ? [self stopActivityView];
? ? ? ? ? ? ? ? });
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? //緩沖中elseif(self.moviePlayer.loadState & IJKMPMovieLoadStateStalled){
? ? ? ? [self showActivityView];
? ? ? ? /*?
? ? ? ? ? ? 這里主播可能已經(jīng)結(jié)束直播了。我們需要請(qǐng)求服務(wù)器查看主播是否已經(jīng)結(jié)束直播适滓。
? ? ? ? ? ? 方法:
? ? ? ? ? ? 1敦迄、從服務(wù)器獲取主播是否已經(jīng)關(guān)閉直播。
? ? ? ? ? ? ? ? 優(yōu)點(diǎn):能夠正確的獲取主播端是否正在直播凭迹。
? ? ? ? ? ? ? ? 缺點(diǎn):主播端異常crash的情況下是沒有辦法通知服務(wù)器該直播關(guān)閉的罚屋。
? ? ? ? ? ? 2、用戶http請(qǐng)求該地址嗅绸,若請(qǐng)求成功表示直播未結(jié)束脾猛,否則結(jié)束
? ? ? ? ? ? ? ? 優(yōu)點(diǎn):能夠真實(shí)的獲取主播端是否有推流數(shù)據(jù)
? ? ? ? ? ? ? ? 缺點(diǎn):如果主播端丟包率太低,但是能夠恢復(fù)的情況下鱼鸠,數(shù)據(jù)請(qǐng)求同樣是失敗的猛拴。
? ? ? ? */? ? }
}
四.記得關(guān)閉前釋放:
- (void)dealloc{
? ? if (_moviePlayer) {
? ? ? ? [_moviePlayer shutdown];
? ? ? ? [_moviePlayer.view removeFromSuperview];
? ? ? ? _moviePlayer = nil;
? ? }
? ? [[NSNotificationCenter defaultCenter]removeObserver:self];
}