当前位置: 首页> 财经> 金融 > FFmpeg 实现从摄像头获取流并通过RTMP推流

FFmpeg 实现从摄像头获取流并通过RTMP推流

时间:2025/8/8 14:12:24来源:https://blog.csdn.net/qq_42161913/article/details/140159226 浏览次数:0次

使用FFmpeg库实现从USB摄像头获取流并通过RTMP推流,FFmpeg版本为4.4.2-0。RTMP服务器使用的是SRS,拉流端使用VLC。我这边是跑在Ubuntu上的,最好是关闭掉系统防火墙。如果想降低延时,首先需要配置SRS为Realtime模式,拉流的话就不要用VLC了,使用 ffplay 来拉流播放,拉流命令如下:

ffplay -fflags nobuffer -flags low_delay -framedrop -strict experimental rtmp://192.168.3.230/live/livestream

 局域网内用摄像头对着手机秒表实测延时只有零点几秒。

在Linux上查看摄像头信息可使用 v4l2-ctl 工具,查看命令如下:

v4l2-ctl --device=/dev/video0 --list-formats-ext

代码如下:

#include <libavdevice/avdevice.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>int main(void)
{const char *input_format_name = "video4linux2";           // 输入格式名称,Linux下为video4linux2或v4l2const 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;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;// 注册所有设备avdevice_register_all();// 查找输入格式fmt = av_find_input_format(input_format_name);if (!fmt){printf("av_find_input_format error");return -1;}// 设置分辨率av_dict_set(&options, "video_size", camera_resolution, 0);// 打开输入流并初始化格式上下文ret = avformat_open_input(&in_context, device_name, fmt, &options);if (ret != 0){// 错误的时候释放options,成功的话 avformat_open_input 内部会释放av_dict_free(&options);printf("avformat_open_input error");return -1;}// 查找流信息if (avformat_find_stream_info(in_context, 0) < 0){printf("avformat_find_stream_info failed\n");return -1;}// 查找视频流索引video_streamid = av_find_best_stream(in_context, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);if (video_streamid < 0){printf("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){printf("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){printf("sws_getContext error\n");goto end;}// 分配输出格式上下文avformat_alloc_output_context2(&out_context, NULL, "flv", NULL);if (!out_context){printf("avformat_alloc_output_context2 failed\n");goto end;}// 查找编码器codec = avcodec_find_encoder(AV_CODEC_ID_H264);if (!codec){printf("Codec not found\n");goto end;}printf("codec name: %s\n", codec->name);// 创建新的视频流out_stream = avformat_new_stream(out_context, NULL);if (!out_stream){printf("avformat_new_stream failed\n");goto end;}// 分配编码器上下文codec_context = avcodec_alloc_context3(codec);if (!codec_context){printf("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 = video_stream->codecpar->width;codec_context->height = video_stream->codecpar->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帧时设置为0av_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和spsif (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){printf("avcodec_open2 failed\n");goto end;}// 将编码器参数复制到流ret = avcodec_parameters_from_context(out_stream->codecpar, codec_context);if (ret < 0){printf("avcodec_parameters_from_context failed\n");goto end;}// 创建帧AVFrame *input_frame = av_frame_alloc();AVFrame *frame_yuv420p = av_frame_alloc();if (!input_frame || !frame_yuv420p){printf("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;// 分配帧内存ret = av_frame_get_buffer(frame_yuv420p, 0);if (ret < 0){printf("av_frame_get_buffer error\n");goto end;}AVPacket *packet = av_packet_alloc();if (!packet){printf("av_packet_alloc failed\n");goto end;}// 打开urlif (!(out_context->oformat->flags & AVFMT_NOFILE)){ret = avio_open(&out_context->pb, url, AVIO_FLAG_WRITE);if (ret < 0){printf("avio_open error (errmsg '%s')\n", av_err2str(ret));goto end;}}// 写文件头ret = avformat_write_header(out_context, NULL);if (ret < 0){printf("avformat_write_header failed\n");goto end;}// 读取帧并进行转换AVPacket pkt;while (av_read_frame(in_context, &pkt) >= 0){if (pkt.stream_index == video_streamid){// 把读取的帧数据(AVPacket)拷贝到输入帧(AVFrame)中ret = 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 (ret < 0){av_packet_unref(&pkt);printf("av_image_fill_arrays error\n");break;}// 转换为 YUV420Psws_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++;// 发送帧到编码器ret = avcodec_send_frame(codec_context, frame_yuv420p);if (ret < 0){printf("avcodec_send_frame error (errmsg '%s')\n", av_err2str(ret));break;}// 接收编码后的数据包while (ret >= 0){ret = avcodec_receive_packet(codec_context, packet);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){break;}else if (ret < 0){printf("avcodec_receive_packet error (errmsg '%s')\n", av_err2str(ret));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服务器ret = av_interleaved_write_frame(out_context, packet);if (ret < 0){printf("av_interleaved_write_frame error (errmsg '%d')\n", ret);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 0;
}

关键字:FFmpeg 实现从摄像头获取流并通过RTMP推流

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: