如何使用ws_scale逐片缩放yuv图像

How to use ws_scale to scale slice by slice of yuv image

我有一张 YUV420 图像。我想将图像分成两个切片 (height/2) 并使用 ws_scale 分别缩放 (yuv->rgb) 它们(可能在不同的线程中)。例如

#include <string>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libavformat/avformat.h>
}
#include "utils.h"

AVFormatContext * initContext(const std::string& file_name) {
    auto * ctx = avformat_alloc_context();
    const char *src_filename = file_name.c_str();
    auto res = avformat_open_input(&ctx, src_filename, nullptr, nullptr);

    if (res < 0) return nullptr;

    return ctx;
}

AVCodecContext * initDecoder(AVFormatContext * ctx) {

    auto type = AVMediaType::AVMEDIA_TYPE_VIDEO;
    auto stream_index = av_find_best_stream(ctx, type, -1, -1, NULL, 0);

    if (stream_index < 0) return nullptr;

    AVStream * st = ctx->streams[stream_index];
    AVCodec * codec = avcodec_find_decoder(st->codecpar->codec_id);

    if (!codec) return nullptr;

    auto * codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) return nullptr;

    auto res = avcodec_parameters_to_context(codec_ctx, st->codecpar);
    if (res < 0)  return nullptr;

    AVDictionary *opts = NULL;
    res = avcodec_open2(codec_ctx, codec, &opts);
    if (res < 0) return nullptr;

    return codec_ctx;
}

int readFrame(AVCodecContext * decoder, AVFormatContext * ctx, AVFrame * out) {
    AVPacket pkt;
    av_init_packet(&pkt);

    auto type = AVMediaType::AVMEDIA_TYPE_VIDEO;
    auto stream_index = av_find_best_stream(ctx, type, -1, -1, NULL, 0);

    for(;;) {
        int res = av_read_frame(ctx, &pkt);
        if (res < 0) return res;

        if (pkt.stream_index != stream_index) continue;

        res = avcodec_send_packet(decoder, &pkt);
        if (res < 0) return res;

        res = avcodec_receive_frame(decoder, out);

        if (res == AVERROR(EAGAIN)) continue;

        if (res < 0) return res;

        break;
    }

    return 0;
}

SwsContext * initSws(int width, int height, AVPixelFormat in, AVPixelFormat out) {
    return sws_getContext(width, height, in, width, height, out,
                          SWS_BICUBIC | SWS_FULL_CHR_H_INT | SWS_ACCURATE_RND, NULL, NULL, NULL);
}


int saveFrame(AVFrame *frame, const std::string& fileName) {
    AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_PNG);
    if (!codec)  return -1;

    AVCodecContext *out_ctx = avcodec_alloc_context3(codec);
    if (!out_ctx)  return -1;

    out_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
    out_ctx->pix_fmt = AVPixelFormat::AV_PIX_FMT_RGB24;
    out_ctx->height = frame->height;
    out_ctx->width = frame->width;
    out_ctx->time_base = {1, 1000};

    if (avcodec_open2(out_ctx, codec, NULL) < 0) return -1;

    FILE *out_file;
    AVPacket packet;
    packet.data = NULL;
    packet.size = 0;

    av_init_packet(&packet);
    int gotFrame;

    if (avcodec_encode_video2(out_ctx, &packet, frame, &gotFrame) < 0) return -1;

    out_file = fopen(fileName.c_str(), "wb");
    fwrite(packet.data, 1, packet.size, out_file);
    fclose(out_file);

    av_packet_unref(&packet);
    avcodec_close(out_ctx);
    return 0;
}


