ffmpeg 采集 yuyv422数据 转成 yuy420数据 并播放

本实验需要 libx264库

安装 libx264


wget https://code.videolan.org/videolan/x264/-/archive/master/x264-master.tar.bz2
2. bunzip2 last_x264.tar.bz2
3. tar -vxf last_x264.tar
4.  ./configure --prefix=/usr/local/libx264 --enable-static --enable-shared --disable-asm --disable-avs
5. make && sudo make install



配置环境变量

export LD_LIBRARY_PATH=$LIB_LIBRARY_PATH:/usr/local/libx264/lib
export PKG_CONFIG_PATH=/usr/local/libx264/lib/pkgconfig

查看是否已经设好,可用命令export查看
已知 ffmpeg 环境变量:
export

..
declare -x LD_LIBRARY_PATH=":/usr/local/libfdk_acc/lib"
declare -x OLDPWD="/home/mhr/Desktop/video/libx264"
declare -x PWD="/home/mhr/Desktop/video/libx264/x264-master"
...


回到 ffmpeg下
重新配置编译:包含libx264

./configure --prefix=/usr/local/ffmpeg --enable-gpl  --enable-nonfree  --enable-libfdk-aac   --enable-libx264  --enable-filter=delogo  --enable-debug  --disable-optimizations --enable-shared   --enable-pthreads


make
sudo make install

实验:

#include <stdio.h>
#include <string.h>
#include "libavutil/avutil.h"
#include "libavdevice/avdevice.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswresample/swresample.h"

#define V_WIDTH 640
#define V_HEIGTH 480


/*打开编码器
参数1 分辨率宽
参数2 分辨率高
参数3 获取编码器上下文 enc_ctx,这是输出参数
注意:C语言只会以值拷贝的方式传递参数,这里我们需要传递一个空指针作为输出参数,
			如果直接传递空指针,那么函数会以值拷贝的方式 生成一个临时的空指针变量,函数内部
			为都是围绕临时变量工作,我们所传空指针没有被输出。所以这里需要传递二维指针!
*/
static void open_encoder(int width, int height, AVCodecContext **enc_ctx)
{
    
    int ret = 0;
    AVCodec *codec = NULL;
    
	//通过名字 查找获取 libx264编码器
    codec = avcodec_find_encoder_by_name("libx264");
    if(!codec){
        printf("Codec libx264 not found\n");
        exit(1);
    }
    
	//创建编码器上下文
    *enc_ctx = avcodec_alloc_context3(codec);
    if(!enc_ctx){
        printf("Could not allocate video codec context!\n");
        exit(1);
    }
    
    //SPS/PPS
	/*
	SPS 序列参数集,作用于一串连续的视频图像,对帧组的参数设置。
	PPS 图像参数集,作用于视频序列中的图像,对GOP一组视频帧的 每一个图像的设置
	
	H264 Profile 对视频压缩特性的描述
	H264 Level 对视频的描述(与 码率,分辨率,fps 成正比)
	*/
    (*enc_ctx)->profile = FF_PROFILE_H264_HIGH_444;
    (*enc_ctx)->level = 50; //表示LEVEL是5.0  清晰度高
    
    //设置分辫率
    (*enc_ctx)->width = width;   //640
    (*enc_ctx)->height = height; //480
    
    //GOP
	/*
	gop_size :GOP的大小,表示一个GOP组的最大帧数目,即两个I帧之间的最大帧数目
	keyint_min :一个GOP组的最下帧数目,即最小25帧可以插入一个I帧
		场景:如果一个GOP组比较大,但是中间有发生图像很明显变化的时候,当很明显的变化达到一定数值,会自动插入一个I帧
			所以当丢帧时,能有多快恢复正常,不至于出现卡顿,此时 keyint_min参数就很重要。因为如果出现丢帧 GOP大小不变的情况下
				等到下一个I帧出现的时间比较久,会出现明显卡顿,此时减少GOP大小,缩短I帧之间的间隔,快速恢复丢帧导致的卡顿问题。
	*/
    (*enc_ctx)->gop_size = 250;
    (*enc_ctx)->keyint_min = 25;  //可选
    
    //设置B帧数据,为了减少码流大小
	/* B帧压缩比大,只占I帧的1/4,所以为了减少码流大小,增加使用B帧
			但是注意 B帧很占用CPU,而且很耗时,B帧越多,延迟性也会越大
	max_b_frames 一个GOP组的B帧数量,一般设置B帧不超过3帧
	has_b_frames 
	*/
    (*enc_ctx)->max_b_frames = 3; //可选
    (*enc_ctx)->has_b_frames = 1; //可选
    
    //参考帧的数量 用于解码
	/*
	参考帧设置越多,处理越慢,但是图像还原性很好。解码后的数据与编码前原始数据越 差别越小
	参考帧设置越小,处理越快,但是图像还原性的就差很多。解码后的数据与编码前原始数据越 差别越大
	*/
    (*enc_ctx)->refs = 3;    //可选
    
    //设置编码器输入数据YUV格式 libx264编码器要求的输入数据格式是 AV_PIX_FMT_YUV420P
    (*enc_ctx)->pix_fmt = AV_PIX_FMT_YUV420P;
    
    //设置码率  640*480 25帧  平均码率600Kbps
    (*enc_ctx)->bit_rate = 600000; //600kbps
    
    //设置帧率
	/*
	time_base :(AVRational){1, 25} 表示当 25帧/每秒,帧与帧之间的间隔时间。
	framerate :帧率,25帧/每秒
	*/
    (*enc_ctx)->time_base = (AVRational){1, 25}; //帧与帧之间的间隔是time_base
    (*enc_ctx)->framerate = (AVRational){25, 1}; //帧率,每秒 25 帧
    
	
	//打开编码器
	/*
	参数1: 编码器上下文
	参数2: 获取的编码器
	参数3:其他选项 暂时不用
	*/
    ret = avcodec_open2((*enc_ctx), codec, NULL);
    if(ret<0){
        printf("Could not open codec: %s!\n", av_err2str(ret));
        exit(1);
    }
}

