Files
zoneminder/tests/zm_hwaccel_candidates.cpp
T
Isaac Connor d376003495 refactor: extract which hwaccel types a DecoderHWAccelName asks to try
Groundwork for moving decoding out of the camera and into the decoder thread.
The parsing of DecoderHWAccelName sat inline in FfmpegCamera::OpenFfmpeg(),
where nothing could test it, and the ai_server branch has a second copy of the
same logic in its Monitor-side decoder open. Moving the decoder without pulling
this out first would carry the duplication across rather than end it.

No behaviour change: same iteration over the types this build offers, same
comma-separated priority list, same warn-and-skip for a name libavutil does not
know, same order.

Being a free function it can now be tested, which it could not be before. The
cases covered are the ones an operator's setting actually runs into -- a single
name, a priority list, spaces around the commas, a typo among good entries,
empty entries -- asserted against whatever device types the host reports rather
than against a fixed list, so the test travels. This host offers seven, so no
section skipped for want of data.

Confirmed non-vacuous by mutation: dropping TrimSpaces fails one case, and
accepting an unknown name instead of skipping it fails two.

Build clean with no warnings; suite 147 cases / 12531 assertions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Y6FieTwEXuLhhR4e2yiax
2026-10-10 09:32:17 -04:00

103 lines
3.7 KiB
C++

/*
* Which hwaccel device types a DecoderHWAccelName asks to try.
*
* The parsing is what an operator's monitor setting runs into, and it used to
* sit inline in FfmpegCamera::OpenFfmpeg() where no test could reach it. The
* cases that matter are the ones a person actually types: a single name, a
* priority list, spaces around the commas, and a typo in the middle of an
* otherwise good list.
*
* What the host can offer is not fixed, so the assertions are about the shape
* of the answer rather than a particular device being present.
*/
#include "zm_catch2.h"
#include "zm_ffmpeg.h"
#include <algorithm>
namespace {
bool contains(const std::vector<enum AVHWDeviceType> &v, enum AVHWDeviceType t) {
return std::find(v.begin(), v.end(), t) != v.end();
}
// A device type this build actually knows, or NONE if it knows none at all.
enum AVHWDeviceType some_known_type() {
enum AVHWDeviceType t = AV_HWDEVICE_TYPE_NONE;
return av_hwdevice_iterate_types(t);
}
} // namespace
TEST_CASE("hwaccel_candidate_types", "[ffmpeg][hwaccel]") {
SECTION("no hwaccel asked for yields nothing") {
REQUIRE(hwaccel_candidate_types("").empty());
}
SECTION("auto offers everything this build has") {
const auto all = hwaccel_candidate_types("auto");
enum AVHWDeviceType expected = AV_HWDEVICE_TYPE_NONE;
size_t count = 0;
while ((expected = av_hwdevice_iterate_types(expected)) != AV_HWDEVICE_TYPE_NONE) count++;
REQUIRE(all.size() == count);
}
SECTION("a name this build does not have is dropped, not fatal") {
// The warning is the operator's signal; the return is simply empty.
REQUIRE(hwaccel_candidate_types("notahwaccel").empty());
}
SECTION("a typo does not cost the working entries beside it") {
const enum AVHWDeviceType known = some_known_type();
if (known == AV_HWDEVICE_TYPE_NONE) return; // nothing to assert against
const std::string name = av_hwdevice_get_type_name(known);
const auto got = hwaccel_candidate_types("notahwaccel," + name);
REQUIRE(got.size() == 1);
REQUIRE(got.front() == known);
}
SECTION("a list keeps the order it was written in") {
std::vector<enum AVHWDeviceType> known;
enum AVHWDeviceType t = AV_HWDEVICE_TYPE_NONE;
while ((t = av_hwdevice_iterate_types(t)) != AV_HWDEVICE_TYPE_NONE) known.push_back(t);
if (known.size() < 2) return; // cannot show ordering with fewer than two
const std::string first = av_hwdevice_get_type_name(known[0]);
const std::string second = av_hwdevice_get_type_name(known[1]);
const auto forward = hwaccel_candidate_types(first + "," + second);
REQUIRE(forward.size() == 2);
REQUIRE(forward[0] == known[0]);
REQUIRE(forward[1] == known[1]);
// Reversing the setting must reverse what gets tried, or the priority the
// operator expressed is not being honoured.
const auto backward = hwaccel_candidate_types(second + "," + first);
REQUIRE(backward.size() == 2);
REQUIRE(backward[0] == known[1]);
REQUIRE(backward[1] == known[0]);
}
SECTION("spaces around the commas are not part of the name") {
const enum AVHWDeviceType known = some_known_type();
if (known == AV_HWDEVICE_TYPE_NONE) return;
const std::string name = av_hwdevice_get_type_name(known);
REQUIRE(contains(hwaccel_candidate_types(" " + name + " "), known));
REQUIRE(contains(hwaccel_candidate_types(name + " , "), known));
}
SECTION("empty entries in a list are skipped") {
const enum AVHWDeviceType known = some_known_type();
if (known == AV_HWDEVICE_TYPE_NONE) return;
const std::string name = av_hwdevice_get_type_name(known);
const auto got = hwaccel_candidate_types("," + name + ",,");
REQUIRE(got.size() == 1);
REQUIRE(got.front() == known);
}
}