Skip to content

Commit

Permalink
helpers: examples: Add helpers and an example converter utility
Browse files Browse the repository at this point in the history
Signed-off-by: Naushir Patuck <naush@raspberrypi.com>
  • Loading branch information
naushir committed Jan 15, 2025
1 parent 00e568d commit aa60ed5
Show file tree
Hide file tree
Showing 14 changed files with 1,323 additions and 3 deletions.
1 change: 1 addition & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
# Copyright (C) 2023, Raspberry Pi Ltd

option('logging', type : 'feature', value : 'auto')
option('examples', type : 'boolean', value : false)
368 changes: 368 additions & 0 deletions src/examples/convert.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,368 @@

/* SPDX-License-Identifier: BSD-2-Clause */
/*
* Copyright (C) 2025 Raspberry Pi Ltd
*
* convert.cpp - libpisp simple image converter example
*/

#include <algorithm>
#include <array>
#include <assert.h>
#include <fstream>
#include <functional>
#include <iostream>
#include <map>
#include <stdint.h>
#include <string>

#include <linux/media.h>

#include <cxxopts.hpp>

#include "helpers/backend_device.hpp"
#include "helpers/media_device.hpp"

#include "libpisp/backend/backend.hpp"
#include "libpisp/common/logging.hpp"
#include "libpisp/common/utils.hpp"
#include "libpisp/variants/variant.hpp"

void read_plane(uint8_t *mem, std::ifstream &in, unsigned int width, unsigned int height, unsigned int file_stride,
unsigned int buffer_stride)
{
width = std::min(width, file_stride);

for (unsigned int y = 0; y < height; y++)
{
in.read((char *)mem + y * buffer_stride, width);
in.seekg(file_stride - width, std::ios_base::cur);
}
}

void write_plane(std::ofstream &out, uint8_t *mem, unsigned int width, unsigned int height, unsigned int file_stride,
unsigned int buffer_stride)
{
width = std::min(width, file_stride);

for (unsigned int y = 0; y < height; y++)
{
out.write((char *)mem + y * buffer_stride, width);
for (unsigned int i = 0; i < file_stride - width; i++)
out.put(0);
}
}

void read_rgb888(std::array<uint8_t *, 3> &mem, std::ifstream &in, unsigned int width, unsigned int height,
unsigned int file_stride, unsigned int buffer_stride)
{
read_plane((uint8_t *)mem[0], in, width * 3, height, file_stride, buffer_stride);
}

void write_rgb888(std::ofstream &out, std::array<uint8_t *, 3> &mem, unsigned int width, unsigned int height,
unsigned int file_stride, unsigned int buffer_stride)
{
write_plane(out, (uint8_t *)mem[0], width * 3, height, file_stride, buffer_stride);
}

void read_yuv(std::array<uint8_t *, 3> &mem, std::ifstream &in, unsigned int width, unsigned int height,
unsigned int file_stride, unsigned int buffer_stride, unsigned int ss_x, unsigned int ss_y)
{
uint8_t *dst = mem[0];
// Y
read_plane(dst, in, width, height, file_stride, buffer_stride);
// U
dst = mem[1] ? mem[1] : dst + buffer_stride * height;
read_plane(dst, in, width / ss_x, height / ss_y, file_stride / ss_x, buffer_stride / ss_x);
// V
dst = mem[2] ? mem[2] : dst + buffer_stride / ss_x * height / ss_y;
read_plane(dst, in, width / ss_x, height / ss_y, file_stride / ss_x, buffer_stride / ss_x);
}

void write_yuv(std::ofstream &out, std::array<uint8_t *, 3> &mem, unsigned int width, unsigned int height,
unsigned int file_stride, unsigned int buffer_stride, unsigned int ss_x, unsigned int ss_y)
{
uint8_t *src = mem[0];
// Y
write_plane(out, src, width, height, file_stride, buffer_stride);
// U
src = mem[1] ? mem[1] : src + buffer_stride * height;
write_plane(out, src, width / ss_x, height / ss_y, file_stride / ss_x, buffer_stride / ss_x);
// V
src = mem[2] ? mem[2] : src + buffer_stride / ss_x * height / ss_y;
write_plane(out, src, width / ss_x, height / ss_y, file_stride / ss_x, buffer_stride / ss_x);
}

void read_yuv420(std::array<uint8_t *, 3> &mem, std::ifstream &in, unsigned int width, unsigned int height,
unsigned int file_stride, unsigned int buffer_stride)
{
read_yuv(mem, in, width, height, file_stride, buffer_stride, 2, 2);
}

