Line data Source code
1 : /*
2 : * Copyright (C) 2004-2025 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 "libav_utils.h"
20 : #include "video_scaler.h"
21 : #include "media_buffer.h"
22 : #include "logger.h"
23 :
24 : #include <cassert>
25 :
26 : namespace jami {
27 : namespace video {
28 :
29 593 : VideoScaler::VideoScaler()
30 593 : : ctx_(0)
31 593 : , mode_(SWS_FAST_BILINEAR)
32 2965 : , tmp_data_()
33 593 : {}
34 :
35 593 : VideoScaler::~VideoScaler()
36 : {
37 593 : sws_freeContext(ctx_);
38 593 : }
39 :
40 : void
41 5753 : VideoScaler::scale(const VideoFrame& input, VideoFrame& output)
42 : {
43 5753 : scale(input.pointer(), output.pointer());
44 5753 : }
45 :
46 : void
47 5753 : VideoScaler::scale(const AVFrame* input_frame, AVFrame* output_frame)
48 : {
49 11506 : ctx_ = sws_getCachedContext(ctx_,
50 5753 : input_frame->width,
51 5753 : input_frame->height,
52 5753 : (AVPixelFormat) input_frame->format,
53 : output_frame->width,
54 : output_frame->height,
55 5753 : (AVPixelFormat) output_frame->format,
56 : mode_,
57 : NULL,
58 : NULL,
59 : NULL);
60 5753 : if (!ctx_) {
61 0 : JAMI_ERR("Unable to create a scaler context");
62 0 : return;
63 : }
64 :
65 5753 : sws_scale(ctx_,
66 5753 : input_frame->data,
67 5753 : input_frame->linesize,
68 : 0,
69 5753 : input_frame->height,
70 5753 : output_frame->data,
71 5753 : output_frame->linesize);
72 : }
73 :
74 : void
75 5752 : VideoScaler::scale_with_aspect(const VideoFrame& input, VideoFrame& output)
76 : {
77 5752 : if (input.width() == output.width() && input.height() == output.height()) {
78 5751 : if (input.format() != output.format()) {
79 5751 : auto outPtr = convertFormat(input, (AVPixelFormat) output.format());
80 5751 : output.copyFrom(*outPtr);
81 5751 : } 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 5752 : }
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 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 = (float) dest_width / dest_height;
107 1 : const float input_ratio = (float) input_frame->width / input_frame->height;
108 :
109 1 : if (local_ratio > input_ratio) {
110 1 : auto old_dest_width = dest_width;
111 1 : dest_width = 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 = 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 : dest_width,
131 : 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 out_desc = av_pix_fmt_desc_get((AVPixelFormat) output_frame->format);
144 1 : memset(tmp_data_, 0, sizeof(tmp_data_));
145 4 : for (int i = 0; i < 4 && output_frame->linesize[i]; i++) {
146 3 : signed x_shift = xoff, y_shift = 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] + y_shift * output_frame->linesize[i] + x_shift * x_step;
153 : }
154 :
155 1 : sws_scale(ctx_, input_frame->data, input_frame->linesize, 0, input_frame->height, tmp_data_, output_frame->linesize);
156 : }
157 :
158 : std::unique_ptr<VideoFrame>
159 5752 : VideoScaler::convertFormat(const VideoFrame& input, AVPixelFormat pix)
160 : {
161 5752 : auto output = std::make_unique<VideoFrame>();
162 5752 : output->reserve(pix, input.width(), input.height());
163 5752 : scale(input, *output);
164 5752 : av_frame_copy_props(output->pointer(), input.pointer());
165 5752 : return output;
166 0 : }
167 :
168 : void
169 0 : VideoScaler::reset()
170 : {
171 0 : if (ctx_) {
172 0 : sws_freeContext(ctx_);
173 0 : ctx_ = nullptr;
174 : }
175 0 : }
176 :
177 : } // namespace video
178 : } // namespace jami
|