|
@@ -0,0 +1,609 @@
|
|
|
|
|
+#include "opengl_video_renderer.h"
|
|
|
|
|
+
|
|
|
|
|
+// #include "../base/logger.h"
|
|
|
|
|
+
|
|
|
|
|
+#include <QPainter>
|
|
|
|
|
+#include <QResizeEvent>
|
|
|
|
|
+#include <QApplication>
|
|
|
|
|
+#include <QDebug>
|
|
|
|
|
+#include <QOpenGLContext>
|
|
|
|
|
+#include <QSurfaceFormat>
|
|
|
|
|
+#include <algorithm>
|
|
|
|
|
+
|
|
|
|
|
+extern "C" {
|
|
|
|
|
+#include <libavutil/imgutils.h>
|
|
|
|
|
+#include <libswscale/swscale.h>
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+namespace av {
|
|
|
|
|
+namespace player {
|
|
|
|
|
+
|
|
|
|
|
+// 顶点着色器源码
|
|
|
|
|
+static const char* vertexShaderSource = R"(
|
|
|
|
|
+#version 330 core
|
|
|
|
|
+layout(location = 0) in vec2 position;
|
|
|
|
|
+layout(location = 1) in vec2 texCoord;
|
|
|
|
|
+
|
|
|
|
|
+out vec2 TexCoord;
|
|
|
|
|
+
|
|
|
|
|
+uniform mat4 projection;
|
|
|
|
|
+
|
|
|
|
|
+void main() {
|
|
|
|
|
+ gl_Position = projection * vec4(position, 0.0, 1.0);
|
|
|
|
|
+ TexCoord = texCoord;
|
|
|
|
|
+}
|
|
|
|
|
+)";
|
|
|
|
|
+
|
|
|
|
|
+// 片段着色器源码 (YUV420P)
|
|
|
|
|
+static const char* fragmentShaderSource = R"(
|
|
|
|
|
+#version 330 core
|
|
|
|
|
+in vec2 TexCoord;
|
|
|
|
|
+out vec4 FragColor;
|
|
|
|
|
+
|
|
|
|
|
+uniform sampler2D yTexture;
|
|
|
|
|
+uniform sampler2D uTexture;
|
|
|
|
|
+uniform sampler2D vTexture;
|
|
|
|
|
+
|
|
|
|
|
+void main() {
|
|
|
|
|
+ float y = texture(yTexture, TexCoord).r;
|
|
|
|
|
+ float u = texture(uTexture, TexCoord).r - 0.5;
|
|
|
|
|
+ float v = texture(vTexture, TexCoord).r - 0.5;
|
|
|
|
|
+
|
|
|
|
|
+ // YUV to RGB conversion
|
|
|
|
|
+ float r = y + 1.402 * v;
|
|
|
|
|
+ float g = y - 0.344 * u - 0.714 * v;
|
|
|
|
|
+ float b = y + 1.772 * u;
|
|
|
|
|
+
|
|
|
|
|
+ FragColor = vec4(r, g, b, 1.0);
|
|
|
|
|
+}
|
|
|
|
|
+)";
|
|
|
|
|
+
|
|
|
|
|
+OpenGLVideoRenderer::OpenGLVideoRenderer(QWidget* parent)
|
|
|
|
|
+ : QOpenGLWidget(parent)
|
|
|
|
|
+ , m_videoWidth(0)
|
|
|
|
|
+ , m_videoHeight(0)
|
|
|
|
|
+ , m_inputFormat(AV_PIX_FMT_NONE)
|
|
|
|
|
+ , m_swsContext(nullptr)
|
|
|
|
|
+ , m_yuvBuffer(nullptr)
|
|
|
|
|
+ , m_yuvBufferSize(0)
|
|
|
|
|
+ , m_backgroundColor(Qt::black)
|
|
|
|
|
+ , m_keepAspectRatio(true)
|
|
|
|
|
+ , m_renderQuality(1.0f)
|
|
|
|
|
+ , m_vSyncEnabled(true)
|
|
|
|
|
+ , m_initialized(false)
|
|
|
|
|
+ , m_glInitialized(false)
|
|
|
|
|
+ , m_hasFrame(false)
|
|
|
|
|
+ , m_updateTimer(new QTimer(this))
|
|
|
|
|
+{
|
|
|
|
|
+ // 设置OpenGL格式
|
|
|
|
|
+ QSurfaceFormat format;
|
|
|
|
|
+ format.setVersion(3, 3);
|
|
|
|
|
+ format.setProfile(QSurfaceFormat::CoreProfile);
|
|
|
|
|
+ format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
|
|
|
|
|
+ format.setSwapInterval(m_vSyncEnabled ? 1 : 0);
|
|
|
|
|
+ setFormat(format);
|
|
|
|
|
+
|
|
|
|
|
+ // 设置基本属性
|
|
|
|
|
+ setMinimumSize(320, 240);
|
|
|
|
|
+ setFocusPolicy(Qt::StrongFocus);
|
|
|
|
|
+
|
|
|
|
|
+ // 连接更新定时器
|
|
|
|
|
+ connect(m_updateTimer, &QTimer::timeout, this, &OpenGLVideoRenderer::updateDisplay);
|
|
|
|
|
+ m_updateTimer->setSingleShot(true);
|
|
|
|
|
+
|
|
|
|
|
+ // av::Logger::instance().info("OpenGLVideoRenderer created");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+OpenGLVideoRenderer::~OpenGLVideoRenderer()
|
|
|
|
|
+{
|
|
|
|
|
+ cleanupOpenGLResources();
|
|
|
|
|
+ if (m_swsContext) {
|
|
|
|
|
+ sws_freeContext(m_swsContext);
|
|
|
|
|
+ m_swsContext = nullptr;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (m_yuvBuffer) {
|
|
|
|
|
+ av_free(m_yuvBuffer);
|
|
|
|
|
+ m_yuvBuffer = nullptr;
|
|
|
|
|
+ }
|
|
|
|
|
+ // av::Logger::instance().info("OpenGLVideoRenderer destroyed");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::initializeGL()
|
|
|
|
|
+{
|
|
|
|
|
+ // av::Logger::instance().info("Initializing OpenGL context");
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化OpenGL函数
|
|
|
|
|
+ initializeOpenGLFunctions();
|
|
|
|
|
+
|
|
|
|
|
+ // 设置清除颜色
|
|
|
|
|
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
|
+
|
|
|
|
|
+ // 启用混合
|
|
|
|
|
+ glEnable(GL_BLEND);
|
|
|
|
|
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化OpenGL资源
|
|
|
|
|
+ if (initializeOpenGLResources()) {
|
|
|
|
|
+ m_glInitialized = true;
|
|
|
|
|
+ // av::Logger::instance().info("OpenGL resources initialized successfully");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // av::Logger::instance().error("Failed to initialize OpenGL resources");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::paintGL()
|
|
|
|
|
+{
|
|
|
|
|
+ if (!m_glInitialized || !m_hasFrame) {
|
|
|
|
|
+ // 绘制背景
|
|
|
|
|
+ glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ QMutexLocker locker(&m_mutex);
|
|
|
|
|
+
|
|
|
|
|
+ // 清除缓冲区
|
|
|
|
|
+ glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
|
+
|
|
|
|
|
+ // 渲染当前帧
|
|
|
|
|
+ renderCurrentFrame();
|
|
|
|
|
+
|
|
|
|
|
+ // 检查OpenGL错误
|
|
|
|
|
+ checkGLError("paintGL");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::resizeGL(int width, int height)
|
|
|
|
|
+{
|
|
|
|
|
+ if (!m_glInitialized) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // av::Logger::instance().debugf("Resizing OpenGL viewport to %dx%d", width, height);
|
|
|
|
|
+
|
|
|
|
|
+ // 设置视口
|
|
|
|
|
+ glViewport(0, 0, width, height);
|
|
|
|
|
+
|
|
|
|
|
+ // 更新投影矩阵
|
|
|
|
|
+ setupProjectionMatrix();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::resizeEvent(QResizeEvent* event)
|
|
|
|
|
+{
|
|
|
|
|
+ QOpenGLWidget::resizeEvent(event);
|
|
|
|
|
+
|
|
|
|
|
+ // 触发重绘
|
|
|
|
|
+ if (m_glInitialized) {
|
|
|
|
|
+ update();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool OpenGLVideoRenderer::initialize(int width, int height, AVPixelFormat pixelFormat, double fps)
|
|
|
|
|
+{
|
|
|
|
|
+ QMutexLocker locker(&m_mutex);
|
|
|
|
|
+
|
|
|
|
|
+ if (m_initialized) {
|
|
|
|
|
+ // av::Logger::instance().warning("OpenGL video renderer already initialized");
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (width <= 0 || height <= 0) {
|
|
|
|
|
+ // av::Logger::instance().error("Invalid video dimensions");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (fps <= 0) {
|
|
|
|
|
+ fps = 25.0; // 默认帧率
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ m_videoWidth = width;
|
|
|
|
|
+ m_videoHeight = height;
|
|
|
|
|
+ m_inputFormat = pixelFormat;
|
|
|
|
|
+ m_fps = fps;
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化图像转换器
|
|
|
|
|
+ if (m_inputFormat != AV_PIX_FMT_YUV420P) {
|
|
|
|
|
+ m_swsContext = sws_getContext(
|
|
|
|
|
+ m_videoWidth, m_videoHeight, m_inputFormat,
|
|
|
|
|
+ m_videoWidth, m_videoHeight, AV_PIX_FMT_YUV420P,
|
|
|
|
|
+ SWS_BILINEAR, nullptr, nullptr, nullptr
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (!m_swsContext) {
|
|
|
|
|
+ // av::Logger::instance().error("Failed to create SwsContext");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 分配YUV缓冲区
|
|
|
|
|
+ m_yuvBufferSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, m_videoWidth, m_videoHeight, 1);
|
|
|
|
|
+ m_yuvBuffer = static_cast<uint8_t*>(av_malloc(m_yuvBufferSize));
|
|
|
|
|
+ if (!m_yuvBuffer) {
|
|
|
|
|
+ // av::Logger::instance().error("Failed to allocate YUV buffer");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置控件大小提示
|
|
|
|
|
+ setMinimumSize(m_videoWidth / 4, m_videoHeight / 4);
|
|
|
|
|
+
|
|
|
|
|
+ m_initialized = true;
|
|
|
|
|
+ // av::Logger::instance().info(
|
|
|
|
|
+ // QString("OpenGL video renderer initialized: %1x%2 @ %3fps").arg(width).arg(height).arg(fps).toStdString());
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool OpenGLVideoRenderer::renderFrame(const AVFramePtr& frame)
|
|
|
|
|
+{
|
|
|
|
|
+ qDebug() << "------->>>>>>>>>>>>>>>>>>-" << frame.get() << m_initialized.load();
|
|
|
|
|
+ if (!frame || !m_initialized) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ QMutexLocker locker(&m_mutex);
|
|
|
|
|
+
|
|
|
|
|
+ // 更新纹理数据
|
|
|
|
|
+ updateTextures(frame);
|
|
|
|
|
+
|
|
|
|
|
+ // 标记有帧数据
|
|
|
|
|
+ m_hasFrame = true;
|
|
|
|
|
+
|
|
|
|
|
+ // 触发更新显示
|
|
|
|
|
+ if (!m_updateTimer->isActive()) {
|
|
|
|
|
+ int interval = static_cast<int>(1000.0 / m_fps); // 根据帧率计算间隔
|
|
|
|
|
+ m_updateTimer->start(interval);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::clear()
|
|
|
|
|
+{
|
|
|
|
|
+ QMutexLocker locker(&m_mutex);
|
|
|
|
|
+ m_hasFrame = false;
|
|
|
|
|
+ update();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::setKeepAspectRatio(bool keepAspectRatio)
|
|
|
|
|
+{
|
|
|
|
|
+ if (m_keepAspectRatio != keepAspectRatio) {
|
|
|
|
|
+ m_keepAspectRatio = keepAspectRatio;
|
|
|
|
|
+ if (m_glInitialized) {
|
|
|
|
|
+ setupProjectionMatrix();
|
|
|
|
|
+ update();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool OpenGLVideoRenderer::getKeepAspectRatio() const
|
|
|
|
|
+{
|
|
|
|
|
+ return m_keepAspectRatio;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::setBackgroundColor(const QColor& color)
|
|
|
|
|
+{
|
|
|
|
|
+ if (m_backgroundColor != color) {
|
|
|
|
|
+ m_backgroundColor = color;
|
|
|
|
|
+ if (m_glInitialized) {
|
|
|
|
|
+ glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
|
|
|
|
|
+ update();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+QSize OpenGLVideoRenderer::getVideoSize() const
|
|
|
|
|
+{
|
|
|
|
|
+ return QSize(m_videoWidth, m_videoHeight);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+QSize OpenGLVideoRenderer::getDisplaySize() const
|
|
|
|
|
+{
|
|
|
|
|
+ return size();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool OpenGLVideoRenderer::isInitialized() const
|
|
|
|
|
+{
|
|
|
|
|
+ return m_initialized && m_glInitialized;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::setRenderQuality(float quality)
|
|
|
|
|
+{
|
|
|
|
|
+ m_renderQuality = std::clamp(quality, 0.0f, 1.0f);
|
|
|
|
|
+ if (m_glInitialized) {
|
|
|
|
|
+ update();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::setVSync(bool enable)
|
|
|
|
|
+{
|
|
|
|
|
+ m_vSyncEnabled = enable;
|
|
|
|
|
+ if (m_glInitialized) {
|
|
|
|
|
+ QSurfaceFormat format = context()->format();
|
|
|
|
|
+ format.setSwapInterval(enable ? 1 : 0);
|
|
|
|
|
+ context()->setFormat(format);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::updateDisplay()
|
|
|
|
|
+{
|
|
|
|
|
+ if (m_glInitialized) {
|
|
|
|
|
+ update();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool OpenGLVideoRenderer::initializeOpenGLResources()
|
|
|
|
|
+{
|
|
|
|
|
+ // 初始化着色器程序
|
|
|
|
|
+ if (!initializeShaders()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化顶点数据
|
|
|
|
|
+ if (!initializeVertexData()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 创建纹理
|
|
|
|
|
+ if (!createTextures()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::cleanupOpenGLResources()
|
|
|
|
|
+{
|
|
|
|
|
+ m_shaderProgram.reset();
|
|
|
|
|
+ m_vertexBuffer.reset();
|
|
|
|
|
+ m_indexBuffer.reset();
|
|
|
|
|
+ m_vao.reset();
|
|
|
|
|
+ m_yTexture.reset();
|
|
|
|
|
+ m_uTexture.reset();
|
|
|
|
|
+ m_vTexture.reset();
|
|
|
|
|
+
|
|
|
|
|
+ m_glInitialized = false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool OpenGLVideoRenderer::initializeShaders()
|
|
|
|
|
+{
|
|
|
|
|
+ m_shaderProgram = std::make_unique<QOpenGLShaderProgram>();
|
|
|
|
|
+
|
|
|
|
|
+ // 添加顶点着色器
|
|
|
|
|
+ if (!m_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource)) {
|
|
|
|
|
+ // av::Logger::instance().error("Failed to compile vertex shader: " + m_shaderProgram->log().toStdString());
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 添加片段着色器
|
|
|
|
|
+ if (!m_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource)) {
|
|
|
|
|
+ // av::Logger::instance().error("Failed to compile fragment shader: " + m_shaderProgram->log().toStdString());
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 链接着色器程序
|
|
|
|
|
+ if (!m_shaderProgram->link()) {
|
|
|
|
|
+ // av::Logger::instance().error("Failed to link shader program: " + m_shaderProgram->log().toStdString());
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // av::Logger::instance().info("Shader program initialized successfully");
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool OpenGLVideoRenderer::initializeVertexData()
|
|
|
|
|
+{
|
|
|
|
|
+ // 顶点数据 (位置 + 纹理坐标)
|
|
|
|
|
+ static const float vertices[] = {
|
|
|
|
|
+ // 位置 // 纹理坐标
|
|
|
|
|
+ -1.0f, -1.0f, 0.0f, 1.0f, // 左下
|
|
|
|
|
+ 1.0f, -1.0f, 1.0f, 1.0f, // 右下
|
|
|
|
|
+ 1.0f, 1.0f, 1.0f, 0.0f, // 右上
|
|
|
|
|
+ -1.0f, 1.0f, 0.0f, 0.0f // 左上
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 索引数据
|
|
|
|
|
+ static const unsigned int indices[] = {
|
|
|
|
|
+ 0, 1, 2, // 第一个三角形
|
|
|
|
|
+ 2, 3, 0 // 第二个三角形
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 创建VAO
|
|
|
|
|
+ m_vao = std::make_unique<QOpenGLVertexArrayObject>();
|
|
|
|
|
+ m_vao->create();
|
|
|
|
|
+ m_vao->bind();
|
|
|
|
|
+
|
|
|
|
|
+ // 创建顶点缓冲区
|
|
|
|
|
+ m_vertexBuffer = std::make_unique<QOpenGLBuffer>(QOpenGLBuffer::VertexBuffer);
|
|
|
|
|
+ m_vertexBuffer->create();
|
|
|
|
|
+ m_vertexBuffer->bind();
|
|
|
|
|
+ m_vertexBuffer->allocate(vertices, sizeof(vertices));
|
|
|
|
|
+
|
|
|
|
|
+ // 设置顶点属性
|
|
|
|
|
+ m_shaderProgram->enableAttributeArray(0);
|
|
|
|
|
+ m_shaderProgram->setAttributeBuffer(0, GL_FLOAT, 0, 2, 4 * sizeof(float));
|
|
|
|
|
+
|
|
|
|
|
+ m_shaderProgram->enableAttributeArray(1);
|
|
|
|
|
+ m_shaderProgram->setAttributeBuffer(1, GL_FLOAT, 2 * sizeof(float), 2, 4 * sizeof(float));
|
|
|
|
|
+
|
|
|
|
|
+ // 创建索引缓冲区
|
|
|
|
|
+ m_indexBuffer = std::make_unique<QOpenGLBuffer>(QOpenGLBuffer::IndexBuffer);
|
|
|
|
|
+ m_indexBuffer->create();
|
|
|
|
|
+ m_indexBuffer->bind();
|
|
|
|
|
+ m_indexBuffer->allocate(indices, sizeof(indices));
|
|
|
|
|
+
|
|
|
|
|
+ m_vao->release();
|
|
|
|
|
+
|
|
|
|
|
+ // av::Logger::instance().info("Vertex data initialized successfully");
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool OpenGLVideoRenderer::createTextures()
|
|
|
|
|
+{
|
|
|
|
|
+ // 创建Y纹理
|
|
|
|
|
+ m_yTexture = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target2D);
|
|
|
|
|
+ m_yTexture->create();
|
|
|
|
|
+ m_yTexture->bind();
|
|
|
|
|
+ m_yTexture->setFormat(QOpenGLTexture::R8_UNorm);
|
|
|
|
|
+ m_yTexture->setSize(m_videoWidth, m_videoHeight);
|
|
|
|
|
+ m_yTexture->allocateStorage();
|
|
|
|
|
+ m_yTexture->setMinificationFilter(QOpenGLTexture::Linear);
|
|
|
|
|
+ m_yTexture->setMagnificationFilter(QOpenGLTexture::Linear);
|
|
|
|
|
+ m_yTexture->setWrapMode(QOpenGLTexture::ClampToEdge);
|
|
|
|
|
+
|
|
|
|
|
+ // 创建U纹理
|
|
|
|
|
+ m_uTexture = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target2D);
|
|
|
|
|
+ m_uTexture->create();
|
|
|
|
|
+ m_uTexture->bind();
|
|
|
|
|
+ m_uTexture->setFormat(QOpenGLTexture::R8_UNorm);
|
|
|
|
|
+ m_uTexture->setSize(m_videoWidth / 2, m_videoHeight / 2);
|
|
|
|
|
+ m_uTexture->allocateStorage();
|
|
|
|
|
+ m_uTexture->setMinificationFilter(QOpenGLTexture::Linear);
|
|
|
|
|
+ m_uTexture->setMagnificationFilter(QOpenGLTexture::Linear);
|
|
|
|
|
+ m_uTexture->setWrapMode(QOpenGLTexture::ClampToEdge);
|
|
|
|
|
+
|
|
|
|
|
+ // 创建V纹理
|
|
|
|
|
+ m_vTexture = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target2D);
|
|
|
|
|
+ m_vTexture->create();
|
|
|
|
|
+ m_vTexture->bind();
|
|
|
|
|
+ m_vTexture->setFormat(QOpenGLTexture::R8_UNorm);
|
|
|
|
|
+ m_vTexture->setSize(m_videoWidth / 2, m_videoHeight / 2);
|
|
|
|
|
+ m_vTexture->allocateStorage();
|
|
|
|
|
+ m_vTexture->setMinificationFilter(QOpenGLTexture::Linear);
|
|
|
|
|
+ m_vTexture->setMagnificationFilter(QOpenGLTexture::Linear);
|
|
|
|
|
+ m_vTexture->setWrapMode(QOpenGLTexture::ClampToEdge);
|
|
|
|
|
+
|
|
|
|
|
+ // av::Logger::instance().info("Textures created successfully");
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::updateTextures(const AVFramePtr& frame)
|
|
|
|
|
+{
|
|
|
|
|
+ if (!m_glInitialized || !frame) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 转换帧格式(如果需要)
|
|
|
|
|
+ AVFrame* yuvFrame = frame.get();
|
|
|
|
|
+ if (m_inputFormat != AV_PIX_FMT_YUV420P && m_swsContext) {
|
|
|
|
|
+ // 创建临时帧
|
|
|
|
|
+ AVFrame* tempFrame = av_frame_alloc();
|
|
|
|
|
+ tempFrame->format = AV_PIX_FMT_YUV420P;
|
|
|
|
|
+ tempFrame->width = m_videoWidth;
|
|
|
|
|
+ tempFrame->height = m_videoHeight;
|
|
|
|
|
+ av_image_fill_arrays(tempFrame->data, tempFrame->linesize, m_yuvBuffer,
|
|
|
|
|
+ AV_PIX_FMT_YUV420P, m_videoWidth, m_videoHeight, 1);
|
|
|
|
|
+
|
|
|
|
|
+ // 转换格式
|
|
|
|
|
+ sws_scale(m_swsContext, frame->data, frame->linesize, 0, m_videoHeight,
|
|
|
|
|
+ tempFrame->data, tempFrame->linesize);
|
|
|
|
|
+
|
|
|
|
|
+ yuvFrame = tempFrame;
|
|
|
|
|
+
|
|
|
|
|
+ // 更新纹理数据
|
|
|
|
|
+ m_yTexture->bind();
|
|
|
|
|
+ m_yTexture->setData(QOpenGLTexture::Red, QOpenGLTexture::UInt8, yuvFrame->data[0], nullptr);
|
|
|
|
|
+
|
|
|
|
|
+ m_uTexture->bind();
|
|
|
|
|
+ m_uTexture->setData(QOpenGLTexture::Red, QOpenGLTexture::UInt8, yuvFrame->data[1], nullptr);
|
|
|
|
|
+
|
|
|
|
|
+ m_vTexture->bind();
|
|
|
|
|
+ m_vTexture->setData(QOpenGLTexture::Red, QOpenGLTexture::UInt8, yuvFrame->data[2], nullptr);
|
|
|
|
|
+
|
|
|
|
|
+ av_frame_free(&tempFrame);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 直接更新纹理数据
|
|
|
|
|
+ m_yTexture->bind();
|
|
|
|
|
+ m_yTexture->setData(QOpenGLTexture::Red, QOpenGLTexture::UInt8, frame->data[0], nullptr);
|
|
|
|
|
+
|
|
|
|
|
+ m_uTexture->bind();
|
|
|
|
|
+ m_uTexture->setData(QOpenGLTexture::Red, QOpenGLTexture::UInt8, frame->data[1], nullptr);
|
|
|
|
|
+
|
|
|
|
|
+ m_vTexture->bind();
|
|
|
|
|
+ m_vTexture->setData(QOpenGLTexture::Red, QOpenGLTexture::UInt8, frame->data[2], nullptr);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 强制更新显示
|
|
|
|
|
+ update();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+QRectF OpenGLVideoRenderer::calculateDisplayRect() const
|
|
|
|
|
+{
|
|
|
|
|
+ QSize widgetSize = size();
|
|
|
|
|
+ QSize videoSize(m_videoWidth, m_videoHeight);
|
|
|
|
|
+
|
|
|
|
|
+ if (!m_keepAspectRatio) {
|
|
|
|
|
+ return QRectF(-1.0f, -1.0f, 2.0f, 2.0f);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算保持宽高比的显示矩形
|
|
|
|
|
+ float widgetAspect = static_cast<float>(widgetSize.width()) / widgetSize.height();
|
|
|
|
|
+ float videoAspect = static_cast<float>(videoSize.width()) / videoSize.height();
|
|
|
|
|
+
|
|
|
|
|
+ QRectF rect;
|
|
|
|
|
+ if (widgetAspect > videoAspect) {
|
|
|
|
|
+ // 控件更宽,以高度为准
|
|
|
|
|
+ float width = 2.0f * videoAspect / widgetAspect;
|
|
|
|
|
+ rect = QRectF(-width / 2.0f, -1.0f, width, 2.0f);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 控件更高,以宽度为准
|
|
|
|
|
+ float height = 2.0f * widgetAspect / videoAspect;
|
|
|
|
|
+ rect = QRectF(-1.0f, -height / 2.0f, 2.0f, height);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return rect;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::setupProjectionMatrix()
|
|
|
|
|
+{
|
|
|
|
|
+ if (!m_shaderProgram) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ QRectF displayRect = calculateDisplayRect();
|
|
|
|
|
+
|
|
|
|
|
+ // 创建投影矩阵
|
|
|
|
|
+ QMatrix4x4 projection;
|
|
|
|
|
+ projection.setToIdentity();
|
|
|
|
|
+ projection.ortho(displayRect.left(), displayRect.right(),
|
|
|
|
|
+ displayRect.bottom(), displayRect.top(), -1.0f, 1.0f);
|
|
|
|
|
+
|
|
|
|
|
+ m_shaderProgram->bind();
|
|
|
|
|
+ m_shaderProgram->setUniformValue("projection", projection);
|
|
|
|
|
+ m_shaderProgram->release();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void OpenGLVideoRenderer::renderCurrentFrame()
|
|
|
|
|
+{
|
|
|
|
|
+ if (!m_shaderProgram || !m_vao) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ m_shaderProgram->bind();
|
|
|
|
|
+ m_vao->bind();
|
|
|
|
|
+
|
|
|
|
|
+ // 绑定纹理
|
|
|
|
|
+ m_yTexture->bind(0);
|
|
|
|
|
+ m_uTexture->bind(1);
|
|
|
|
|
+ m_vTexture->bind(2);
|
|
|
|
|
+
|
|
|
|
|
+ m_shaderProgram->setUniformValue("yTexture", 0);
|
|
|
|
|
+ m_shaderProgram->setUniformValue("uTexture", 1);
|
|
|
|
|
+ m_shaderProgram->setUniformValue("vTexture", 2);
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制
|
|
|
|
|
+ glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
|
|
|
|
|
+
|
|
|
|
|
+ m_vao->release();
|
|
|
|
|
+ m_shaderProgram->release();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool OpenGLVideoRenderer::checkGLError(const QString& operation)
|
|
|
|
|
+{
|
|
|
|
|
+ GLenum error = glGetError();
|
|
|
|
|
+ if (error != GL_NO_ERROR) {
|
|
|
|
|
+ // av::Logger::instance().error("OpenGL error in " + operation.toStdString() + ": " + std::to_string(error));
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+} // namespace player
|
|
|
|
|
+} // namespace av
|