/* 申请 libx264编码器 视频输入数据空间
参数 :分辨率
*/
static AVFrame* create_frame(int width, int height){
    
    int ret = 0;
	//视频输入数据 空间初始化,它包含了 存放未编码的视频数据的空间
    AVFrame *frame = NULL;
    
	//视频输入数据 空间初始化,它包含了 存放未编码的视频数据的空间
	//初始化 AVFrame 结构
    frame = av_frame_alloc();
    if(!frame){
        printf("Error, No Memory!\n");
        goto __ERROR;
    }
    
    //设置 AVFrame 结构 信息,用于指定存储数据空间的大小
	/*
	分辨率
	数据格式,libx264 编码器要求输入数据格式必须是 AV_PIX_FMT_YUV420P
	*/
    frame->width = width;
    frame->height = height;
    frame->format = AV_PIX_FMT_YUV420P;
    
   	/* 为 AVFrame结构中用于存放未编码的视频数据的空间 分配空间
	参数1: AVFrame
	参数2: 对齐方式	
	*/
    ret = av_frame_get_buffer(frame, 32); //按 32 位对齐
    if(ret < 0){
        printf("Error, Failed to alloc buffer for frame!\n");
        goto __ERROR;
    }
    
    return frame;
    
__ERROR:
    
    if(frame){
        av_frame_free(&frame);
    }
    
    return NULL;
}

