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阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容