int main() {

    avcodec_register_all();
    avformat_network_init();
    av_log_set_level(AV_LOG_INFO);

    AVFormatContext * fmt_ctx = initContext("res/fhd.mp4");
    if (!fmt_ctx) return -1;

    AVCodecContext * decoder = initDecoder(fmt_ctx);
    if (!decoder) return -1;

    AVFrame * frame = av_frame_alloc();
    auto res = readFrame(decoder, fmt_ctx, frame);
    if (res < 0) return -1;


    AVPixelFormat out_format = AVPixelFormat::AV_PIX_FMT_RGB24;
    SwsContext * sws = initSws(frame->width, frame->height, (AVPixelFormat) frame->format, out_format);
    if (!sws) return -1;

    AVFrame * scaled_frame = av_frame_alloc();
    scaled_frame->width = frame->width;
    scaled_frame->height = frame->height;
    scaled_frame->format = out_format;
    av_frame_get_buffer(scaled_frame, 32);

    auto h1 = frame->height/2;
    auto h2 = frame->height - h1;

    auto y1 = 0;
    auto y2 = h1;

    uint8_t * slice2[3] = {
            (uint8_t*) frame->data[0] + frame->linesize[0] * y2,
            (uint8_t*) frame->data[1] + frame->linesize[1] * y2/2,
            (uint8_t*) frame->data[2] + frame->linesize[2] * y2/2
    };

    // for image 1920x1080 res1 = 537
    auto res1 = sws_scale(sws, frame->data, frame->linesize, y1, h1, scaled_frame->data, scaled_frame->linesize);

    // res2 = 543
    auto res2 = sws_scale(sws, slice2, frame->linesize, y2, h2, scaled_frame->data, scaled_frame->linesize);

    saveFrame(scaled_frame, "img/out.png");

    return 0;
}

sws_scale可以吗?如果可能,如何在这种情况下指定 sw_scale 参数?

srcSlice 包含指向源切片平面的指针的数组

srcStride 包含源图像每个平面的步幅的数组

srcSliceY待处理切片在源图像中的位置,即切片第一行图像中的编号(从零开始计数)

srcSliceH源切片的高度,即切片中的行数

PS

我找到了一些解决方案,但如果我对两个切片使用相同的 SwsContext,它就会起作用。如果我为每个切片使用单独的上下文,结果是图像中间的透明矩形(高度 6 像素)。在这种情况下需要切片重叠吗?还是我做错了什么?我可以在不同的线程中使用相同的上下文吗?

看起来切片功能只有在两个切片使用相同的 SwsContext 时才有效。

如果我们想为每个切片使用单独的上下文,我们必须将每个 SwsContext 配置为好像有两个单独的图像。

第一个上下文可能“缩放”上半部分,第二个上下文可能“缩放”下半部分。

  • 两种上下文的图像大小都适用 height/2。
  • 拳头上下文获取指向上半部分的源数据和目标数据的指针。
  • 第二个上下文获取指向下半部分的源数据和目标数据的指针(源指针和目标指针都是高级的)。

对于 YUV420p 输入,该解决方案仅在高度为 4 的倍数时才有效。


代码的相关部分如下:

auto h1 = frame->height / 2;
auto h2 = frame->height - h1;

auto y1 = 0;
auto y2 = h1;

//sws1 applies top half
SwsContext* sws1 = initSws(frame->width, h1, (AVPixelFormat)frame->format, out_format);  //Set the height to frame->height/2
//SwsContext* sws1 = initSws(frame->width, frame->height, (AVPixelFormat)frame->format, out_format);
if (!sws1) return -1;

//sws2 applies bottom half
SwsContext* sws2 = initSws(frame->width, h2, (AVPixelFormat)frame->format, out_format);  //Set the height to frame->height/2
if (!sws2) return -1;


AVFrame* scaled_frame = av_frame_alloc();
scaled_frame->width = frame->width;
scaled_frame->height = frame->height;
scaled_frame->format = out_format;
av_frame_get_buffer(scaled_frame, 32);


uint8_t* src_slice2[3] = {
        (uint8_t*)frame->data[0] + frame->linesize[0] * y2,
        (uint8_t*)frame->data[1] + frame->linesize[1] * y2 / 2,
        (uint8_t*)frame->data[2] + frame->linesize[2] * y2 / 2
};

//Bottom half of the destination
uint8_t* dst_slice2[1] = {
        (uint8_t*)scaled_frame->data[0] + scaled_frame->linesize[0] * y2,
};


// for image 1920x1080 res1 = 540
auto res1 = sws_scale(sws1, frame->data, frame->linesize, 0, h1, scaled_frame->data, scaled_frame->linesize);   //Set srcSliceY = 0

// res2 = 540
auto res2 = sws_scale(sws2, src_slice2, frame->linesize, 0, h2, dst_slice2, scaled_frame->linesize);    //Set srcSliceY = 0
//auto res2 = sws_scale(sws1, src_slice2, frame->linesize, y2, h2, scaled_frame->data, scaled_frame->linesize);