本文为CSDN博主「涂文远」的原创文章,原文链接:https://blog.csdn.net/daividtu/article/details/84141849
为什么可以压缩
● 去除冗余信息
○ 空间冗余:图像相邻像素之间有较强的相关性
○ 时间冗余:视频序列的相邻图像之间内容相似
○ 编码冗余:不同像素值出现的概率不同
○ 视觉冗余:人的视觉系统对某些细节不敏感
○ 知识冗余:规律性的结构可由先验知识和背景知识得到
3. 数据压缩分类
● 无损压缩(Lossless)
○ 压缩前解压缩后图像完全一致X=X’
○ 压缩比低(2:1~3:1)
○ 例如:Winzip,JPEG-LS
● 有损压缩(Lossy)
○ 压缩前解压缩后图像不一致X≠X’
○ 压缩比高(10:1~20:1)
○ 利用人的视觉系统的特性
○ 例如:MPEG-2,H.264/AVC,AVS
人类视觉系统HVS
● HVS特点:
○ 对高频信息不敏感
○ 对高对比度更敏感
○ 对亮度信息比色度信息更敏感
○ 对运动的信息更敏感X
● RGB转化到YUV空间
亮度分量Y与三原色有如下关系:
主流的编解码标准的压缩对象都是YUV图像
解协议的作用,就是将流媒体协议的数据,解析为标准的相应的封装格式数据。视音频在网络上传播的时候,常常采用各种流媒体协议,例如HTTP,RTMP,或是MMS等等。这些协议在传输视音频数据的同时,也会传输一些信令数据。
信令数据包括对播放的控制(播放,暂停,停止),或者对网络状态的描述等。解协议的过程中会去除掉信令数据而只保留视音频数据。例如,采用RTMP协议传输的数据,经过解协议操作后,输出FLV格式的数据。
解封装的作用,就是将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码数据。封装格式种类很多,例如MP4,MKV,RMVB,TS,FLV,AVI等等,它的作用就是将已经压缩编码的视频数据和音频数据按照一定的格式放到一起。例如,FLV格式的数据,经过解封装操作后,输出H.264编码的视频码流和AAC编码的音频码流。
解码的作用,就是将视频/音频压缩编码数据,解码成为非压缩的视频/音频原始数据。音频的压缩编码标准包含AAC,MP3,AC-3等等,视频的压缩编码标准则包含H.264,MPEG2,VC-1等等。解码是整个系统中最重要也是最复杂的一个环节。通过解码,压缩编码的视频数据输出成为非压缩的颜色数据,例如YUV420P,RGB等等;压缩编码的音频数据输出成为非压缩的音频抽样数据,例如PCM数据。
视音频同步的作用,就是根据解封装模块处理过程中获取到的参数信息,同步解码出来的视频和音频数据,并将视频音频数据送至系统的显卡
由表可见,除了AVI之外,其他封装格式都支持流媒体,即可以“边下边播”。有些格式更“万能”一些,支持的视音频编码标准多一些,比如MKV。而有些格式则支持的相对比较少,比如说RMVB。
这些封装格式都有相关的文档,在这里就不一一例举了。
我自己也做过辅助学习的小项目:
视频编码
视频编码的主要作用是将视频像素数据(RGB,YUV等)压缩成为视频码流,从而降低视频的数据量。如果视频不经过压缩编码的话,体积通常是非常大的,一部电影可能就要上百G的空间。视频编码是视音频技术中最重要的技术之一。视频码流的数据量占了视音频总数据量的绝大部分。高效率的视频编码在同等的码率下,可以获得更高的视频质量。
音频编码
音频编码的主要作用是将音频采样数据(PCM等)压缩成为音频码流,从而降低音频的数据量。音频编码也是互联网视音频技术中一个重要的技术。但是一般情况下音频的数据量要远小于视频的数据量,因而即使使用稍微落后的音频编码标准,而导致音频数据量有所增加,也不会对视音频的总数据量产生太大的影响。高效率的音频编码在同等的码率下,可以获得更高的音质。
音频编码的简单原理
YUV420数据格式
YUV简介
YUV定义:分为三个分量,
“Y”表示明亮度(Luminance或Luma)也就是灰度值
而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。
YUV存储:格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0,
YUV特点:也是一种颜色编码方法,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样 可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。并且,YUV不像RGB那样要求三个独立的视频信号同时传 输,所以用YUV方式传送占用极少的频宽。
在采集到RGB24数据后,需要对这个格式的数据进行第一次压缩。即将图像的颜色空间由RGB2YUV。因为,X264在进行编码的时候需要标准的YUV(4:2:0)。但是这里需要注意的是,虽然YV12也是(4:2:0),但是YV12和I420的却是不同的,在存储空间上面有些区别。如下:
YV420: 亮度(行×列) + V(行×列/4) + U(行×列/4)
以后提取每个像素的YUV分量会用到。
-----------------------------------函数作用----------------------------------------------------------------------------------
av_register_all
基于ffmpeg的应用程序中 几乎都是第一个被调用的。只有调用了该函数,才能使用复用器,编码器才能起作用,必须调用此函数。
一般来说,直接采集到的视频数据是RGB24的格式,RGB24一帧的大小size=width×heigth×3 Byte,RGB32的size=width×heigth×4,如果是I420(即YUV标准格式4:2:0)的数据量是 size=width×heigth×1.5 Byte。
AVFormatContext是包含码流参数较多的结构体。本文将会详细分析一下该结构体里每个变量的含义和作用。
struct AVInputFormat *iformat:输入数据的封装格式
AVIOContext *pb:输入数据的缓存
unsigned int nb_streams:视音频流的个数
AVStream **streams:视音频流
char filename[1024]:文件名
int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000)
int bit_rate:比特率(单位bps,转换为kbps需要除以1000)
AVDictionary *metadata:元数据
avformat_find_stream_info
该函数主要用于给每个媒体流(音频/视频)的AVStream结构体赋值。我们大致浏览一下这个函数的代码,会发现它其实已经实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作。换句话说,该函数实际上已经“走通”的解码的整个流程。下面看一下除了成员变量赋值之外,该函数的几个关键流程。
从avcodec_decode_video2()主要做了以下几个方面的工作:
(1)对输入的字段进行了一系列的检查工作:例如宽高是否正确,输入是否为视频等等。
(2)通过ret = avctx->codec->decode(avctx, picture, got_picture_ptr,&tmp)这句代码,调用了相应AVCodec的decode()函数,完成了解码操作。
(3)对得到的AVFrame的一些字段进行了赋值,例如宽高、像素格式等等。
FFmpeg并没有垃圾回收机制,所分配的空间都需要自己维护。而由于视频处理过程中数据量是非常大,对于动态内存的使用更要谨慎。
AVFormatContext 在FFmpeg中有很重要的作用,描述一个多媒体文件的构成及其基本信息,存放了视频编解码过程中的大部分信息。通常该结构体由avformat_open_input分配
存储空间,在最后调用avformat_input_close关闭。
AVStream 描述一个媒体流,在解码的过程中,作为AVFormatContext的一个字段存在,不需要单独的处理。
AVpacket 用来存放解码之前的数据,它只是一个容器,其data成员指向实际的数据缓冲区,在解码的过程中可有av_read_frame创建和填充AVPacket中的数据缓冲区,
当数据缓冲区不再使用的时候可以调用av_free_apcket释放这块缓冲区。
AVFrame 存放从AVPacket中解码出来的原始数据,其必须通过av_frame_alloc来创建,通过av_frame_free来释放。和AVPacket类似,AVFrame中也有一块数据缓存空间,
在调用av_frame_alloc的时候并不会为这块缓存区域分配空间,需要使用其他的方法。在解码的过程使用了两个AVFrame,这两个AVFrame分配缓存空间的方法也不相同
● 一个AVFrame用来存放从AVPacket中解码出来的原始数据,这个AVFrame的数据缓存空间通过调avcodec_decode_video分配和填充。
● 另一个AVFrame用来存放将解码出来的原始数据变换为需要的数据格式(例如RGB,RGBA)的数据,这个AVFrame需要手动的分配数据缓存空间。
sws_getContext
srcW:源图像的宽
srcH:源图像的高
srcFormat:源图像的像素格式
dstW:目标图像的宽
dstH:目标图像的高
dstFormat:目标图像的像素格式
flags:设定图像拉伸使用的算法
在摄像头之类编程经常是会碰到YUV格式,而非大家比较熟悉的RGB格式. 我们可以把YUV看成是一个RGB的变种来理解.
YUV的原理是把亮度与色度分离,研究证明,人眼对亮度的敏感超过色度。利用这个原理,可以把色度信息减少一点,人眼也无法查觉这一点。
YUV三个字母中,其中"Y"表示明亮度(Lumina nce或Luma),也就是灰阶值;而"U"和"V"表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。用这个三个字母好象就是通道命令
使用YUV的优点有两个:
一.彩色YUV图像转黑白YUV图像转换非常简单,这一特性用在于电视信号上。
二.YUV是数据总尺寸小于RGB格式
RGB格式中,一个24bpp像素要占用4字节空间。在YUV格式中,可以对于UV分量的数据压缩,但是对图像整体质量影响不大,这样YUV所占的空间就比RGB要小一些
YUV的存储中与RGB格式最大不同在于,RGB格式每个点的数据是连继保存在一起的。即R,G,B是前后不间隔的保存在2-4byte空间中。而YUV的数据中为了节约空间,U,V分量空间会减小。每一个点的Y分量独立保存,但连续几个点的U,V分量是保存在一起的,(反正人眼一般也看不出区别).这几个点合起来称
为macro-pixel, 这种存储格式称为Packed格式。
另外一种存储格式是把一幅图像中Y,U,V分别用三个独立的数组表示。这种模式称为planar模式。
播放器
G:\ScreenCapture\ffmpeg-20170915-6743351-win64-static\bin
下载地址
http://ffmpeg.zeranoe.com/builds/
命令
ffplay -f rawvideo -video_size 480x208 X:\Users\twy\Desktop\output.yuv
实现代码
public class MainActivity extends AppCompatActivity {
static{
System.loadLibrary("avcodec-56");
System.loadLibrary("avdevice-56");
System.loadLibrary("avfilter-5");
System.loadLibrary("avformat-56");
System.loadLibrary("avutil-54");
System.loadLibrary("postproc-53");
System.loadLibrary("swresample-1");
System.loadLibrary("swscale-3");
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public native void open(String inputStr,String outStr);
public void load(View view){
load();
}
private void load(){
String[] permissions1 = checkPermission(this);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
if(permissions1.length<=0){
File inputFile = new File(Environment.getExternalStorageDirectory(),"input.mp4");
String input = inputFile.getAbsolutePath();
String output = new File(Environment.getExternalStorageDirectory(),"output.yuv").getAbsolutePath();
Log.i("twy",input +"******"+ inputFile.exists()+inputFile.length());
open(input,output);
}else{
//申请权限
ActivityCompat.requestPermissions(this, permissions, 100);
}
}else{
//6.0以下不需要申请权限
File inputFile = new File(Environment.getExternalStorageDirectory(),"input.mp4");
String input = inputFile.getAbsolutePath();
String output = new File(Environment.getExternalStorageDirectory(),"output.yuv").getAbsolutePath();
Log.i("twy",input +"******"+ inputFile.exists()+inputFile.length());
open(input,output);
}
}
//需要申请的权限
private static String[] permissions = new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
};
//检测权限
public static String[] checkPermission(Context context){
List<String> data = new ArrayList<>();//存储未申请的权限
for (String permission : permissions) {
int checkSelfPermission = ContextCompat.checkSelfPermission(context, permission);
if(checkSelfPermission == PackageManager.PERMISSION_DENIED){
//未申请
data.add(permission);
}
}
return data.toArray(new String[data.size()]);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == 100){
boolean flag = true;
for(int i : grantResults){
if(i != PackageManager.PERMISSION_GRANTED){
flag = false;
break;
}
}
if(flag){
load();
}else{
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}else{
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
#include <jni.h>
#include <string>
#include <android/log.h>
extern "C"{
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
}
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"jason",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"jason",FORMAT,##__VA_ARGS__);
extern "C" JNIEXPORT jstring
JNICALL
Java_com_ican_ffmpegdemo1_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
av_register_all();
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_ican_ffmpegdemo1_MainActivity_open(JNIEnv *env, jobject instance, jstring inputStr_,
jstring outStr_) {
const char *inputStr = env->GetStringUTFChars(inputStr_, 0);
const char *outStr = env->GetStringUTFChars(outStr_, 0);
//注册各大组件
av_register_all();
//打开文件
AVFormatContext *pContext = avformat_alloc_context();
if(avformat_open_input(&pContext,inputStr,NULL,NULL)<0){
LOGE("打开失败");
return;
}
//给AVFormatContext填充数据
if(avformat_find_stream_info(pContext,NULL)<0){
LOGE("获取信息失败");
return;
}
int vedio_stream_idx = -1;
LOGE("size::%d",pContext->nb_streams);
//找到视频流
for(int i = 0;i<pContext->nb_streams;i++){
LOGE("循环 %d",i);
//streams包含了视频流 音频流 字母流 codex 每一个流 对应的解码上下文 code_type 流的类型
if(pContext->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
vedio_stream_idx = i;
}
}
//获取到解码器上下文
AVCodecContext *avCodectCtx = pContext->streams[vedio_stream_idx]->codec;
//解码器
AVCodec *pCodex = avcodec_find_decoder(avCodectCtx->codec_id);
if(avcodec_open2(avCodectCtx,pCodex,NULL)<0){
LOGE("解码失败");
return;
}
//分配内存
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//初始化结构体
av_init_packet(packet);
AVFrame * frame = av_frame_alloc();
//声明一个yuv fram
AVFrame * yuvframe = av_frame_alloc();
//给yuvframe 的缓冲区 初始化
uint8_t * out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P,avCodectCtx->width,avCodectCtx->height));
int re = avpicture_fill((AVPicture *)yuvframe,out_buffer,AV_PIX_FMT_YUV420P,avCodectCtx->width,avCodectCtx->height);
//avCodectCtx->pix_fmt原文件的封装格式
SwsContext * swsContext = sws_getContext(avCodectCtx->width,avCodectCtx->height,avCodectCtx->pix_fmt,avCodectCtx->width,avCodectCtx->height,AV_PIX_FMT_YUV420P,SWS_BILINEAR,NULL,NULL,NULL);
int frameCount = 0;
FILE * fp_yuv = fopen(outStr,"wb");
//packet 入参 出参对象 >=0 不满足条件代表读到尾
int got_fram;
while (av_read_frame(pContext,packet)>=0){
LOGE("解码 %d",frameCount++);
//解封装
//根据fram进行原生绘制 会把packet放入frame
avcodec_decode_video2(avCodectCtx,frame,&got_fram,packet);
if(got_fram>0){
//fram数据拿到 视频像素数据 yuv 三个rgb r g b 数据量大 三个通道
// r g b 1824 yuv1970 y 亮度 u v 4:1:1
sws_scale(swsContext,(const uint8_t *const *)frame->data,frame->linesize,0,frame->height,yuvframe->data,yuvframe->linesize);
int y_size = avCodectCtx->width*avCodectCtx->height;
//y亮度信息写完了
fwrite(yuvframe->data[0],1,y_size,fp_yuv);
fwrite(yuvframe->data[1],1,y_size/4,fp_yuv);
fwrite(yuvframe->data[2],1,y_size/4,fp_yuv);
}
av_free_packet(packet);
}
fclose(fp_yuv);
av_frame_free(&frame);
av_frame_free(&yuvframe);
avcodec_close(avCodectCtx);
avformat_free_context(pContext);
env->ReleaseStringUTFChars(inputStr_, inputStr);
env->ReleaseStringUTFChars(outStr_, outStr);
}
播放器
G:\ScreenCapture\ffmpeg-20170915-6743351-win64-static\bin
下载地址
http://ffmpeg.zeranoe.com/builds/
文章浏览阅读2.8k次。虚拟机,添加新的硬盘进行分区,格式化,挂载的操作_centos 永久挂载
文章浏览阅读504次。2、只要建立索引就能显著提高查询速度 事实上,我们可以发现上面的例子中,第2、3条语句完全相同,且建立索引的字段也相同;不同的仅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查询速度却有着天壤之别。所以,并非是在任何字段上简单地建立索引就能提高查询速度。 从建表的语句中,我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同记录。在此..._索引对查询效率非常有用,在建表时就应该建好且建完整
文章浏览阅读3.2w次,点赞20次,收藏180次。当前分为以下四个阶段第一阶段库/框架选型(暂定react)第二阶段简单构建优化 NPM管理包node+webpack打包第三阶段JS、CSS模块化开发第四阶段组件化开发 开发过程当中注意:前端安全XSS CSRF攻击等 后期文章中将讲述如何_前端开发流程sop
文章浏览阅读1w次。个人云电脑-推荐方案 - Parsec / FastlinkParsec安利原文局域网游戏串流:让我们都做一回「云」玩家Parsec 是游戏串流工具中的新秀。与其他不同的是,Parsec 推荐 PC-PC 间云游戏,不论是局域网还是公网通吃,这就是 Parsec 比较厉害的地方。两台设备之间的流量不通过 Parsec 云服务器,而是 Peer to Peer,Parsec 自己宣称自己使用了很多技术来保证玩家的联机体验。但国内的家庭宽带一般都是 NAT 环境(部分运营商可以._parsec 局域网
文章浏览阅读148次。在运维人员操作系统时,要接触大量的文件,为了避免忘记文件存放位置的尴尬,就需要我们有一种文件查找工具的帮忙,下面是两个文件查找工具的详解,locate以及find,分别分享给大家。第一款工具: Locatelocate - find files by namelocate的工作依赖于事先构建好的索引库;查找文件时,直接搜索索引库里记载的文件的位置;索引库的构建:系统自动实现(周期性任务);手动更新..._find -name -r
文章浏览阅读6.7k次,点赞7次,收藏87次。SpringSecurity Oauth2 jwtSpringSecurity Oauth2 jwt1 用户认证分析1.1 单点登录1.2 第三方账号登录2 认证解决方案2.1 单点登录技术方案2.2 第三方登录技术方案2.2.1 Oauth2认证流程2.2.2 Oauth2在项目的应用2.3 Spring security Oauth2认证解决方案3 Jwt令牌回顾3.1 令牌结构3.2 生成私钥公钥3.3 基于私钥生成jwt令牌3.3.1导入认证服务3.3.2 认证服务中创建测试类3.4 基于公_spring security 6+oauth2 +jwt+密码认证
文章浏览阅读1.3w次。金山软件办公套件的最新更新 WPS 2016 for Linux,日前发布了几项新功能,性能改进和各种修复。为什么选择WPS办公套件?WPS Office由三个主要组件组成:WPS 文字,WPS 演示和WPS 表格。它看起来非常类似于Microsoft Office! 与Microsoft Office提供的文档格式(包括PPT,DOC,DOCX,XLS和XLSX)完全兼容性。WPS的个人版是供个..._wps office 2016 for linux
文章浏览阅读8k次,点赞8次,收藏95次。用自己数据实现偏最小二乘回归。用Hitters数据集做演示如何使用自己的数据实现偏最小二乘回归。 此数据集有322个运动员的20个变量的数据, 其中的变量Salary(工资)是我们关心的。数据下载百度网盘链接:https://pan.baidu.com/s/13pb7VN_kTzV0hUEsg-1S1A提取码:3333import pandas as pdimport numpy as npfrom sklearn.cross_decomposition import PLSRegression_python 偏最小二乘回归
文章浏览阅读368次,点赞7次,收藏8次。记住常用的基本数据类型int,double熟悉位数: byte8位,int 32位等等记住特性: long需要加L,flaot需要加F,char必须是单引号且只有一个2.1类型转换数据类型转换, 即 它们之间可以变换.2.1.1默认转换按照数据的表示范围, 小范围向大范围转换,可以默认进行// 类型转换默认进行(小转大)long b = a;2.1.2强制转换通过强制转换,可以将数据转换过去,但是有可能丢失精度口诀: 小转大默认进行,大转小强制进行3.1字符串。
文章浏览阅读2.5k次。"h5" : { "sdkConfigs" : { "maps" : {} }, "router" : { "base" : "./" }, "devServer" : { "port" : 8080, "disableHostCheck" : true, "proxy" : { ..._uniapp配置后台ip
文章浏览阅读1k次。1、概述日志管理工具journalctl是centos7上专有的日志管理工具,该工具是从message这个文件里读取信息。Systemd统一管理所有Unit的启动日志。带来的好处就是,可以只用journalctl一个命令,查看所有日志(内核日志和应用日志)。日志的配置文件是/etc/systemd/journald.conf。2、查看所有日志(默认情况下 ,只保存本次启动的日志)[root@CEN..._journalctl -b 0
文章浏览阅读535次。前言: 在属性被 static 修饰后,Spring 便不能直接对变量进行直接注入,这是因为被 static 修饰后,会被放到常量池中,而Spring 需要使用set方法进行注入,这是就需要我们手动进行配置注入成员变量第一步:在类上添加@Component注解,让Spring扫描到这个类第二步:为成员变量添加set方法,注意去掉static关键字,否则会导致注入失败第三步:在set方法上添加@Resource注解,告诉Spring自动注入这个方法/** * @author: mi_静态成员变量怎么注入