个人学习总结__打开摄像头、播放网络视频的以及ffmpeg推流

前言

最近入手了一款非常便宜的usb摄像头(买回来感觉画质很低,没有描述的4k,不过也够用于学习了),想着利用它来开启流媒体相关技术的学习。第一步便是打开摄像头,从而才能够对它进行一系列后续操作,诸如实时编解码,推流摄像头采集的数据等等。

本篇文章记录了如何通过QT、libvlc、ffmpeg三种方式来调用usb摄像头,顺带讲述了如何播放http网络视频流。以及如何搭建一个srs流媒体服务器。如代码有逻辑错误或者可优化,欢迎大家指正!希望和大家共同进步。

VS2022 + QT 调用usb摄像头

vs2022如果还没配置QT可参考博客: https://blog.csdn.net/H0893_888/article/details/129772600

第一步: vs2022新建一个Qt Widgets Application

第二步: 填写项目名称和项目位置,大家随意

第三步: qt向导中进行如下勾选(这里没选全,后续也可再更改)

主要代码

复制代码
void Qt_Camera::openCamera(void)
{
    //获取可用摄像头列表信息
    QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
    for(auto camera: cameras)
    {
        qDebug() << camera;
    }
    
    /* 创建摄像头对象
     * QCamera *camera = new QCamera;
     * 这里使用QCameraInfo::availableCameras().at(1)是因为0为电脑自带的摄像头,1为usb摄像头
     */
    QCamera *camera = new QCamera(QCameraInfo::availableCameras().at(1));
    
    // 创建摄像头取景器对象
    QCameraViewfinder *finder = new QCameraViewfinder;
    
    // 设置取景器为摄像头的视图
    camera->setViewfinder(finder);
    camera->setCaptureMode(QCamera::CaptureVideo);
    
    //创建图像捕获对象
    QCameraImageCapture *imageCapture = new QCameraImageCapture(camera);
    
    //设置图片保存路径
    imageCapture->setCaptureDestination(QCameraImageCapture::CaptureToFile);
    
    // 创建布局管理器
    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(finder);

    // 创建拍照按钮
    QPushButton *captureButton = new QPushButton("Capture");
    layout->addWidget(captureButton);

    //信号与槽连接
    connect(imageCapture, &QCameraImageCapture::imageCaptured, this, &MainWindow::takePhoto);
    
    // 连接拍照按钮的点击事件到图像捕获对象的拍照槽函数
    connect(captureButton, &QPushButton::clicked, [=](){
        imageCapture->capture();
    });

    // 创建主窗口
    QWidget *window = new QWidget;

    window->setLayout(layout);
    window->show();
    // 启动摄像头
    camera->start();
}

/* 这里的 id 和 image 参数 由 QCameraImageCapture::imageCaptured 这个信号传递过来 */
void Qt_Camera::takePhoto(int id, const QImage &image)
{
    //使用系统时间来命名图片的名称,时间是唯一的,图片名也是唯一的
    QDateTime dateTime(QDateTime::currentDateTime());
    QString time = dateTime.toString("yyyy-MM-dd-hh-mm-ss");
    
    //创建图片保存路径名
    QString filename = QString("D:/%1.jpg").arg(time);
    
    //保存一帧数据
    image.save(filename);
    
    //提示获取到了一帧数据
    QMessageBox::information(this, "提示:", "获取到一帧图片");
}

效果图

VS2022 + libvlc 调用usb摄像头/网络视频/本地视频

libvlc官网下载: https://download.videolan.org/pub/videolan/vlc/ (32/64位自选)

第一步: 创建空项目

第二步: 填写项目名称/路径,大家随意

第三步: 新建一个main.cpp

第四步: 拷贝libvlc至项目路径下

第五步: 拷贝libvlc下的所有内容至项目路径下(plugins、libvlc.dll、libvlccore.dll)
注意: 如果忘记拷贝 plugins 文件夹,无法通过 libvlc_new 得到 libvlc_instance_t* 类型的一个实例。

第六步: 获取usb摄像头名称,libvlc中需要先获得usb摄像头的名称

  • 1 打开电脑中计算机管理
  • 2 点击设备管理器
  • 3 点击照相机,下方即为电脑摄像头设备名称

main.cpp代码(这里使用C++代码,相对清爽一些)

