ffmpeg在iOS的使用 - iFrameExtractor源码解析
http://www.cocoachina.com/ios/20150914/13284.html

iFrameExtractor地址:https://github.com/lajos/iFrameExtractor
ffmpeg的简介
FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。
"FFmpeg"这个单词中的"FF"指的是"Fast Forward"。
ffmpeg支持的格式
ASF
AVI
BFI
FLV
GXF, General eXchange Format, SMPTE 360M
IFF
RL2
ISO base media file format(包括QuickTime, 3GP和MP4)
Matroska(包括WebM)
Maxis XA
MPEG program stream
MPEG transport stream(including AVCHD)
MXF, Material eXchange Format, SMPTE 377M
MSN Webcam stream
Ogg
OMA
TXD
WTV
ffmpeg支持的协议
IETF标准:TCP, UDP, Gopher, HTTP, RTP, RTSP和SDP
苹果公司的相关标准:HTTP Live Streaming
RealMedia的相关标准:RealMedia RTSP/RDT
Adobe的相关标准:RTMP, RTMPT(由librtmp实现),RTMPE(由librtmp实现),RTMPTE(由librtmp)和RTMPS(由librtmp实现)
微软的相关标准:MMS在TCP上和MMS在HTTP上
iFrameExtractor的使用
初始化
|
1
2
3
|
self.video = [[VideoFrameExtractor alloc] initWithVideo:[Utilities bundlePath:@"sophie.mov"]]; video.outputWidth = 426; video.outputHeight = 320; |
播放
|
1
2
3
4
5
6
7
8
9
10
11
12
|
[video seekTime:0.0]; [NSTimer scheduledTimerWithTimeInterval:1.0/30 target:self selector:@selector(displayNextFrame:) userInfo:nil repeats:YES];-(void)displayNextFrame:(NSTimer *)timer { if (![video stepFrame]) { return; } imageView.image = video.currentImage;} |
VideoFrameExtractor类解析
initWithVideo:(NSString *)moviePath方法
VideoFrameExtractor的初始化,主要是配置三个全局的结构体变量。
AVFormatContext类型的pFormatCtx,AVFormatContext主要存储视音频封装格式中包含的信息;AVInputFormat存储输入视音频使用的封装格式。每种视音频封装格式都对应一个AVInputFormat 结构。
AVCodecContext类型的pCodecCtx ,每个AVStream存储一个视频/音频流的相关数据;每个AVStream对应一个AVCodecContext,存储该视频/音频流使用解码方式的相关数据;每个AVCodecContext中对应一个AVCodec,包含该视频/音频对应的解码器。每种解码器都对应一个AVCodec结构。
AVFrame类型的pFrame,视频的话,每个结构一般是存一帧,音频可能有好几帧。解码前数据是AVPacket,解码后数据是AVFrame。
FMPEG中结构体很多。最关键的结构体他们之间的对应关系如下所示:

