一例 LIBVLC 窗口卡死分析

最近学习了lbvlc的使用,在把libvlc和qt结合使用的时候遇到一个问题,指定libvlc播放窗口的时候,切换视频文件出现卡死,重复播放是没有问题的,如果让libvlc自己创建窗口,切换文件也美问题

我写的部分代码:

MediaController类:

//MediaController类(部分)
class MediaController
{
	libvlc_media_t* pMedia;
	libvlc_media_player_t* pPlayer;

public:

	class LibVlcInstance
	{
		libvlc_instance_t* pInstance;
		LibVlcInstance() : pInstance(NULL) {}
		static LibVlcInstance _instance;
    public:
        //初始化VLC句柄
		static LibVlcInstance& getInstance()
		{
			static const char* argv[1] = { "--ignore-config" };
			if (_instance.pInstance == NULL)
				_instance.pInstance = libvlc_new(1, argv);
			return _instance;
		}
        //获得VLC句柄,不要释放
		libvlc_instance_t* getVlcInstance() { return pInstance; }
        //释放VLC句柄
		~LibVlcInstance()
		{
			if (pInstance != NULL)
			{
				libvlc_release(pInstance);
				pInstance = NULL;
			}
		}
	};

	MediaController() : pMedia(NULL), pPlayer(NULL) {};
    //加载和卸载媒体
	bool loadMedia(const char* location);
	void unloadMedia();
    //测试媒体是否加载
	bool isMediaLoaded();
    //加载和卸载播放器
	bool loadPlayer();
	void unloadPlayer();
    //测试播放器是否加载
	bool isPlayerLoaded();

};


bool MediaController::loadMedia(const char* location)
{
	if (isMediaLoaded())
		return false;
	libvlc_instance_t* pVlcInstance = LibVlcInstance::getInstance().getVlcInstance();
	if (pVlcInstance == NULL || location == NULL)
		return false;
	pMedia = libvlc_media_new_path(pVlcInstance, location);
	return isMediaLoaded();
}

void MediaController::unloadMedia()
{
	if (isMediaLoaded())
	{
		libvlc_media_release(pMedia);
		pMedia = NULL;
	}
}

bool MediaController::isMediaLoaded()
{
	return pMedia != NULL;
}

bool MediaController::loadPlayer()
{
	if (isPlayerLoaded() || !isMediaLoaded())
		return false;
	pPlayer = libvlc_media_player_new_from_media(pMedia);
	return isPlayerLoaded();
}
void MediaController::unloadPlayer()
{
	if (isPlayerLoaded())
	{
		libvlc_media_player_release(pPlayer);
		pPlayer = NULL;
	}
}
bool MediaController::isPlayerLoaded()
{
	return pPlayer != NULL;
}

MainWindow类:

//MainWindow 类定义(部分)

class MainWindow : public QMainWindow
{
	Q_OBJECT

public:
	MainWindow(QWidget* parent = nullptr);
	~MainWindow();

private:
	Ui::MainWindow* ui;
	QThread* pThread;
    QWidget* pVideoVidget;
	MediaController controller;
	friend void positionElapsedCallback(const struct libvlc_event_t* p_event, void* p_data);
	friend void playEndCallback(const struct libvlc_event_t* p_event, void* p_data);

signals:
    void resetPlayStatus(bool startPlay);

private slots:
	void on_actionOpen_triggered();
    void onResetPlayStatus(bool startPlay);
};

关键函数:



//关键函数(部分)

void positionElapsedCallback(const struct libvlc_event_t* p_event, void* p_data)
{
    //VLC位置改变,同步进度到UI
	MainWindow* pWindow = (MainWindow*)p_data;
	double position = pWindow->controller.getPlayerPosition();
    if (position >= 0)
        pWindow->ui->videoBar->setProgress(pWindow->ui->videoBar->progressMax() * position);
}

void playEndCallback(const struct libvlc_event_t* p_event, void* p_data)
{
    //播放完成,重置VLC资源,重置UI
	MainWindow* pWindow = (MainWindow*)p_data;
    pWindow->ui->videoBar->setProgress(0);
    emit pWindow->resetPlayStatus(false);
}

//用户打开媒体的处理
void MainWindow::on_actionOpen_triggered()
{
    //锁定UI
	ui->menubar->setEnabled(false);
	ui->videoBar->setEnabled(false);

    //打开文件的名字(转换为系统路径)
	QString _filePath = QFileDialog::getOpenFileName(this, u8"打开");

    //没有路径则解除锁定,有路径则发出重置播放状态信号
    if (!_filePath.isEmpty())
    {
        filePath = QDir::toNativeSeparators(_filePath);
        emit resetPlayStatus(true);
    }
    else
    {
        ui->menubar->setEnabled(true);
        ui->videoBar->setEnabled(true);
    }
}

