mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-12-06 07:12:28 +01:00
cellDmuxPamf implementation part 1: SPU thread
This commit is contained in:
parent
359163c442
commit
1c2a2ab01a
|
|
@ -301,7 +301,7 @@ using CellDmuxCoreOpResetStream = error_code(vm::ptr<void>);
|
|||
using CellDmuxCoreOpCreateThread = error_code(vm::ptr<void>);
|
||||
using CellDmuxCoreOpJoinThread = error_code(vm::ptr<void>);
|
||||
using CellDmuxCoreOpSetStream = error_code(vm::ptr<void>, vm::cptr<void>, u32, b8, u64);
|
||||
using CellDmuxCoreOpFreeMemory = error_code(vm::ptr<void>, vm::ptr<void>, u32);
|
||||
using CellDmuxCoreOpReleaseAu = error_code(vm::ptr<void>, vm::ptr<void>, u32);
|
||||
using CellDmuxCoreOpQueryEsAttr = error_code(vm::cptr<void>, vm::cptr<void>, vm::ptr<CellDmuxPamfEsAttr>);
|
||||
using CellDmuxCoreOpEnableEs = error_code(vm::ptr<void>, vm::cptr<void>, vm::cptr<CellDmuxEsResource>, vm::cptr<DmuxCb<DmuxEsNotifyAuFound>>, vm::cptr<DmuxCb<DmuxEsNotifyFlushDone>>, vm::cptr<void>, vm::pptr<void>);
|
||||
using CellDmuxCoreOpDisableEs = u32(vm::ptr<void>);
|
||||
|
|
@ -318,7 +318,7 @@ struct CellDmuxCoreOps
|
|||
vm::bptr<CellDmuxCoreOpCreateThread> createThread;
|
||||
vm::bptr<CellDmuxCoreOpJoinThread> joinThread;
|
||||
vm::bptr<CellDmuxCoreOpSetStream> setStream;
|
||||
vm::bptr<CellDmuxCoreOpFreeMemory> freeMemory;
|
||||
vm::bptr<CellDmuxCoreOpReleaseAu> releaseAu;
|
||||
vm::bptr<CellDmuxCoreOpQueryEsAttr> queryEsAttr;
|
||||
vm::bptr<CellDmuxCoreOpEnableEs> enableEs;
|
||||
vm::bptr<CellDmuxCoreOpDisableEs> disableEs;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,666 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/savestate_utils.hpp"
|
||||
#include "Utilities/Thread.h"
|
||||
#include "cellPamf.h"
|
||||
#include "cellDmux.h"
|
||||
#include <span>
|
||||
|
||||
// Replacement for CellSpursQueue
|
||||
template <typename T, u32 max_num_of_entries> requires(std::is_trivial_v<T> && max_num_of_entries > 0)
|
||||
class alignas(0x80) dmux_pamf_hle_spurs_queue
|
||||
{
|
||||
T* buffer;
|
||||
|
||||
alignas(atomic_t<u32>) std::array<std::byte, sizeof(atomic_t<u32>)> _size; // Stored in a byte array since the PPU context needs to be trivial
|
||||
u32 front;
|
||||
u32 back;
|
||||
|
||||
template <bool is_peek>
|
||||
bool _pop(T* lhs)
|
||||
{
|
||||
atomic_t<u32>& _size = *std::launder(reinterpret_cast<atomic_t<u32>*>(this->_size.data()));
|
||||
|
||||
if (_size == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lhs)
|
||||
{
|
||||
*lhs = buffer[front];
|
||||
}
|
||||
|
||||
if constexpr (!is_peek)
|
||||
{
|
||||
front = (front + 1) % max_num_of_entries;
|
||||
_size--;
|
||||
_size.notify_one();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
void init(T (&buffer)[max_num_of_entries])
|
||||
{
|
||||
this->buffer = buffer;
|
||||
new (_size.data()) atomic_t<u32>(0);
|
||||
front = 0;
|
||||
back = 0;
|
||||
}
|
||||
|
||||
bool pop(T& lhs) { return _pop<false>(&lhs); }
|
||||
bool pop() { return _pop<false>(nullptr); }
|
||||
bool peek(T& lhs) const { return const_cast<dmux_pamf_hle_spurs_queue*>(this)->_pop<true>(&lhs); }
|
||||
bool emplace(auto&&... args)
|
||||
{
|
||||
atomic_t<u32>& _size = *std::launder(reinterpret_cast<atomic_t<u32>*>(this->_size.data()));
|
||||
|
||||
if (_size >= max_num_of_entries)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
new (&buffer[back]) T(std::forward<decltype(args)>(args)...);
|
||||
|
||||
back = (back + 1) % max_num_of_entries;
|
||||
_size++;
|
||||
_size.notify_one();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] u32 size() const { return std::launder(reinterpret_cast<const atomic_t<u32>*>(this->_size.data()))->observe(); }
|
||||
|
||||
void wait() const
|
||||
{
|
||||
const atomic_t<u32>& _size = *std::launder(reinterpret_cast<const atomic_t<u32>*>(this->_size.data()));
|
||||
|
||||
while (_size == 0 && thread_ctrl::state() != thread_state::aborting)
|
||||
{
|
||||
thread_ctrl::wait_on(_size, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
enum class DmuxPamfCommandType : u32
|
||||
{
|
||||
enable_es = 0,
|
||||
disable_es = 2,
|
||||
set_stream = 4,
|
||||
release_au = 6,
|
||||
flush_es = 8,
|
||||
close = 10,
|
||||
reset_stream = 12,
|
||||
reset_es = 14,
|
||||
resume = 16,
|
||||
};
|
||||
|
||||
struct alignas(0x80) DmuxPamfCommand
|
||||
{
|
||||
be_t<DmuxPamfCommandType> type;
|
||||
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
be_t<u32> stream_id;
|
||||
be_t<u32> private_stream_id;
|
||||
be_t<u32> is_avc;
|
||||
vm::bptr<u8, u64> au_queue_buffer;
|
||||
be_t<u32> au_queue_buffer_size;
|
||||
be_t<u32> au_max_size;
|
||||
be_t<u32> au_specific_info_size;
|
||||
be_t<u32> is_raw_es;
|
||||
be_t<u32> user_data;
|
||||
}
|
||||
enable_es;
|
||||
|
||||
struct
|
||||
{
|
||||
be_t<u32> stream_id;
|
||||
be_t<u32> private_stream_id;
|
||||
}
|
||||
disable_flush_es;
|
||||
|
||||
struct
|
||||
{
|
||||
vm::ptr<u8, be_t<u64, 4>> au_addr;
|
||||
be_t<u32> au_size;
|
||||
be_t<u32> stream_id;
|
||||
be_t<u32> private_stream_id;
|
||||
}
|
||||
release_au;
|
||||
|
||||
struct
|
||||
{
|
||||
be_t<u32> stream_id;
|
||||
be_t<u32> private_stream_id;
|
||||
vm::ptr<u8, be_t<u64, 4>> au_addr;
|
||||
}
|
||||
reset_es;
|
||||
};
|
||||
|
||||
DmuxPamfCommand() = default;
|
||||
|
||||
DmuxPamfCommand(be_t<DmuxPamfCommandType>&& type)
|
||||
: type(type)
|
||||
{
|
||||
}
|
||||
|
||||
DmuxPamfCommand(be_t<DmuxPamfCommandType>&& type, const be_t<u32>& stream_id, const be_t<u32>& private_stream_id)
|
||||
: type(type), disable_flush_es{ stream_id, private_stream_id }
|
||||
{
|
||||
}
|
||||
|
||||
DmuxPamfCommand(be_t<DmuxPamfCommandType>&& type, const be_t<u32>& stream_id, const be_t<u32>& private_stream_id, const vm::ptr<u8,be_t<u64, 4>>& au_addr)
|
||||
: type(type), reset_es{ stream_id, private_stream_id, au_addr }
|
||||
{
|
||||
}
|
||||
|
||||
DmuxPamfCommand(be_t<DmuxPamfCommandType>&& type, const vm::ptr<u8,be_t<u64, 4>>& au_addr, const be_t<u32>& au_size, const be_t<u32>& stream_id, const be_t<u32>& private_stream_id)
|
||||
: type(type), release_au{ au_addr, au_size, stream_id, private_stream_id }
|
||||
{
|
||||
}
|
||||
|
||||
DmuxPamfCommand(be_t<DmuxPamfCommandType>&& type, const be_t<u32>& stream_id, const be_t<u32>& private_stream_id, const be_t<u32>& is_avc, const vm::bptr<u8, u64>& au_queue_buffer,
|
||||
const be_t<u32>& au_queue_buffer_size, const be_t<u32>& au_max_size, const be_t<u32>& au_specific_info_size, const be_t<u32>& is_raw_es, const be_t<u32>& user_data)
|
||||
: type(type), enable_es{ stream_id, private_stream_id, is_avc, au_queue_buffer, au_queue_buffer_size, au_max_size, au_specific_info_size, is_raw_es, user_data }
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
CHECK_SIZE_ALIGN(DmuxPamfCommand, 0x80, 0x80);
|
||||
|
||||
enum class DmuxPamfEventType : u32
|
||||
{
|
||||
au_found,
|
||||
demux_done,
|
||||
fatal_error,
|
||||
close,
|
||||
flush_done,
|
||||
prog_end_code,
|
||||
};
|
||||
|
||||
struct alignas(0x80) DmuxPamfEvent
|
||||
{
|
||||
be_t<DmuxPamfEventType> type;
|
||||
|
||||
union
|
||||
{
|
||||
u8 pad[0x78];
|
||||
|
||||
struct
|
||||
{
|
||||
be_t<u32> stream_id;
|
||||
be_t<u32> private_stream_id;
|
||||
vm::ptr<u8, be_t<u64, 4>> au_addr;
|
||||
CellCodecTimeStamp pts;
|
||||
CellCodecTimeStamp dts;
|
||||
be_t<u64, 4> unk;
|
||||
u8 reserved[4];
|
||||
be_t<u32> au_size;
|
||||
be_t<u32> stream_header_size;
|
||||
std::array<u8, 0x10> stream_header_buf;
|
||||
be_t<u32> user_data;
|
||||
be_t<u32> is_rap;
|
||||
}
|
||||
au_found;
|
||||
|
||||
struct
|
||||
{
|
||||
be_t<u32> stream_id;
|
||||
be_t<u32> private_stream_id;
|
||||
be_t<u32> user_data;
|
||||
}
|
||||
flush_done;
|
||||
};
|
||||
|
||||
be_t<u32> event_queue_was_too_full;
|
||||
|
||||
DmuxPamfEvent() = default;
|
||||
|
||||
DmuxPamfEvent(be_t<DmuxPamfEventType>&& type, const be_t<u32>& event_queue_was_too_full)
|
||||
: type(type), event_queue_was_too_full(event_queue_was_too_full)
|
||||
{
|
||||
}
|
||||
|
||||
DmuxPamfEvent(be_t<DmuxPamfEventType>&& type, const be_t<u32>& stream_id, const be_t<u32>& private_stream_id, const be_t<u32>& user_data, const be_t<u32>& event_queue_was_too_full)
|
||||
: type(type), flush_done{ stream_id, private_stream_id, user_data }, event_queue_was_too_full(event_queue_was_too_full)
|
||||
{
|
||||
}
|
||||
|
||||
DmuxPamfEvent(be_t<DmuxPamfEventType>&& type, const be_t<u32>& stream_id, const be_t<u32>& private_stream_id, const vm::ptr<u8,be_t<u64, 4>>& au_addr, const CellCodecTimeStamp& pts, const CellCodecTimeStamp& dts, const be_t<u64>& unk,
|
||||
const be_t<u32>& au_size, const be_t<u32>& au_specific_info_size, const std::array<u8, 0x10>& au_specific_info, const be_t<u32>& user_data, const be_t<u32>& is_rap, const be_t<u32>& event_queue_was_too_full)
|
||||
: type(type)
|
||||
, au_found{ stream_id, private_stream_id, au_addr, pts, dts, static_cast<be_t<u64, 4>>(unk), {}, au_size, au_specific_info_size, au_specific_info, user_data, is_rap }
|
||||
, event_queue_was_too_full(event_queue_was_too_full)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
CHECK_SIZE_ALIGN(DmuxPamfEvent, 0x80, 0x80);
|
||||
|
||||
struct alignas(0x80) DmuxPamfStreamInfo
|
||||
{
|
||||
vm::bcptr<u8, u64> stream_addr;
|
||||
be_t<u32> stream_size;
|
||||
be_t<u32> user_data;
|
||||
be_t<u32> continuity;
|
||||
be_t<u32> is_raw_es;
|
||||
};
|
||||
|
||||
CHECK_SIZE_ALIGN(DmuxPamfStreamInfo, 0x80, 0x80);
|
||||
|
||||
enum DmuxPamfStreamTypeIndex
|
||||
{
|
||||
DMUX_PAMF_STREAM_TYPE_INDEX_INVALID = -1,
|
||||
DMUX_PAMF_STREAM_TYPE_INDEX_VIDEO,
|
||||
DMUX_PAMF_STREAM_TYPE_INDEX_LPCM,
|
||||
DMUX_PAMF_STREAM_TYPE_INDEX_AC3,
|
||||
DMUX_PAMF_STREAM_TYPE_INDEX_ATRACX,
|
||||
DMUX_PAMF_STREAM_TYPE_INDEX_USER_DATA,
|
||||
};
|
||||
|
||||
|
||||
// SPU thread
|
||||
|
||||
class dmux_pamf_base
|
||||
{
|
||||
// Event handlers for the demuxer. These correspond to the events that the SPU thread sends to the PPU thread on LLE (except for au_queue_full(): the SPU thread just sets a global bool,
|
||||
// but it is never notified to the PPU thread or the user).
|
||||
|
||||
virtual bool on_au_found(u8 stream_id, u8 private_stream_id, u32 user_data, std::span<const u8> au, u64 pts, u64 dts, bool rap, u8 au_specific_info_size, std::array<u8, 0x10> au_specific_info_buf) = 0;
|
||||
virtual bool on_demux_done() = 0;
|
||||
virtual void on_fatal_error() = 0;
|
||||
virtual bool on_flush_done(u8 stream_id, u8 private_stream_id, u32 user_data) = 0;
|
||||
virtual bool on_prog_end() = 0;
|
||||
virtual void on_au_queue_full() = 0;
|
||||
|
||||
public:
|
||||
virtual ~dmux_pamf_base() = default;
|
||||
|
||||
bool enable_es(u32 stream_id, u32 private_stream_id, bool is_avc, std::span<u8> au_queue_buffer, u32 au_max_size, bool raw_es, u32 user_data);
|
||||
bool disable_es(u32 stream_id, u32 private_stream_id);
|
||||
bool release_au(u32 stream_id, u32 private_stream_id, u32 au_size) const;
|
||||
bool flush_es(u32 stream_id, u32 private_stream_id);
|
||||
void set_stream(std::span<const u8> stream, bool continuity);
|
||||
void reset_stream();
|
||||
bool reset_es(u32 stream_id, u32 private_stream_id, u8* au_addr);
|
||||
bool process_next_pack();
|
||||
|
||||
protected:
|
||||
void save_base(utils::serial& ar);
|
||||
[[nodiscard]] bool has_work() const { return !!stream || !demux_done_notified; }
|
||||
[[nodiscard]] u32 get_enabled_es_count() const;
|
||||
|
||||
private:
|
||||
static constexpr u16 PACK_SIZE = 0x800;
|
||||
static constexpr s8 PACK_STUFFING_LENGTH_OFFSET = 0xd;
|
||||
static constexpr s8 PES_PACKET_LENGTH_OFFSET = 0x4;
|
||||
static constexpr s8 PES_HEADER_DATA_LENGTH_OFFSET = 0x8;
|
||||
static constexpr s8 PTS_DTS_FLAG_OFFSET = 0x7;
|
||||
static constexpr u8 PACKET_START_CODE_PREFIX = 1;
|
||||
|
||||
static constexpr be_t<u32> M2V_PIC_START = 0x100;
|
||||
static constexpr be_t<u32> AVC_AU_DELIMITER = 0x109;
|
||||
static constexpr be_t<u32> M2V_SEQUENCE_HEADER = 0x1b3;
|
||||
static constexpr be_t<u32> M2V_SEQUENCE_END = 0x1b7;
|
||||
static constexpr be_t<u32> PACK_START = 0x1ba;
|
||||
static constexpr be_t<u32> SYSTEM_HEADER = 0x1bb;
|
||||
static constexpr be_t<u32> PRIVATE_STREAM_1 = 0x1bd;
|
||||
static constexpr be_t<u32> PRIVATE_STREAM_2 = 0x1bf;
|
||||
static constexpr be_t<u32> PROG_END = 0x1b9;
|
||||
static constexpr be_t<u32> VIDEO_STREAM_BASE = 0x1e0; // The lower 4 bits indicate the channel
|
||||
|
||||
// Partial access unit that will be written to the output queue
|
||||
struct access_unit_chunk
|
||||
{
|
||||
std::vector<u8> cached_data; // Up to three bytes of data from the previous PES packet (copied into this vector, since it might not be in memory anymore)
|
||||
std::span<const u8> data; // Data of the current PES packet
|
||||
};
|
||||
|
||||
// Output queue for access units
|
||||
// The queue doesn't keep track of where access units are in the buffer (only which parts are used and which are free), this has to be done extenally
|
||||
class output_queue
|
||||
{
|
||||
public:
|
||||
explicit output_queue(std::span<u8> buffer) : buffer(buffer) {}
|
||||
|
||||
explicit output_queue(utils::serial& ar)
|
||||
: buffer{vm::_ptr<u8>(ar.pop<vm::addr_t>()), ar.pop<u32>()}
|
||||
, back(vm::_ptr<u8>(ar.pop<vm::addr_t>()))
|
||||
, front(vm::_ptr<u8>(ar.pop<vm::addr_t>()))
|
||||
, wrap_pos(vm::_ptr<u8>(ar.pop<vm::addr_t>()))
|
||||
{
|
||||
}
|
||||
|
||||
void save(utils::serial& ar) const { ar(vm::get_addr(buffer.data()), static_cast<u32>(buffer.size()), vm::get_addr(back), vm::get_addr(front), vm::get_addr(wrap_pos)); }
|
||||
|
||||
// The queue itself doesn't keep track of the location of each access unit, so the pop and access operations need the size or address of the access unit to remove/return
|
||||
void pop_back(u32 au_size);
|
||||
void pop_back(u8* au_addr);
|
||||
void pop_front(u32 au_size);
|
||||
[[nodiscard]] const u8* peek_back(u32 au_size) const { return back - au_size; }
|
||||
|
||||
void clear() { wrap_pos = front = back = buffer.data(); }
|
||||
|
||||
void push_unchecked(const access_unit_chunk& au_chunk);
|
||||
bool push(const access_unit_chunk& au_chunk, const std::function<void()>& on_fatal_error);
|
||||
|
||||
[[nodiscard]] bool prepare_next_au(u32 au_max_size);
|
||||
|
||||
[[nodiscard]] usz get_free_size() const { return wrap_pos != buffer.data() ? front - back : std::to_address(buffer.end()) - back; }
|
||||
|
||||
private:
|
||||
const std::span<u8> buffer;
|
||||
|
||||
// Since access units have a variable size, uses pointers instead of indices
|
||||
u8* back = buffer.data();
|
||||
const u8* front = buffer.data();
|
||||
const u8* wrap_pos = buffer.data(); // The address where the back pointer wrapped around to the beginning of the queue
|
||||
};
|
||||
|
||||
// Base class for elementary streams and subclasses for each stream type
|
||||
// Responsible for processing the data section of PES packets and splitting it into access units with the stream parsers of each subclass
|
||||
class elementary_stream
|
||||
{
|
||||
public:
|
||||
elementary_stream(u8 channel, u32 au_max_size, dmux_pamf_base& ctx, u32 user_data, u8 au_specific_info_size, std::span<u8> au_queue_buffer)
|
||||
: channel(channel)
|
||||
, au_max_size(au_max_size == umax || au_max_size > au_queue_buffer.size() ? 0x800 : au_max_size)
|
||||
, ctx(ctx)
|
||||
, au_specific_info_size(au_specific_info_size)
|
||||
, user_data(user_data)
|
||||
, au_queue(au_queue_buffer)
|
||||
{
|
||||
// The cache sizes will never exceed three bytes
|
||||
cache.reserve(3);
|
||||
au_chunk.cached_data.reserve(3);
|
||||
}
|
||||
|
||||
elementary_stream(utils::serial& ar, u8 channel, dmux_pamf_base& ctx, u8 au_specific_info_size)
|
||||
: channel(channel)
|
||||
, au_max_size(ar.pop<u32>())
|
||||
, ctx(ctx)
|
||||
, au_specific_info_size(au_specific_info_size)
|
||||
, user_data(ar.pop<u32>())
|
||||
, au_queue(ar)
|
||||
{
|
||||
save(ar);
|
||||
}
|
||||
|
||||
virtual ~elementary_stream() = default;
|
||||
void save(utils::serial& ar);
|
||||
|
||||
static bool is_enabled(const std::unique_ptr<elementary_stream>& es) { return !!es; }
|
||||
|
||||
[[nodiscard]] virtual std::pair<u8, u8> get_stream_id() const = 0;
|
||||
|
||||
void set_pes_packet_data(std::span<const u8> pes_packet_data) { ensure(!this->pes_packet_data); this->pes_packet_data = this->stream_chunk = pes_packet_data; }
|
||||
void set_pts(u64 pts) { this->pts = pts; }
|
||||
void set_dts(u64 dts) { this->dts = dts; }
|
||||
void set_rap() { rap = true; }
|
||||
|
||||
// Parses the proprietary header of private streams. Returns the size of the header or umax if the stream is invalid
|
||||
virtual u32 parse_stream_header(std::span<const u8> elementary_stream, s8 pts_dts_flag) = 0;
|
||||
|
||||
// Processes the current PES packet. Returns true if it has been entirely consumed
|
||||
bool process_pes_packet_data();
|
||||
|
||||
void release_au(u32 au_size) { au_queue.pop_front(au_size); }
|
||||
void flush_es();
|
||||
void reset_es(u8* au_addr);
|
||||
void discard_access_unit();
|
||||
|
||||
protected:
|
||||
const u8 channel : 4;
|
||||
const u32 au_max_size; // Maximum possible size of an access unit
|
||||
u32 au_size_unk = 0; // For user data streams, used to store the size of the current access unit. For other private streams, used as a bool for some reason
|
||||
alignas(0x10) std::array<u8, 0x10> au_specific_info_buf{}; // For LPCM streams, stores the first 0x10 bytes of the current PES packet data, contains info like the number of channels
|
||||
|
||||
// The access unit that is currently being cut out
|
||||
struct access_unit
|
||||
{
|
||||
ENABLE_BITWISE_SERIALIZATION
|
||||
|
||||
enum class state : u8
|
||||
{
|
||||
none, // An access unit is not currently being cut out
|
||||
incomplete, // An access unit is currently being cut out
|
||||
commenced, // The current PES packet contains the beginning of an access unit
|
||||
complete, // The current PES packet contains the end of an access unit
|
||||
size_mismatch, // The distance between sync words and size indicated in the access unit's info header does not match
|
||||
m2v_sequence, // Special case for M2V, access unit commenced, but the next start code does not complete the access unit
|
||||
}
|
||||
state = state::none;
|
||||
|
||||
bool rap = false;
|
||||
bool timestamps_rap_set = false;
|
||||
|
||||
// Since the delimiters of compressed audio streams are allowed to appear anywhere in the stream (instead of just the beginning of an access unit), we need to parse the size of the access unit from the stream
|
||||
u8 size_info_offset = 0;
|
||||
u16 parsed_size = 0;
|
||||
|
||||
u32 accumulated_size = 0; // Incremented after every access unit chunk cut out from the stream
|
||||
|
||||
u64 pts = umax;
|
||||
u64 dts = umax;
|
||||
|
||||
alignas(0x10) std::array<u8, 0x10> au_specific_info_buf{};
|
||||
}
|
||||
current_au;
|
||||
|
||||
access_unit_chunk au_chunk; // A partial access unit that will be written to the access unit queue. Set by the stream parsers
|
||||
std::vector<u8> cache; // The last three bytes of the current PES packet need to be saved, since they could contain part of an access unit delimiter
|
||||
|
||||
// Returns the stream header size of audio streams. The only difference between LPCM and compressed streams is the extra_header_size_unk_mask
|
||||
template <u32 extra_header_size_unk_mask>
|
||||
u32 parse_audio_stream_header(std::span<const u8> pes_packet_data);
|
||||
|
||||
private:
|
||||
dmux_pamf_base& ctx; // For access to event handlers
|
||||
|
||||
enum class state : u8
|
||||
{
|
||||
initial,
|
||||
pushing_au_queue,
|
||||
notifying_au_found,
|
||||
preparing_for_next_au
|
||||
}
|
||||
state = state::initial;
|
||||
|
||||
// Size of the "CellDmuxPamfAuSpecificInfo..." struct for the type of this stream ("reserved" fields are not counted, so for all stream types other than LPCM this will be 0)
|
||||
// This does NOT correspond to the amount of data in au_specific_info_buf, the info in the buffer gets unpacked by the PPU thread
|
||||
const u8 au_specific_info_size;
|
||||
|
||||
const u32 user_data;
|
||||
|
||||
// Data section of the current PES packet. Needs to be remembered separately from the span we're working with below
|
||||
std::optional<std::span<const u8>> pes_packet_data;
|
||||
|
||||
std::span<const u8> stream_chunk; // The current section of the PES packet data to be processed
|
||||
u64 pts = umax; // Presentation time stamp of the current PES packet
|
||||
u64 dts = umax; // Decoding time stamp of the current PES packet
|
||||
bool rap = false; // Random access point indicator
|
||||
|
||||
output_queue au_queue;
|
||||
|
||||
// Extracts access units from the stream by searching for the access unit delimiter and setting au_chunk accordingly. Returns the number of bytes that were parsed
|
||||
virtual u32 parse_stream(std::span<const u8> stream) = 0;
|
||||
|
||||
void reset()
|
||||
{
|
||||
state = state::initial;
|
||||
pes_packet_data.reset();
|
||||
au_size_unk = 0;
|
||||
pts =
|
||||
dts = umax;
|
||||
rap = false;
|
||||
au_chunk.data = {};
|
||||
au_chunk.cached_data.clear();
|
||||
current_au = {};
|
||||
}
|
||||
|
||||
void set_au_timestamps_rap()
|
||||
{
|
||||
current_au.pts = pts;
|
||||
current_au.dts = dts;
|
||||
current_au.rap = rap;
|
||||
pts =
|
||||
dts = umax;
|
||||
rap = false;
|
||||
current_au.timestamps_rap_set = true;
|
||||
}
|
||||
};
|
||||
|
||||
template <bool avc>
|
||||
class video_stream final : public elementary_stream
|
||||
{
|
||||
public:
|
||||
video_stream(u8 channel, u32 au_max_size, dmux_pamf_base& ctx, u32 user_data, std::span<u8> au_queue_buffer) : elementary_stream(channel, au_max_size, ctx, user_data, 0, au_queue_buffer) {}
|
||||
video_stream(utils::serial& ar, u8 channel, dmux_pamf_base& ctx) : elementary_stream(ar, channel, ctx, 0) {}
|
||||
|
||||
private:
|
||||
u32 parse_stream(std::span<const u8> stream) override;
|
||||
u32 parse_stream_header([[maybe_unused]] std::span<const u8> pes_packet_data, [[maybe_unused]] s8 pts_dts_flag) override { return 0; }
|
||||
[[nodiscard]] std::pair<u8, u8> get_stream_id() const override { return { 0xe0 | channel, 0 }; }
|
||||
};
|
||||
|
||||
class lpcm_stream final : public elementary_stream
|
||||
{
|
||||
public:
|
||||
lpcm_stream(u8 channel, u32 au_max_size, dmux_pamf_base& ctx, u32 user_data, std::span<u8> au_queue_buffer) : elementary_stream(channel, au_max_size, ctx, user_data, 3, au_queue_buffer) {}
|
||||
lpcm_stream(utils::serial& ar, u8 channel, dmux_pamf_base& ctx) : elementary_stream(ar, channel, ctx, 3) {}
|
||||
|
||||
private:
|
||||
u32 parse_stream(std::span<const u8> stream) override;
|
||||
u32 parse_stream_header(std::span<const u8> pes_packet_data, [[maybe_unused]] s8 pts_dts_flag) override;
|
||||
[[nodiscard]] std::pair<u8, u8> get_stream_id() const override { return { 0xbd, 0x40 | channel }; }
|
||||
};
|
||||
|
||||
template <bool ac3>
|
||||
class audio_stream final : public elementary_stream
|
||||
{
|
||||
public:
|
||||
audio_stream(u8 channel, u32 au_max_size, dmux_pamf_base& ctx, u32 user_data, std::span<u8> au_queue_buffer) : elementary_stream(channel, au_max_size, ctx, user_data, 0, au_queue_buffer) {}
|
||||
audio_stream(utils::serial& ar, u8 channel, dmux_pamf_base& ctx) : elementary_stream(ar, channel, ctx, 0) {}
|
||||
|
||||
private:
|
||||
static constexpr be_t<u16> SYNC_WORD = ac3 ? 0x0b77 : 0x0fd0;
|
||||
static constexpr u8 ATRACX_ATS_HEADER_SIZE = 8;
|
||||
static constexpr u16 AC3_FRMSIZE_TABLE[3][38] =
|
||||
{
|
||||
{ 0x40, 0x40, 0x50, 0x50, 0x60, 0x60, 0x70, 0x70, 0x80, 0x80, 0xa0, 0xa0, 0xc0, 0xc0, 0xe0, 0xe0, 0x100, 0x100, 0x140, 0x140, 0x180, 0x180, 0x1c0, 0x1c0, 0x200, 0x200, 0x280, 0x280, 0x300, 0x300, 0x380, 0x380, 0x400, 0x400, 0x480, 0x480, 0x500, 0x500 },
|
||||
{ 0x45, 0x46, 0x57, 0x58, 0x68, 0x69, 0x79, 0x7a, 0x8b, 0x8c, 0xae, 0xaf, 0xd0, 0xd1, 0xf3, 0xf4, 0x116, 0x117, 0x15c, 0x15d, 0x1a1, 0x1a2, 0x1e7, 0x1e8, 0x22d, 0x22e, 0x2b8, 0x2b9, 0x343, 0x344, 0x3cf, 0x3d0, 0x45a, 0x45b, 0x4e5, 0x4e6, 0x571, 0x572 },
|
||||
{ 0x60, 0x60, 0x78, 0x78, 0x90, 0x90, 0xa8, 0xa8, 0xc0, 0xc0, 0xf0, 0xf0, 0x120, 0x120, 0x150, 0x150, 0x180, 0x180, 0x1e0, 0x1e0, 0x240, 0x240, 0x2a0, 0x2a0, 0x300, 0x300, 0x3c0, 0x3c0, 0x480, 0x480, 0x540, 0x540, 0x600, 0x600, 0x6c0, 0x6c0, 0x780, 0x780 }
|
||||
};
|
||||
|
||||
u32 parse_stream(std::span<const u8> stream) override;
|
||||
u32 parse_stream_header(std::span<const u8> pes_packet_data, [[maybe_unused]] s8 pts_dts_flag) override { return parse_audio_stream_header<0xffff>(pes_packet_data); }
|
||||
[[nodiscard]] std::pair<u8, u8> get_stream_id() const override { return { 0xbd, (ac3 ? 0x30 : 0x00) | channel }; }
|
||||
};
|
||||
|
||||
class user_data_stream final : public elementary_stream
|
||||
{
|
||||
public:
|
||||
user_data_stream(u8 channel, u32 au_max_size, dmux_pamf_base& ctx, u32 user_data, std::span<u8> au_queue_buffer) : elementary_stream(channel, au_max_size, ctx, user_data, 0, au_queue_buffer) {}
|
||||
user_data_stream(utils::serial& ar, u8 channel, dmux_pamf_base& ctx) : elementary_stream(ar, channel, ctx, 0) {}
|
||||
|
||||
private:
|
||||
u32 parse_stream(std::span<const u8> stream) override;
|
||||
u32 parse_stream_header(std::span<const u8> pes_packet_data, s8 pts_dts_flag) override;
|
||||
[[nodiscard]] std::pair<u8, u8> get_stream_id() const override { return { 0xbd, 0x20 | channel }; }
|
||||
};
|
||||
|
||||
|
||||
enum class state : u8
|
||||
{
|
||||
initial,
|
||||
elementary_stream,
|
||||
prog_end
|
||||
}
|
||||
state = state::initial;
|
||||
|
||||
bool demux_done_notified = true; // User was successfully notified that the stream has been consumed
|
||||
|
||||
u8 pack_es_type_idx = umax; // Elementary stream type in the current pack
|
||||
u8 pack_es_channel = 0; // Elementary stream channel in the current pack
|
||||
|
||||
bool raw_es = false; // Indicates that the input stream is a raw elementary stream instead of a multiplexed MPEG program stream. If set to true, MPEG-PS related parsing will be skipped
|
||||
|
||||
std::optional<std::span<const u8>> stream; // The stream to be demultiplexed, provided by the user
|
||||
|
||||
std::unique_ptr<elementary_stream> elementary_streams[5][0x10]; // One for each possible type and channel
|
||||
};
|
||||
|
||||
// Implementation of the SPU thread
|
||||
class dmux_pamf_spu_context : dmux_pamf_base
|
||||
{
|
||||
public:
|
||||
static constexpr u32 id_base = 0;
|
||||
static constexpr u32 id_step = 1;
|
||||
static constexpr u32 id_count = 0x400;
|
||||
SAVESTATE_INIT_POS(std::numeric_limits<f64>::max()); // Doesn't depend on or is a dependency of anything
|
||||
|
||||
dmux_pamf_spu_context(vm::ptr<dmux_pamf_hle_spurs_queue<DmuxPamfCommand, 1>> cmd_queue, vm::ptr<dmux_pamf_hle_spurs_queue<be_t<u32>, 1>> cmd_result_queue,
|
||||
vm::ptr<dmux_pamf_hle_spurs_queue<DmuxPamfStreamInfo, 1>> stream_info_queue, vm::ptr<dmux_pamf_hle_spurs_queue<DmuxPamfEvent, 132>> event_queue)
|
||||
: cmd_queue(cmd_queue), cmd_result_queue(cmd_result_queue), stream_info_queue(stream_info_queue), event_queue(event_queue)
|
||||
{
|
||||
}
|
||||
|
||||
explicit dmux_pamf_spu_context(utils::serial& ar)
|
||||
: cmd_queue(ar.pop<vm::ptr<dmux_pamf_hle_spurs_queue<DmuxPamfCommand, 1>>>())
|
||||
, cmd_result_queue(vm::ptr<dmux_pamf_hle_spurs_queue<be_t<u32>, 1>>::make(cmd_queue.addr() + sizeof(dmux_pamf_hle_spurs_queue<DmuxPamfCommand, 1>)))
|
||||
, stream_info_queue(vm::ptr<dmux_pamf_hle_spurs_queue<DmuxPamfStreamInfo, 1>>::make(cmd_result_queue.addr() + sizeof(dmux_pamf_hle_spurs_queue<be_t<u32>, 1>)))
|
||||
, event_queue(vm::ptr<dmux_pamf_hle_spurs_queue<DmuxPamfEvent, 132>>::make(stream_info_queue.addr() + sizeof(dmux_pamf_hle_spurs_queue<DmuxPamfStreamInfo, 1>)))
|
||||
, new_stream(ar.pop<bool>())
|
||||
{
|
||||
save_base(ar);
|
||||
max_enqueued_events += 2 * get_enabled_es_count();
|
||||
}
|
||||
|
||||
void save(utils::serial& ar);
|
||||
|
||||
void operator()(); // cellSpursMain()
|
||||
static constexpr auto thread_name = "HLE PAMF demuxer SPU thread"sv;
|
||||
|
||||
private:
|
||||
// These are globals in the SPU thread
|
||||
const vm::ptr<dmux_pamf_hle_spurs_queue<DmuxPamfCommand, 1>> cmd_queue;
|
||||
const vm::ptr<dmux_pamf_hle_spurs_queue<be_t<u32>, 1>> cmd_result_queue;
|
||||
const vm::ptr<dmux_pamf_hle_spurs_queue<DmuxPamfStreamInfo, 1>> stream_info_queue;
|
||||
const vm::ptr<dmux_pamf_hle_spurs_queue<DmuxPamfEvent, 132>> event_queue;
|
||||
bool wait_for_au_queue = false;
|
||||
bool wait_for_event_queue = false;
|
||||
bool event_queue_was_too_full = false; // Sent to the PPU thread
|
||||
u8 max_enqueued_events = 4; // 4 + 2 * number of enabled elementary streams
|
||||
|
||||
// This is a local variable in cellSpursMain(), needs to be saved for savestates
|
||||
bool new_stream = false;
|
||||
|
||||
bool get_next_cmd(DmuxPamfCommand& lhs, bool new_stream) const;
|
||||
bool send_event(auto&&... args) const;
|
||||
|
||||
// The events are sent to the PPU thread via the event_queue
|
||||
bool on_au_found(u8 stream_id, u8 private_stream_id, u32 user_data, std::span<const u8> au, u64 pts, u64 dts, bool rap, u8 au_specific_info_size, std::array<u8, 0x10> au_specific_info_buf) override
|
||||
{
|
||||
return !((wait_for_event_queue = !send_event(DmuxPamfEventType::au_found, stream_id, private_stream_id, vm::get_addr(au.data()), std::bit_cast<CellCodecTimeStamp>(static_cast<be_t<u64>>(pts)),
|
||||
std::bit_cast<CellCodecTimeStamp>(static_cast<be_t<u64>>(dts)), 0, static_cast<u32>(au.size()), au_specific_info_size, au_specific_info_buf, user_data, rap)));
|
||||
}
|
||||
bool on_demux_done() override { return !((wait_for_event_queue = !send_event(DmuxPamfEventType::demux_done))); }
|
||||
void on_fatal_error() override { send_event(DmuxPamfEventType::fatal_error); }
|
||||
bool on_flush_done(u8 stream_id, u8 private_stream_id, u32 user_data) override { return send_event(DmuxPamfEventType::flush_done, stream_id, private_stream_id, user_data); } // The "flush done" event does not set wait_for_event_queue if the queue is full
|
||||
bool on_prog_end() override { return !((wait_for_event_queue = !send_event(DmuxPamfEventType::prog_end_code))); }
|
||||
void on_au_queue_full() override { wait_for_au_queue = true; }
|
||||
};
|
||||
|
||||
using dmux_pamf_spu_thread = named_thread<dmux_pamf_spu_context>;
|
||||
|
||||
|
||||
// PPU thread
|
||||
|
||||
struct CellDmuxPamfAttr
|
||||
{
|
||||
be_t<u32> maxEnabledEsNum;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Cell/ErrorCodes.h"
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
|
||||
// Error Codes
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ struct serial_ver_t
|
|||
std::set<u16> compatible_versions;
|
||||
};
|
||||
|
||||
static std::array<serial_ver_t, 27> s_serial_versions;
|
||||
static std::array<serial_ver_t, 28> s_serial_versions;
|
||||
|
||||
#define SERIALIZATION_VER(name, identifier, ...) \
|
||||
\
|
||||
|
|
@ -85,6 +85,7 @@ SERIALIZATION_VER(LLE, 24, 1)
|
|||
SERIALIZATION_VER(HLE, 25, 1)
|
||||
|
||||
SERIALIZATION_VER(cellSysutil, 26, 1, 2/*AVC2 Muting,Volume*/)
|
||||
SERIALIZATION_VER(cellDmuxPamf, 27, 1)
|
||||
|
||||
template <>
|
||||
void fmt_class_string<std::remove_cvref_t<decltype(s_serial_versions)>>::format(std::string& out, u64 arg)
|
||||
|
|
|
|||
Loading…
Reference in a new issue