FFmpeg的使用

现状:现在视频直播非常的火,所以在视频直播开发中,使用的对视频进行遍解码的框架显得尤为重要了,其实,这种框架蛮多的,这次主要介绍一下FFmpeg视频播放器的集成和使用,FFmpeg是视频编解码的利器。

介绍:视频播放过程

首先简单介绍以下视频文件的相关知识。我们平时看到的视频文件有许多格式,比如 avi, mkv, rmvb, mov, mp4等等,这些被称为容器Container), 不同的容器格式规定了其中音视频数据的组织方式(也包括其他数据,比如字幕等)。容器中一般会封装有视频和音频轨,也称为视频流(stream)和音频 流,播放视频文件的第一步就是根据视频文件的格式,解析(demux)出其中封装的视频流、音频流以及字幕(如果有的话),解析的数据读到包 (packet)中,每个包里保存的是视频帧(frame)或音频帧,然后分别对视频帧和音频帧调用相应的解码器(decoder)进行解码,比如使用 H.264编码的视频和MP3编码的音频,会相应的调用H.264解码器和MP3解码器,解码之后得到的就是原始的图像(YUV or RGB)和声音(PCM)数据,然后根据同步好的时间将图像显示到屏幕上,将声音输出到声卡,最终就是我们看到的视频。

FFmpeg的API就是根据这个过程设计的,因此使用FFmpeg来处理视频文件的方法非常直观简单。下面就一步一步介绍从视频文件中解码出图片的过程。

属性:声明变量

AVFormatContext:保存需要读入的文件的格式信息,比如流的个数以及流数据等

AVCodecCotext:保存了相应流的详细编码信息,比如视频的宽、高,编码类型等。

pCodec:真正的编解码器,其中有编解码需要调用的函数

AVFrame:用于保存数据帧的数据结构,这里的两个帧分别是保存颜色转换前后的两帧图像

AVPacket:解析文件时会将音/视频帧读入到packet中

一 本播放器原理:

通过ffmpeg对视频进行解码,解码出每一帧图片,然后根据一定时间播放每一帧图

二 如何集成 ffmpeg

下载脚本ffmpeg脚本

根据上面链接的 README 进行编译

大致步骤:

1. 下载脚本:https://github.com/kewlbear/FFmpeg-iOS-build-script

2. 解压,找到文件 build-ffmpeg.sh

3. 进入终端,执行服本文件:./build-ffmpeg.sh, 由于本人没有事先安装Yasm,执行脚本文件会出错,提示Homebrew not found,Trying 头install.....如图:

根据提示,按下enter键进行安装并编译静态库FFmpeg,如下图:

这是编译后的静态库,截图如下:

集成到项目,新建工程,将编译好的静态库以及头文件导入工程(demo)

导入依赖库

设置头文件路径,路径一定要对,不然胡找不到头文件

我设置路径如下图:

先 command + B 编译一下,确保能编译成功

三 开始编写代码

新建一个OC文件

////SJMoiveObject.h//SJLiveVideo////Created by king on 16/6/16.//Copyright © 2016年 king. All rights reserved.//#import"Common.h"#import#import"NSString+Extions.h"#include#include#include@interfaceSJMoiveObject : NSObject/*解码后的UIImage*/@property (nonatomic, strong,readonly) UIImage *currentImage;/*视频的frame高度*/@property (nonatomic, assign,readonly)intsourceWidth, sourceHeight;/*输出图像大小。默认设置为源大小。*/@property (nonatomic,assign)intoutputWidth, outputHeight;/*视频的长度,秒为单位*/@property (nonatomic, assign,readonly)doubleduration;/*视频的当前秒数*/@property (nonatomic, assign,readonly)doublecurrentTime;/*视频的帧率*/@property (nonatomic, assign,readonly)doublefps;/*视频路径。*/- (instancetype)initWithVideo:(NSString *)moviePath;/*切换资源*/- (void)replaceTheResources:(NSString *)moviePath;/*重拨*/- (void)redialPaly;/*从视频流中读取下一帧。返回假,如果没有帧读取(视频)。*/-(BOOL)stepFrame;/*寻求最近的关键帧在指定的时间*/- (void)seekTime:(double)seconds;@end开始实现API////SJMoiveObject.m//SJLiveVideo////Created by king on 16/6/16.//Copyright © 2016年 king. All rights reserved.//#import"SJMoiveObject.h"@interfaceSJMoiveObject ()

@property (nonatomic, copy) NSString*cruutenPath;@end@implementationSJMoiveObject

{

AVFormatContext*SJFormatCtx;

AVCodecContext*SJCodecCtx;

AVFrame*SJFrame;

AVStream*stream;

AVPacket            packet;

AVPicture          picture;intvideoStream;doublefps;

BOOL                isReleaseResources;

}#pragmamark ------------------------------------#pragmamark  初始化- (instancetype)initWithVideo:(NSString *)moviePath {if(!(self=[super init]))returnnil;if([self initializeResources:[moviePath UTF8String]]) {

self.cruutenPath=[moviePath copy];returnself;

}else{returnnil;

}

}- (BOOL)initializeResources:(constchar*)filePath {

isReleaseResources=NO;

AVCodec*pCodec;//注册所有解码器avcodec_register_all();

av_register_all();

avformat_network_init();//打开视频文件if(avformat_open_input(&SJFormatCtx, filePath, NULL, NULL) !=0) {

SJLog(@"打开文件失败");gotoinitError;

}//检查数据流if(avformat_find_stream_info(SJFormatCtx, NULL) <0) {

SJLog(@"检查数据流失败");gotoinitError;

}//根据数据流,找到第一个视频流if((videoStream =  av_find_best_stream(SJFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec,0)) <0) {

SJLog(@"没有找到第一个视频流");gotoinitError;

}//获取视频流的编解码上下文的指针stream      = SJFormatCtx->streams[videoStream];

SJCodecCtx= stream->codec;#ifDEBUG//打印视频流的详细信息av_dump_format(SJFormatCtx, videoStream, filePath,0);#endifif(stream->avg_frame_rate.den && stream->avg_frame_rate.num) {

fps= av_q2d(stream->avg_frame_rate);

}else{ fps =30; }//查找解码器pCodec = avcodec_find_decoder(SJCodecCtx->codec_id);if(pCodec ==NULL) {

SJLog(@"没有找到解码器");gotoinitError;

}//打开解码器if(avcodec_open2(SJCodecCtx, pCodec, NULL) <0) {

SJLog(@"打开解码器失败");gotoinitError;

}//分配视频帧SJFrame =av_frame_alloc();

_outputWidth= SJCodecCtx->width;

_outputHeight= SJCodecCtx->height;returnYES;

initError:returnNO;

}- (void)seekTime:(double)seconds {

AVRational timeBase= SJFormatCtx->streams[videoStream]->time_base;

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

avformat_seek_file(SJFormatCtx,

videoStream,0,

targetFrame,

targetFrame,

AVSEEK_FLAG_FRAME);

avcodec_flush_buffers(SJCodecCtx);

}-(BOOL)stepFrame {intframeFinished =0;while(!frameFinished && av_read_frame(SJFormatCtx, &packet) >=0) {if(packet.stream_index ==videoStream) {

avcodec_decode_video2(SJCodecCtx,

SJFrame,&frameFinished,&packet);

}

}if(frameFinished ==0&& isReleaseResources ==NO) {

[self releaseResources];

}returnframeFinished !=0;

}- (void)replaceTheResources:(NSString *)moviePath {if(!isReleaseResources) {

[self releaseResources];

}

self.cruutenPath=[moviePath copy];

[self initializeResources:[moviePath UTF8String]];

}- (void)redialPaly {

[self initializeResources:[self.cruutenPath UTF8String]];

}#pragmamark ------------------------------------#pragmamark  重写属性访问方法-(void)setOutputWidth:(int)newValue {if(_outputWidth == newValue)return;

