ffmpeg的那点小事儿--ffmpeg的导入和视频解码,YUV保存(ffmpeg4.0.2)

一、ffmpeg开发的基本知识了解

       第一点:一个视频播放流程

             通常看到视频格式:mp4、mov、flv、wmv等等…

             称之为:封装格式

             ffmpeg的那点小事儿--ffmpeg的导入和视频解码,YUV保存(ffmpeg4.0.2)_第1张图片

    第二点:视频播放器

两种模式播放器

        第一种:可视化界面播放器(直接用户直观操作->简单易懂)

        e.g:腾讯视频、爱奇艺视频、QQ影音、暴风影音、快播、优酷等等…

        第二种:非可视化界面播放器->命令操作播放器->用户看不懂,使用起来非常麻烦

        e.g:FFmpeg->ffplay(命令)播放器(内置播放器)

               vlc播放器、mplayer播放器

 

        第三点:播放器信息查看工具

                整个视频信息:MediaInfo工具->帮助我们查看视频完整信息

                二进制查看信息:直接查看视频二进制数据(0101010)->UItraEdit

                视频单项信息

                封装格式信息工具->Elecard Format Analyzer

                视频编码信息工具->Elecard Stream Eye

                视频像素信息工具->YUVPlayer

                音频采样数据工具->Adobe Audition

         第四点:音视频->封装格式

1、封装格式:mp4、mov、flv、wmv等等…

2、封装格式作用

视频流+音频流按照格式进行存储在一个文件中

3、MPEG2-TS格式

视频压缩数据格式:MPEG2-TS

特定:数据排版,不包含头文件,数据大小固定(188byte)的TS-Packet

4、FLV格式

优势:由于它形成的文件极小、加载速度极快,使得网络观看视频文件成为可能,它的出现有效地解决了视频文件导入Flash后,使导出的SWF文件体积庞大,不能在网络上很好的使用等问题。

  文件结构:FLV是一个二进制文件,由文件头(FLV header)和很多tag组成。tag又可以分成三类:audio,video,script,分别代表音频流,视频流,脚本流(关键字或者文件信息之类)。

FLV文件=FLV头文件+ tag1+tag内容1 + tag2+tag内容2 + ...+... + tagN+tag内容N。

FLV头文件:(9字节)

1-3个字节: 前3个字节是文件格式标识(FLV 0x46 0x4C 0x56).

第4个字节: 第4个字节是版本(0x01)

第5个字节: 第5个字节的前5个bit是保留的必须是0.

6-9个字节: 第6-9的四个字节还是保留的.其数据为 00000009 .

整个文件头的长度,一般是9(3+1+1+4)

 

第五点:视频编码数据了解一下

1、视频编码作用

将视频像素数据(YUV、RGB)进行压缩成为视频码流,从而降低视频数据量。(减小内存暂用)

2、视频编码格式有哪些

MJPEG

MJPEG(Motion JPEG)压缩技术,主要是基于静态视频压缩发展起来的技术,它的主要特点是基本不考虑视频流中不同帧之间的变化,只单独对某一帧进行压缩。

MJPEG压缩技术可以获取清晰度很高的视频图像,可以动态调整帧率、分辨率。但由于没有考虑到帧间变化,造成大量冗余信息被重复存储,因此单帧视频的占用空间较大,流行的MJPEG技术监控与视频编码最好的也只能做到3K字节/帧,通常要8~20K!

MPEG-1/2

MPEG-1标准主要针对SIF标准分辨率(NTSC制为352X240;PAL制为352X288)的图像进行压缩. 压缩位率主要目标为1.5Mb/s.较MJPEG技术,MPEG1在实时压缩、每帧数据量、处理速度上有显著的提高。但MPEG1也有较多不利地方:存储容量还是过大、清晰度不够高和网络传输困难。

MPEG-2 在MPEG-1基础上进行了扩充和提升,和MPEG-1向下兼容,主要针对存储媒体、数字电视、高清晰等应用领域,分辨率为:低(352x288),中(720x480),次高(1440x1080),高(1920x1080)。MPEG-2视频相对MPEG-1提升了分辨率,满足了用户高清晰的要求,但由于压缩性能没有多少提高,使得存储容量还是太大,也不适合网络传输。

