vlc简单推流本地视频记录

VLC依赖库准备

vlc库下载地址:
官网地址:https://download.videolan.org/pub/videolan/vlc/
解压后如下:
解压后如上图

工程准备

1.将vlc库sdk文件夹中的include和lib文件夹拷入工程目录中,根据自己习惯放置,如:

在这里插入图片描述

2.工程中添加依赖库路径和头文件路径:

INCLUDEPATH +=  $$PWD/include/vlc \
                $$PWD/include/vlc/plugins

LIBS += -L$$PWD/lib -llibvlc -llibvlccore

DESTDIR = $$PWD/bin

3.代码准备

a.操作界面如下:

在这里插入图片描述
上述界面中的推流参数表示转码参数,如:
#transcode{vcodec=h264,acodec=mpga,ab=128,channels=2,samplerate=44100,scodec=none}

vlc推流参数表示用vlc客户端生成的完整参数,包括推流方式,如:
#transcode{vcodec=h264,acodec=mpga,ab=128,channels=2,samplerate=44100,scodec=none}:rtp{sdp=rtsp://127.0.0.1:10086/live}

b.h文件代码
#ifndef VLCSTREAMER_H
#define VLCSTREAMER_H

#include <QObject>
#include <QIODevice>
#include <QProcess>
#include <QUrl>
#include <QTimer>
#include <QFuture>
#include <QSemaphore>

struct libvlc_instance_t;
struct libvlc_media_t;
struct libvlc_media_player_t;

class VLCStreamer : public QObject
{
    Q_OBJECT
public:
    explicit VLCStreamer(QObject *parent = nullptr);
    ~VLCStreamer();

    /**
     * @brief startStreaming 开始推流
     * @param strFile 本地推流媒体文件
     * @param strIP 推流IP
     * @param nPort 推流端口
     * @param nStreamType 推流类型
     * @param strParams 转码参数
     * @return true推流成功,false推流失败
     */
    bool startStreaming(const QString &strFile, const QString &strIP,const int& nPort,const int& nStreamType,const QString& strParams = "");

    /**
     * @brief startStreaming 开始推流
     * @param strFile 本地推流媒体文件
     * @param strParams vlc客户端生成的推流指令 #transcode{vcodec=h264,acodec=mpga,ab=128,channels=2,samplerate=44100,scodec=none}:rtp{sdp=rtsp://127.0.0.1:10086/live}
     * @return true推流成功,false推流失败
     */
    bool startStreaming(const QString &strFile,const QString& strParams);

    /**
     * @brief stopStreaming 停止推流
     * @return true停止成功,false停止失败
     */
    bool stopStreaming();
signals:
    void streamingFinished();//无用

private:
    /**
     * @brief convertOupputUrl 转换为推流指令
     * @param nStreamType 推流方式
     * @param strIP 推流IP
     * @param nPort 推流端口
     * @return 推流指令
     */
    QString convertOupputUrl(const int& nStreamType,const QString &strIP,const int& nPort);

private:
    void onCheckThread();//无用

private:
    libvlc_instance_t       *m_pVlcInstance = nullptr;
    libvlc_media_t          *m_pMedia = nullptr;            //无用
    libvlc_media_player_t   *m_pMediaPlayer = nullptr;      //无用

    bool                    m_bHasTask = false;

    QFuture<void>           m_future;               //无用
    QSemaphore              m_semaphore_timer;      //无用
    int                     m_nMediaLength = 0;     //无用


};

#endif // VLCSTREAMER_H

c.cpp文件代码
#include "vlcStreamer.h"


// VLC头文件
#include "vlc/vlc.h"
#include "GlobalDefine.h"

#include <QDir>
#include <QDebug>
#include <QtConcurrent>

#define VIDEO_MEDIA_NAME        "Video"   //todo 这个名字重复有影响吗?比如这个类实例化两次

VLCStreamer::VLCStreamer(QObject *parent) : QObject(parent)
{
    //创建vlc实例
    m_pVlcInstance = libvlc_new (0, nullptr);
    // m_pMediaPlayer = libvlc_media_player_new(m_pVlcInstance);
}

