? ? ?公司的一個(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的朋友帶來很大幫助