mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-23 17:13:13 -05:00
Decode and Flush let FFmpeg crop every picture to its display size, so a 1080p H.264 stream came out as 1920x1080 with the chroma after 1080 luma rows. The console reports and writes the coded picture: EVO-PLAYER-PS5's hardware run of a 1080p H.264 IDR returned 1920x1088 with pitch 2048 and the chroma after the 1088 luma rows, and ProsperoLight rejects a 1080p H.264 or HEVC picture whose height is not 1088. FFmpeg's cropping is now off, so the frame size, the chroma offset and the sizes in the AVC picture info are those of the coded picture. The pitch is unchanged, and the frame size QueryDecoderMemoryInfo reports, which aligns the maximum height to 32 rows, still holds a picture coded within the maximum and the macroblock-padded H.264 picture of a maximum given as the display size. libSceVdecsw derived its crop offsets from the difference between that size and the macroblock grid, which is now zero. It takes the crop FFmpeg exports for the picture from libSceVideodec2 instead. The H.264 fixture is coded at 64x48 with frame_crop_bottom_offset 4: its pictures are checked at 64x48 and the crop through Vdecsw. The HEVC and VP9 fixtures are coded at 64x40 and keep their hashes. The new hashes come from FFmpeg 8.1 decoding with apply_cropping 0, hashed as the tests do; the same method reproduces every previous H.264, HEVC and VP9 hash. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
699 lines
30 KiB
C++
699 lines
30 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;
|
|
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;
|
|
}
|
|
|
|
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) throw std::runtime_error("Videodec2: reset required after flushing");
|
|
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));
|
|
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 (av_new_packet(packet, static_cast<int>(unit->size())) < 0) throw std::runtime_error("Videodec2: cannot allocate packet data");
|
|
std::memcpy(packet->data, unit->data(), unit->size());
|
|
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.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;
|
|
}
|
|
|
|
}
|