ffmpeg调用USB摄像头并推RTMP流

Ubuntu上实现ffmpeg采集摄像头编码RTMP流到nginx服务器
1.环境准备
ffmpeg源码编译
https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu官网编译教程
一定要支持h264
虽说这个教程会有点问题,但我觉得你应该能克服。

2.ffmpeg框架流程
第一步:打开infmt_ctx输入封装器

AVFormatContext(视频格式上下文)

//输入封装器
AVFormatContext* infmt_ctx = NULL;
//输出封装器
AVFormatContext* outfmt_ctx = NULL;

AVFormatContext是FFMPEG解封装功能的结构体。

AVInputFormat(视频输入格式)

AVInputFormat* ifmt =NULL;
//轮询链表查找v4l2框架下的设备并获取视频输入格式
ifmt = av_find_input_format("linux4video2");

AVFormatContext结构体包含AVInputFormat 。

按照目前我的理解v4l2框架相当于一个驱动来控制USB摄像头。

//通过视频格式上下文打开USB摄像头
avformat_open_input(&infmt_ctx, "/dev/video0", ifmt, NULL)
//读取一部分视音频流并且获得一些相关的信息
avformat_find_stream_info(infmt_ctx, NULL)
//显示输入设备信息
av_dump_format(infmt_ctx, 0,"/dev/video0", 0);

第二步:打开decodec_ctx 解码器上下文

AVStream(视频流)

AVStream *in_stream = NULL;
AVStream *out_stream = NULL;

AVStream是存储每一个视频/音频流信息的结构体。AVFormatContext结构体里面包含AVStream(视频和音频)。

AVCodecContext(视频编解码上下文)

//编码器上下文
AVCodecContext* encodec_ctx = NULL;
//解码器上下文
AVCodecContext* decodec_ctx = NULL;

AVStream结构体里面都包含一个AVCodecContext

AVCodec(视频编解码器)

AVCodec* encodec = NULL;
AVCodec* decodec = NULL;

AVCodecContext结构体包含一个AVCodec。

视频流都需要编解码实现。
USB摄像头获取的视频流都需要解码才能获取里面的每一帧

//查找输入封装器的视频流
for(i = 0; i<infmt_ctx->nb_streams; i++)
{
    if (infmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
    {
        videoindex = i;
    }
    else
    {
        break;
    }
}
//初始化解码器上下文
decodec_ctx=infmt_ctx->streams[videoindex]->codec;
//根据解码器上下文的编码格式设置解码器
decodec=avcodec_find_decoder(decodec_ctx->codec_id);
//打开解码器上下文
avcodec_open2(decodec_ctx, decodec, NULL)

第三步:打开encodec_ctx 编码器上下文

//设置编码器
encodec = avcodec_find_encoder(AV_CODEC_ID_H264);
//初始化编码器上下文
encodec_ctx = avcodec_alloc_context3(encodec);
//配置编码器参数
encodec_ctx->codec_id = encodec->id;
encodec_ctx->bit_rate = 400000 ;
encodec_ctx->width = 640;
encodec_ctx->height = 480;
encodec_ctx->time_base.num = 1;
encodec_ctx->time_base.den = 30;
encodec_ctx->framerate.num = 30;
encodec_ctx->framerate.den = 1;
encodec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
//编码质量和速度
av_opt_set(encodec_ctx->priv_data, "preset", "ultrafast", 0);
av_opt_set(encodec_ctx->priv_data, "tune", "zerolatency", 0);
av_opt_set(encodec_ctx->priv_data, "crf", "24", 0);
//打开编码器上下文
avcodec_open2(encodec_ctx, encodec, NULL);

将视频帧编码为H.264格式,视频帧主要是YUV420格式,摄像头帧是YUV422格式,所以后面要进行格式转换

第四步:打开outfmt_ctx 输出封装器上下文

//初始化输出封装器上下文
avformat_alloc_output_context2(&outfmt_ctx, NULL, "flv","rtmp://127.0.0.1/live/stream");

rtmp只能推flv格式,不能直接推H.264,要将H.264格式封装成FlV。

//给输出封装器上下文添加视频流out_stream指向outfmt_ctx->stream
out_stream = avformat_new_stream(outfmt_ctx,NULL);
//复制encodec_ctx参数给out_stream
avcodec_parameters_from_context(out_stream->codecpar, encodec_ctx);
//查看输出封装器上下文内容
av_dump_format(outfmt_ctx, 0, "rtmp://127.0.0.1/live/stream", 1);
//打开outfmt_ctx的rtmp的网络输出IO
avio_open(&outfmt_ctx->pb,"rtmp://127.0.0.1/live/stream", AVIO_FLAG_WRITE);
//根据rmtp协议写入封装头
avformat_write_header(outfmt_ctx, NULL);

第五步:初始化各种所需
AVPacket (视频包裹)

AVPacket *dec_pkt,enc_pkt;
//视频包裹申请内存
dec_pkt = (AVPacket *)av_malloc(sizeof(AVPacket)); 
memset(&enc_pkt, 0, sizeof(enc_pkt));

AVPacket是存储压缩编码数据相关信息的结构体。

struct SwsContext(像素格式转换上下文)

struct SwsContext *img_convert_ctx  = NULL;
//设置像素格式转换上下文   YUV422->YUV420
img_convert_ctx  = sws_getCachedContext(img_convert_ctx, decodec_ctx->width,decodec_ctx->height,decodec_ctx->pix_fmt, encodec_ctx->width,encodec_ctx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, 0, 0, 0);

AVFrame(视频帧)

AVFrame *pFrameYUV,*pFrame ;
//初始化原始帧
pFrame = av_frame_alloc();
//初始化输出帧
pFrameYUV = av_frame_alloc();
//设置输出帧
pFrameYUV->format = AV_PIX_FMT_YUV420P;
pFrameYUV->width = 640;
pFrameYUV->height = 480;
//对pFrameYUV的数据区域进行提前分配
av_frame_get_buffer(pFrameYUV, 1);

AVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM)

第六步:循环传输RTMP流

while(1)
{
	//每一帧加1
	pFrameYUV->pts = vpts;
	vpts+=1;
	//获取视频包裹
	av_read_frame(infmt_ctx,dec_pkt);

	//用decodec_ctx解码dec_pkt获取pFrame,got_picture为标志1为解码成功,0为否则
    avcodec_decode_video2(decodec_ctx, pFrame, &got_picture, dec_pkt);
    //释放dec_pkt,不然会积累存放编码数据
    av_free_packet(dec_pkt);
    //YUV422格式->YUV420
    sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, encodec_ctx->height, pFrameYUV->data, pFrameYUV->linesize);
    //用encodec_ctx对pFrameYUV编码
    avcodec_send_frame(encodec_ctx, pFrameYUV);

    //编码到enc_pkt
    avcodec_receive_packet(encodec_ctx, &enc_pkt);
    //设置enc_pkt时间轴
    enc_pkt.pts = av_rescale_q(enc_pkt.pts, encodec_ctx->time_base, out_stream->time_base);
	enc_pkt.dts = av_rescale_q(enc_pkt.dts, encodec_ctx->time_base, out_stream->time_base);
	enc_pkt.duration = av_rescale_q(enc_pkt.duration, encodec_ctx->time_base, out_stream->time_base);
		
    //发送RTMP流到服务器
    av_interleaved_write_frame(outfmt_ctx, &enc_pkt);

    //释放enc_pkt,不然会积累存放编码数据
	av_free_packet(&enc_pkt);    
}

3.完整代码

