当输入分辨率为 1200x1600 时,通过 C-API 进行的 ffmpeg 解码会导致伪影。我做错了什么吗?

问题描述 投票:0回答:1

使用 C-API 和 FFmpeg 5.1,我已经能够在 Android 上使用 libx264 对 h264 视频进行编码。 现在我想在我的 C++ 应用程序中的 Linux 上重放它们。这些视频可以在浏览器或我试过的其他播放器(例如 ffmpeg 的 mplayer 或 ffplay)上正确播放。此外,我可以使用

ffmpeg -i recording.mp4 -start_number 0 -qscale:v 5 %06d.jpg
展开框架,图像看起来不错。

然而,在我的 C++ 应用程序中,时不时地,但以一种非常可重复的方式,我会得到伪像(比如显示器上方出现的明亮像素)。它们不会累积,即使它们与关键帧无关。因此,无论发生什么错误,它似乎都不会对后续帧产生影响。我使用 OpenCV 来可视化输出,我很确定问题不在于转换为 BGR,因为如果我只显示 y 通道(亮度、灰度),工件就已经存在了。

这些伪像出现在我以 1200x1600 分辨率录制的视频中。请注意,1200 不能被 32 整除,因此 ffmpeg 确实添加了一些填充,但我正在处理它,这不是问题。以 1920x1440 录制的视频在回放时没有任何瑕疵。可以在这里找到两个示例视频以供下载。

下面是我正在使用的代码,在底部你可以看到我的解码图像的图片和工件,与 ffmpeg 命令行展开的相同。应该注意的是,我正在使用 ffmpeg 的自定义构建版本,它来自 conan 包,而展开是通过 Ubuntu 附带的命令行使用 ffmpeg 完成的。

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <iostream>
int main(int argc, char** argv) {

    int ret;

    auto pkt = av_packet_alloc();
    if (!pkt) {
        std::cerr << "Failed av_packet_alloc()" << std::endl;
        exit(1);
    }

    AVFormatContext* av_format = avformat_alloc_context();
    ret = avformat_open_input(&av_format, FILE_NAME, nullptr, nullptr);
    if (ret < 0) {
        std::cerr << "Failed avformat_open_input, Error: " << ret << std::endl;
        ///Error codes https://stackoverflow.com/questions/12780931/ffmpeg-exit-status-1094995529
        exit(1);
    }
    av_dump_format(av_format, 0, FILE_NAME, 0);
    auto video_st_number = av_find_best_stream(av_format, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
    if (video_st_number < 0) {
        std::cerr << "av_find_best_stream couldn't find video stream" << std::endl;
        exit(1);
    }
    auto video_st = av_format->streams[video_st_number];
    auto codec_id = video_st->codecpar->codec_id;
    std::cout << "Duration " << video_st->duration << std::endl;
    std::cout << "n_frames " << video_st->nb_frames << std::endl;

    auto frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }

    auto codec = avcodec_find_decoder(codec_id);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    auto c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }


    if ((ret = avcodec_parameters_to_context(c, video_st->codecpar))) {
        fprintf(stderr, "Failed avcodec_parameters_to_context\n");
        exit(1);
    }

    c->pix_fmt = AV_PIX_FMT_YUV420P;///Not really necessary
    c->thread_count = 1;///No impact

    /* open it */
    if (avcodec_open2(c, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n ");
        exit(1);
    }

    std::size_t counter = 0;
    std::size_t n_keyframes = 0;

    while (ret >= 0) {
        ret = av_read_frame(av_format, pkt);
        if (pkt->size == 0) {
            std::cout << "Skipping packet of size zero" << std::endl;
            av_packet_unref(pkt);
            continue;
        }
        while (avcodec_send_packet(c, pkt) != 0) {
            if (avcodec_receive_frame(c, frame) != 0) {
                std::cerr << "Error receiving frame" << std::endl;
                exit(1);
            } else {
                n_keyframes += frame->key_frame;
                std::cout << "Decoded " << ++counter << " frames. Frame No. " << frame->pts / pkt->duration << " "
                          << frame->decode_error_flags << " " << frame->key_frame << " " << n_keyframes << " "
                          << frame->pkt_dts << std::endl;
            }
            display(frame);
        }
        av_packet_unref(pkt);
    }

    avcodec_send_packet(c, nullptr);
    std::cout << "Flushing decoder" << std::endl;

    while (avcodec_receive_frame(c, frame) == 0) {
        n_keyframes += frame->key_frame;
        std::cout << "Decoded " << ++counter << " frames. Frame No. " << frame->pts << " " << frame->decode_error_flags
                  << " " << frame->key_frame << " " << n_keyframes << " " << frame->pkt_dts << std::endl;

        display(frame);
    }

    avcodec_free_context(&c);
    avformat_free_context(av_format);
    av_frame_free(&frame);
    av_packet_free(&pkt);

    return 0;
}

