20 std::atomic<float>
left{1.0F};
25 left.store(
gains.left, std::memory_order_relaxed);
26 right.store(
gains.right, std::memory_order_relaxed);
36 const auto& pan = *
static_cast<const StereoPanState*
>(
userdata);
37 const float left = pan.left.load(std::memory_order_relaxed);
38 const float right = pan.right.load(std::memory_order_relaxed);
40 const int channels =
static_cast<int>(spec->channels);
41 for (
int sample = 0; sample + 1 <
samples; sample += channels)
67 frame.rgba.size() !=
static_cast<std::uint64_t
>(
frame.width) *
frame.height * 4U)
68 return std::unexpected(
"decoded video frame dimensions do not match RGBA pixels");
70 return std::unexpected(
"non-default legacy Indeo colour controls are unsupported");
73 const auto stride =
static_cast<std::size_t
>(
frame.width) * 4U;
74 for (std::size_t
y = 0;
y <
frame.height / 2U; ++
y)
80 for (std::size_t
i = 3;
i <
frame.rgba.size();
i += 4)
96 !std::ranges::all_of(world.
values, [](
float value) { return std::isfinite(value); }))
97 return std::unexpected(
"video rectangle or world transform is invalid");
101 transform.values[6] =
static_cast<float>(bounds.
left);
102 transform.values[7] =
static_cast<float>(bounds.
top);
107 std::uint32_t
width, std::uint32_t
height)
noexcept
114 return {{},
static_cast<std::int32_t
>(
left),
static_cast<std::int32_t
>(
top),
115 static_cast<std::int32_t
>(std::min<std::int64_t>(800,
left +
doubledWidth)),
116 static_cast<std::int32_t
>(std::min<std::int64_t>(600,
top +
doubledHeight))};
164 const std::filesystem::path& file,
bool enableAudio,
const DecoderOptions& options)
167 impl_->audioEnabled = enableAudio;
168 auto selected = options;
170 return impl_->decoder.open(file, selected);
175 impl_->decoder.stop();
177 impl_->nextFrame.reset();
178 impl_->anchored =
false;
179 impl_->sequenceOrigin = impl_->lastSequenceTime = impl_->mediaOrigin = impl_->lastElapsed = 0;
186 impl_->mediaOrigin = impl_->lastElapsed =
timestamp;
187 impl_->sequenceOrigin = impl_->lastSequenceTime =
sequenceTime;
188 impl_->anchored =
true;
190 impl_->nextFrame.reset();
195 if (!std::isfinite(gain))
196 return std::unexpected(
"video audio gain is not finite");
197 impl_->gain = std::clamp(gain, 0.0F, 1.0F);
200 return std::unexpected(
sdlError(
"set video audio gain"));
211 impl_->stream, impl_->frequencyRatio))
212 return std::unexpected(
sdlError(
"set video audio pitch"));
224 auto&
state = *impl_;
235 state.anchored =
true;
249 if (!
state.stream)
return std::unexpected(
sdlError(
"open video audio stream"));
251 return std::unexpected(
sdlError(
"set initial video audio gain"));
254 return std::unexpected(
sdlError(
"set initial video audio pitch"));
256 return std::unexpected(
sdlError(
"set video audio panning callback"));
259 if (queued < 0)
return std::unexpected(
sdlError(
"query video audio queue"));
263 std::min(
state.submitted,
static_cast<std::uint64_t
>(queued)));
266 auto chunk =
state.decoder.popAudio();
268 if (chunk->pcm.empty() || chunk->pcm.size() % 4U ||
270 return std::unexpected(
"decoded video PCM block is outside stream limits");
271 if (!
state.submitted)
state.audioOrigin = chunk->timestampMicroseconds;
273 static_cast<int>(chunk->pcm.size())))
274 return std::unexpected(
sdlError(
"queue video PCM"));
275 state.submitted += chunk->pcm.size();
276 queued +=
static_cast<int>(chunk->pcm.size());
282 return std::unexpected(
sdlError(
"flush final video PCM"));
283 state.flushed =
true;
290 if (!changed)
return std::unexpected(
sdlError(
"pause/resume video audio"));
298 if (!
state.tailAnchored)
300 state.tailAnchored =
true;
314 auto&
state = *impl_;
317 std::optional<DecodedVideoFrame>
latest;
319 for (
int frames = 0; frames < 8; ++frames)
324 state.nextFrame.reset();
331 state.sequenceOrigin =
state.lastSequenceTime;
332 state.anchored =
true;
338 const auto snapshot = impl_->decoder.snapshot();
std::atomic< float > left
std::atomic< float > right
std::expected< std::optional< DecodedVideoFrame >, std::string > frameAt(std::uint64_t elapsedMicroseconds)
std::expected< void, std::string > setPitch(std::uint32_t hertz, std::uint32_t originalHertz)
std::expected< PresentationClock, std::string > pump(std::uint64_t sequenceMicroseconds, bool paused=false)
bool videoDrained() const
std::expected< void, std::string > setGain(float gain)
void setPanning(std::int32_t percentage) noexcept
std::expected< void, std::string > open(const std::filesystem::path &file, bool enableAudio, const DecoderOptions &options={})
std::expected< void, std::string > seek(std::uint64_t timestampMicroseconds, std::uint64_t sequenceMicroseconds)
DecoderSnapshot snapshot() const
constexpr StereoPanGains legacyPanGains(std::int32_t percentage) noexcept
constexpr float legacyPitchFrequencyRatio(std::uint32_t requestedHertz, std::uint32_t originalHertz) noexcept
Matrix2D identity2D() noexcept
Matrix2D multiply(const Matrix2D &left, const Matrix2D &right) noexcept
std::expected< sequence::Matrix2D, std::string > videoFrameTransform(std::uint32_t width, std::uint32_t height, const data::Sequence2DBoundingBoxAttribute &bounds, const sequence::Matrix2D &world)
std::expected< data::LegacyBitmapRGBA8, std::string > prepareVideoFrame(DecodedVideoFrame frame, const data::SequenceVideoData &options)
data::Sequence2DBoundingBoxAttribute binkDoubleSizeBounds(std::uint32_t width, std::uint32_t height) noexcept
std::array< float, 9 > values
std::size_t queuedVideoFrames
std::optional< DecoderMetadata > metadata
std::size_t queuedAudioChunks
std::uint64_t audioOrigin
std::uint64_t sequenceOrigin
std::uint64_t lastElapsed
std::optional< DecodedVideoFrame > nextFrame
std::uint64_t lastSequenceTime
std::uint64_t mediaOrigin
std::uint64_t tailSequence