FFmpeg視頻解碼播放

一 原理:

  • 通過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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市谴古,隨后出現(xiàn)的幾起案子质涛,更是在濱河造成了極大的恐慌悄窃,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹂窖,死亡現(xiàn)場離奇詭異轧抗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瞬测,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門横媚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人月趟,你說我怎么就攤上這事灯蝴。” “怎么了孝宗?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵穷躁,是天一觀的道長。 經(jīng)常有香客問我因妇,道長问潭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任婚被,我火速辦了婚禮狡忙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘址芯。我一直安慰自己灾茁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布谷炸。 她就那樣靜靜地躺著北专,像睡著了一般。 火紅的嫁衣襯著肌膚如雪旬陡。 梳的紋絲不亂的頭發(fā)上拓颓,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機(jī)與錄音季惩,去河邊找鬼录粱。 笑死腻格,一個(gè)胖子當(dāng)著我的面吹牛画拾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播菜职,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼青抛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了酬核?” 一聲冷哼從身側(cè)響起蜜另,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤适室,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后举瑰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捣辆,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年此迅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了汽畴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耸序,死狀恐怖忍些,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坎怪,我是刑警寧澤罢坝,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站搅窿,受9級特大地震影響嘁酿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜男应,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一痹仙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧殉了,春花似錦开仰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至隔箍,卻和暖如春谓娃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜒滩。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工滨达, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俯艰。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓捡遍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親竹握。 傳聞我的和親對象是個(gè)殘疾皇子画株,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 前言 代碼github地址https://github.com/ccj659/NDK-FFmpeg-master ...
    Chauncey_Chen閱讀 3,838評論 3 31
  • Android FFmpeg視頻播放 本文介紹了使用Native Window和FFmpeg在Android平臺上...
    JasonXiao閱讀 12,218評論 20 55
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,167評論 25 707
  • 自在,由你!幸福就是做自己的想自己的谓传,而不是做自己想對方的蜈项。
    啟程_8967閱讀 173評論 0 0
  • 上帝賜予我們地震、颶風(fēng)和龍卷風(fēng)续挟。 他賜予了朝我們頭頂噴火的高山紧卒,以及吞噬船只的大海。 他賜予我們大自然诗祸,而大自然是...
    五侯閱讀 248評論 0 1