MPEG-4

MPEG-4视频压缩算法相对于MPEG-1/2在低比特率压缩上有着显著提高,在CIF(352*288)或者更高清晰度(768*576)情况下的视频压缩,无论从清晰度还是从存储量上都比MPEG1具有更大的优势,也更适合网络传输。另外MPEG-4可以方便地动态调整帧率、比特率,以降低存储量。

MPEG-4由于系统设计过于复杂,使得MPEG-4难以完全实现并且兼容,很难在视频会议、可视电话等领域实现,这一点有点偏离原来地初衷。另外对于中国企业来说还要面临高昂的专利费问题,规定:

- 每台解码设备需要交给MPEG-LA 0.25美元。

-编码/解码设备还需要按时间交费(4美分/天=1.2美元/月 =14.4美元/年)。

H.264/AVC

视频压缩国际标准主要有由ITU-T制定的H.261、H.262、H.263、H.264和由MPEG制定的MPEG-1、MPEG-2、MPEG-4,其中H.262/MPEG-2和H.264/MPEG-4 AVC由ITU-T与MPEG联合制定。

从简单来说H.264就是一种视频编码技术,与微软的WMV9都属于同一种技术也就是压缩动态图像数据的“编解码器”程序。

3、H.264视频压缩数据格式

非常复杂算法->压缩->占用内存那么少?(例如:帧间预测、帧内预测…)->提高压缩性能

第六点:音频编码数据?

1、音频编码作用?

将音频采样数据(PCM格式)进行压缩成为音频码流,从而降低音频数据量。(减小内存暂用)

2、音频编码飞逝有哪些

AAC、MP3、WMV等等…

3、AAC格式

AAC,全称Advanced Audio Coding,是一种专为声音数据设计的文件压缩格式。与MP3不同,它采用了全新的算法进行编码,更加高效,具有更高的“性价比”。利用AAC格式,可使人感觉声音质量没有明显降低的前提下,更加小巧。苹果ipod、诺基亚手机支持AAC格式的音频文件。

优点:相对于mp3,AAC格式的音质更佳,文件更小。

不足:AAC属于有损压缩的格式,与时下流行的APE、FLAC等无损格式相比音质存在“本质上”的差距。加之,传输速度更快的USB3.0和16G以上大容量MP3正在加速普及,也使得AAC头上“小巧”的光环不复存在。

①提升的压缩率:可以以更小的文件大小获得更高的音质;

②支持多声道:可提供最多48个全音域声道;

③更高的解析度:最高支持96KHz的采样频率;

④提升的解码效率:解码播放所占的资源更少;

 

第七点:视频像素数据

1、作用

保存了屏幕上面每一个像素点的值

2、视频像素数据格式种类?

常见格式:RGB24、RGB32、YUV420P、YUV422P、YUV444P等等…一般最常见:YUV420P

3、视频像素数据文件大小计算?

例如:RGB24高清视频体积?(1个小时时长)

体积:3600 * 25 * 1920 * 1080 * 3 = 559GB(非常大)

假设:帧率25HZ,采样精度8bit,3个字节

4、YUV播放器

人类:对色度不敏感,对亮度敏感

Y表示:亮度

UV表示:色度

第八点:音频采样数据格式

1、作用

保存了音频中的每一个采样点值

2、音频采样数据文件大小计算?

例如:1分钟PCM格式歌曲

体积:60 * 44100 * 2 * 2 = 11MB

分析:60表示时间,44100表示采样率(一般情况下,都是这个采样率,人的耳朵能够分辨的声音),2表示声道数量,2表示采样精度16位 = 2字节

3、音频采样数据查看工具

 

4、PCM格式

存储顺序?

               

第九点:FFmepg应用?

提供了一套比较完整代码->开源免费

核心架构设计思想:(核心 + 插件)设计

重要命令学习

1、ffmpeg.exe(视频压缩->转码来完成)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

作用:用于对视频进行转码

将mp4->mov,mov->mp4,wmv->mp4等等…

