IOS-RTMP相關(guān)

介紹:IOS下的RTMP播放器真心不多,好用的更少。本人找了一圈最后還是在GITHUB里使用了
ijkplayer 播放器
簡(jiǎn)單介紹一下蹂随,ijkplayer 是基于ffmpeg 上封裝了一層的播放器支持IOS,android,寫這篇文章的時(shí)候ios 方面github上介紹還不多因惭,我這里說(shuō)下怎么來(lái)播放RTMP的視頻岳锁。
1、如何播放RTMP視頻
找到 JKDemoInputURLViewController.m 修改代碼如下

- (void)onClickPlayButton {
NSURL*url = [NSURLURLWithString:self.textView.text];
NSString*scheme = [[urlscheme]lowercaseString];
//if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"]) {
[IJKVideoViewControllerpresentFromViewController:selfwithTitle:[NSStringstringWithFormat:@"URL: %@", url]URL:urlcompletion:^{
//[self.navigationController popViewControllerAnimated:NO];
}];
//}
}

好了 你直接可以在輸入U(xiǎn)RL模式中輸入你的RTMP地址就好了

  1. 如何降低延遲的問(wèn)題
    服務(wù)器:本人的流媒體服務(wù)器是自建的蹦魔,使用的是云主機(jī)激率,5MB帶寬2CPU 1.0GHz 2G內(nèi)存屬于比較爛配置,用的red5 1.0.7 最新的版本勿决,具體怎么安裝請(qǐng)查看Red5 GITHUB內(nèi)的WIKI部分即可乒躺,推流端:IOS 用的 LFLiveKit 可以自行GITHUB 內(nèi)搜索即可。
    延遲主要存在于推流端和播放器端 LFLiveKit 不存在緩沖區(qū)沒(méi)什么好配置的低缩,如果使用OBS 推流請(qǐng)?jiān)?輸出-->高級(jí)-->緩沖大小填0即可嘉冒,
    Red5 去conf 文件夾下里修改red5.properties 文件
rtmp.send_buffer_size=200
rtmp.receive_buffer_size=200

服務(wù)端就好了沒(méi)什么別的,重啟red5 服務(wù)即可
ijkplayer播放器默認(rèn)是帶緩沖的所以我們主要修改播放器部分的緩沖區(qū),原理就是去掉作者封裝的緩沖區(qū)那一層咆繁,激活ffplay 播放器的無(wú)緩沖標(biāo)志讳推。我們來(lái)操作吧
首頁(yè)我們?cè)?公共頭文件IJKMediaPlayer-Prefix.pch里增加一個(gè)宏定義

#define USE_IJK_BUFERING  0

在 IJKFFOptions.m 里修改

+ (IJKFFOptions*)optionsByDefault
{
IJKFFOptions*options = [[IJKFFOptionsalloc]init];
[optionssetPlayerOptionIntValue:30forKey:@"max-fps"];
[optionssetPlayerOptionIntValue:0forKey:@"framedrop"];
[optionssetPlayerOptionIntValue:3forKey:@"video-pictq-size"];
[optionssetPlayerOptionIntValue:0forKey:@"videotoolbox"];
[optionssetPlayerOptionIntValue:960forKey:@"videotoolbox-max-frame-width"];
[optionssetFormatOptionIntValue:0forKey:@"auto_convert"];
[optionssetFormatOptionIntValue:1forKey:@"reconnect"];
[optionssetFormatOptionIntValue:30*1000*1000forKey:@"timeout"];
[optionssetFormatOptionValue:@"ijkplayer"forKey:@"user-agent"];
[optionssetFormatOptionValue:@"2000000"forKey:@"analyzeduration"];//增加的
[optionssetFormatOptionValue:@"nobuffer"forKey:@"fflags"];//增加的
[optionssetFormatOptionValue:@"4096"forKey:@"probsize"]; //增加的
options.showHudView=NO;
returnoptions;
}

在 ff_ffplay_def.h 文件里修改

