使用FFmpeg库实现从USB摄像头获取流并通过RTMP推流,FFmpeg版本为4.4.2-0。RTMP服务器使用的是SRS,拉流端使用VLC。
在Linux上查看摄像头信息可使用 v4l2-ctl 工具,查看命令如下:
bash
v4l2-ctl --device=/dev/video0 --list-formats-ext
代码如下:
cpp
#include <libavdevice/avdevice.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
int main(void)
{
const char *input_format_name = "video4linux2"; // 输入格式名称,Linux下为video4linux2或v4l2
const char *device_name = "/dev/video0"; // 摄像头设备名称
const char *camera_resolution = "640x480"; // 摄像头分辨率
enum AVPixelFormat camera_pix_fmt = AV_PIX_FMT_YUYV422; // 摄像头像素格式
const char *url = "rtmp://192.168.3.230/live/livestream"; // rtmp地址
int frame_rate = 25; // 帧率
int ret = -1, retVal = -1;
int video_streamid = -1;
int64_t frame_index = 0;
AVDictionary *options = NULL;
AVInputFormat *fmt = NULL;
AVFormatContext *in_context = NULL, *out_context = NULL;
struct SwsContext *sws_ctx = NULL;
AVCodecContext *codec_context = NULL;
AVStream *out_stream = NULL;
AVCodec *codec = NULL;
AVPacket *packet = NULL;
// 注册所有设备
avdevice_register_all();
// 查找输入格式
fmt = av_find_input_format(input_format_name);
if (!fmt)
{
fprintf(stderr, "av_find_input_format error");
return -1;
}
// 设置分辨率
av_dict_set(&options, "video_size", camera_resolution, 0);
// 打开输入流并初始化格式上下文
retVal = avformat_open_input(&in_context, device_name, fmt, &options);
if (retVal != 0)
{
// 打印错误信息
fprintf(stderr, "avformat_open_input error");
return -1;
}
// 查找视频流索引
video_streamid = av_find_best_stream(in_context, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (video_streamid < 0)
{
fprintf(stderr, "cannot find video stream");
goto end;
}
AVStream *video_stream = in_context->streams[video_streamid];
printf("input stream, width: %d, height: %d, format: %s\n",
video_stream->codecpar->width, video_stream->codecpar->height,
av_get_pix_fmt_name((enum AVPixelFormat)video_stream->codecpar->format));
// 检查实际获取到的格式是否为设置的摄像头像素格式
if (video_stream->codecpar->format != camera_pix_fmt)
{
fprintf(stderr, "pixel format error");
goto end;
}
// 初始化转换上下文
sws_ctx = sws_getContext(
video_stream->codecpar->width, video_stream->codecpar->height, camera_pix_fmt,
video_stream->codecpar->width, video_stream->codecpar->height, AV_PIX_FMT_YUV420P,
SWS_BILINEAR, NULL, NULL, NULL);
if (!sws_ctx)
{
fprintf(stderr, "sws_getContext error\n");
goto end;
}
// 创建帧
AVFrame *input_frame = av_frame_alloc();
AVFrame *frame_yuv420p = av_frame_alloc();
if (!input_frame || !frame_yuv420p)
{
fprintf(stderr, "av_frame_alloc error\n");
goto end;
}
// 设置帧格式
input_frame->format = camera_pix_fmt;
input_frame->width = video_stream->codecpar->width;
input_frame->height = video_stream->codecpar->height;
frame_yuv420p->format = AV_PIX_FMT_YUV420P;
frame_yuv420p->width = video_stream->codecpar->width;
frame_yuv420p->height = video_stream->codecpar->height;
// 分配帧内存
retVal = av_frame_get_buffer(frame_yuv420p, 0);
if (retVal < 0)
{
fprintf(stderr, "av_frame_get_buffer error\n");
goto end;
}
// 分配输出格式上下文
avformat_alloc_output_context2(&out_context, NULL, "flv", NULL);
if (!out_context)
{
fprintf(stderr, "avformat_alloc_output_context2 failed\n");
goto end;
}
// 查找编码器
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec)
{
fprintf(stderr, "Codec not found\n");
goto end;
}
printf("codec name: %s\n", codec->name);
// 创建新的视频流
out_stream = avformat_new_stream(out_context, NULL);
if (!out_stream)
{
fprintf(stderr, "avformat_new_stream failed\n");
goto end;
}
// 分配编码器上下文
codec_context = avcodec_alloc_context3(codec);
if (!codec_context)
{
fprintf(stderr, "avcodec_alloc_context3 failed\n");
goto end;
}
// 设置编码器参数
codec_context->codec_id = AV_CODEC_ID_H264;
codec_context->codec_type = AVMEDIA_TYPE_VIDEO;
codec_context->pix_fmt = AV_PIX_FMT_YUV420P;
codec_context->width = frame_yuv420p->width;
codec_context->height = frame_yuv420p->height;
codec_context->time_base = (AVRational){1, frame_rate}; // 设置时间基
codec_context->framerate = (AVRational){frame_rate, 1}; // 设置帧率
codec_context->bit_rate = 750 * 1000; // 设置比特率
codec_context->gop_size = frame_rate; // 设置GOP大小
codec_context->max_b_frames = 0; // 设置最大B帧数,不需要B帧时设置为0
av_opt_set(codec_context->priv_data, "profile", "baseline", 0); // 设置h264画质级别
av_opt_set(codec_context->priv_data, "tune", "zerolatency", 0); // 设置h264编码优化参数
// 检测输出上下文的封装格式,判断是否设置 AV_CODEC_FLAG_GLOBAL_HEADER
// AV_CODEC_FLAG_GLOBAL_HEADER:由原来编码时在每个关键帧前加入pps和sps,改变为在extradate这个字节区加入pps和sps
if (out_context->oformat->flags & AVFMT_GLOBALHEADER)
{
printf("set AV_CODEC_FLAG_GLOBAL_HEADER\n");
codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
// 打开编码器
if (avcodec_open2(codec_context, codec, NULL) < 0)
{
fprintf(stderr, "avcodec_open2 failed\n");
goto end;
}
// 将编码器参数复制到流
int result = avcodec_parameters_from_context(out_stream->codecpar, codec_context);
if (result < 0)
{
fprintf(stderr, "avcodec_parameters_from_context failed\n");
goto end;
}
// 打开url
if (!(out_context->oformat->flags & AVFMT_NOFILE))
{
result = avio_open(&out_context->pb, url, AVIO_FLAG_WRITE);
if (result < 0)
{
fprintf(stderr, "avio_open error (errmsg '%s')\n", av_err2str(result));
goto end;
}
}
// 写文件头
result = avformat_write_header(out_context, NULL);
if (result < 0)
{
fprintf(stderr, "avformat_write_header failed\n");
goto end;
}
packet = av_packet_alloc();
if (!packet)
{
fprintf(stderr, "av_packet_alloc failed\n");
goto end;
}
// 读取帧并进行转换
AVPacket pkt;
while (av_read_frame(in_context, &pkt) >= 0)
{
if (pkt.stream_index == video_streamid)
{
// 把读取的帧数据(AVPacket)拷贝到输入帧(AVFrame)中
retVal = av_image_fill_arrays(input_frame->data, input_frame->linesize, pkt.data, camera_pix_fmt,
video_stream->codecpar->width, video_stream->codecpar->height, 1);
if (retVal < 0)
{
av_packet_unref(&pkt);
fprintf(stderr, "av_image_fill_arrays error\n");
break;
}
// 转换为 YUV420P
sws_scale(sws_ctx, (const uint8_t *const *)input_frame->data, input_frame->linesize, 0,
input_frame->height, frame_yuv420p->data, frame_yuv420p->linesize);
frame_yuv420p->pts = frame_index;
frame_index++;
// 发送帧到编码器
result = avcodec_send_frame(codec_context, frame_yuv420p);
if (result < 0)
{
fprintf(stderr, "avcodec_send_frame error (errmsg '%s')\n", av_err2str(result));
break;
}
// 接收编码后的数据包
while (result >= 0)
{
result = avcodec_receive_packet(codec_context, packet);
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF)
{
break;
}
else if (result < 0)
{
fprintf(stderr, "avcodec_receive_packet error (errmsg '%s')\n", av_err2str(result));
goto end;
}
packet->stream_index = out_stream->index;
// 将时间戳从编码器时间基转换到流时间基
av_packet_rescale_ts(packet, codec_context->time_base, out_stream->time_base);
packet->pos = -1;
// 推送到RTMP服务器
result = av_interleaved_write_frame(out_context, packet);
if (result < 0)
{
fprintf(stderr, "av_interleaved_write_frame error (errmsg '%d')\n", result);
av_packet_unref(packet);
goto end;
}
av_packet_unref(packet);
}
}
av_packet_unref(&pkt);
}
end:
// 释放资源
if (input_frame)
av_frame_free(&input_frame);
if (frame_yuv420p)
av_frame_free(&frame_yuv420p);
if (sws_ctx)
sws_freeContext(sws_ctx);
if (in_context)
avformat_close_input(&in_context);
if (packet)
av_packet_free(&packet);
if (codec_context)
avcodec_free_context(&codec_context);
if (out_context)
avformat_free_context(out_context);
return ret;
}