VLCStreamer::~VLCStreamer()
{
    // 停止推流
    libvlc_vlm_stop_media(m_pVlcInstance, VIDEO_MEDIA_NAME);
    // libvlc_media_player_stop(m_pMediaPlayer);


    //释放播放器
    // libvlc_media_player_release(m_pMediaPlayer);

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

    // 释放VLC实例
    libvlc_vlm_release(m_pVlcInstance);

    m_pVlcInstance = nullptr;

    // libvlc_media_player_release(m_pMediaPlayer);

    // libvlc_media_release(m_pMedia);

    if(m_future.isRunning())
    {
        m_semaphore_timer.release();
        m_future.waitForFinished();
    }
}

bool VLCStreamer::startStreaming(const QString &strFile, const QString &strIP, const int &nPort, const int &nStreamType, const QString &strParams)
{
    if(m_bHasTask)
    {
        return false;
    }
    // 转码参数:strParams

    // 网络参数:rtp{sdp=rtsp://xx.xx.xx.xx:yyyy/}
    // 表示本机ip时,可省略ip,只写端口,如rtp{sdp=rtsp://:8554/}
    QString strNetPara = convertOupputUrl(nStreamType,strIP,nPort);

    //如果有转码参数则完整的rtsp如下
    // 如sout = "#transcode{vcodec=h264,acodec=mpga,ab=128,channels=2,samplerate=44100,scodec=none}:rtp{sdp=rtsp://127.0.0.1:8554/}"
    QString strsout = "";
    if(strParams.isEmpty())
    {
        strsout = QString("#%1").arg(strNetPara);
    }
    else
    {
        //strParams 格式为:transcode{vcodec=h264,acodec=mpga,ab=128,channels=2,samplerate=44100,scodec=none}
        strsout = QString("#%1:%2").arg(strParams).arg(strNetPara);
    }

    // 将推流视频路径转换为本地系统风格,win下"a\\b\\c",linux下"a/b/c"
    QString path = QDir::toNativeSeparators(strFile);

    // 添加名为VIDEO_MEDIA_NAME的广播
    int ret = libvlc_vlm_add_broadcast(m_pVlcInstance,
                                       VIDEO_MEDIA_NAME,
                                       path.toStdString().c_str(),
                                       strsout.toStdString().c_str(),
                                       0,
                                       nullptr,
                                       true,
                                       false);//是否循环播放
    if (ret != 0)
    {
        return false;
    }
#if 0
    m_pMedia = libvlc_media_new_path(m_pVlcInstance,path.toStdString().c_str());

    libvlc_media_player_set_media(m_pMediaPlayer,m_pMedia);


    if(libvlc_media_player_play(m_pMediaPlayer))
    {
        return false;
    }
#endif
    // 播放该广播
    ret = libvlc_vlm_play_media(m_pVlcInstance, VIDEO_MEDIA_NAME);


    if(ret == 0)
    {
        m_bHasTask = true;
        // if(!m_future.isRunning())
        // {
        //     m_future = QtConcurrent::run(this,&VLCStreamer::onCheckThread);
        // }
    }

    // m_nMediaLength = libvlc_vlm_get_media_instance_length(m_pVlcInstance,VIDEO_MEDIA_NAME,nInstance);//todo 组后一个参数的id怎么获取的?
    qDebug()<<libvlc_vlm_show_media( m_pVlcInstance,
                          VIDEO_MEDIA_NAME );//这里打印了流信息,后续如果要判断推流结束可能需要解析才行,具体信息看最后注释内容
    return m_bHasTask;
}


