视频解码API调用流程图
**FFmpeg解码函数**
av_register_all():注册所有组件
avformat_open_input():打开输入视频文件
avformat_find_stream_info():获取视频文件信息
avcodec_find_decoder():查找解码器
avcodec_open2():打开解码器
avcodec_alloc_context3():获取解码器上下文(FFmpeg 以后的API)
avcodec_parameters_to_context():为解码器上下文获取视频信息FFmpeg 以后的API)
av_read_frame():从输入文件读取一帧压缩数据
avcodec_decode_video2():解码一帧压缩数据
avcodec_close():关闭解码器
avformat_close_input():关闭输入视频文件
FFmpeg解码的数据结构如下图所示
FFmpeg数据结构简介
AVFormatContext
封装格式上下文结构体,也是统称全局的结构体,保存了视频文件封装格式相关信息
AVInputFormat
每种封装格式(例如:FLV、MKV、MP4、AVI等)对应一个该结构体
AVStream
视频文件中每个视频(音频)流对应一个该结构体
AVCodecContext
编码器上下文结构体,保存了视频(音频)编解码相关的信息
AVCodec
每种视频(音频)编解码器(例如:H.264解码器)对应一个该结构体。
AVPacket
存储一帧压缩编码数据
AVFrame
存储一帧解码后像素(采样)数据。
FFmpeg数据结构分析
AVFormatContext
iformat:输入视频的AVInputFormat
nb_streams:输入视频的AVStream个数
streams:输入视频的AVStream[]数组
druation:输入视频的时长(以微秒为单位)
bit_rate:输入视频码率
AVInputFormat
name:封装格式名称
long_name:封装格式的长名称
extensions:封装格式的扩展名
id:封装格式的ID
一些封装格式处理的接口函数
AVStream
id:序号
codec:该流对应的AVCodecContext
time_base:该流的时基
r_frame_rate:该流的帧率
AVCodecContext
codec:编解码器的AVCodec
width,height:图像的宽高(只针对视频)
pix_fmt:像素格式(只针对视频)
sample_rae:采样率(只针对音频)
channels:声道数(只针对音频)
sample_fmt:采样格式(只针对音频)
AVCodec
name:编解码器名称
long_name:编解码器的长名称
type:编解码器类型
id:编解码器ID
一些编解码的接口函数
AVPacket
pts:显示时间戳
dts:解码时间戳
data:压缩编码数据
size:压缩编码数据大小
stream_index:所属的AVStream
AVFrame
data:解码后的图像像素数据(音频采样数据)
linesize:对视频来说是图像中一行像素的大小;对应音频来说是整个音频帧的大小
width,height:图像的宽高(只针对视频)
key_frame:是否为关键帧(只针对视频)
pict_type:帧类型(值针对视频)。例如:I,P,B
补充知识
解码后的数据为什么要经过sws_scale()函数处理?
解码后YUV格式的视频像素数据保存在AVFrame的data[0]、data[1]、data[2]中。但是这些像素值并不是连续存储的,每行有效像素之后存储了一些无效像素。以亮度Y数据为例,data[0]中一共包含了linesize[0]*height个数据。但是出于优化等方面的考虑,linesize[0]实际上并不等于宽度width,而是一个比宽度大一些的值。因此需要使用sws_scale()进行转换。转换后去除了无效数据,width和linesize[0]取值相等。
#include "com_xy_ndk_ffmpeg_NDKFFmpeg.h"
#include <android/log.h>
#define LOG_I_ARGS(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"FFmpeg_cpp",FORMAT,__VA_ARGS__);
#define LOG_I(FORMAT) LOG_I_ARGS(FORMAT,0);
//当前C++兼容C语言
extern "C" {
//编解码(最重要的库)
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//工具库(大部分库都需要这个库支持)
#include "libavutil/imgutils.h"
//视频像素数据格式转换
#include "libswscale/swscale.h"
}
JNIEXPORT void JNICALL Java_com_xy_ndk_ffmpeg_NDKFFmpeg_callFFmpegNewDecode
(JNIEnv *env, jobject, jstring jInputFilePath, jstring jOutputFilePath) {
//将jstring转成C的字符串
//视频输入地址
const char* cInFilePath = env->GetStringUTFChars(jInputFilePath,NULL);
//解码之后的输出文件地址
const char* cOutFilePath = env->GetStringUTFChars(jOutputFilePath,NULL);
//接下来就是读取视频信息
//分析音视频解码流程
//第一步:注册组件
av_register_all();
//第二步:打开输入视频文件
//初始化封装格式上下文
AVFormatContext* avFmtCtx = avformat_alloc_context();
int fmt_open_result = avformat_open_input(&avFmtCtx,cInFilePath,NULL,NULL);
if(fmt_open_result != ){
LOG_I("打开视频文件失败");
return;
}
//第三步:获取视频文件信息(文件流)
//很多流(例如:视频流、音频流、字幕流等等......)
//然后我的目的:我只需要视频流信息
int fmt_fd_info = avformat_find_stream_info(avFmtCtx,NULL);
if(fmt_fd_info < ){
LOG_I("获取视频文件信息失败");
//打印错误码
//错误信息
char* error_info;
//根据错误码找到对应的错误信息s
av_strerror(fmt_fd_info,error_info,);
LOG_I_ARGS("错误信息:%s",error_info);
return;
}
//第四步:查找解码器
//1.获取视频流的索引位置
//遍历所有的流,找到视频流
int av_stream_index = -;
//avFmtCtx->nb_streams:返回流的大小
for (int i = ; i < avFmtCtx->nb_streams; ++i) {
//判断流的类型(老的API实现)
//是否是视频流
if(avFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
av_stream_index = i;
break;
}
}
if(av_stream_index == -){
LOG_I("不能存在视频流......");
return;
}
//2.根据视频流的索引位置,查找视频流的解码器
//根据视频流的索引位置,获取到指定的参数上下文
//编码方式(编码上下文)
//
AVCodecParameters *avParams= avFmtCtx->streams[av_stream_index]->codecpar;
AVCodec *avCd = avcodec_find_decoder(avParams->codec_id);
if(avCd == NULL){
LOG_I("没有找到这个解码器");
return;
}
AVCodecContext *avCdCtx = avcodec_alloc_context3(avCd);
//根据所提供的编解码器的值填充编译码上下文
int avcodec_to_context = avcodec_parameters_to_context(avCdCtx,avParams);
if(avcodec_to_context < ){
return;
}
//第五步:打开解码器
int av_cd_open_result = avcodec_open2(avCdCtx,avCd,NULL);
if(av_cd_open_result != ){
LOG_I("解码器打开失败......");
return;
}
//获取配置视频信息
//文件格式、文件的宽高、解码器的名称等等......
LOG_I_ARGS("视频文件的格式:%s",avFmtCtx->iformat->name);
//返回的单位是:微秒(avFmtCtx->duration)
LOG_I_ARGS("视频的时长:%lld秒",(avFmtCtx->duration)/);
//获取宽高
LOG_I_ARGS("视频的宽高:%d x %d = ",avCdCtx->width,avCdCtx->height);
//解码器的名称
LOG_I_ARGS("解码器的名称:%s",avCd->name);
//第六步:从输入文件读取一帧压缩数据(解压缩:一帧一帧读取解压缩)
//循环读取每一帧数据
//读取的帧数据缓存到那里(开辟一块内存空间用于保存)
AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//缓存一帧数据(就是一张图片)
AVFrame* in_frame_picture = av_frame_alloc();
//定义输出一帧数据(缓冲区:YUV420p类型)
AVFrame* out_frame_picture_YUV42P = av_frame_alloc();
//指定缓冲区的类型(像素格式:YUV420P)
//老API
//开启空间的大小是:YUV420P格式数据大小
// uint8_t *out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P,avCdCtx->width,avCdCtx->height));
//指定填充数据(YUV420P数据)
// avpicture_fill((AVPicture *)out_frame_picture_YUV42P,out_buffer,AV_PIX_FMT_YUV420P,avCdCtx->width,avCdCtx->height);
//新API
//av_image_get_buffer_size: 计算缓冲区的大小
//参数一:缓冲区格式(需要:AV_PIX_FMT_YUV420P)
//参数二:缓冲区宽度
//参数三:缓冲区高度
//参数四:字节对齐的方式(通用设置:1)
//回忆:以前学习结构体的大小的时候,讲解过字节对齐(字节对齐目的:为了提高读取的效率或者说性能)
int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,avCdCtx->width,avCdCtx->height,);
//开辟缓存空间
uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);
//av_image_fill_arrays: 指定缓冲区填充的像素数据类型(要求:YUV420P视频像素数据格式)
//参数分析
//参数一:填充的数据
//参数二:每一行大小
//参数三:缓冲区
//参数四:填充的像素数据格式(在这里我们要求:YUV420P)
//参数五:每一帧宽度(视频宽)
//参数六:每一帧高度(视频高)
//参数七:字节对齐的方式(通用设置:1)
av_image_fill_arrays(out_frame_picture_YUV42P->data,out_frame_picture_YUV42P->linesize,out_buffer
,AV_PIX_FMT_YUV420P,avCdCtx->width,avCdCtx->height,);
int ret,y_size = ,u_size = ,v_size = ,frame_index = ;
//打开文件
FILE* out_file_yuv = fopen(cOutFilePath,"wb");
if(out_file_yuv == NULL){
LOG_I("文件不存在!");
return;
}
//视频像素数据格式转换上下文
//参数一:输入的宽度
//参数二:输入的高度
//参数三:输入的数据
//参数四:输出的宽度
//参数五:输出的高度
//参数六:输出的数据
//参数七:视频像素数据格式转换算法类型(使用什么算法)
//参数八:字节对齐类型,一般都是默认1(字节对齐类型:提高读取效率)
SwsContext *sws_ctx =
sws_getContext(avCdCtx->width,avCdCtx->height, avCdCtx->pix_fmt,
avCdCtx->width,avCdCtx->height,AV_PIX_FMT_YUV420P,
SWS_BICUBIC,NULL,NULL,NULL);
//读取返回值
//>=0:正在读取
//<0:读取失败或者说读取完毕
while (av_read_frame(avFmtCtx,packet) >= ){
//有视频流帧数据、音频流帧数据、字幕流......
if(packet->stream_index == av_stream_index){
//我们只需要解码一帧视频压缩数据,得到视频像素数据
//老的API
//参数一:解码器上下文
//参数二:一帧数据
//参数三:是否正在解码(0:代表解码完毕,非0:正在解码)
//返回值:小于0解码失败(错误、异常),否则成功解码一帧数据
// ret = avcodec_decode_video2(avCdCtx,in_frame_picture,&got_picture_ptr,packet);
//新的API: avcodec_send_packet() and avcodec_receive_frame().
//avcodec_send_packet: 发送数据包(通俗解释:解压一帧视频压缩数据)
//avcodec_receive_frame: 接收一帧解析成功之后的视频像素数据
//第一步:avcodec_send_packet
//参数一:解码器上下文
//参数二:数据包(通俗讲解:一帧视频压缩数据)
//返回值:
//AVERROR(EAGAIN): 当前数据不可用,你可以尝试继续重新发送一次(通俗讲解:继续解码下一帧)
//AVERROR_EOF: 解码完成
//AVERROR(EINVAL): 当前你一个解码器,但是没有打开或者已经关闭了(通俗讲解:不可用)
//AVERROR(ENOMEM): 当前解码一帧视频压缩数据发送了异常
avcodec_send_packet(avCdCtx,packet);
// if(ret != 0 || ret != AVERROR(EAGAIN)){
// LOG_I("解码失败");
// return;
// }
//第二步:avcodec_receive_frame
//接收一帧解码视频像素数据
//返回值和avcodec_send_packet返回值含义相同
ret = avcodec_receive_frame(avCdCtx,in_frame_picture);
if(ret == ){
//接下来我要将解码后的数据(视频像素数据,保存为YUV420P文件)
//在这个地方需要指定输出文件的类型(格式转换)
//我要将AVFrame转成视频像素数YUV420P格式
//参数一:视频像素数据格式上下文(SwsContext)
//参数二:输入的数据(转格式前的视频像素数据)
//参数三:输入画面每一行的大小(视频像素数据转换一行一行的转)
//参数四:输入画面每一行的要转码的开始位置
//参数五:输出画面数据(转格式后的视频像素数据)
//参数六:输出画面每一行的大小
sws_scale(sws_ctx,(const uint8_t *const*)in_frame_picture->data,
in_frame_picture->linesize,,avCdCtx->height,
out_frame_picture_YUV42P->data,out_frame_picture_YUV42P->linesize);
//普及: YUV420P格式结构
//Y代表亮度,UV代表色度(人的眼睛对亮度敏感,对色度不敏感)
//再深入:计算机图像学相关
//YUV420P格式规定一:Y结构表示一个像素点(一个像素点就是一个Y)
//YUV420P格式规定二:四个Y对应一个U和一个V(也就是四个像素点,对应一个U和V)
//Y默认情况下:灰度
//计算Y大小:y = 宽x高
y_size = avCdCtx->width * avCdCtx->height;
u_size = y_size / ;
v_size = y_size / ;
//写入文件
//首先写入Y,再是U,再是V
//in_frame_picture->data[0]表示Y
fwrite(in_frame_picture->data[],,y_size,out_file_yuv);
//in_frame_picture->data[1]表示U
fwrite(in_frame_picture->data[],,u_size,out_file_yuv);
//in_frame_picture->data[2]表示V
fwrite(in_frame_picture->data[],,v_size,out_file_yuv);
frame_index++;
LOG_I_ARGS("当前是第%d帧",frame_index);
}
}
//老的API写法
//关闭流
// av_free_packet(packet);
}
av_packet_free(&packet);
//关闭流
fclose(out_file_yuv);
av_frame_free(&in_frame_picture);
av_frame_free(&out_frame_picture_YUV42P);
avcodec_close(avCdCtx);
avformat_free_context(avFmtCtx);
}
代码地址:CSDN下载地址