void read_yuv422p(std::array<uint8_t *, 3> &mem, std::ifstream &in, unsigned int width, unsigned int height,
unsigned int file_stride, unsigned int buffer_stride)
{
read_yuv(mem, in, width, height, file_stride, buffer_stride, 2, 1);
}

void read_yuv444p(std::array<uint8_t *, 3> &mem, std::ifstream &in, unsigned int width, unsigned int height,
unsigned int file_stride, unsigned int buffer_stride)
{
read_yuv(mem, in, width, height, file_stride, buffer_stride, 1, 1);
}

void read_yuv422i(std::array<uint8_t *, 3> &mem, std::ifstream &in, unsigned int width, unsigned int height,
unsigned int file_stride, unsigned int buffer_stride)
{
read_plane(mem[0], in, width * 2, height, file_stride, buffer_stride);
}

void write_yuv420(std::ofstream &out, std::array<uint8_t *, 3> &mem, unsigned int width, unsigned int height,
unsigned int file_stride, unsigned int buffer_stride)
{
write_yuv(out, mem, width, height, file_stride, buffer_stride, 2, 2);
}

void write_yuv422p(std::ofstream &out, std::array<uint8_t *, 3> &mem, unsigned int width, unsigned int height,
unsigned int file_stride, unsigned int buffer_stride)
{
write_yuv(out, mem, width, height, file_stride, buffer_stride, 2, 1);
}

void write_yuv444p(std::ofstream &out, std::array<uint8_t *, 3> &mem, unsigned int width, unsigned int height,
unsigned int file_stride, unsigned int buffer_stride)
{
write_yuv(out, mem, width, height, file_stride, buffer_stride, 1, 1);
}

void write_yuv422i(std::ofstream &out, std::array<uint8_t *, 3> &mem, unsigned int width, unsigned int height,
unsigned int file_stride, unsigned int buffer_stride)
{
write_plane(out, mem[0], width * 2, height, file_stride, buffer_stride);
}

struct FormatFuncs
{
std::function<void(std::array<uint8_t *, 3> &, std::ifstream &, unsigned int, unsigned int, unsigned int,
unsigned int)> read_file;
std::function<void(std::ofstream &, std::array<uint8_t *, 3> &, unsigned int, unsigned int, unsigned int,
unsigned int)> write_file;
};

const std::map<std::string, FormatFuncs> Formats =
{
{ "RGB888", { read_rgb888, write_rgb888 } },
{ "YUV420P", { read_yuv420, write_yuv420 } },
{ "YUV422P", { read_yuv422p, write_yuv422p } },
{ "YUV444P", { read_yuv444p, write_yuv444p } },
{ "YUYV", { read_yuv422i, write_yuv422i } },
{ "UYVY", { read_yuv422i, write_yuv422i } },
};

struct Format
{
unsigned int width;
unsigned int height;
unsigned int stride;
std::string format;
};

Format parse_format(const std::string &fmt)
{
Format format;
size_t pos = 0, start = 0;

pos = fmt.find(':', start);
if (pos == std::string::npos)
return {};
format.width = std::stoi(fmt.substr(start, pos - start));
start = pos + 1;

pos = fmt.find(':', start);
if (pos == std::string::npos)
return {};
format.height = std::stoi(fmt.substr(start, pos - start));
start = pos + 1;

pos = fmt.find(':', start);
if (pos == std::string::npos)
return {};
format.stride = std::stoi(fmt.substr(start, pos - start));
start = pos + 1;

format.format = fmt.substr(start);

return format;
}