bool VLCStreamer::startStreaming(const QString &strFile, const QString &strParams)
{
    if(m_bHasTask)
    {
        return false;
    }
    // 将推流视频路径转换为本地系统风格,win下"a\\b\\c",linux下"a/b/c"
    QString path = QDir::toNativeSeparators(strFile);

    // 添加名为VIDEO_MEDIA_NAME的广播
    int ret = libvlc_vlm_add_broadcast(m_pVlcInstance,
                                       VIDEO_MEDIA_NAME,
                                       path.toStdString().c_str(),
                                       strParams.toStdString().c_str(),
                                       0,
                                       nullptr,
                                       true,
                                       false);//是否循环播放
    if (ret != 0)
    {
        return false;
    }

    // 播放该广播
    ret = libvlc_vlm_play_media(m_pVlcInstance, VIDEO_MEDIA_NAME);
    if(ret == 0)
    {
        m_bHasTask = true;
        // if(!m_future.isRunning())
        // {
        //     m_future = QtConcurrent::run(this,&VLCStreamer::onCheckThread);
        // }
    }
    // m_nMediaLength = libvlc_vlm_get_media_instance_length(m_pVlcInstance,VIDEO_MEDIA_NAME,0);//todo 组后一个参数的id怎么获取的?
    return m_bHasTask;
}

//当前程序缺陷,需要手动停止,哪怕是推流结束
bool VLCStreamer::stopStreaming()
{
    if(!m_bHasTask)
    {
        return false;
    }
#if 1
    // 停止推流
     int ret = libvlc_vlm_stop_media(m_pVlcInstance, VIDEO_MEDIA_NAME);

    if(ret == 0)
    {
        m_bHasTask = false;
        m_semaphore_timer.release();
        m_future.waitForFinished();
        emit streamingFinished();

    }
    else
    {
        return false;
    }
#else
        libvlc_media_player_stop(m_pMediaPlayer);
        m_bHasTask = true;
        m_semaphore_timer.release();
        m_future.waitForFinished();
        emit streamingFinished();
#endif
    return !m_bHasTask;
}

QString VLCStreamer::convertOupputUrl(const int &nStreamType, const QString &strIP, const int &nPort)
{
    QString strOutputUrl = "";
    switch (nStreamType)
    {
    case E_STREAM_TYPE_RTSP:
        strOutputUrl = QString("rtp{sdp=rtsp://%1:%2/}").arg(strIP).arg(nPort);
        break;
    case E_STREAM_TYPE_RTP_TS:
        strOutputUrl = QString("rtp{dst=%1,port=%2,mux=ts}").arg(strIP).arg(nPort);
        break;
    case E_STREAM_TYPE_RTP_AVP:
        strOutputUrl = QString("rtp{dst=%1,port=%2}").arg(strIP).arg(nPort);
        break;
    case E_STREAM_TYPE_UDP:
        strOutputUrl = QString("udp{dst=%1:%2}").arg(strIP).arg(nPort); //todo 客户端不能播放,待后续排查
        break;
    case E_STREAM_TYPE_HTTP:
        strOutputUrl = QString("http{mux=ffmpeg{mux=flv},dst=%1:%2/}").arg(strIP).arg(nPort);
        break;
    default:
        break;
    }
    return strOutputUrl;
}

void VLCStreamer::onCheckThread()
{
    int nTime = 0;
    while(m_bHasTask)
    {
        if(m_semaphore_timer.tryAcquire(1,20))
        {
            break;
        }
        else
        {
            //  nTime = libvlc_vlm_get_media_instance_time(m_pVlcInstance,VIDEO_MEDIA_NAME,0);

            //  if(nTime >= m_nMediaLength)
            // {
            //     m_bHasTask = false;
            //     emit streamingFinished();
            //     break;
            // }
            qDebug()<<libvlc_vlm_show_media( m_pVlcInstance,
                                              VIDEO_MEDIA_NAME );
        }
    }
}
/**
 *
 *
 * {
    "name": "Video",
    "type": "broadcast",
    "enabled": "yes",
    "loop": "no",
    "inputs": [
        "E:\little.mp4"

    ],
    "output": "#rtp{sdp=rtsp://127.0.0.1:10086/}",
    "options": null,
    "instances": {
        "instance": {
            "name": "default",
            "state": "playing",
            "position": "0.007775",
            "time": "1657000",
            "length": "213120000",
            "rate": "1.000000",
            "title": "0",
            "chapter": "0",
            "can-seek": "1",
            "playlistindex": "1"

        }

    }

}


{
    "name": "Video",
    "type": "broadcast",
    "enabled": "yes",
    "loop": "no",
    "inputs": [
        "E:\little.mp4"

    ],
    "output": "#rtp{sdp=rtsp://127.0.0.1:10086/}",
    "options": null,
    "instances": {
        "instance": {
            "name": "default",
            "state": "playing",
            "position": "0.007775",
            "time": "1657000",
            "length": "213120000",
            "rate": "1.000000",
            "title": "0",
            "chapter": "0",
            "can-seek": "1",
            "playlistindex": "1"

        }

    }

}
 *
 *
 *
 *
 *
 *
 *
 *
 *
 * */