复制代码
#include <iostream>

#include "vlc/vlc.h"

using namespace std;

void setMedia(const char* url)
{
	//判断url是否为空
	if (!url)
	{
		cout << "url is nullptr!" << endl;
		return;
	}

	libvlc_instance_t* vlcInstance = libvlc_new(0, nullptr);
	//判断vlc实例是否为空
	if (!vlcInstance)
	{
		cout << "vlcInstance is nullptr!" << endl;
		return;
	}

	//创建媒体
	//libvlc_media_t *vlcMedia= libvlc_media_new_path(vlcInstance,url); //播放本地视频文件
	//libvlc_media_t* vlcMedia = libvlc_media_new_location(vlcInstance, "http://sovv.qianpailive.com/a455359ee41b684baaf0e4a094ee34c4/1714302042/b740/20240427/e5eba6250b87047662500bd5a3356465?a=ylu7kvm4yc47&t=1714302042023730569&u=el2KNaf9FEzbwLuS6FKdUaqObdUwp2i5Q7qqeWI3&vid=Vjkz13DanVMG"); //播放网络视频文件
	libvlc_media_t* vlcMedia = libvlc_media_new_location(vlcInstance, "dshow://DV20 USB CAMERA");   //打开usb摄像头

	if (!vlcMedia)
	{
		cout << "vlcMedia is nullptr!" << endl;
		return;
	}

	//创建媒体播放器
	libvlc_media_player_t* vlcMediaPlayer = libvlc_media_player_new_from_media(vlcMedia);
	if (!vlcMediaPlayer)
	{
		cout << "vlcMediaPlayer is nullptr!" << endl;
		return;
	}

	libvlc_media_player_play(vlcMediaPlayer);
	getchar();

	//释放媒体播放器
	if (vlcMediaPlayer)
	{
		libvlc_media_player_release(vlcMediaPlayer);
	}

	//释放媒体
	if (vlcMedia)
	{
		libvlc_media_release(vlcMedia);
	}

	//释放vlc实例
	if (vlcInstance)
	{
		libvlc_release(vlcInstance);
	}

}

int main(void)
{
	setMedia("D:\\3.flv");    //传递播放媒体url
	cout << "hello vlc" << endl;
	getchar();
	return 0;
}

效果图:

  • 本地视频

  • 网络视频

  • 摄像头采集一帧画面(感觉不是很清晰)

VS2022 + ffmpeg + sdl 摄像头采集数据

不了解 ffmpeg 和 sdl 可参考我的上一篇博客: https://blog.csdn.net/m0_73431159/article/details/138195924

由于上一篇已经讲过 ffmpeg + sdl 在 vs2022 中的环境配置,这里就不再赘述了。

ffmpeg可以直接用命令行查看摄像头设备名

可先使用chcp 65001 临时改变 cmd 窗口编码格式为 utf-8

ffmpeg -list_devices true -f dshow -i dummy

代码

复制代码
#pragma warning(disable:4996)

#include <stdio.h>

extern "C"  // ffmpeg有C语言编写,这里为cpp文件
{
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
#include "SDL/SDL.h"
#include "SDL/SDL_thread.h"
}

#undef main   // sdl中已有main的相关定义

#define REFRESH_EVENT  (SDL_USEREVENT + 1)  // 自定义事件

int thread_exit = 0;    //线程循环状态变量

int refresh_thread(void* opaque)
{
    while (thread_exit == 0) {
        SDL_Event event;
        event.type = REFRESH_EVENT; // 将事件类型定为REFRESH_EVENT
        SDL_PushEvent(&event);  // 送入事件队列中
        SDL_Delay(40);   // 如果是打开摄像头,可注释该行。如果是播放视频,该行表示每秒25帧
    }
    return 0;
}**加粗样式**

