FFmpeg:为什么录制的视频质量远低于 OpenGL 中呈现的质量

FFmpeg: Why the recorded video's quality is much lower than rendered in OpenGL

我尝试使用 FFmpeg 捕获由 OpenGL 渲染的帧。结果是一个用于回放的 .mp4 文件。它工作因为我得到了我期望的 .mp4,但是与 OpenGL 渲染的质量相比质量很低。谁能告诉我为什么?以及如何调整我的代码以使 mp4 的质量与 OpenGL 生成的原始帧相同?

我得到的结果:

图像 OpenGL 与 FFmpeg

这是我的简单代码:

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);

// settings
const unsigned int SCR_WIDTH = 500;
const unsigned int SCR_HEIGHT = 500;

// start ffmpeg telling it to expect raw rgba 720p-60hz frames
// -i - tells it to read frames from stdin
const char* cmd = "ffmpeg -f rawvideo -pix_fmt rgba -s 500x500 -i - "
"-threads 0 -preset fast -y -pix_fmt yuv420p -crf 21 -vf vflip output.mp4";

// open pipe to ffmpeg's stdin in binary write mode
FILE* ffmpeg = _popen(cmd, "wb");

int* buffer = new int[SCR_WIDTH*SCR_HEIGHT];

// shaders
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"layout (location = 1) in vec3 aColor;\n"
"layout (location = 2) in vec2 aTexCoord;\n"

"out vec3 ourColor;\n"
"out vec2 texCoord;\n"

"uniform mat4 transform;\n"

"void main()\n"
"{\n"
"   gl_Position = transform*vec4(aPos, 1.0);\n"
"   ourColor = aColor;\n"
"   texCoord = aTexCoord;\n"
"}[=10=]";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"

"in vec3 ourColor;\n"
"in vec2 texCoord;\n"

"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);\n"
"}\n[=10=]";

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // render preparation: data collection and passing
    // -----------------------------------------------

    // vertex shader: create and compile
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    // fragment shader: create and compile
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);


    // shader program
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    // first shape: square
    float vertices[] = {
        // positions          // colors           // texture coords
         0.25f,  0.25f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // top right
         0.25f, -0.25f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // bottom right
        -0.25f, -0.25f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // bottom left
        -0.25f,  0.25f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // top left 
    };

    int indices[] = {
        0, 1, 2,
        2, 3, 0
    };

    unsigned int VAO, VBO, EBO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), 0);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), 0);
    glEnableVertexAttribArray(1);

    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), 0);
    glEnableVertexAttribArray(2);

    // second shape: line(from the center of the screen to the center of the square
    float vertices2[] = {
        0.0f, 0.0f, 0.0f,
        0.5f, 0.5f, 0.0f
    };

    unsigned int VAO2, VBO2;
    glGenVertexArrays(1, &VAO2);
    glGenBuffers(1, &VBO2);

    glBindVertexArray(VAO2);
    glBindBuffer(GL_ARRAY_BUFFER, VBO2);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
    glEnableVertexAttribArray(0);



    // wireframe mode
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    int frameCounter = 0;
    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        if (frameCounter > 900) break;

        // input
        // -----
        processInput(window);

        // render
        // ------
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // be sure to activate the shader before any calls to glUniform
        glUseProgram(shaderProgram);

        /**********************draw rotating line*****************************/
        glm::mat4 transform = glm::mat4(1.0f);

        /// rotate( around the center of the screen )
        transform = glm::rotate(transform, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));
        unsigned int transLoc = glGetUniformLocation(shaderProgram, "transform");
        glUniformMatrix4fv(transLoc, 1, GL_FALSE, glm::value_ptr(transform));

        // draw
        glBindVertexArray(VAO2);
        glDrawArrays(GL_LINES, 0, 2);

        /**********************draw rotating square*****************************/
        transform = glm::mat4(1.0f);

        /// rotate( around the center of the screen )
        transform = glm::rotate(transform, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));
        //// translate
        transform = glm::translate(transform, glm::vec3(0.5f, 0.5f, 0.0f));
        //// rotate( self rotate)
        transform = glm::rotate(transform, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));

        transLoc = glGetUniformLocation(shaderProgram, "transform");
        glUniformMatrix4fv(transLoc, 1, GL_FALSE, glm::value_ptr(transform));

        // draw
        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        /**********************draw center squares*****************************/
        transform = glm::mat4(1.0f);
        float scaler = sin((float)glfwGetTime())*4;
        transform = glm::scale(transform, glm::vec3(scaler, scaler, scaler));

        transLoc = glGetUniformLocation(shaderProgram, "transform");
        glUniformMatrix4fv(transLoc, 1, GL_FALSE, glm::value_ptr(transform));

        // draw
        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        


        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);

        /****** ffmpeg *****/
        glReadPixels(0, 0, SCR_WIDTH, SCR_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

        fwrite(buffer, sizeof(int)*SCR_WIDTH*SCR_HEIGHT, 1, ffmpeg);
        frameCounter++;
        /****** end: ffmpeg *****/

        glfwPollEvents();
    }

    _pclose(ffmpeg);

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

切换到 libx264rgb 编码器 & crf 0 进行无损捕获:

const char* cmd = "ffmpeg -framerate 60 -f rawvideo -pix_fmt rgba -s 500x500 -i - "
"-c:v libx264rgb -threads 0 -preset fast -y -crf 0 -vf vflip output.mp4";

请注意,在将输出传递给其他比 ffmpeg 更不通用的软件之前,您需要重新编码输出,因为 RGB 不是一种非常常见的颜色-space 对于 H .264.

检查结果时请小心使用播放器软件,我的 Linux 系统上的 MPV --profile=gpu-hq 在线路周围引入了振铃伪像,而 VLC 则没有。