参考文章
参考了这两篇文章还有网上的文章大部分都是从解码器输出的frame中的视频数据进行格式转换。由于自己刚刚入门,有一个需求是对packet.data摄像头读取的原始yuv数据进行格式转换,我的环境是Ubuntu18.04,在使用AVDictionary *option 设置图像的参数的时候使用,都能设置图像的分辨率和帧率
av_dict_set(&option,"video_size","640x480",0);
av_dict_set(&option,"framerate","30",0);
但是我使用
av_dict_set(&option,"pixel_format","nv12",0)
设置yuv视频格式的时候明明函数返回执行成功了,但是视频格式还是没有被设置成nv12的yuv的视频格式。写入文件的数据还是yuyv422的格式,参考了雷神的文章后,知道了使用sws_scale()函数进行操作,但是围绕sws_scale()函数进行的配套辅助操作自己并不是很明白,在看来这些文章思考了一段时间联想到了音频的重采样,音频重采样不就是对音频三元组中的采样率、采样大小、通道数这些音频属性进行改变吗?视频数据格式的转换不就是不也是视频属性的改变吗?在音频重采样中不也是从设备读取的原始数据packet.data中获取数据吗?于是我仿照音频重采样的思路也构造了转换前后的数据缓冲区src_frame和dst_frame.在进行转换结果证明自己的猜想是对的。从packet.data中的数据进行格式转换需要创建frame,包括把数据输入前后也要构造输入输出数据frame。
代码演示
void h264_codec(void)
{
char errors[1024]={0};
int file_fd=0;
//create file
file_fd=open("./video.yuv",O_CREAT|O_RDWR,0666);
//ctx
AVFormatContext *fmt_ctx=NULL;
AVDictionary *option =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);
//转换前后frame
AVFrame *src_frame,*dst_frame;
src_frame=av_frame_alloc();
src_frame->width=640;
src_frame->height=480;
dst_frame=av_frame_alloc();
dst_frame->width=640;
dst_frame->height=480;
//转换前后数据缓冲区
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++<300)
{
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,
480,
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分量
av_packet_unref(&pkt);
}
close(file_fd);
avformat_close_input(&fmt_ctx);
av_log_set_level(AV_LOG_DEBUG);
}
当然上面的代码是将yuyv的视频格式转换为420p的视频格式,再上面的代码上对参数稍微作一些参数的修改也可以将packet.data(格式为yuyv)中的数据转换为其他的yuv格式或者是RGB格式。