diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index f87b4349a1..d45dace1ca 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -770,8 +770,8 @@ namespace gem if constexpr (use_gain) { dst0[0] = static_cast(std::clamp(r * gain_r, 0.0f, 255.0f)); - dst0[1] = static_cast(std::clamp(b * gain_b, 0.0f, 255.0f)); - dst0[2] = static_cast(std::clamp(g * gain_g, 0.0f, 255.0f)); + dst0[1] = static_cast(std::clamp(g * gain_g, 0.0f, 255.0f)); + dst0[2] = static_cast(std::clamp(b * gain_b, 0.0f, 255.0f)); } else { @@ -822,8 +822,8 @@ namespace gem if constexpr (use_gain) { dst0[0] = static_cast(std::clamp(r * gain_r, 0.0f, 255.0f)); - dst0[1] = static_cast(std::clamp(b * gain_b, 0.0f, 255.0f)); - dst0[2] = static_cast(std::clamp(g * gain_g, 0.0f, 255.0f)); + dst0[1] = static_cast(std::clamp(g * gain_g, 0.0f, 255.0f)); + dst0[2] = static_cast(std::clamp(b * gain_b, 0.0f, 255.0f)); } else { @@ -845,6 +845,53 @@ namespace gem debayer_raw8_impl(src, dst, alpha, gain_r, gain_g, gain_b); } + template + static inline void debayer_raw8_downscale_impl(const u8* src, u8* dst, u8 alpha, f32 gain_r, f32 gain_g, f32 gain_b) + { + constexpr u32 in_pitch = 640; + constexpr u32 out_pitch = 320 * 4; + + // Simple debayer + for (s32 y = 0; y < 240; y++) + { + const u8* src0 = src + y * 2 * in_pitch; + const u8* src1 = src0 + in_pitch; + + u8* dst0 = dst + y * out_pitch; + + for (s32 x = 0; x < 320; x++, dst0 += 4, src0 += 2, src1 += 2) + { + const u8 b = src0[0]; + const u8 g0 = src0[1]; + const u8 g1 = src1[0]; + const u8 r = src1[1]; + const u8 g = (g0 + g1) >> 1; + + if constexpr (use_gain) + { + dst0[0] = static_cast(std::clamp(r * gain_r, 0.0f, 255.0f)); + dst0[1] = static_cast(std::clamp(g * gain_g, 0.0f, 255.0f)); + dst0[2] = static_cast(std::clamp(b * gain_b, 0.0f, 255.0f)); + } + else + { + dst0[0] = r; + dst0[1] = g; + dst0[2] = b; + } + dst0[3] = alpha; + } + } + } + + static void debayer_raw8_downscale(const u8* src, u8* dst, u8 alpha, f32 gain_r, f32 gain_g, f32 gain_b) + { + if (gain_r != 1.0f || gain_g != 1.0f || gain_b != 1.0f) + debayer_raw8_downscale_impl(src, dst, alpha, gain_r, gain_g, gain_b); + else + debayer_raw8_downscale_impl(src, dst, alpha, gain_r, gain_g, gain_b); + } + bool convert_image_format(CellCameraFormat input_format, const CellGemVideoConvertAttribute& vc, const std::vector& video_data_in, u32 width, u32 height, u8* video_data_out, u32 video_data_out_size, u8* buffer_memory, @@ -881,9 +928,9 @@ namespace gem const u8* src_data = video_data_in.data(); const u8 alpha = vc.alpha; - const f32 gain_r = vc.gain * vc.blue_gain; + const f32 gain_r = vc.gain * vc.red_gain; const f32 gain_g = vc.gain * vc.green_gain; - const f32 gain_b = vc.gain * vc.red_gain; + const f32 gain_b = vc.gain * vc.blue_gain; // Only RAW8 should be relevant for cellGem unless I'm mistaken if (input_format == CELL_CAMERA_RAW8) @@ -1183,34 +1230,7 @@ namespace gem { case CELL_CAMERA_RAW8: { - const u32 in_pitch = width; - const u32 out_pitch = width * 4 / 2; - - for (u32 y = 0; y < height - 1; y += 2) - { - const u8* src0 = src_data + y * in_pitch; - const u8* src1 = src0 + in_pitch; - - u8* dst0 = video_data_out + (y / 2) * out_pitch; - u8* dst1 = dst0 + out_pitch; - - for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst0 += 4, dst1 += 4) - { - const u8 b = src0[0]; - const u8 g0 = src0[1]; - const u8 g1 = src1[0]; - const u8 r = src1[1]; - - const u8 top[4] = { r, g0, b, alpha }; - const u8 bottom[4] = { r, g1, b, alpha }; - - // Top-Left - std::memcpy(dst0, top, 4); - - // Bottom-Left Pixel - std::memcpy(dst1, bottom, 4); - } - } + debayer_raw8_downscale(src_data, video_data_out, alpha, gain_r, gain_g, gain_b); break; } case CELL_CAMERA_RGBA: @@ -1609,12 +1629,6 @@ public: return false; } - if (!m_camera_info.bytesize) - { - cellGem.error("gem_tracker: unexpected image size: %d", m_camera_info.bytesize); - return false; - } - m_tracker.set_image_data(m_camera_info.buffer.get_ptr(), m_camera_info.bytesize, m_camera_info.width, m_camera_info.height, m_camera_info.format); m_framenumber++; // using framenumber instead of timestamp since the timestamp could be identical return true; @@ -3625,7 +3639,7 @@ error_code cellGemReadExternalPortDeviceInfo(u32 gem_num, vm::ptr ext_id, v if (!pad->move_data.external_device_read_requested) { *ext_id = controller.ext_id = pad->move_data.external_device_id; - std::memcpy(pad->move_data.external_device_read.data(), ext_info.get_ptr(), CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE); + std::memcpy(ext_info.get_ptr(), pad->move_data.external_device_read.data(), CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE); break; } } diff --git a/rpcs3/Emu/GameInfo.h b/rpcs3/Emu/GameInfo.h index 3efca1410a..da8b2638ba 100644 --- a/rpcs3/Emu/GameInfo.h +++ b/rpcs3/Emu/GameInfo.h @@ -8,6 +8,7 @@ struct GameInfo std::string path; std::string icon_path; std::string movie_path; + std::string audio_path; std::string name; std::string serial; diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index ea7b87ff6d..5ba31d9d77 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -637,6 +637,12 @@ void game_list_frame::OnParsingFinished() game.has_hover_pam = true; } + if (std::string audio_path = sfo_dir + "/SND0.AT3"; file_exists(audio_path)) + { + game.info.audio_path = std::move(audio_path); + game.has_audio_file = true; + } + const QString serial = QString::fromStdString(game.info.serial); m_games_mutex.lock(); diff --git a/rpcs3/rpcs3qt/game_list_grid.cpp b/rpcs3/rpcs3qt/game_list_grid.cpp index a33755ff54..4dfcf9744a 100644 --- a/rpcs3/rpcs3qt/game_list_grid.cpp +++ b/rpcs3/rpcs3qt/game_list_grid.cpp @@ -109,11 +109,23 @@ void game_list_grid::populate( } }); - if (play_hover_movies && (game->has_hover_gif || game->has_hover_pam)) + if (play_hover_movies && (game->has_hover_gif || game->has_hover_pam || game->has_audio_file)) { - item->set_video_path(game->info.movie_path); + bool check_iso = false; - if (!fs::exists(game->info.movie_path) && is_file_iso(game->info.path)) + if (game->has_hover_gif || game->has_hover_pam) + { + item->set_video_path(game->info.movie_path); + check_iso |= !fs::exists(game->info.movie_path); + } + + if (game->has_audio_file) + { + item->set_audio_path(game->info.audio_path); + check_iso |= !fs::exists(game->info.audio_path); + } + + if (check_iso && is_file_iso(game->info.path)) { item->set_iso_path(game->info.path); } diff --git a/rpcs3/rpcs3qt/game_list_table.cpp b/rpcs3/rpcs3qt/game_list_table.cpp index 98b9ef344d..737709dabb 100644 --- a/rpcs3/rpcs3qt/game_list_table.cpp +++ b/rpcs3/rpcs3qt/game_list_table.cpp @@ -299,11 +299,23 @@ void game_list_table::populate( } }); - if (play_hover_movies && (game->has_hover_gif || game->has_hover_pam)) + if (play_hover_movies && (game->has_hover_gif || game->has_hover_pam || game->has_audio_file)) { - icon_item->set_video_path(game->info.movie_path); + bool check_iso = false; - if (!fs::exists(game->info.movie_path) && is_file_iso(game->info.path)) + if (game->has_hover_gif || game->has_hover_pam) + { + icon_item->set_video_path(game->info.movie_path); + check_iso |= !fs::exists(game->info.movie_path); + } + + if (game->has_audio_file) + { + icon_item->set_audio_path(game->info.audio_path); + check_iso |= !fs::exists(game->info.audio_path); + } + + if (check_iso && is_file_iso(game->info.path)) { icon_item->set_iso_path(game->info.path); } diff --git a/rpcs3/rpcs3qt/gui_game_info.h b/rpcs3/rpcs3qt/gui_game_info.h index 08483fa7fb..693483dd6a 100644 --- a/rpcs3/rpcs3qt/gui_game_info.h +++ b/rpcs3/rpcs3qt/gui_game_info.h @@ -21,6 +21,7 @@ struct gui_game_info bool has_custom_icon = false; bool has_hover_gif = false; bool has_hover_pam = false; + bool has_audio_file = false; bool icon_in_archive = false; movie_item_base* item = nullptr; diff --git a/rpcs3/rpcs3qt/qt_video_source.cpp b/rpcs3/rpcs3qt/qt_video_source.cpp index d74d395c58..2d4ce34aa3 100644 --- a/rpcs3/rpcs3qt/qt_video_source.cpp +++ b/rpcs3/rpcs3qt/qt_video_source.cpp @@ -1,11 +1,19 @@ #include "stdafx.h" #include "Emu/System.h" +#include "Emu/system_config.h" #include "qt_video_source.h" #include "Loader/ISO.h" +#include #include +static video_source* s_audio_source = nullptr; +static std::unique_ptr s_audio_player = nullptr; +static std::unique_ptr s_audio_output = nullptr; +static std::unique_ptr s_audio_buffer = nullptr; +static std::unique_ptr s_audio_data = nullptr; + qt_video_source::qt_video_source() : video_source() { @@ -21,6 +29,11 @@ void qt_video_source::set_video_path(const std::string& video_path) m_video_path = QString::fromStdString(video_path); } +void qt_video_source::set_audio_path(const std::string& audio_path) +{ + m_audio_path = QString::fromStdString(audio_path); +} + void qt_video_source::set_iso_path(const std::string& iso_path) { m_iso_path = iso_path; @@ -89,7 +102,6 @@ void qt_video_source::init_movie() m_video_buffer = std::make_unique(&m_video_data); m_video_buffer->open(QIODevice::ReadOnly); m_movie = std::make_unique(m_video_buffer.get()); - } if (!m_movie->isValid()) @@ -179,6 +191,8 @@ void qt_video_source::start_movie() m_media_player->play(); } + start_audio(); + m_active = true; } @@ -196,6 +210,71 @@ void qt_video_source::stop_movie() m_media_player.reset(); m_video_buffer.reset(); m_video_data.clear(); + + stop_audio(); +} + +void qt_video_source::start_audio() +{ + if (m_audio_path.isEmpty() || s_audio_source == this) return; + + if (!s_audio_player) + { + s_audio_output = std::make_unique(); + s_audio_player = std::make_unique(); + s_audio_player->setAudioOutput(s_audio_output.get()); + } + + if (m_iso_path.empty()) + { + s_audio_player->setSource(QUrl::fromLocalFile(m_audio_path)); + } + else + { + iso_archive archive(m_iso_path); + auto audio_file = archive.open(m_audio_path.toStdString()); + const auto audio_size = audio_file.size(); + if (audio_size == 0) return; + + std::unique_ptr old_audio_data = std::move(s_audio_data); + s_audio_data = std::make_unique(audio_size, 0); + audio_file.read(s_audio_data->data(), audio_size); + + if (!s_audio_buffer) + { + s_audio_buffer = std::make_unique(); + } + + s_audio_buffer->setBuffer(s_audio_data.get()); + s_audio_buffer->open(QIODevice::ReadOnly); + s_audio_player->setSourceDevice(s_audio_buffer.get()); + + if (old_audio_data) + { + old_audio_data.reset(); + } + } + + s_audio_output->setVolume(g_cfg.audio.volume.get() / 100.0f); + s_audio_player->play(); + s_audio_source = this; +} + +void qt_video_source::stop_audio() +{ + if (s_audio_source != this) return; + + s_audio_source = nullptr; + + if (s_audio_player) + { + s_audio_player->stop(); + s_audio_player.reset(); + } + + s_audio_output.reset(); + s_audio_buffer.reset(); + s_audio_data.reset(); } QPixmap qt_video_source::get_movie_image(const QVideoFrame& frame) const @@ -288,6 +367,14 @@ void qt_video_source_wrapper::set_video_path(const std::string& video_path) }); } +void qt_video_source_wrapper::set_audio_path(const std::string& audio_path) +{ + Emu.CallFromMainThread([this, path = audio_path]() + { + // TODO + }); +} + void qt_video_source_wrapper::set_active(bool active) { Emu.CallFromMainThread([this, active]() diff --git a/rpcs3/rpcs3qt/qt_video_source.h b/rpcs3/rpcs3qt/qt_video_source.h index ce43d593d7..cda92671a2 100644 --- a/rpcs3/rpcs3qt/qt_video_source.h +++ b/rpcs3/rpcs3qt/qt_video_source.h @@ -19,7 +19,9 @@ public: void set_iso_path(const std::string& iso_path); void set_video_path(const std::string& video_path) override; + void set_audio_path(const std::string& audio_path) override; const QString& video_path() const { return m_video_path; } + const QString& audio_path() const { return m_audio_path; } void get_image(std::vector& data, int& w, int& h, int& ch, int& bpp) override; bool has_new() const override { return m_has_new; } @@ -30,6 +32,9 @@ public: void start_movie(); void stop_movie(); + void start_audio(); + void stop_audio(); + QPixmap get_movie_image(const QVideoFrame& frame) const; void image_change_callback(const QVideoFrame& frame = {}) const; @@ -44,6 +49,7 @@ protected: atomic_t m_has_new = false; QString m_video_path; + QString m_audio_path; std::string m_iso_path; // path of the source archive QByteArray m_video_data{}; QImage m_image{}; @@ -67,6 +73,7 @@ public: virtual ~qt_video_source_wrapper(); void set_video_path(const std::string& video_path) override; + void set_audio_path(const std::string& audio_path) override; void set_active(bool active) override; bool get_active() const override; bool has_new() const override { return m_qt_video_source && m_qt_video_source->has_new(); } diff --git a/rpcs3/rpcs3qt/save_manager_dialog.cpp b/rpcs3/rpcs3qt/save_manager_dialog.cpp index 2dd2a14e86..002df8d527 100644 --- a/rpcs3/rpcs3qt/save_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/save_manager_dialog.cpp @@ -360,6 +360,11 @@ void save_manager_dialog::UpdateList() icon_item->set_video_path(movie_path); } + if (const std::string audio_path = dir_path + "SND0.AT3"; fs::is_file(audio_path)) + { + icon_item->set_audio_path(audio_path); + } + icon_item->set_image_change_callback([this, icon_item](const QVideoFrame& frame) { if (!icon_item) @@ -686,6 +691,7 @@ void save_manager_dialog::UpdateDetails() const SaveDataEntry& save = ::at32(m_save_entries, idx); m_details_icon->set_video_path(icon_item->video_path().toStdString()); + m_details_icon->set_audio_path(icon_item->audio_path().toStdString()); m_details_icon->set_thumbnail(icon_item->data(SaveUserRole::Pixmap).value()); m_details_icon->set_active(false); diff --git a/rpcs3/util/video_source.h b/rpcs3/util/video_source.h index 9449ed238e..a18b564209 100644 --- a/rpcs3/util/video_source.h +++ b/rpcs3/util/video_source.h @@ -9,6 +9,7 @@ public: video_source() {}; virtual ~video_source() {}; virtual void set_video_path(const std::string& video_path) = 0; + virtual void set_audio_path(const std::string& audio_path) = 0; virtual void set_active(bool active) = 0; virtual bool get_active() const = 0; virtual bool has_new() const = 0;