如何使用libavcodec将.yuv图像序列编码为.h264的视频码流?
1.实现打开和关闭输入文件和输出文件的操作
//io_data.cpp
static FILE* input_file= nullptr;
static FILE* output_file= nullptr;
int32_t open_input_output_files(const char* input_name,const char* output_name){
if(strlen(input_name)==0||strlen(output_name)==0){
cout<<"Error:empty input or output file name."<<endl;
return -1;
}
close_input_output_files();
input_file=fopen(input_name,"rb");//rb:读取一个二进制文件,该文件必须存在
if(input_file==nullptr){
cerr<<"Error:failed to open input file."<<endl;
return -1;
}
output_file=fopen(output_name,"wb");//wb:打开或新建一个二进制文件,只允许写
if(output_file== nullptr){
cout<<"Error:failed to open output file."<<endl;
return -1;
}
return 0;
}
void close_input_output_files(){
if(input_file!= nullptr){
fclose(input_file);
input_file= nullptr;
}
if(output_file!= nullptr){
fclose(output_file);
output_file= nullptr;
}
}
2.视频编码器的初始化(在介绍这部分内容之前,先来了解一下几个非常重要的结构体:AVCodec,AVCodecContext,AVPacket以及AVFrame)
AVCodec:
AVCodec类型的结构包含了FFmpeg libavcodec对一个编码器底层实现的封装,其内部定义的部分结构如下:
typedef struct AVCodec{
const char *name;//简要名称
const char *longname;//完整名称
enum AVMediaType type;//媒体类型
enum AVCodecID id;
enum AVPixelFormat *pix_fmts;//像素格式,一般为yuv420p
const AVProfile *profiles;//编码档次
}
AVCodecContext:
在FFmpeg中,每一个编码器都对应一个上下文结构;在编码开始前,可以通过该结构配置相应的编码参数,比如:编码的profile,图像的宽和高,关键帧间距,码率和帧率等。对于其他编码器(如libx264)的私有参数,AVCodecContext结构可以使用成员priv_data保存编码器的配置信息。该结构的部分定义如下:
typedef struct AVCodecContext{
void *priv_data;//私有参数
int64_t bit_rate;//码率
int width,height;
enum AVPixelFormat pix_fmt;
int max_b_frames;//最大的b帧数量
}
AVFrame:
在FFmpeg中,未压缩的图像用AVFrame结构来表示。在AVFrame结构中,所包含的最重要的结构即图像数据的缓存区。待编码图像的像素数据保存在AVFrame结构的data指针所指向的内存区。在保存图像像素数据时,存储区的宽度有时会大于图像的宽度,这时可以在每一行像素的末尾填充字节。此时,存储区的宽度可以通过AVFrame的linesize获取。其内部定义的部分结构如下:
typedef struct AVFrame{
#define AV_NUM_DATA_POINTERS 8
uint8_t *data[AV_NUM_DATA_POINTERS];//图像数据缓存区
int linesize[AV_NUM_DATA_POINTERS];//存储区的宽度
int width,height;
int format;
}
AVPacket:
AVPacket结构用于保存未解码的二进制码流的一个数据包,在该结构中,码流数据保存在data指针指向的内存区中,数据长度为size字节。在从编码器获取到输出的AVPacket结构后,可以通过data指针和size值读取编码后的码流。其内部定义的部分结构如下:
typedef struct AVPacket{
int64_t pts;//显示时间戳
int64_t dts;//解码时间戳
uint8_t *data;//码流数据
int size;
int stream_index;//所从属的stream序号
}
编码器初始化的代码如下:
//video_encoder_core.cpp
static const AVCodec* codec= nullptr;
static AVCodecContext* codec_ctx= nullptr;
static AVFrame* frame= nullptr;
static AVPacket* pkt= nullptr;
int32_t init_video_encoder(const char* codec_name){
if(strlen(codec_name)==0){
cerr<<"Error:empty codec name."<<endl;
return -1;
}
//查找编码器
codec=avcodec_find_encoder_by_name(codec_name);
if(!codec){
cerr<<"Error:could not find codec with codec name:"<<string(codec_name)<<endl;
return -1;
}
//创建编码器上下文结构的实例
codec_ctx= avcodec_alloc_context3(codec);
if(!codec_ctx){
cerr<<"Error:could not allocate video codec context."<<endl;
return -1;
}
//配置编码参数
codec_ctx->profile=FF_PROFILE_H264_HIGH;
codec_ctx->bit_rate=2000000;
codec_ctx->width=1920;
codec_ctx->height=1080;
codec_ctx->gop_size=10;//关键帧间距
codec_ctx->time_base=(AVRational){1,25};//num:分子,den:分母
codec_ctx->framerate=(AVRational){25,1};
codec_ctx->max_b_frames=3;
codec_ctx->pix_fmt=AV_PIX_FMT_YUV420P;
if(codec->id==AV_CODEC_ID_H264){
av_opt_set(codec_ctx->priv_data,"preset","slow",0);
av_opt_set(codec_ctx->priv_data,"tune","zerolatency",0);
}
//使用指定的codec初始化编码器上下文结构,并分配内存
int32_t result=avcodec_open2(codec_ctx,codec, nullptr);
if(result<0){
cerr<<"Error:could not open codec"<<endl;
return -1;
}
pkt=av_packet_alloc();
if(!pkt){
cerr<<"Error:could not allocate AVPacket."<<endl;
return -1;
}
frame=av_frame_alloc();
if(!frame){
cerr<<"Error:could not allocate AVFrame."<<endl;
return -1;
}
frame->width=codec_ctx->width;
frame->height=codec_ctx->height;
frame->format=codec_ctx->pix_fmt;
result= av_frame_get_buffer(frame,0);//给AVFrame结构中的音视频数据分配空间
if(result<0){
cerr<<"Error:could not get AVFrame buffer."<<endl;
return -1;
}
return 0;
}
3.编码循环体
在编码循环体中,至少需要实现以下三个功能:
(1)从视频源中循环获取输入图像
(2)将当前帧传入编码器进行编码,获取输出的码流包
(3)输出码流包中的压缩码流到输出文件
读取图像数据和写出码流数据:
//io_data.cpp
int32_t read_yuv_to_frame(AVFrame* frame){
int32_t frame_width=frame->width;
int32_t frame_height=frame->height;
int32_t luma_stride=frame->linesize[0];
int32_t chroma_stride=frame->linesize[1];
int32_t frame_size=frame_width*frame_height*3/2;
int32_t read_size=0;
if(frame_width==luma_stride){
//如果width等于stride,则说明frame中不存在padding字节,可整体读取
read_size+=fread(frame->data[0],1,frame_width*frame_height,input_file);
read_size+=fread(frame->data[1],1,frame_width*frame_height/4,input_file);
read_size+=fread(frame->data[2],1,frame_width*frame_height/4,input_file);
}
else{
//如果width不等于stride,则说明frame中存在padding字节
//对三个分量应该逐行读取
for(size_t i=0;i<frame_height;i++){
read_size+=fread(frame->data[0]+i*luma_stride,1,frame_width,input_file);
}
for(size_t uv=1;uv<=2;uv++){
for(size_t i=0;i<frame_height/2;i++){
read_size+=fread(frame->data[uv]+i*chroma_stride,1,frame_width/2,input_file);
}
}
}
if(read_size!=frame_size){
cerr<<"Error:Read data error,frame_size:"<<frame_size<<",read_size:"<<read_size<<endl;
return -1;
}
return 0;
}
void write_pkt_to_file(AVPacket* pkt){
fwrite(pkt->data,1,pkt->size,output_file);
}
编码一帧图像数据:
//video_encoder_core.cpp
static int32_t encode_frame(bool flushing){
int32_t result=0;
if(!flushing){
cout<<"Send frame to encoder with pts:"<<frame->pts<<endl;
}
result=avcodec_send_frame(codec_ctx,flushing? nullptr:frame);
if(result<0){
cerr<<"Error:avcodec_send_frame failed."<<endl;
return result;
}
while(result>=0){
result= avcodec_receive_packet(codec_ctx,pkt);
if(result==AVERROR(EAGAIN)||result==AVERROR_EOF){//尚未完成对新一帧的编码,要传入后续帧或编码器已完全输出内部缓存的码流
return 1;
}
else if(result<0){
cerr<<"Error:avcodec_receive_packet failed."<<endl;
return result;
}
if(flushing){
cout<<"Flushing:";
}
cout<<"Got encoded package with dts:"<<pkt->dts<<",pts:"<<pkt->pts<<", "<<endl;
write_pkt_to_file(pkt);
}
return 0;
}
编码循环体的整体实现:
//video_encoder_core.cpp
int32_t encoding(int32_t frame_cnt){
int result=0;
for(size_t i=0;i<frame_cnt;i++){
result= av_frame_make_writable(frame);//确保AVFrame是可写的
if(result<0){
cerr<<"Error:could not av_frame_make_writable."<<endl;
return result;
}
result= read_yuv_to_frame(frame);
if(result<0){
cerr<<"Error:read_yuv_to_frame failed."<<endl;
return result;
}
frame->pts=i;
result= encode_frame(false);
if(result<0){
cerr<<"Error:encode_frame failed."<<endl;
return result;
}
}
result= encode_frame(true);
if(result<0){
cerr<<"Error:flushing failed."<<endl;
return result;
}
return 0;
}
关闭编码器:
//video_encoder_core.cpp
void destroy_video_encoder(){
avcodec_free_context(&codec_ctx);
av_frame_free(&frame);
av_packet_free(&pkt);
}
最终main函数的实现如下:
int main(){
const char* input_file_name= "../input.yuv";
const char* output_file_name= "../output.h264";
const char* codec_name= "libx264";
int32_t result= open_input_output_files(input_file_name,output_file_name);
if(result<0){
return result;
}
result=init_video_encoder(codec_name);
if(result<0){
return result;
}
result=encoding(250);
if(result<0){
return result;
}
destroy_video_encoder();
close_input_output_files();
return 0;
}
执行完成后会生成码流文件output.h264,使用ffplay可以播放该文件,查看编码结果。
如何使用libavcodec将.yuv图像序列编码为.h264的视频码流?的更多相关文章
- 嵌入式 视频编码(H264)
这几天在编写视频录制模块,所以,闲暇之余,又粗粗的整理了一下,主要是API,以备不时之用 摄像头获取的模拟信号通过经芯片处理(我们使用的是CX25825),将模拟信号转成数字信号,产生标准的IT ...
- 嵌入式 视频编码(H264)hi3518
这几天在编写视频录制模块,所以,闲暇之余,又粗粗的整理了一下,主要是API,以备不时之用 摄像头获取的模拟信号通过经芯片处理(我们使用的是CX25825),将模拟信号转成数字信号,产生标准的IT ...
- 直接将视频文件原码流转换成YUV,输出到屏幕显示
#include "stdafx.h" #define inline _inline#ifndef INT64_C#define INT64_C(c) (c ## LL)#defi ...
- 【转载】视频编码(H264概述)
一视频编码介绍 1.1 视频压缩编码的目标 1)保证压缩比例 2)保证恢复的质量 3)易实现,低成本,可靠性 1.2 压缩的出发点(可行性) 1)时间相关性 在一组视频序列中,相邻相邻两帧只有极少的不 ...
- 利用zxing制作彩色,高容错,支持中文等UTF编码的QR二维码图片
利用zxing制作彩色,高容错,支持中文等UTF编码的QR二维码图片.代码如下 import java.awt.Color;import java.io.File;import java.util.H ...
- 使用iconv进行编码gb2312转utf8 转码失败的坑
iconv 编码gb2312转utf8 转码失败的坑 使用背景 项目中使用thrift进行C#程序调用c++接口,其中的协议是通过json进行传输的,由于默认thrift使用utf8进行传输,而C#和 ...
- javacpp-FFmpeg系列之2:通用拉流解码器,支持视频拉流解码并转换为YUV、BGR24或RGB24等图像像素数据
javacpp-ffmpeg系列: javacpp-FFmpeg系列之1:视频拉流解码成YUVJ420P,并保存为jpg图片 javacpp-FFmpeg系列之2:通用拉流解码器,支持视频拉流解码并转 ...
- H264编码原理以及I帧、B和P帧详解, H264码流结构分析
H264码流结构分析 http://blog.csdn.net/chenchong_219/article/details/37990541 1.码流总体结构: h264的功能分为两层,视频编码层(V ...
- 【H264】视频编码发展简史
一.常见视频编码格式 编码格式有很多,如下图: 目前比较常用的编码有: H26x系列:由ITU(国际电传视讯联盟)主导,侧重网络传输 MPEG系列:由ISO(国际标准组织机构)下属的MPEG(运动图象 ...
- YUV颜色编码解析(转)
原文转自 https://www.jianshu.com/p/a91502c00fb0
随机推荐
- 生成器、迭代器、高级函数、map、reduce和filter
1.创建生成器(generation)的两种方法: 第一种就是通过将列表生成式的{}改为() 第二种就是函数中包含yield关键字的函数 2.迭代器是指可以不断返回下一个值的对象,我们可以导入from ...
- 【Vue项目】尚品汇(四)Search组件开发
Search模块开发 分析:1)编写静态页面 2)编写api 3)编写vuex三大件 4)组件获取仓库数据,并进行动态展示 1 SearchSelector 1 编写api export const ...
- 如何在 .NET Core WebApi 中处理 MultipartFormDataContent
最近在对某个后端服务做 .NET Core 升级时,里面使用了多处处理 MultipartFormDataContent 相关内容的代码.这些地方从 .NET Framework 迁移到 .NET C ...
- 天梯赛L1-027 出租
一.问题描述 下面是新浪微博上曾经很火的一张图: 一时间网上一片求救声,急问这个怎么破.其实这段代码很简单,index数组就是arr数组的下标,index[0]=2 对应 arr[2]=1,index ...
- 《爆肝整理》保姆级系列教程-玩转Charles抓包神器教程(15)-Charles如何配置反向代理
1.简介 在App开发的过程当中,抓包是一个很常见的需求,而有些app的请求不会在网络设置代理时被抓到数据包,这里若是需要抓包就需要搭建反向代理. 2.什么是代理? 什么是代理,来一张图了解一下. 代 ...
- java中各引用类型的生存时间
引用类型由上往下一次减弱: 强引用:Object obj=new Object(),无论什么情况下,只要强引用关系还存在,就不会回收被引用的对象. 软引用:像系统中缓存这些,在系统即将报内存溢出异常时 ...
- 2020-01-20:mysql中,一张表里有3亿数据,未分表,要求是在这个大表里添加一列数据。数据库不能停,并且还有增删改操作。请问如何操作?
2020-01-20:mysql中,一张表里有3亿数据,未分表,要求是在这个大表里添加一列数据.数据库不能停,并且还有增删改操作.请问如何操作?福哥答案2020-01-20: 陌陌答案:用pt_onl ...
- 【GiraKoo】Github无法打开,导致无法下载Git安装包
环境 Windows 11 原因 Git应用的安装程序在Github上,由于Github访问不稳定,导致无法下载. 对策 打开迅雷.将下载链接拷贝进去,利用迅雷的P2P技术,从其他网友处进行下载. 打 ...
- 二进制部署k8s集群
部署k8s有多种方式,本章我们采取二进制的部署方式来部署k8s集群,二进制部署麻烦点,但是可以在我们通过部署各个组件的时候,也通知能让我们更好的深入了解组件之间的关联,也利于后期维护 主机环境 系统: ...
- WPF入门实例 WPF完整例子 WPF DEMO WPF学习完整例子 WPF实战例子 WPF sql实例应用 WPF资料源码
WPF 和 WinForms 都是用于创建 Windows 桌面应用程序的开发框架,它们有一些相似之处,但也有很多不同之处. 在开发速度方面,这取决于具体情况.如果您熟悉 WinForms 开发并且正 ...