FFmpeg视频解码播放

一 原理:

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

二 如何集成 ffmpeg

  • 下载脚本 ffmpeg脚本
  • 根据上面链接的 README 进行编译
  • 集成到项目,新建工程,将编译好的静态库以及头文件导入工程


  • 导入依赖库


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


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

三 开始编写代码

  • 新建一个OC文件
//

// SJMoiveObject.h
// SJLiveVideo
//
// Created by king on 16/6/16.
// Copyright © 2016年 king. All rights reserved.
//
​
#import <UIKit/UIKit.h>
#import "NSString+Extions.h"
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale//swscale.h>
​
@interface SJMoiveObject : NSObject
​
/* 解码后的UIImage */
@property (nonatomic, strong, readonly) UIImage *currentImage;
​
/* 视频的frame高度 */
@property (nonatomic, assign, readonly) int sourceWidth, sourceHeight;
​
/* 输出图像大小。默认设置为源大小。 */
@property (nonatomic,assign) int outputWidth, outputHeight;
​
/* 视频的长度,秒为单位 */
@property (nonatomic, assign, readonly) double duration;
​
/* 视频的当前秒数 */
@property (nonatomic, assign, readonly) double currentTime;
​
/* 视频的帧率 */
@property (nonatomic, assign, readonly) double fps;
​
/* 视频路径。 */
- (instancetype)initWithVideo:(NSString *)moviePath;
​
/* 从视频流中读取下一帧。返回假,如果没有帧读取(视频)。 */
- (BOOL)stepFrame;
​
/* 寻求最近的关键帧在指定的时间 */
- (void)seekTime:(double)seconds;
​
@end
  • 实现文件
//