d.代码注意事项

加入vlc头文件和函数时,编译会报错,如下:
在这里插入图片描述
解决办法为:
在这里插入图片描述
在vlc.h中加入typedef __int64 ssize_t;
另需要注意,可执行程序路径下需要将vlc解压文件夹中的plugins文件放入可执行目录同级,否则会出错,同时记得把依赖的dll拷贝到该目录。
在这里插入图片描述
最终情况上图。

测试结果

5种方式测试情况如下:
rtsp:
推流:#rtp{sdp=rtsp://127.0.0.1:8554/}
拉流:rtsp://127.0.0.1:10086/

rtp:
推流:#rtp{dst=127.0.0.1,port=5004,mux=ts}
拉流:rtp://@127.0.0.1:10086

推流:#rtp{dst=127.0.0.1,port=5004}
拉流:rtp://@127.0.0.1:10086 报错,原因未知
rtp有两种方式,并且在vlc客户端推流时,会填一个流名称,此代码默认为空,不填
在这里插入图片描述

udp:
推流:#udp{dst=127.0.0.1:1234}
拉流:udp://@127.0.0.1:10086/

http:
推流:#http{mux=ffmpeg{mux=flv},dst=:8080/}
拉流:http://127.0.0.1:10086/
基本都可以通过,注意拉流地址最后的 '/'有的时候如果不加会出错,vlc客户端播放失败。
在这里插入图片描述rtsp测试截图。

参考资料

参考地址https://blog.csdn.net/zyhse/article/details/113760441

修改完善【20241001】

1.添加状态推流状态逻辑

完善前面代码不能知道停止状态的问题,虽然不完善,好歹有个状态了。不能停止的原因是,vlc里面没有相关函数,只有json数据表示状态。
在这里插入图片描述vlc头文件截图

2.推流后,再次推流会异常,解决此bug

推流结束后,应该移除此推流实例,否则不能再次用同样的实例和名字进行推流。

3.优化后代码如下,需要的自行复制,界面逻辑未做修改,只需修改如下cpp和h文件即可

h文件代码

#ifndef VLCSTREAMER_H
#define VLCSTREAMER_H

#include <QObject>
#include <QIODevice>
#include <QProcess>
#include <QUrl>
#include <QTimer>
#include <QFuture>
#include <QSemaphore>

struct libvlc_instance_t;
struct libvlc_media_t;
struct libvlc_media_player_t;

enum E_STOP_TYPE
{
    E_STOP_TYPE_UNKNOWNED = 0,  //初始化状态,未知
    E_STOP_TYPE_NORMAL = 1,     //正常推流结束停止
    E_STOP_TYPE_FORCED ,        //强制停止,stopStreaming()
    E_STOP_TYPE_ERROR           //媒体异常结束停止
};

class VLCStreamer : public QObject
{
    Q_OBJECT
public:
    explicit VLCStreamer(QObject *parent = nullptr);
    ~VLCStreamer();