void rec_audio() {

    int ret = 0;
    char errors[1024] = {0, };

    //视频数据上下文
    AVFormatContext *fmt_ctx = NULL;
    AVDictionary *options = NULL;
	
	//编码器上下文
	AVCodecContext *enc_ctx = NULL;

    //pakcet 存储获取的视频数据
    AVPacket pkt;

	//视频设备节点
    char *devicename = "/dev/video0";

    //set log level
    av_log_set_level(AV_LOG_DEBUG);

    //register audio device  向ffmpeg注册设备
    avdevice_register_all();

    //设置采集方式,对于不同的平台,采集数据的方式不同  linux系统是 
    /*
    返回值:输入格式
    */
	AVInputFormat *iformat = av_find_input_format("v4l2");

	/* 设置视频其他参数
	参数1:视频参数 options
	参数2:指定    -video_size 分辨率参数 ; -framerate 帧率
	参数3:分辨率
	参数4:暂时不关心
	*/
	av_dict_set(&options,"video_size","640x480",0);
	av_dict_set(&options,"framerate","30",0);
	av_dict_set(&options, "pixel_format", "yuyv422", 0);

    //打开视频设备
    /*
	参数1 获得 视频数据上下文 AVFormatContext
	参数2 网络地址/本地文件(设备名)
	参数3 输入格式
	参数4 其他参数 这里为NULL
    */
    if((ret = avformat_open_input(&fmt_ctx, devicename, iformat, &options)) < 0 ){
        av_strerror(ret, errors, 1024);
        fprintf(stderr, "Failed to open video device, [%d]%s\n", ret, errors);
        return;

    }
	
	/*打开编码器
	
	参数1 分辨率宽
	参数2 分辨率高
	参数3 获取编码器上下文 enc_ctx,这是输出参数
		注意:C语言只会以值拷贝的方式传递参数,这里我们需要传递一个空指针作为输出参数,
			如果直接传递空指针,那么函数会以值拷贝的方式 生成一个临时的空指针变量,函数内部
			为都是围绕临时变量工作,我们所传空指针没有被输出。所以这里需要传递二维指针!
	*/
	open_encoder(V_WIDTH, V_HEIGTH, &enc_ctx);
	
	
	//创建 AVFrame,包含 libx264编码器的视频输入数据存储空间
	//分配编码前保存AVFrame格式的视频数据空间
	/* 
	参数 :分辨率
	*/
    AVFrame* frame = create_frame(V_WIDTH, V_HEIGTH);
	
	//创建 AVPacket,包含 libx264编码器的视频输出数据存储空间
	//分配 编码后保存AVPacket格式的视频数据的空间 
    AVPacket *newpkt = av_packet_alloc();
    if(!newpkt){
        printf("Error, Failed to alloc avpacket!\n");
        goto __ERROR;
    }

    //创建输出的视频文件  将视频数据写到该文件
	char *yuvout = "/home/mhr/Desktop/video/video_test/video.yuv";
	char *out = "/home/mhr/Desktop/video/video_test/video.h264";
	
    FILE *outfile = fopen(out, "wb+");
	FILE *yuvoutfile = fopen(yuvout, "wb+");
	
	/* read data from device  获取视频数据 到pkt
	参数1: 视频数据上下文
	参数2: 视频数据存放的目标地址
	返回值:return 0 is OK
	*/
    while((ret = av_read_frame(fmt_ctx, &pkt)) == 0) {
		
		int i = 0;
		int j = 0;
		int k = 0;

        av_log(NULL, AV_LOG_INFO,"packet size is %d(%p)\n", pkt.size, pkt.data);

		
		//YUYVYUYV    	   yuyv422
        //YYYYYYYYUUVV YUV420 ,Y数据,数据包前307200个数据, 640*480=307200 每个像素点有一个Y分量(亮度)
		
		/*
		抽取 YUV 数据,
			yuyv422总字节:2*640*480=614400
			总像素640*480=307200 每个像素点有一个Y分量
		data[0] 存放 YUV420格式 Y 数据
		data[1] 存放 YUV420格式 U 数据
		data[2] 存放 YUV420格式 V 数据
		*/
	
		//抽取Y分量
		for(i=0; i < 307200; i++)
		{	
			// Y : 0 2 4 6 ...
			frame->data[0][i] = pkt.data[i*2];
		}
		
		//抽取U分量
		for(i=0; i<V_HEIGTH; i++){ 
			//丢弃偶数行的uv分量
			if((i%2)!=0)continue;  
			//提取目标列U分量
			for(j=0;j<(V_WIDTH/2);j++){  
				if((4*j+1)>(2*V_WIDTH))
					break;  
				frame->data[1][k++]=pkt.data[i*2*V_WIDTH+4*j+1];  
			}   
		}  
		
		k = 0;
		
		//抽取V分量  
        for(i=0;i<V_HEIGTH;i++){  
			//丢弃偶数行的uv分量
			if((i%2)==0)
				continue;  
			//提取目标列V分量
			for(j=0;j<(V_WIDTH/2);j++){  
				if((4*j+3)>(2*V_WIDTH))
					break;  
               frame->data[2][k++]=pkt.data[i*2*V_WIDTH+4*j+3];
           }  
        }

		//将抽取的 Y V U分量 按照YUV420格式写到 video.yuv文件
		fwrite(frame->data[0], 1, 307200, yuvoutfile);
        fwrite(frame->data[1], 1, 307200/4, yuvoutfile);
        fwrite(frame->data[2], 1, 307200/4, yuvoutfile);
		
        av_packet_unref(&pkt); //release pkt
    }
	
	
__ERROR:
    if(yuvoutfile){
        //close file
        fclose(yuvoutfile);
    }
    
    //关闭设备 并 释放视频数据上下文
    if(fmt_ctx) {
        avformat_close_input(&fmt_ctx);
    }

    av_log(NULL, AV_LOG_DEBUG, "finish!\n");
    
    return;
}

int main(int argc, char *argv[])
{
    rec_audio();
    return 0;
}

实验环境摄像头采集的视频格式是 yuyv422, 经过格式转换为 yuv420p

播放:ffplay -pix_fmt yuv420p -s 640x480 video.yuv

在这里插入图片描述

播放Y分量
ffplay -pix_fmt yuv420p -s 640x480 -vf extractplanes=‘y’ video.yuv

在这里插入图片描述

播放 U分量,注意 播放格式
ffplay -pix_fmt yuv420p -s 640x480 -vf extractplanes=‘u’ video.yuv
在这里插入图片描述

播放 V分量,注意 播放格式
ffplay -pix_fmt yuv420p -s 640x480 -vf extractplanes=‘v’ video.yuv

在这里插入图片描述

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Linux老A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值