Skip to content

Commit aa60ed5

Browse files
committed
helpers: examples: Add helpers and an example converter utility
Signed-off-by: Naushir Patuck <[email protected]>
1 parent 00e568d commit aa60ed5

File tree

14 files changed

+1323
-3
lines changed

14 files changed

+1323
-3
lines changed

meson_options.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
# Copyright (C) 2023, Raspberry Pi Ltd
33

44
option('logging', type : 'feature', value : 'auto')
5+
option('examples', type : 'boolean', value : false)

src/examples/convert.cpp

Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
2+
/* SPDX-License-Identifier: BSD-2-Clause */
3+
/*
4+
* Copyright (C) 2025 Raspberry Pi Ltd
5+
*
6+
* convert.cpp - libpisp simple image converter example
7+
*/
8+
9+
#include <algorithm>
10+
#include <array>
11+
#include <assert.h>
12+
#include <fstream>
13+
#include <functional>
14+
#include <iostream>
15+
#include <map>
16+
#include <stdint.h>
17+
#include <string>
18+
19+
#include <linux/media.h>
20+
21+
#include <cxxopts.hpp>
22+
23+
#include "helpers/backend_device.hpp"
24+
#include "helpers/media_device.hpp"
25+
26+
#include "libpisp/backend/backend.hpp"
27+
#include "libpisp/common/logging.hpp"
28+
#include "libpisp/common/utils.hpp"
29+
#include "libpisp/variants/variant.hpp"
30+
31+
void read_plane(uint8_t *mem, std::ifstream &in, unsigned int width, unsigned int height, unsigned int file_stride,
32+
unsigned int buffer_stride)
33+
{
34+
width = std::min(width, file_stride);
35+
36+
for (unsigned int y = 0; y < height; y++)
37+
{
38+
in.read((char *)mem + y * buffer_stride, width);
39+
in.seekg(file_stride - width, std::ios_base::cur);
40+
}
41+
}
42+
43+
void write_plane(std::ofstream &out, uint8_t *mem, unsigned int width, unsigned int height, unsigned int file_stride,
44+
unsigned int buffer_stride)
45+
{
46+
width = std::min(width, file_stride);
47+
48+
for (unsigned int y = 0; y < height; y++)
49+
{
50+
out.write((char *)mem + y * buffer_stride, width);
51+
for (unsigned int i = 0; i < file_stride - width; i++)
52+
out.put(0);
53+
}
54+
}
55+
56+
void read_rgb888(std::array<uint8_t *, 3> &mem, std::ifstream &in, unsigned int width, unsigned int height,
57+
unsigned int file_stride, unsigned int buffer_stride)
58+
{
59+
read_plane((uint8_t *)mem[0], in, width * 3, height, file_stride, buffer_stride);
60+
}
61+
62+
void write_rgb888(std::ofstream &out, std::array<uint8_t *, 3> &mem, unsigned int width, unsigned int height,
63+
unsigned int file_stride, unsigned int buffer_stride)
64+
{
65+
write_plane(out, (uint8_t *)mem[0], width * 3, height, file_stride, buffer_stride);
66+
}
67+
68+
void read_yuv(std::array<uint8_t *, 3> &mem, std::ifstream &in, unsigned int width, unsigned int height,
69+
unsigned int file_stride, unsigned int buffer_stride, unsigned int ss_x, unsigned int ss_y)
70+
{
71+
uint8_t *dst = mem[0];
72+
// Y
73+
read_plane(dst, in, width, height, file_stride, buffer_stride);
74+
// U
75+
dst = mem[1] ? mem[1] : dst + buffer_stride * height;
76+
read_plane(dst, in, width / ss_x, height / ss_y, file_stride / ss_x, buffer_stride / ss_x);
77+
// V
78+
dst = mem[2] ? mem[2] : dst + buffer_stride / ss_x * height / ss_y;
79+
read_plane(dst, in, width / ss_x, height / ss_y, file_stride / ss_x, buffer_stride / ss_x);
80+
}
81+
82+
void write_yuv(std::ofstream &out, std::array<uint8_t *, 3> &mem, unsigned int width, unsigned int height,
83+
unsigned int file_stride, unsigned int buffer_stride, unsigned int ss_x, unsigned int ss_y)
84+
{
85+
uint8_t *src = mem[0];
86+
// Y
87+
write_plane(out, src, width, height, file_stride, buffer_stride);
88+
// U
89+
src = mem[1] ? mem[1] : src + buffer_stride * height;
90+
write_plane(out, src, width / ss_x, height / ss_y, file_stride / ss_x, buffer_stride / ss_x);
91+
// V
92+
src = mem[2] ? mem[2] : src + buffer_stride / ss_x * height / ss_y;
93+
write_plane(out, src, width / ss_x, height / ss_y, file_stride / ss_x, buffer_stride / ss_x);
94+
}
95+
96+
void read_yuv420(std::array<uint8_t *, 3> &mem, std::ifstream &in, unsigned int width, unsigned int height,
97+
unsigned int file_stride, unsigned int buffer_stride)
98+
{
99+
read_yuv(mem, in, width, height, file_stride, buffer_stride, 2, 2);
100+
}
101+
102+
void read_yuv422p(std::array<uint8_t *, 3> &mem, std::ifstream &in, unsigned int width, unsigned int height,
103+
unsigned int file_stride, unsigned int buffer_stride)
104+
{
105+
read_yuv(mem, in, width, height, file_stride, buffer_stride, 2, 1);
106+
}
107+
108+
void read_yuv444p(std::array<uint8_t *, 3> &mem, std::ifstream &in, unsigned int width, unsigned int height,
109+
unsigned int file_stride, unsigned int buffer_stride)
110+
{
111+
read_yuv(mem, in, width, height, file_stride, buffer_stride, 1, 1);
112+
}
113+
114+
void read_yuv422i(std::array<uint8_t *, 3> &mem, std::ifstream &in, unsigned int width, unsigned int height,
115+
unsigned int file_stride, unsigned int buffer_stride)
116+
{
117+
read_plane(mem[0], in, width * 2, height, file_stride, buffer_stride);
118+
}
119+
120+
void write_yuv420(std::ofstream &out, std::array<uint8_t *, 3> &mem, unsigned int width, unsigned int height,
121+
unsigned int file_stride, unsigned int buffer_stride)
122+
{
123+
write_yuv(out, mem, width, height, file_stride, buffer_stride, 2, 2);
124+
}
125+
126+
void write_yuv422p(std::ofstream &out, std::array<uint8_t *, 3> &mem, unsigned int width, unsigned int height,
127+
unsigned int file_stride, unsigned int buffer_stride)
128+
{
129+
write_yuv(out, mem, width, height, file_stride, buffer_stride, 2, 1);
130+
}
131+
132+
void write_yuv444p(std::ofstream &out, std::array<uint8_t *, 3> &mem, unsigned int width, unsigned int height,
133+
unsigned int file_stride, unsigned int buffer_stride)
134+
{
135+
write_yuv(out, mem, width, height, file_stride, buffer_stride, 1, 1);
136+
}
137+
138+
void write_yuv422i(std::ofstream &out, std::array<uint8_t *, 3> &mem, unsigned int width, unsigned int height,
139+
unsigned int file_stride, unsigned int buffer_stride)
140+
{
141+
write_plane(out, mem[0], width * 2, height, file_stride, buffer_stride);
142+
}
143+
144+
struct FormatFuncs
145+
{
146+
std::function<void(std::array<uint8_t *, 3> &, std::ifstream &, unsigned int, unsigned int, unsigned int,
147+
unsigned int)> read_file;
148+
std::function<void(std::ofstream &, std::array<uint8_t *, 3> &, unsigned int, unsigned int, unsigned int,
149+
unsigned int)> write_file;
150+
};
151+
152+
const std::map<std::string, FormatFuncs> Formats =
153+
{
154+
{ "RGB888", { read_rgb888, write_rgb888 } },
155+
{ "YUV420P", { read_yuv420, write_yuv420 } },
156+
{ "YUV422P", { read_yuv422p, write_yuv422p } },
157+
{ "YUV444P", { read_yuv444p, write_yuv444p } },
158+
{ "YUYV", { read_yuv422i, write_yuv422i } },
159+
{ "UYVY", { read_yuv422i, write_yuv422i } },
160+
};
161+
162+
struct Format
163+
{
164+
unsigned int width;
165+
unsigned int height;
166+
unsigned int stride;
167+
std::string format;
168+
};
169+
170+
Format parse_format(const std::string &fmt)
171+
{
172+
Format format;
173+
size_t pos = 0, start = 0;
174+
175+
pos = fmt.find(':', start);
176+
if (pos == std::string::npos)
177+
return {};
178+
format.width = std::stoi(fmt.substr(start, pos - start));
179+
start = pos + 1;
180+
181+
pos = fmt.find(':', start);
182+
if (pos == std::string::npos)
183+
return {};
184+
format.height = std::stoi(fmt.substr(start, pos - start));
185+
start = pos + 1;
186+
187+
pos = fmt.find(':', start);
188+
if (pos == std::string::npos)
189+
return {};
190+
format.stride = std::stoi(fmt.substr(start, pos - start));
191+
start = pos + 1;
192+
193+
format.format = fmt.substr(start);
194+
195+
return format;
196+
}
197+
198+
int main(int argc, char *argv[])
199+
{
200+
libpisp::helpers::MediaDevice devices;
201+
202+
libpisp::logging_init();
203+
204+
cxxopts::Options options(argv[0], "PiSP image converter");
205+
206+
options.add_options()
207+
("input", "Input file", cxxopts::value<std::string>())
208+
("output", "Output file", cxxopts::value<std::string>())
209+
("input-format", "Input format in the form width:height:stride:format\n"
210+
"Bit-depth is assumed to be 8-bit.",cxxopts::value<std::string>()->default_value(""))
211+
("output-format", "Output format in the form width:height:stride:format\n"
212+
"Bit-depth is assumed to be 8-bit.", cxxopts::value<std::string>()->default_value(""))
213+
("f,formats", "List available format strings that can be used")
214+
("l,list", "Enumerate the media device nodes")
215+
("h,help", "Print usage")
216+
;
217+
218+
options.parse_positional({ "input", "output" });
219+
options.positional_help("<input file> <output file>");
220+
options.set_width(120);
221+
222+
auto args = options.parse(argc, argv);
223+
224+
if (args.count("help"))
225+
{
226+
std::cerr << options.help() << std::endl;
227+
exit(0);
228+
}
229+
else if (args.count("list"))
230+
{
231+
std::cerr << devices.List() << std::endl;
232+
exit(0);
233+
}
234+
else if (args.count("formats"))
235+
{
236+
for (const auto &f : Formats)
237+
std::cerr << f.first << " ";
238+
std::cerr << std::endl;
239+
exit(0);
240+
}
241+
242+
std::string media_dev = devices.Acquire();
243+
if (media_dev.empty())
244+
{
245+
std::cerr << "Unable to acquire any pisp_be device!" << std::endl;
246+
exit(-1);
247+
}
248+
249+
libpisp::helpers::BackendDevice backend_device { media_dev };
250+
std::cerr << "Acquired device " << media_dev << std::endl;
251+
252+
auto in_file = parse_format(args["input-format"].as<std::string>());
253+
if (!Formats.count(in_file.format))
254+
{
255+
std::cerr << "Invalid input-format specified" << std::endl;
256+
exit(-1);
257+
}
258+
259+
auto out_file = parse_format(args["output-format"].as<std::string>());
260+
if (!Formats.count(out_file.format))
261+
{
262+
std::cerr << "Invalid output-format specified" << std::endl;
263+
exit(-1);
264+
}
265+
266+
const std::vector<libpisp::PiSPVariant> &variants = libpisp::get_variants();
267+
const media_device_info info = devices.DeviceInfo(media_dev);
268+
auto variant = std::find_if(variants.begin(), variants.end(),
269+
[&info](const auto &v) { return v.BackEndVersion() == info.hw_revision; });
270+
if (variant == variants.end())
271+
{
272+
std::cerr << "Backend hardware cound not be identified: " << info.hw_revision << std::endl;
273+
exit(-1);
274+
}
275+
276+
libpisp::BackEnd be(libpisp::BackEnd::Config({}), *variant);
277+
278+
pisp_be_global_config global;
279+
be.GetGlobal(global);
280+
global.bayer_enables = 0;
281+
global.rgb_enables = PISP_BE_RGB_ENABLE_INPUT + PISP_BE_RGB_ENABLE_OUTPUT0;
282+
283+
pisp_image_format_config i = {};
284+
i.width = in_file.width;
285+
i.height = in_file.height;
286+
i.format = libpisp::get_pisp_image_format(in_file.format);
287+
assert(i.format);
288+
libpisp::compute_optimal_stride(i);
289+
be.SetInputFormat(i);
290+
291+
pisp_be_output_format_config o = {};
292+
o.image.width = out_file.width;
293+
o.image.height = out_file.height;
294+
o.image.format = libpisp::get_pisp_image_format(out_file.format);
295+
assert(o.image.format);
296+
libpisp::compute_optimal_stride(o.image, true);
297+
be.SetOutputFormat(0, o);
298+
299+
if (!out_file.stride)
300+
out_file.stride = o.image.stride;
301+
302+
if (in_file.format != "RGB888")
303+
{
304+
pisp_be_ccm_config csc;
305+
be.InitialiseYcbcrInverse(csc, "jpeg");
306+
be.SetCcm(csc);
307+
global.rgb_enables += PISP_BE_RGB_ENABLE_CCM;
308+
}
309+
310+
if (out_file.format != "RGB888")
311+
{
312+
pisp_be_ccm_config csc;
313+
be.InitialiseYcbcr(csc, "jpeg");
314+
be.SetCsc(0, csc);
315+
global.rgb_enables += PISP_BE_RGB_ENABLE_CSC0;
316+
}
317+
318+
be.SetGlobal(global);
319+
be.SetCrop(0, { 0, 0, i.width, i.height });
320+
be.SetSmartResize(0, { o.image.width, o.image.height });
321+
322+
pisp_be_tiles_config config = {};
323+
be.Prepare(&config);
324+
325+
backend_device.Setup(config);
326+
auto buffers = backend_device.GetBuffers();
327+
328+
std::string input_filename = args["input"].as<std::string>();
329+
std::ifstream in(input_filename, std::ios::binary);
330+
if (!in.is_open())
331+
{
332+
std::cerr << "Unable to open " << input_filename << std::endl;
333+
exit(-1);
334+
}
335+
336+
std::cerr << "Reading " << input_filename << " "
337+
<< in_file.width << ":" << in_file.height << ":" << in_file.stride << ":" << in_file.format << std::endl;
338+
339+
Formats.at(in_file.format)
340+
.read_file(buffers["pispbe-input"].mem, in, in_file.width, in_file.height, in_file.stride,
341+
i.stride);
342+
in.close();
343+
344+
int ret = backend_device.Run();
345+
if (ret)
346+
{
347+
std::cerr << "Job run error!" << std::endl;
348+
exit(-1);
349+
}
350+
351+
std::string output_file = args["output"].as<std::string>();
352+
std::ofstream out(output_file, std::ios::binary);
353+
if (!out.is_open())
354+
{
355+
std::cerr << "Unable to open " << output_file << std::endl;
356+
exit(-1);
357+
}
358+
359+
Formats.at(out_file.format)
360+
.write_file(out, buffers["pispbe-output0"].mem, out_file.width, out_file.height, out_file.stride,
361+
o.image.stride);
362+
out.close();
363+
364+
std::cerr << "Writing " << output_file << " "
365+
<< out_file.width << ":" << out_file.height << ":" << out_file.stride << ":" << out_file.format << std::endl;
366+
367+
return 0;
368+
}

src/examples/meson.build

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: CC0-1.0
2+
# Copyright (C) 2025, Raspberry Pi Ltd
3+
4+
opts_dep = dependency('cxxopts', fallback : ['cxxopts', 'cxxopts_dep'])
5+
6+
libpisp_convert = executable('convert', files('convert.cpp'),
7+
dependencies: [libpisp_dep, opts_dep],
8+
install : false)

0 commit comments

Comments
 (0)