    /**
     * @brief startStreaming 开始推流
     * @param strFile 本地推流媒体文件
     * @param strIP 推流IP
     * @param nPort 推流端口
     * @param nStreamType 推流类型
     * @param strParams 转码参数
     * @return true推流成功,false推流失败
     */
    bool startStreaming(const QString &strFile, const QString &strIP,const int& nPort,const int& nStreamType,const QString& strParams = "");

    /**
     * @brief startStreaming 开始推流
     * @param strFile 本地推流媒体文件
     * @param strParams vlc客户端生成的推流指令 #transcode{vcodec=h264,acodec=mpga,ab=128,channels=2,samplerate=44100,scodec=none}:rtp{sdp=rtsp://127.0.0.1:10086/live}
     * @return true推流成功,false推流失败
     */
    bool startStreaming(const QString &strFile,const QString& strParams);

    /**
     * @brief stopStreaming 停止推流
     * @return true停止成功,false停止失败
     */
    bool stopStreaming();
signals:
    void streamingFinished(const int& nStopType);//无用

private:
    /**
     * @brief convertOupputUrl 转换为推流指令
     * @param nStreamType 推流方式
     * @param strIP 推流IP
     * @param nPort 推流端口
     * @return 推流指令
     */
    QString convertOupputUrl(const int& nStreamType,const QString &strIP,const int& nPort);

    /**
     * @brief parseJsonData 解析json数据
     * @param jsonArray json 数据
     * @param nVideoTotalTime 视频总长度
     * @param nCurTime 当前时间
     * @param IsNull 判断instances是否为空,为空表示播放结束
     * @return true 解析成功,false 解析失败。
     */
    bool parseJsonData(const QByteArray& jsonArray,unsigned long long& nVideoTotalTime,unsigned long long& nCurTime,bool& IsNull);

private:
    void onCheckThread();//无用

private:
    libvlc_instance_t       *m_pVlcInstance = nullptr;  //vlc实例
    int                     m_nMediaStopType = 0;       //推流结束方式
    bool                    m_bHasTask = false;         //当前是否正在推流
    QFuture<void>           m_future;                   //检测推流状态的线程
    QSemaphore              m_semaphore_timer;          //检查周期定时器信号量


    libvlc_media_t          *m_pMedia = nullptr;            //无用
    libvlc_media_player_t   *m_pMediaPlayer = nullptr;      //无用
};

#endif // VLCSTREAMER_H

cpp文件代码

#include "vlcStreamer.h"


// VLC头文件
#include "vlc/vlc.h"
#include "GlobalDefine.h"

#include <QDir>
#include <QDebug>
#include <QtConcurrent>
#include <QFileInfo>

#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>

#define VIDEO_MEDIA_NAME        "Video"   //todo 这个名字重复有影响吗?比如这个类实例化两次

#define ERROR_TIME          2,000,000   //结束时间和总时间大于2秒,判断为异常结束

VLCStreamer::VLCStreamer(QObject *parent) : QObject(parent)
{
    //创建vlc实例
    m_pVlcInstance = libvlc_new (0, nullptr);
    // m_pMediaPlayer = libvlc_media_player_new(m_pVlcInstance);
}

VLCStreamer::~VLCStreamer()
{
    // 停止推流
    libvlc_vlm_stop_media(m_pVlcInstance, VIDEO_MEDIA_NAME);
    // libvlc_media_player_stop(m_pMediaPlayer);


    //释放播放器
    // libvlc_media_player_release(m_pMediaPlayer);

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

    // 释放VLC实例
    libvlc_vlm_release(m_pVlcInstance);

    m_pVlcInstance = nullptr;

    // libvlc_media_player_release(m_pMediaPlayer);

    // libvlc_media_release(m_pMedia);

    if(m_future.isRunning())
    {
        m_semaphore_timer.release();
        m_future.waitForFinished();
    }
}

