FFMPEG-RTST

? ? ?公司的一個(gè)項(xiàng)目二次開發(fā)要用到RTSP解碼,對(duì)于我這個(gè)剛出道的彩筆工程師無疑是巨大的挑戰(zhàn)。盏袄。網(wǎng)上教程不算多蛾绎,但是也不算少。第一步編譯ffmpeg 就卡了好久撞反。妥色。一個(gè)星期終于完工 ? 下面我把代碼貼出來吧 ? 每個(gè)方法基本都有注釋,有些是自己理解的遏片,有些網(wǎng)上大神博客記載的 嘹害,有錯(cuò)誤的地方麻煩多見諒見諒,因?yàn)樽约号@個(gè)弄了很久吮便,深知c是非人類語言=笔呀。=

- (id)initWithVideo:(NSString*)moviePath usesTcp:(BOOL)usesTcp{

if(!(self=[superinit]))returnnil;

AVCodec*pCodec;

//注冊(cè)編碼器(其實(shí)編碼器和解碼器用的注冊(cè)函數(shù)都是一樣的:avcodec_register())

avcodec_register_all();

//注冊(cè)所有容器的格式和codec (能夠自動(dòng)選擇相應(yīng)的文件格式和編碼器,只需要調(diào)用一次)

av_register_all();

//打開流媒體(或本地文件)的函數(shù)是avformat_open_input()其中打開網(wǎng)絡(luò)流的話髓需,前面要加上函數(shù)avformat_network_init()

avformat_network_init();

/*

ps:在打開一些流媒體的時(shí)候可能需要附加一些參數(shù)许师。

如果直接進(jìn)行打開是不會(huì)成功的:

ffplayrtsp://mms.cnr.cn/cnr003?MzE5MTg0IzEjIzI5NjgwOQ==

這時(shí)候我們需要指定其傳輸方式為TCP

ffplay -rtsp_transport tcprtsp://mms.cnr.cn/cnr003?MzE5MTg0IzEjIzI5NjgwOQ==

此外還可以附加一些參數(shù)

ffplay -rtsp_transport tcp -max_delay 5000000rtsp://mms.cnr.cn/cnr003?MzE5MTg0IzEjIzI5NjgwOQ==

在實(shí)際使用ffmpeg編程中,可以通過AVDictionary把參數(shù)傳給avformat_open_input()

轉(zhuǎn)化的代碼: (鍵值)

AVDictionary *avdic=NULL;

char option_key[]="rtsp_transport";

char option_value[]="tcp";

av_dict_set(&avdic,option_key,option_value,0);

char option_key2[]="max_delay";

char option_value2[]="5000000";

av_dict_set(&avdic,option_key2,option_value2,0);

char url[]="rtsp://mms.cnr.cn/cnr003?MzE5MTg0IzEjIzI5NjgwOQ==";

avformat_open_input(&pFormatCtx,url,NULL,&avdic);

*/

// Set the RTSP Options

AVDictionary*opts =0;

//usesTcp外部傳yes則添加參數(shù)

if(usesTcp)

av_dict_set(&opts,"rtsp_transport","tcp",0);

/*

ps:函數(shù)調(diào)用成功之后處理過的AVFormatContext結(jié)構(gòu)體。

file:打開的視音頻流的URL微渠。

fmt:強(qiáng)制指定AVFormatContext中AVInputFormat的搭幻。這個(gè)參數(shù)一般情況下可以設(shè)置為NULL,這樣FFmpeg可以自動(dòng)檢測(cè)AVInputFormat逞盆。

dictionay:附加的一些選項(xiàng)參數(shù)檀蹋,一般情況下可以設(shè)置為NULL。

avformat_open_input(ps, url, fmt, dict);

*/

if(avformat_open_input(&pFormatCtx, [moviePathUTF8String],NULL, &opts) !=0) {

av_log(NULL,AV_LOG_ERROR,"Couldn't open file\n");

NSLog(@"error not open");

gotoinitError;

}

NSLog(@"fileName = %@",moviePath);

//取出包含在文件內(nèi)的流信息

/*

它其實(shí)已經(jīng)實(shí)現(xiàn)了解碼器的查找纳击,解碼器的打開续扔,視音頻幀的讀取,視音頻幀的解碼等工作焕数。換句話說纱昧,該函數(shù)實(shí)際上已經(jīng)“走通”的解碼的整個(gè)流程

1.查找解碼器:find_decoder()

2.打開解碼器:avcodec_open2()

3.讀取完整的一幀壓縮編碼的數(shù)據(jù):read_frame_internal()

注:av_read_frame()內(nèi)部實(shí)際上就是調(diào)用的read_frame_internal()。

4.解碼一些壓縮編碼數(shù)據(jù):try_decode_frame()

ps:

score變量是一個(gè)判決AVInputFormat的分?jǐn)?shù)的門限值堡赔,如果最后得到的AVInputFormat的分?jǐn)?shù)低于該門限值识脆,就認(rèn)為沒有找到合適的AVInputFormat。

FFmpeg內(nèi)部判斷封裝格式的原理實(shí)際上是對(duì)每種AVInputFormat給出一個(gè)分?jǐn)?shù)善已,滿分是100分灼捂,越有可能正確的AVInputFormat給出的分?jǐn)?shù)就越高。最后選擇分?jǐn)?shù)最高的AVInputFormat作為推測(cè)結(jié)果换团。

score的值是一個(gè)宏定義AVPROBE_SCORE_RETRY悉稠,我們可以看一下它的定義:

#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)

其中AVPROBE_SCORE_MAX是score的最大值,取值是100:

#define AVPROBE_SCORE_MAX100 ///< maximum score

由此我們可以得出score取值是25艘包,即如果推測(cè)后得到的最佳AVInputFormat的分值低于25的猛,就認(rèn)為沒有找到合適的AVInputFormat。

*/

if(avformat_find_stream_info(pFormatCtx,NULL) <0) {//函數(shù)正常執(zhí)行返回值是大于等于0的想虎,這個(gè)函數(shù)只是檢測(cè)文件的頭部卦尊,所以接著我們需要檢查在晚間中的流信息

av_log(NULL,AV_LOG_ERROR,"Couldn't find stream information\n");

NSLog(@"error not find");

gotoinitError;

}

//函數(shù)為pFormatCtx->streams填充上正確的信息

//dump_format(pFormatCtx,0,argv[1],0);

// Find the first video stream

videoStream=-1;

audioStream=-1;

//區(qū)分音頻視屏及其他流(現(xiàn)在pFormatCtx->streams僅僅是一組大小為pFormatCtx->nb_streams的指針,所以讓我們先跳過它直到我們找到一個(gè)視頻流舌厨。)

for(inti=0; inb_streams; i++) {

NSLog(@"Stream");

if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {

NSLog(@"found video stream");

videoStream= i;

}elseif(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO) {

audioStream= i;

NSLog(@"found audio stream");

}else{

NSLog(@"其他流");

}

}

//兩種流都沒找到返回錯(cuò)誤

if(videoStream==-1&&audioStream==-1) {

NSLog(@"error not found stream");

gotoinitError;

}

//流中關(guān)于編解碼器的信息就是被我們叫做"codec context"(編解碼器上下文)的東西岂却。這里面包含了流中所使用的關(guān)于編解碼器的所有信息,現(xiàn)在我們有了一個(gè)指向他的指針裙椭。但是我們必需要找到真正的編解碼器并且打開它

pCodecCtx=pFormatCtx->streams[videoStream]->codec;

//尋找解碼器

pCodec =avcodec_find_decoder(pCodecCtx->codec_id);

if(pCodec ==NULL) {

av_log(NULL,AV_LOG_ERROR,"Unsupported codec!\n");

gotoinitError;

}

//打開解碼器

if(avcodec_open2(pCodecCtx, pCodec,NULL) <0) {

av_log(NULL,AV_LOG_ERROR,"Cannot open video decoder\n");

gotoinitError;

}

/*

ps:有些人可能會(huì)從舊的指導(dǎo)中記得有兩個(gè)關(guān)于這些代碼其它部分:添加CODEC_FLAG_TRUNCATED到pCodecCtx->flags和添加一個(gè)hack來粗糙的修正幀率躏哩。這兩個(gè)修正已經(jīng)不在存在于ffplay.c中。

因此揉燃,我必需假設(shè)它們不再必要震庭。我們移除了那些代碼后還有一個(gè)需要指出的不同點(diǎn):pCodecCtx->time_base現(xiàn)在已經(jīng)保存了幀率的信息。time_base是一個(gè)結(jié)構(gòu)體你雌,它里面有一個(gè)分子和分母(AVRational)器联。

我們使用分?jǐn)?shù)的方式來表示幀率是因?yàn)楹芏嗑幗獯a器使用非整數(shù)的幀率(例如NTSC使用29.97fps)

*/

//表示有音頻輸入

if(audioStream> -1) {

NSLog(@"set up audiodecoder");

[selfsetupAudioDecoder];

}

//給視頻幀分配空間以便存儲(chǔ)解碼后的圖片

pFrame=av_frame_alloc();

//外部可以手動(dòng)設(shè)置視屏寬高

outputWidth=pCodecCtx->width;

self.outputHeight=pCodecCtx->height;

returnself;

initError:

returnnil;

}

