Vita3K/vita3k/modules/SceAvPlayer/SceAvPlayer.cpp
2025-01-19 18:51:43 -03:00

523 lines
20 KiB
C++

// Vita3K emulator project
// Copyright (C) 2025 Vita3K team
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <module/module.h>
#include <codec/state.h>
#include <io/functions.h>
#include <kernel/state.h>
#include <util/lock_and_find.h>
#include <util/log.h>
#include <algorithm>
// Defines stop/pause behaviour. If true, GetVideo/AudioData will return false when stopped.
constexpr bool REJECT_DATA_ON_PAUSE = true;
// Uses a catchup video style if lag causes the video to go behind.
constexpr bool CATCHUP_VIDEO_PLAYBACK = true;
/*
typedef Ptr<void> (*SceAvPlayerAllocator)(uint32_t arguments, uint32_t alignment, uint32_t size);
typedef void (*SceAvPlayerDeallocator)(uint32_t arguments, Ptr<void> memory);
typedef int32_t (*SceAvPlayerOpenFile)(uint32_t arguments, Ptr<const char> filename);
typedef int32_t (*SceAvPlayerCloseFile)(uint32_t arguments);
typedef int32_t (*SceAvPlayerReadFile)(uint32_t arguments, Ptr<uint8_t> buffer, uint64_t offset, uint32_t size);
typedef uint64_t (*SceAvPlayerGetFileSize)(uint32_t arguments);
typedef void (*SceAvPlayerEventCallback)(uint32_t arguments, int32_t event_id, int32_t source_id, Ptr<void> event_data);
*/
struct PlayerInfoState;
typedef std::shared_ptr<PlayerInfoState> PlayerPtr;
typedef std::map<SceUID, PlayerPtr> PlayerStates;
struct AvPlayerState {
std::mutex mutex;
PlayerStates players;
};
struct SceAvPlayerMemoryAllocator {
uint32_t user_data = 0;
// All of these should be cast to SceAvPlayerAllocator or SceAvPlayerDeallocator types.
Ptr<void> general_allocator;
Ptr<void> general_deallocator;
Ptr<void> texture_allocator;
Ptr<void> texture_deallocator;
};
struct SceAvPlayerFileManager {
uint32_t user_data = 0;
// Cast to SceAvPlayerOpenFile, SceAvPlayerCloseFile, SceAvPlayerReadFile and SceAvPlayerGetFileSize.
Ptr<void> open_file;
Ptr<void> close_file;
Ptr<void> read_file;
Ptr<void> file_size;
};
struct SceAvPlayerEventManager {
uint32_t user_data = 0;
// Cast to SceAvPlayerEventCallback.
Ptr<void> event_callback;
};
struct PlayerInfoState {
PlayerState player;
// Framebuffer count is defined in info. I'm being safe now and forcing it to 4 (even though its usually 2).
constexpr static uint32_t RING_BUFFER_COUNT = 4;
uint32_t video_buffer_ring_index = 0;
uint32_t video_buffer_size = 0;
std::array<Ptr<uint8_t>, RING_BUFFER_COUNT> video_buffer;
uint32_t audio_buffer_ring_index = 0;
uint32_t audio_buffer_size = 0;
std::array<Ptr<uint8_t>, RING_BUFFER_COUNT> audio_buffer;
bool do_loop = false;
bool paused = false;
uint64_t last_frame_time = 0;
SceAvPlayerMemoryAllocator memory_allocator;
SceAvPlayerFileManager file_manager;
SceAvPlayerEventManager event_manager;
};
enum class DebugLevel {
NONE,
INFO,
WARNINGS,
ALL,
};
struct SceAvPlayerInfo {
SceAvPlayerMemoryAllocator memory_allocator;
SceAvPlayerFileManager file_manager;
SceAvPlayerEventManager event_manager;
DebugLevel debug_level;
uint32_t base_priority;
int32_t frame_buffer_count;
int32_t auto_start;
uint32_t unknown0;
};
struct SceAvPlayerAudio {
uint16_t channels;
uint16_t unknown;
uint32_t sample_rate;
uint32_t size;
char language[4];
};
struct SceAvPlayerVideo {
uint32_t width;
uint32_t height;
float aspect_ratio;
char language[4];
};
struct SceAvPlayerTextPosition {
uint16_t top;
uint16_t left;
uint16_t bottom;
uint16_t right;
};
struct SceAvPlayerTimedText {
char language[4];
uint16_t text_size;
uint16_t font_size;
SceAvPlayerTextPosition position;
};
union SceAvPlayerStreamDetails {
SceAvPlayerAudio audio;
SceAvPlayerVideo video;
SceAvPlayerTimedText text;
};
struct SceAvPlayerFrameInfo {
Ptr<uint8_t> data;
uint32_t unknown;
uint64_t timestamp;
SceAvPlayerStreamDetails stream_details;
};
enum class MediaType {
VIDEO,
AUDIO,
};
struct SceAvPlayerStreamInfo {
MediaType stream_type;
uint32_t unknown;
SceAvPlayerStreamDetails stream_details;
};
enum SceAvPlayerErrorCode : uint32_t {
SCE_AVPLAYER_ERROR_ILLEGAL_ADDR = 0x806a0001,
SCE_AVPLAYER_ERROR_INVALID_ARGUMENT = 0x806a0002,
SCE_AVPLAYER_ERROR_NOT_ENOUGH_MEMORY = 0x806a0003,
SCE_AVPLAYER_ERROR_INVALID_EVENT = 0x806a0004,
SCE_AVPLAYER_WAR_FILE_NONINTERLEAVED = 0x806a00a0,
SCE_AVPLAYER_WAR_LOOPING_BACK = 0x806a00a1,
SCE_AVPLAYER_WAR_JUMP_COMPLETE = 0x806a00a3
};
enum SceAvPlayerState {
SCE_AVPLAYER_STATE_UNKNOWN = 0,
SCE_AVPLAYER_STATE_STOP = 1,
SCE_AVPLAYER_STATE_READY = 2,
SCE_AVPLAYER_STATE_PLAY = 3,
SCE_AVPLAYER_STATE_PAUSE = 4,
SCE_AVPLAYER_STATE_BUFFERING = 5,
SCE_AVPLAYER_TIMED_TEXT_DELIVERY = 16,
SCE_AVPLAYER_WARNING_ID = 32,
SCE_AVPLAYER_ENCRYPTION = 48
};
static inline uint64_t current_time() {
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count();
}
static Ptr<uint8_t> get_buffer(const PlayerPtr &player, MediaType media_type,
MemState &mem, uint32_t size, bool new_frame = true) {
uint32_t &buffer_size = media_type == MediaType::VIDEO ? player->video_buffer_size : player->audio_buffer_size;
uint32_t &ring_index = media_type == MediaType::VIDEO ? player->video_buffer_ring_index : player->audio_buffer_ring_index;
auto &buffers = media_type == MediaType::VIDEO ? player->video_buffer : player->audio_buffer;
if (buffer_size < size) {
buffer_size = size;
for (uint32_t a = 0; a < PlayerInfoState::RING_BUFFER_COUNT; a++) {
if (buffers[a])
free(mem, buffers[a]);
std::string alloc_name = fmt::format("AvPlayer {} Media Ring {}",
media_type == MediaType::VIDEO ? "Video" : "Audio", a);
buffers[a] = alloc(mem, size, alloc_name.c_str());
}
}
if (new_frame)
ring_index++;
Ptr<uint8_t> buffer = buffers[ring_index % PlayerInfoState::RING_BUFFER_COUNT];
return buffer;
}
void run_event_callback(EmuEnvState &emuenv, const ThreadStatePtr &thread, const PlayerPtr &player_info, uint32_t event_id, uint32_t source_id, Ptr<void> event_data) {
if (player_info->event_manager.event_callback) {
thread->run_callback(player_info->event_manager.event_callback.address(), { player_info->event_manager.user_data, event_id, source_id, event_data.address() });
}
}
EXPORT(int32_t, sceAvPlayerAddSource, SceUID player_handle, Ptr<const char> path) {
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
if (!player_info) {
return RET_ERROR(SCE_AVPLAYER_ERROR_INVALID_ARGUMENT);
}
const auto thread = lock_and_find(thread_id, emuenv.kernel.threads, emuenv.kernel.mutex);
auto file_path = expand_path(emuenv.io, path.get(emuenv.mem), emuenv.pref_path);
if (!fs::exists(file_path) && player_info->file_manager.open_file && player_info->file_manager.close_file && player_info->file_manager.read_file && player_info->file_manager.file_size) {
fs::create_directories(emuenv.cache_path);
// Create temp media file
const auto temp_file_path = emuenv.cache_path / "temp_vita_media.mp4";
fs::ofstream temp_file(temp_file_path, std::ios::out | std::ios::binary);
const Address buf = alloc(emuenv.mem, KiB(512), "AvPlayer buffer");
const auto buf_ptr = Ptr<char>(buf).get(emuenv.mem);
thread->run_callback(player_info->file_manager.open_file.address(), { player_info->file_manager.user_data, path.address() });
// TODO: support file_size > 4GB (callback function returns uint64_t, but I dont know how to get high dword of uint64_t)
const uint32_t file_size = thread->run_callback(player_info->file_manager.file_size.address(), { player_info->file_manager.user_data });
auto remaining = file_size;
uint32_t offset = 0;
while (remaining) {
const auto buf_size = std::min((uint32_t)KiB(512), remaining);
// zero in 5 parameter means high dword of uint64_t parameter. see previous todo
thread->run_callback(player_info->file_manager.read_file.address(), { player_info->file_manager.user_data, buf, offset, 0, buf_size });
temp_file.write(buf_ptr, buf_size);
offset += buf_size;
remaining -= buf_size;
}
free(emuenv.mem, buf);
temp_file.close();
thread->run_callback(player_info->file_manager.close_file.address(), { player_info->file_manager.user_data });
if (fs::file_size(temp_file_path) != file_size) {
LOG_ERROR("File is corrupted or incomplete: {}", temp_file_path);
return -1;
}
file_path = temp_file_path;
}
player_info->player.queue(file_path.string());
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_BUFFERING, 0, Ptr<void>(0)); // may be important for sound
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_READY, 0, Ptr<void>(0));
return 0;
}
EXPORT(int, sceAvPlayerClose, SceUID player_handle) {
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
const auto thread = emuenv.kernel.get_thread(thread_id);
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_STOP, 0, Ptr<void>(0));
std::lock_guard<std::mutex> lock(state->mutex);
state->players.erase(player_handle);
return 0;
}
EXPORT(uint64_t, sceAvPlayerCurrentTime, SceUID player_handle) {
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
return player_info->player.last_timestamp;
}
EXPORT(int, sceAvPlayerDisableStream) {
return UNIMPLEMENTED();
}
EXPORT(int32_t, sceAvPlayerStreamCount, SceUID player_handle) {
STUBBED("ALWAYS RETURN 2 (VIDEO AND AUDIO)");
return 2;
}
EXPORT(int32_t, sceAvPlayerEnableStream, SceUID player_handle, uint32_t stream_no) {
if (player_handle == 0) {
return SCE_AVPLAYER_ERROR_ILLEGAL_ADDR;
}
if (stream_no > (uint32_t)(CALL_EXPORT(sceAvPlayerStreamCount, player_handle))) {
return SCE_AVPLAYER_ERROR_INVALID_ARGUMENT;
}
return UNIMPLEMENTED();
}
EXPORT(bool, sceAvPlayerGetAudioData, SceUID player_handle, SceAvPlayerFrameInfo *frame_info) {
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
if (!player_info) {
return false;
}
Ptr<uint8_t> buffer;
if (player_info->paused) {
if (REJECT_DATA_ON_PAUSE) {
return false;
} else {
// This is probably incorrect and will make weird noises :P
buffer = get_buffer(player_info, MediaType::AUDIO, emuenv.mem,
player_info->player.last_sample_count * sizeof(int16_t) * player_info->player.last_channels, true);
}
} else {
std::vector<int16_t> data = player_info->player.receive_audio();
if (data.empty())
return false;
buffer = get_buffer(player_info, MediaType::AUDIO, emuenv.mem, (uint32_t)data.size() * sizeof(int16_t), false);
std::memcpy(buffer.get(emuenv.mem), data.data(), data.size() * sizeof(int16_t));
}
frame_info->timestamp = player_info->player.last_timestamp;
frame_info->stream_details.audio.channels = player_info->player.last_channels;
frame_info->stream_details.audio.sample_rate = player_info->player.last_sample_rate;
frame_info->stream_details.audio.size = player_info->player.last_channels * player_info->player.last_sample_count * sizeof(int16_t);
frame_info->data = buffer;
strcpy(frame_info->stream_details.audio.language, "ENG");
return true;
}
EXPORT(uint32_t, sceAvPlayerGetStreamInfo, SceUID player_handle, SceUInt32 stream_no, SceAvPlayerStreamInfo *stream_info) {
if (!stream_info) {
return SCE_AVPLAYER_ERROR_ILLEGAL_ADDR;
}
if (player_handle == 0) {
return SCE_AVPLAYER_ERROR_ILLEGAL_ADDR;
}
STUBBED("ALWAYS SUSPECTS 2 STREAMS: VIDEO AND AUDIO");
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
if (stream_no == 0) { // suspect always two streams: audio and video //first is video
DecoderSize size = player_info->player.get_size();
stream_info->stream_type = MediaType::VIDEO;
stream_info->stream_details.video.width = size.width;
stream_info->stream_details.video.height = size.height;
stream_info->stream_details.video.aspect_ratio = static_cast<float>(size.width) / static_cast<float>(size.height);
strcpy(stream_info->stream_details.video.language, "ENG");
} else if (stream_no == 1) { // audio
player_info->player.receive_audio(); // TODO: Get audio info without skipping data frames
stream_info->stream_type = MediaType::AUDIO;
stream_info->stream_details.audio.channels = player_info->player.last_channels;
stream_info->stream_details.audio.sample_rate = player_info->player.last_sample_rate;
stream_info->stream_details.audio.size = player_info->player.last_channels * player_info->player.last_sample_count * sizeof(int16_t);
strcpy(stream_info->stream_details.audio.language, "ENG");
} else {
return SCE_AVPLAYER_ERROR_INVALID_ARGUMENT;
}
return 0;
}
EXPORT(bool, sceAvPlayerGetVideoData, SceUID player_handle, SceAvPlayerFrameInfo *frame_info) {
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
if (!player_info) {
return false;
}
Ptr<uint8_t> buffer;
DecoderSize size = player_info->player.get_size();
uint64_t framerate = player_info->player.get_framerate_microseconds();
// needs new frame
if (player_info->last_frame_time + framerate < current_time()) {
if (CATCHUP_VIDEO_PLAYBACK)
player_info->last_frame_time += framerate;
else
player_info->last_frame_time = current_time();
if (player_info->paused) {
if (REJECT_DATA_ON_PAUSE)
return false;
else
buffer = get_buffer(player_info, MediaType::VIDEO, emuenv.mem, H264DecoderState::buffer_size(size), false);
} else {
buffer = get_buffer(player_info, MediaType::VIDEO, emuenv.mem, H264DecoderState::buffer_size(size), true);
std::vector<uint8_t> data = player_info->player.receive_video();
std::memcpy(buffer.get(emuenv.mem), data.data(), data.size());
}
} else {
buffer = get_buffer(player_info, MediaType::VIDEO, emuenv.mem, H264DecoderState::buffer_size(size), false);
}
// TODO: catch eof error and call
// uint32_t buf = SCE_AVPLAYER_ERROR_MAYBE_EOF;
// run_event_callback(emuenv, thread_id, player_info, SCE_AVPLAYER_STATE_ERROR, 0, &buf);
frame_info->timestamp = player_info->player.last_timestamp;
frame_info->stream_details.video.width = size.width;
frame_info->stream_details.video.height = size.height;
frame_info->stream_details.video.aspect_ratio = static_cast<float>(size.width) / static_cast<float>(size.height);
strcpy(frame_info->stream_details.video.language, "ENG");
frame_info->data = buffer;
return true;
}
EXPORT(bool, sceAvPlayerGetVideoDataEx, SceUID player_handle, SceAvPlayerFrameInfo *frame_info) {
STUBBED("Use GetVideoData");
return CALL_EXPORT(sceAvPlayerGetVideoData, player_handle, frame_info);
}
EXPORT(SceUID, sceAvPlayerInit, SceAvPlayerInfo *info) {
emuenv.kernel.obj_store.create<AvPlayerState>();
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
SceUID player_handle = emuenv.kernel.get_next_uid();
PlayerPtr player = std::make_shared<PlayerInfoState>();
state->players[player_handle] = player;
player->last_frame_time = current_time();
player->memory_allocator = info->memory_allocator;
player->file_manager = info->file_manager;
player->event_manager = info->event_manager;
// Result is defined as a void *, but I just call it SceUID because it is easier to deal with. Same size.
return player_handle;
}
EXPORT(bool, sceAvPlayerIsActive, SceUID player_handle) {
if (player_handle == 0) {
return false;
}
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
return !player_info->player.video_playing.empty();
}
EXPORT(int, sceAvPlayerJumpToTime) {
return UNIMPLEMENTED();
}
EXPORT(int, sceAvPlayerPause, SceUID player_handle) {
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
player_info->paused = true;
const auto thread = emuenv.kernel.get_thread(thread_id);
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_PAUSE, 0, Ptr<void>(0));
return 0;
}
EXPORT(int, sceAvPlayerPostInit) {
return UNIMPLEMENTED();
}
EXPORT(int, sceAvPlayerResume, SceUID player_handle) {
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
if (!player_info->paused) {
const auto thread = emuenv.kernel.get_thread(thread_id);
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_PLAY, 0, Ptr<void>(0));
}
player_info->paused = false;
return 0;
}
EXPORT(int, sceAvPlayerSetLooping, SceUID player_handle, bool do_loop) {
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
player_info->do_loop = do_loop;
return STUBBED("LOOPING NOT IMPLEMENTED");
}
EXPORT(int, sceAvPlayerSetTrickSpeed) {
return UNIMPLEMENTED();
}
EXPORT(int, sceAvPlayerStart, SceUID player_handle) {
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
if (!player_info->player.videos_queue.empty()) {
player_info->player.pop_video();
}
const auto thread = emuenv.kernel.get_thread(thread_id);
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_PLAY, 0, Ptr<void>(0));
return 0;
}
EXPORT(int, sceAvPlayerStop, SceUID player_handle) {
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, emuenv.kernel.mutex);
player_info->player.free_video();
const auto thread = emuenv.kernel.get_thread(thread_id);
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_STOP, 0, Ptr<void>(0));
return 0;
}