如何使用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);
我有一张 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);