//對(duì)幀的操作?

- (void)seekTime:(double)seconds{

AVRationaltimeBase =pFormatCtx->streams[videoStream]->time_base;

int64_ttargetFrame = (int64_t)((double)timeBase.den/ timeBase.num* seconds);

avformat_seek_file(pFormatCtx,videoStream, targetFrame, targetFrame, targetFrame,AVSEEK_FLAG_FRAME);

avcodec_flush_buffers(pCodecCtx);

}

//what?

- (double)currentTime{

AVRationaltimeBase =pFormatCtx->streams[videoStream]->time_base;

returnpacket.pts* (double)timeBase.num/ timeBase.den;

}

//需要輸出的格式可以修改(這里是RGB)

- (void)setupScaler

{

// Release old picture and scaler

avpicture_free(&picture);

sws_freeContext(img_convert_ctx);

// Allocate RGB picture

avpicture_alloc(&picture,PIX_FMT_RGB24,outputWidth,outputHeight);

// S轉(zhuǎn)化(PIX_FMT_UYVY422PIX_FMT_YUV422PPIX_FMT_RGB24)

staticintsws_flags =SWS_FAST_BILINEAR;

img_convert_ctx=sws_getContext(pCodecCtx->width,

pCodecCtx->height,

pCodecCtx->pix_fmt,

outputWidth,

outputHeight,

PIX_FMT_RGB24,

sws_flags,NULL,NULL,NULL);

//解碼后的視頻幀數(shù)據(jù)保存在pFrame變量中二汛,然后經(jīng)過swscale函數(shù)轉(zhuǎn)換后,將視頻幀數(shù)據(jù)保存在pFrameYUV變量中拨拓。最后將pFrameYUV中的數(shù)據(jù)寫入成文件肴颊。AVFrame *pFrameYUV

//sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

}

