FFMPEG-RTST

? ? ?公司的一個項目二次開發要用到RTSP解碼,對于我這個剛出道的彩筆工程師無疑是巨大的挑戰。。網上教程不算多,但是也不算少。第一步編譯ffmpeg 就卡了好久。。一個星期終于完工 ? 下面我把代碼貼出來吧 ? 每個方法基本都有注釋,有些是自己理解的,有些網上大神博客記載的 ,有錯誤的地方麻煩多見諒見諒,因為自己弄這個弄了很久,深知c是非人類語言=。=

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

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

AVCodec*pCodec;

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

avcodec_register_all();

//注冊所有容器的格式和codec (能夠自動選擇相應的文件格式和編碼器,只需要調用一次)

av_register_all();

//打開流媒體(或本地文件)的函數是avformat_open_input()其中打開網絡流的話,前面要加上函數avformat_network_init()

avformat_network_init();

/*

ps:在打開一些流媒體的時候可能需要附加一些參數。

如果直接進行打開是不會成功的:

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

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

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

此外還可以附加一些參數

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

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

轉化的代碼: (鍵值)

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則添加參數

if(usesTcp)

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

/*

ps:函數調用成功之后處理過的AVFormatContext結構體。

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

fmt:強制指定AVFormatContext中AVInputFormat的。這個參數一般情況下可以設置為NULL,這樣FFmpeg可以自動檢測AVInputFormat。

dictionay:附加的一些選項參數,一般情況下可以設置為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);

//取出包含在文件內的流信息

/*

它其實已經實現了解碼器的查找,解碼器的打開,視音頻幀的讀取,視音頻幀的解碼等工作。換句話說,該函數實際上已經“走通”的解碼的整個流程

1.查找解碼器:find_decoder()

2.打開解碼器:avcodec_open2()

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

注:av_read_frame()內部實際上就是調用的read_frame_internal()。

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

ps:

score變量是一個判決AVInputFormat的分數的門限值,如果最后得到的AVInputFormat的分數低于該門限值,就認為沒有找到合適的AVInputFormat。

FFmpeg內部判斷封裝格式的原理實際上是對每種AVInputFormat給出一個分數,滿分是100分,越有可能正確的AVInputFormat給出的分數就越高。最后選擇分數最高的AVInputFormat作為推測結果。

score的值是一個宏定義AVPROBE_SCORE_RETRY,我們可以看一下它的定義:

#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)

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

#define AVPROBE_SCORE_MAX100 ///< maximum score

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

*/

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

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

NSLog(@"error not find");

gotoinitError;

}

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

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

// Find the first video stream

videoStream=-1;

audioStream=-1;

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

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(@"其他流");

}

}

//兩種流都沒找到返回錯誤

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

NSLog(@"error not found stream");

gotoinitError;

}

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

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:有些人可能會從舊的指導中記得有兩個關于這些代碼其它部分:添加CODEC_FLAG_TRUNCATED到pCodecCtx->flags和添加一個hack來粗糙的修正幀率。這兩個修正已經不在存在于ffplay.c中。

因此,我必需假設它們不再必要。我們移除了那些代碼后還有一個需要指出的不同點:pCodecCtx->time_base現在已經保存了幀率的信息。time_base是一個結構體,它里面有一個分子和分母(AVRational)。

我們使用分數的方式來表示幀率是因為很多編解碼器使用非整數的幀率(例如NTSC使用29.97fps)

*/

//表示有音頻輸入

if(audioStream> -1) {

NSLog(@"set up audiodecoder");

[selfsetupAudioDecoder];

}

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

pFrame=av_frame_alloc();

//外部可以手動設置視屏寬高

outputWidth=pCodecCtx->width;

self.outputHeight=pCodecCtx->height;

returnself;

initError:

returnnil;

}

//對幀的操作?

- (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轉化(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);

//解碼后的視頻幀數據保存在pFrame變量中,然后經過swscale函數轉換后,將視頻幀數據保存在pFrameYUV變量中。最后將pFrameYUV中的數據寫入成文件。AVFrame *pFrameYUV

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

}

- (void)convertFrameToRGB{

//解碼后的視頻幀數據保存在pFrame變量中,然后經過swscale函數轉換后,將視頻幀數據保存在pFrameYUV變量中。最后將pFrameYUV中的數據寫入成文件。AVFrame *pFrameYUV

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

sws_scale(img_convert_ctx,

(constuint8_t*const*)pFrame->data,

pFrame->linesize,

0,

pCodecCtx->height,

picture.data,

picture.linesize);

}

- (BOOL)stepFrame{

/*

av_read_frame()讀取一個包并且把它保存到AVPacket結構體中。注意我們僅僅申請了一個包的結構體――ffmpeg為我們申請了內部的數據的內存并通過packet.data指針來指向它。

這些數據可以在后面通過av_free_packet()來釋放。函數avcodec_decode_video()把包轉換為幀。然而當解碼一個包的時候,我們可能沒有得到我們需要的關于幀的信息。因此,

當我們得到下一幀的時候,avcodec_decode_video()為我們設置了幀結束標志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;

}

//文件打開動作,然后寫入RGB數據。我們一次向文件寫入一行數據

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

NSLog(@"WRITE?");

/*

ps:如果想將轉換后的原始數據存成文件,只需要將pFrameYUV的data指針指向的數據寫入文件就可以了。

1.保存YUV420P格式的數據

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格式的數據

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

3.保存UYVY格式的數據

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 (如果百度云服務器太渣,留郵箱我發給你們) ? ?audioStream沒有實現,因為目前的需求只是完成視頻流的解析,有興趣的朋友自己去完善吧,相信它會給剛剛接觸到ffmpeg的朋友帶來很大幫助

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容