bool VLCStreamer::startStreaming(const QString &strFile, const QString &strIP, const int &nPort, const int &nStreamType, const QString &strParams)
{
    QFileInfo tmpInfo(strFile);
    if(!tmpInfo.exists())
    {
        return false;
    }
    if(m_bHasTask)
    {
        return false;
    }
    // 转码参数:strParams

    // 网络参数:rtp{sdp=rtsp://xx.xx.xx.xx:yyyy/}
    // 表示本机ip时,可省略ip,只写端口,如rtp{sdp=rtsp://:8554/}
    QString strNetPara = convertOupputUrl(nStreamType,strIP,nPort);

    //如果有转码参数则完整的rtsp如下
    // 如sout = "#transcode{vcodec=h264,acodec=mpga,ab=128,channels=2,samplerate=44100,scodec=none}:rtp{sdp=rtsp://127.0.0.1:8554/}"
    QString strsout = "";
    if(strParams.isEmpty())
    {
        strsout = QString("#%1").arg(strNetPara);
    }
    else
    {
        //strParams 格式为:transcode{vcodec=h264,acodec=mpga,ab=128,channels=2,samplerate=44100,scodec=none}
        strsout = QString("#%1:%2").arg(strParams).arg(strNetPara);
    }

    // 将推流视频路径转换为本地系统风格,win下"a\\b\\c",linux下"a/b/c"
    QString path = QDir::toNativeSeparators(strFile);

    // 添加名为VIDEO_MEDIA_NAME的广播
    int ret = libvlc_vlm_add_broadcast(m_pVlcInstance,
                                       VIDEO_MEDIA_NAME,
                                       path.toStdString().c_str(),
                                       strsout.toStdString().c_str(),
                                       0,
                                       nullptr,
                                       true,
                                       false);//是否循环播放
    if (ret != 0)
    {
        return false;
    }
#if 0
    m_pMedia = libvlc_media_new_path(m_pVlcInstance,path.toStdString().c_str());

    libvlc_media_player_set_media(m_pMediaPlayer,m_pMedia);


    if(libvlc_media_player_play(m_pMediaPlayer))
    {
        return false;
    }
#endif
    // 播放该广播
    ret = libvlc_vlm_play_media(m_pVlcInstance, VIDEO_MEDIA_NAME);


    if(ret == 0)
    {
        m_bHasTask = true;
        if(!m_future.isRunning())
        {
            m_future = QtConcurrent::run(this,&VLCStreamer::onCheckThread);
        }
    }

    // m_nMediaLength = libvlc_vlm_get_media_instance_length(m_pVlcInstance,VIDEO_MEDIA_NAME,nInstance);//todo 组后一个参数的id怎么获取的?
    qDebug()<<libvlc_vlm_show_media( m_pVlcInstance,
                          VIDEO_MEDIA_NAME );//这里打印了流信息,后续如果要判断推流结束可能需要解析才行,具体信息看最后注释内容
    return m_bHasTask;
}


bool VLCStreamer::startStreaming(const QString &strFile, const QString &strParams)
{
    QFileInfo tmpInfo(strFile);
    if(!tmpInfo.exists())
    {
        return false;
    }
    if(m_bHasTask)
    {
        return false;
    }
    // 将推流视频路径转换为本地系统风格,win下"a\\b\\c",linux下"a/b/c"
    QString path = QDir::toNativeSeparators(strFile);

    // 添加名为VIDEO_MEDIA_NAME的广播
    int ret = libvlc_vlm_add_broadcast(m_pVlcInstance,
                                       VIDEO_MEDIA_NAME,
                                       path.toStdString().c_str(),
                                       strParams.toStdString().c_str(),
                                       0,
                                       nullptr,
                                       true,
                                       false);//是否循环播放
    if (ret != 0)
    {
        return false;
    }

    // 播放该广播
    ret = libvlc_vlm_play_media(m_pVlcInstance, VIDEO_MEDIA_NAME);
    if(ret == 0)
    {
        m_bHasTask = true;
        if(!m_future.isRunning())
        {
            m_future = QtConcurrent::run(this,&VLCStreamer::onCheckThread);
        }
    }
    // m_nMediaLength = libvlc_vlm_get_media_instance_length(m_pVlcInstance,VIDEO_MEDIA_NAME,0);//todo 组后一个参数的id怎么获取的?
    return m_bHasTask;
}