- (void)convertFrameToRGB{

//解碼后的視頻幀數(shù)據(jù)保存在pFrame變量中,然后經(jīng)過swscale函數(shù)轉(zhuǎn)換后渣磷,將視頻幀數(shù)據(jù)保存在pFrameYUV變量中婿着。最后將pFrameYUV中的數(shù)據(jù)寫入成文件。AVFrame *pFrameYUV

//這里是保存在avpicture(AVPicture的結(jié)成:AVPicture結(jié)構(gòu)體是AVFrame結(jié)構(gòu)體的子集――AVFrame結(jié)構(gòu)體的開始部分與AVPicture結(jié)構(gòu)體是一樣的)

sws_scale(img_convert_ctx,

(constuint8_t*const*)pFrame->data,

pFrame->linesize,

0,

pCodecCtx->height,

picture.data,

picture.linesize);

}

- (BOOL)stepFrame{

/*

av_read_frame()讀取一個(gè)包并且把它保存到AVPacket結(jié)構(gòu)體中醋界。注意我們僅僅申請(qǐng)了一個(gè)包的結(jié)構(gòu)體――ffmpeg為我們申請(qǐng)了內(nèi)部的數(shù)據(jù)的內(nèi)存并通過packet.data指針來指向它竟宋。

這些數(shù)據(jù)可以在后面通過av_free_packet()來釋放。函數(shù)avcodec_decode_video()把包轉(zhuǎn)換為幀形纺。然而當(dāng)解碼一個(gè)包的時(shí)候丘侠,我們可能沒有得到我們需要的關(guān)于幀的信息。因此逐样,

當(dāng)我們得到下一幀的時(shí)候蜗字,avcodec_decode_video()為我們?cè)O(shè)置了幀結(jié)束標(biāo)志frameFinished

*/

// AVPacket packet;

intframeFinished=0;

//NSLog(@"TS");

while(!frameFinished &&av_read_frame(pFormatCtx, &packet) >=0) {

// Is this a packet from the video stream?

if(packet.stream_index==videoStream) {

// Decode video frame

avcodec_decode_video2(pCodecCtx,pFrame, &frameFinished, &packet);

}

if(packet.stream_index==audioStream) {

// NSLog(@"audio stream");

[audioPacketQueueLocklock];

audioPacketQueueSize+=packet.size;

[audioPacketQueueaddObject:[NSMutableDatadataWithBytes:&packetlength:sizeof(packet)]];

[audioPacketQueueLockunlock];

if(!primed) {

primed=YES;

[_audioController_startAudio];

}

if(emptyAudioBuffer) {

[_audioControllerenqueueBuffer:emptyAudioBuffer];

}

}

}

returnframeFinished!=0;

}