命令格式:ffmpeg -i {指定输入文件路径} -b:v {输出视频码率} {输出文件路径}

测试运行:将Test.mov->Test.mp4

时间格式:如何指定

截取视频: ./ffmpeg -i Test.mov -ss 00:00:05 -t 00:00:10 sub.mov

从视频的第5秒开始截取,然后往后10秒结束,这个视频长度为10秒

2、ffplay.exe

 

 

 

 

 

 

 

 

 

 

作用:播放视频

格式:ffplay {文件路径}

例如:./ffplay Test.mov

案例:视频,转为高质量 GIF 动图?

命令:./ffmpeg -ss 00:00:03 -t 3 -i Test.mov -s 640x360 -r “15” dongtu.gif

解释:

1、ffmpeg 是你刚才安装的程序;

2、-ss 00:00:03 表示从第 00 分钟 03 秒开始制作 GIF,如果你想从第 9 秒开始,则输入 -ss 00:00:09,或者 -ss 9,支持小数点,所以也可以输入 -ss 00:00:11.3,或者 -ss 34.6 之类的,如果不加该命令,则从 0 秒开始制作;

3、-t 3 表示把持续 3 秒的视频转换为 GIF,你可以把它改为其他数字,例如 1.5,7 等等,时间越长,GIF 体积越大,如果不加该命令,则把整个视频转为 GIF;

4、-i 表示 invert 的意思吧,转换;

5、Test.mov 就是你要转换的视频,名称最好不要有中文,不要留空格,支持多种视频格式;

6、-s 640x360 是 GIF 的分辨率,视频分辨率可能是 1080p,但你制作的 GIF 可以转为 720p 等,允许自定义,分辨率越高体积越大,如果不加该命令,则保持分辨率不变;

7、-r “15” 表示帧率,网上下载的视频帧率通常为 24,设为 15 效果挺好了,帧率越高体积越大,如果不加该命令,则保持帧率不变;

8、dongtu.gif:就是你要输出的文件,你也可以把它命名为 hello.gif 等等。

二、将ffmpeg导入到工程里面

1.创建一个工程

2.创建ffmpeg导入到工程

ffmpeg的那点小事儿--ffmpeg的导入和视频解码,YUV保存(ffmpeg4.0.2)_第2张图片

ffmpeg的那点小事儿--ffmpeg的导入和视频解码,YUV保存(ffmpeg4.0.2)_第3张图片

ffmpeg的那点小事儿--ffmpeg的导入和视频解码,YUV保存(ffmpeg4.0.2)_第4张图片

添加依赖库,这里的依赖库都是多媒体相关的:CoreMedia.framework, VideoToolbox.framework, AudioToolbox.framework, CoreGraphics.framework, libz.tbd, libiconv.tbd, libbz2.tbd

ffmpeg的那点小事儿--ffmpeg的导入和视频解码,YUV保存(ffmpeg4.0.2)_第5张图片

配置Header Search Paths 路径,这个路径可以将Library Search Paths:将ffmpeg.a库文件lib文件夹路径加进去,只需将lib改为存放头文件的文件夹即可

ffmpeg的那点小事儿--ffmpeg的导入和视频解码,YUV保存(ffmpeg4.0.2)_第6张图片

三、测试ffmpeg的可行性

这里用三个方法去测试

创建一个AVAFFmpegConfigure类

.h文件代码如下


//
//  AVAFFmpegConfigure.h
//  Avalanching_FFmpeg_Project
//
//  Created by Avalanching on 2018/9/12.
//  Copyright © 2018年 Avalanching. All rights reserved.
//

#import 
// 导入头文件
#import 
// 导入封装格式库
#import 
// 工具包
#import 
// 视频像素格式哭
#import 

@interface AVAFFmpegConfigure : NSObject

+ (void)ffmpegConfigurationTest;

+ (void)ffmpegOpenVideoFile:(NSString *)filePath;

+ (void)ffmpegDecodecWithFilePath:(NSString *)filepath;

@end

.m文件代码如下

