zoukankan      html  css  js  c++  java
  • Qt音视频开发3-vlc录像存储

    一、前言

    录像功能是视频监控系统的常用功能,就是将打开的视频流或者视频文件重新保存成MP4文件,当然也可以保存成其他格式,一般默认用MP4比较好,比较标准一些,MP4格式的兼容性最好,基本上没有说那台电脑不能播放MP4文件,所以就保存成这种最常用的视频文件格式就好了。

    vlc的录像功能是内置封装好的,在打开文件的前面设置相应的命令参数即可,如果只是要求整个过程保存成一个视频文件,这个很好办,网上方法一大堆,只要调用libvlc_media_add_option函数设置:sout=#duplicate{dst=file{dst=d:/1.mp4},dst=display}即可,最开始用的是:sout=#stream_out_duplicate{dst=display,dst=std{access=file,mux=%1,dst=%2}}参数,后面换成vlc3以后发现不支持了,查阅相关资料后发现要用duplicate,可能vlc3开始不支持stream_out_duplicate只支持duplicate吧。

    保存成单个视频文件,这个没有任何问题和难度,但是视频监控领域中经常需要的是定时保存成单个文件,比如30分钟一个视频文件,这样方便检索,而且也不会看起来一个视频文件很大很大,毕竟视频监控是7*24小时运行的,那这个文件不知道多大,vlc要动态保存多个文件,这就需要模拟执行录像、停止录像的功能来实现,主要的流程就是通过var_CreateGetString函数拿到录像文件存储路径变量,然后var_SetString设置该变量,最后调用var_ToggleBool来模拟单击了录像,停止录像只需要再次执行一次即可,所以要存储成多个视频文件,只需要动态改变录像文件存储路径这个变量即可。

    二、功能特点

    1. 多线程实时播放视频流和本地视频。
    2. 支持windows+linux+mac,支持vlc2和vlc3。
    3. 多线程显示图像,不卡主界面。
    4. 自动重连网络摄像头。
    5. 可设置边框大小即偏移量和边框颜色。
    6. 可设置是否绘制OSD标签即标签文本或图片和标签位置。
    7. 可设置两种OSD位置和风格。
    8. 可设置是否保存到文件以及文件名。
    9. 可直接拖曳文件到vlcwidget控件播放。
    10. 支持h265视频流+rtmp等常见视频流。
    11. 可暂停播放和继续播放。
    12. 支持回调模式和句柄两种模式。
    13. 支持线程读取进度等信息和事件回调两种处理模式。
    14. 自动将当前播放位置和音量大小是否静音以信号发出去。
    15. 提供接口设置播放位置和音量及设置静音。
    16. 支持存储单个视频文件和定时存储视频文件。
    17. 自定义顶部悬浮条,发送单击信号通知,可设置是否启用。

    三、效果图

    四、相关站点

    1. 国内站点:https://gitee.com/feiyangqingyun/QWidgetDemo
    2. 国际站点:https://github.com/feiyangqingyun/QWidgetDemo
    3. 个人主页:https://blog.csdn.net/feiyangqingyun
    4. 知乎主页:https://www.zhihu.com/people/feiyangqingyun/
    5. 体验地址:https://blog.csdn.net/feiyangqingyun/article/details/97565652

    五、核心代码

    void VlcThread::initSave()
    {
        if (!saveFile) {
            return;
        }
    
        if (saveInterval == 0) {
            saveOne(fileName);
        }
    }
    
    void VlcThread::saveOne(const QString &fileName)
    {
        QString temp = url.toUpper();
        QString mux = "ts";
        if (temp.endsWith("MP4") || temp.endsWith("MOV")) {
            mux = "mp4";
        }
    
        //旧格式 :sout=#stream_out_duplicate{dst=display,dst=std{access=file,mux=%1,dst=%2}}
        //新格式 :sout=#duplicate{dst=file{dst=d:/1.mp4},dst=display}
        //vlc3开始不支持stream_out_duplicate只支持duplicate
        QString option = QString(":sout=#duplicate{dst=display,dst=std{access=file,mux=%1,dst=%2}}").arg(mux).arg(fileName);
        setOption(option);
    }
    
    void VlcThread::saveVideo()
    {
        //只有启用了保存文件才保存,这里不要加拓展名,会自动生成
        //文件会在到了间隔后生成
        QMutexLocker locker(&mutex);
        if (saveFile) {
            //重新设置文件名称
            QString dirName = QString("%1/%2").arg(savePath).arg(QDATE);
            newDir(dirName);
            fileName = QString("%1/%2_%3").arg(dirName).arg(fileFlag).arg(STRDATETIME);
            saveVideo(fileName);
        }
    }
    
    void VlcThread::saveVideo(const QString &fileName)
    {
        //除了第一次不要执行外,其他都执行,因为第一次需要先启动存储
        if (!first) {
            stopSave(vlcPlayer);
        }
    
        first = false;
        startSave(vlcPlayer, fileName);
    }
    
    //录像用函数
    static input_thread_t *libvlc_get_input_thread(libvlc_media_player_t *vlcPlayer)
    {
        input_thread_t *input = NULL;
        if (vlcPlayer != NULL) {
            input = vlcPlayer->input.p_thread;
            if (input) {
                vlc_object_hold(input);
            }
        }
    
        return input;
    }
    
    //开始录像
    static void startSave(libvlc_media_player_t *vlcPlayer, const QString &fileName = "")
    {
        input_thread_t *input = libvlc_get_input_thread(vlcPlayer);
        if (input == NULL) {
            return;
        }
    
    #ifdef vlc3
        // 传过来的是带文件名的路径,需要去掉后面的文件名
        QStringList list = fileName.split("/");
        QStringList paths;
        int count = list.count() - 1;
        for (int i = 0; i < count; i++) {
            paths << list.at(i);
        }
    
        QString path = paths.join("/");
        QString name = list.last();
    
        var_CreateGetString(input, "input-record-path");
        var_SetString(input, "input-record-path", path.toUtf8().data());
        //var_CreateGetString(input, "sout-record-dst-prefix");
        //var_SetString(input, "sout-record-dst-prefix", name.toUtf8().data());
        //var_CreateGetString(input, "record-video-name");
        //var_SetString(input, "record-video-name", name.toUtf8().data());
    #else
        var_CreateGetString(input, "input-record-path");
        var_SetString(input, "input-record-path", fileName.toUtf8().constData());
        //var_SetString(input, "sout-record-dst-prefix", fileName.toUtf8().constData());
    #endif
    
        var_ToggleBool(input, "record");
        vlc_object_release(input);
    }
    
    //停止录像
    static void stopSave(libvlc_media_player_t *vlcPlayer)
    {
        input_thread_t *input = libvlc_get_input_thread(vlcPlayer);
        if (input == NULL) {
            return;
        }
    
        var_ToggleBool(input, "record");
        vlc_object_release(input);
    }
    
  • 相关阅读:
    HTML特殊字符编码对照表(备记)
    【java线程】锁机制
    java判断一个字符串是否为空,isEmpty和isBlank的区别
    对Java中properties类的理解
    使用redis的zset实现高效分页查询(附完整代码)
    ServiceStack.Redis高效封装和简易破解
    3.NetDh框架之缓存操作类和二次开发模式简单设计(附源码和示例代码)
    2.NetDh框架之简单高效的日志操作类(附源码和示例代码)
    1.NetDh框架之数据库操作层--Dapper简单封装,可支持多库实例、多种数据库类型等(附源码和示例代码)
    SQL Server索引原理解析
  • 原文地址:https://www.cnblogs.com/feiyangqingyun/p/13437667.html
Copyright © 2011-2022 走看看