Files
thuanquangandClaude Opus 5.5 e1341df3b9 fix(libSceVideodec2): decode after a flush without a reset
sceVideodec2Decode threw when it followed sceVideodec2Flush without
sceVideodec2Reset. It now resets FFmpeg's drained decoder and decodes the
unit, keeping the pictures the flush has not returned yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 02:42:34 +07:00

722 lines
31 KiB
C++

#include <array>
#include <span>
#include <cstdint>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <map>
#include <memory>
#include <mutex>
#include <vector>
#include <cerrno>
#include <limits>
#include <stdexcept>
#include "prx/libc/include/General.hpp"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/frame.h>
#include <libavutil/pixdesc.h>
#include <libswscale/swscale.h>
}
namespace {
constexpr std::uint64_t WorkMemoryBytes = 1u << 20u;
constexpr std::uint32_t PitchAlignment = 256;
constexpr std::uint32_t HeightAlignment = 32;
constexpr std::uint32_t CodecAvc = 1;
constexpr std::uint32_t CodecHevc = 974921;
constexpr std::uint32_t CodecVp9 = 2382845;
struct ComputeMemoryInfo {
std::uint64_t thisSize;
std::uint64_t cpuGpuMemorySize;
void* cpuGpuMemory;
};
struct ComputeConfigInfo {
std::uint64_t thisSize;
std::uint16_t computePipeId;
std::uint16_t computeQueueId;
bool checkMemoryType;
};
struct DecoderConfigInfo {
std::uint64_t thisSize;
std::uint32_t resourceType;
std::uint32_t codecType;
std::uint32_t profile;
std::uint32_t maxLevel;
std::int32_t maxFrameWidth;
std::int32_t maxFrameHeight;
std::int32_t maxDpbFrameCount;
std::uint32_t decodePipelineDepth;
};
struct DecoderMemoryInfo {
std::uint64_t thisSize;
std::uint64_t cpuMemorySize;
void* cpuMemory;
std::uint64_t gpuMemorySize;
void* gpuMemory;
std::uint64_t cpuGpuMemorySize;
void* cpuGpuMemory;
std::uint64_t maxFrameBufferSize;
std::uint32_t frameBufferAlignment;
};
struct InputData {
std::uint64_t thisSize;
const std::uint8_t* auData;
std::uint64_t auSize;
std::uint64_t ptsData;
std::uint64_t dtsData;
std::uint64_t attachedData;
};
struct FrameBuffer {
std::uint64_t thisSize;
void* frameBuffer;
std::uint64_t frameBufferSize;
bool isAccepted;
};
struct OutputInfo {
std::uint64_t thisSize;
bool isValid;
bool isErrorFrame;
std::uint8_t pictureCount;
std::uint32_t codecType;
std::uint32_t frameWidth;
std::uint32_t framePitch;
std::uint32_t frameHeight;
void* frameBuffer;
std::uint64_t frameBufferSize;
std::uint32_t frameFormat;
std::uint32_t framePitchInBytes;
};
struct AvcPictureInfo {
std::uint64_t thisSize;
bool isValid;
std::uint64_t ptsData;
std::uint64_t dtsData;
std::uint64_t attachedData;
std::uint8_t idrPictureFlag;
std::uint8_t profileIdc;
std::uint8_t levelIdc;
std::uint32_t picWidthInLumaSamples;
std::uint32_t picHeightInLumaSamples;
};
struct Vp9PictureInfo {
std::uint64_t thisSize;
bool isValid;
std::uint64_t ptsData;
std::uint64_t dtsData;
std::uint64_t attachedData;
std::uint32_t profile;
std::uint32_t level;
std::uint32_t keyFrameFlag;
std::uint32_t intraOnly;
std::uint8_t bitDepth;
std::uint8_t colorRange;
std::uint32_t colorSpace;
std::uint32_t numFrames;
std::uint32_t frameWidth;
std::uint32_t frameHeight;
std::uint32_t renderWidth;
std::uint32_t renderHeight;
};
static_assert(sizeof(Vp9PictureInfo) == 88);
static_assert(offsetof(Vp9PictureInfo, colorSpace) == 60);
class Vp9Bits {
public:
explicit Vp9Bits(std::span<const std::uint8_t> bytes) : bytes(bytes) {}
std::uint32_t Read(unsigned count) {
std::uint32_t result = 0;
while (count-- != 0) {
if (position / 8 >= bytes.size()) throw std::runtime_error("Videodec2: truncated VP9 header");
result = (result << 1u) | ((bytes[position / 8] >> (7u - position % 8)) & 1u);
++position;
}
return result;
}
private:
std::span<const std::uint8_t> bytes;
std::size_t position = 0;
};
struct Vp9Unit {
std::span<const std::uint8_t> bytes;
Vp9PictureInfo info{};
bool shown = false;
};
Vp9Unit ParseVp9Unit(std::span<const std::uint8_t> bytes, std::array<Vp9PictureInfo, 8>& references, Vp9PictureInfo& colors, std::uint32_t count) {
Vp9Bits bits(bytes);
if (bits.Read(2) != 2) throw std::runtime_error("Videodec2: invalid VP9 frame marker");
const auto low = bits.Read(1);
const auto profile = low | (bits.Read(1) << 1u);
if (profile == 3 && bits.Read(1) != 0) throw std::runtime_error("Videodec2: invalid VP9 reserved bit");
Vp9Unit unit{bytes};
auto& info = unit.info;
if (bits.Read(1) != 0) {
info = references[bits.Read(3)];
if (!info.isValid) throw std::runtime_error("Videodec2: missing VP9 display reference");
info.keyFrameFlag = 0;
info.intraOnly = 0;
info.profile = profile;
info.numFrames = count;
unit.shown = true;
return unit;
}
const bool key = bits.Read(1) == 0;
unit.shown = bits.Read(1) != 0;
const bool resilient = bits.Read(1) != 0;
const bool intra = !key && !unit.shown && bits.Read(1) != 0;
if (!key && !resilient) bits.Read(2);
const auto dimensions = [&] {
info.frameWidth = bits.Read(16) + 1;
info.frameHeight = bits.Read(16) + 1;
};
const auto render = [&] {
const bool different = bits.Read(1) != 0;
info.renderWidth = different ? bits.Read(16) + 1 : info.frameWidth;
info.renderHeight = different ? bits.Read(16) + 1 : info.frameHeight;
};
std::uint32_t refresh = 0;
if (key || intra) {
if (bits.Read(24) != 0x498342) throw std::runtime_error("Videodec2: invalid VP9 sync code");
info.bitDepth = 8;
info.colorSpace = 1;
if (key || profile != 0) {
if (profile >= 2) info.bitDepth = bits.Read(1) != 0 ? 12 : 10;
info.colorSpace = bits.Read(3);
if (info.colorSpace != 7) {
info.colorRange = static_cast<std::uint8_t>(bits.Read(1));
if (profile == 1 || profile == 3) {
bits.Read(2);
if (bits.Read(1) != 0) throw std::runtime_error("Videodec2: invalid VP9 color reserved bit");
}
} else {
info.colorRange = 1;
if (profile == 1 || profile == 3) {
if (bits.Read(1) != 0) throw std::runtime_error("Videodec2: invalid VP9 RGB reserved bit");
} else throw std::runtime_error("Videodec2: invalid VP9 RGB profile");
}
}
refresh = key ? 255 : bits.Read(8);
dimensions();
} else {
refresh = bits.Read(8);
std::array<unsigned, 3> indexes{};
for (auto& index : indexes) { index = bits.Read(3); bits.Read(1); }
info = colors;
if (!info.isValid) throw std::runtime_error("Videodec2: missing VP9 color configuration");
bool inherited = false;
for (const auto index : indexes) {
if (bits.Read(1) != 0) {
if (!references[index].isValid) throw std::runtime_error("Videodec2: missing VP9 size reference");
info.frameWidth = references[index].frameWidth;
info.frameHeight = references[index].frameHeight;
inherited = true;
break;
}
}
if (!inherited) dimensions();
}
render();
info.isValid = true;
info.profile = profile;
info.keyFrameFlag = key ? 1 : 0;
info.intraOnly = intra ? 1 : 0;
info.numFrames = count;
if (key || intra) colors = info;
for (unsigned index = 0; index < references.size(); ++index)
if ((refresh & (1u << index)) != 0) references[index] = info;
return unit;
}
std::vector<Vp9Unit> ParseVp9Packet(std::span<const std::uint8_t> bytes, std::array<Vp9PictureInfo, 8>& references, Vp9PictureInfo& colors) {
std::vector<std::span<const std::uint8_t>> frames;
const auto marker = bytes.back();
const unsigned count = (marker & 7u) + 1;
const unsigned magnitude = ((marker >> 3u) & 3u) + 1;
const std::size_t indexSize = 2 + count * magnitude;
if ((marker & 0xe0u) == 0xc0u && bytes.size() >= indexSize && bytes[bytes.size() - indexSize] == marker) {
const auto end = bytes.size() - indexSize;
std::size_t offset = 0;
for (unsigned frame = 0; frame < count; ++frame) {
std::size_t size = 0;
for (unsigned byte = 0; byte < magnitude; ++byte)
size |= static_cast<std::size_t>(bytes[end + 1 + frame * magnitude + byte]) << (8u * byte);
if (size == 0 || size > end - offset) throw std::runtime_error("Videodec2: invalid VP9 superframe size");
frames.push_back(bytes.subspan(offset, size));
offset += size;
}
if (offset != end) throw std::runtime_error("Videodec2: VP9 superframe contains unindexed bytes");
} else frames.push_back(bytes);
std::vector<Vp9Unit> result;
for (const auto frame : frames) result.push_back(ParseVp9Unit(frame, references, colors, static_cast<std::uint32_t>(frames.size())));
return result;
}
struct Crop {
std::uint32_t left = 0;
std::uint32_t right = 0;
std::uint32_t top = 0;
std::uint32_t bottom = 0;
};
struct Picture {
std::uint64_t pts = 0;
std::uint64_t dts = 0;
std::uint64_t attached = 0;
bool idr = false;
Vp9PictureInfo vp9{};
Crop crop{};
};
struct FrameDeleter {
void operator()(AVFrame* frame) const { av_frame_free(&frame); }
};
using FramePointer = std::unique_ptr<AVFrame, FrameDeleter>;
struct Decoder {
std::uint32_t codec = 0;
std::uint32_t width = 0;
std::uint32_t height = 0;
std::uint32_t profile = 0;
std::uint32_t level = 0;
std::map<const void*, Picture> pictures;
std::uint64_t decoded = 0;
std::mutex mutex;
AVCodecContext* context = nullptr;
SwsContext* scaler = nullptr;
std::map<std::int64_t, Picture> inputs;
std::int64_t nextKey = 0;
std::deque<FramePointer> ready;
std::vector<std::uint8_t> annexB;
std::vector<std::uint8_t> parameterSets;
bool flushing = false;
std::array<Vp9PictureInfo, 8> vp9References{};
Vp9PictureInfo vp9Colors{};
~Decoder() {
sws_freeContext(scaler);
avcodec_free_context(&context);
}
};
std::mutex lock;
std::map<std::uint64_t, std::shared_ptr<Decoder>> decoders;
std::map<const void*, std::uint64_t> pictureOwners;
std::uint64_t nextDecoder = 1;
std::uint64_t nextQueue = 1;
bool Trace() {
static const bool trace = std::getenv("APS5_TRACE_VIDEODEC") != nullptr;
return trace;
}
std::uint32_t AlignUp(std::uint32_t value, std::uint32_t alignment) {
return (value + alignment - 1) / alignment * alignment;
}
std::uint64_t ChromaOffset(std::uint32_t width, std::uint32_t height) {
return static_cast<std::uint64_t>(AlignUp(width, PitchAlignment)) * height;
}
std::uint64_t LumaBytes(std::uint32_t width, std::uint32_t height) {
return static_cast<std::uint64_t>(AlignUp(width, PitchAlignment)) * AlignUp(height, HeightAlignment);
}
template <typename TObject>
bool Fits(const TObject* object, std::size_t end) {
return object->thisSize >= end;
}
std::shared_ptr<Decoder> FindDecoder(std::uint64_t handle) {
std::lock_guard guard(lock);
const auto found = decoders.find(handle);
return found != decoders.end() ? found->second : nullptr;
}
AVCodecID CodecId(std::uint32_t codec) {
switch (codec) {
case CodecAvc: return AV_CODEC_ID_H264;
case CodecHevc: return AV_CODEC_ID_HEVC;
case CodecVp9: return AV_CODEC_ID_VP9;
default: throw std::runtime_error("Videodec2: unsupported codec");
}
}
void OpenCodec(Decoder& decoder) {
const AVCodec* codec = avcodec_find_decoder(CodecId(decoder.codec));
if (codec == nullptr) throw std::runtime_error("Videodec2: decoder is unavailable");
decoder.context = avcodec_alloc_context3(codec);
if (decoder.context == nullptr) throw std::runtime_error("Videodec2: cannot allocate codec context");
decoder.context->err_recognition = AV_EF_EXPLODE;
decoder.context->thread_count = 0;
decoder.context->thread_type = FF_THREAD_SLICE;
decoder.context->apply_cropping = 0;
if (avcodec_open2(decoder.context, codec, nullptr) < 0) {
avcodec_free_context(&decoder.context);
throw std::runtime_error("Videodec2: cannot open decoder");
}
}
const std::vector<std::uint8_t>* ToAnnexB(Decoder& decoder, const std::uint8_t* data, std::size_t size) {
if (size >= 4 && data[0] == 0 && data[1] == 0 && (data[2] == 1 || (data[2] == 0 && data[3] == 1))) {
decoder.annexB.assign(data, data + size);
return &decoder.annexB;
}
decoder.annexB.clear();
for (std::size_t index = 0; index < size;) {
if (size - index < 4) throw std::runtime_error("Videodec2: truncated NAL length");
const std::size_t length = (static_cast<std::size_t>(data[index]) << 24u) | (static_cast<std::size_t>(data[index + 1]) << 16u) | (static_cast<std::size_t>(data[index + 2]) << 8u) | data[index + 3];
if (length == 0 || length > size - index - 4) throw std::runtime_error("Videodec2: invalid NAL length");
static constexpr std::uint8_t startCode[4] = {0, 0, 0, 1};
decoder.annexB.insert(decoder.annexB.end(), startCode, startCode + 4);
decoder.annexB.insert(decoder.annexB.end(), data + index + 4, data + index + 4 + length);
index += 4 + length;
}
return &decoder.annexB;
}
bool CarriesAvcSlice(const std::vector<std::uint8_t>& unit) {
for (std::size_t index = 0; index + 3 < unit.size(); ++index) {
if (unit[index] != 0 || unit[index + 1] != 0 || unit[index + 2] != 1) continue;
const auto type = unit[index + 3] & 0x1fu;
if (type >= 1 && type <= 5) return true;
index += 2;
}
return false;
}
void Receive(Decoder& decoder) {
for (;;) {
FramePointer frame(av_frame_alloc());
if (!frame) throw std::runtime_error("Videodec2: cannot allocate frame");
const int result = avcodec_receive_frame(decoder.context, frame.get());
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) return;
if (result < 0 || frame->decode_error_flags != 0) throw std::runtime_error("Videodec2: frame decoding failed");
decoder.ready.push_back(std::move(frame));
}
}
void Send(Decoder& decoder, const AVPacket* packet) {
const int result = avcodec_send_packet(decoder.context, packet);
if (result < 0) throw std::runtime_error("Videodec2: packet submission failed");
Receive(decoder);
}
bool WriteNv12(Decoder& decoder, const AVFrame& frame, std::uint8_t* target, std::uint64_t bytes) {
if (frame.width <= 0 || frame.height <= 0 || frame.width % 2 != 0 || frame.height % 2 != 0) throw std::runtime_error("Videodec2: unsupported frame dimensions");
const auto width = static_cast<std::uint32_t>(frame.width);
const auto height = static_cast<std::uint32_t>(frame.height);
const auto pitch = AlignUp(width, PitchAlignment);
const auto luma = ChromaOffset(width, height);
if (luma + static_cast<std::uint64_t>(pitch) * (height / 2) > bytes) return false;
std::uint8_t* planes[4] = {target, target + luma, nullptr, nullptr};
const int strides[4] = {static_cast<int>(pitch), static_cast<int>(pitch), 0, 0};
const auto format = static_cast<AVPixelFormat>(frame.format);
const AVPixFmtDescriptor* descriptor = av_pix_fmt_desc_get(format);
if (descriptor == nullptr || descriptor->comp[0].depth > 8) throw std::runtime_error("Videodec2: output deeper than 8 bits is not implemented");
if (format == AV_PIX_FMT_YUV420P || format == AV_PIX_FMT_YUVJ420P) {
for (std::uint32_t row = 0; row < height; ++row) std::memcpy(planes[0] + static_cast<std::size_t>(row) * pitch, frame.data[0] + static_cast<std::ptrdiff_t>(row) * frame.linesize[0], width);
for (std::uint32_t row = 0; row < height / 2; ++row) {
const auto* u = frame.data[1] + static_cast<std::ptrdiff_t>(row) * frame.linesize[1];
const auto* v = frame.data[2] + static_cast<std::ptrdiff_t>(row) * frame.linesize[2];
auto* uv = planes[1] + static_cast<std::size_t>(row) * pitch;
for (std::uint32_t column = 0; column < width / 2; ++column) {
uv[column * 2] = u[column];
uv[column * 2 + 1] = v[column];
}
}
return true;
}
decoder.scaler = sws_getCachedContext(decoder.scaler, frame.width, frame.height, format, frame.width, frame.height, AV_PIX_FMT_NV12, SWS_BILINEAR, nullptr, nullptr, nullptr);
if (decoder.scaler == nullptr) return false;
return sws_scale(decoder.scaler, frame.data, frame.linesize, 0, frame.height, planes, strides) == frame.height;
}
void EmitDecoded(std::uint64_t handle, Decoder& decoder, FrameBuffer* frame, OutputInfo* output) {
output->isValid = false;
output->isErrorFrame = false;
output->pictureCount = 0;
output->codecType = decoder.codec;
if (frame) frame->isAccepted = false;
if (decoder.ready.empty()) return;
if (!frame || !frame->frameBuffer) throw std::runtime_error("Videodec2: missing output buffer");
const auto picture = std::move(decoder.ready.front());
decoder.ready.pop_front();
const auto key = picture->pts;
const auto found = decoder.inputs.find(key);
if (found == decoder.inputs.end()) throw std::runtime_error("Videodec2: missing picture timestamps");
auto timestamps = found->second;
decoder.inputs.erase(found);
timestamps.idr = (picture->flags & AV_FRAME_FLAG_KEY) != 0;
timestamps.crop = {static_cast<std::uint32_t>(picture->crop_left), static_cast<std::uint32_t>(picture->crop_right), static_cast<std::uint32_t>(picture->crop_top), static_cast<std::uint32_t>(picture->crop_bottom)};
decoder.width = static_cast<std::uint32_t>(picture->width);
decoder.height = static_cast<std::uint32_t>(picture->height);
if (decoder.context->profile > 0) decoder.profile = static_cast<std::uint32_t>(decoder.context->profile) & 0xffu;
if (decoder.context->level > 0) decoder.level = static_cast<std::uint32_t>(decoder.context->level);
if (!WriteNv12(decoder, *picture, static_cast<std::uint8_t*>(frame->frameBuffer), frame->frameBufferSize)) throw std::runtime_error("Videodec2: NV12 conversion failed");
const auto pitch = AlignUp(decoder.width, PitchAlignment);
frame->isAccepted = true;
output->isValid = true;
output->isErrorFrame = false;
output->pictureCount = 1;
output->frameWidth = decoder.width;
output->framePitch = pitch;
output->frameHeight = decoder.height;
output->frameBuffer = frame->frameBuffer;
output->frameBufferSize = frame->frameBufferSize;
if (Fits(output, offsetof(OutputInfo, framePitchInBytes) + 4)) {
output->frameFormat = 0;
output->framePitchInBytes = pitch;
}
decoder.pictures[frame->frameBuffer] = timestamps;
std::lock_guard guard(lock);
if (decoders.contains(handle)) pictureOwners[frame->frameBuffer] = handle;
}
}
extern "C" {
int APS5_VABI sceVideodec2QueryComputeMemoryInfo_nid_postfix(ComputeMemoryInfo* info) {
if (!info) throw std::runtime_error("Videodec2: invalid argument pointer");
if (Trace()) std::fprintf(stderr, "[videodec2] QueryComputeMemoryInfo size=%llu\n", static_cast<unsigned long long>(info->thisSize));
info->cpuGpuMemorySize = WorkMemoryBytes;
return 0;
}
int APS5_VABI sceVideodec2AllocateComputeQueue_nid_postfix(const ComputeConfigInfo* config, const ComputeMemoryInfo* memory, std::uint64_t* queue) {
if (!config || !memory || !queue) throw std::runtime_error("Videodec2: invalid argument pointer");
if (Trace()) std::fprintf(stderr, "[videodec2] AllocateComputeQueue config=%llu memory=%llu\n", static_cast<unsigned long long>(config->thisSize), static_cast<unsigned long long>(memory->thisSize));
std::lock_guard guard(lock);
*queue = nextQueue++;
return 0;
}
int APS5_VABI sceVideodec2ReleaseComputeQueue_nid_postfix(std::uint64_t queue) {
(void)queue;
return 0;
}
int APS5_VABI sceVideodec2QueryDecoderMemoryInfo_nid_postfix(const DecoderConfigInfo* config, DecoderMemoryInfo* memory) {
if (!config || !memory) throw std::runtime_error("Videodec2: invalid argument pointer");
if (Trace()) std::fprintf(stderr, "[videodec2] QueryDecoderMemoryInfo config=%llu memory=%llu codec=%u profile=%u %dx%d dpb=%d\n", static_cast<unsigned long long>(config->thisSize), static_cast<unsigned long long>(memory->thisSize), config->codecType, config->profile, config->maxFrameWidth, config->maxFrameHeight, config->maxDpbFrameCount);
memory->cpuMemorySize = WorkMemoryBytes;
memory->gpuMemorySize = WorkMemoryBytes;
memory->cpuGpuMemorySize = WorkMemoryBytes;
if (config->maxFrameWidth <= 0 || config->maxFrameHeight <= 0) throw std::runtime_error("Videodec2: invalid frame dimensions");
const auto width = static_cast<std::uint32_t>(config->maxFrameWidth);
const auto height = static_cast<std::uint32_t>(config->maxFrameHeight);
memory->maxFrameBufferSize = LumaBytes(width, height) * 3 / 2;
memory->frameBufferAlignment = PitchAlignment;
return 0;
}
int APS5_VABI sceVideodec2CreateDecoder_nid_postfix(const DecoderConfigInfo* config, const DecoderMemoryInfo* memory, std::uint64_t* handle) {
if (!config || !memory || !handle) throw std::runtime_error("Videodec2: invalid argument pointer");
CodecId(config->codecType);
auto decoder = std::make_shared<Decoder>();
decoder->codec = config->codecType;
OpenCodec(*decoder);
std::lock_guard guard(lock);
*handle = nextDecoder++;
decoders.emplace(*handle, decoder);
std::fprintf(stderr, "[videodec2] decoder %llu: codec=%u max %dx%d (%s)\n", static_cast<unsigned long long>(*handle), config->codecType, config->maxFrameWidth, config->maxFrameHeight, decoder->context->codec->long_name);
return 0;
}
int APS5_VABI sceVideodec2DeleteDecoder_nid_postfix(std::uint64_t handle) {
std::lock_guard guard(lock);
if (decoders.erase(handle) == 0) throw std::runtime_error("Videodec2: invalid decoder handle");
std::erase_if(pictureOwners, [handle](const auto& owner) { return owner.second == handle; });
return 0;
}
int APS5_VABI sceVideodec2Decode_nid_postfix(std::uint64_t handle, const InputData* input, FrameBuffer* frame, OutputInfo* output) {
if (!input || !output) throw std::runtime_error("Videodec2: invalid argument pointer");
const auto found = FindDecoder(handle);
if (!found) throw std::runtime_error("Videodec2: invalid decoder handle");
auto& decoder = *found;
std::lock_guard guard(decoder.mutex);
if (Trace() && decoder.decoded < 3) std::fprintf(stderr, "[videodec2] Decode input=%llu frame=%llu output=%llu au=%llu pts=%llu size=%ux%u fb=%p/%llu\n", static_cast<unsigned long long>(input->thisSize), static_cast<unsigned long long>(frame ? frame->thisSize : 0), static_cast<unsigned long long>(output->thisSize), static_cast<unsigned long long>(input->auSize), static_cast<unsigned long long>(input->ptsData), decoder.width, decoder.height, frame ? frame->frameBuffer : nullptr, static_cast<unsigned long long>(frame ? frame->frameBufferSize : 0));
++decoder.decoded;
const Picture picture{input->ptsData, input->dtsData, input->attachedData};
if (decoder.flushing) {
avcodec_flush_buffers(decoder.context);
decoder.flushing = false;
}
if (!input->auData || input->auSize == 0 || input->auSize > static_cast<std::uint64_t>(std::numeric_limits<int>::max())) throw std::runtime_error("Videodec2: invalid access unit");
if (decoder.codec == CodecVp9) {
auto references = decoder.vp9References;
auto colors = decoder.vp9Colors;
const auto units = ParseVp9Packet({input->auData, static_cast<std::size_t>(input->auSize)}, references, colors);
for (const auto& unit : units) {
AVPacket* packet = av_packet_alloc();
if (!packet) throw std::runtime_error("Videodec2: cannot allocate VP9 packet");
const auto freePacket = [](AVPacket* value) { av_packet_free(&value); };
const std::unique_ptr<AVPacket, decltype(freePacket)> packetOwner(packet, freePacket);
if (av_new_packet(packet, static_cast<int>(unit.bytes.size())) < 0) throw std::runtime_error("Videodec2: cannot allocate VP9 packet data");
std::memcpy(packet->data, unit.bytes.data(), unit.bytes.size());
packet->pts = decoder.nextKey;
auto tagged = picture;
tagged.vp9 = unit.info;
tagged.vp9.ptsData = picture.pts;
tagged.vp9.dtsData = picture.dts;
tagged.vp9.attachedData = picture.attached;
const auto key = decoder.nextKey++;
decoder.inputs.emplace(key, tagged);
Send(decoder, packet);
if (!unit.shown) decoder.inputs.erase(key);
}
decoder.vp9References = references;
decoder.vp9Colors = colors;
EmitDecoded(handle, decoder, frame, output);
return 0;
}
const auto* unit = ToAnnexB(decoder, input->auData, static_cast<std::size_t>(input->auSize));
if (decoder.codec == CodecAvc && !CarriesAvcSlice(*unit)) {
decoder.parameterSets.insert(decoder.parameterSets.end(), unit->begin(), unit->end());
EmitDecoded(handle, decoder, frame, output);
return 0;
}
AVPacket* packet = av_packet_alloc();
if (!packet) throw std::runtime_error("Videodec2: cannot allocate packet");
const auto freePacket = [](AVPacket* value) { av_packet_free(&value); };
const std::unique_ptr<AVPacket, decltype(freePacket)> packetOwner(packet, freePacket);
if (decoder.parameterSets.size() + unit->size() > static_cast<std::size_t>(std::numeric_limits<int>::max())) throw std::runtime_error("Videodec2: invalid access unit");
if (av_new_packet(packet, static_cast<int>(decoder.parameterSets.size() + unit->size())) < 0) throw std::runtime_error("Videodec2: cannot allocate packet data");
std::memcpy(packet->data, decoder.parameterSets.data(), decoder.parameterSets.size());
std::memcpy(packet->data + decoder.parameterSets.size(), unit->data(), unit->size());
decoder.parameterSets.clear();
packet->pts = decoder.nextKey;
decoder.inputs.emplace(decoder.nextKey++, picture);
Send(decoder, packet);
EmitDecoded(handle, decoder, frame, output);
return 0;
}
int APS5_VABI sceVideodec2Flush_nid_postfix(std::uint64_t handle, FrameBuffer* frame, OutputInfo* output) {
if (!output) throw std::runtime_error("Videodec2: invalid argument pointer");
const auto found = FindDecoder(handle);
if (!found) throw std::runtime_error("Videodec2: invalid decoder handle");
auto& decoder = *found;
std::lock_guard guard(decoder.mutex);
if (!decoder.flushing) {
Send(decoder, nullptr);
decoder.flushing = true;
}
EmitDecoded(handle, decoder, frame, output);
return 0;
}
int APS5_VABI sceVideodec2Reset_nid_postfix(std::uint64_t handle) {
const auto found = FindDecoder(handle);
if (!found) throw std::runtime_error("Videodec2: invalid decoder handle");
auto& decoder = *found;
std::lock_guard guard(decoder.mutex);
decoder.pictures.clear();
decoder.vp9References = {};
decoder.vp9Colors = {};
{
std::lock_guard ownersGuard(lock);
std::erase_if(pictureOwners, [handle](const auto& owner) { return owner.second == handle; });
}
avcodec_flush_buffers(decoder.context);
decoder.ready.clear();
decoder.inputs.clear();
decoder.parameterSets.clear();
decoder.flushing = false;
return 0;
}
int APS5_VABI sceVideodec2GetPictureInfo_nid_postfix(const OutputInfo* output, AvcPictureInfo* first, void* second) {
(void)second;
if (!output || !first) throw std::runtime_error("Videodec2: invalid argument pointer");
const auto owner = [output]() -> std::uint64_t {
std::lock_guard guard(lock);
const auto found = pictureOwners.find(output->frameBuffer);
return found != pictureOwners.end() ? found->second : 0;
};
for (auto handle = owner(); handle != 0; handle = owner()) {
const auto decoder = FindDecoder(handle);
if (!decoder) continue;
std::lock_guard guard(decoder->mutex);
if (owner() != handle) continue;
const auto found = decoder->pictures.find(output->frameBuffer);
if (found == decoder->pictures.end()) throw std::runtime_error("Videodec2: owned picture is missing");
if (decoder->codec != CodecAvc) throw std::runtime_error("Videodec2: picture info is only implemented for H.264");
const auto& picture = found->second;
first->isValid = true;
first->ptsData = picture.pts;
first->dtsData = picture.dts;
first->attachedData = picture.attached;
if (Fits(first, offsetof(AvcPictureInfo, picHeightInLumaSamples) + 4)) {
first->idrPictureFlag = picture.idr ? 1 : 0;
first->profileIdc = static_cast<std::uint8_t>(decoder->profile);
first->levelIdc = static_cast<std::uint8_t>(decoder->level);
first->picWidthInLumaSamples = output->frameWidth;
first->picHeightInLumaSamples = output->frameHeight;
}
return 0;
}
first->isValid = false;
return 0;
}
int APS5_VABI sceVideodec2GetAvcPictureInfo_nid_postfix(const OutputInfo* output, AvcPictureInfo* first, AvcPictureInfo* second) {
return sceVideodec2GetPictureInfo_nid_postfix(output, first, second);
}
int APS5_VABI sceVideodec2GetVp9PictureInfo_nid_postfix(const OutputInfo* output, Vp9PictureInfo* info) {
if (!output || !info) throw std::runtime_error("Videodec2: invalid VP9 picture argument");
if (!Fits(output, offsetof(OutputInfo, frameBuffer) + sizeof(output->frameBuffer)) || !Fits(info, sizeof(Vp9PictureInfo)))
throw std::runtime_error("Videodec2: VP9 picture structure is too small");
const auto owner = [output]() -> std::uint64_t {
std::lock_guard guard(lock);
const auto found = pictureOwners.find(output->frameBuffer);
return found != pictureOwners.end() ? found->second : 0;
};
for (auto handle = owner(); handle != 0; handle = owner()) {
const auto decoder = FindDecoder(handle);
if (!decoder) continue;
std::lock_guard guard(decoder->mutex);
if (owner() != handle) continue;
const auto found = decoder->pictures.find(output->frameBuffer);
if (found == decoder->pictures.end()) throw std::runtime_error("Videodec2: owned VP9 picture is missing");
if (decoder->codec != CodecVp9) throw std::runtime_error("Videodec2: VP9 info requested for another codec");
const auto size = info->thisSize;
*info = found->second.vp9;
info->thisSize = size;
return 0;
}
info->isValid = false;
return 0;
}
void Videodec2PictureCrop_nid_no_patch(std::uint64_t handle, const void* frameBuffer, Crop* crop) {
if (!crop) throw std::runtime_error("Videodec2: invalid argument pointer");
const auto found = FindDecoder(handle);
if (!found) throw std::runtime_error("Videodec2: invalid decoder handle");
auto& decoder = *found;
std::lock_guard guard(decoder.mutex);
const auto picture = decoder.pictures.find(frameBuffer);
if (picture == decoder.pictures.end()) throw std::runtime_error("Videodec2: unknown frame buffer");
*crop = picture->second.crop;
}
}