//
//  AVAFFmpegConfigure.m
//  Avalanching_FFmpeg_Project
//
//  Created by Avalanching on 2018/9/12.
//  Copyright © 2018年 Avalanching. All rights reserved.
//

#import "AVAFFmpegConfigure.h"

@implementation AVAFFmpegConfigure

+ (void)ffmpegConfigurationTest {
    const char *configuration = avcodec_configuration();
    NSLog(@"%s", configuration);
}

+ (void)ffmpegOpenVideoFile:(NSString *)filePath {
    // 第一步注册组建
    // 旧的接口:av_register_all();
    // 这不需要注册组建
    // 4.0.0 API大量更换,让很多初学者很难去学习
    // 详细接口替换可以查看下载的源码目录下的doc/APIChange 这个文件
    // 具体的用法就需要查看方法注释
    
    // 第二步打开封装格式文件
    // param1:封装格式的上下文
    AVFormatContext *formatContext = avformat_alloc_context();
    // param2:视频文件地址
    const char *url = [filePath UTF8String];
    // param3:指定输入的封装格式
    // param4:指定默认配置信息
    
    int result = avformat_open_input(&formatContext, url, NULL, NULL);
    if (result != 0) {
        // 这里是打开失败
        NSLog(@"open false!!");
    } else {
        NSLog(@"open success!!");
    }
}

