iOS: FFmpeg视频播放器

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

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

一 本播放器原理:

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

二 如何集成 ffmpeg

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

  • 导入依赖库

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

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

三 开始编写代码

  • 新建一个OC文件
//
//  SJMoiveObject.h
//  SJLiveVideo
//
//  Created by king on 16/6/16.
//  Copyright © 2016年 king. All rights reserved.
//

#import "Common.h"
#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;
/* 切换资源 */
- (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"

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

@implementation SJMoiveObject
{
    AVFormatContext     *SJFormatCtx;
    AVCodecContext      *SJCodecCtx;
    AVFrame             *SJFrame;
    AVStream            *stream;
    AVPacket            packet;
    AVPicture           picture;
    int                 videoStream;
    double              fps;
    BOOL                isReleaseResources;
}

#pragma mark ------------------------------------
#pragma mark  初始化
- (instancetype)initWithVideo:(NSString *)moviePath {

    if (!(self=[super init])) return nil;
    if ([self initializeResources:[moviePath UTF8String]]) {
        self.cruutenPath = [moviePath copy];
        return self;
    } else {
        return nil;
    }
}
- (BOOL)initializeResources:(const char *)filePath {

    isReleaseResources = NO;
    AVCodec *pCodec;
    // 注册所有解码器
    avcodec_register_all();
    av_register_all();
    avformat_network_init();
    // 打开视频文件
    if (avformat_open_input(&SJFormatCtx, filePath, NULL, NULL) != 0) {
        SJLog(@"打开文件失败");
        goto initError;
    }
    // 检查数据流
    if (avformat_find_stream_info(SJFormatCtx, NULL) < 0) {
        SJLog(@"检查数据流失败");
        goto initError;
    }
    // 根据数据流,找到第一个视频流
    if ((videoStream =  av_find_best_stream(SJFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0)) < 0) {
        SJLog(@"没有找到第一个视频流");
        goto initError;
    }
    // 获取视频流的编解码上下文的指针
    stream      = SJFormatCtx->streams[videoStream];
    SJCodecCtx  = stream->codec;
#if DEBUG
    // 打印视频流的详细信息
    av_dump_format(SJFormatCtx, videoStream, filePath, 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) {
        SJLog(@"没有找到解码器");
        goto initError;
    }
    // 打开解码器
    if(avcodec_open2(SJCodecCtx, pCodec, NULL) < 0) {
        SJLog(@"打开解码器失败");
        goto initError;
    }
    // 分配视频帧
    SJFrame = av_frame_alloc();
    _outputWidth = SJCodecCtx->width;
    _outputHeight = SJCodecCtx->height;
    return YES;
initError:
    return NO;
}
- (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);
        }
    }
    if (frameFinished == 0 && isReleaseResources == NO) {
        [self releaseResources];
    }
    return frameFinished != 0;
}

- (void)replaceTheResources:(NSString *)moviePath {
    if (!isReleaseResources) {
        [self releaseResources];
    }
    self.cruutenPath = [moviePath copy];
    [self initializeResources:[moviePath UTF8String]];
}
- (void)redialPaly {
    [self initializeResources:[self.cruutenPath UTF8String]];
}
#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)releaseResources {
    SJLog(@"释放资源");
    SJLogFunc
    isReleaseResources = YES;
    // 释放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);
    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 <AVFoundation/AVFoundation.h>
#import "SJAudioObject.h"
#import "SJAudioQueuPlay.h"
#define LERP(A,B,C) ((A)*(1.0-C)+(B)*C)