int main(int argc, char *argv[])
{
libpisp::helpers::MediaDevice devices;

libpisp::logging_init();

cxxopts::Options options(argv[0], "PiSP image converter");

options.add_options()
("input", "Input file", cxxopts::value<std::string>())
("output", "Output file", cxxopts::value<std::string>())
("input-format", "Input format in the form width:height:stride:format\n"
"Bit-depth is assumed to be 8-bit.",cxxopts::value<std::string>()->default_value(""))
("output-format", "Output format in the form width:height:stride:format\n"
"Bit-depth is assumed to be 8-bit.", cxxopts::value<std::string>()->default_value(""))
("f,formats", "List available format strings that can be used")
("l,list", "Enumerate the media device nodes")
("h,help", "Print usage")
;

options.parse_positional({ "input", "output" });
options.positional_help("<input file> <output file>");
options.set_width(120);

auto args = options.parse(argc, argv);

if (args.count("help"))
{
std::cerr << options.help() << std::endl;
exit(0);
}
else if (args.count("list"))
{
std::cerr << devices.List() << std::endl;
exit(0);
}
else if (args.count("formats"))
{
for (const auto &f : Formats)
std::cerr << f.first << " ";
std::cerr << std::endl;
exit(0);
}

std::string media_dev = devices.Acquire();
if (media_dev.empty())
{
std::cerr << "Unable to acquire any pisp_be device!" << std::endl;
exit(-1);
}

libpisp::helpers::BackendDevice backend_device { media_dev };
std::cerr << "Acquired device " << media_dev << std::endl;

auto in_file = parse_format(args["input-format"].as<std::string>());
if (!Formats.count(in_file.format))
{
std::cerr << "Invalid input-format specified" << std::endl;
exit(-1);
}

auto out_file = parse_format(args["output-format"].as<std::string>());
if (!Formats.count(out_file.format))
{
std::cerr << "Invalid output-format specified" << std::endl;
exit(-1);
}

const std::vector<libpisp::PiSPVariant> &variants = libpisp::get_variants();
const media_device_info info = devices.DeviceInfo(media_dev);
auto variant = std::find_if(variants.begin(), variants.end(),
[&info](const auto &v) { return v.BackEndVersion() == info.hw_revision; });
if (variant == variants.end())
{
std::cerr << "Backend hardware cound not be identified: " << info.hw_revision << std::endl;
exit(-1);
}

libpisp::BackEnd be(libpisp::BackEnd::Config({}), *variant);

pisp_be_global_config global;
be.GetGlobal(global);
global.bayer_enables = 0;
global.rgb_enables = PISP_BE_RGB_ENABLE_INPUT + PISP_BE_RGB_ENABLE_OUTPUT0;

pisp_image_format_config i = {};
i.width = in_file.width;
i.height = in_file.height;
i.format = libpisp::get_pisp_image_format(in_file.format);
assert(i.format);
libpisp::compute_optimal_stride(i);
be.SetInputFormat(i);

pisp_be_output_format_config o = {};
o.image.width = out_file.width;
o.image.height = out_file.height;
o.image.format = libpisp::get_pisp_image_format(out_file.format);
assert(o.image.format);
libpisp::compute_optimal_stride(o.image, true);
be.SetOutputFormat(0, o);

if (!out_file.stride)
out_file.stride = o.image.stride;

if (in_file.format != "RGB888")
{
pisp_be_ccm_config csc;
be.InitialiseYcbcrInverse(csc, "jpeg");
be.SetCcm(csc);
global.rgb_enables += PISP_BE_RGB_ENABLE_CCM;
}

if (out_file.format != "RGB888")
{
pisp_be_ccm_config csc;
be.InitialiseYcbcr(csc, "jpeg");
be.SetCsc(0, csc);
global.rgb_enables += PISP_BE_RGB_ENABLE_CSC0;
}

be.SetGlobal(global);
be.SetCrop(0, { 0, 0, i.width, i.height });
be.SetSmartResize(0, { o.image.width, o.image.height });

pisp_be_tiles_config config = {};
be.Prepare(&config);

backend_device.Setup(config);
auto buffers = backend_device.GetBuffers();

std::string input_filename = args["input"].as<std::string>();
std::ifstream in(input_filename, std::ios::binary);
if (!in.is_open())
{
std::cerr << "Unable to open " << input_filename << std::endl;
exit(-1);
}

std::cerr << "Reading " << input_filename << " "
<< in_file.width << ":" << in_file.height << ":" << in_file.stride << ":" << in_file.format << std::endl;

Formats.at(in_file.format)
.read_file(buffers["pispbe-input"].mem, in, in_file.width, in_file.height, in_file.stride,
i.stride);
in.close();

int ret = backend_device.Run();
if (ret)
{
std::cerr << "Job run error!" << std::endl;
exit(-1);
}

std::string output_file = args["output"].as<std::string>();
std::ofstream out(output_file, std::ios::binary);
if (!out.is_open())
{
std::cerr << "Unable to open " << output_file << std::endl;
exit(-1);
}

Formats.at(out_file.format)
.write_file(out, buffers["pispbe-output0"].mem, out_file.width, out_file.height, out_file.stride,
o.image.stride);
out.close();

std::cerr << "Writing " << output_file << " "
<< out_file.width << ":" << out_file.height << ":" << out_file.stride << ":" << out_file.format << std::endl;

return 0;
}
8 changes: 8 additions & 0 deletions src/examples/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: CC0-1.0
# Copyright (C) 2025, Raspberry Pi Ltd

opts_dep = dependency('cxxopts', fallback : ['cxxopts', 'cxxopts_dep'])

libpisp_convert = executable('convert', files('convert.cpp'),
dependencies: [libpisp_dep, opts_dep],
install : false)
Loading

0 comments on commit aa60ed5

Please sign in to comment.