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

179 lines
6.2 KiB
C++

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "mediagenerator.h"
#include <QVideoFrame>
#include <QAudioBuffer>
#include <QDebug>
namespace {
uint32_t channelFrequency(int channelIndex, const std::vector<uint32_t> &channelFrequencies)
{
return channelFrequencies[channelIndex % channelFrequencies.size()];
}
double normalizedSineSampleValue(uint32_t frequency, uint32_t sampleIndex, uint32_t sampleRate)
{
return sin(2 * M_PI * frequency * sampleIndex / sampleRate);
}
template <typename ResultHandler>
void toSampleValue(double normalizedValue, QAudioFormat::SampleFormat sampleFormat,
ResultHandler &&resultHandler)
{
switch (sampleFormat)
case QAudioFormat::UInt8: {
resultHandler(static_cast<quint8>(qRound((1.0 + normalizedValue) / 2 * 255)));
break;
case QAudioFormat::Int16:
resultHandler(
static_cast<qint16>(qRound(normalizedValue * std::numeric_limits<qint16>::max())));
break;
case QAudioFormat::Int32: {
resultHandler(
static_cast<quint32>(qRound(normalizedValue * std::numeric_limits<qint32>::max())));
break;
}
case QAudioFormat::Float:
resultHandler(static_cast<float>(normalizedValue));
break;
case QAudioFormat::Unknown:
case QAudioFormat::NSampleFormats:
Q_ASSERT(!"Unknown audio sample format");
break;
}
}
QByteArray createSineWaveData(const QAudioFormat &format, uint32_t &sampleIndex,
std::chrono::microseconds duration,
const std::vector<uint32_t> &channelFrequencies)
{
const int bytesPerSample = format.bytesPerSample();
qint32 remainingBytes = format.bytesForDuration(duration.count());
Q_ASSERT(bytesPerSample);
Q_ASSERT((remainingBytes % bytesPerSample) == 0);
Q_ASSERT(!channelFrequencies.empty());
QByteArray result(remainingBytes, 0);
unsigned char *ptr = reinterpret_cast<unsigned char *>(result.data());
auto writeNextSampleValue = [&](auto sampleValue) {
Q_ASSERT(sizeof(sampleValue) == bytesPerSample);
*reinterpret_cast<decltype(sampleValue) *>(ptr) = sampleValue;
ptr += sizeof(sampleValue);
remainingBytes -= sizeof(sampleValue);
};
while (remainingBytes) {
for (int channelIndex = 0; channelIndex < format.channelCount(); ++channelIndex) {
const uint32_t frequency = channelFrequency(channelIndex, channelFrequencies);
const double normalizedSampleValue =
normalizedSineSampleValue(frequency, sampleIndex, format.sampleRate());
toSampleValue(normalizedSampleValue, format.sampleFormat(), writeNextSampleValue);
}
++sampleIndex;
}
return result;
}
QRgb imagePatternValue(int xIndex, int yIndex, double patternSpeed, uint32_t patternWidth,
uint32_t imageIndex)
{
static const uint32_t availableColors[] = { qRgba(255, 0, 0, 0), qRgba(0, 255, 0, 0),
qRgba(0, 0, 255, 0) };
constexpr double angle = M_PI / 3;
// inverse sin and cos as angle, otherise the angle will represent the orthogonal line
static const double xFactor = sin(angle);
static const double yFactor = cos(angle);
const double value =
(xIndex * xFactor + yIndex * yFactor + imageIndex * patternSpeed) / patternWidth;
int colorIndex = qRound(value) % std::size(availableColors);
if (colorIndex < 0)
colorIndex += std::size(availableColors);
return availableColors[colorIndex];
}
QImage createPatternImage(QSize size, uint32_t patternWidth, float patternSpeed,
uint32_t imageIndex)
{
QImage image(size, QImage::Format_RGBA8888);
uchar *imageData = image.bits();
for (int yIndex = 0; yIndex < size.height(); ++yIndex) {
auto colors = reinterpret_cast<uint32_t *>(imageData + yIndex * image.bytesPerLine());
for (int xIndex = 0; xIndex < size.width(); ++xIndex)
colors[xIndex] =
imagePatternValue(xIndex, yIndex, patternSpeed, patternWidth, imageIndex);
}
return image;
}
std::chrono::microseconds videoFrameTime(uint32_t frameIndex, uint32_t frameRate)
{
using namespace std::chrono;
return microseconds(seconds(1)) * frameIndex / frameRate;
}
} // namespace
AudioGenerator::AudioGenerator(const Settings &settings) : m_settings(settings)
{
m_format.setSampleFormat(m_settings.sampleFormat);
m_format.setSampleRate(m_settings.sampleRate);
m_format.setChannelConfig(m_settings.channelConfig);
// align duration
m_settings.bufferDuration = std::chrono::microseconds(m_format.durationForBytes(
m_format.bytesForDuration(m_settings.bufferDuration.count())));
}
QAudioBuffer AudioGenerator::generate()
{
if (m_index == m_settings.duration / m_settings.bufferDuration)
return {};
const QByteArray sineData = createSineWaveData(
m_format, m_sampleIndex, m_settings.bufferDuration, m_settings.channelFrequencies);
qint64 startTime = -1;
if (qToUnderlying(m_settings.timeGenerationMode)
& qToUnderlying(MediaTimeGenerationMode::TimeStamps))
startTime = std::chrono::microseconds(m_settings.bufferDuration * m_index).count();
++m_index;
return QAudioBuffer(sineData, m_format, startTime);
}
VideoGenerator::VideoGenerator(const Settings &settings) : m_settings(settings) { }
QVideoFrame VideoGenerator::generate()
{
if (m_index == m_settings.frameRate * m_settings.duration.count() / 1000)
return {};
QVideoFrame frame(createPatternImage(m_settings.resolution, m_settings.patternWidth,
m_settings.patternSpeed, m_index));
if (qToUnderlying(m_settings.timeGenerationMode)
& qToUnderlying(MediaTimeGenerationMode::FrameRate))
frame.setStreamFrameRate(m_settings.frameRate);
if (qToUnderlying(m_settings.timeGenerationMode)
& qToUnderlying(MediaTimeGenerationMode::TimeStamps)) {
frame.setStartTime(videoFrameTime(m_index, m_settings.frameRate).count());
frame.setEndTime(videoFrameTime(m_index + 1, m_settings.frameRate).count());
}
++m_index;
return frame;
}