@interface ViewController ()
@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) float lastFrameTime;
@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 = 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])
    int tns, 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;
    float frameTime = 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 {

    int tns, 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
  • 运程序 ,点击播放

时间: 2024-11-06 20:50:58

iOS: FFmpeg视频播放器的相关文章

ios媒体视频播放器应用项目

源码WSY_XMHelper,基于ReactiveCocoa和MVVM架构的流媒体视频播放器应用,此项目为一个完整项目.基于MVVM架构, 集成流媒体视频下载及播放,支持下拉刷新,上拉加载等.如果你想学习MVVM,此项目为一个很好的例子. 项目源码下载:http://code.662p.com/view/9891.html<ignore_js_op> <ignore_js_op> 详细说明:http://ios.662p.com/thread-2344-1-1.html

iOS媒体视频播放器应用源码

源码WSY_XMHelper,基于ReactiveCocoa和MVVM架构的流媒体视频播放器应用,此项目为一个完整项目.基于MVVM架构, 集成流媒体视频下载及播放,支持下拉刷新,上拉加载等.如果你想学习MVVM,此项目为一个很好的例子. 源码下载: http://code.662p.com/view/9891.html 2015-1-7 15:12 上传 详细说明:http://ios.662p.com/thread-2344-1-1.html

ios系统视频播放器MPMoviePlayerController应用遇到的坑

最近在做视频播放时应用系统MPMoviePlayerController播放器播放视频,发现点击快进快退会出现黑屏现象,并且点击完成按钮也不会返回,代码以及界面如下所示: NSURL *url=[self getNetworkUrl]; _moviePlayer=[[MPMoviePlayerController alloc]initWithContentURL:url]; _moviePlayer.view.frame=CGRectMake(0, 0, kScreenWidth, kScree

ios avplayer 视频播放器

#import <UIKit/UIKit.h> @interface XGGesTuresView : UIView //左下角播放按钮 @property (nonatomic ,strong)UIButton                * leftplayerbutton; //最小时间 @property (nonatomic ,strong)UILabel                 * mintimelable; //滑动条 @property (nonatomic ,str

100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)【转】

转自:http://blog.csdn.net/leixiaohua1020/article/details/8652605 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 简介 流程图 simplest_ffmpeg_player标准版代码 simplest_ffmpeg_player_suSU版代码 结果 FFMPEG相关学习资料 补充问题 ===================================================== 最简单的基于FFmp

最简单的基于FFMPEG+SDL的视频播放器 ver2 (採用SDL2.0)

===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x) 最简单的基于FFMPEG+SDL的视频播放器 ver2 (採用SDL2.0) 最简单的基于FFmpeg的解码器-纯净版(不包括libavformat) 最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器 最简单的基于FFMPEG的Hellowor

最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)

简介 之前做过一个FFMPEG+SDL的简单播放器:<100行代码实现最简单的基于FFMPEG+SDL的视频播放器>.该播放器采用SDL1.2显示视频.最近有不少人反映SDL已经升级到2.0版本了,甚至官网的Wiki上都只有SDL2.0的文档了,因此下载了SDL 2.0 并且进行了简单的研究.随后对此前的播放器进行了修改,将SDL1.2换成了SDL2.0. 注:<100行代码实现最简单的基于FFMPEG+SDL的视频播放器>文章中提到的很多知识这里不再重复.本文重点记录SDL1.2

最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器

本文补充记录<最简单的基于FFMPEG+SDL的视频播放器>中的两个例子:FFmpeg视频解码器和SDL像素数据播放器.这两个部分是从视频播放器中拆分出来的两个例子.FFmpeg视频解码器实现了视频数据到YUV数据的解码,而SDL像素数据播放器实现了YUV数据的显示.简而言之,原先的FFmpeg+SDL视频播放器实现了: 视频数据->YUV->显示器 FFmpeg视频解码器实现了: 视频数据->YUV SDL像素数据播放器实现了: YUV->显示器 FFmpeg视频解码

用JavaCV改写“100行代码实现最简单的基于FFMPEG+SDL的视频播放器 ”

FFMPEG的文档少,JavaCV的文档就更少了.从网上找到这篇100行代码实现最简单的基于FFMPEG+SDL的视频播放器.地址是http://blog.csdn.net/leixiaohua1020/article/details/8652605. 用JavaCV重新实现并使用opencv_highgui进行显示. 1 import com.googlecode.javacpp.IntPointer; 2 import com.googlecode.javacpp.Pointer; 3 im