+ (void)ffmpegDecodecWithFilePath:(NSString *)filepath {
    // 第一步:组册组件
    // av_register_all() 4.0.0之前的接口
    // 新版的API,此处不需要注册组建
    // 例如:编码器、解码器等等…
    
    // 第二步:打开封装格式->打开文件
    // 例如:.mp4、.mov、.wmv文件等等...
    // avformat_open_input();
    AVFormatContext *formatContext = avformat_alloc_context();
    const char *url = [filepath UTF8String];
    int avformat_result = avformat_open_input(&formatContext, url, NULL, NULL);
    
    if (avformat_result != 0) {
        NSLog(@"open false");
        return;
    }
    
    // 第三步:查找视频流
    // 如果是视频解码,那么查找视频流,如果是音频解码,那么就查找音频流
    // avformat_find_stream_info();
    // AVProgram 视频的相关信息
    int avformat_find_stream_result = avformat_find_stream_info(formatContext, NULL);
    if (avformat_find_stream_result < 0) {
        NSLog(@"search video stream is false!!!");
        return;
    }
    
    // 第四步:查找视频解码器
    // 1、查找视频流索引位置
    int avformat_stream_index = 0;
    for (int i = 0; i < formatContext->nb_streams; i++) {
        // 判断流的类型
        // 旧的接口 formatContext->streams[i]->codec->codec_type
        // 4.0.0以后新加入的类型用于替代codec
        // codec -> codecpar
        enum AVMediaType mediatype = formatContext->streams[i]->codecpar->codec_type;
        if (mediatype == AVMEDIA_TYPE_VIDEO) {
            // 视频流
            avformat_stream_index = i;
            break;
        } else if (mediatype == AVMEDIA_TYPE_AUDIO) {
            // 音频流
            
        } else {
            // 其他流
        }
    }
    // 2、根据视频流索引,获取解码器上下文
    // 旧的接口 拿到上下文,formatContext->streams[i]->codec
    // 4.0.0以后新加入的类型用于替代codec
    // codec -> codecpar 此处新接口不需要上下文
    AVCodecParameters *avcodecParameters = formatContext->streams[avformat_stream_index]->codecpar;
    enum AVCodecID codeid = avcodecParameters->codec_id;
    
    // 3、根据解码器上下文,获得解码器ID,然后查找解码器
    // avcodec_find_encoder(enum AVCodecID id) 编码器
    AVCodec *codec = avcodec_find_decoder(codeid);
    
    // 第五步:打开解码器
    // avcodec_open2();
    // 旧接口直接使用codec作为上下文传入
    // formatContext->streams[avformat_stream_index]->codec被遗弃
    // 新接口如下
    AVCodecContext *avCodecContext = avcodec_alloc_context3(NULL);
    if (avCodecContext == NULL) {
        // 创建解码器上下文失败
        NSLog(@"create codecContext is false");
        return;
    }
    // avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *par)
    // 将新的API中的 codecpar 转成 AVCodecContext
    avcodec_parameters_to_context(avCodecContext, avcodecParameters);
    
    int avcodec_open2_result = avcodec_open2(avCodecContext, codec, NULL);
    if (avcodec_open2_result != 0) {
        NSLog(@"open decodec is false");
        return;
    }
    NSLog(@"decodec name: %s", codec->name);
    
    // 第六步:读取视频压缩数据->循环读取
    // av_read_frame(AVFormatContext *s, AVPacket *pkt)
    // s: 封装格式上下文
    // pkt: 一帧的压缩数据
    AVPacket *avpacket = (AVPacket *)av_mallocz(sizeof(AVPacket));
    
    // 用于存放解码之后的像素数据
    AVFrame *avFrame_in = av_frame_alloc();
    int avcodec_receive_frame_result = 0;
    // sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param)
    // 原始数据
    // scrW: 原始格式宽度
    // scrH: 原始格式高度
    // scrFormat: 原始数据格式
    // 目标数据
    // dstW: 目标格式宽度
    // dstH: 目标格式高度
    // dstFormat: 目标数据格式
    // 当遇到Assertion desc failed at src/libswscale/swscale_internal.h:668
    // 这个问题就是获取元数据的高度有问题
    struct SwsContext *swsContext = sws_getContext(avcodecParameters->width,
                                                   avcodecParameters->height,
                                                   avCodecContext->pix_fmt,
                                                   avcodecParameters->width,
                                                   avcodecParameters->height,
                                                   AV_PIX_FMT_YUV420P,
                                                   SWS_BITEXACT, NULL, NULL, NULL);
    
    // 创建缓冲区
    //创建一个yuv420视频像素数据格式缓冲区(一帧数据)
    AVFrame* avframe_yuv420p = av_frame_alloc();
    //给缓冲区设置类型->yuv420类型
    //得到YUV420P缓冲区大小
    // av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align)
    //pix_fmt: 视频像素数据格式类型->YUV420P格式
    //width: 一帧视频像素数据宽 = 视频宽
    //height: 一帧视频像素数据高 = 视频高
    //align: 字节对齐方式->默认是1
    int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                                               avCodecContext->width,
                                               avCodecContext->height,
                                               1);
    //  开辟一块内存空间
    uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);
    //  向avframe_yuv420p->填充数据
    // av_image_fill_arrays(uint8_t **dst_data, int *dst_linesize, const uint8_t *src, enum AVPixelFormat pix_fmt, int width, int height, int align)
    //dst_data: 目标->填充数据(avframe_yuv420p)
    //dst_linesize: 目标->每一行大小
    //src: 原始数据
    //pix_fmt: 目标->格式类型
    //width: 宽
    //height: 高
    //align: 字节对齐方式
    av_image_fill_arrays(avframe_yuv420p->data,
                         avframe_yuv420p->linesize,
                         out_buffer,
                         AV_PIX_FMT_YUV420P,
                         avCodecContext->width,
                         avCodecContext->height,
                         1);
    
    int y_size, u_size, v_size;
    
    //5.2 将yuv420p数据写入.yuv文件中
    //打开写入文件
    //获得应用程序沙盒的Documents目录
    NSString* doc_path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    //创建文件管理器对象
    NSString* filename = [doc_path stringByAppendingPathComponent:@"output.yuv"];
    //创建目录
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ( ![fileManager fileExistsAtPath:filename]) {
        [fileManager createFileAtPath:filename contents:nil attributes:nil];
    }
    
    const char *fileYUV = [filename UTF8String];
    /*
     * fopen的参数rb+和wb+都是操作可读可写的二进制文件
     区别是
     对于rb+如果打开的文件不存在 会报错(返回NULL)
     对于wb+ 如果文件不存在则会建立,如果文件存在 会覆盖
     */
    FILE* file_yuv420p = fopen(fileYUV,"rb+"); //
    
    if (file_yuv420p == NULL) {
        NSLog(@"file open is false");
    }
    
    int current_index = 0;

    while (av_read_frame(formatContext, avpacket) >= 0) {
        
        // 判断是不是视频
        if (avpacket->stream_index == avformat_stream_index) {
            // 每读取一帧数据,立马解码一帧数据
            // 解码之后得到视频的像素数据->YUV
            // avcodec_send_packet(AVCodecContext *avctx, AVPacket *pkt)
            // avctx: 解码器上下文
            // pkt: 获取到数据包
            // 获取一帧数据
            avcodec_send_packet(avCodecContext, avpacket);
            
            // 解码
            avcodec_receive_frame_result = avcodec_receive_frame(avCodecContext, avFrame_in);
            if (avcodec_receive_frame_result == 0) {
                // 解码成功
                // 此处无法保证视频的像素格式是一定是YUV格式
                // 将解码出来的这一帧数据,统一转类型为YUV
                // sws_scale(struct SwsContext *c, const uint8_t *const *srcSlice, const int *srcStride, int srcSliceY, int srcSliceH, uint8_t *const *dst, const int *dstStride)
                // SwsContext *c: 视频像素格式的上下文
                // srcSlice: 原始视频输入数据
                // srcStride: 原数据每一行的大小
                // srcSliceY: 输入画面的开始位置,一般从0开始
                // srcSliceH: 原始数据的长度
                // dst: 输出的视频格式
                // dstStride: 输出的画面大小
                sws_scale(swsContext, 
                          (const uint8_t *const *)avFrame_in->data, 
                          avFrame_in->linesize, 
                          0, 
                          avCodecContext->height,
                          avframe_yuv420p->data, 
                          avframe_yuv420p->linesize);
                
                //方式一:直接显示视频上面去
                //方式二:写入yuv文件格式
                //5、将yuv420p数据写入.yuv文件中
                //5.1 计算YUV大小
                //分析一下原理?
                //Y表示:亮度
                //UV表示:色度
                //有规律
                //YUV420P格式规范一:Y结构表示一个像素(一个像素对应一个Y)
                //YUV420P格式规范二:4个像素点对应一个(U和V: 4Y = U = V)
                y_size = avCodecContext->width * avCodecContext->height;
                u_size = y_size / 4;
                v_size = y_size / 4;
                //5.2 写入.yuv文件
                //首先->Y数据
                fwrite(avframe_yuv420p->data[0], 1, y_size, file_yuv420p);
                //其次->U数据
                fwrite(avframe_yuv420p->data[1], 1, u_size, file_yuv420p);
                //再其次->V数据
                fwrite(avframe_yuv420p->data[2], 1, v_size, file_yuv420p);
                current_index++;
                NSLog(@"当前解码%d帧", current_index);
            }
        }
        
    }
    // 第八步:关闭解码器->解码完成
    av_packet_free(&avpacket);
    fclose(file_yuv420p);
    av_frame_free(&avFrame_in);
    av_frame_free(&avframe_yuv420p);
    free(out_buffer);
    avcodec_close(avCodecContext);
    avformat_free_context(formatContext);
}

@end

总结: 

1、新的ffmpeg库不需要集中初始化的组建

2、新的API中将AVStream结构体中codec作了遗弃处理,当需要解码器上下文的时候,需要用AVCodecParameters去转化,解决方案是如下:

AVCodecContext *avCodecContext = avcodec_alloc_context3(NULL);
if (avCodecContext == NULL) {
return;
}
// avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *par)
// 将新的API中的 codecpar 转成 AVCodecContext
// 此处的avcodecParameters 是AVStream中的codecpar属性,codecpar里面包含了视频基础信息,
// 但是此处不能直接使用codecpar中的width和height,否者会报错误,错误如下
// Assertion desc failed at src/libswscale/swscale_internal.h:668
avcodec_parameters_to_context(avCodecContext, avcodecParameters);

3、av_mallocz(size_t size)动态内存空间,C/C++知识点,动态去请求内存空间,需要多少开辟多少。

4、处理结束需要按照使用的对象,结构体的层级关系去释放内存。

5、尽量使用新的API

 

你可能感兴趣的:(iOS)