ffmpeg 采集 yuyv422数据 转 yuy420数据 后 编码 播放

实验 ffmpeg 采集 yuyv422数据 转 yuy420数据 后 编码 播放

#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;
}


/*  将视频帧数据 送给 编码器进行编码 ,将编码后的数据 写到目标文件中
参数1 编码器上下文
参数2 libx264编码器的视频输入数据存储空间
参数3 libx264编码器的视频输出数据存储空间
参数4 写到目标文件
*/
static void encode(AVCodecContext *enc_ctx,
                   AVFrame *frame,
                   AVPacket *newpkt,
                   FILE *outfile){
    
    int ret = 0;
    
    if(!enc_ctx){
        printf("Error, enc_ctx is NULL\n");
    }

    if(frame){
        printf("send frame to encoder, pts=%lld", frame->pts);
    }
	
    //送原始数据(yuv420p)给编码器进行编码
	/*
	参数1:编码器上下文
	参数2:需要编码的视频数据
	*/
    ret = avcodec_send_frame(enc_ctx, frame);
    if(ret < 0) {
        printf("Error, Failed to send a frame for enconding!\n");
        exit(1);
    }
    
	//如果ret>=0说明数据送给编码器成功,此后 我们才能从编码器中去获取编码好的视频帧数据
    while(ret >=0) {
		
		
		//获取编码后的视频数据,如果成功,需要重复获取,直到失败为止
		/*
		我们向编码器送一帧视频帧数据的时候,编码器不一定就会马上返回一个AVPacket视频帧。
		有可能是我们送了很多帧视频数据后,编码器才会返回一个编码好的AVPacket视频帧。也有
		可能同时返回多个编码好的AVPacket视频帧。
		
		参数1:编码器上下文
		参数2:编码器编码后的视频帧数据
		*/
        ret = avcodec_receive_packet(enc_ctx, newpkt);
        
        //如果编码器数据不足时会返回  EAGAIN,或者到数据尾时会返回 AVERROR_EOF
				/*
		ret > 0  表示本次获取成功,然后继续循环获取,因为有可能后面还有编码好的数据需要获取
		ret == AVERROR(EAGAIN) : 表示当前编码没有任何问题,但是输入的视频频帧不够,所以当前没有packet输出,需要继续送入frame进行编码码
		ret == AVERROR_EOF :内部缓冲区中数据全部编码完成,不再有编码后的数据包输出。编码到最后了 没有任何数据了
		other ,ret < 0 && ret!= AVERROR(EAGAIN) && ret!=AVERROR_EOF  :编码错误 直接退出
		*/
        if(ret == AVERROR(EAGAIN)){
		//	printf("data is not enough\n");
			return;
		} 
		if(ret == AVERROR_EOF){
		//	printf("encoding end\n");
			return;
		}else if( ret < 0){ // 编码错误直接退出
			printf("Error, encoding video frame\n");
			exit(1);
		}
        
        fwrite(newpkt->data, 1, newpkt->size, outfile);
        av_packet_unref(newpkt);//减少newpkt引用计数
    }
}


void rec_audio() {

    int ret = 0;
	int base = 0;
	int i = 0;
	int j = 0;
	int k = 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) {
		
        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);
		
		/* pts 暂时理解为每一帧的编号
		对于 x264编码器来说,它要求我们送数据的时候 要求我们送进去的每一帧的pts都是连续的值,
		这样它才会编码号我们送进去的数据。
		
		pts这个值非常重要,必须要做处理。
		*/
		frame->pts = base++;
		
		/*
		参数1 编码器上下文
		参数2 libx264编码器的视频输入数据存储空间
		参数3 libx264编码器的视频输出数据存储空间
		参数4 写到目标文件
		*/
		encode(enc_ctx, frame, newpkt, outfile);
		
        av_packet_unref(&pkt); //release pkt
    }
	
	/*再次调用编码器,强制让编码器编码缓存中剩下的视频帧数据 并吐出
	此时不送数据到编码器,让编码器对缓冲区中剩下的数据进行编码并吐出
	*/
	encode(enc_ctx, NULL, newpkt, outfile);
	
__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;
}

ffplay video.h264
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Linux老A

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

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

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

打赏作者

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

抵扣说明:

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

余额充值