第一点:一个视频播放流程
通常看到视频格式:mp4、mov、flv、wmv等等…
称之为:封装格式
第二点:视频播放器
两种模式播放器
第一种:可视化界面播放器(直接用户直观操作->简单易懂)
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 等等。
1.创建一个工程
2.创建ffmpeg导入到工程
配置Header Search Paths 路径,这个路径可以将Library Search Paths:将ffmpeg.a库文件lib文件夹路径加进去,只需将lib改为存放头文件的文件夹即可
这里用三个方法去测试
创建一个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