qt6-bb10/qtmultimedia/tests/manual/mediaframeinputs/commandlineparser.cpp

391 lines
16 KiB
C++

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "commandlineparser.h"
#include <QApplication>
namespace {
bool contains(const QStringList &list, const QStringView &str)
{
return list.contains(str, Qt::CaseInsensitive);
}
bool equals(const QStringView &a, const QStringView &b)
{
return a.compare(b, Qt::CaseInsensitive) == 0;
}
template <typename Container, typename Separator, typename ToString>
QString join(const Container &container, const Separator &separator, ToString &&toString)
{
QString result;
for (const auto &element : container) {
if (!result.isEmpty())
result += separator;
result += toString(element);
}
return result;
}
template <typename Numbers, typename Separator>
QString joinNumberElements(const Numbers &numbers, const Separator &separator)
{
return join(numbers, separator, [](auto number) { return QString::number(number); });
}
template <typename Map, typename Separator>
QString joinMapStringValues(const Map &map, const Separator &separator)
{
return join(map, separator, [](const auto &pair) { return pair.second; });
}
auto toUInt(const QString &value, bool &ok)
{
return value.toUInt(&ok);
}
auto toUIntList(const QString &value, bool &ok)
{
std::vector<uint32_t> result;
const QStringList split = value.split(',');
ok = !split.isEmpty();
for (auto it = split.cbegin(); ok && it != split.cend(); ++it)
result.push_back(it->toUInt(&ok));
return result;
}
auto toSize(const QString &value, bool &ok)
{
const QStringList xy = value.split('x');
ok = xy.size() == 2;
QSize result;
if (ok)
result.setWidth(xy.front().toUInt(&ok));
if (ok)
result.setHeight(xy.back().toUInt(&ok));
return result;
}
auto toPositiveFloat(const QString &value, bool &ok)
{
const float result = value.toFloat(&ok);
ok = ok && result > 0;
return result;
}
auto toUrl(const QString &value, [[maybe_unused]] bool &ok)
{
return QUrl::fromLocalFile(value);
}
auto toStreams(const QStringList &streams, const QString &value, bool &ok)
{
const QStringList result = value.split(',');
ok = !result.empty() && std::all_of(result.cbegin(), result.cend(), [&](const QString &s) {
return streams.contains(s, Qt::CaseInsensitive);
});
return result;
}
auto toMode(const QStringList &modes, const QString &value, bool &ok)
{
ok = modes.contains(value, Qt::CaseInsensitive);
return value;
}
auto toEnum = [](const auto &enumMap, const QString &value, bool &ok) {
auto found = std::find_if(enumMap.begin(), enumMap.end(),
[&](const auto &pair) { return equals(pair.second, value); });
ok = found != enumMap.end();
return ok ? found->first : decltype(found->first)();
};
template <typename Duration>
auto toMsCount(Duration duration)
{
return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
}
const char *noCheck()
{
return nullptr;
};
} // namespace
CommandLineParser::CommandLineParser()
: m_result{ AudioGeneratorSettings{}, VideoGeneratorSettings{}, PushModeSettings{}, {} }
{
m_parser.setApplicationDescription(QStringLiteral(
"The application tests QtMultimedia media frame inputs with media "
"recording and shows a simplistic result preview via the media player."));
m_parser.addHelpOption();
}
CommandLineParser::Result CommandLineParser::process()
{
m_parser.process(*QApplication::instance());
parseCommonSettings(); // parse common settings first
parseAudioGeneratorSettings();
parseVideoGeneratorSettings();
parsePushModeSettings();
parseRecorderSettings();
return std::move(m_result);
}
void CommandLineParser::parseCommonSettings()
{
if (auto duration = parsedValue(m_options.duration, noCheck, toUInt)) {
m_result.audioGeneratorSettings->duration = std::chrono::milliseconds(*duration);
m_result.videoGeneratorSettings->duration = std::chrono::milliseconds(*duration);
}
if (auto streams = parsedValue(m_options.streams, noCheck, toStreams, m_streams)) {
if (!contains(*streams, m_audioStream))
m_result.audioGeneratorSettings.reset();
if (!contains(*streams, m_videoStream))
m_result.videoGeneratorSettings.reset();
}
if (auto mode = parsedValue(m_options.mode, noCheck, toMode, m_modes))
m_mode = *mode;
if (!equals(m_mode, m_pushMode))
m_result.pushModeSettings.reset();
}
void CommandLineParser::parseAudioGeneratorSettings()
{
AudioGeneratorSettingsOpt &settings = m_result.audioGeneratorSettings;
auto check = [&settings]() -> const char * {
return settings ? nullptr : "audio stream is not enabled";
};
if (auto frequencies = parsedValue(m_options.channelFrequencies, check, toUIntList))
settings->channelFrequencies = std::move(*frequencies);
if (auto bufferDuration = parsedValue(m_options.audioBufferDuration, check, toUInt))
settings->bufferDuration = std::chrono::milliseconds(*bufferDuration);
if (auto format = parsedValue(m_options.sampleFormat, check, toEnum, m_sampleFormats))
settings->sampleFormat = *format;
if (auto config = parsedValue(m_options.channelConfig, check, toEnum, m_channelConfigs))
settings->channelConfig = *config;
}
void CommandLineParser::parseVideoGeneratorSettings()
{
VideoGeneratorSettingsOpt &settings = m_result.videoGeneratorSettings;
auto check = [&settings]() -> const char * {
return settings ? nullptr : "video stream is not enabled";
};
if (auto frameRate = parsedValue(m_options.frameRate, check, toUInt))
settings->frameRate = *frameRate;
if (auto resolution = parsedValue(m_options.resolution, check, toSize))
settings->resolution = *resolution;
if (auto patternWidth = parsedValue(m_options.framePatternWidth, check, toUInt))
settings->patternWidth = *patternWidth;
if (auto patternSpeed = parsedValue(m_options.framePatternSpeed, check, toUInt))
settings->patternSpeed = *patternSpeed;
}
void CommandLineParser::parsePushModeSettings()
{
PushModeSettingsOpt &settings = m_result.pushModeSettings;
auto check = [&settings]() -> const char * {
return settings ? nullptr : "push mode is not enabled";
};
if (auto producingRate = parsedValue(m_options.pushModeProducingRate, check, toPositiveFloat))
settings->producingRate = *producingRate;
if (auto maxQueueSize = parsedValue(m_options.pushModeMaxQueueSize, check, toUInt))
settings->maxQueueSize = *maxQueueSize;
}
void CommandLineParser::parseRecorderSettings()
{
RecorderSettings &settings = m_result.recorderSettings;
if (auto location = parsedValue(m_options.outputLocation, noCheck, toUrl))
settings.outputLocation = *location;
if (auto frameRate = parsedValue(m_options.recorderFrameRate, noCheck, toUInt))
settings.frameRate = *frameRate;
if (auto resolution = parsedValue(m_options.recorderResolution, noCheck, toSize))
settings.resolution = *resolution;
settings.quality = parsedValue(m_options.recorderQuality, noCheck, toEnum, m_recorderQualities);
settings.audioCodec = parsedValue(m_options.recorderAudioCodec, noCheck, toEnum, m_audioCodecs);
settings.videoCodec = parsedValue(m_options.recorderVideoCodec, noCheck, toEnum, m_videoCodecs);
settings.fileFormat = parsedValue(m_options.recorderFileFormat, noCheck, toEnum, m_fileFormats);
}
template <typename ContextChecker, typename... ResultGetter>
auto CommandLineParser::parsedValue(const QString &option, ContextChecker &&contextChecker,
ResultGetter &&...resultGetter)
-> std::optional<decltype(std::invoke(resultGetter..., QString(), std::declval<bool &>()))>
{
Q_ASSERT(!option.isEmpty());
if (!m_parser.isSet(option))
return {};
const QString value = m_parser.value(option);
if (value.isEmpty())
return {};
bool ok = true;
auto result = std::invoke(std::forward<ResultGetter>(resultGetter)..., value, ok);
if (!ok) {
qWarning() << "Invalid value for option" << option << ":" << value;
} else if (const auto error = contextChecker()) {
ok = false;
qWarning() << error << ", so the option" << option << "will not be applied";
}
if (!ok)
return {};
return { std::move(result) };
}
QString CommandLineParser::addOption(QCommandLineOption option)
{
m_parser.addOption(option);
return option.names().constFirst();
}
CommandLineParser::Options CommandLineParser::createOptions()
{
Options result;
Q_ASSERT(m_result.audioGeneratorSettings && m_result.videoGeneratorSettings
&& m_result.pushModeSettings);
const AudioGeneratorSettings &audioGeneratorSettings = *m_result.audioGeneratorSettings;
const VideoGeneratorSettings &videoGeneratorSettings = *m_result.videoGeneratorSettings;
const PushModeSettings &pushModeSettings = *m_result.pushModeSettings;
result.streams = addOption({ QStringLiteral("streams"),
QStringLiteral("Types of generated streams (%1).\nDefaults to %2.")
.arg(m_streams.join(u", "), m_streams.join(',')),
QStringLiteral("list of enum") });
result.mode = addOption({ QStringLiteral("mode"),
QStringLiteral("Media frames generation mode (%1).\nDefaults to %2.")
.arg(m_modes.join(u", "), m_pullMode),
QStringLiteral("list of enum") });
result.duration =
addOption({ { QStringLiteral("generator.duration"), QStringLiteral("duration") },
QStringLiteral("Media duration.\nDefaults to %1.")
.arg(toMsCount(audioGeneratorSettings.duration)),
QStringLiteral("ms") });
result.channelFrequencies = addOption(
{ { QStringLiteral("generator.frequencies"), QStringLiteral("frequencies") },
QStringLiteral("Generated audio frequencies for each channel. It's "
"possible to set one or several ones: x,y,z.\nDefaults to %1.")
.arg(joinNumberElements(audioGeneratorSettings.channelFrequencies, u",")),
QStringLiteral("list of hz") });
result.audioBufferDuration =
addOption({ { QStringLiteral("generator.audioBufferDuration"),
QStringLiteral("audioBufferDuration") },
QStringLiteral("Duration of single audio buffer.\nDefaults to %1.")
.arg(toMsCount(audioGeneratorSettings.bufferDuration)),
QStringLiteral("ms") });
result.sampleFormat = addOption(
{ { QStringLiteral("generator.sampleFormat"), QStringLiteral("sampleFormat") },
QStringLiteral("Generated audio sample format (%1).\nDefaults to %2.")
.arg(joinMapStringValues(m_sampleFormats, u", "),
m_sampleFormats.at(audioGeneratorSettings.sampleFormat)),
QStringLiteral("enum") });
result.channelConfig = addOption(
{ { QStringLiteral("generator.channelConfig"), QStringLiteral("channelConfig") },
QStringLiteral("Generated audio channel config (%1).\nDefaults to %2.")
.arg(joinMapStringValues(m_channelConfigs, u", "),
m_channelConfigs.at(audioGeneratorSettings.channelConfig)),
QStringLiteral("enum") });
result.frameRate =
addOption({ { QStringLiteral("generator.frameRate"), QStringLiteral("frameRate") },
QStringLiteral("Generated video frame rate.\nDefaults to %1.")
.arg(videoGeneratorSettings.frameRate),
QStringLiteral("uint") });
result.resolution = addOption(
{ { QStringLiteral("generator.resolution"), QStringLiteral("resolution") },
QStringLiteral("Generated video frame resolution.\nDefaults to %1x%2.")
.arg(QString::number(videoGeneratorSettings.resolution.width()),
QString::number(videoGeneratorSettings.resolution.height())),
QStringLiteral("WxH") });
result.framePatternWidth =
addOption({ { QStringLiteral("generator.framePatternWidth"),
QStringLiteral("framePatternWidth") },
QStringLiteral("Generated video frame patern pixel width.\nDefaults to %1.")
.arg(videoGeneratorSettings.patternWidth),
QStringLiteral("uint") });
result.framePatternSpeed = addOption(
{ { QStringLiteral("generator.framePatternSpeed"),
QStringLiteral("framePatternSpeed") },
QStringLiteral("Relative speed of moving the frame pattern.\nDefaults to %1.")
.arg(videoGeneratorSettings.patternSpeed),
QStringLiteral("float") });
result.pushModeProducingRate = addOption(
{ { QStringLiteral("generator.pushMode.producingRate"),
QStringLiteral("producingRate") },
QStringLiteral(
"Relative factor of media producing rate in push mode.\nDefaults to %1.")
.arg(pushModeSettings.producingRate),
QStringLiteral("positive float") });
result.pushModeMaxQueueSize =
addOption({ {
QStringLiteral("generator.pushMode.maxQueueSize"),
QStringLiteral("maxQueueSize"),
},
QStringLiteral("Max number of generated media frames in the queue on "
"the front of media recorder.\nDefaults to %1.")
.arg(pushModeSettings.maxQueueSize),
QStringLiteral("uint") });
result.outputLocation = addOption(
{ { QStringLiteral("recorder.outputLocation"), QStringLiteral("outputLocation") },
QStringLiteral("Output location for the media."),
QStringLiteral("file or directory") });
result.recorderFrameRate =
addOption({ QStringLiteral("recorder.frameRate"),
QStringLiteral("Video frame rate for media recorder.\nNo default value."),
QStringLiteral("uint") });
result.recorderQuality =
addOption({ { QStringLiteral("recorder.quality"), QStringLiteral("quality") },
QStringLiteral("Quality of media recording (%1).\nNo default value.")
.arg(joinMapStringValues(m_recorderQualities, u", ")),
QStringLiteral("enum") });
result.recorderResolution =
addOption({ QStringLiteral("recorder.resolution"),
QStringLiteral("Video resolution for media recorder.\nNo default value."),
QStringLiteral("WxH") });
result.recorderAudioCodec =
addOption({ { QStringLiteral("recorder.audioCodec"), QStringLiteral("audioCodec") },
QStringLiteral("Audio codec for media recorder. \nNo default value."),
QStringLiteral("enum") });
result.recorderVideoCodec =
addOption({ { QStringLiteral("recorder.videoCodec"), QStringLiteral("videoCodec") },
QStringLiteral("Video codec for media recorder.\nNo default value."),
QStringLiteral("enum") });
result.recorderFileFormat =
addOption({ { QStringLiteral("recorder.fileFormat"), QStringLiteral("fileFormat") },
QStringLiteral("File format for media recorder.\nNo default value."),
QStringLiteral("enum") });
return result;
}