一 原理:
- 通過ffmpeg對視頻進(jìn)行解碼,解碼出每一幀圖片,然后根據(jù)一定時(shí)間播放每一幀圖
二 如何集成 ffmpeg
- 下載腳本 ffmpeg腳本
- 根據(jù)上面鏈接的 README 進(jìn)行編譯
-
集成到項(xiàng)目,新建工程,將編譯好的靜態(tài)庫以及頭文件導(dǎo)入工程
-
導(dǎo)入依賴庫
-
設(shè)置頭文件路徑,路徑一定要對,不然胡找不到頭文件
- 先 command + B 編譯一下,確保能編譯成功
三 開始編寫代碼
- 新建一個(gè)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;
?
/* 輸出圖像大小。默認(rèn)設(shè)置為源大小衷掷。 */
@property (nonatomic,assign) int outputWidth, outputHeight;
?
/* 視頻的長度枝笨,秒為單位 */
@property (nonatomic, assign, readonly) double duration;
?
/* 視頻的當(dāng)前秒數(shù) */
@property (nonatomic, assign, readonly) double currentTime;
?
/* 視頻的幀率 */
@property (nonatomic, assign, readonly) double fps;
?
/* 視頻路徑谆扎。 */
- (instancetype)initWithVideo:(NSString *)moviePath;
?
/* 從視頻流中讀取下一幀欢嘿。返回假彰阴,如果沒有幀讀扰陆巍(視頻)断傲。 */
- (BOOL)stepFrame;
?
/* 尋求最近的關(guān)鍵幀在指定的時(shí)間 */
- (void)seekTime:(double)seconds;
?
@end
- 實(shí)現(xiàn)文件
//
// 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;
}
// 檢查數(shù)據(jù)流
if (avformat_find_stream_info(SJFormatCtx, NULL) < 0) {
av_log(NULL, AV_LOG_ERROR, "檢查數(shù)據(jù)流失敗\n");
goto initError;
}
// 根據(jù)數(shù)據(jù)流,找到第一個(gè)視頻流
if ((videoStream = av_find_best_stream(SJFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0)) < 0) {
av_log(NULL, AV_LOG_ERROR, "沒有找到第一個(gè)視頻流\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 - 內(nèi)部方法
- (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);
// 關(guān)閉解碼器
if (SJCodecCtx) avcodec_close(SJCodecCtx);
// 關(guān)閉文件
if (SJFormatCtx) avformat_close_input(&SJFormatCtx);
?
}
@end
- 為了方便,在SB 拖一個(gè) 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"];
// 設(shè)置輸出圖像尺寸 默認(rèn)為原尺寸
// video.outputWidth = 1920;
// video.outputHeight = 1080;
NSLog(@"視頻總時(shí)長>>>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];
// 從指定時(shí)間開始播放
[video seekTime:0.0];
// 創(chuàng)建定時(shí)器
[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]) {
// 如果沒有下一幀可讀取 應(yīng)該講定時(shí)器 釋放
[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
四 運(yùn)程序 ,點(diǎn)擊播放
我的博客
我的微博
百度云下載 密碼: aqnp