//当前程序缺陷,需要手动停止,哪怕是推流结束  【20241001已经解决,但是不是很合理】
bool VLCStreamer::stopStreaming()
{
    if(!m_bHasTask)
    {
        return false;
    }
#if 1
    // 停止推流
     int ret = libvlc_vlm_stop_media(m_pVlcInstance, VIDEO_MEDIA_NAME);

    if(ret == 0)
    {
        m_nMediaStopType = E_STOP_TYPE_FORCED;
        // m_semaphore_timer.release();
        // m_future.waitForFinished();
    }
    else
    {
        return false;
    }
#else
        libvlc_media_player_stop(m_pMediaPlayer);
        m_bHasTask = true;
        m_semaphore_timer.release();
        m_future.waitForFinished();
        emit streamingFinished();
#endif
    return true;
}

QString VLCStreamer::convertOupputUrl(const int &nStreamType, const QString &strIP, const int &nPort)
{
    QString strOutputUrl = "";
    switch (nStreamType)
    {
    case E_STREAM_TYPE_RTSP:
        strOutputUrl = QString("rtp{sdp=rtsp://%1:%2/}").arg(strIP).arg(nPort);
        break;
    case E_STREAM_TYPE_RTP_TS:
        strOutputUrl = QString("rtp{dst=%1,port=%2,mux=ts}").arg(strIP).arg(nPort);
        break;
    case E_STREAM_TYPE_RTP_AVP:
        strOutputUrl = QString("rtp{dst=%1,port=%2}").arg(strIP).arg(nPort);
        break;
    case E_STREAM_TYPE_UDP:
        strOutputUrl = QString("udp{dst=%1:%2}").arg(strIP).arg(nPort); //todo 客户端不能播放,待后续排查
        break;
    case E_STREAM_TYPE_HTTP:
        strOutputUrl = QString("http{mux=ffmpeg{mux=flv},dst=%1:%2/}").arg(strIP).arg(nPort);
        break;
    default:
        break;
    }
    return strOutputUrl;
}

bool VLCStreamer::parseJsonData(const QByteArray &jsonArray,unsigned long long &nVideoTotalTime,unsigned long long &nCurTime,bool& IsNull)
{
    // qDebug()<<"==================================";
    // qDebug()<<jsonArray;
    // qDebug()<<"==================================";
    QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonArray);
    if(jsonDoc.isEmpty())
    {
        return false;
    }
    if(!jsonDoc.isObject())//根据打印结果,就是json对象
    {
        return false;
    }

    QJsonObject jsonObj = jsonDoc.object();

    QJsonValue jsonValue = jsonObj.value("instances");

    if(jsonValue.isNull())
    {
        IsNull = true;
        return false;
    }
    /**
     *
     *
     *
     *结束后的状态信息
    "{
    "name": "Video",
    "type": "broadcast",
    "enabled": "yes\,
    "loop\": "no"
    "inputs": ["E:\\little.mp4" ],
    "output": "#rtp{sdp=rtsp://127.0.0.1:10086/}",
    "options": null,
    "instances": null
    }"
     *
     *
     *
     *
     * */
    if(!jsonValue.isObject())//按照正常逻辑,这里肯定为object,但是播放完成后,instances里面的内容就为空了
    {
        return false;
    }

    jsonObj = jsonValue.toObject();//得到第二个instance 对象

    jsonValue = jsonObj.value("instance");//得到第二个instance对象里面的对象

    if(!jsonValue.isObject())
    {
        return false;
    }

    jsonObj = jsonValue.toObject();
    if(jsonObj.contains("length") && jsonObj.contains("time"))//不能通过时间或者state来判断,因为播放完成后,vlc会打印结束[main input debug: EOF reached],然乎里面的state还是为playing,甚至时间和播放进度都未达到结束时间,播放进度未到达1,接近1,然后突然结束,instances里面的内容为null
    {
        bool res = false;
        nVideoTotalTime = jsonObj.value("length").toVariant().toLongLong(&res);
        if(!res)
        {
            return false;
        }
        nCurTime = jsonObj.value("time").toVariant().toLongLong(&res);
        if(!res)
        {
            return false;
        }
    }
    else
    {
        return false;
    }
    return true;
}