//文件打開動(dòng)作,然后寫入RGB數(shù)據(jù)脂新。我們一次向文件寫入一行數(shù)據(jù)

- (void)savePPMPicture:(AVPicture)pict width:(int)width height:(int)height index:(int)iFrame{

NSLog(@"WRITE?");

/*

ps:如果想將轉(zhuǎn)換后的原始數(shù)據(jù)存成文件挪捕,只需要將pFrameYUV的data指針指向的數(shù)據(jù)寫入文件就可以了。

1.保存YUV420P格式的數(shù)據(jù)

fwrite(pFrameYUV->data[0],(pCodecCtx->width)*(pCodecCtx->height),1,output);

fwrite(pFrameYUV->data[1],(pCodecCtx->width)*(pCodecCtx->height)/4,1,output);

fwrite(pFrameYUV->data[2],(pCodecCtx->width)*(pCodecCtx->height)/4,1,output);

2.保存RGB24格式的數(shù)據(jù)

fwrite(pFrameYUV->data[0],(pCodecCtx->width)*(pCodecCtx->height)*3,1,output);

3.保存UYVY格式的數(shù)據(jù)

fwrite(pFrameYUV->data[0],(pCodecCtx->width)*(pCodecCtx->height),2,output);

*/

FILE*pFile;

NSString*fileName;

inty;

fileName = [UtilitiesdocumentsPath:[NSStringstringWithFormat:@"image%04d.ppm",iFrame]];

// Open file

NSLog(@"write image file: %@",fileName);

//打開輸入文件

pFile =fopen([fileNamecStringUsingEncoding:NSASCIIStringEncoding],"wb");

if(pFile ==NULL) {

return;

}

// Write header

fprintf(pFile,"P6\n%d %d\n255\n", width, height);

// Write pixel data

for(y=0; y

fwrite(pict.data[0]+y*pict.linesize[0],1, width*3, pFile);

}

// Close file

fclose(pFile);

}

源碼鏈接https://pan.baidu.com/s/1jINgjI2 (如果百度云服務(wù)器太渣争便,留郵箱我發(fā)給你們) ? ?audioStream沒有實(shí)現(xiàn)级零,因?yàn)槟壳暗男枨笾皇峭瓿梢曨l流的解析,有興趣的朋友自己去完善吧滞乙,相信它會(huì)給剛剛接觸到ffmpeg的朋友帶來很大幫助

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奏纪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子酷宵,更是在濱河造成了極大的恐慌亥贸,老刑警劉巖躬窜,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浇垦,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡荣挨,警方通過查閱死者的電腦和手機(jī)男韧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來默垄,“玉大人此虑,你說我怎么就攤上這事】诙В” “怎么了朦前?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵介杆,是天一觀的道長。 經(jīng)常有香客問我韭寸,道長春哨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任恩伺,我火速辦了婚禮赴背,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晶渠。我一直安慰自己凰荚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布褒脯。 她就那樣靜靜地躺著便瑟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪憨颠。 梳的紋絲不亂的頭發(fā)上胳徽,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音爽彤,去河邊找鬼养盗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛适篙,可吹牛的內(nèi)容都是我干的往核。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼嚷节,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼聂儒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起硫痰,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤衩婚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后效斑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體非春,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年缓屠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奇昙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡敌完,死狀恐怖储耐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情滨溉,我是刑警寧澤什湘,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布长赞,位于F島的核電站,受9級(jí)特大地震影響闽撤,放射性物質(zhì)發(fā)生泄漏涧卵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一腹尖、第九天 我趴在偏房一處隱蔽的房頂上張望柳恐。 院中可真熱鬧,春花似錦热幔、人聲如沸乐设。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽近尚。三九已至,卻和暖如春场勤,著一層夾襖步出監(jiān)牢的瞬間戈锻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工和媳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留格遭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓留瞳,卻偏偏與公主長得像拒迅,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子她倘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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