为了完整性,这是

display
函数,使用openCV

void display(const AVFrame* frame) {
    static std::vector<uint8_t> yuv_buffer;
    yuv_buffer.resize(frame->linesize[0] * 3 / 2 * frame->width);
    cv::Mat mYUV(frame->height * 3 / 2, frame->width, CV_8UC1, yuv_buffer.data(), frame->linesize[0]);
    memcpy(mYUV.ptr(), frame->data[0], frame->linesize[0] * frame->height);
    //cv::imshow("grayscale", mYUV.rowRange(0, frame->height));
    //cv::imshow("u", cv::Mat(frame->height / 2, frame->width / 2, CV_8UC1, frame->data[1], frame->linesize[1]));
    //cv::imshow("v", cv::Mat(frame->height / 2, frame->width / 2, CV_8UC1, frame->data[2], frame->linesize[2]));

    int dest_row = frame->height;
    for (int j = 0; j < frame->height / 2; j++) {
        memcpy(mYUV.ptr(dest_row), frame->data[1] + frame->linesize[1] * j, frame->width);
        j++;
        memcpy(mYUV.ptr(dest_row) + frame->width / 2, frame->data[1] + frame->linesize[1] * j, frame->width);
        dest_row++;
    }
    for (int j = 0; j < frame->height / 2; j++) {
        memcpy(mYUV.ptr(dest_row), frame->data[2] + frame->linesize[2] * j, frame->width);
        j++;
        memcpy(mYUV.ptr(dest_row) + frame->width / 2, frame->data[2] + frame->linesize[2] * j, frame->width);
        dest_row++;
    }
    cv::Mat mRGB(frame->height, frame->width, CV_8UC3);
    cvtColor(mYUV, mRGB, cv::COLOR_YUV2BGR_I420, 3);
    cv::imshow("Video", mRGB);
    cv::waitKey(0);
}
c++ c ffmpeg decode h.264
1个回答
0
投票

在 Visual Studio 中执行代码时,在 display 函数中出现异常“

Access violation writing location
...”。

原因是分配的大小是

frame->linesize[0] * 3 / 2 * frame->width
而不是
frame->linesize[0] * 3 / 2 * frame->height
(应该是
height
而不是
width
)。

替换:

yuv_buffer.resize(frame->linesize[0] * 3 / 2 * frame->width);
为:

yuv_buffer.resize(frame->linesize[0] * 3 / 2 * frame->height);

写出缓冲区的边界可能会导致奇怪的人工制品。


另一个小问题是在复制

U
V
频道的循环中:
要复制的字节数应该是
frame->width/2
而不是
frame->width
(U,V行大小是
frame->width/2
)。

int dest_row = frame->height;
for (int j = 0; j < frame->height / 2; j++) {
    //memcpy(mYUV.ptr(dest_row), frame->data[1] + frame->linesize[1] * j, frame->width);  //<--- should be frame->width/2 instead of frame->width
    memcpy(mYUV.ptr(dest_row), frame->data[1] + frame->linesize[1] * j, frame->width/2);
    j++;
    memcpy(mYUV.ptr(dest_row) + frame->width / 2, frame->data[1] + frame->linesize[1] * j, frame->width/2);
    dest_row++;
}
for (int j = 0; j < frame->height / 2; j++) {
    memcpy(mYUV.ptr(dest_row), frame->data[2] + frame->linesize[2] * j, frame->width/2);
    j++;
    memcpy(mYUV.ptr(dest_row) + frame->width / 2, frame->data[2] + frame->linesize[2] * j, frame->width/2);
    dest_row++;
}

为了完整性,这里是完整的代码示例:

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}

#include <opencv2/opencv.hpp>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <vector>

#include <iostream>

#define FILE_NAME "portrait1200x1400.mp4"

