需要注意的是从Ubuntu采集到的yuv数据是yuyv422格式的yuv数据,这个格式的原始数据是不能输入编码器进行编码的,所以我们要对yuv的格式进行一下转换参考
在Linux环境下使用ffmpeg将YUYV格式的yuv数据转换成yuv420p格式的yuv数据
实现步骤
前面已经通过设备获取到yuv格式的数据,并且进行了格式转换,现在只需要创建x264编码器并对编码器进行一些设置然后向编码器喂数据,和读数据即可,具体步骤如下:
1、查找编码器
codec=avcodec_find_encoder_by_name(“libx264”)这里需要注意如果此函数返回NULL可能是此前编译ffmpeg的时候没有–enable-gpl and --enable-libx264选项,当然可能还有其他可能。
2、创建编码器上下文
enc_ctx=avcodec_alloc_context3(codec)
编码器上下文创建成功就可以通过上下文对编码器进行一些初始化的设置。
3、打开编码器
avcodec_open2(enc_ctx,codec,NULL)
4、向编码器输入数据
avcodec_send_frame(enc_ctx,dst_frame)
5、读取编码器数据
avcodec_receive_packet(enc_ctx,newpacket)
代码演示
#include "ffmpeg_function.h"
/*********************
函数名称:static AVCodecContext* open_encoder(int width,int height,AVCodecContext *enc_ctx)
函数作用:初始化编码器和设置编码器相关参数
函数参数:int width与int height为编码图像的分辨率,AVCodecContext *enc_ctx编码器上下文指针
函数返回值:AVCodecContext *enc_ctx编码器上下文指针
*************************/
static AVCodecContext* open_encoder(int width,int height,AVCodecContext *enc_ctx)
{
AVCodec *codec=NULL;
codec=avcodec_find_encoder_by_name("libx264"); //查找编码器
if (NULL==codec)
{
printf("find libx264 error\n");
exit(1);
}
enc_ctx=avcodec_alloc_context3(codec); //创建编码器上下文
if (NULL==enc_ctx)
{
printf("could not alloc vidio for codec\n");
}
//SPS/PPS
enc_ctx->profile=FF_PROFILE_H264_HIGH_444;
enc_ctx->level=50;//表示level是5.0
//设置分辨率
enc_ctx->width=width;
enc_ctx->height=height;
//设置GOP相关参数
enc_ctx->gop_size=250; //设置gop大小
enc_ctx->keyint_min=25;//设置在一组gop中插入I帧的最小间隔
//设置B帧数量
enc_ctx->max_b_frames=3;
enc_ctx->has_b_frames=1;
//设置参考帧的数量
enc_ctx->refs=3;
//设置输入的YUV编码数据的像素格式
enc_ctx->pix_fmt=AV_PIX_FMT_YUV420P;
//设置码率/码流大小
enc_ctx->bit_rate=600000; //600kbps
//设置时间戳(帧率)
enc_ctx->time_base=(AVRational){1,25}; //帧与帧之间的间隔是25帧time_base
enc_ctx->framerate=(AVRational){25,1}; //帧率,每秒25帧
int ret=avcodec_open2(enc_ctx,codec,NULL); //打开编码器
if (ret<0)
{
printf("could open codec\n");
exit(1);
}
return enc_ctx;
}
/*********************
函数名称:static void encode(AVCodecContext *enc_ctx,AVFrame *dst_frame,int file_fd264,AVPacket *newpacket)
函数作用:进行编码器编码
函数参数:AVCodecContext *enc_ctx编码器上下文指针
AVFrame *dst_frame 编码器输入数据缓冲区
int file_fd264写h264文件描述符
AVPacket *newpacket编码器输出数据packet
函数返回值:无
*************************/
static void encode(AVCodecContext *enc_ctx,AVFrame *dst_frame,int file_fd264,AVPacket *newpacket)
{
int ret=0;
if (NULL!=dst_frame)
{
printf("send frame to encoder,pts=%lld\n",dst_frame->pts);
}
ret=avcodec_send_frame(enc_ctx,dst_frame); //给编码器喂数据
if (ret<0)
{
printf("error failded to send a frame encodeing\n");
exit(1);
}
while (ret>=0)
{
ret=avcodec_receive_packet(enc_ctx,newpacket); //从编码器读取编码器好的数据
//如果编码器数据不足时就会返回EAGIN,或者到数据尾时会返回AVERROR_EOF
if(ret==AVERROR(EAGAIN)||ret==AVERROR_EOF) return;
else if (ret<0) //编码器出错,退出程序
{
printf("error fialded to encode!\n");
exit(1);
}
write(file_fd264,newpacket->data,newpacket->size);
av_packet_unref(newpacket);
}
}
void h264_codec(void)
{
char errors[1024]={0};
int file_fd=0;
int file_fd264=0;
int base=0;
//create file
file_fd264=open("./video.h264",O_CREAT|O_RDWR,0666);
file_fd=open("./video.yuv",O_CREAT|O_RDWR,0666);
//ctx
AVFormatContext *fmt_ctx=NULL; //视频设备上下文
AVDictionary *option =NULL;
AVCodecContext *enc_ctx=NULL; //编码器上下文
//格式转换结构体
struct SwsContext *img_convert_ctx = NULL;
//packet
int count =0;
AVPacket pkt;
char *devicename="/dev/video0";
//注册设备
avdevice_register_all();
AVInputFormat *iformat=av_find_input_format("video4linux2");
if(av_dict_set(&option,"pixel_format","nv12",0)<0) //指定格式
{
printf("set pixel_format error\n");
return;
}
av_dict_set(&option,"video_size","640x480",0);
av_dict_set(&option,"framerate","30",0); //指定帧率
//打开设备
int ret=avformat_open_input(&fmt_ctx,devicename,iformat,&option);
if (ret<0)
{
av_strerror(ret,errors,1024);
return;
}
av_init_packet(&pkt);
//打开编码器
enc_ctx=open_encoder(640,480,enc_ctx);
AVPacket *newpacket=av_packet_alloc();
if (NULL==newpacket)
{
printf("av_packet_alloc error\n");
exit(1);
}
//转换前后frame
AVFrame *src_frame,*dst_frame;
src_frame=av_frame_alloc();
src_frame->width=640;
src_frame->height=480;
src_frame->format=AV_PIX_FMT_YUYV422;
dst_frame=av_frame_alloc();
dst_frame->width=640;
dst_frame->height=480;
dst_frame->format=AV_PIX_FMT_YUV420P;
av_frame_get_buffer(dst_frame,32);
//转换前后数据缓冲区
uint8_t * src_buffer;
uint8_t * dts_buffer;
//构建转换后frame数据缓冲区
dts_buffer=(uint8_t*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
dst_frame->width,
dst_frame->height,
1));
av_image_fill_arrays(dst_frame->data,
dst_frame->linesize,
dts_buffer,
AV_PIX_FMT_YUV420P, //转换成为的格式
dst_frame->width,
dst_frame->height,
1);
//构建转换前frame数据缓冲区
src_buffer=(uint8_t*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUYV422,
src_frame->width,
src_frame->height,
1));
av_image_fill_arrays(src_frame->data,
src_frame->linesize,
src_buffer,
AV_PIX_FMT_YUYV422, //转换前的格式
src_frame->width,
src_frame->height,
1);
//分配并返回SwsContext,使用sws_scale()进行缩放/转换操作
img_convert_ctx = sws_getContext(src_frame->width,
src_frame->height,
AV_PIX_FMT_YUYV422,
dst_frame->width,
dst_frame->height,
AV_PIX_FMT_YUV420P,
SWS_BICUBIC,
NULL,
NULL,
NULL);
while (ret=av_read_frame(fmt_ctx,&pkt)==0&&count++<100)
{
printf("packet size is %d(%p),count=%d\n",pkt.size,pkt.data,count);
memcpy(src_frame->data[0],pkt.data,pkt.size);
//格式转换操作
sws_scale(img_convert_ctx,
(const uint8_t* const*)src_frame->data,
src_frame->linesize,
0,
src_frame->height,
dst_frame->data,
dst_frame->linesize);
//具体的yuv各分量的大小结合yuv的格式结构来计算,转换后的结构为420
write(file_fd,dst_frame->data[0],307200); //Y分量
write(file_fd,dst_frame->data[1],307200/4); //U分量
write(file_fd,dst_frame->data[2],307200/4); //V分量
dst_frame->pts=base++;
encode(enc_ctx,dst_frame,file_fd264,newpacket);
av_packet_unref(&pkt);
}
encode(enc_ctx,NULL,file_fd,newpacket);
close(file_fd);
close(file_fd264);
avformat_close_input(&fmt_ctx);
av_log_set_level(AV_LOG_DEBUG);
}