图片来自:FFMPEG中最关键的结构体之间的关系
下面就是初始化的代码
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
-(id)initWithVideo:(NSString *)moviePath { if (!(self=[super init])) return nil; AVCodec *pCodec; // Register all formats and codecs avcodec_register_all(); av_register_all(); // Open video file if(avformat_open_input(&pFormatCtx, [moviePath cStringUsingEncoding:NSASCIIStringEncoding], NULL, NULL) != 0) { av_log(NULL, AV_LOG_ERROR, "Couldn't open file\n"); goto initError; } // Retrieve stream information if(avformat_find_stream_info(pFormatCtx,NULL) < 0) { av_log(NULL, AV_LOG_ERROR, "Couldn't find stream information\n"); goto initError; } // Find the first video stream if ((videoStream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0)) < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot find a video stream in the input file\n"); goto initError; } // Get a pointer to the codec context for the video stream pCodecCtx = pFormatCtx->streams[videoStream]->codec; // Find the decoder for the video stream pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec == NULL) { av_log(NULL, AV_LOG_ERROR, "Unsupported codec!\n"); goto initError; } // Open codec if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot open video decoder\n"); goto initError; } // Allocate video frame pFrame = avcodec_alloc_frame(); outputWidth = pCodecCtx->width; self.outputHeight = pCodecCtx->height; return self; initError: [self release]; return nil;} |
sourceWidth和sourceHeight方法
获取屏幕的宽和高
|
1
2
3
4
5
6
|
-(int)sourceWidth { return pCodecCtx->width;}-(int)sourceHeight { return pCodecCtx->height;} |
setupScaler方法
设置视频播放视图的尺寸
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
-(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); // Setup scaler static int sws_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); } |
duration方法
获取音视频文件的总时间
|
1
2
3
|
-(double)duration { return (double)pFormatCtx->duration / AV_TIME_BASE;} |
currentTime方法
显示音视频当前播放的时间
|
1
2
3
4
|
-(double)currentTime { AVRational timeBase = pFormatCtx->streams[videoStream]->time_base; return packet.pts * (double)timeBase.num / timeBase.den;} |
seekTime:(double)seconds方法
直接跳到音视频的第seconds秒进行播放,默认从第0.0秒开始
|
1
2
3
4
5
6
|
-(void)seekTime:(double)seconds { AVRational timeBase = pFormatCtx->streams[videoStream]->time_base; int64_t targetFrame = (int64_t)((double)timeBase.den / timeBase.num * seconds); avformat_seek_file(pFormatCtx, videoStream, targetFrame, targetFrame, targetFrame, AVSEEK_FLAG_FRAME); avcodec_flush_buffers(pCodecCtx);} |
stepFrame方法
解码视频得到帧
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
-(BOOL)stepFrame { // AVPacket packet; int frameFinished=0; 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); } } return frameFinished!=0;} |
currentImage方法
获取当前的UIImage对象,以呈现当前播放的画面
|
1
2
3
4
5
|
-(UIImage *)currentImage { if (!pFrame->data[0]) return nil; [self convertFrameToRGB]; return [self imageFromAVPicture:picture width:outputWidth height:outputHeight];} |
convertFrameToRGB
转换音视频帧到RGB
|
1
2
3
4
5
|
-(void)convertFrameToRGB { sws_scale (img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, picture.data, picture.linesize); } |
(UIImage *)imageFromAVPicture:(AVPicture)pict width:(int)width height:(int)height方法
把AVPicture转换成UIImage把音视频画面显示出来
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
-(UIImage *)imageFromAVPicture:(AVPicture)pict width:(int)width height:(int)height { CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, pict.data[0], pict.linesize[0]*height,kCFAllocatorNull); CGDataProviderRef provider = CGDataProviderCreateWithCFData(data); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGImageRef cgImage = CGImageCreate(width, height, 8, 24, pict.linesize[0], colorSpace, bitmapInfo, provider, NULL, NO, kCGRenderingIntentDefault); CGColorSpaceRelease(colorSpace); UIImage *image = [UIImage imageWithCGImage:cgImage]; CGImageRelease(cgImage); CGDataProviderRelease(provider); CFRelease(data); return image;} |
Reference
ElevenPlayer: 这是我用ffmpeg写的iOS万能播放器。
FFMPEG结构体分析-系列文章:包括AVFrame、AVFormatContext、AVCodecContext、AVIOContext、AVCodec、AVStream、AVPacket
作者 :coderyi
ffmpeg在iOS的使用 - iFrameExtractor源码解析的更多相关文章
- iOS网络请求-AFNetworking源码解析
趁着端午节日,自己没有什么过多的安排,准备花4-5天左右,针对网络请求源码AFNetworking和YTKNetwork进行解析以及这两年多iOS实际开发经验(其实YTKNetwork也是对AFNet ...
- iOS开发之Alamofire源码解析
今天博客中的Alamofire源码的版本是以3.4版本为例.上篇博客系统的对NSURLSession相关的东西进行了详细的解析,详情请看<详解NSURLSession>,为了就是给本篇博客 ...
- iOS开发之Alamofire源码解析前奏--NSURLSession全家桶
今天博客的主题不是Alamofire, 而是iOS网络编程中经常使用的NSURLSession.如果你想看权威的NSURLSession的东西,那么就得去苹果官方的开发中心去看了,虽然是英文的,但是结 ...
- iOS高仿app源码:纯代码打造高仿优质《内涵段子》
iOS高仿app源码:纯代码打造高仿优质<内涵段子>收藏下来 字数1950 阅读4999 评论173 喜欢133 Github 地址 https://github.com/Charlesy ...
- 比较不错的一个ios找茬游戏源码
找茬游戏源码 ,这个是一款非常不错的ios找茬游戏源码,该游戏的兼容性非常好的,并且还可以支持ipad和iphone,UI界面设计得也很漂亮,游戏源码真的是一款非常完美,而且又很完整的一款休闲类的游戏 ...
- ios水果风暴游戏源码下载
游戏源码是从那个IOS教程网IOS.662p.com分享给大家的. 这是一款ios水果风暴游戏源码下载,介绍给大家一下,喜欢的朋友可以下载学习一下吧.应用介绍:这是一个以获得高分和挑战更高难度为目的的 ...
- iOS Socket 整理以及CocoaAsyncSocket、SRWebSocket源码解析(一)
写在准备动手的时候: Socket通讯在iOS中也是很常见,自己最近也一直在学习Telegram这个开源项目,Telegram就是在Socket的基础上做的即时通讯,这个相信了解这个开源项目的也都知道 ...
- iOS开发之Masonry框架源码解析
Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁.Masonry简化了NSLayoutConstraint的使用方式,让 ...
- ios水果风暴游戏源码项目下载
这是一款ios水果风暴游戏源码下载,介绍给大家一下,喜欢的朋友可以下载学习一下吧.应用介绍:这是一个以获得高分和挑战更高难度为目的的游戏.游戏中有九种不同的卡通水果,您可以交换屏幕中两个相邻水果的位置 ...
随机推荐
- Django--csrf跨站请求伪造、Auth认证模块
form表单中使用跨站请求伪造 { % csrf_token % } 会动态生成一个input框,内部的value是随机刷新的 如果不想校验csrf from django.views.decorat ...
- HTML入门:Tag学习
即使 <br> 在所有浏览器中的显示都没有问题,使用 <br /> 也是更长远的保障. 标签 描述 <html> 定义 HTML 文档. <body> ...
- HBase 三维模型解析
总结下一直想写hbase的实践经验,在用hbase的过程中,我们都知道,rowkey设计的好坏,是我们能最大发挥hbase的架构优势,也是我们是否正确理解hbase的一个关键点.闲话少说,进入正题. ...
- Codeforces 142B(二分染色、搜索)
要点 会发现本质上棋盘分成了若干个独立集,本集合内的点放不放棋子并不影响其他集合内的 集合的划分方式就是满棋盘跳马步直到全跳过了,然后每个集合就分成两队,我们选人多的那队放棋子,人少那队当禁区 con ...
- 一句话介绍python线程、进程和协程
一.进程: Python的os模块封装了常见的系统调用,其中就包括fork.而fork是linux常用的产生子进程的方法,简言之是一个调用,两个返回. 在python中,以下的两个模块用于进程的使用. ...
- 微信小程序示例
http://www.cnblogs.com/ihardcoder/p/6097941.html http://www.wxapp-union.com/ http://blog.csdn.net/li ...
- 猜数字游戏_Python
预先设置数字变量 age_of_test = 25 #这里设置为25,也可随意 guess_age = int (input("guess age:")) if guess_age ...
- 【洛谷P2722 USACO】 总分 01背包模板
P2722 总分 Score Inflation 题目背景 学生在我们USACO的竞赛中的得分越多我们越高兴. 我们试着设计我们的竞赛以便人们能尽可能的多得分,这需要你的帮助 题目描述 我们可以从几个 ...
- Laravel 安装mysql、表增加模拟数据、生成控制器
参考中文网教程: 安装mysql.表增加模拟数据 http://www.golaravel.com/post/2016-ban-laravel-xi-lie-ru-men-jiao-cheng-yi/ ...
- Python多线程在爬虫中的应用
题记:作为测试工程师经常需要解决测试数据来源的问题,解决思路无非是三种:(1)直接从生产环境拷贝真实数据 (2)从互联网上爬取数据 (3)自己用脚本或者工具造数据.前段时间,为了获取更多的测试数据,笔 ...