#ifdef USE_IJK_BUFERING
#define DEFAULT_HIGH_WATER_MARK_IN_BYTES (256*1024)
/*
* START: buffering after prepared/seeked
* NEXT:buffering for the second time after START
* MAX:...
*/
#define DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS (100)
#define DEFAULT_NEXT_HIGH_WATER_MARK_IN_MS (1*1000)
#define DEFAULT_LAST_HIGH_WATER_MARK_IN_MS (5*1000)
#define BUFFERING_CHECK_PER_BYTES  (512)
#define BUFFERING_CHECK_PER_MILLISECONDS (500)
#define MAX_QUEUE_SIZE (15*1024*1024)
#ifdef FFP_MERGE
#define MIN_FRAMES 25
#endif
#define DEFAULT_MIN_FRAMES 50000
#define MIN_MIN_FRAMES 5
#define MAX_MIN_FRAMES 50000
#define MIN_FRAMES (ffp->dcc.min_frames)
#else
#define MAX_QUEUE_SIZE (0)
#define MIN_FRAMES 5
#define MIN_MIN_FRAMES 5
#endif

簡(jiǎn)單說(shuō)明下就是去掉原作者的緩沖,使用我們自己定義的緩沖區(qū)大小 MAX_QUEUE_SIZE 為0 就是不緩沖了(這里的緩沖區(qū)是顯示層的玩般,不是網(wǎng)絡(luò)數(shù)據(jù)的緩沖)
接著ff_ffplay_options.h 文件里肯定要報(bào)錯(cuò)了

//{ "min-frames","minimal frames to stop pre-reading",
//OPTION_OFFSET(dcc.min_frames),OPTION_INT(DEFAULT_MIN_FRAMES, MIN_MIN_FRAMES, MAX_MIN_FRAMES) },
//{ "first-high-water-mark-ms","first chance to wakeup read_thread",
//OPTION_OFFSET(dcc.first_high_water_mark_in_ms),
//OPTION_INT(DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS,
//DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS,
//DEFAULT_LAST_HIGH_WATER_MARK_IN_MS) },
//{ "next-high-water-mark-ms","second chance to wakeup read_thread",
//OPTION_OFFSET(dcc.next_high_water_mark_in_ms),
//OPTION_INT(DEFAULT_NEXT_HIGH_WATER_MARK_IN_MS,
//DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS,
//DEFAULT_LAST_HIGH_WATER_MARK_IN_MS) },
//{ "last-high-water-mark-ms","last chance to wakeup read_thread",
//OPTION_OFFSET(dcc.last_high_water_mark_in_ms),
//OPTION_INT(DEFAULT_LAST_HIGH_WATER_MARK_IN_MS,
//DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS,
//DEFAULT_LAST_HIGH_WATER_MARK_IN_MS) },

注釋掉相關(guān)報(bào)錯(cuò)的屬性即可
繼續(xù)在 ff_ffplay.c 文件里修改
staticintdecoder_decode_frame(FFPlayerffp,Decoderd,AVFrameframe,AVSubtitlesub)函數(shù)內(nèi)修改

do{
if(d->queue->nb_packets==0)
SDL_CondSignal(d->empty_queue_cond);
#ifdef USE_IJK_BUFERING
if(packet_queue_get_or_buffering(ffp, d->queue, &pkt, &d->pkt_serial, &d->finished) <0)
return-1;
#else
if(packet_queue_get(d->queue, &pkt,1, &d->pkt_serial) <0)
return-1;
#endif
if(pkt.data==flush_pkt.data) {
avcodec_flush_buffers(d->avctx);
d->finished=0;
d->next_pts= d->start_pts;
d->next_pts_tb= d->start_pts_tb;
}
}while(pkt.data==flush_pkt.data|| d->queue->serial!= d->pkt_serial);

staticintread_thread(void*arg) 函數(shù)最后修改

if(ffp->packet_buffering) {
#ifdef USE_IJK_BUFERING
io_tick_counter = SDL_GetTickHR();
if(abs((int)(io_tick_counter - prev_io_tick_counter)) > BUFFERING_CHECK_PER_MILLISECONDS) {
prev_io_tick_counter = io_tick_counter;
ffp_check_buffering_l(ffp);
}
#endif
}

最后 ffpipenode_ios_videotoolbox_vdec.m 文件里修改
intdecoder_decode_frame_videotoolbox(VideoToolBoxContext* context)函數(shù)里