void MainWindow::onResetPlayStatus(bool startPlay)
{
    //锁定UI
	ui->menubar->setEnabled(false);
	ui->videoBar->setEnabled(false);

	pThread = new QThread();
    connect(pThread, &QThread::started, pThread, [this]() -> void {
        //卸载vlc资源
        controller.unloadPlayer();
        controller.unloadMedia();

        QByteArray utf8Path = filePath.toUtf8();
        //加载vlc资源
        controller.loadMedia(utf8Path.data());
        controller.loadPlayer();

        if (controller.isMediaLoaded() && controller.isPlayerLoaded())
        {
            //回调和播放窗口设置
            controller.setEventHandlerAndUserData(libvlc_MediaPlayerPositionChanged, positionElapsedCallback, this);
            controller.setEventHandlerAndUserData(libvlc_MediaPlayerEndReached, playEndCallback, this);
            if(pVideoWidget != NULL)
                controller.setPlayerWindow((HWND)pVideoWidget->winId());
            //从开头播放
            controller.setPlayerPosition(0);
        }
        else
        {
            emit playFailed();
        }

		pThread->quit();
		});

    connect(pThread, &QThread::finished, this, [this,startPlay]() -> void {
        //解锁UI
        ui->menubar->setEnabled(true);
        ui->videoBar->setEnabled(true);
        //释放线程资源
        delete pThread;
        pThread = NULL;
        //设置播放控制窗口的播放状态和进度
        ui->videoBar->setPlayPause(startPlay);
        ui->videoBar->setElapsed(0);
        ui->videoBar->setProgress(0);

		});

	pThread->start();
}

复现bug,用windbg查看线程栈是这样的:

 

可以看到libvlc_vlc_release底下调用了WaitForSingleObjecEx然后一直没返回

不过由于x64的原因这里的参数并不正确

不过可以手动卡libvlc_media_player_release,,卡在libvlc_media_player_release里面后,再卡WaitForSingleObjectEx 卡 WaitForSingleObjectEx 的时候看 rcx 这就是第一个参数

(真正windbg调试的时候稍有区别,这是复现,不过都是看rcx)

按照这个思路重启测试,在一次WaitForSingleObjectEx调用之后就没触发断点,然后程序也是卡死的

那就看这次的rcx,是这样:

然后!handle看看是个啥:

 

原来是一个线程,线程ID在Thread ID那一栏,前面的是进程ID后面的是线程ID,注意是16进制

~* 查找下现在在运行的线程,这里显示的线程ID也是16进制,能找到这个ID:

 

~ 32 s 切换到这个线程(32是查到的序号),然后 kb 查看这线程的调用栈:

 

看到这是一个消息循环的执行过程,联想到libvlc设置的窗口句柄,可以知道这个是libvlc处理窗口消息的线程,但是怎么知道消息循环卡死了呢?,这个时候,可以卡GetMessage(这个线程调用的GetMessage)试试,正常消息循环一次消息处理时非常迅速的,加断点继续执行之后应该马上就能卡住

我这里是GetMessage就卡了一次,然后没下次了

 

 基本可以判断是窗口消息循环卡死,主线程又在等待该消息循环退出,导致的卡死

仔细看卡死的时候执行的指令,是syscall,可以知道是卡在内核里面出不来:

 分析结束,如何导致消息循环卡死的我不得而知(毕竟在内核里面)

而且这也是我用windbg第一次分析问题

解决办法,在不知道消息循环为何卡死的情况下,我只能在释放vlx资源之前,关调窗口,这样卡死的条件就不存在了

改过的代码:

void MainWindow::onResetPlayStatus(bool startPlay)
{
    //锁定UI
	ui->menubar->setEnabled(false);
	ui->videoBar->setEnabled(false);

	pThread = new QThread();
    connect(pThread, &QThread::started, pThread, [this]() -> void {
        //这两行关键,关掉vlc之前用的窗口,重新开个
        if (controller.isPlayerLoaded())
            initVideoWindow();

        //卸载vlc资源
        controller.unloadPlayer();
        controller.unloadMedia();

        QByteArray utf8Path = filePath.toUtf8();
        //加载vlc资源
        controller.loadMedia(utf8Path.data());
        controller.loadPlayer();

        if (controller.isMediaLoaded() && controller.isPlayerLoaded())
        {
            //回调和播放窗口设置
            controller.setEventHandlerAndUserData(libvlc_MediaPlayerPositionChanged, positionElapsedCallback, this);
            controller.setEventHandlerAndUserData(libvlc_MediaPlayerEndReached, playEndCallback, this);
            if(pVideoWidget != NULL)
                controller.setPlayerWindow((HWND)pVideoWidget->winId());
            //从开头播放
            controller.setPlayerPosition(0);
        }
        else
        {
            emit playFailed();
        }

		pThread->quit();
		});

    connect(pThread, &QThread::finished, this, [this,startPlay]() -> void {
        //解锁UI
        ui->menubar->setEnabled(true);
        ui->videoBar->setEnabled(true);
        //释放线程资源
        delete pThread;
        pThread = NULL;
        //设置播放控制窗口的播放状态和进度
        ui->videoBar->setPlayPause(startPlay);
        ui->videoBar->setElapsed(0);
        ui->videoBar->setProgress(0);

		});

	pThread->start();
}

//初始化视频窗口
void MainWindow::initVideoWindow()
{
    if (pVideoWidget != NULL)
    {
        pVideoWidget->close();
        delete pVideoWidget;
    }

    pVideoWidget = new QWidget(this);
    pVideoWidget->setAutoFillBackground(true);
    QPalette pal = pVideoWidget->palette();
    pal.setColor(QPalette::Window, Qt::black);
    pVideoWidget->setPalette(pal);

    resizeVideoWindow();

    pVideoWidget->show();
}

至此问题解决

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值