mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
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>
722 lines
31 KiB
C++
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;
|
|
}
|
|
|
|
}
|