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

使用FFmpeg库(版本号为:4.4.2-0ubuntu0.22.04.1)实现从麦克风获取流并通过RTMP推流。
RTMP服务器使用的是SRS,我这边是跑在Ubuntu上的,最好是关闭掉系统防火墙,不然连接服务器好像会出问题,拉流端使用VLC。如果想降低延时,首先需要配置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或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;
    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;

    // 打印ffmpeg版本信息
    printf("ffmpeg version: %s\n", av_version_info());

    // 注册所有设备
    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帧时设置为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)
    {
        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;
    }
    AVPacket *packet = av_packet_alloc();
    if (!packet)
    {
        printf("av_packet_alloc failed\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;
    }

    // 打开url
    if (!(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;
            }

            // 转换为 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++;
            // 发送帧到编码器
            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 && !(out_context->flags & AVFMT_NOFILE))
        avio_close(out_context->pb);
    if (out_context)
        avformat_free_context(out_context);

    return 0;
}

相关博客:FFmpeg YUV编码为H264 

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个更完整的C代码示例,用于在Linux上使用FFmpeg库推送摄像头RTMP服务器: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/videodev2.h> #define WIDTH 640 #define HEIGHT 480 #define FPS 30 #define RTMP_URL "rtmp://your-rtmp-server-url" int main(void) { int fd; struct v4l2_format fmt; struct v4l2_requestbuffers req; struct v4l2_buffer buf; enum v4l2_buf_type type; FILE *pipein; char command[256]; int frame_size = WIDTH * HEIGHT * 3; // 打开摄像头设备 fd = open("/dev/video0", O_RDWR); if (fd == -1) { perror("Error opening video device"); return -1; } // 配置摄像头格式 memset(&fmt, 0, sizeof(fmt)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = WIDTH; fmt.fmt.pix.height = HEIGHT; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; fmt.fmt.pix.field = V4L2_FIELD_NONE; if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) { perror("Error setting video format"); close(fd); return -1; } // 请求摄像头缓冲区 memset(&req, 0, sizeof(req)); req.count = 1; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) { perror("Error requesting buffers"); close(fd); return -1; } // 映射摄像头缓冲区到用户空间 memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = 0; if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) { perror("Error querying buffer"); close(fd); return -1; } void *buffer_start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (buffer_start == MAP_FAILED) { perror("Error mapping buffer"); close(fd); return -1; } // 开始摄像头捕获 type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) { perror("Error starting streaming"); munmap(buffer_start, buf.length); close(fd); return -1; } // 构建FFmpeg命令 sprintf(command, "ffmpeg -f rawvideo -pixel_format rgb24 -video_size %dx%d -framerate %d -i - -c:v libx264 -pix_fmt yuv420p -f flv %s", WIDTH, HEIGHT, FPS, RTMP_URL); // 打开管道 pipein = popen(command, "w"); if (pipein == NULL) { perror("Error opening pipe for output"); munmap(buffer_start, buf.length); close(fd); return -1; } // 循环读取摄像头帧并推流 while (1) { // 从摄像头获取帧 memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) { perror("Error queuing buffer"); break; } // 开始采集帧 if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) { perror("Error dequeuing buffer"); break; } // 将帧数据写入pipein以推流 if (fwrite(buffer_start, 1, frame_size, pipein) != frame_size) { perror("Error writing to pipe"); break; } // 重新将帧放回摄像头缓冲区队列 if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) { perror("Error requeuing buffer"); break; } } // 停止摄像头捕获 type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) { perror("Error stopping streaming"); } // 释放资源 pclose(pipein); munmap(buffer_start, buf.length); close(fd); return 0; } ``` 请注意,这个示例代码假设你的摄像头设备文件为`/dev/video0`,并且使用RGB24像素格式。如果你的设备文件或像素格式不同,你需要相应地修改代码。另外,还需要将`RTMP_URL`替换为你的RTMP服务器的URL。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值