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 <stdio.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libswscale/swscale.h>
#include <libavutil/time.h>
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.查看推流
想办法吧。
感谢雷神大佬无私奉献的博客,所以我的博客会一直免费分享,向雷神学习。