class VideoRenderer : public QObject, public QQuickFramebufferObject::Renderer, public QOpenGLFunctions
{
Q_OBJECT
public:
explicit VideoRenderer(QQuickFramebufferObject* object);
~VideoRenderer();
QOpenGLFramebufferObject* createFramebufferObject(const QSize& size);
void render();
public slots:
void updateVideoFrame(AVFrame* frame);
void paint();
private:
std::mutex mux;
QGLShaderProgram program;
GLuint yuv[3] = { 0 };
GLuint textures[3] = { 0 };
unsigned char* datas[3] = { 0 };
QSize videoSize;
QQuickFramebufferObject* fbo;
};
VideoRenderer::VideoRenderer(QQuickFramebufferObject* object):
QObject(object)
{
fbo = object;
initializeOpenGLFunctions();
qDebug() << program.addShaderFromSourceCode(QGLShader::Fragment, tString);
qDebug() << program.addShaderFromSourceCode(QGLShader::Vertex, vString);
program.bindAttributeLocation("vertexIn", A_VER);
program.bindAttributeLocation("textureIn", T_VER);
qDebug() << "program.link() = " << program.link();
qDebug() << "program.bind() = " << program.bind();
static const GLfloat ver[] = {
-1.0f,-1.0f,
1.0f,-1.0f,
-1.0f, 1.0f,
1.0f,1.0f
};
static const GLfloat tex[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f
};
glVertexAttribPointer(A_VER, 2, GL_FLOAT, 0, 0, ver);
glEnableVertexAttribArray(A_VER);
glVertexAttribPointer(T_VER, 2, GL_FLOAT, 0, 0, tex);
glEnableVertexAttribArray(T_VER);
yuv[0] = program.uniformLocation("tex_y");
yuv[1] = program.uniformLocation("tex_u");
yuv[2] = program.uniformLocation("tex_v");
mux.unlock();
glGenTextures(3, textures);
}
VideoRenderer::~VideoRenderer()
{
if(datas[0])
{
for(int i = 0; i < 3; i++)
delete datas[i];
}
}
void SaveAvFrame(const QString& file,AVFrame* avFrame)
{
FILE *fDump = fopen(file.toLocal8Bit().data(), "ab");
uint32_t pitchY = avFrame->linesize[0];
uint32_t pitchU = avFrame->linesize[1];
uint32_t pitchV = avFrame->linesize[2];
uint8_t *avY = avFrame->data[0];
uint8_t *avU = avFrame->data[1];
uint8_t *avV = avFrame->data[2];
for (uint32_t i = 0; i < avFrame->height; i++) {
fwrite(avY, avFrame->width, 1, fDump);
avY += pitchY;
}
for (uint32_t i = 0; i < avFrame->height / 2; i++) {
fwrite(avU, avFrame->width / 2, 1, fDump);
avU += pitchU;
}
for (uint32_t i = 0; i < avFrame->height / 2; i++) {
fwrite(avV, avFrame->width / 2, 1, fDump);
avV += pitchV;
}
fclose(fDump);
}
void VideoRenderer::updateVideoFrame(AVFrame* frame)
{
if(!frame)
return;
mux.lock();
if (frame->width != videoSize.width() || frame->height != videoSize.height())
{
videoSize.setWidth(frame->width);
videoSize.setHeight(frame->height);
if (datas[0])
{
for (int i = 0; i < 3; i++)
{
delete datas[i];
datas[i] = 0;
}
}
datas[0] = new unsigned char[frame->width*frame->height]; //Y
datas[1] = new unsigned char[frame->width*frame->height / 4]; //U
datas[2] = new unsigned char[frame->width*frame->height / 4]; //V
}
if (!datas[0])
{
datas[0] = new unsigned char[frame->width*frame->height]; //Y
datas[1] = new unsigned char[frame->width*frame->height / 4]; //U
datas[2] = new unsigned char[frame->width*frame->height / 4]; //V
}
if (videoSize.width() == frame->linesize[0])
{
memcpy(datas[0], frame->data[0], videoSize.width()*videoSize.height());
memcpy(datas[1], frame->data[1], videoSize.width()*videoSize.height() / 4);
memcpy(datas[2], frame->data[2], videoSize.width()*videoSize.height() / 4);
}
else
{
for (int i = 0; i < videoSize.height(); i++) //Y
memcpy(datas[0] + videoSize.width()*i, frame->data[0] + frame->linesize[0] * i, videoSize.width());
for (int i = 0; i < videoSize.height() / 2; i++) //U
memcpy(datas[1] + videoSize.width() / 2 * i, frame->data[1] + frame->linesize[1] * i, videoSize.width());
for (int i = 0; i < videoSize.height() / 2; i++) //V
memcpy(datas[2] + videoSize.width() / 2 * i, frame->data[2] + frame->linesize[2] * i, videoSize.width());
}
mux.unlock();
av_frame_free(&frame);
update();
}
void VideoRenderer::render()
{
paint();
}
void VideoRenderer::paint()
{
if (videoSize.isEmpty())
return;
mux.lock();
//Y
glBindTexture(GL_TEXTURE_2D, textures[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, videoSize.width(), videoSize.height(), 0, GL_RED, GL_UNSIGNED_BYTE, 0);
//U
glBindTexture(GL_TEXTURE_2D, textures[1]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, videoSize.width() / 2, videoSize.height() / 2, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
//V
glBindTexture(GL_TEXTURE_2D, textures[2]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, videoSize.width() / 2, videoSize.height() / 2, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textures[0]); // to y
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoSize.width(), videoSize.height(), GL_RED, GL_UNSIGNED_BYTE, datas[0]);
glUniform1i(yuv[0], 0);
glActiveTexture(GL_TEXTURE0 + 1);
glBindTexture(GL_TEXTURE_2D, textures[1]); // 1 to u
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoSize.width() / 2, videoSize.height() / 2, GL_RED, GL_UNSIGNED_BYTE, datas[1]);
glUniform1i(yuv[1], 1);
glActiveTexture(GL_TEXTURE0 + 2);
glBindTexture(GL_TEXTURE_2D, textures[2]); // 2 to v
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoSize.width() / 2, videoSize.height() / 2, GL_RED, GL_UNSIGNED_BYTE, datas[2]);
glUniform1i(yuv[2], 2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
mux.unlock();
}
QOpenGLFramebufferObject* VideoRenderer::createFramebufferObject(const QSize &size)
{
QOpenGLFramebufferObjectFormat format; //当大小发生变化时,会调用此函数生成对应大小的FBO
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
format.setSamples(4);
return new QOpenGLFramebufferObject(size,format);
}