int main(void)
{
    //-----------ffmpeg------------
    AVFormatContext* pFormatContext = NULL;     //封装格式上下文
    AVDictionary* opts = NULL;  // 字典
    const char* url = "http://sovv.qianpailive.com/6ddf66db8dc4449123e9f93259db899b/1714201703/cf05/20231002/c36e0216c67cdfb009dcd1467ac4075f?a=ylu7kvm4yc47&t=1714201703527245094&u=lEqwQq9pxNSGX4uxOLgaVBmDh9FrvJwoKTdAX6t2&vid=8jLl7AGQLxjA";    //网络视频地址
    int videoIndex = -1;    //视频流索引号
    int i = 0;  // 循环变量
    AVCodecContext* pCodecContext = NULL;   // 编解码上下文
    AVCodec* pCodec = NULL;     // 编解码器
    AVPacket* packet = NULL;    // 解码前一帧视频数据
    AVFrame* pFrame = NULL;     // 解码后一帧数据
    AVFrame* pFrameYUV = NULL;  // 裁剪后一帧数据
    struct SwsContext* img_convert_ctx = NULL;  //像素格式转换等

    //-------------SDL-------------
    SDL_Surface* screen;    //画布
    int screen_w = 0, screen_h = 0;
    SDL_Overlay* bmp;   //涂层叠层
    SDL_Rect rect;  //绘图区域
    int ret, got_picture;   
    SDL_Thread* video_tid;  //线程
    SDL_Event event;    //事件

    av_register_all();  // 注册所有组件
    avformat_network_init();    //网络初始化
    avdevice_register_all();    //注册所有设备

#if 0   //如果想要播放网络流视频,将其置 1j
    /*  加上一些限制条件 */
    av_dict_set(&opts, "http_transport", "tcp", 0);
    av_dict_set(&opts, "max_delay", "500", 0);

    pFormatContext = avformat_alloc_context();
    /*  打开网络视频 */
    if (avformat_open_input(&pFormatContext, url, NULL, &opts) != 0)
    {
        printf("Can't open input !\n");
        return -1;
    }
#else
    pFormatContext = avformat_alloc_context();

    /* DShow是DirectShow的简称,它是一种用于Windows平台的多媒体框架 */
    pFormatContext->iformat = av_find_input_format("dshow");
    // pFormatContext->iformat = av_find_input_format("vfwcap");
    /*  打开摄像头作为输入 */
    if (avformat_open_input(&pFormatContext, "video=DV20 USB CAMERA", pFormatContext->iformat, NULL) != 0)
    {
        printf("Can't open input !\n");
        return -1;
    }
#endif
    /*  获取流信息 */
    if (avformat_find_stream_info(pFormatContext, NULL) < 0)
    {
        printf("Couldn't find stream information.\n");
        return -1;
    }

    /* 寻找视频流 */
    for (int i = 0; i < pFormatContext->nb_streams; i++)
    {
        if (pFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoIndex = i;
            break;
        }
    }
    
    /*  判断是否找到视频流 */
    if (videoIndex == -1)
    {
        printf("Can't find video stream !\n");
        return -1;
    }

    pCodecContext = pFormatContext->streams[videoIndex]->codec; //获取解码器上下文
    pCodec = avcodec_find_decoder(pCodecContext->codec_id);     //获取解码器
    
    /*  判断是否成功获取解码器  */
    if (pCodec == NULL) 
    {
        printf("Can't find decoder !\n");
        return -1;
    }

    /*  打开解码器是否成功 */
    if (avcodec_open2(pCodecContext, pCodec, NULL) < 0)
    {
        printf("Can't open decoder !\n");
        return -1;
    }

    /*  分配空间  */
    packet = (AVPacket*)av_malloc(sizeof(AVPacket));
    pFrame = avcodec_alloc_frame();
    pFrameYUV = avcodec_alloc_frame();

    /*  初始化SDL子系统 */
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
        printf("Could not initialize SDL - %s\n", SDL_GetError());
        return -1;
    }

    /*  获取宽、高  */
    screen_w = pCodecContext->width;
    screen_h = pCodecContext->height;

    /*
     * SDL_SetVideoMode是一个用于设置SDL窗口的宽、高度、位深度和标志的函数。
     * @return 返回一个指向SDL_Surface结构的针,该结构表示窗口的表面。
     */
    screen = SDL_SetVideoMode(screen_w, screen_h, 0, 0);

    if (!screen) {
        printf("SDL: could not set video mode - exiting:%s\n", SDL_GetError());
        return -1;
    }

    /*
     * SDL_CreateYUVOverlay函数: SDL库中用于创建YUV覆盖的函数。YUV覆盖是一种用于在屏幕上显示视频的数据结构。
     * SDL_YV12_OVERLAY是SDL库中的一个宏定义,用于创建一个YV12格式的图像叠加层(overlay)。
     * YV12是一种颜色编码格式,常用于视频图像的处理和显示。
     * YV12格式将图像的亮度(Y)和色度(UV)分开存储,可以有效地压缩图像数据并提高图像质量。
     */
    bmp = SDL_CreateYUVOverlay(pCodecContext->width, pCodecContext->height, SDL_YV12_OVERLAY, screen);
    
    rect.x = 0;
    rect.y = 0;
    rect.w = screen_w;
    rect.h = screen_h;
 
    /* 图片格式转换 */
    img_convert_ctx = sws_getContext(pCodecContext->width, pCodecContext->height, pCodecContext->pix_fmt, pCodecContext->width, pCodecContext->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
    
    /* 创建一个线程 */
    video_tid = SDL_CreateThread(refresh_thread, NULL);
    
    /* SDL_WM_SetCaption函数是SDL库中的一个函数,用于设置显示窗口的标题和图标 */
    SDL_WM_SetCaption("MyCameraTest", NULL);
 
    while(1)
    {
        //等待事件到来
        SDL_WaitEvent(&event);

        /* 判断事件类型是否为REFRESH_EVENT,是则读帧 */
        if (event.type == REFRESH_EVENT) 
        {
            //------------------------------
            if (av_read_frame(pFormatContext, packet) >= 0) 
            {
                //判断是否为视频帧
                if (packet->stream_index == videoIndex) 
                {
                    //解码packet
                    ret = avcodec_decode_video2(pCodecContext, pFrame, &got_picture, packet);
                    if (ret < 0) 
                    {
                        printf("Decode Error.\n");
                        return -1;
                    }
                    
                    //判断是否获得一帧画面
                    if (got_picture)
                    {
                        /* SDL_LockYUVOverlay函数用于锁定YUV覆盖层,以便在其上进行像素操作。 */
                        SDL_LockYUVOverlay(bmp);
                        pFrameYUV->data[0] = bmp->pixels[0];
                        pFrameYUV->data[1] = bmp->pixels[2];
                        pFrameYUV->data[2] = bmp->pixels[1];
                        pFrameYUV->linesize[0] = bmp->pitches[0];
                        pFrameYUV->linesize[1] = bmp->pitches[2];
                        pFrameYUV->linesize[2] = bmp->pitches[1];

                        /*
                         * sws_scale是一个图像缩放函数,用于将输入图像缩放到指定的输出尺寸。
                         * c:SwsContext结构体指针,包含了图像缩放的相关参数。
                         * srcSlice:输入图像数据的指针数组,每个指针指向一个输入图像平面。
                         * srcStride:输入图像数据的步长数组,每个步长对应一个输入图像平面。
                         * srcSliceY:输入图像的起始行数。
                         * srcSliceH:输入图像的高度。
                         * dst:输出图像数据的指针数组,每个指针指向一个输出图像平面。
                         * dstStride:输出图像数据的步长数组,每个步长对应一个输出图像平面。
                         */
                        sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecContext->height, pFrameYUV->data, pFrameYUV->linesize);

                        /*
                         * SDL_UnlockYUVOverlay函数: SDL库中用于解锁YUV叠加层的函数。
                         * 它的作用是解锁一个已经锁定的YUV叠加层,以便可以对其进行修改或者渲染操作。
                         * @param overlay 是一个指向SDL_Overlay结构体的指针,它表示要解锁的YUV叠加层。
                         */
                        SDL_UnlockYUVOverlay(bmp);

                        /*
                         * SDL_DisplayYUVOverlay函数: SDL库中用于显示YUV覆盖层的函数。
                         * @param overlay:指向SDL_Overlay结构的指针,表示要显示的YUV覆盖层。
                         * @param dstrect:指向SDL_Rect结构的指针,表示覆盖层在屏幕上的位置和大小。
                         */
                        SDL_DisplayYUVOverlay(bmp, &rect);

                    }
                }

                //free a packet
                av_free_packet(packet);
            }
            else 
            {
                //Exit Thread
                thread_exit = 1;
                break;
            }
        }

        // 事件类型为退出事件则退出循环
        else if (event.type == SDL_QUIT)
        {
            thread_exit = 1;
            break;
        }

    }
    
    /* 关闭释放相关资源 */
    sws_freeContext(img_convert_ctx);
    SDL_Quit();     
    av_free(pFrameYUV);
    avcodec_close(pCodecContext);
    avformat_close_input(&pFormatContext);

    return 0;
}