_outputWidth=newValue;

}-(void)setOutputHeight:(int)newValue {if(_outputHeight == newValue)return;

_outputHeight=newValue;

}-(UIImage *)currentImage {if(!SJFrame->data[0])returnnil;return[self imageFromAVPicture];

}-(double)duration {return(double)SJFormatCtx->duration /AV_TIME_BASE;

}- (double)currentTime {

AVRational timeBase= SJFormatCtx->streams[videoStream]->time_base;returnpacket.pts * (double)timeBase.num /timeBase.den;

}- (int)sourceWidth {returnSJCodecCtx->width;

}- (int)sourceHeight {returnSJCodecCtx->height;

}- (double)fps {returnfps;

}#pragmamark --------------------------#pragmamark - 内部方法- (UIImage *)imageFromAVPicture

{

avpicture_free(&picture);

avpicture_alloc(&picture, AV_PIX_FMT_RGB24, _outputWidth, _outputHeight);structSwsContext * imgConvertCtx = sws_getContext(SJFrame->width,

SJFrame->height,

AV_PIX_FMT_YUV420P,

_outputWidth,

_outputHeight,

AV_PIX_FMT_RGB24,

SWS_FAST_BILINEAR,

NULL,

NULL,

NULL);if(imgConvertCtx == nil)returnnil;

sws_scale(imgConvertCtx,

SJFrame->data,

SJFrame->linesize,0,

SJFrame->height,

picture.data,

picture.linesize);

sws_freeContext(imgConvertCtx);

CGBitmapInfo bitmapInfo=kCGBitmapByteOrderDefault;

CFDataRef data=CFDataCreate(kCFAllocatorDefault,

picture.data[0],

picture.linesize[0] *_outputHeight);

CGDataProviderRef provider=CGDataProviderCreateWithCFData(data);

CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();

CGImageRef cgImage=CGImageCreate(_outputWidth,

_outputHeight,8,24,

picture.linesize[0],

colorSpace,

bitmapInfo,

provider,

NULL,

NO,

kCGRenderingIntentDefault);

UIImage*image =[UIImage imageWithCGImage:cgImage];

CGImageRelease(cgImage);

CGColorSpaceRelease(colorSpace);

CGDataProviderRelease(provider);

CFRelease(data);returnimage;

}#pragmamark --------------------------#pragmamark - 释放资源- (void)releaseResources {

SJLog(@"释放资源");

SJLogFunc

isReleaseResources=YES;//释放RGBavpicture_free(&picture);//释放frameav_packet_unref(&packet);//释放YUV frameav_free(SJFrame);//关闭解码器if(SJCodecCtx) avcodec_close(SJCodecCtx);//关闭文件if(SJFormatCtx) avformat_close_input(&SJFormatCtx);

avformat_network_deinit();

}@end

为了方便,在SB 拖一个 UIImageView 控件 和按钮  并连好线

////ViewController.m//SJLiveVideo////Created by king on 16/6/14.//Copyright © 2016年 king. All rights reserved.//#import"ViewController.h"#import"SJMoiveObject.h"#import#import"SJAudioObject.h"#import"SJAudioQueuPlay.h"#defineLERP(A,B,C) ((A)*(1.0-C)+(B)*C)@interfaceViewController ()

@property (weak, nonatomic) IBOutlet UIImageView*ImageView;

@property (weak, nonatomic) IBOutlet UILabel*fps;

@property (weak, nonatomic) IBOutlet UIButton*playBtn;

@property (weak, nonatomic) IBOutlet UIButton*TimerBtn;

@property (weak, nonatomic) IBOutlet UILabel*TimerLabel;

@property (nonatomic, strong) SJMoiveObject*video;

@property (nonatomic, strong) SJAudioObject*audio;

@property (nonatomic, strong) SJAudioQueuPlay*audioPlay;

