一、视频基础知识
-
1.封装格式
mp4 rmvb avi ...等我们常见的视频播放格式他同时封装了视频和音频数据我们称之为封装格式;封装格式的主要作用是把视频码流和音频码流按照一定的格式存储在一个文件中。
-
2.RGB、RGBA和YUV图像格式
rgb:由三原色组成,即 红绿蓝,rgb的数据量大,需要三个通道
rgba:比rgb多一个a通道即透明度,我们android使用的bitmap图像既为rgba四通道图像
yuv:y表示亮度,u和v表示色度和饱和度,根据y:u:v=4:1:1的比例顺序排列,所以只需要一个通道即可表示画面
-
3.视频编码
将像素数据yuv、rgb压缩为视频码流,不经过压缩的视频是非常大的一个电影可能会有上百个G
视频编码标准
名称 推出机构 推出时间 目前使用领域 HEVC(H.265) MPEG/ITU-T 2013 研发中 H.264 MPEG/ITU-T 2003 各个领域 MPEG4 MPEG 2001 不温不火 MPEG2 MPEG 1994 数字电视 VP9 Google 2013 研发中 VP8 Google 2008 不普及 VC-1 Microsoft Inc. 2006 微软平台
-
4.视频压缩
空间压缩:假设一块红色区域有1000个像素每个像素由一个int值表示那么就需要1000个int值,现在我用一个值将这一块区域的轮廓标记起来然后再用一个值表示颜色,那么现在这块区域可能只需要少部分值就能表示出来,从而达到压缩效果
时间压缩:假设某段视频这10s的时间画面都没有变化,那么我们就可以不需要全部保存每一帧,而只需要保存一帧再保存一个时间值就可以达到压缩效果。
除此之外还有编码压缩,视觉压缩等等...
无损压缩:压缩前 和压缩后的画面完全一致
有损压缩:压缩前和压缩后的图像质量有变化,我们通常时候都是采用的有损压缩
-
5.不管视频进行了哪种编码和压缩,播放的时候都会转换为yuv等原始视频数据
二、解码流程

三、小试牛刀,将mp4文件转换为yuv格式的视频文件
//ffmpeg都是使用C语言编写,如果是用C++引入头文件时记得加上extern "C"
extern "C" {
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
}
/**
*将一个MP4格式的视频转码成yuv420p格式文件
*inputStr:MP4文件路径
*outStr:转码后的输出路径
*/
void mp4toYUV420p(char *inputStr,char *outStr){
// 1.注册各大组件,执行ffmgpe都必须调用此函数
av_register_all();
//2.得到一个ffmpeg的上下文(上下文里面封装了视频的比特率,分辨率等等信息...非常重要)
AVFormatContext *pContext = avformat_alloc_context();
//3.打开一个视频
if(avformat_open_input(&pContext, inputStr, NULL, NULL)<0) {
LOGE("打开失败");
return;
}
//4.获取视频信息(将视频信息封装到上下文中)
if (avformat_find_stream_info(pContext, NULL) < 0) {
LOGE("获取信息失败");
return;
}
//5.用来记住视频流的索引
int vedio_stream_idx=-1;
//从上下文中寻找找到视频流
for (int i = 0; i < pContext->nb_streams; ++i) {
LOGE("循环 %d", i);
//codec:每一个流 对应的解码上下文
//codec_type:流的类型
if (pContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
//如果找到的流类型 == AVMEDIA_TYPE_VIDEO 即视频流,就将其索引保存下来
vedio_stream_idx = i;
}
}
//获取到解码器上下文
AVCodecContext* pCodecCtx=pContext->streams[vedio_stream_idx]->codec;
//获取解码器
AVCodec *pCodex=avcodec_find_decoder(pCodecCtx->codec_id);
//6.打开解码器。 (ffempg版本升级名字叫做avcodec_open2)
if (avcodec_open2(pCodecCtx, pCodex, NULL) < 0) {
LOGE("解码失败");
return;
}
//----------------------解码前准备--------------------------------------
//准备开始解码时需要一个AVPacket存储数据(通过av_malloc分配内存)
AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));
av_init_packet(packet);//初始化结构体
//解封装需要AVFrame
AVFrame *frame = av_frame_alloc();
//声明一个yuvframe的缓冲区
AVFrame *yuvFrame = av_frame_alloc();
//给yuvframe 的缓冲区 初始化
uint8_t *out_buffer= (uint8_t *) av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
//给缓冲区进行替换
int re=avpicture_fill((AVPicture *) yuvFrame, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
LOGE("宽 %d 高 %d",pCodecCtx->width,pCodecCtx->height);
//格式转码需要的转换上下文(根据封装格式的宽高和编码格式,以及需要得到的格式的宽高)
//pCodecCtx->pix_fmt :mp4的上下文
//AV_PIX_FMT_YUV420P : 目标格式
//SWS_BILINEAR :标准格式 无损耗
//NULL,NULL,NULL : 过滤器等
SwsContext *swsContext=sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,
pCodecCtx->width,pCodecCtx->height,AV_PIX_FMT_YUV420P
,SWS_BILINEAR,NULL,NULL,NULL
);
int frameCount = 0;
//转换为YUV后输出到一个文件中
FILE *fp_yuv = fopen(outStr, "wb");
//------------------------一桢一帧开始解码--------------------
int got_frame;
while (av_read_frame(pContext, packet) >= 0) {//开始读每一帧的数据
//7.解封装(将packet解压给frame,即:拿到了视频数据frame)
avcodec_decode_video2(pCodecCtx, frame, &got_frame, packet);//解封装函数
LOGE("解码%d ",frameCount++);
if (got_frame > 0) {
//8.转码(转码上下文,原数据,一行数据,开始位置,yuv的缓冲数组,yuv一行的数据)
sws_scale(swsContext, (const uint8_t *const *) frame->data, frame->linesize, 0, frame->height, yuvFrame->data,
yuvFrame->linesize
);
//计算图像的总大小
int y_size = pCodecCtx->width * pCodecCtx->height;
//写入yuv格式信息
fwrite(yuvFrame->data[0], 1, y_size, fp_yuv);//写入亮度信息Y
fwrite(yuvFrame->data[1], 1, y_size/4, fp_yuv);//写入u
fwrite(yuvFrame->data[2], 1, y_size/4, fp_yuv);//写入
}
av_free_packet(packet);v//释放
}
fclose(fp_yuv);//关闭文件流
av_frame_free(&frame);
av_frame_free(&yuvFrame);
avcodec_close(pCodecCtx);
avformat_free_context(pContext);
free(inputStr);
free(outStr);
}
网友评论