FFmpeg視頻解碼播放

一 原理:

  • 通過ffmpeg對視頻進行解碼,解碼出每一幀圖片,然后根據一定時間播放每一幀圖

二 如何集成 ffmpeg

  • 下載腳本 ffmpeg腳本
  • 根據上面鏈接的 README 進行編譯
  • 集成到項目,新建工程,將編譯好的靜態(tài)庫以及頭文件導入工程


  • 導入依賴庫


  • 設置頭文件路徑,路徑一定要對,不然胡找不到頭文件


  • 先 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
  • 實現(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;
   }
   // 檢查數據流
   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];
 // 創(chuàng)建定時器
   [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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

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

友情鏈接更多精彩內容