最近学习了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();
}
至此问题解决