Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "libav_deps.h" // MUST BE INCLUDED FIRST
19 : #include "video_scaler.h"
20 : #include "media_buffer.h"
21 : #include "logger.h"
22 :
23 : #include <cassert>
24 : #include <cstddef>
25 :
26 : namespace jami {
27 : namespace video {
28 :
29 560 : VideoScaler::VideoScaler()
30 560 : : ctx_(0)
31 560 : , mode_(SWS_FAST_BILINEAR)
32 2800 : , tmp_data_()
33 560 : {}
34 :
35 561 : VideoScaler::~VideoScaler()
36 : {
37 561 : sws_freeContext(ctx_);
38 561 : }
39 :
40 : void
41 5594 : VideoScaler::scale(const VideoFrame& input, VideoFrame& output)
42 : {
43 5594 : scale(input.pointer(), output.pointer());
44 5594 : }
45 :
46 : void
47 5594 : VideoScaler::scale(const AVFrame* input_frame, AVFrame* output_frame)
48 : {
49 11188 : ctx_ = sws_getCachedContext(ctx_,
50 5594 : input_frame->width,
51 5594 : input_frame->height,
52 5594 : (AVPixelFormat) input_frame->format,
53 : output_frame->width,
54 : output_frame->height,
55 5594 : (AVPixelFormat) output_frame->format,
56 : mode_,
57 : NULL,
58 : NULL,
59 : NULL);
60 5594 : if (!ctx_) {
61 0 : JAMI_ERR("Unable to create a scaler context");
62 0 : return;
63 : }
64 :
65 5594 : sws_scale(ctx_,
66 5594 : input_frame->data,
67 5594 : input_frame->linesize,
68 : 0,
69 5594 : input_frame->height,
70 5594 : output_frame->data,
71 5594 : output_frame->linesize);
72 : }
73 :
74 : void
75 5593 : VideoScaler::scale_with_aspect(const VideoFrame& input, VideoFrame& output)
76 : {
77 5593 : if (input.width() == output.width() && input.height() == output.height()) {
78 5592 : if (input.format() != output.format()) {
79 5592 : auto outPtr = convertFormat(input, (AVPixelFormat) output.format());
80 5592 : output.copyFrom(*outPtr);
81 5592 : } else {
82 0 : output.copyFrom(input);
83 : }
84 : } else {
85 1 : auto* output_frame = output.pointer();
86 1 : scale_and_pad(input, output, 0, 0, output_frame->width, output_frame->height, true);
87 : }
88 5593 : }
89 :
90 : void
91 1 : VideoScaler::scale_and_pad(const VideoFrame& input,
92 : VideoFrame& output,
93 : unsigned xoff,
94 : unsigned yoff,
95 : unsigned dest_width,
96 : unsigned dest_height,
97 : bool keep_aspect)
98 : {
99 1 : const auto* const input_frame = input.pointer();
100 1 : auto* output_frame = output.pointer();
101 :
102 : /* Correct destination width/height and offset if we need to keep input
103 : * frame aspect.
104 : */
105 1 : if (keep_aspect) {
106 1 : const float local_ratio = static_cast<float>(dest_width) / static_cast<float>(dest_height);
107 1 : const float input_ratio = static_cast<float>(input_frame->width) / static_cast<float>(input_frame->height);
108 :
109 1 : if (local_ratio > input_ratio) {
110 1 : auto old_dest_width = dest_width;
111 1 : dest_width = static_cast<unsigned int>(static_cast<float>(dest_height) * input_ratio);
112 1 : xoff += (old_dest_width - dest_width) / 2;
113 : } else {
114 0 : auto old_dest_heigth = dest_height;
115 0 : dest_height = static_cast<unsigned int>(static_cast<float>(dest_width) / input_ratio);
116 0 : yoff += (old_dest_heigth - dest_height) / 2;
117 : }
118 : }
119 :
120 : // buffer overflow checks
121 1 : if ((xoff + dest_width > (unsigned) output_frame->width) || (yoff + dest_height > (unsigned) output_frame->height)) {
122 0 : JAMI_ERR("Unable to scale video");
123 0 : return;
124 : }
125 :
126 2 : ctx_ = sws_getCachedContext(ctx_,
127 1 : input_frame->width,
128 1 : input_frame->height,
129 1 : (AVPixelFormat) input_frame->format,
130 : static_cast<int>(dest_width),
131 : static_cast<int>(dest_height),
132 1 : (AVPixelFormat) output_frame->format,
133 : mode_,
134 : NULL,
135 : NULL,
136 : NULL);
137 1 : if (!ctx_) {
138 0 : JAMI_ERR("Unable to create a scaler context");
139 0 : return;
140 : }
141 :
142 : // Make an offset'ed copy of output data from xoff and yoff
143 1 : const auto* const out_desc = av_pix_fmt_desc_get((AVPixelFormat) output_frame->format);
144 1 : memset(static_cast<void*>(tmp_data_), 0, sizeof(tmp_data_));
145 4 : for (int i = 0; i < 4 && output_frame->linesize[i]; i++) {
146 3 : signed x_shift = static_cast<int>(xoff), y_shift = static_cast<int>(yoff);
147 3 : if (i == 1 || i == 2) {
148 2 : x_shift = -((-x_shift) >> out_desc->log2_chroma_w);
149 2 : y_shift = -((-y_shift) >> out_desc->log2_chroma_h);
150 : }
151 3 : auto x_step = out_desc->comp[i].step;
152 3 : tmp_data_[i] = output_frame->data[i] + static_cast<ptrdiff_t>(y_shift * output_frame->linesize[i])
153 3 : + static_cast<ptrdiff_t>(x_shift * x_step);
154 : }
155 :
156 1 : sws_scale(ctx_, input_frame->data, input_frame->linesize, 0, input_frame->height, tmp_data_, output_frame->linesize);
157 : }
158 :
159 : std::unique_ptr<VideoFrame>
160 5593 : VideoScaler::convertFormat(const VideoFrame& input, AVPixelFormat pix)
161 : {
162 5593 : auto output = std::make_unique<VideoFrame>();
163 5593 : output->reserve(pix, input.width(), input.height());
164 5593 : scale(input, *output);
165 5593 : av_frame_copy_props(output->pointer(), input.pointer());
166 5593 : return output;
167 0 : }
168 :
169 : void
170 0 : VideoScaler::reset()
171 : {
172 0 : if (ctx_) {
173 0 : sws_freeContext(ctx_);
174 0 : ctx_ = nullptr;
175 : }
176 0 : }
177 :
178 : } // namespace video
179 : } // namespace jami
|