#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    
    //初始化网络
	avformat_network_init();
	//初始化设备
	avdevice_register_all();
    //输入封装器
    AVFormatContext* infmt_ctx = NULL;
    //输出封装器
    AVFormatContext* outfmt_ctx = NULL;
	
	//视频输入格式
	AVInputFormat* ifmt =NULL;
	
	//通过v4l2框架来获取视频输入格式
	ifmt = av_find_input_format("linux4video2");

	//视频输入设备
	char *in_filename  = "/dev/video0";
	
	//视频输出设备
    char *out_filename = "rtmp://127.0.0.1/live/stream";
    
    //打开视频设备
    if (0 > avformat_open_input(&infmt_ctx, in_filename, ifmt, NULL)) {
		printf("failed open input file\n");
		return -1;
	}
	
	//读取设备信息
	if (0 > avformat_find_stream_info(infmt_ctx, NULL)) {
		printf("failed find stream info\n");
		avformat_close_input(&infmt_ctx);
		return -1;
	}
	
	
	//对流(Stream)的封装和抽象
	AVStream *in_stream = NULL;
    AVStream *out_stream = NULL;
    
    //视频流和音频流的标志
    int videoindex=-1;
	
	int i=0;
	int ret;
	//查找视频||音频流
	for (i = 0; i < infmt_ctx->nb_streams; i++)
	{
		//Create output AVStream according to input AVStream
		
		if (infmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			videoindex = i;
		}
		else
		{
			break;
		}
	}
	
	if (videoindex == -1) 
	{
        printf("input video stream not exist\n");
        return -1;
    }
    
    
	AVCodec* encodec = NULL;
	AVCodec* decodec = NULL;
	
	
	//找到编码器
	encodec = avcodec_find_encoder(AV_CODEC_ID_H264);
	if (!encodec) 
	{
		printf("not find encoder\n");
		avformat_close_input(&infmt_ctx);
		return -1;
	}
	
	
	AVCodecContext* encodec_ctx = NULL;
	AVCodecContext* decodec_ctx = NULL;
	
	decodec_ctx=infmt_ctx->streams[videoindex]->codec;
	//找到解码器
	decodec = avcodec_find_decoder(decodec_ctx->codec_id);
	if (!decodec) 
	{
		printf("not find decoder\n");
		avformat_close_input(&infmt_ctx);
		return -1;
	}
	
	//创建编码器
	encodec_ctx = avcodec_alloc_context3(encodec);
	if (!encodec_ctx) 
	{
		printf("not alloc context3\n\n");
		avformat_close_input(&infmt_ctx);
		return -1;
	}
	
	//打开解码器
	ret = avcodec_open2(decodec_ctx, decodec, NULL);
    if (ret < 0) {
        fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));
        return -1;
    }
	
	
	//配置编码器参数
	encodec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; 
	encodec_ctx->codec_id = encodec->id;
	encodec_ctx->thread_count = 8;
	encodec_ctx->bit_rate = 400000;
	encodec_ctx->width = 640;
	encodec_ctx->height = 480;
	encodec_ctx->time_base = (AVRational){1, 5};    //5是编多少帧就发送,可根据编码速度改变
    encodec_ctx->framerate = (AVRational){5, 1};
	encodec_ctx->gop_size = 15;
	encodec_ctx->max_b_frames = 0;
	encodec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
	
    
	//编码质量和速度
	av_opt_set(encodec_ctx->priv_data, "preset", "ultrafast", 0);
	av_opt_set(encodec_ctx->priv_data, "tune", "zerolatency", 0);
	AVDictionary *opts = NULL;
	av_dict_set(&opts, "profile", "baseline", 0);
	//av_opt_set(encodec_ctx->priv_data, "crf", "18", 0);
	
	//打开编码器
	ret = avcodec_open2(encodec_ctx, encodec, &opts);
    if (ret < 0) {
        fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));
        return -1;
    }
    
    //初始化输出封装器
    ret=avformat_alloc_output_context2(&outfmt_ctx, NULL, "flv",out_filename);
    if (ret != 0) {
        printf("failed alloc output context\n");
		avformat_close_input(&infmt_ctx);
        return -1;;
    }
    
    //添加视频流
    out_stream = avformat_new_stream(outfmt_ctx,NULL);
	if (!out_stream) {
		printf("failed new stream\n");
		avformat_close_input(&infmt_ctx);
		avformat_close_input(&outfmt_ctx);
		return -1;
	}
	out_stream->codecpar->codec_tag = 0;
	//复制参数
	avcodec_parameters_from_context(out_stream->codecpar, encodec_ctx);
	
	//查看输出封装内容
	av_dump_format(outfmt_ctx, 0, out_filename, 1);
	
	//打开rtmp的网络输出IO
	ret=avio_open(&outfmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
	if (ret!=0) {
		printf("failed to open outfile\n");
		avformat_close_input(&infmt_ctx);
		avformat_close_input(&outfmt_ctx);
		return -1;
	}
	
	//写入封装头
	ret=avformat_write_header(outfmt_ctx, NULL);
	if (ret!=0) {
		printf("failed to write header\n");
		avio_close(outfmt_ctx->pb);
		avformat_close_input(&infmt_ctx);
		avformat_close_input(&outfmt_ctx);
		return -1;
	}
	
	
	AVPacket *dec_pkt,enc_pkt;
	//包裹申请内存
	dec_pkt = (AVPacket *)av_malloc(sizeof(AVPacket)); 
	memset(&enc_pkt, 0, sizeof(enc_pkt));
	//像素格式转换YU420
	struct SwsContext *img_convert_ctx  = NULL;
	img_convert_ctx  = sws_getCachedContext(img_convert_ctx, decodec_ctx->width, decodec_ctx->height,decodec_ctx->pix_fmt, encodec_ctx->width, encodec_ctx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, 0, 0, 0);
	if (!img_convert_ctx)
	{
		printf("fail to sws_getCachedContext\n");
	} 
	
	AVFrame *pFrameYUV,*pFrame ;
	//原始帧
	pFrame = av_frame_alloc();
	//输出帧
	pFrameYUV = av_frame_alloc();
	pFrameYUV->format = AV_PIX_FMT_YUV420P;
	pFrameYUV->width = 640;
	pFrameYUV->height = 480;
	pFrameYUV->pts = 0;
	
	ret = av_frame_get_buffer(pFrameYUV, 1);
	if (ret != 0)
	{
		printf("fail to frame get buffer\n");
		return -1;
	}
	//开始计时
	int64_t start_time = av_gettime();
	//标记
    int got_picture=0,enc_got_frame=0;
    //每一帧编号
    int vpts = 0;
	
	while(1)
	{
	    //每一帧加1
	    pFrameYUV->pts = vpts;
		vpts+=1;
		//获取摄像头帧
	    ret=av_read_frame(infmt_ctx,dec_pkt);
		if (ret != 0)
        {    
            printf("fail to read_frame\n");
            break;
        }
        
		//解码获取初始图片
        ret = avcodec_decode_video2(infmt_ctx->streams[dec_pkt->stream_index]->codec, pFrame, &got_picture, dec_pkt);
        if(!got_picture)
        {
            printf("123\n");
            continue;
        }
        //h264格式转换
        ret=sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, encodec_ctx->height, pFrameYUV->data, pFrameYUV->linesize);
        if (ret <= 0)
		{
			printf("123\n");
			continue;
		}
		
        //输出帧编码
        ret = avcodec_send_frame(encodec_ctx, pFrameYUV);
		if (ret != 0)
		{
		    printf("123\n");
			continue;
		}
        //打包到输出包裹
		ret = avcodec_receive_packet(encodec_ctx, &enc_pkt);
		if (ret != 0 || enc_pkt.size > 0)
		{
				//cout << "*" << pack.size << flush;
		}
		else
		{
				continue;
		}
		
		//推流
		enc_pkt.pts = av_rescale_q(enc_pkt.pts, encodec_ctx->time_base, out_stream->time_base);
		enc_pkt.dts = av_rescale_q(enc_pkt.dts, encodec_ctx->time_base, out_stream->time_base);
		enc_pkt.duration = av_rescale_q(enc_pkt.duration, encodec_ctx->time_base, out_stream->time_base);
		
        //发送到服务器
		ret = av_interleaved_write_frame(outfmt_ctx, &enc_pkt);
		if (ret < 0) {
            fprintf(stderr, "Error muxing packet\n");
            break;
        }	
        //查看第几帧
        printf("%d\n",vpts);
		//av_packet_unref(&enc_pkt);
        
	}
    avio_close(outfmt_ctx->pb);
	avformat_close_input(&infmt_ctx);
	avformat_close_input(&outfmt_ctx);
    return 0;
}

3.编译

gcc camera2rtmp.c -o camera2rtmp -lavdevice -lavformat -lavfilter -lavcodec  -lswresample -lswscale -lavutil -lpthread -lm

4.查看推流
想办法吧。

感谢雷神大佬无私奉献的博客,所以我的博客会一直免费分享,向雷神学习。

你可能感兴趣的:(实战项目,FFmpeg)