效果图:

搭建srs流媒体服务器

srs官⽹:https://github.com/ossrs/srs

码云的源速度快:https://gitee.com/winlinvip/srs.oschina.git

github的源速度慢:https://github.com/ossrs/srs.git

第⼀步,获取SRS

git clone https://gitee.com/winlinvip/srs.oschina.git

cd srs.oschina

git checkout -b 5.0 remotes/origin/5.0release

cd trunk

第二步,编译SRS

./configure && make

第三步,编写SRS配置⽂件

将以下内容保存为⽂件,譬如 conf/rtmp.conf ,服务器启动时指定该配置⽂件(srs的conf⽂件夹有该⽂件)。

复制代码
# conf/rtmp.conf
listen        1935;
max_connections   1000;
vhost __defaultVhost__ {
}

第四步,启动srs

./objs/srs -c conf/rtmp.conf

第五步,启动推流编码器

视频推流

ffmpeg -re -i 3.flv -vcodec copy -acodec copy -f flv -y rtmp://192.168.72.128/live/livestream

摄像头推流

ffmpeg -f dshow -i video="Integrated Camera" -c:v libx264 -preset ultrafast -tune zerolatency -f flv rtmp://192.168.72.128/live/livestream

这里需要根据每个人的实际情况进行填写,如: 3.flv 需替换成您电脑的视频地址

