diff --git a/.github/workflows/pisp-verification.test.yaml b/.github/workflows/pisp-verification.test.yaml index 2894365..99374ea 100644 --- a/.github/workflows/pisp-verification.test.yaml +++ b/.github/workflows/pisp-verification.test.yaml @@ -26,7 +26,7 @@ jobs: clean: true - name: Configure meson - run: meson setup build -Dbuildtype=debug + run: meson setup build -Dbuildtype=debug -Dexamples=true timeout-minutes: 5 - name: Build @@ -42,3 +42,7 @@ jobs: - name: Run verification tests run: LD_LIBRARY_PATH=${{github.workspace}}/build/src LIBPISP_BE_CONFIG_FILE="${{github.workspace}}/src/libpisp/backend/backend_default_config.json" ${{env.BE_TEST_DIR}}/run_be_tests.py --hw --logall --test ${{env.BE_TEST_DIR}}/be_test ${{env.TESTS_DIR}}/back_end timeout-minutes: 20 + + - name: Run convert tests + run: LD_LIBRARY_PATH=${{github.workspace}}/build/src LIBPISP_BE_CONFIG_FILE="${{github.workspace}}/src/libpisp/backend/backend_default_config.json" python3 ${{github.workspace}}/utils/test_convert.py ${{github.workspace}}/build/src/examples/convert --out /tmp/ --in $HOME/libpisp_conv/ --ref ~/libpisp_conv/ref/ + timeout-minutes: 10 diff --git a/src/examples/convert.cpp b/src/examples/convert.cpp index b732e0d..a42cb67 100644 --- a/src/examples/convert.cpp +++ b/src/examples/convert.cpp @@ -364,7 +364,7 @@ int main(int argc, char *argv[]) be.Prepare(&config); backend_device.Setup(config); - auto buffers = backend_device.GetBuffers(); + auto buffers = backend_device.AcquireBuffers(); std::string input_filename = args["input"].as(); std::ifstream in(input_filename, std::ios::binary); @@ -382,7 +382,7 @@ int main(int argc, char *argv[]) i.stride); in.close(); - int ret = backend_device.Run(); + int ret = backend_device.Run(buffers); if (ret) { std::cerr << "Job run error!" << std::endl; @@ -402,6 +402,8 @@ int main(int argc, char *argv[]) o.image.stride); out.close(); + backend_device.ReleaseBuffer(buffers); + std::cerr << "Writing " << output_file << " " << out_file.width << ":" << out_file.height << ":" << out_file.stride << ":" << out_file.format << std::endl; diff --git a/src/helpers/backend_device.cpp b/src/helpers/backend_device.cpp index 4132cbf..5647d77 100644 --- a/src/helpers/backend_device.cpp +++ b/src/helpers/backend_device.cpp @@ -8,8 +8,6 @@ #include #include -#include "libpisp/common/utils.hpp" - #include "backend_device.hpp" using namespace libpisp::helpers; @@ -24,7 +22,7 @@ BackendDevice::BackendDevice(const std::string &device) // Allocate a config buffer to persist. nodes_.at("pispbe-config").RequestBuffers(1); nodes_.at("pispbe-config").StreamOn(); - config_buffer_ = nodes_.at("pispbe-config").GetBuffer().value(); + config_buffer_ = nodes_.at("pispbe-config").AcquireBuffer().value(); } BackendDevice::~BackendDevice() @@ -32,57 +30,101 @@ BackendDevice::~BackendDevice() nodes_.at("pispbe-config").StreamOff(); } -void BackendDevice::Setup(const pisp_be_tiles_config &config) +void BackendDevice::Setup(const pisp_be_tiles_config &config, unsigned int buffer_count, bool use_opaque_format) { nodes_enabled_.clear(); - if (config.config.global.rgb_enables & PISP_BE_RGB_ENABLE_INPUT) + if ((config.config.global.rgb_enables & PISP_BE_RGB_ENABLE_INPUT) || + (config.config.global.bayer_enables & PISP_BE_BAYER_ENABLE_INPUT)) { - const pisp_image_format_config &f = config.config.input_format; - nodes_.at("pispbe-input").SetFormat(f.width, f.height, f.stride, f.stride2, - libpisp::get_pisp_image_format(f.format)); + nodes_.at("pispbe-input").SetFormat(config.config.input_format, use_opaque_format); // Release old/allocate a single buffer. - nodes_.at("pispbe-input").ReleaseBuffers(); - nodes_.at("pispbe-input").RequestBuffers(1); + nodes_.at("pispbe-input").ReturnBuffers(); + nodes_.at("pispbe-input").RequestBuffers(buffer_count); nodes_enabled_.emplace("pispbe-input"); - buffers_["pispbe-input"] = nodes_.at("pispbe-input").GetBuffer().value(); } if (config.config.global.rgb_enables & PISP_BE_RGB_ENABLE_OUTPUT0) { - const pisp_image_format_config &f = config.config.output_format[0].image; - nodes_.at("pispbe-output0").SetFormat(f.width, f.height, f.stride, f.stride2, - libpisp::get_pisp_image_format(f.format)); + nodes_.at("pispbe-output0").SetFormat(config.config.output_format[0].image, use_opaque_format); // Release old/allocate a single buffer. - nodes_.at("pispbe-output0").ReleaseBuffers(); - nodes_.at("pispbe-output0").RequestBuffers(1); + nodes_.at("pispbe-output0").ReturnBuffers(); + nodes_.at("pispbe-output0").RequestBuffers(buffer_count); nodes_enabled_.emplace("pispbe-output0"); - buffers_["pispbe-output0"] = nodes_.at("pispbe-output0").GetBuffer().value(); } if (config.config.global.rgb_enables & PISP_BE_RGB_ENABLE_OUTPUT1) { - const pisp_image_format_config &f = config.config.output_format[1].image; - nodes_.at("pispbe-output1").SetFormat(f.width, f.height, f.stride, f.stride2, - libpisp::get_pisp_image_format(f.format)); + nodes_.at("pispbe-output1").SetFormat(config.config.output_format[1].image, use_opaque_format); // Release old/allocate a single buffer. - nodes_.at("pispbe-output1").ReleaseBuffers(); - nodes_.at("pispbe-output1").RequestBuffers(1); + nodes_.at("pispbe-output1").ReturnBuffers(); + nodes_.at("pispbe-output1").RequestBuffers(buffer_count); nodes_enabled_.emplace("pispbe-output1"); - buffers_["pispbe-output1"] = nodes_.at("pispbe-output1").GetBuffer().value(); + } + + if (config.config.global.bayer_enables & PISP_BE_BAYER_ENABLE_TDN_INPUT) + { + nodes_.at("pispbe-tdn_input").SetFormat(config.config.tdn_input_format, use_opaque_format); + // Release old/allocate a single buffer. + nodes_.at("pispbe-tdn_input").ReturnBuffers(); + nodes_.at("pispbe-tdn_input").RequestBuffers(buffer_count); + nodes_enabled_.emplace("pispbe-tdn_input"); + } + + if (config.config.global.bayer_enables & PISP_BE_BAYER_ENABLE_TDN_OUTPUT) + { + nodes_.at("pispbe-tdn_output").SetFormat(config.config.tdn_output_format, use_opaque_format); + // Release old/allocate a single buffer. + nodes_.at("pispbe-tdn_output").ReturnBuffers(); + nodes_.at("pispbe-tdn_output").RequestBuffers(buffer_count); + nodes_enabled_.emplace("pispbe-tdn_output"); + } + + if (config.config.global.bayer_enables & PISP_BE_BAYER_ENABLE_STITCH_INPUT) + { + nodes_.at("pispbe-stitch_input").SetFormat(config.config.stitch_input_format, use_opaque_format); + // Release old/allocate a single buffer. + nodes_.at("pispbe-stitch_input").ReturnBuffers(); + nodes_.at("pispbe-stitch_input").RequestBuffers(buffer_count); + nodes_enabled_.emplace("pispbe-stitch_input"); + } + + if (config.config.global.bayer_enables & PISP_BE_BAYER_ENABLE_STITCH_OUTPUT) + { + nodes_.at("pispbe-stitch_output").SetFormat(config.config.stitch_output_format, use_opaque_format); + // Release old/allocate a single buffer. + nodes_.at("pispbe-stitch_output").ReturnBuffers(); + nodes_.at("pispbe-stitch_output").RequestBuffers(buffer_count); + nodes_enabled_.emplace("pispbe-stitch_output"); } std::memcpy(reinterpret_cast(config_buffer_.mem[0]), &config, sizeof(config)); } -int BackendDevice::Run() +std::map BackendDevice::AcquireBuffers() +{ + std::map buffers; + + for (auto const &n : nodes_enabled_) + buffers[n] = nodes_.at(n).AcquireBuffer().value(); + + return buffers; +} + +void BackendDevice::ReleaseBuffer(const std::map &buffers) +{ + for (auto const &[n, b] : buffers) + nodes_.at(n).ReleaseBuffer(b); +} + +int BackendDevice::Run(const std::map &buffers) { int ret = 0; for (auto const &n : nodes_enabled_) { nodes_.at(n).StreamOn(); - if (nodes_.at(n).QueueBuffer(buffers_.at(n).buffer.index)) + if (nodes_.at(n).QueueBuffer(buffers.at(n).buffer.index)) ret = -1; } diff --git a/src/helpers/backend_device.hpp b/src/helpers/backend_device.hpp index 9995b12..01c9d3e 100644 --- a/src/helpers/backend_device.hpp +++ b/src/helpers/backend_device.hpp @@ -22,17 +22,24 @@ class BackendDevice BackendDevice(const std::string &device); ~BackendDevice(); - void Setup(const pisp_be_tiles_config &config); - int Run(); + void Setup(const pisp_be_tiles_config &config, unsigned int buffer_count = 1, bool use_opaque_format = false); + int Run(const std::map &buffers); bool Valid() const { return valid_; } - const std::map &GetBuffers() const + V4l2Device &Node(const std::string &node) { - return buffers_; + return nodes_.at(node); + } + + std::map AcquireBuffers(); + void ReleaseBuffer(const std::map &buffers); + V4l2Device::Buffer &ConfigBuffer() + { + return config_buffer_; } private: @@ -41,7 +48,6 @@ class BackendDevice MediaDevice devices_; std::unordered_set nodes_enabled_; V4l2Device::Buffer config_buffer_; - std::map buffers_; }; } // namespace libpisp diff --git a/src/helpers/v4l2_device.cpp b/src/helpers/v4l2_device.cpp index d3f5489..34d4d08 100644 --- a/src/helpers/v4l2_device.cpp +++ b/src/helpers/v4l2_device.cpp @@ -17,10 +17,14 @@ #include #include +#include "libpisp/common/utils.hpp" + #include "v4l2_device.hpp" using namespace libpisp::helpers; +namespace { + struct FormatInfo { unsigned int v4l2_pixfmt; @@ -46,6 +50,8 @@ static FormatInfo get_v4l2_format(const std::string &format) return it->second; } +} // namespace + V4l2Device::V4l2Device(const std::string &device) : fd_(device, O_RDWR | O_NONBLOCK | O_CLOEXEC), num_memory_planes_(1) { @@ -67,7 +73,7 @@ V4l2Device::V4l2Device(const std::string &device) V4l2Device::~V4l2Device() { - ReleaseBuffers(); + ReturnBuffers(); Close(); } @@ -75,7 +81,7 @@ int V4l2Device::RequestBuffers(unsigned int count) { int ret; - ReleaseBuffers(); + ReturnBuffers(); v4l2_requestbuffers req_bufs {}; @@ -127,7 +133,7 @@ int V4l2Device::RequestBuffers(unsigned int count) return v4l2_buffers_.size(); } -void V4l2Device::ReleaseBuffers() +void V4l2Device::ReturnBuffers() { v4l2_requestbuffers req_bufs {}; @@ -148,7 +154,7 @@ void V4l2Device::ReleaseBuffers() v4l2_buffers_.clear(); } -std::optional V4l2Device::GetBuffer() +std::optional V4l2Device::AcquireBuffer() { if (available_buffers_.empty()) return {}; @@ -158,13 +164,18 @@ std::optional V4l2Device::GetBuffer() return findBuffer(index); } +void V4l2Device::ReleaseBuffer(const Buffer &buffer) +{ + available_buffers_.push(buffer.buffer.index); +} + int V4l2Device::QueueBuffer(unsigned int index) { std::optional buf = findBuffer(index); if (!buf) return -1; - v4l2_plane planes[VIDEO_MAX_PLANES]; + v4l2_plane planes[VIDEO_MAX_PLANES] = {}; if (!isMeta()) { buf->buffer.m.planes = planes; @@ -216,31 +227,55 @@ int V4l2Device::DequeueBuffer(unsigned int timeout_ms) if (ret) return -1; - available_buffers_.push(buf.index); return buf.index; } -void V4l2Device::SetFormat(unsigned int width, unsigned int height, unsigned int stride, unsigned int stride2, - const std::string &format) +void V4l2Device::SetFormat(const pisp_image_format_config &format, bool use_opaque_format) { struct v4l2_format f = {}; - FormatInfo info = get_v4l2_format(format); - - assert(info.v4l2_pixfmt); + FormatInfo info = get_v4l2_format(libpisp::get_pisp_image_format(format.format)); num_memory_planes_ = info.num_memory_planes; f.type = buf_type_; - f.fmt.pix_mp.width = width; - f.fmt.pix_mp.height = height; + f.fmt.pix_mp.width = format.width; + f.fmt.pix_mp.height = format.height; f.fmt.pix_mp.pixelformat = info.v4l2_pixfmt; f.fmt.pix_mp.field = V4L2_FIELD_NONE; f.fmt.pix_mp.num_planes = num_memory_planes_; - for (unsigned int p = 0; p < num_memory_planes_; p++) + unsigned int num_image_planes = libpisp::num_planes((pisp_image_format)format.format); + + if (use_opaque_format || info.v4l2_pixfmt == 0) + { + // This format is not specified by V4L2, we use an opaque buffer buffer as a workaround. + // Size the dimensions down so the kernel drive does not attempt to resize it. + f.fmt.pix_mp.width = 16; + f.fmt.pix_mp.height = 16; + f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUV444M; + num_memory_planes_ = 3; + f.fmt.pix_mp.plane_fmt[0].bytesperline = format.stride; + + f.fmt.pix_mp.plane_fmt[0].sizeimage = 0; + for (unsigned int i = 0; i < 3; i++) + f.fmt.pix_mp.plane_fmt[0].sizeimage += libpisp::get_plane_size(format, i); + + f.fmt.pix_mp.plane_fmt[1].sizeimage = f.fmt.pix_mp.plane_fmt[2].sizeimage = f.fmt.pix_mp.plane_fmt[0].sizeimage; + f.fmt.pix_mp.plane_fmt[1].bytesperline = f.fmt.pix_mp.plane_fmt[2].bytesperline = format.stride2; + } + else { - f.fmt.pix_mp.plane_fmt[p].bytesperline = p == 0 ? stride : stride2; - f.fmt.pix_mp.plane_fmt[p].sizeimage = 0; + unsigned int p = 0; + for (; p < num_memory_planes_; p++) + { + const unsigned int stride = p == 0 ? format.stride : format.stride2; + // Wallpaper stride is not something the V4L2 kernel knows about! + f.fmt.pix_mp.plane_fmt[p].bytesperline = stride; + f.fmt.pix_mp.plane_fmt[p].sizeimage = libpisp::get_plane_size(format, p); + } + + for (; p < num_image_planes; p++) + f.fmt.pix_mp.plane_fmt[num_memory_planes_ - 1].sizeimage += libpisp::get_plane_size(format, p); } int ret = ioctl(fd_.Get(), VIDIOC_S_FMT, &f); diff --git a/src/helpers/v4l2_device.hpp b/src/helpers/v4l2_device.hpp index a5a552b..fe53bbf 100644 --- a/src/helpers/v4l2_device.hpp +++ b/src/helpers/v4l2_device.hpp @@ -15,6 +15,8 @@ #include +#include "libpisp/backend/pisp_be_config.h" + #include "device_fd.hpp" namespace libpisp::helpers @@ -55,7 +57,7 @@ class V4l2Device } Buffer(const v4l2_buffer& buf) - : buffer(buf), size({}), mem({}) + : buffer(buf), size({}), mem({}) { } @@ -65,15 +67,19 @@ class V4l2Device }; int RequestBuffers(unsigned int count = 1); - void ReleaseBuffers(); + void ReturnBuffers(); - std::optional GetBuffer(); + std::optional AcquireBuffer(); + void ReleaseBuffer(const Buffer &buffer); + const std::vector &Buffers() const + { + return v4l2_buffers_; + }; int QueueBuffer(unsigned int index); int DequeueBuffer(unsigned int timeout_ms = 500); - void SetFormat(unsigned int width, unsigned int height, unsigned int stride, unsigned int stride2, - const std::string &format); + void SetFormat(const pisp_image_format_config &format, bool use_opaque_format = false); void StreamOn(); void StreamOff(); diff --git a/src/libpisp/backend/backend.cpp b/src/libpisp/backend/backend.cpp index 26fdde5..02edb28 100644 --- a/src/libpisp/backend/backend.cpp +++ b/src/libpisp/backend/backend.cpp @@ -24,6 +24,8 @@ BackEnd::BackEnd(Config const &config, PiSPVariant const &variant) PISP_LOG(fatal, "Configured max tile width " << config_.max_tile_width << " exceeds " << max_tile_width); smart_resize_dirty_ = 0; + memset(&be_config_, 0, sizeof(be_config_)); + memset(&be_config_extra_, 0, sizeof(be_config_extra_)); const char *env = std::getenv("LIBPISP_BE_CONFIG_FILE"); initialiseDefaultConfig(env ? std::string(env) : config.defaults_file); @@ -68,6 +70,11 @@ void BackEnd::SetInputFormat(pisp_image_format_config const &input_format) retile_ = true; } +void BackEnd::GetInputFormat(pisp_image_format_config &input_format) const +{ + input_format = be_config_.input_format; +} + void BackEnd::SetDecompress(pisp_decompress_config const &decompress) { be_config_.decompress = decompress; @@ -95,6 +102,11 @@ void BackEnd::SetTdnInputFormat(pisp_image_format_config const &tdn_input_format finalise_tiling_ = true; } +void BackEnd::GetTdnInputFormat(pisp_image_format_config &tdn_input_format) const +{ + tdn_input_format = be_config_.tdn_input_format; +} + void BackEnd::SetTdnDecompress(pisp_decompress_config const &tdn_decompress) { be_config_.tdn_decompress = tdn_decompress; @@ -206,7 +218,7 @@ void BackEnd::SetWbg(pisp_wbg_config const &wbg) be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_WBG; } -void BackEnd::GetWbg(pisp_wbg_config &wbg) +void BackEnd::GetWbg(pisp_wbg_config &wbg) const { wbg = be_config_.wbg; } @@ -235,7 +247,7 @@ void BackEnd::SetDebin(pisp_be_debin_config const &debin) be_config_extra_.dirty_flags_bayer |= PISP_BE_BAYER_ENABLE_DEBIN; } -void BackEnd::GetDebin(pisp_be_debin_config &debin) +void BackEnd::GetDebin(pisp_be_debin_config &debin) const { debin = be_config_.debin; } @@ -279,7 +291,7 @@ void BackEnd::SetYcbcr(pisp_be_ccm_config const &ycbcr) be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_YCBCR; } -void BackEnd::GetYcbcr(pisp_be_ccm_config &ycbcr) +void BackEnd::GetYcbcr(pisp_be_ccm_config &ycbcr) const { ycbcr = be_config_.ycbcr; } @@ -304,7 +316,7 @@ void BackEnd::SetSharpen(pisp_be_sharpen_config const &sharpen) be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_SHARPEN; } -void BackEnd::GetSharpen(pisp_be_sharpen_config &sharpen) +void BackEnd::GetSharpen(pisp_be_sharpen_config &sharpen) const { sharpen = be_config_.sharpen; } @@ -329,7 +341,7 @@ void BackEnd::SetGamma(pisp_be_gamma_config const &gamma) be_config_extra_.dirty_flags_rgb |= PISP_BE_RGB_ENABLE_GAMMA; } -void BackEnd::GetGamma(pisp_be_gamma_config &gamma) +void BackEnd::GetGamma(pisp_be_gamma_config &gamma) const { gamma = be_config_.gamma; } @@ -356,7 +368,7 @@ void BackEnd::SetCsc(unsigned int i, pisp_be_ccm_config const &csc) be_config_extra_.dirty_flags_bayer |= PISP_BE_RGB_ENABLE_CSC(i); } -void BackEnd::GetCsc(unsigned int i, pisp_be_ccm_config &csc) +void BackEnd::GetCsc(unsigned int i, pisp_be_ccm_config &csc) const { csc = be_config_.csc[i]; } diff --git a/src/libpisp/backend/backend.hpp b/src/libpisp/backend/backend.hpp index 0d572d3..6b1d5e0 100644 --- a/src/libpisp/backend/backend.hpp +++ b/src/libpisp/backend/backend.hpp @@ -68,59 +68,87 @@ class BackEnd final void SetGlobal(pisp_be_global_config const &global); void GetGlobal(pisp_be_global_config &global) const; void SetInputFormat(pisp_image_format_config const &input_format); + void GetInputFormat(pisp_image_format_config &input_format) const; void SetDecompress(pisp_decompress_config const &decompress); + void GetDecompress(pisp_decompress_config &decompress) const; void SetDpc(pisp_be_dpc_config const &dpc); + void GetDpc(pisp_be_dpc_config &dpc) const; void SetGeq(pisp_be_geq_config const &geq); + void GetGeq(pisp_be_geq_config &geq) const; void SetTdnInputFormat(pisp_image_format_config const &tdn_input_format); + void GetTdnInputFormat(pisp_image_format_config &tdn_input_format) const; void SetTdnDecompress(pisp_decompress_config const &tdn_decompress); + void GetTdnDecompress(pisp_decompress_config &tdn_decompress) const; void SetTdn(pisp_be_tdn_config const &tdn); void GetTdn(pisp_be_tdn_config &tdn) const; void SetTdnCompress(pisp_compress_config const &tdn_compress); + void GetTdnCompress(pisp_compress_config &tdn_compress) const; void SetTdnOutputFormat(pisp_image_format_config const &tdn_output_format); void GetTdnOutputFormat(pisp_image_format_config &tdn_output_format) const; void SetSdn(pisp_be_sdn_config const &sdn); + void GetSdn(pisp_be_sdn_config &sdn) const; void SetBlc(pisp_bla_config const &blc); void GetBlc(pisp_bla_config &blc) const; void SetStitchInputFormat(pisp_image_format_config const &stitch_input_format); void GetStitchInputFormat(pisp_image_format_config &stitch_input_format) const; void SetStitchDecompress(pisp_decompress_config const &stitch_decompress); + void GetStitchDecompress(pisp_decompress_config &stitch_decompress) const; void SetStitch(pisp_be_stitch_config const &stitch); + void GetStitch(pisp_be_stitch_config &stitch) const; void SetStitchCompress(pisp_compress_config const &stitch_compress); + void GetStitchCompress(pisp_compress_config &stitch_compress) const; void SetStitchOutputFormat(pisp_image_format_config const &stitch_output_format); void GetStitchOutputFormat(pisp_image_format_config &stitch_output_format) const; void SetWbg(pisp_wbg_config const &wbg); - void GetWbg(pisp_wbg_config &wbg); + void GetWbg(pisp_wbg_config &wbg) const; void SetCdn(pisp_be_cdn_config const &cdn); + void GetCdn(pisp_be_cdn_config &cdn) const; void SetLsc(pisp_be_lsc_config const &lsc, pisp_be_lsc_extra lsc_extra = { 0, 0 }); + void GetLsc(pisp_be_lsc_config &lsc, pisp_be_lsc_extra &lsc_extra) const; void SetCac(pisp_be_cac_config const &cac, pisp_be_cac_extra cac_extra = { 0, 0 }); + void GetCac(pisp_be_cac_config &cac, pisp_be_cac_extra &cac_extra) const; void SetDebin(pisp_be_debin_config const &debin); - void GetDebin(pisp_be_debin_config &debin); + void GetDebin(pisp_be_debin_config &debin) const; void SetTonemap(pisp_be_tonemap_config const &tonemap); + void GetTonemap(pisp_be_tonemap_config &tonemap) const; void SetDemosaic(pisp_be_demosaic_config const &demosaic); void GetDemosaic(pisp_be_demosaic_config &demosaic) const; void SetCcm(pisp_be_ccm_config const &ccm); + void GetCcm(pisp_be_ccm_config &ccm) const; void SetSatControl(pisp_be_sat_control_config const &sat_control); + void GetSatControl(pisp_be_sat_control_config &sat_control) const; void SetYcbcr(pisp_be_ccm_config const &ycbcr); - void GetYcbcr(pisp_be_ccm_config &ccm); + void GetYcbcr(pisp_be_ccm_config &ccm) const; void SetFalseColour(pisp_be_false_colour_config const &false_colour); + void GetFalseColour(pisp_be_false_colour_config &false_colour) const; void SetSharpen(pisp_be_sharpen_config const &sharpen); - void GetSharpen(pisp_be_sharpen_config &sharpen); + void GetSharpen(pisp_be_sharpen_config &sharpen) const; void SetShFcCombine(pisp_be_sh_fc_combine_config const &sh_fc_combine); + void GetShFcCombine(pisp_be_sh_fc_combine_config &sh_fc_combine) const; void SetYcbcrInverse(pisp_be_ccm_config const &ycbcr_inverse); + void GetYcbcrInverse(pisp_be_ccm_config &ycbcr_inverse) const; void SetGamma(pisp_be_gamma_config const &gamma); - void GetGamma(pisp_be_gamma_config &gamma); + void GetGamma(pisp_be_gamma_config &gamma) const; void SetCrop(pisp_be_crop_config const &crop); + void GetCrop(pisp_be_crop_config &crop) const; void SetCrop(unsigned int i, pisp_be_crop_config const &crop); + void GetCrop(unsigned int i, pisp_be_crop_config &crop) const; void SetCsc(unsigned int i, pisp_be_ccm_config const &csc); - void GetCsc(unsigned int i, pisp_be_ccm_config &csc); + void GetCsc(unsigned int i, pisp_be_ccm_config &csc) const; void SetOutputFormat(unsigned int i, pisp_be_output_format_config const &output_format); void GetOutputFormat(unsigned int i, pisp_be_output_format_config &output_format) const; void SetResample(unsigned int i, pisp_be_resample_config const &resample, pisp_be_resample_extra const &resample_extra); + void GetResample(unsigned int i, pisp_be_resample_config &resample, + pisp_be_resample_extra &resample_extra) const; void SetResample(unsigned int i, pisp_be_resample_extra const &resample_extra); + void GetResample(unsigned int i, pisp_be_resample_extra &resample_extra) const; void SetDownscale(unsigned int i, pisp_be_downscale_config const &downscale, pisp_be_downscale_extra const &downscale_extra); + void GetDownscale(unsigned int i, pisp_be_downscale_config &downscale, + pisp_be_downscale_extra &downscale_extra) const; void SetDownscale(unsigned int i, pisp_be_downscale_extra const &downscale_extra); + void GetDownscale(unsigned int i, pisp_be_downscale_extra &downscale_extra) const; void InitialiseYcbcr(pisp_be_ccm_config &ycbcr, const std::string &colour_space); void InitialiseYcbcrInverse(pisp_be_ccm_config &ycbcr_inverse, const std::string &colour_space); @@ -140,6 +168,16 @@ class BackEnd final std::string GetJsonConfig(pisp_be_tiles_config *config); void SetJsonConfig(const std::string &json_config); + void SetMaxTileWidth(unsigned int width) + { + config_.max_tile_width = width; + } + + void SetMaxStripeHeight(unsigned int height) + { + config_.max_stripe_height = height; + } + void lock() { mutex_.lock(); diff --git a/src/libpisp/common/pisp_common.h b/src/libpisp/common/pisp_common.h index 2bbff9f..c41dd0e 100644 --- a/src/libpisp/common/pisp_common.h +++ b/src/libpisp/common/pisp_common.h @@ -74,6 +74,8 @@ enum pisp_image_format { PISP_IMAGE_FORMAT_BPP_32 = 0x00100000, + PISP_IMAGE_FORMAT_X_VALUE = 0x00200000, + PISP_IMAGE_FORMAT_UNCOMPRESSED = 0x00000000, PISP_IMAGE_FORMAT_COMPRESSION_MODE_1 = 0x01000000, PISP_IMAGE_FORMAT_COMPRESSION_MODE_2 = 0x02000000, @@ -140,6 +142,8 @@ enum pisp_image_format { #define PISP_IMAGE_FORMAT_HOG(fmt) \ ((fmt) & \ (PISP_IMAGE_FORMAT_HOG_SIGNED | PISP_IMAGE_FORMAT_HOG_UNSIGNED)) +#define PISP_IMAGE_FORMAT_X_VALUE(fmt) \ + ((fmt) & PISP_IMAGE_FORMAT_X_VALUE) #define PISP_WALLPAPER_WIDTH 128 /* in bytes */ diff --git a/src/libpisp/common/pisp_utils.cpp b/src/libpisp/common/pisp_utils.cpp index e078478..9ec594e 100644 --- a/src/libpisp/common/pisp_utils.cpp +++ b/src/libpisp/common/pisp_utils.cpp @@ -242,7 +242,7 @@ static const std::map &formats_table() { "RGB888", PISP_IMAGE_FORMAT_THREE_CHANNEL }, { "RGBX8888", PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPP_32 }, { "RGB161616", PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPS_16 }, - { "BAYER", PISP_IMAGE_FORMAT_BPS_16 + PISP_IMAGE_FORMAT_UNCOMPRESSED }, + { "BAYER16", PISP_IMAGE_FORMAT_BPS_16 + PISP_IMAGE_FORMAT_UNCOMPRESSED }, { "PISP_COMP1", PISP_IMAGE_FORMAT_COMPRESSION_MODE_1 }, { "PISP_COMP2", PISP_IMAGE_FORMAT_COMPRESSION_MODE_2 }, }; @@ -261,6 +261,9 @@ unsigned int get_pisp_image_format(const std::string &format) std::string get_pisp_image_format(uint32_t format) { + // Remove shift from the format assignment, its value does not change format. + format = format & ~PISP_IMAGE_FORMAT_SHIFT_MASK; + const auto &fmts = formats_table(); auto it = std::find_if(fmts.begin(), fmts.end(), [format](const auto &f) { return f.second == format; }); if (it == fmts.end()) diff --git a/src/libpisp/common/utils.hpp b/src/libpisp/common/utils.hpp index 165c34e..2387846 100644 --- a/src/libpisp/common/utils.hpp +++ b/src/libpisp/common/utils.hpp @@ -6,6 +6,7 @@ */ #pragma once +#include #include #include "pisp_common.h" diff --git a/src/libpisp/frontend/frontend.cpp b/src/libpisp/frontend/frontend.cpp index 9f7ed88..51083ba 100644 --- a/src/libpisp/frontend/frontend.cpp +++ b/src/libpisp/frontend/frontend.cpp @@ -276,7 +276,7 @@ void FrontEnd::SetAgcStats(pisp_fe_agc_stats_config const &agc_stats) fe_config_.dirty_flags |= PISP_FE_ENABLE_AGC_STATS; } -void FrontEnd::GetAgcStats(pisp_fe_agc_stats_config &agc_stats) +void FrontEnd::GetAgcStats(pisp_fe_agc_stats_config &agc_stats) const { agc_stats = fe_config_.agc_stats; } @@ -287,7 +287,7 @@ void FrontEnd::SetAwbStats(pisp_fe_awb_stats_config const &awb_stats) fe_config_.dirty_flags |= PISP_FE_ENABLE_AWB_STATS; } -void FrontEnd::GetAwbStats(pisp_fe_awb_stats_config &awb_stats) +void FrontEnd::GetAwbStats(pisp_fe_awb_stats_config &awb_stats) const { awb_stats = fe_config_.awb_stats; } @@ -304,7 +304,7 @@ void FrontEnd::SetCdafStats(pisp_fe_cdaf_stats_config const &cdaf_stats) fe_config_.dirty_flags |= PISP_FE_ENABLE_CDAF_STATS; } -void FrontEnd::GetCdafStats(pisp_fe_cdaf_stats_config &cdaf_stats) +void FrontEnd::GetCdafStats(pisp_fe_cdaf_stats_config &cdaf_stats) const { cdaf_stats = fe_config_.cdaf_stats; } @@ -358,6 +358,97 @@ void FrontEnd::SetOutputBuffer(unsigned int output_num, pisp_fe_output_buffer_co // Assume these always get written. } +void FrontEnd::GetInput(pisp_fe_input_config &input) const +{ + input = fe_config_.input; +} + +void FrontEnd::GetInputBuffer(pisp_fe_input_buffer_config &input_buffer) const +{ + input_buffer = fe_config_.input_buffer; +} + +void FrontEnd::GetDecompress(pisp_decompress_config &decompress) const +{ + decompress = fe_config_.decompress; +} + +void FrontEnd::GetDecompand(pisp_fe_decompand_config &decompand) const +{ + decompand = fe_config_.decompand; +} + +void FrontEnd::GetDpc(pisp_fe_dpc_config &dpc) const +{ + dpc = fe_config_.dpc; +} + +void FrontEnd::GetBla(pisp_bla_config &bla) const +{ + bla = fe_config_.bla; +} + +void FrontEnd::GetStatsCrop(pisp_fe_crop_config &stats_crop) const +{ + stats_crop = fe_config_.stats_crop; +} + +void FrontEnd::GetBlc(pisp_bla_config &blc) const +{ + blc = fe_config_.blc; +} + +void FrontEnd::GetRGBY(pisp_fe_rgby_config &rgby) const +{ + rgby = fe_config_.rgby; +} + +void FrontEnd::GetLsc(pisp_fe_lsc_config &lsc) const +{ + lsc = fe_config_.lsc; +} + +void FrontEnd::GetFloatingStats(pisp_fe_floating_stats_config &floating_stats) const +{ + floating_stats = fe_config_.floating_stats; +} + +void FrontEnd::GetCrop(unsigned int output_num, pisp_fe_crop_config &crop) const +{ + PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); + crop = fe_config_.ch[output_num].crop; +} + +void FrontEnd::GetDownscale(unsigned int output_num, pisp_fe_downscale_config &downscale) const +{ + PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); + downscale = fe_config_.ch[output_num].downscale; +} + +void FrontEnd::GetCompress(unsigned int output_num, pisp_compress_config &compress) const +{ + PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); + compress = fe_config_.ch[output_num].compress; +} + +void FrontEnd::GetOutputFormat(unsigned int output_num, pisp_image_format_config &output_format) const +{ + PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); + output_format = fe_config_.ch[output_num].output.format; +} + +void FrontEnd::GetOutputBuffer(unsigned int output_num, pisp_fe_output_buffer_config &output_buffer) const +{ + PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); + output_buffer = fe_config_.output_buffer[output_num]; +} + +int FrontEnd::GetOutputIntrLines(unsigned int output_num) const +{ + PISP_ASSERT(output_num < variant_.FrontEndNumBranches(0)); + return fe_config_.ch[output_num].output.ilines; +} + void FrontEnd::Prepare(pisp_fe_config *config) { // Only finalise blocks that are dirty *and* enabled. diff --git a/src/libpisp/frontend/frontend.hpp b/src/libpisp/frontend/frontend.hpp index dc8f042..5762023 100644 --- a/src/libpisp/frontend/frontend.hpp +++ b/src/libpisp/frontend/frontend.hpp @@ -32,28 +32,45 @@ class FrontEnd final void SetGlobal(pisp_fe_global_config const &global); void GetGlobal(pisp_fe_global_config &global) const; void SetInput(pisp_fe_input_config const &input); + void GetInput(pisp_fe_input_config &input) const; void SetInputBuffer(pisp_fe_input_buffer_config const &input_buffer); + void GetInputBuffer(pisp_fe_input_buffer_config &input_buffer) const; void SetDecompress(pisp_decompress_config const &decompress); + void GetDecompress(pisp_decompress_config &decompress) const; void SetDecompand(pisp_fe_decompand_config const &decompand); + void GetDecompand(pisp_fe_decompand_config &decompand) const; void SetDpc(pisp_fe_dpc_config const &dpc); + void GetDpc(pisp_fe_dpc_config &dpc) const; void SetBla(pisp_bla_config const &bla); + void GetBla(pisp_bla_config &bla) const; void SetStatsCrop(pisp_fe_crop_config const &stats_crop); + void GetStatsCrop(pisp_fe_crop_config &stats_crop) const; void SetBlc(pisp_bla_config const &blc); + void GetBlc(pisp_bla_config &blc) const; void SetRGBY(pisp_fe_rgby_config const &rgby); + void GetRGBY(pisp_fe_rgby_config &rgby) const; void SetLsc(pisp_fe_lsc_config const &lsc); + void GetLsc(pisp_fe_lsc_config &lsc) const; void SetAgcStats(pisp_fe_agc_stats_config const &agc_stats); - void GetAgcStats(pisp_fe_agc_stats_config &agc_stats); + void GetAgcStats(pisp_fe_agc_stats_config &agc_stats) const; void SetAwbStats(pisp_fe_awb_stats_config const &awb_stats); - void GetAwbStats(pisp_fe_awb_stats_config &awb_stats); + void GetAwbStats(pisp_fe_awb_stats_config &awb_stats) const; void SetFloatingStats(pisp_fe_floating_stats_config const &floating_stats); + void GetFloatingStats(pisp_fe_floating_stats_config &floating_stats) const; void SetCdafStats(pisp_fe_cdaf_stats_config const &cdaf_stats); - void GetCdafStats(pisp_fe_cdaf_stats_config &cdaf_stats); + void GetCdafStats(pisp_fe_cdaf_stats_config &cdaf_stats) const; void SetCrop(unsigned int output_num, pisp_fe_crop_config const &crop); + void GetCrop(unsigned int output_num, pisp_fe_crop_config &crop) const; void SetDownscale(unsigned int output_num, pisp_fe_downscale_config const &downscale); + void GetDownscale(unsigned int output_num, pisp_fe_downscale_config &downscale) const; void SetCompress(unsigned int output_num, pisp_compress_config const &compress); + void GetCompress(unsigned int output_num, pisp_compress_config &compress) const; void SetOutputFormat(unsigned int output_num, pisp_image_format_config const &output_format); + void GetOutputFormat(unsigned int output_num, pisp_image_format_config &output_format) const; void SetOutputBuffer(unsigned int output_num, pisp_fe_output_buffer_config const &output_buffer); + void GetOutputBuffer(unsigned int output_num, pisp_fe_output_buffer_config &output_buffer) const; void SetOutputIntrLines(unsigned int output_num, int lines); + int GetOutputIntrLines(unsigned int output_num) const; void Prepare(pisp_fe_config *config); void lock() diff --git a/utils/test_convert.py b/utils/test_convert.py new file mode 100644 index 0000000..3effbf1 --- /dev/null +++ b/utils/test_convert.py @@ -0,0 +1,239 @@ +#!/usr/bin/python3 + +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2025 Raspberry Pi Ltd +# +# test_convert.py - Test script for libpisp convert utility +# + +import argparse +import os +import subprocess +import sys +import tempfile +import shutil +from pathlib import Path +import hashlib + + +class ConvertTester: + def __init__(self, convert_binary, output_dir=None, input_dir=None, reference_dir=None): + """Initialize the tester with the path to the convert binary.""" + self.convert_binary = convert_binary + self.output_dir = output_dir + self.input_dir = input_dir + self.reference_dir = reference_dir + if not os.path.exists(convert_binary): + raise FileNotFoundError(f"Convert binary not found: {convert_binary}") + + # Test cases: (input_file, output_file, input_format, output_format, reference_file) + self.test_cases = [ + { + "input_file": "conv_yuv420_4056x3040_4056s.yuv", + "output_file": "out_4056x3050_12168s_rgb888.rgb", + "input_format": "4056:3040:4056:YUV420P", + "output_format": "4056:3040:12168:RGB888", + "reference_file": "ref_4056x3050_12168s_rgb888.rgb" + }, + { + "input_file": "conv_800x600_1200s_422_yuyv.yuv", + "output_file": "out_1600x1200_422p.yuv", + "input_format": "800:600:1600:YUYV", + "output_format": "1600:1200:1600:YUV422P", + "reference_file": "ref_1600x1200_422p.yuv" + }, + { + "input_file": "conv_rgb888_800x600_2432s.rgb", + "output_file": "out_4000x3000_4032s.yuv", + "input_format": "800:600:2432:RGB888", + "output_format": "4000:3000:0:YUV444P", + "reference_file": "ref_4000x3000_4032s.yuv" + }, + # Add more test cases here as needed + ] + + def run_convert(self, input_file, output_file, input_format, output_format): + """Run the convert utility with the specified parameters.""" + # Use input directory if specified + if self.input_dir: + input_file = os.path.join(self.input_dir, input_file) + + # Use output directory if specified + if self.output_dir: + output_file = os.path.join(self.output_dir, output_file) + + cmd = [ + self.convert_binary, + input_file, + output_file, + "--input-format", input_format, + "--output-format", output_format + ] + + print(f"Running: {' '.join(cmd)}") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + print("Convert completed successfully") + return True + except subprocess.CalledProcessError as e: + print(f"Convert failed with exit code {e.returncode}") + print(f"stdout: {e.stdout}") + print(f"stderr: {e.stderr}") + return False + + def compare_files(self, file1, file2): + """Compare two files and return True if they are identical.""" + if not os.path.exists(file1): + print(f"Error: File {file1} does not exist") + return False + if not os.path.exists(file2): + print(f"Error: File {file2} does not exist") + return False + + # Compare file sizes first + size1 = os.path.getsize(file1) + size2 = os.path.getsize(file2) + + if size1 != size2: + print(f"Files have different sizes: {size1} vs {size2}") + return False + + # Compare file contents using hash + hash1 = self._file_hash(file1) + hash2 = self._file_hash(file2) + + if hash1 == hash2: + print("Files are identical") + return True + else: + print("Files are different") + return False + + def _file_hash(self, filepath): + """Calculate SHA256 hash of a file.""" + hash_sha256 = hashlib.sha256() + with open(filepath, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_sha256.update(chunk) + return hash_sha256.hexdigest() + + def run_test_case(self, test_case): + """Run a single test case.""" + print(f"\n=== Running test case ===") + print(f"Input file: {test_case['input_file']}") + print(f"Output file: {test_case['output_file']}") + print(f"Input format: {test_case['input_format']}") + print(f"Output format: {test_case['output_format']}") + print(f"Reference file: {test_case['reference_file']}") + + # Check if input file exists + input_file = test_case['input_file'] + if self.input_dir: + input_file = os.path.join(self.input_dir, test_case['input_file']) + + if not os.path.exists(input_file): + print(f"Error: Input file {input_file} does not exist") + return False + + # Run the convert utility + success = self.run_convert( + test_case['input_file'], + test_case['output_file'], + test_case['input_format'], + test_case['output_format'] + ) + + if not success: + return False + + # Compare with reference file if it exists + reference_file = test_case['reference_file'] + if self.reference_dir: + reference_file = os.path.join(self.reference_dir, test_case['reference_file']) + + if os.path.exists(reference_file): + print(f"Comparing output with reference file...") + # Use output directory for the generated output file + output_file = test_case['output_file'] + if self.output_dir: + output_file = os.path.join(self.output_dir, test_case['output_file']) + return self.compare_files(output_file, reference_file) + else: + print(f"Reference file {reference_file} not found, skipping comparison") + return True + + def run_all_tests(self): + """Run all test cases.""" + print(f"Testing convert utility: {self.convert_binary}") + if self.input_dir: + print(f"Input directory: {self.input_dir}") + if self.output_dir: + print(f"Output directory: {self.output_dir}") + if self.reference_dir: + print(f"Reference directory: {self.reference_dir}") + print(f"Number of test cases: {len(self.test_cases)}") + + passed = 0 + failed = 0 + + for i, test_case in enumerate(self.test_cases, 1): + print(f"\n--- Test case {i}/{len(self.test_cases)} ---") + + if self.run_test_case(test_case): + passed += 1 + print("✓ Test PASSED") + else: + failed += 1 + print("✗ Test FAILED") + + print(f"\n=== Test Summary ===") + print(f"Passed: {passed}") + print(f"Failed: {failed}") + print(f"Total: {len(self.test_cases)}") + + return failed == 0 + + +def main(): + parser = argparse.ArgumentParser(description="Test script for libpisp convert utility") + parser.add_argument("convert_binary", help="Path to the convert binary") + parser.add_argument("--test-dir", help="Directory containing test files") + parser.add_argument("--in", dest="input_dir", help="Directory containing input files") + parser.add_argument("--out", help="Directory where output files will be written") + parser.add_argument("--ref", help="Directory containing reference files") + + args = parser.parse_args() + + try: + tester = ConvertTester(args.convert_binary, args.out, args.input_dir, args.ref) + + # Change to test directory if specified + if args.test_dir: + if not os.path.exists(args.test_dir): + print(f"Error: Test directory {args.test_dir} does not exist") + return 1 + os.chdir(args.test_dir) + print(f"Changed to test directory: {args.test_dir}") + + # Create output directory if specified and it doesn't exist + if args.out: + if not os.path.exists(args.out): + os.makedirs(args.out) + print(f"Created output directory: {args.out}") + + # Run all tests + success = tester.run_all_tests() + return 0 if success else 1 + + except FileNotFoundError as e: + print(f"Error: {e}") + return 1 + except Exception as e: + print(f"Unexpected error: {e}") + return 1 + + +if __name__ == "__main__": + sys.exit(main())