void VLCStreamer::onCheckThread()
{

    unsigned long long nCurTime = 0,nTotalTime = 0;//播放当前时刻和视频总时长,单位应该是微秒
    bool bInit = false;
    while(true)
    {
        if(m_semaphore_timer.tryAcquire(1,20))
        {
            m_nMediaStopType = E_STOP_TYPE_FORCED;
            m_bHasTask = false;
            break;
        }
        else
        {
            QByteArray jsonData = libvlc_vlm_show_media( m_pVlcInstance,
                                              VIDEO_MEDIA_NAME );
            jsonData = jsonData.trimmed();
            bool bIsNull = false;
            if(!parseJsonData(jsonData,nTotalTime,nCurTime,bIsNull))
            {
                if(bInit && bIsNull)
                {
                    if(nTotalTime - nCurTime > ERROR_TIME)
                    {
                        m_nMediaStopType = E_STOP_TYPE_ERROR;
                    }
                    else
                    {
                        if(E_STOP_TYPE_UNKNOWNED == m_nMediaStopType)
                        {
                            m_nMediaStopType = E_STOP_TYPE_NORMAL;
                        }
                    }
                    m_bHasTask = false;
                    break;//这里表示解析失败了,当推流结束时,json中instances里面的内容会为空,此时表示推流结束了
                }
                continue;
            }
            if(nTotalTime != 0)
            {
                bInit = true;//表示正常播放了,因为目前无法知道退出状态,所以当这里为true时,表示正常推流过
            }
            if((nCurTime >= nTotalTime) &&(nCurTime!=0 && nTotalTime!=0))
            {
                m_bHasTask = false;
                break;
            }
        }
    }

    libvlc_vlm_del_media(m_pVlcInstance,VIDEO_MEDIA_NAME);//结束此媒体播放任务

    emit streamingFinished(m_nMediaStopType);

    switch(m_nMediaStopType)
    {
    case E_STOP_TYPE_NORMAL:
        qDebug()<<"video push finished normal";
        break;
    case E_STOP_TYPE_FORCED:
        qDebug()<<"video push finished forced";
        break;
    case E_STOP_TYPE_ERROR:
        qDebug()<<"video push finished error";
        break;
    default:
        qDebug()<<"video push finished unknowned";
        break;
    }
    m_nMediaStopType = E_STOP_TYPE_UNKNOWNED;
}
/**
 *
 *
 * {
    "name": "Video",
    "type": "broadcast",
    "enabled": "yes",
    "loop": "no",
    "inputs": [
        "E:\little.mp4"

    ],
    "output": "#rtp{sdp=rtsp://127.0.0.1:10086/}",
    "options": null,
    "instances": {
        "instance": {
            "name": "default",
            "state": "playing",
            "position": "0.007775",
            "time": "1657000",
            "length": "213120000",
            "rate": "1.000000",
            "title": "0",
            "chapter": "0",
            "can-seek": "1",
            "playlistindex": "1"

        }

    }

}


{
    "name": "Video",
    "type": "broadcast",
    "enabled": "yes",
    "loop": "no",
    "inputs": [
        "E:\little.mp4"

    ],
    "output": "#rtp{sdp=rtsp://127.0.0.1:10086/}",
    "options": null,
    "instances": {
        "instance": {
            "name": "default",
            "state": "playing",
            "position": "0.007775",
            "time": "1657000",
            "length": "213120000",
            "rate": "1.000000",
            "title": "0",
            "chapter": "0",
            "can-seek": "1",
            "playlistindex": "1"

        }

    }

}
 *
 *
 *
 *
 *
 *
 *
 *
 *
 * */

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值