rtmp后面的地址也得改成实际的服务器地址

第六步,观看RTMP流。

  • 可以使用ffplay rtmp://192.168.72.128/live/livestream
  • 可以使用vlc播放器进行拉流播放

注意:如果是在虚拟机上进行测试,记得关闭虚拟机防火墙

第五步,启动推流编码器

视频推流

ffmpeg -re -i 3.flv -vcodec copy -acodec copy -f flv -y rtmp://192.168.72.128/live/livestream

摄像头推流

ffmpeg -f dshow -i video="Integrated Camera" -c:v libx264 -preset ultrafast -tune zerolatency -f flv rtmp://192.168.72.128/live/livestream

这里需要根据每个人的实际情况进行填写,如: 3.flv 需替换成您电脑的视频地址

rtmp后面的地址也得改成实际的服务器地址

第六步,观看RTMP流。

  • 可以使用ffplay rtmp://192.168.72.128/live/livestream
  • 可以使用vlc播放器进行拉流播放

注意:如果是在虚拟机上进行测试,记得关闭虚拟机防火墙

systemctl stop firewalld

相关推荐
charlie1145141912 分钟前
FreeRTOS: 信号量(Semaphores)、互斥量(Mutex)与优先级继承
开发语言·笔记·学习·c·freertos·实时操作系统
Stanford_110611 分钟前
【2026新年启程】学习之路,探索之路,技术之路,成长之路……都与你同行!!!
前端·c++·学习·微信小程序·排序算法·微信开放平台
Ahtacca18 分钟前
Redis 五大常用数据类型详解及 Java 客户端(RedisTemplate)操作实战
java·数据库·redis·学习·缓存
知识分享小能手21 分钟前
Ubuntu入门学习教程,从入门到精通, Ubuntu 22.04中的进程管理详解(15)
linux·学习·ubuntu
0和1的舞者29 分钟前
SpringAOP详解(二)
学习·spring·切面·代理·知识·springaop
iiiiii1137 分钟前
TD(λ),资格迹(Eligibility Traces)与时序差分学习的统一
人工智能·学习·机器学习·强化学习·rl
走在路上的菜鸟1 小时前
Android学Flutter学习笔记 第一节 Android视角认知Flutter(View,intent,Async UI)
android·学习·flutter
坚持学习前端日记1 小时前
个人运营小网站的最佳策略
java·学习·程序人生·职场和发展·创业创新
崇山峻岭之间1 小时前
Matlab学习记录20
开发语言·学习·matlab
笔夏2 小时前
【安卓学习之myt】adb常用命令
android·学习·adb