do{
if(d->queue->nb_packets==0)
SDL_CondSignal(d->empty_queue_cond);
ffp_video_statistic_l(ffp);
#ifdef USE_IJK_BUFERING
if(ffp_packet_queue_get_or_buffering(ffp, d->queue, &pkt, &d->pkt_serial, &d->finished) <0)
return-1;
#else
if(ffp_packet_queue_get(d->queue, &pkt,1,&d->pkt_serial) <0)
return-1;
#endif
if(ffp_is_flush_packet(&pkt)) {
avcodec_flush_buffers(d->avctx);
context->refresh_request=true;
context->serial+=1;
d->finished=0;
ALOGI("flushed last keyframe pts %lld \n",d->pkt.pts);
d->next_pts= d->start_pts;
d->next_pts_tb= d->start_pts_tb;
}
}while(ffp_is_flush_packet(&pkt) || d->queue->serial!= d->pkt_serial);

好了跑一下银觅,帶寬足夠的情況下延遲應(yīng)該在0.5-1秒左右,基本是實(shí)時(shí)的了

3.ijkplayer 的BUG 解決
ijkplayer 確實(shí)已經(jīng)算很贊的播放器了坏为,畢竟是開(kāi)源的東東究驴,自然多少會(huì)有點(diǎn)BUG慨仿,說(shuō)下使用中發(fā)現(xiàn)的BUG問(wèn)題也節(jié)省各位看官時(shí)間。
現(xiàn)象:觀看一下直播然后點(diǎn)擊DONE按鈕 關(guān)閉后后會(huì)有一個(gè)超時(shí)提示纳胧,DEBUG時(shí)是在IJKSDLAudioQueueController.m 文件里的stop 函數(shù)里的 AudioQueueStop(_audioQueueRef,true); 這句話報(bào)出來(lái)的。查了下蘋果的資料說(shuō)明帘撰,這個(gè)函數(shù)默認(rèn)是和音頻的處理函數(shù)在同一個(gè)線程下執(zhí)行的所以會(huì)卡死報(bào)出超時(shí)警告跑慕。建議是用不同的線程去執(zhí)行這個(gè)函數(shù),并且需要判定kAudioQueueProperty_IsRunning 是否是1 才可以調(diào)用的那么我們就來(lái)解決下吧:

// do not lock AudioQueueStop, or may be run into deadlock
dispatch_queue_tconcurrentQueue =dispatch_queue_create("leon.audio.queue",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
while(true) {
UInt32askRunning =0;
UInt32runsize =sizeof(askRunning);
AudioQueueGetProperty(_audioQueueRef,kAudioQueueProperty_IsRunning, &askRunning, &runsize);
if(askRunning) {
AudioQueueStop(_audioQueueRef,true);
AudioQueueDispose(_audioQueueRef,true);
_audioQueueRef=nil;
break;
}
}
});

再運(yùn)行下發(fā)現(xiàn)不再出現(xiàn)警告可以順利執(zhí)行,但是接著來(lái)了一個(gè)野指針訪問(wèn)摧找。核行。。程序直接CRASH 了(當(dāng)然不改的話等個(gè)5-10秒這個(gè)CRASH 也會(huì)出現(xiàn))
這個(gè)野指針?lè)治鱿聛?lái)小弟的看法是網(wǎng)絡(luò)數(shù)據(jù)包一次性給了10KB的數(shù)據(jù)蹬耘,audio 處理單元拿下來(lái)處理成了對(duì)應(yīng)的音頻包結(jié)構(gòu)后變成了10個(gè)音頻單元包芝雪,這10個(gè)音頻包是按順序處理的,數(shù)據(jù)源是在FFPlayer結(jié)構(gòu)體里的is分配的 ,當(dāng)我們點(diǎn)擊了DONE按鈕后FFPlayer 對(duì)象里的is 的對(duì)象釋放時(shí)综苔,那10個(gè)音頻包是在不同線程里處理的沒(méi)有處理完比如只處理4個(gè)還有6個(gè)惩系,從而造成了野指針訪問(wèn)導(dǎo)致程序CRASH,解釋了這么多其實(shí)小弟我也頭暈一開(kāi)始想的是如何做好線程間的調(diào)度如筛,想了半天沒(méi)什么思路為了方便就順著正常思路想下去那就是如果我把音頻包都處理完不就沒(méi)這個(gè)問(wèn)題了嘛堡牡,那就來(lái)動(dòng)手吧:
在FFPlayer 結(jié)構(gòu)體里增加一個(gè) int willclose; 屬性(我隨便起的)接著在FFPlayer*ffp_create()函數(shù)加一個(gè)初始化