@property (nonatomic, assign)floatlastFrameTime;@end@implementationViewController@synthesizeImageView, fps, playBtn, video;- (void)viewDidLoad {

[super viewDidLoad];

self.video= [[SJMoiveObject alloc] initWithVideo:[NSString bundlePath:@"Dalshabet.mp4"]];//self.video = [[SJMoiveObject alloc] initWithVideo:@"/Users/king/Desktop/Stellar.mp4"];//self.video = [[SJMoiveObject alloc] initWithVideo:@"/Users/king/Downloads/Worth it - Fifth Harmony ft.Kid Ink - May J Lee Choreography.mp4"];//self.video = [[SJMoiveObject alloc] initWithVideo:@"/Users/king/Downloads/4K.mp4"];//self.video = [[SJMoiveObject alloc] initWithVideo:@"http://wvideo.spriteapp.cn/video/2016/0328/56f8ec01d9bfe_wpd.mp4"];//video.outputWidth = 800;//video.outputHeight = 600;self.audio = [[SJAudioObject alloc] initWithVideo:@"/Users/king/Desktop/Stellar.mp4"];

NSLog(@"视频总时长>>>video duration: %f",video.duration);

NSLog(@"源尺寸>>>video size: %d x %d", video.sourceWidth, video.sourceHeight);

NSLog(@"输出尺寸>>>video size: %d x %d", video.outputWidth, video.outputHeight);////[self.audio seekTime:0.0];//SJLog(@"%f", [self.audio duration])//AVPacket *packet = [self.audio readPacket];//SJLog(@"%ld", [self.audio decode])inttns, thh, tmm, tss;

tns=video.duration;

thh= tns /3600;

tmm= (tns %3600) /60;

tss= tns %60;//NSLog(@"fps --> %.2f", video.fps);////        [ImageView setTransform:CGAffineTransformMakeRotation(M_PI)];//NSLog(@"%02d:%02d:%02d",thh,tmm,tss);}- (IBAction)PlayClick:(UIButton *)sender {

[playBtn setEnabled:NO];

_lastFrameTime= -1;//seek to 0.0 seconds[video seekTime:0.0];

[NSTimer scheduledTimerWithTimeInterval:1/video.fps

target:self

selector:@selector(displayNextFrame:)

userInfo:nil

repeats:YES];

}- (IBAction)TimerCilick:(id)sender {//NSLog(@"current time: %f s",video.currentTime);//[video seekTime:150.0];//[video replaceTheResources:@"/Users/king/Desktop/Stellar.mp4"];if(playBtn.enabled) {

[video redialPaly];

[self PlayClick:playBtn];

}

}-(void)displayNextFrame:(NSTimer *)timer {

NSTimeInterval startTime=[NSDate timeIntervalSinceReferenceDate];//self.TimerLabel.text = [NSString stringWithFormat:@"%f s",video.currentTime];self.TimerLabel.text  =[self dealTime:video.currentTime];if(![video stepFrame]) {

[timer invalidate];

[playBtn setEnabled:YES];return;

}

ImageView.image=video.currentImage;floatframeTime =1.0/ ([NSDate timeIntervalSinceReferenceDate] -startTime);if(_lastFrameTime <0) {

_lastFrameTime=frameTime;

}else{

_lastFrameTime= LERP(frameTime, _lastFrameTime,0.8);

}

[fps setText:[NSString stringWithFormat:@"fps %.0f",_lastFrameTime]];

}- (NSString *)dealTime:(double)time {inttns, thh, tmm, tss;

tns=time;

thh= tns /3600;

tmm= (tns %3600) /60;

tss= tns %60;//[ImageView setTransform:CGAffineTransformMakeRotation(M_PI)];return[NSString stringWithFormat:@"%02d:%02d:%02d",thh,tmm,tss];

}@end

运程序 ,点击播放

我的测试结果如下:

原文地址:http://bbs.520it.com/forum.php?mod=viewthread&tid=707&page=1&extra=#pid3821

我集成后的demo:github源码下载:https://github.com/xiayuanquan/FFmpegDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容