cellMusic/Decode: implement playlist shuffle and repeat

This commit is contained in:
Megamouse 2022-07-08 22:43:13 +02:00
parent 683fa2a392
commit c40439ae6b
13 changed files with 790 additions and 247 deletions

View File

@ -264,6 +264,7 @@ target_sources(rpcs3_emu PRIVATE
Cell/Modules/cellMsgDialog.cpp
Cell/Modules/cellMusic.cpp
Cell/Modules/cellMusicDecode.cpp
Cell/Modules/cellMusicSelectionContext.cpp
Cell/Modules/cellMusicExport.cpp
Cell/Modules/cellNetAoi.cpp
Cell/Modules/cellNetCtl.cpp

View File

@ -72,7 +72,7 @@ struct music_state
vm::ptr<void> userData{};
std::mutex mtx;
std::shared_ptr<music_handler_base> handler;
music_selection_context current_selection_context;
music_selection_context current_selection_context{};
SAVESTATE_INIT_POS(16);
@ -118,17 +118,17 @@ error_code cell_music_select_contents()
{
sysutil_register_cb([&music, dir_path, vfs_dir_path, info, status](ppu_thread& ppu) -> s32
{
std::lock_guard lock(music.mtx);
const u32 result = status >= 0 ? u32{CELL_OK} : u32{CELL_MUSIC_CANCELED};
if (result == CELL_OK)
{
music_selection_context context;
context.content_path = info.path;
context.content_path = dir_path + info.path.substr(vfs_dir_path.length()); // We need the non-vfs path here
context.content_type = fs::is_dir(info.path) ? CELL_SEARCH_CONTENTTYPE_MUSICLIST : CELL_SEARCH_CONTENTTYPE_MUSIC;
music_selection_context context{};
context.set_playlist(info.path);
// TODO: context.repeat_mode = CELL_SEARCH_REPEATMODE_NONE;
// TODO: context.context_option = CELL_SEARCH_CONTEXTOPTION_NONE;
music.current_selection_context = context;
cellMusic.success("Media list dialog: selected entry '%s'", context.content_path);
music.current_selection_context.create_playlist(music_selection_context::get_next_hash());
cellMusic.success("Media list dialog: selected entry '%s'", context.playlist.front());
}
else
{
@ -178,21 +178,8 @@ error_code cellMusicSetSelectionContext2(vm::ptr<CellMusicSelectionContext> cont
}
const u32 status = result ? u32{CELL_OK} : u32{CELL_MUSIC2_ERROR_INVALID_CONTEXT};
if (result)
{
cellMusic.success("cellMusicSetSelectionContext2: new selection context = %s", music.current_selection_context.to_string());
}
else
{
std::string dahex;
for (usz i = 0; i < CELL_MUSIC_SELECTION_CONTEXT_SIZE; i++)
{
fmt::append(dahex, " %.2x", context.data[i]);
}
cellMusic.todo("cellMusicSetSelectionContext2: failed. context = %s", dahex);
}
if (result) cellMusic.success("cellMusicSetSelectionContext2: new selection context = %s", music.current_selection_context.to_string());
else cellMusic.todo("cellMusicSetSelectionContext2: failed. context = %s", music_selection_context::context_to_hex(context));
music.func(ppu, CELL_MUSIC2_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(status), music.userData);
return CELL_OK;
@ -248,17 +235,17 @@ error_code cellMusicSetSelectionContext(vm::ptr<CellMusicSelectionContext> conte
if (!music.func)
return CELL_MUSIC_ERROR_GENERIC;
sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32
sysutil_register_cb([context = *context, &music](ppu_thread& ppu) -> s32
{
bool result = false;
{
std::lock_guard lock(music.mtx);
result = music.current_selection_context.set(*context);
result = music.current_selection_context.set(context);
}
const u32 status = result ? u32{CELL_OK} : u32{CELL_MUSIC_ERROR_INVALID_CONTEXT};
if (result) cellMusic.success("cellMusicSetSelectionContext: new selection context = %s)", music.current_selection_context.to_string());
else cellMusic.todo("cellMusicSetSelectionContext: failed. context = %s)", context->data);
else cellMusic.todo("cellMusicSetSelectionContext: failed. context = %s)", music_selection_context::context_to_hex(context));
music.func(ppu, CELL_MUSIC_EVENT_SET_SELECTION_CONTEXT_RESULT, vm::addr_t(status), music.userData);
return CELL_OK;
@ -404,6 +391,7 @@ error_code cellMusicGetSelectionContext2(vm::ptr<CellMusicSelectionContext> cont
auto& music = g_fxo->get<music_state>();
std::lock_guard lock(music.mtx);
*context = music.current_selection_context.get();
cellMusic.success("cellMusicGetSelectionContext2: selection context = %s", music.current_selection_context.to_string());
@ -450,31 +438,53 @@ error_code cellMusicSetPlaybackCommand2(s32 command, vm::ptr<void> param)
sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32
{
// TODO: play proper song when the context is a playlist
std::string path;
{
std::lock_guard lock(music.mtx);
path = vfs::get(music.current_selection_context.content_path);
cellMusic.notice("cellMusicSetPlaybackCommand2: current vfs path: '%s' (unresolved='%s')", path, music.current_selection_context.content_path);
}
switch (command)
{
case CELL_MUSIC2_PB_CMD_STOP:
music.handler->stop();
break;
case CELL_MUSIC2_PB_CMD_PLAY:
music.handler->play(path);
break;
case CELL_MUSIC2_PB_CMD_PAUSE:
music.handler->pause();
break;
case CELL_MUSIC2_PB_CMD_PLAY:
case CELL_MUSIC2_PB_CMD_NEXT:
music.handler->play(path);
break;
case CELL_MUSIC2_PB_CMD_PREV:
{
std::string path;
bool playback_finished = false;
{
std::lock_guard lock(music.mtx);
const std::vector<std::string>& playlist = music.current_selection_context.playlist;
u32 next_track = music.current_selection_context.current_track;
if (command != CELL_MUSIC2_PB_CMD_PLAY)
{
next_track = music.current_selection_context.step_track(command == CELL_MUSIC2_PB_CMD_NEXT);
}
if (next_track < playlist.size())
{
path = vfs::get(playlist.at(next_track));
cellMusic.notice("cellMusicSetPlaybackCommand2: current vfs path: '%s' (unresolved='%s')", path, playlist.at(next_track));
}
else
{
playback_finished = true;
}
}
if (playback_finished)
{
// TODO: is CELL_MUSIC2_PLAYBACK_FINISHED correct here ?
cellMusic.notice("cellMusicSetPlaybackCommand2: no more tracks to play");
music.handler->stop();
music.func(ppu, CELL_MUSIC2_EVENT_SET_PLAYBACK_COMMAND_RESULT, vm::addr_t(CELL_MUSIC2_PLAYBACK_FINISHED), music.userData);
return CELL_OK;
}
music.handler->play(path);
break;
}
case CELL_MUSIC2_PB_CMD_FASTFORWARD:
music.handler->fast_forward();
break;
@ -506,31 +516,53 @@ error_code cellMusicSetPlaybackCommand(s32 command, vm::ptr<void> param)
sysutil_register_cb([=, &music](ppu_thread& ppu) -> s32
{
// TODO: play proper song when the context is a playlist
std::string path;
{
std::lock_guard lock(music.mtx);
path = vfs::get(music.current_selection_context.content_path);
cellMusic.notice("cellMusicSetPlaybackCommand: current vfs path: '%s' (unresolved='%s')", path, music.current_selection_context.content_path);
}
switch (command)
{
case CELL_MUSIC_PB_CMD_STOP:
music.handler->stop();
break;
case CELL_MUSIC_PB_CMD_PLAY:
music.handler->play(path);
break;
case CELL_MUSIC_PB_CMD_PAUSE:
music.handler->pause();
break;
case CELL_MUSIC_PB_CMD_PLAY:
case CELL_MUSIC_PB_CMD_NEXT:
music.handler->play(path);
break;
case CELL_MUSIC_PB_CMD_PREV:
{
std::string path;
bool playback_finished = false;
{
std::lock_guard lock(music.mtx);
const std::vector<std::string>& playlist = music.current_selection_context.playlist;
u32 next_track = music.current_selection_context.current_track;
if (command != CELL_MUSIC_PB_CMD_PLAY)
{
next_track = music.current_selection_context.step_track(command == CELL_MUSIC_PB_CMD_NEXT);
}
if (next_track < playlist.size())
{
path = vfs::get(playlist.at(next_track));
cellMusic.notice("cellMusicSetPlaybackCommand: current vfs path: '%s' (unresolved='%s')", path, playlist.at(next_track));
}
else
{
playback_finished = true;
}
}
if (playback_finished)
{
// TODO: is CELL_MUSIC_PLAYBACK_FINISHED correct here ?
cellMusic.notice("cellMusicSetPlaybackCommand: no more tracks to play");
music.handler->stop();
music.func(ppu, CELL_MUSIC_EVENT_SET_PLAYBACK_COMMAND_RESULT, vm::addr_t(CELL_MUSIC_PLAYBACK_FINISHED), music.userData);
return CELL_OK;
}
music.handler->play(path);
break;
}
case CELL_MUSIC_PB_CMD_FASTFORWARD:
music.handler->fast_forward();
break;

View File

@ -142,56 +142,30 @@ struct CellMusicSelectionContext
struct music_selection_context
{
char magic[4] = "SUS";
u32 content_type{0};
u32 repeat_mode{0};
u32 context_option{0};
std::string content_path;
std::string hash;
CellSearchContentType content_type = CELL_SEARCH_CONTENTTYPE_MUSIC;
CellSearchRepeatMode repeat_mode = CELL_SEARCH_REPEATMODE_NONE;
CellSearchContextOption context_option = CELL_SEARCH_CONTEXTOPTION_NONE;
u32 first_track{0};
u32 current_track{0};
std::vector<std::string> playlist;
static constexpr u32 max_depth = 2; // root + 1 folder + file
static constexpr const char* target_file_type = "Music Playlist";
static constexpr const char* target_version = "1.0";
static std::string get_next_hash();
static std::string context_to_hex(const CellMusicSelectionContext& context);
bool set(const CellMusicSelectionContext& in)
{
if (memcmp(in.data, magic, sizeof(magic)) != 0)
{
return false;
}
bool set(const CellMusicSelectionContext& in);
CellMusicSelectionContext get() const;
u32 pos = sizeof(magic);
memcpy(&content_type, &in.data[pos], sizeof(content_type));
pos += sizeof(content_type);
repeat_mode = in.data[pos++];
context_option = in.data[pos++];
content_path = &in.data[pos];
std::string to_string() const;
std::string get_yaml_path() const;
return true;
}
CellMusicSelectionContext get() const
{
if (content_path.size() + 2 + sizeof(content_type) + sizeof(magic) > CELL_MUSIC_SELECTION_CONTEXT_SIZE)
{
fmt::throw_exception("Contents of music_selection_context are too large");
}
CellMusicSelectionContext out{};
u32 pos = 0;
std::memset(out.data, 0, CELL_MUSIC_SELECTION_CONTEXT_SIZE);
std::memcpy(out.data, magic, sizeof(magic));
pos += sizeof(magic);
std::memcpy(&out.data[pos], &content_type, sizeof(content_type));
pos += sizeof(content_type);
out.data[pos++] = repeat_mode;
out.data[pos++] = context_option;
std::memcpy(&out.data[pos], content_path.c_str(), content_path.size());
return out;
}
std::string to_string() const
{
return fmt::format("{ .magic='%s', .content_type=%d, .repeat_mode=%d, .context_option=%d, .path='%s' }", magic, content_type, repeat_mode, context_option, content_path);
}
void set_playlist(const std::string& path);
void create_playlist(const std::string& new_hash);
bool load_playlist();
u32 step_track(bool next);
// Helper
error_code find_content_id(vm::ptr<CellSearchContentId> contents_id);

View File

@ -5,6 +5,7 @@
#include "Emu/Cell/lv2/sys_spu.h"
#include "Emu/RSX/Overlays/overlay_media_list_dialog.h"
#include "Emu/VFS.h"
#include "cellMusicDecode.h"
#include "cellMusic.h"
#include "cellSearch.h"
#include "cellSpurs.h"
@ -16,25 +17,6 @@
LOG_CHANNEL(cellMusicDecode);
// Return Codes (CELL_MUSIC_DECODE2 codes are omitted if they are identical)
enum CellMusicDecodeError : u32
{
CELL_MUSIC_DECODE_CANCELED = 1,
CELL_MUSIC_DECODE_DECODE_FINISHED = 0x8002C101,
CELL_MUSIC_DECODE_ERROR_PARAM = 0x8002C102,
CELL_MUSIC_DECODE_ERROR_BUSY = 0x8002C103,
CELL_MUSIC_DECODE_ERROR_NO_ACTIVE_CONTENT = 0x8002C104,
CELL_MUSIC_DECODE_ERROR_NO_MATCH_FOUND = 0x8002C105,
CELL_MUSIC_DECODE_ERROR_INVALID_CONTEXT = 0x8002C106,
CELL_MUSIC_DECODE_ERROR_DECODE_FAILURE = 0x8002C107,
CELL_MUSIC_DECODE_ERROR_NO_MORE_CONTENT = 0x8002C108,
CELL_MUSIC_DECODE_DIALOG_OPEN = 0x8002C109,
CELL_MUSIC_DECODE_DIALOG_CLOSE = 0x8002C10A,
CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA = 0x8002C10B,
CELL_MUSIC_DECODE_NEXT_CONTENTS_READY = 0x8002C10C,
CELL_MUSIC_DECODE_ERROR_GENERIC = 0x8002C1FF,
};
template<>
void fmt_class_string<CellMusicDecodeError>::format(std::string& out, u64 arg)
{
@ -62,55 +44,6 @@ void fmt_class_string<CellMusicDecodeError>::format(std::string& out, u64 arg)
});
}
// Constants (CELL_MUSIC_DECODE2 codes are omitted if they are identical)
enum
{
CELL_MUSIC_DECODE_EVENT_STATUS_NOTIFICATION = 0,
CELL_MUSIC_DECODE_EVENT_INITIALIZE_RESULT = 1,
CELL_MUSIC_DECODE_EVENT_FINALIZE_RESULT = 2,
CELL_MUSIC_DECODE_EVENT_SELECT_CONTENTS_RESULT = 3,
CELL_MUSIC_DECODE_EVENT_SET_DECODE_COMMAND_RESULT = 4,
CELL_MUSIC_DECODE_EVENT_SET_SELECTION_CONTEXT_RESULT = 5,
CELL_MUSIC_DECODE_EVENT_UI_NOTIFICATION = 6,
CELL_MUSIC_DECODE_EVENT_NEXT_CONTENTS_READY_RESULT = 7,
CELL_MUSIC_DECODE_MODE_NORMAL = 0,
CELL_MUSIC_DECODE_CMD_STOP = 0,
CELL_MUSIC_DECODE_CMD_START = 1,
CELL_MUSIC_DECODE_CMD_NEXT = 2,
CELL_MUSIC_DECODE_CMD_PREV = 3,
CELL_MUSIC_DECODE_STATUS_DORMANT = 0,
CELL_MUSIC_DECODE_STATUS_DECODING = 1,
CELL_MUSIC_DECODE_POSITION_NONE = 0,
CELL_MUSIC_DECODE_POSITION_START = 1,
CELL_MUSIC_DECODE_POSITION_MID = 2,
CELL_MUSIC_DECODE_POSITION_END = 3,
CELL_MUSIC_DECODE_POSITION_END_LIST_END = 4,
CELL_MUSIC_DECODE2_MODE_NORMAL = 0,
CELL_MUSIC_DECODE2_SPEED_MAX = 0,
CELL_MUSIC_DECODE2_SPEED_2 = 2,
CELL_SYSUTIL_MUSIC_DECODE2_INITIALIZING_FINISHED = 1,
CELL_SYSUTIL_MUSIC_DECODE2_SHUTDOWN_FINISHED = 4, // 3(SDK103) -> 4(SDK110)
CELL_SYSUTIL_MUSIC_DECODE2_LOADING_FINISHED = 5,
CELL_SYSUTIL_MUSIC_DECODE2_UNLOADING_FINISHED = 7,
CELL_SYSUTIL_MUSIC_DECODE2_RELEASED = 9,
CELL_SYSUTIL_MUSIC_DECODE2_GRABBED = 11,
CELL_MUSIC_DECODE2_MIN_BUFFER_SIZE = 448 * 1024,
CELL_MUSIC_DECODE2_MANAGEMENT_SIZE = 64 * 1024,
CELL_MUSIC_DECODE2_PAGESIZE_64K = 64 * 1024,
CELL_MUSIC_DECODE2_PAGESIZE_1M = 1 * 1024 * 1024,
};
using CellMusicDecodeCallback = void(u32, vm::ptr<void> param, vm::ptr<void> userData);
using CellMusicDecode2Callback = void(u32, vm::ptr<void> param, vm::ptr<void> userData);
struct music_decode
{
vm::ptr<CellMusicDecodeCallback> func{};
@ -118,7 +51,7 @@ struct music_decode
music_selection_context current_selection_context{};
s32 decode_status = CELL_MUSIC_DECODE_STATUS_DORMANT;
s32 decode_command = CELL_MUSIC_DECODE_CMD_STOP;
u64 readPos = 0;
u64 read_pos = 0;
utils::audio_decoder decoder{};
shared_mutex mutex;
@ -138,21 +71,41 @@ struct music_decode
case CELL_MUSIC_DECODE_CMD_START:
{
decode_status = CELL_MUSIC_DECODE_STATUS_DECODING;
readPos = 0;
read_pos = 0;
// Decode data. The format of the decoded data is 48kHz, float 32bit, 2ch LPCM data interleaved in order from left to right.
const std::string path = vfs::get(current_selection_context.content_path);
cellMusicDecode.notice("set_decode_command(START): Setting vfs path: '%s' (unresolved='%s')", path, current_selection_context.content_path);
cellMusicDecode.notice("set_decode_command(START): context: %s", current_selection_context.to_string());
decoder.set_path(path);
music_selection_context context = current_selection_context;
for (usz i = 0; i < context.playlist.size(); i++)
{
context.playlist[i] = vfs::get(context.playlist[i]);
}
// TODO: set speed if small-memory decoding is used (music_decode2)
decoder.set_context(std::move(context));
decoder.set_swap_endianness(true);
decoder.decode();
break;
}
case CELL_MUSIC_DECODE_CMD_NEXT: // TODO: set path of next file if possible
case CELL_MUSIC_DECODE_CMD_PREV: // TODO: set path of prev file if possible
case CELL_MUSIC_DECODE_CMD_NEXT:
case CELL_MUSIC_DECODE_CMD_PREV:
{
return CELL_MUSIC_DECODE_ERROR_NO_MORE_CONTENT;
decoder.stop();
if (decoder.set_next_index(command == CELL_MUSIC_DECODE_CMD_NEXT) == umax)
{
decode_status = CELL_MUSIC_DECODE_STATUS_DORMANT;
return CELL_MUSIC_DECODE_ERROR_NO_MORE_CONTENT;
}
decoder.decode();
break;
}
default:
{
fmt::throw_exception("Unknown decode command %d", command);
}
}
return CELL_OK;
@ -163,7 +116,7 @@ struct music_decode
decoder.stop();
decode_status = CELL_MUSIC_DECODE_STATUS_DORMANT;
decode_command = CELL_MUSIC_DECODE_CMD_STOP;
readPos = 0;
read_pos = 0;
return CELL_OK;
}
};
@ -194,13 +147,13 @@ error_code cell_music_decode_select_contents()
const u32 result = status >= 0 ? u32{CELL_OK} : u32{CELL_MUSIC_DECODE_CANCELED};
if (result == CELL_OK)
{
music_selection_context context;
context.content_path = dir_path + info.path.substr(vfs_dir_path.length()); // We need the non-vfs path here
context.content_type = fs::is_dir(info.path) ? CELL_SEARCH_CONTENTTYPE_MUSICLIST : CELL_SEARCH_CONTENTTYPE_MUSIC;
music_selection_context context{};
context.set_playlist(info.path);
// TODO: context.repeat_mode = CELL_SEARCH_REPEATMODE_NONE;
// TODO: context.context_option = CELL_SEARCH_CONTEXTOPTION_NONE;
dec.current_selection_context = context;
cellMusicDecode.success("Media list dialog: selected entry '%s'", context.content_path);
dec.current_selection_context.create_playlist(music_selection_context::get_next_hash());
cellMusicDecode.success("Media list dialog: selected entry '%s'", context.playlist.front());
}
else
{
@ -217,49 +170,73 @@ template <typename Music_Decode>
error_code cell_music_decode_read(vm::ptr<void> buf, vm::ptr<u32> startTime, u64 reqSize, vm::ptr<u64> readSize, vm::ptr<s32> position)
{
if (!buf || !startTime || !position || !reqSize || !readSize)
{
return CELL_MUSIC_DECODE_ERROR_PARAM;
}
*position = CELL_MUSIC_DECODE_POSITION_NONE;
*readSize = 0;
*startTime = 0;
auto& dec = g_fxo->get<Music_Decode>();
std::lock_guard lock(dec.mutex);
std::scoped_lock slock(dec.decoder.m_mtx);
if (dec.decoder.has_error)
{
return CELL_MUSIC_DECODE_ERROR_DECODE_FAILURE;
}
if (dec.decoder.m_size == 0)
{
*position = CELL_MUSIC_DECODE_POSITION_NONE;
*readSize = 0;
return CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA;
}
if (dec.readPos == 0)
const u64 size_left = dec.decoder.m_size - dec.read_pos;
if (dec.read_pos == 0)
{
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_START, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load());
*position = CELL_MUSIC_DECODE_POSITION_START;
}
else if ((dec.readPos + reqSize) >= dec.decoder.m_size)
else if (!dec.decoder.track_fully_decoded || size_left > reqSize) // track_fully_decoded is not guarded by a mutex, but since it is set to true after the decode, it should be fine.
{
// TODO: CELL_MUSIC_DECODE_POSITION_END
*position = CELL_MUSIC_DECODE_POSITION_END_LIST_END;
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_MID, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load());
*position = CELL_MUSIC_DECODE_POSITION_MID;
}
else
{
*position = CELL_MUSIC_DECODE_POSITION_MID;
if (dec.decoder.set_next_index(true) == umax)
{
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_END_LIST_END, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load());
*position = CELL_MUSIC_DECODE_POSITION_END_LIST_END;
}
else
{
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_END, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load());
*position = CELL_MUSIC_DECODE_POSITION_END;
}
}
const u64 size_to_read = (dec.readPos + reqSize) <= dec.decoder.m_size ? reqSize : dec.decoder.m_size - dec.readPos;
std::memcpy(buf.get_ptr(), &dec.decoder.data[dec.readPos], size_to_read);
dec.readPos += size_to_read;
const u64 size_to_read = std::min(reqSize, size_left);
*readSize = size_to_read;
if (size_to_read == 0)
{
return CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA; // TODO: speculative
}
std::memcpy(buf.get_ptr(), &dec.decoder.data[dec.read_pos], size_to_read);
dec.read_pos += size_to_read;
s64 start_time_ms = 0;
if (!dec.decoder.timestamps_ms.empty())
{
start_time_ms = dec.decoder.timestamps_ms.front().second;
while (dec.decoder.timestamps_ms.size() > 1 && dec.readPos >= dec.decoder.timestamps_ms.at(1).first)
while (dec.decoder.timestamps_ms.size() > 1 && dec.read_pos >= dec.decoder.timestamps_ms.at(1).first)
{
dec.decoder.timestamps_ms.pop_front();
}
@ -267,6 +244,29 @@ error_code cell_music_decode_read(vm::ptr<void> buf, vm::ptr<u32> startTime, u64
*startTime = static_cast<u32>(start_time_ms); // startTime is milliseconds
switch (*position)
{
case CELL_MUSIC_DECODE_POSITION_END_LIST_END:
{
// Reset the decoder and the decode status
ensure(dec.set_decode_command(CELL_MUSIC_DECODE_CMD_STOP) == CELL_OK);
dec.read_pos = 0;
break;
}
case CELL_MUSIC_DECODE_POSITION_END:
{
dec.read_pos = 0;
dec.decoder.clear();
dec.decoder.track_fully_consumed = true;
dec.decoder.track_fully_consumed.notify_one();
break;
}
default:
{
break;
}
}
cellMusicDecode.trace("cell_music_decode_read(size_to_read=%d, samples=%d, start_time_ms=%d)", size_to_read, size_to_read / sizeof(u64), start_time_ms);
return CELL_OK;
@ -360,7 +360,11 @@ error_code cellMusicDecodeSetDecodeCommand(s32 command)
if (!dec.func)
return CELL_MUSIC_DECODE_ERROR_GENERIC;
const error_code result = dec.set_decode_command(command);
error_code result = CELL_OK;
{
std::scoped_lock slock(dec.decoder.m_mtx);
result = dec.set_decode_command(command);
}
sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32
{
@ -402,6 +406,7 @@ error_code cellMusicDecodeGetSelectionContext(vm::ptr<CellMusicSelectionContext>
auto& dec = g_fxo->get<music_decode>();
std::lock_guard lock(dec.mutex);
*context = dec.current_selection_context.get();
cellMusicDecode.warning("cellMusicDecodeGetSelectionContext: selection_context = %s", dec.current_selection_context.to_string());
@ -423,7 +428,7 @@ error_code cellMusicDecodeSetSelectionContext(vm::ptr<CellMusicSelectionContext>
const bool result = dec.current_selection_context.set(*context);
if (result) cellMusicDecode.warning("cellMusicDecodeSetSelectionContext: new selection_context = %s", dec.current_selection_context.to_string());
else cellMusicDecode.error("cellMusicDecodeSetSelectionContext: failed. context = '%s'", context->data);
else cellMusicDecode.error("cellMusicDecodeSetSelectionContext: failed. context = '%s'", music_selection_context::context_to_hex(*context));
sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32
{
@ -540,7 +545,11 @@ error_code cellMusicDecodeSetDecodeCommand2(s32 command)
if (!dec.func)
return CELL_MUSIC_DECODE_ERROR_GENERIC;
const error_code result = dec.set_decode_command(command);
error_code result = CELL_OK;
{
std::scoped_lock slock(dec.decoder.m_mtx);
result = dec.set_decode_command(command);
}
sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32
{
@ -582,6 +591,7 @@ error_code cellMusicDecodeGetSelectionContext2(vm::ptr<CellMusicSelectionContext
auto& dec = g_fxo->get<music_decode2>();
std::lock_guard lock(dec.mutex);
*context = dec.current_selection_context.get();
cellMusicDecode.warning("cellMusicDecodeGetSelectionContext2: selection context = %s)", dec.current_selection_context.to_string());
@ -603,7 +613,7 @@ error_code cellMusicDecodeSetSelectionContext2(vm::ptr<CellMusicSelectionContext
const bool result = dec.current_selection_context.set(*context);
if (result) cellMusicDecode.warning("cellMusicDecodeSetSelectionContext2: new selection_context = %s", dec.current_selection_context.to_string());
else cellMusicDecode.error("cellMusicDecodeSetSelectionContext2: failed. context = '%s'", context->data);
else cellMusicDecode.error("cellMusicDecodeSetSelectionContext2: failed. context = '%s'", music_selection_context::context_to_hex(*context));
sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32
{

View File

@ -0,0 +1,69 @@
#pragma once
// Return Codes (CELL_MUSIC_DECODE2 codes are omitted if they are identical)
enum CellMusicDecodeError : u32
{
CELL_MUSIC_DECODE_CANCELED = 1,
CELL_MUSIC_DECODE_DECODE_FINISHED = 0x8002C101,
CELL_MUSIC_DECODE_ERROR_PARAM = 0x8002C102,
CELL_MUSIC_DECODE_ERROR_BUSY = 0x8002C103,
CELL_MUSIC_DECODE_ERROR_NO_ACTIVE_CONTENT = 0x8002C104,
CELL_MUSIC_DECODE_ERROR_NO_MATCH_FOUND = 0x8002C105,
CELL_MUSIC_DECODE_ERROR_INVALID_CONTEXT = 0x8002C106,
CELL_MUSIC_DECODE_ERROR_DECODE_FAILURE = 0x8002C107,
CELL_MUSIC_DECODE_ERROR_NO_MORE_CONTENT = 0x8002C108,
CELL_MUSIC_DECODE_DIALOG_OPEN = 0x8002C109,
CELL_MUSIC_DECODE_DIALOG_CLOSE = 0x8002C10A,
CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA = 0x8002C10B,
CELL_MUSIC_DECODE_NEXT_CONTENTS_READY = 0x8002C10C,
CELL_MUSIC_DECODE_ERROR_GENERIC = 0x8002C1FF,
};
// Constants (CELL_MUSIC_DECODE2 codes are omitted if they are identical)
enum
{
CELL_MUSIC_DECODE_EVENT_STATUS_NOTIFICATION = 0,
CELL_MUSIC_DECODE_EVENT_INITIALIZE_RESULT = 1,
CELL_MUSIC_DECODE_EVENT_FINALIZE_RESULT = 2,
CELL_MUSIC_DECODE_EVENT_SELECT_CONTENTS_RESULT = 3,
CELL_MUSIC_DECODE_EVENT_SET_DECODE_COMMAND_RESULT = 4,
CELL_MUSIC_DECODE_EVENT_SET_SELECTION_CONTEXT_RESULT = 5,
CELL_MUSIC_DECODE_EVENT_UI_NOTIFICATION = 6,
CELL_MUSIC_DECODE_EVENT_NEXT_CONTENTS_READY_RESULT = 7,
CELL_MUSIC_DECODE_MODE_NORMAL = 0,
CELL_MUSIC_DECODE_CMD_STOP = 0,
CELL_MUSIC_DECODE_CMD_START = 1,
CELL_MUSIC_DECODE_CMD_NEXT = 2,
CELL_MUSIC_DECODE_CMD_PREV = 3,
CELL_MUSIC_DECODE_STATUS_DORMANT = 0,
CELL_MUSIC_DECODE_STATUS_DECODING = 1,
CELL_MUSIC_DECODE_POSITION_NONE = 0,
CELL_MUSIC_DECODE_POSITION_START = 1,
CELL_MUSIC_DECODE_POSITION_MID = 2,
CELL_MUSIC_DECODE_POSITION_END = 3,
CELL_MUSIC_DECODE_POSITION_END_LIST_END = 4,
CELL_MUSIC_DECODE2_MODE_NORMAL = 0,
CELL_MUSIC_DECODE2_SPEED_MAX = 0,
CELL_MUSIC_DECODE2_SPEED_2 = 2,
CELL_SYSUTIL_MUSIC_DECODE2_INITIALIZING_FINISHED = 1,
CELL_SYSUTIL_MUSIC_DECODE2_SHUTDOWN_FINISHED = 4, // 3(SDK103) -> 4(SDK110)
CELL_SYSUTIL_MUSIC_DECODE2_LOADING_FINISHED = 5,
CELL_SYSUTIL_MUSIC_DECODE2_UNLOADING_FINISHED = 7,
CELL_SYSUTIL_MUSIC_DECODE2_RELEASED = 9,
CELL_SYSUTIL_MUSIC_DECODE2_GRABBED = 11,
CELL_MUSIC_DECODE2_MIN_BUFFER_SIZE = 448 * 1024,
CELL_MUSIC_DECODE2_MANAGEMENT_SIZE = 64 * 1024,
CELL_MUSIC_DECODE2_PAGESIZE_64K = 64 * 1024,
CELL_MUSIC_DECODE2_PAGESIZE_1M = 1 * 1024 * 1024,
};
using CellMusicDecodeCallback = void(u32, vm::ptr<void> param, vm::ptr<void> userData);
using CellMusicDecode2Callback = void(u32, vm::ptr<void> param, vm::ptr<void> userData);

View File

@ -0,0 +1,333 @@
#include "stdafx.h"
#include "cellMusic.h"
#include "util/yaml.hpp"
#include "Emu/VFS.h"
#include <random>
// This is just a helper and not a real cell entity
LOG_CHANNEL(cellMusicSelectionContext);
bool music_selection_context::set(const CellMusicSelectionContext& in)
{
if (memcmp(in.data, magic, sizeof(magic)) != 0)
{
return false;
}
u32 pos = sizeof(magic);
hash = &in.data[pos];
return load_playlist();
}
CellMusicSelectionContext music_selection_context::get() const
{
if (hash.size() + sizeof(magic) > CELL_MUSIC_SELECTION_CONTEXT_SIZE)
{
fmt::throw_exception("Contents of music_selection_context are too large");
}
CellMusicSelectionContext out{};
u32 pos = 0;
std::memset(out.data, 0, CELL_MUSIC_SELECTION_CONTEXT_SIZE);
std::memcpy(out.data, magic, sizeof(magic));
pos += sizeof(magic);
std::memcpy(&out.data[pos], hash.c_str(), hash.size());
return out;
}
std::string music_selection_context::to_string() const
{
return fmt::format("{ .magic='%s', .content_type=%d, .repeat_mode=%d, .context_option=%d, .first_track=%d, .tracks=%d, .hash='%s' }",
magic, static_cast<u32>(content_type), static_cast<u32>(repeat_mode), static_cast<u32>(context_option), first_track, playlist.size(), hash);
}
std::string music_selection_context::get_next_hash()
{
static u64 hash_counter = 0;
return fmt::format("music_selection_context_%d", hash_counter++);
}
std::string music_selection_context::context_to_hex(const CellMusicSelectionContext& context)
{
std::string dahex;
for (usz i = 0; i < CELL_MUSIC_SELECTION_CONTEXT_SIZE; i++)
{
fmt::append(dahex, " %.2x", context.data[i]);
}
return dahex;
}
std::string music_selection_context::get_yaml_path() const
{
std::string path = fs::get_cache_dir() + "cache/playlists/";
if (!fs::create_path(path))
{
cellMusicSelectionContext.fatal("Failed to create path: %s (%s)", path, fs::g_tls_error);
}
return path + hash + ".yml";
}
void music_selection_context::set_playlist(const std::string& path)
{
playlist.clear();
const std::string dir_path = "/dev_hdd0/music";
const std::string vfs_dir_path = vfs::get("/dev_hdd0/music");
if (fs::is_dir(path))
{
content_type = CELL_SEARCH_CONTENTTYPE_MUSICLIST;
for (auto&& dir_entry : fs::dir{path})
{
dir_entry.name = vfs::unescape(dir_entry.name);
if (dir_entry.name == "." || dir_entry.name == "..")
{
continue;
}
playlist.push_back(dir_path + std::string(path + "/" + dir_entry.name).substr(vfs_dir_path.length()));
}
}
else
{
content_type = CELL_SEARCH_CONTENTTYPE_MUSIC;
playlist.push_back(dir_path + path.substr(vfs_dir_path.length()));
}
}
void music_selection_context::create_playlist(const std::string& new_hash)
{
hash = new_hash;
const std::string yaml_path = get_yaml_path();
cellMusicSelectionContext.notice("Saving music playlist file %s", yaml_path);
YAML::Emitter out;
out << YAML::BeginMap;
out << "Version" << target_version;
out << "FileType" << target_file_type;
out << "ContentType" << content_type;
out << "ContextOption" << context_option;
out << "RepeatMode" << repeat_mode;
out << "FirstTrack" << first_track;
out << "Tracks" << YAML::BeginSeq;
for (const std::string& track : playlist)
{
out << track;
}
out << YAML::EndSeq;
out << YAML::EndMap;
fs::pending_file file(yaml_path);
if (!file.file || (file.file.write(out.c_str(), out.size()), !file.commit()))
{
cellMusicSelectionContext.error("Failed to create music playlist file %s (%s)", yaml_path, fs::g_tls_error);
}
}
bool music_selection_context::load_playlist()
{
playlist.clear();
const std::string path = get_yaml_path();
cellMusicSelectionContext.notice("Loading music playlist file %s", path);
std::string content;
{
// Load patch file
fs::file file{path};
if (!file)
{
cellMusicSelectionContext.error("Failed to load music playlist file %s: %s", path, fs::g_tls_error);
return false;
}
content = file.to_string();
}
auto [root, error] = yaml_load(content);
if (!error.empty() || !root)
{
cellMusicSelectionContext.error("Failed to load music playlist file %s:\n%s", path, error);
return false;
}
std::string err;
const std::string version = get_yaml_node_value<std::string>(root["Version"], err);
if (!err.empty())
{
cellMusicSelectionContext.error("No Version entry found. Error: '%s' (file: %s)", err, path);
return false;
}
if (version != target_version)
{
cellMusicSelectionContext.error("Version '%s' does not match music playlist target '%s' (file: %s)", version, target_version, path);
return false;
}
const std::string file_type = get_yaml_node_value<std::string>(root["FileType"], err);
if (!err.empty())
{
cellMusicSelectionContext.error("No FileType entry found. Error: '%s' (file: %s)", err, path);
return false;
}
if (file_type != target_file_type)
{
cellMusicSelectionContext.error("FileType '%s' does not match music playlist target '%s' (file: %s)", file_type, target_file_type, path);
return false;
}
content_type = static_cast<CellSearchContentType>(get_yaml_node_value<u32>(root["ContentType"], err));
if (!err.empty())
{
cellMusicSelectionContext.error("No ContentType entry found. Error: '%s' (file: %s)", err, path);
return false;
}
context_option = static_cast<CellSearchContextOption>(get_yaml_node_value<u32>(root["ContextOption"], err));
if (!err.empty())
{
cellMusicSelectionContext.error("No ContextOption entry found. Error: '%s' (file: %s)", err, path);
return false;
}
repeat_mode = static_cast<CellSearchRepeatMode>(get_yaml_node_value<u32>(root["RepeatMode"], err));
if (!err.empty())
{
cellMusicSelectionContext.error("No RepeatMode entry found. Error: '%s' (file: %s)", err, path);
return false;
}
first_track = get_yaml_node_value<u32>(root["FirstTrack"], err);
if (!err.empty())
{
cellMusicSelectionContext.error("No FirstTrack entry found. Error: '%s' (file: %s)", err, path);
return false;
}
const YAML::Node& track_node = root["Tracks"];
if (!track_node || track_node.Type() != YAML::NodeType::Sequence)
{
cellMusicSelectionContext.error("No Tracks entry found or Tracks is not a Sequence. (file: %s)", path);
return false;
}
for (usz i = 0; i < track_node.size(); i++)
{
playlist.push_back(track_node[i].Scalar());
}
return true;
}
u32 music_selection_context::step_track(bool next)
{
if (playlist.empty())
{
cellMusicSelectionContext.error("No tracks to play...");
current_track = umax;
return umax;
}
switch (repeat_mode)
{
case CELL_SEARCH_REPEATMODE_NONE:
{
if (next)
{
// Try to play the next track.
if (++current_track >= playlist.size())
{
// We are at the end of the playlist.
cellMusicSelectionContext.notice("No more tracks to play in playlist...");
current_track = umax;
return umax;
}
}
else
{
// Try to play the previous track.
if (current_track == 0)
{
// We are at the start of the playlist.
cellMusicSelectionContext.notice("No more tracks to play in playlist...");
current_track = umax;
return umax;
}
current_track--;
}
break;
}
case CELL_SEARCH_REPEATMODE_REPEAT1:
{
// Keep decoding the same track.
break;
}
case CELL_SEARCH_REPEATMODE_ALL:
{
if (next)
{
// Play the next track. Start with the first track if we reached the end of the playlist.
current_track = (current_track + 1) % playlist.size();
}
else
{
// Play the previous track. Start with the last track if we reached the start of the playlist.
if (current_track == 0)
{
current_track = playlist.size() - 1;
}
else
{
current_track--;
}
}
break;
}
case CELL_SEARCH_REPEATMODE_NOREPEAT1:
{
// We are done. We only wanted to decode a single track.
cellMusicSelectionContext.notice("No more tracks to play...");
current_track = umax;
return umax;
}
default:
{
fmt::throw_exception("Unknown repeat mode %d", static_cast<u32>(repeat_mode));
}
}
if (context_option == CELL_SEARCH_CONTEXTOPTION_SHUFFLE && repeat_mode == CELL_SEARCH_REPEATMODE_ALL && playlist.size() > 1)
{
if (next ? current_track == 0 : current_track == (playlist.size() - 1))
{
// We reached the first or last track again. Let's shuffle!
cellMusicSelectionContext.notice("Shuffling playlist...");
auto engine = std::default_random_engine{};
std::shuffle(std::begin(playlist), std::end(playlist), engine);
}
}
return current_track;
}

View File

@ -71,7 +71,9 @@ struct search_info
struct search_content_t
{
CellSearchContentType type = CELL_SEARCH_CONTENTTYPE_NONE;
CellSearchTimeInfo timeInfo;
CellSearchRepeatMode repeat_mode = CELL_SEARCH_REPEATMODE_NONE;
CellSearchContextOption context_option = CELL_SEARCH_CONTEXTOPTION_NONE;
CellSearchTimeInfo time_info;
CellSearchContentInfoPath infoPath;
union
{
@ -1555,7 +1557,7 @@ error_code cellSearchGetContentIdByOffset(CellSearchId searchId, s32 offset, vm:
if (outTimeInfo)
{
std::memcpy(outTimeInfo.get_ptr(), &content_id.second->timeInfo, sizeof(content_id.second->timeInfo));
std::memcpy(outTimeInfo.get_ptr(), &content_id.second->time_info, sizeof(content_id.second->time_info));
}
}
@ -1644,7 +1646,7 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
return CELL_SEARCH_ERROR_CONTENT_NOT_FOUND;
}
music_selection_context context;
music_selection_context context{};
// Use the first track in order to get info about this search
const auto& first_content_id = searchObject->content_ids[0];
@ -1685,8 +1687,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
}
// Use the found content
context.content_path = content->second->infoPath.contentPath;
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path=%s", content_hash, +content->second->type, context.content_path);
context.playlist.push_back(content->second->infoPath.contentPath);
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path=%s", content_hash, +content->second->type, context.playlist.back());
}
else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
{
@ -1696,15 +1698,17 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
else if (option == CELL_SEARCH_CONTEXTOPTION_SHUFFLE)
{
// Select random track
// TODO: whole playlist
std::shared_ptr<search_content_t> content = get_random_content();
context.content_path = content->infoPath.contentPath;
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning random track: Type=0x%x, Path=%s", content_hash, +content->type, context.content_path);
context.playlist.push_back(content->infoPath.contentPath);
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning random track: Type=0x%x, Path=%s", content_hash, +content->type, context.playlist.back());
}
else
{
// Select the first track by default
context.content_path = first_content->infoPath.contentPath;
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path=%s", content_hash, +first_content->type, context.content_path);
// TODO: whole playlist
context.playlist.push_back(first_content->infoPath.contentPath);
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path=%s", content_hash, +first_content->type, context.playlist.back());
}
}
else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
@ -1715,27 +1719,34 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
else if (option == CELL_SEARCH_CONTEXTOPTION_SHUFFLE)
{
// Select random track
// TODO: whole playlist
std::shared_ptr<search_content_t> content = get_random_content();
context.content_path = content->infoPath.contentPath;
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning random track: Type=0x%x, Path=%s", +content->type, context.content_path);
context.playlist.push_back(content->infoPath.contentPath);
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning random track: Type=0x%x, Path=%s", +content->type, context.playlist.back());
}
else
{
// Select the first track by default
context.content_path = first_content->infoPath.contentPath;
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path=%s", +first_content->type, context.content_path);
// TODO: whole playlist
context.playlist.push_back(first_content->infoPath.contentPath);
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path=%s", +first_content->type, context.playlist.back());
}
context.content_type = first_content->type;
context.repeat_mode = repeatMode;
context.context_option = option;
// TODO: context.first_track = ?;
// Resolve hashed paths
if (auto found = search.content_links.find(context.content_path); found != search.content_links.end())
for (std::string& track : context.playlist)
{
context.content_path = found->second;
if (auto found = search.content_links.find(track); found != search.content_links.end())
{
track = found->second;
}
}
context.create_playlist(music_selection_context::get_next_hash());
*outContext = context.get();
cellSearch.success("cellSearchGetMusicSelectionContext: found selection context: %d", context.to_string());
@ -1780,15 +1791,21 @@ error_code cellSearchGetMusicSelectionContextOfSingleTrack(vm::cptr<CellSearchCo
return CELL_SEARCH_ERROR_INVALID_CONTENTTYPE;
}
music_selection_context context;
context.content_path = content_info->infoPath.contentPath;
music_selection_context context{};
context.playlist.push_back(content_info->infoPath.contentPath);
context.repeat_mode = content_info->repeat_mode;
context.context_option = content_info->context_option;
// Resolve hashed paths
if (auto found = search.content_links.find(context.content_path); found != search.content_links.end())
for (std::string& track : context.playlist)
{
context.content_path = found->second;
if (auto found = search.content_links.find(track); found != search.content_links.end())
{
track = found->second;
}
}
context.create_playlist(music_selection_context::get_next_hash());
*outContext = context.get();
cellSearch.success("cellSearchGetMusicSelectionContextOfSingleTrack: found selection context: %s", context.to_string());
@ -2066,14 +2083,33 @@ error_code music_selection_context::find_content_id(vm::ptr<CellSearchContentId>
// Search for the content that matches our current selection
auto& content_map = g_fxo->get<content_id_map>();
const u64 hash = std::hash<std::string>()(content_path);
auto found = content_map.map.find(hash);
if (found != content_map.map.end())
std::shared_ptr<search_content_t> found_content;
u64 hash = 0;
for (const std::string& track : playlist)
{
if (content_type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
{
hash = std::hash<std::string>()(fs::get_parent_dir(track));
}
else
{
hash = std::hash<std::string>()(track);
}
if (auto found = content_map.map.find(hash); found != content_map.map.end())
{
found_content = found->second;
break;
}
}
if (found_content)
{
// TODO: check if the content type is correct
const u128 content_id_128 = found->first;
const u128 content_id_128 = hash;
std::memcpy(contents_id->data, &content_id_128, CELL_SEARCH_CONTENT_ID_SIZE);
cellSearch.warning("find_content_id: found existing content for %s (path control: '%s')", to_string(), found->second->infoPath.contentPath);
cellSearch.warning("find_content_id: found existing content for %s (path control: '%s')", to_string(), found_content->infoPath.contentPath);
return CELL_OK;
}
@ -2116,6 +2152,8 @@ error_code music_selection_context::find_content_id(vm::ptr<CellSearchContentId>
// TODO: check for actual content inside the directory
std::shared_ptr<search_content_t> curr_find = std::make_shared<search_content_t>();
curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSICLIST;
curr_find->repeat_mode = repeat_mode;
curr_find->context_option = context_option;
if (dir_path.length() > CELL_SEARCH_PATH_LEN_MAX)
{
@ -2170,6 +2208,8 @@ error_code music_selection_context::find_content_id(vm::ptr<CellSearchContentId>
std::shared_ptr<search_content_t> curr_find = std::make_shared<search_content_t>();
curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSIC;
curr_find->repeat_mode = repeat_mode;
curr_find->context_option = context_option;
if (file_path.length() > CELL_SEARCH_PATH_LEN_MAX)
{

View File

@ -282,6 +282,7 @@ namespace rsx
{
rsx_log.notice("parse_media_recursive: Found %d matches in directory '%s'", current_entry.children.size(), media_path);
current_entry.type = media_list_dialog::media_type::directory;
current_entry.info.path = media_path;
}
}
else if (type == media_list_dialog::media_type::photo)
@ -352,32 +353,32 @@ namespace rsx
ensure(static_cast<size_t>(result) < media->children.size());
media = &media->children[result];
rsx_log.notice("Left media list dialog with entry: '%s' ('%s')", media->name, media->path);
continue;
}
else if (result == user_interface::selection_code::canceled)
if (result == user_interface::selection_code::canceled)
{
if (media == &root_media_entry)
{
rsx_log.notice("Media list dialog canceled");
break;
}
else
{
focused = media->index;
media = media->parent;
result = 0;
rsx_log.notice("Media list dialog moving to parent directory (focused=%d)", focused);
}
}
else
{
rsx_log.error("Left media list dialog with error: '%d'", result);
focused = media->index;
media = media->parent;
result = 0;
rsx_log.notice("Media list dialog moving to parent directory (focused=%d)", focused);
continue;
}
rsx_log.error("Left media list dialog with error: '%d'", result);
break;
}
else
{
media = nullptr;
result = user_interface::selection_code::canceled;
rsx_log.error("Media selection is only possible when the native user interface is enabled in the settings. The action will be canceled.");
}
media = nullptr;
result = user_interface::selection_code::canceled;
rsx_log.error("Media selection is only possible when the native user interface is enabled in the settings. The action will be canceled.");
break;
}
if (result >= 0 && media && media->type == type)

View File

@ -61,6 +61,7 @@
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="Emu\cache_utils.cpp" />
<ClCompile Include="Emu\Cell\Modules\cellMusicSelectionContext.cpp" />
<ClCompile Include="Emu\Cell\Modules\libfs_utility_init.cpp" />
<ClCompile Include="Emu\Cell\Modules\sys_crashdump.cpp" />
<ClCompile Include="Emu\Io\camera_config.cpp" />
@ -470,6 +471,7 @@
<ClInclude Include="Emu\cache_utils.hpp" />
<ClInclude Include="Emu\Cell\lv2\sys_crypto_engine.h" />
<ClInclude Include="Emu\Cell\Modules\cellCrossController.h" />
<ClInclude Include="Emu\Cell\Modules\cellMusicDecode.h" />
<ClInclude Include="Emu\Cell\Modules\cellRec.h" />
<ClInclude Include="Emu\Cell\Modules\cellRemotePlay.h" />
<ClInclude Include="Emu\Cell\Modules\cellSsl.h" />
@ -843,4 +845,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -1087,6 +1087,9 @@
<ClCompile Include="Emu\savestate_utils.cpp">
<Filter>Emu</Filter>
</ClCompile>
<ClCompile Include="Emu\Cell\Modules\cellMusicSelectionContext.cpp">
<Filter>Emu\Cell\Modules</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Crypto\aes.h">
@ -2164,6 +2167,9 @@
<ClInclude Include="Emu\RSX\Overlays\overlay_cursor.h">
<Filter>Emu\GPU\RSX\Overlays</Filter>
</ClInclude>
<ClInclude Include="Emu\Cell\Modules\cellMusicDecode.h">
<Filter>Emu\Cell\Modules</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl">
@ -2194,4 +2200,4 @@
<Filter>Emu\GPU\RSX\Program\Snippets</Filter>
</None>
</ItemGroup>
</Project>
</Project>

View File

@ -2,6 +2,9 @@
#include "media_utils.h"
#include "logs.hpp"
#include "Utilities/StrUtil.h"
#include "Emu/Cell/Modules/cellSearch.h"
#include <random>
#ifdef _MSC_VER
#pragma warning(push, 0)
@ -196,9 +199,9 @@ namespace utils
stop();
}
void audio_decoder::set_path(const std::string& path)
void audio_decoder::set_context(music_selection_context context)
{
m_path = path;
m_context = std::move(context);
}
void audio_decoder::set_swap_endianness(bool swapped)
@ -206,27 +209,41 @@ namespace utils
m_swap_endianness = swapped;
}
void audio_decoder::clear()
{
track_fully_decoded = false;
track_fully_consumed = false;
has_error = false;
m_size = 0;
duration_ms = 0;
timestamps_ms.clear();
data.clear();
}
void audio_decoder::stop()
{
if (m_thread)
{
auto& thread = *m_thread;
thread = thread_state::aborting;
track_fully_consumed = true;
track_fully_consumed.notify_one();
thread();
m_thread.reset();
}
has_error = false;
m_size = 0;
timestamps_ms.clear();
data.clear();
clear();
}
void audio_decoder::decode()
{
stop();
m_thread = std::make_unique<named_thread<std::function<void()>>>("Music Decode Thread", [this, path = m_path]()
media_log.notice("audio_decoder: %d entries in playlist. Start decoding...", m_context.playlist.size());
const auto decode_track = [this](const std::string& path)
{
media_log.notice("audio_decoder: decoding %s", path);
scoped_av av;
// Get format from audio file
@ -411,9 +428,61 @@ namespace utils
if (buffer)
av_free(buffer);
media_log.trace("audio_decoder: decoded frame_count=%d buffer_size=%d timestamp_us=%d", frame_count, buffer_size, av.frame->best_effort_timestamp);
media_log.notice("audio_decoder: decoded frame_count=%d buffer_size=%d timestamp_us=%d", frame_count, buffer_size, av.frame->best_effort_timestamp);
}
}
};
m_thread = std::make_unique<named_thread<std::function<void()>>>("Music Decode Thread", [this, decode_track]()
{
for (const std::string& track : m_context.playlist)
{
media_log.notice("audio_decoder: playlist entry: %s", track);
}
if (m_context.playlist.empty())
{
media_log.error("audio_decoder: Can not play empty playlist");
has_error = true;
return;
}
m_context.current_track = m_context.first_track;
if (m_context.context_option == CELL_SEARCH_CONTEXTOPTION_SHUFFLE && m_context.playlist.size() > 1)
{
// Shuffle once if necessary
media_log.notice("audio_decoder: shuffling initial playlist...");
auto engine = std::default_random_engine{};
std::shuffle(std::begin(m_context.playlist), std::end(m_context.playlist), engine);
}
while (thread_ctrl::state() != thread_state::aborting)
{
ensure(m_context.current_track < m_context.playlist.size());
media_log.notice("audio_decoder: about to decode: %s (index=%d)", m_context.playlist.at(m_context.current_track), m_context.current_track);
decode_track(m_context.playlist.at(m_context.current_track));
track_fully_decoded = true;
if (has_error)
{
media_log.notice("audio_decoder: stopping with error...");
break;
}
// Let's only decode one track at a time. Wait for the consumer to finish reading the track.
media_log.notice("audio_decoder: waiting until track is consumed...");
thread_ctrl::wait_on(track_fully_consumed, false);
track_fully_consumed = false;
}
media_log.notice("audio_decoder: finished playlist");
});
}
u32 audio_decoder::set_next_index(bool next)
{
return m_context.step_track(next);
}
}

View File

@ -7,6 +7,7 @@
#include <thread>
#include "Utilities/StrUtil.h"
#include "Utilities/Thread.h"
#include "Emu/Cell/Modules/cellMusic.h"
namespace utils
{
@ -49,22 +50,26 @@ namespace utils
audio_decoder();
~audio_decoder();
void set_path(const std::string& path);
void set_context(music_selection_context context);
void set_swap_endianness(bool swapped);
void clear();
void stop();
void decode();
u32 set_next_index(bool next);
std::mutex m_mtx;
shared_mutex m_mtx;
const s32 sample_rate = 48000;
std::vector<u8> data;
atomic_t<u64> m_size = 0;
atomic_t<u64> duration_ms = 0;
atomic_t<bool> track_fully_decoded{false};
atomic_t<bool> track_fully_consumed{false};
atomic_t<bool> has_error{false};
std::deque<std::pair<u64, u64>> timestamps_ms;
private:
bool m_swap_endianness = false;
std::string m_path;
music_selection_context m_context{};
std::unique_ptr<named_thread<std::function<void()>>> m_thread;
};
}

View File

@ -80,4 +80,5 @@ template u32 get_yaml_node_value<u32>(YAML::Node, std::string&);
template u64 get_yaml_node_value<u64>(YAML::Node, std::string&);
template s64 get_yaml_node_value<s64>(YAML::Node, std::string&);
template f64 get_yaml_node_value<f64>(YAML::Node, std::string&);
template std::string get_yaml_node_value<std::string>(YAML::Node, std::string&);
template cheat_info get_yaml_node_value<cheat_info>(YAML::Node, std::string&);