FFPlayer*ffp_create()
{
av_log(NULL,AV_LOG_INFO,"av_version_info: %s\n",av_version_info());
FFPlayer* ffp = (FFPlayer*)av_mallocz(sizeof(FFPlayer));
if(!ffp)
returnNULL;
msg_queue_init(&ffp->msg_queue);
ffp->af_mutex=SDL_CreateMutex();
ffp->vf_mutex=SDL_CreateMutex();
ffp_reset_internal(ffp);
ffp->av_class= &ffp_context_class;
ffp->meta=ijkmeta_create();
ffp->willclose=10;
av_opt_set_defaults(ffp);
returnffp;
}

在 intffp_wait_stop_l(FFPlayer*ffp) 函數(shù)修改

intffp_wait_stop_l(FFPlayer*ffp)
{
assert(ffp);
if(ffp->is) {
ffp_stop_l(ffp);
ffp->willclose=90;
stream_close(ffp);
ffp->is=NULL;
}
return0;
}

最后 staticvoidsdl_audio_callback(voidopaque,Uint8stream,intlen) 函數(shù)做如下修改

while(len >0) {
if(is->audio_buf_index>= is->audio_buf_size) {
//增加部分(以下)
if(ffp->willclose==90) {
is->audio_buf_index= is->audio_buf_size;
memset(stream,0, len);
SDL_AoutFlushAudio(ffp->aout);
break;
}
//增加部分(以上)
audio_size =audio_decode_frame(ffp);
if(audio_size <0) {
/* if error, just output silence */
is->audio_buf= is->silence_buf;
is->audio_buf_size=sizeof(is->silence_buf) / is->audio_tgt.frame_size* is->audio_tgt.frame_size;
}else{
if(is->show_mode!=SHOW_MODE_VIDEO)
update_sample_display(is, (int16_t*)is->audio_buf, audio_size);
is->audio_buf_size= audio_size;
}
is->audio_buf_index=0;
}
if(is->auddec.pkt_serial!= is->audioq.serial) {
is->audio_buf_index= is->audio_buf_size;
memset(stream,0, len);
// stream += len;
// len = 0;
SDL_AoutFlushAudio(ffp->aout);
break;
}
len1 = is->audio_buf_size- is->audio_buf_index;
if(len1 > len)
len1 = len;
if(!is->muted&& is->audio_volume==SDL_MIX_MAXVOLUME)
memcpy(stream, (uint8_t*)is->audio_buf+ is->audio_buf_index, len1);
else{
memset(stream, is->silence_buf[0], len1);
if(!is->muted)
SDL_MixAudio(stream, (uint8_t*)is->audio_buf+ is->audio_buf_index, len1, is->audio_volume);
}
len -= len1;
stream += len1;
is->audio_buf_index+= len1;
}

運(yùn)行一下,咩嘿嘿順利釋放不再CRASH 了杨刨。就先寫到這里了后面有新的成果再來(lái)記錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末晤柄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子妖胀,更是在濱河造成了極大的恐慌芥颈,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赚抡,死亡現(xiàn)場(chǎng)離奇詭異爬坑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)怕品,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門妇垢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人肉康,你說(shuō)我怎么就攤上這事闯估。” “怎么了吼和?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵涨薪,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我炫乓,道長(zhǎng)刚夺,這世上最難降的妖魔是什么献丑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮侠姑,結(jié)果婚禮上创橄,老公的妹妹穿的比我還像新娘。我一直安慰自己莽红,他們只是感情好妥畏,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著安吁,像睡著了一般醉蚁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鬼店,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天网棍,我揣著相機(jī)與錄音,去河邊找鬼妇智。 笑死滥玷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的俘陷。 我是一名探鬼主播罗捎,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼拉盾!你這毒婦竟也來(lái)了桨菜?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捉偏,失蹤者是張志新(化名)和其女友劉穎倒得,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體夭禽,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡霞掺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了讹躯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菩彬。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖潮梯,靈堂內(nèi)的尸體忽然破棺而出骗灶,到底是詐尸還是另有隱情,我是刑警寧澤秉馏,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布耙旦,位于F島的核電站,受9級(jí)特大地震影響萝究,放射性物質(zhì)發(fā)生泄漏免都。R本人自食惡果不足惜锉罐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绕娘。 院中可真熱鬧脓规,春花似錦、人聲如沸险领。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)舷暮。三九已至,卻和暖如春噩茄,著一層夾襖步出監(jiān)牢的瞬間下面,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工绩聘, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沥割,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓凿菩,卻偏偏與公主長(zhǎng)得像机杜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子衅谷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容