static void display(const AVFrame* frame) {
    static std::vector<uint8_t> yuv_buffer;    
    //yuv_buffer.resize(frame->linesize[0] * 3 / 2 * frame->width); // <--- should be frame->height instead of frame->width
    yuv_buffer.resize(frame->linesize[0] * 3 / 2 * frame->height);
    
    cv::Mat mYUV(frame->height * 3 / 2, frame->width, CV_8UC1, yuv_buffer.data(), frame->linesize[0]);
    memcpy(mYUV.data, frame->data[0], frame->linesize[0] * frame->height);
    //cv::imshow("grayscale", mYUV.rowRange(0, frame->height));
    //cv::imshow("u", cv::Mat(frame->height / 2, frame->width / 2, CV_8UC1, frame->data[1], frame->linesize[1]));
    //cv::imshow("v", cv::Mat(frame->height / 2, frame->width / 2, CV_8UC1, frame->data[2], frame->linesize[2]));

    int dest_row = frame->height;
    for (int j = 0; j < frame->height / 2; j++) {
        //memcpy(mYUV.ptr(dest_row), frame->data[1] + frame->linesize[1] * j, frame->width);  //<--- should be frame->width/2 instead of frame->width
        memcpy(mYUV.ptr(dest_row), frame->data[1] + frame->linesize[1] * j, frame->width/2);
        j++;
        memcpy(mYUV.ptr(dest_row) + frame->width / 2, frame->data[1] + frame->linesize[1] * j, frame->width/2);
        dest_row++;
    }
    for (int j = 0; j < frame->height / 2; j++) {
        memcpy(mYUV.ptr(dest_row), frame->data[2] + frame->linesize[2] * j, frame->width/2);
        j++;
        memcpy(mYUV.ptr(dest_row) + frame->width / 2, frame->data[2] + frame->linesize[2] * j, frame->width/2);
        dest_row++;
    }

    cv::Mat mRGB(frame->height, frame->width, CV_8UC3);
    cvtColor(mYUV, mRGB, cv::COLOR_YUV2BGR_I420, 3);
    cv::imshow("Video", mRGB);
    cv::waitKey(0);
}


int main(int argc, char** argv) {

    int ret;

    auto pkt = av_packet_alloc();
    if (!pkt) {
        std::cerr << "Failed av_packet_alloc()" << std::endl;
        exit(1);
    }

    AVFormatContext* av_format = avformat_alloc_context();
    ret = avformat_open_input(&av_format, FILE_NAME, nullptr, nullptr);
    if (ret < 0) {
        std::cerr << "Failed avformat_open_input, Error: " << ret << std::endl;
        ///Error codes https://stackoverflow.com/questions/12780931/ffmpeg-exit-status-1094995529
        exit(1);
    }
    av_dump_format(av_format, 0, FILE_NAME, 0);
    auto video_st_number = av_find_best_stream(av_format, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
    if (video_st_number < 0) {
        std::cerr << "av_find_best_stream couldn't find video stream" << std::endl;
        exit(1);
    }
    auto video_st = av_format->streams[video_st_number];
    auto codec_id = video_st->codecpar->codec_id;
    std::cout << "Duration " << video_st->duration << std::endl;
    std::cout << "n_frames " << video_st->nb_frames << std::endl;

    auto frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }

    auto codec = avcodec_find_decoder(codec_id);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    auto c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }


    if ((ret = avcodec_parameters_to_context(c, video_st->codecpar))) {
        fprintf(stderr, "Failed avcodec_parameters_to_context\n");
        exit(1);
    }

    c->pix_fmt = AV_PIX_FMT_YUV420P;///Not really necessary
    c->thread_count = 1;///No impact

    /* open it */
    if (avcodec_open2(c, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n ");
        exit(1);
    }

    std::size_t counter = 0;
    std::size_t n_keyframes = 0;

    while (ret >= 0) {
        ret = av_read_frame(av_format, pkt);
        if (pkt->size == 0) {
            std::cout << "Skipping packet of size zero" << std::endl;
            av_packet_unref(pkt);
            continue;
        }
        while (avcodec_send_packet(c, pkt) != 0) {
            if (avcodec_receive_frame(c, frame) != 0) {
                std::cerr << "Error receiving frame" << std::endl;
                exit(1);
            } else {
                n_keyframes += frame->key_frame;
                std::cout << "Decoded " << ++counter << " frames. Frame No. " << frame->pts / pkt->duration << " "
                          << frame->decode_error_flags << " " << frame->key_frame << " " << n_keyframes << " "
                          << frame->pkt_dts << std::endl;
            }
            display(frame);
        }
        av_packet_unref(pkt);
    }

    avcodec_send_packet(c, nullptr);
    std::cout << "Flushing decoder" << std::endl;

    while (avcodec_receive_frame(c, frame) == 0) {
        n_keyframes += frame->key_frame;
        std::cout << "Decoded " << ++counter << " frames. Frame No. " << frame->pts << " " << frame->decode_error_flags
                  << " " << frame->key_frame << " " << n_keyframes << " " << frame->pkt_dts << std::endl;

        display(frame);
    }

    avcodec_free_context(&c);
    avformat_free_context(av_format);
    av_frame_free(&frame);
    av_packet_free(&pkt);

    return 0;
}
© www.soinside.com 2019 - 2024. All rights reserved.