// SJMoiveObject.m
// SJLiveVideo
//
// Created by king on 16/6/16.
// Copyright © 2016年 king. All rights reserved.
//
​
#import "SJMoiveObject.h"
​
@implementation SJMoiveObject
{
   AVFormatContext     *SJFormatCtx;
   AVCodecContext     *SJCodecCtx;
   AVFrame             *SJFrame;
   AVStream           *stream;
   AVPacket           packet;
   AVPicture           picture;
   int                 videoStream;
   double             fps;
}
​
#pragma mark ------------------------------------
#pragma mark 初始化
- (instancetype)initWithVideo:(NSString *)moviePath {
   
   if (!(self=[super init])) return nil;
   AVCodec *pCodec;
   // 注册所有解码器
   avcodec_register_all();
   av_register_all();
   avformat_network_init();
   // 打开视频文件
   if (avformat_open_input(&SJFormatCtx, [moviePath UTF8String], NULL, NULL) != 0) {
       av_log(NULL, AV_LOG_ERROR, "打开文件失败\n");
       goto initError;
   }
   // 检查数据流
   if (avformat_find_stream_info(SJFormatCtx, NULL) < 0) {
       av_log(NULL, AV_LOG_ERROR, "检查数据流失败\n");
       goto initError;
   }
   // 根据数据流,找到第一个视频流
   if ((videoStream = av_find_best_stream(SJFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0)) < 0) {
       av_log(NULL, AV_LOG_ERROR, "没有找到第一个视频流\n");
       goto initError;
   }
   // 获取视频流的编解码上下文的指针
   stream     = SJFormatCtx->streams[videoStream];
   SJCodecCtx = stream->codec;
#if DEBUG
   av_dump_format(SJFormatCtx, videoStream, [moviePath UTF8String], 0);
#endif
   if(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) {
       av_log(NULL, AV_LOG_ERROR, "没有找到解码器\n");
       goto initError;
   }
   // 打开解码器
   if(avcodec_open2(SJCodecCtx, pCodec, NULL) < 0) {
       av_log(NULL, AV_LOG_ERROR, "打开解码器失败\n");
       goto initError;
   }
   // 分配视频帧
   SJFrame = av_frame_alloc();
   _outputWidth = SJCodecCtx->width;
   _outputHeight = SJCodecCtx->height;
   return self;
initError:
   return nil;
}
​
- (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 {
   int frameFinished = 0;
   while (!frameFinished && av_read_frame(SJFormatCtx, &packet) >= 0) {
       if (packet.stream_index == videoStream) {
           avcodec_decode_video2(SJCodecCtx,
                                 SJFrame,
                                 &frameFinished,
                                 &packet);
       }
   }
   return frameFinished != 0;
}
#pragma mark ------------------------------------
#pragma mark 重写属性访问方法
-(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]) return nil;
   return [self imageFromAVPicture];
}
-(double)duration {
   return (double)SJFormatCtx->duration / AV_TIME_BASE;
}
- (double)currentTime {
   AVRational timeBase = SJFormatCtx->streams[videoStream]->time_base;
   return packet.pts * (double)timeBase.num / timeBase.den;
}
- (int)sourceWidth {
   return SJCodecCtx->width;
}
- (int)sourceHeight {
   return SJCodecCtx->height;
}
- (double)fps {
   return fps;
}
#pragma mark --------------------------
#pragma mark - 内部方法
- (UIImage *)imageFromAVPicture
{
   avpicture_free(&picture);
   avpicture_alloc(&picture, AV_PIX_FMT_RGB24, _outputWidth, _outputHeight);
   struct SwsContext * 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) return nil;
   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);
   
   return image;
}
​
#pragma mark --------------------------
#pragma mark - 释放资源
- (void)dealloc {
   // 释放RGB
   avpicture_free(&picture);
   // 释放frame
   av_packet_unref(&packet);
   // 释放YUV frame
   av_free(SJFrame);
   // 关闭解码器
   if (SJCodecCtx) avcodec_close(SJCodecCtx);
   // 关闭文件
   if (SJFormatCtx) avformat_close_input(&SJFormatCtx);
​
}
@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"
​
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *ImageView;
@property (weak, nonatomic) IBOutlet UIButton *playBtn;
@property (nonatomic, strong) SJMoiveObject *video;
@end
​
@implementation ViewController
​
@synthesize ImageView, 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 = 1920;
//   video.outputHeight = 1080;
   
   
   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);
   
   int tns, thh, tmm, tss;
   tns = video.duration;
   thh = tns / 3600;
   tmm = (tns % 3600) / 60;
   tss = tns % 60;
   
   NSLog(@"fps --> %.2f", video.fps);
   NSLog(@"%02d:%02d:%02d",thh,tmm,tss);
}
​
- (IBAction)PlayClick:(UIButton *)sender {
   
   [playBtn setEnabled:NO];
   // 从指定时间开始播放
   [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);
}
​
-(void)displayNextFrame:(NSTimer *)timer {
   self.TimerLabel.text = [self dealTime:video.currentTime];
   if (![video stepFrame]) {
    // 如果没有下一帧可读取 应该讲定时器 释放
       [timer invalidate];
       [playBtn setEnabled:YES];
       return;
   }
   ImageView.image = video.currentImage;
}
​
- (NSString *)dealTime:(double)time {
   
   int tns, thh, tmm, tss;
   tns = time;
   thh = tns / 3600;
   tmm = (tns % 3600) / 60;
   tss = tns % 60;
   return [NSString stringWithFormat:@"%02d:%02d:%02d",thh,tmm,tss];
}
@end

四 运程序 ,点击播放


我的博客
我的微博
百度云下载 密码: aqnp

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

推荐阅读更多精彩内容

  • 前言 代码github地址https://github.com/ccj659/NDK-FFmpeg-master ...
    Chauncey_Chen阅读 3,821评论 3 31
  • Android FFmpeg视频播放 本文介绍了使用Native Window和FFmpeg在Android平台上...
    JasonXiao阅读 12,198评论 20 55
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,387评论 25 707
  • 自在,由你!幸福就是做自己的想自己的,而不是做自己想对方的。
    启程_8967阅读 156评论 0 0
  • 上帝赐予我们地震、飓风和龙卷风。 他赐予了朝我们头顶喷火的高山,以及吞噬船只的大海。 他赐予我们大自然,而大自然是...
    五侯阅读 245评论 0 1