Skip to content

Various changes + helper utils #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/libpisp-build-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
run: sudo apt install -y libboost-log-dev libboost-thread-dev

- name: Configure meson
run: CXX=${{matrix.compiler}} meson setup build_${{matrix.compiler}}_${{matrix.build_type}} -Dbuildtype=${{matrix.build_type}}
run: CXX=${{matrix.compiler}} meson setup build_${{matrix.compiler}}_${{matrix.build_type}} -Dbuildtype=${{matrix.build_type}} -Dexamples=true
timeout-minutes: 5

- name: Build
Expand Down
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
Loading