【问题标题】:Creating a usable H.264 video file创建可用的 H.264 视频文件
【发布时间】:2019-09-22 09:57:41
【问题描述】:

我正在尝试使用 libavcodec 从各个帧生成 mp4 视频文件。每个输入帧都是一个 qt QImage,输出文件是使用 Qt QFile 类写入的。

我通过 VideoTarget 类完成了这项工作,该类在初始化时打开给定的“目标”文件,在调用 addFrame(image) 时记录帧,然后在调用其析构函数时保存/关闭文件。

该类具有以下字段:

AVCodec* m_codec = nullptr;
AVCodecContext *m_context = nullptr;
AVPacket* m_packet = nullptr;
AVFrame* m_frame = nullptr;

QFile m_target;

看起来像这样:

VideoTarget::VideoTarget(QString target, QObject *parent) : QObject(parent), m_target(target)
{
    // Find video codec
    m_codec = avcodec_find_encoder_by_name("libx264rgb");
    if (!m_codec) throw std::runtime_error("Unable to find codec.");

    // Make codec context
    m_context = avcodec_alloc_context3(m_codec);
    if (!m_context) throw std::runtime_error("Unable to allocate codec context.");

    // Make codec packet
    m_packet = av_packet_alloc();
    if (!m_packet) throw std::runtime_error("Unable to allocate packet.");

    // Configure context
    m_context->bit_rate = 400000;
    m_context->width = 1280;
    m_context->height = 720;
    m_context->time_base = (AVRational){1, 60};
    m_context->framerate = (AVRational){60, 1};
    m_context->gop_size = 10;
    m_context->max_b_frames = 1;
    m_context->pix_fmt = AV_PIX_FMT_RGB24;

    if (m_codec->id == AV_CODEC_ID_H264)
        av_opt_set(m_context->priv_data, "preset", "slow", 0);

    // Open Codec
    int ret = avcodec_open2(m_context, m_codec, nullptr);
    if (ret < 0) {
        throw std::runtime_error("Unable to open codec.");
    }

    // Open file
    if (!m_target.open(QIODevice::WriteOnly))
        throw std::runtime_error("Unable to open target file.");

    // Allocate frame
    m_frame = av_frame_alloc();
    if (!m_frame) throw std::runtime_error("Unable to allocate frame.");

    m_frame->format = m_context->pix_fmt;
    m_frame->width = m_context->width;
    m_frame->height = m_context->height;
    m_frame->pts = 0;

    ret = av_frame_get_buffer(m_frame, 24);
    if (ret < 0) throw std::runtime_error("Unable to allocate frame buffer.");
}

void VideoTarget::addFrame(QImage &image)
{
    // Ensure frame data is writable
    int ret = av_frame_make_writable(m_frame);
    if (ret < 0) throw std::runtime_error("Unable to make frame writable.");

    // Prepare image
    for (int y = 0; y < m_context->height; y++) {
        for (int x = 0; x < m_context->width; x++) {
            auto pixel = image.pixelColor(x, y);
            int pos = (y * 1024 + x) * 3;
            m_frame->data[0][pos] = pixel.red();
            m_frame->data[0][pos + 1] = pixel.green();
            m_frame->data[0][pos + 2] = pixel.blue();
        }
    }

    m_frame->pts++;

    // Send the frame
    ret = avcodec_send_frame(m_context, m_frame);
    if (ret < 0) throw std::runtime_error("Unable to send AV frame.");

    while (ret >= 0) {
        ret = avcodec_receive_packet(m_context, m_packet);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) throw std::runtime_error("Error during encoding.");

        m_target.write((const char*)m_packet->data, m_packet->size);
        av_packet_unref(m_packet);
    }
}

VideoTarget::~VideoTarget()
{
    int ret = avcodec_send_frame(m_context, nullptr);
    if (ret < 0) throw std::runtime_error("Unable to send AV null frame.");

    while (ret >= 0) {
        ret = avcodec_receive_packet(m_context, m_packet);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) throw std::runtime_error("Error during encoding.");

        m_target.write((const char*)m_packet->data, m_packet->size);
        av_packet_unref(m_packet);
    }

    // Magic number at the end of the file
    uint8_t endcode[] = { 0, 0, 1, 0xb7 };
    m_target.write((const char*)endcode, sizeof(endcode));
    m_target.close();

    // Free codec stuff
    avcodec_free_context(&m_context);
    av_frame_free(&m_frame);
    av_packet_free(&m_packet);
}

使用时,类似乎可以工作,并且数据被写入文件,但我无法在任何应用程序中播放生成的文件。

我的主要怀疑是以下几行:

    // Prepare image
    for (int y = 0; y < m_context->height; y++) {
        for (int x = 0; x < m_context->width; x++) {
            auto pixel = image.pixelColor(x, y);
            int pos = (y * 1024 + x) * 3;
            m_frame->data[0][pos] = pixel.red();
            m_frame->data[0][pos + 1] = pixel.green();
            m_frame->data[0][pos + 2] = pixel.blue();
        }
    }

libavcodec 文档对图像数据的布局非常模糊,所以我实际上不得不猜测第一件没有崩溃的东西并对此感到满意,所以我很可能写错了。还有我的pixel 颜色数据调用(给出int 值)和我选择的每像素 24 位 RGB 格式之间大小不匹配的问题。

如何调整此代码以输出实际正常运行的视频文件?

【问题讨论】:

  • 在那个 sn-p 中,您只是在填充帧像素,所以我怀疑您的问题出在那些行上,如果只是这样,视频实际上会播放,但只会显示垃圾图像。

标签: c++ qt ffmpeg h.264 libavcodec


【解决方案1】:

libavcodec 文档在布局方面非常含糊 图像数据

这是因为每个编解码器都不同。我建议你使用 yuv420p 而不是 RGB24。很多玩家玩不了h264 rgb。您可以使用 libswscale 进行转换。

接下来,您制作的流是什么格式的? Annex B 可以直接播放,但是如果使用的是 extradata + NALU size (AVCC),则需要将流包装在容器中。

最后,为什么要使用 libavcodec?在我看来,libx264 提供了一个更干净的 API。除非您以后玩切换编解码器,否则请避免抽象。

【讨论】:

  • 我不知道附件 B、extraDATA、NALU 或 AVCC 是什么,所以我想我还有一些工作要做。不过,我会考虑直接使用 libx264 并转换颜色空间。
  • @EthanMcTague 试图简化它,他的意思是你似乎只是在生成一个 H264 编解码器流,也许你想实际生成一个包装该流的 mp4 文件,以便更容易播放。 libx264 通常使用 FFmpeg 编译,可能比您当前的方法更易于使用。
猜你喜欢
  • 2018-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-03
  • 1970-01-01
相关资源
最近更新 更多