制作一个播放器(一)
制作一个播放器(二)
接着上一章的热身,咱们继续写播放器。上一篇中咱们用的是句柄的方式来播放视频。实际开发中,我们更多的是把数据给回调出来,这样更好的去显示视频,处理视频。这期,继续写一个小demo。
这一篇,咱们把数据给回调出来。
做个小广告
推荐免费学习直播课程:C/C++Linux服务器开发高级架构师学习视频
这期,使用回调方式,将数据直接回调出来播放。
实现代码
main.h
#ifndef _MAIN_H_
#define _MAIN_H_
#include <QWidget>
#include <QPainter>
class CDisplay : public QWidget
{
Q_OBJECT
public:
CDisplay(QWidget* parent = nullptr) {}
~CDisplay() = default;
void SetImage(QImage img)
{
m_img = img;
update();
}
protected:
void paintEvent(QPaintEvent* e)
{
if (m_img.isNull())
{
return;
}
QPainter p(this);
p.drawImage(rect(), m_img);
}
private:
QImage m_img;
};
#endif // _MAIN_H_
main.cpp
#include <QApplication>
#include "vlc/vlc.h"
#include <QDialog>
#include <QHBoxLayout>
#include <QObject>
#include <memory>
#include <stdint.h>
#include <thread>
#include "main.h"
#define qtu(str) ((str).toUtf8().constData())
const char* const vlc_args[] = {
"-I",
"dummy",
"--ignore-config",
"--rtsp-frame-buffer-size=1000000",
"--rtsp-tcp",
};
// 通过方式获取宽高
int g_width = 0;
int g_height = 0;
std::shared_ptr<uint8_t> g_spBuffer;
libvlc_media_player_t* g_pVlcMediaPlayer = nullptr;
CDisplay* g_pDisplay = nullptr;
static void* lock(void* opaque, void** planes)
{
try
{
*planes = g_spBuffer.get();
return *planes;
}
catch (const std::exception&)
{
}
return nullptr;
}
static void unlock(void* opaque, void* picture, void* const* planes)
{
Q_UNUSED(opaque);
Q_UNUSED(picture);
Q_UNUSED(planes);
}
static void display(void* opaque, void* picture)
{
QImage img((uchar*)picture, g_width, g_height, QImage::Format_ARGB32);
if (g_pDisplay)
{
g_pDisplay->SetImage(img);
}
}
void StartPlayer(void *context, QString strUrl)
{
int argc = sizeof(vlc_args) / sizeof(vlc_args[0]);
libvlc_instance_t* pInstance = libvlc_new(argc, vlc_args);
libvlc_media_t* pVlcMedia = libvlc_media_new_location(pInstance, qtu(strUrl));
g_pVlcMediaPlayer = libvlc_media_player_new_from_media(pVlcMedia);
libvlc_media_player_set_hwnd(g_pVlcMediaPlayer, context);
libvlc_media_release(pVlcMedia);
libvlc_media_player_play(g_pVlcMediaPlayer);
libvlc_state_t state = libvlc_media_player_get_state(g_pVlcMediaPlayer);
if (state == libvlc_NothingSpecial || state == libvlc_Opening)
{
state = libvlc_media_player_get_state(g_pVlcMediaPlayer);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
if (state == libvlc_Error)
{
// error
libvlc_media_player_stop(g_pVlcMediaPlayer);
return;
}
libvlc_media_track_t** tracks = nullptr;
bool bFindResolution = false;
while (!bFindResolution)
{
unsigned tracksCount = libvlc_media_tracks_get(pVlcMedia, &tracks);
if (tracks != nullptr)
{
for (unsigned i = 0; i < tracksCount; i++)
{
if (tracks[i]->i_type == libvlc_track_video
&& tracks[i]->video->i_height != 0
&& tracks[i]->video->i_width != 0)
{
g_width = tracks[i]->video->i_width;
g_height = tracks[i]->video->i_height;
bFindResolution = true;
break;
}
}
}
libvlc_media_tracks_release(tracks, tracksCount);
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
libvlc_media_player_stop(g_pVlcMediaPlayer);
if (bFindResolution)
{
libvlc_video_set_callbacks(g_pVlcMediaPlayer, lock, unlock, display, context);
libvlc_video_set_format(g_pVlcMediaPlayer, "RV32", g_width, g_height, g_width << 2);
g_spBuffer.reset(new uint8_t[(g_width * g_height) * 4], std::default_delete<uint8_t[]>());
std::this_thread::sleep_for(std::chrono::milliseconds(10));
libvlc_media_player_play(g_pVlcMediaPlayer);
}
}
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
QWidget w; // 避免屏幕出现闪动
w.setVisible(false);
CDisplay d;
d.resize(640, 480);
g_pDisplay = &d;
QString strUrl = "rtsp://wowzaec2demo.streamlock.net/vod/mp4";
std::thread th(std::bind(&StartPlayer, (void *)w.winId(), strUrl));
d.show();
int ret = a.exec();
//release
th.join();
if (g_pVlcMediaPlayer)
{
libvlc_media_player_stop(g_pVlcMediaPlayer);
libvlc_media_player_release(g_pVlcMediaPlayer);
}
return ret;
}
代码地址:gitee
参考链接https://github.com/rocking5566/RtspPlayerDemo
注意事项:
1、生成工程以及一些注意事项,见我的第一篇《制作一个播放器(一)》
2、main方法中,为什么使用QWidget w, w隐藏,这样可以避免屏幕闪(不使用CDisplay的原因也是基于此)。开始会使用句柄,是能获取对应流的宽高。
3、这期使用回调的方式,能够很方便的让我们处理图片。
4、新增了sqlite3,方便后期使用