From 68a1da31a04f85408fd80e068c5eb2f4b48da3c5 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 17 Jun 2021 22:44:06 +0200 Subject: [PATCH] cellSearch: move audio info to separate util file --- rpcs3/Emu/CMakeLists.txt | 1 + rpcs3/Emu/Cell/Modules/cellAdec.cpp | 3 - rpcs3/Emu/Cell/Modules/cellSearch.cpp | 297 +++++++++----------------- rpcs3/emucore.vcxproj | 2 + rpcs3/emucore.vcxproj.filters | 6 + rpcs3/util/media_utils.cpp | 127 +++++++++++ rpcs3/util/media_utils.h | 22 ++ 7 files changed, 258 insertions(+), 200 deletions(-) create mode 100644 rpcs3/util/media_utils.cpp create mode 100644 rpcs3/util/media_utils.h diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 139f403c48..cb9fb70c8e 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -36,6 +36,7 @@ target_include_directories(rpcs3_emu # Utilities target_sources(rpcs3_emu PRIVATE ../util/atomic.cpp + ../util/media_utils.cpp ../util/logs.cpp ../util/yaml.cpp ../util/vm_native.cpp diff --git a/rpcs3/Emu/Cell/Modules/cellAdec.cpp b/rpcs3/Emu/Cell/Modules/cellAdec.cpp index ad05765e71..b4c04b11ef 100644 --- a/rpcs3/Emu/Cell/Modules/cellAdec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellAdec.cpp @@ -315,9 +315,6 @@ public: , cbFunc(func) , cbArg(arg) { - //av_register_all(); - //avcodec_register_all(); - switch (type) { case CELL_ADEC_TYPE_ATRACX: diff --git a/rpcs3/Emu/Cell/Modules/cellSearch.cpp b/rpcs3/Emu/Cell/Modules/cellSearch.cpp index e5a7a6f349..535474d760 100644 --- a/rpcs3/Emu/Cell/Modules/cellSearch.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSearch.cpp @@ -8,17 +8,7 @@ #include "cellSearch.h" #include "Utilities/StrUtil.h" - -#ifdef _MSC_VER -#pragma warning(push, 0) -#else -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#endif -extern "C" { -#include "libavformat/avformat.h" -#include "libavutil/dict.h" -} +#include "util/media_utils.h" LOG_CHANNEL(cellSearch); @@ -112,6 +102,86 @@ struct search_object_t std::vector content_ids; }; +template +void parse_metadata(D& dst, const utils::media_info& mi, const std::string& key, const std::string& def, usz max_length) +{ + std::string value = mi.get_metadata(key, def); + if (value.size() > max_length) + { + value.resize(max_length); + } + strcpy_trunc(dst, value); +}; + +void populate_music_info(CellSearchMusicInfo& info, const utils::media_info& mi, const fs::dir_entry& item) +{ + parse_metadata(info.artistName, mi, "artist", "Unknown Artist", CELL_SEARCH_TITLE_LEN_MAX); + parse_metadata(info.albumTitle, mi, "album", "Unknown Album", CELL_SEARCH_TITLE_LEN_MAX); + parse_metadata(info.genreName, mi, "genre", "Unknown Genre", CELL_SEARCH_TITLE_LEN_MAX); + parse_metadata(info.title, mi, "title", item.name.substr(0, item.name.find_last_of('.')), CELL_SEARCH_TITLE_LEN_MAX); + parse_metadata(info.diskNumber, mi, "disc", "1/1", sizeof(info.diskNumber) - 1); + + // Special case: track is usually stored as e.g. 2/11 + const std::string tmp = mi.get_metadata("track", ""s); + s64 value{}; + const bool success = try_to_int64(&value, tmp.substr(0, tmp.find('/')).c_str(), s32{smin}, s32{smax}); + info.trackNumber = success ? static_cast(value) : -1; + + info.size = item.size; + info.releasedYear = static_cast(mi.get_metadata("date", -1)); + info.duration = mi.duration_us / 1000; // we need microseconds + info.samplingRate = mi.sample_rate; + info.bitrate = mi.bitrate_bps; + info.quantizationBitrate = mi.bitrate_bps; // TODO: Assumption, verify value + info.playCount = 0; // we do not track this for now + info.lastPlayedDate = -1; // we do not track this for now + info.importedDate = -1; // we do not track this for now + info.drmEncrypted = 0; // TODO: Needs to be 1 if it's encrypted + info.status = CELL_SEARCH_CONTENTSTATUS_AVAILABLE; + + // Convert AVCodecID to CellSearchCodec + switch (mi.av_codec_id) + { + case 86017: // AV_CODEC_ID_MP3 + info.codec = CELL_SEARCH_CODEC_MP3; + break; + case 86018: // AV_CODEC_ID_AAC + info.codec = CELL_SEARCH_CODEC_AAC; + break; + case 86019: // AV_CODEC_ID_AC3 + info.codec = CELL_SEARCH_CODEC_AC3; + break; + case 86023: // AV_CODEC_ID_WMAV1 + case 86024: // AV_CODEC_ID_WMAV2 + info.codec = CELL_SEARCH_CODEC_WMA; + break; + case 86047: // AV_CODEC_ID_ATRAC3 + info.codec = CELL_SEARCH_CODEC_AT3; + break; + case 86055: // AV_CODEC_ID_ATRAC3P + info.codec = CELL_SEARCH_CODEC_AT3P; + break; + case 88078: // AV_CODEC_ID_ATRAC3AL + //case 88079: // AV_CODEC_ID_ATRAC3PAL TODO: supported ? + info.codec = CELL_SEARCH_CODEC_ATALL; + break; + // TODO: Find out if any of this works + //case 88069: // AV_CODEC_ID_DSD_LSBF + //case 88070: // AV_CODEC_ID_DSD_MSBF + //case 88071: // AV_CODEC_ID_DSD_LSBF_PLANAR + //case 88072: // AV_CODEC_ID_DSD_MSBF_PLANAR + // info.codec = CELL_SEARCH_CODEC_DSD; + // break; + //case ???: + // info.codec = CELL_SEARCH_CODEC_WAV; + // break; + default: + info.codec = CELL_SEARCH_CODEC_UNKNOWN; + info.status = CELL_SEARCH_CONTENTSTATUS_NOT_SUPPORTED; + break; + } +} + error_code cellSearchInitialize(CellSearchMode mode, u32 container, vm::ptr func, vm::ptr userData) { cellSearch.warning("cellSearchInitialize(mode=0x%x, container=0x%x, func=*0x%x, userData=*0x%x)", +mode, container, func, userData); @@ -175,7 +245,7 @@ error_code cellSearchFinalize() return CELL_SEARCH_ERROR_GENERIC; } - sysutil_register_cb([=, &search](ppu_thread& ppu) -> s32 + sysutil_register_cb([&search](ppu_thread& ppu) -> s32 { { std::lock_guard lock(search.links_mutex); @@ -576,180 +646,21 @@ error_code cellSearchStartContentSearchInList(vm::cptr list { strcpy_trunc(curr_find->infoPath.contentPath, item_path); } + // TODO - curr_find.infoPath.thumbnailPath + if (type == CELL_SEARCH_CONTENTSEARCHTYPE_MUSIC_ALL) { curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSIC; - CellSearchMusicInfo& info = curr_find->data.music; - // Only print FFMPEG errors, fatals and panics - av_log_set_level(AV_LOG_ERROR); - - AVDictionary* av_dict_opts = nullptr; - av_dict_set(&av_dict_opts, "probesize", "96", 0); - AVFormatContext* av_format_ctx = nullptr; - av_format_ctx = avformat_alloc_context(); - - // Open input file - if (avformat_open_input(&av_format_ctx, (vfs::get(vpath) + "/" + item.name).c_str(), nullptr, &av_dict_opts) < 0) + const std::string path = vfs::get(vpath) + "/" + item.name; + const auto [success, mi] = utils::get_media_info(path, 1); // AVMEDIA_TYPE_AUDIO + if (!success) { - // Failed to open file - av_dict_free(&av_dict_opts); - avformat_free_context(av_format_ctx); - continue; - } - av_dict_free(&av_dict_opts); - - // Find stream information - if (avformat_find_stream_info(av_format_ctx, nullptr) < 0) - { - // Failed to load stream information - avformat_free_context(av_format_ctx); continue; } - // Derive first audio stream id from avformat context - int stream_index = -1; - for (uint i = 0; i < av_format_ctx->nb_streams; i++) - { - if (av_format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) - { - stream_index = i; - break; - } - } - if (stream_index == -1) - { - // Failed to find an audio stream - avformat_free_context(av_format_ctx); - continue; - } - - AVStream* stream = av_format_ctx->streams[stream_index]; - AVCodecParameters* codec = stream->codecpar; - - info.bitrate = codec->bit_rate / 1000; // TODO: Assumption, verify value - info.quantizationBitrate = codec->bit_rate / 1000; // TODO: Assumption, verify value - info.samplingRate = codec->sample_rate; // TODO: Assumption, verify value - info.drmEncrypted = 0; // Needs to be 0 or it wont be accepted - info.duration = av_format_ctx->duration / 1000; // TODO: Assumption, verify value - info.releasedYear = 0; // TODO: Use "date" id3 tag for this - info.size = item.size; - info.playCount = 0; // we do not track this for now - info.lastPlayedDate = 0; // we do not track this for now - info.importedDate = 0; // we do not track this for now - info.status = CELL_SEARCH_CONTENTSTATUS_AVAILABLE; // CellSearchContentStatus - - switch (codec->codec_id) // AVCodecID - { - case AV_CODEC_ID_MP3: - info.codec = CELL_SEARCH_CODEC_MP3; // CellSearchCodec - break; - case AV_CODEC_ID_AAC: - info.codec = CELL_SEARCH_CODEC_AAC; // CellSearchCodec - break; - case AV_CODEC_ID_AC3: - info.codec = CELL_SEARCH_CODEC_AC3; // CellSearchCodec - break; - case AV_CODEC_ID_WMAV1: - case AV_CODEC_ID_WMAV2: - info.codec = CELL_SEARCH_CODEC_WMA; // CellSearchCodec - break; - case AV_CODEC_ID_ATRAC3: - info.codec = CELL_SEARCH_CODEC_AT3; // CellSearchCodec - break; - case AV_CODEC_ID_ATRAC3P: - info.codec = CELL_SEARCH_CODEC_AT3P; // CellSearchCodec - break; - default: - info.codec = CELL_SEARCH_CODEC_UNKNOWN; // CellSearchCodec - info.status = CELL_SEARCH_CONTENTSTATUS_NOT_SUPPORTED; // CellSearchContentStatus - break; - } - - AVDictionaryEntry *tag; - std::string value; - - info.trackNumber = 0; - tag = av_dict_get(av_format_ctx->metadata, "track", nullptr, AV_DICT_IGNORE_SUFFIX); - if (tag != nullptr) - { - std::string tmp(tag->value); - info.trackNumber = stoi(tmp.substr(0, tmp.find('/'))); - } - - tag = av_dict_get(av_format_ctx->metadata, "album", nullptr, AV_DICT_IGNORE_SUFFIX); - if (tag != nullptr) - { - value = tag->value; - if (value.size() > CELL_SEARCH_TITLE_LEN_MAX) - { - value.resize(CELL_SEARCH_TITLE_LEN_MAX); - } - strcpy_trunc(info.albumTitle, value); - } - else - { - strcpy_trunc(info.albumTitle, "Unknown Album"); - } - - tag = av_dict_get(av_format_ctx->metadata, "title", 0, AV_DICT_IGNORE_SUFFIX); - if (tag != nullptr) - { - value = tag->value; - if (value.size() > CELL_SEARCH_TITLE_LEN_MAX) - { - value.resize(CELL_SEARCH_TITLE_LEN_MAX); - } - strcpy_trunc(info.title, value); - } - else - { - // Fall back to filename - value = item.name.substr(0, ext_offset); - if (value.size() > CELL_SEARCH_TITLE_LEN_MAX) - { - value.resize(CELL_SEARCH_TITLE_LEN_MAX); - strcpy_trunc(info.title, value); - } - else - { - strcpy_trunc(info.title, value); - } - } - - tag = av_dict_get(av_format_ctx->metadata, "artist", nullptr, AV_DICT_IGNORE_SUFFIX); - if (tag != nullptr) - { - value = tag->value; - if (value.size() > CELL_SEARCH_TITLE_LEN_MAX) - { - value.resize(CELL_SEARCH_TITLE_LEN_MAX); - } - strcpy_trunc(info.artistName, value); - } - else - { - strcpy_trunc(info.artistName, "Unknown Artist"); - } - - tag = av_dict_get(av_format_ctx->metadata, "genre", nullptr, AV_DICT_IGNORE_SUFFIX); - if (tag != nullptr) - { - value = tag->value; - if (value.size() > CELL_SEARCH_TITLE_LEN_MAX) - { - value.resize(CELL_SEARCH_TITLE_LEN_MAX); - } - strcpy_trunc(info.genreName, value); - } - else - { - strcpy_trunc(info.genreName, "Unknown Genre"); - } - - avformat_close_input(&av_format_ctx); - avformat_free_context(av_format_ctx); + populate_music_info(curr_find->data.music, mi, item); } else if (type == CELL_SEARCH_CONTENTSEARCHTYPE_PHOTO_ALL) { @@ -916,7 +827,7 @@ error_code cellSearchStartContentSearch(CellSearchContentSearchType type, CellSe auto ext_offset = item.name.find_last_of('.'); // used later if no "Title" found std::shared_ptr curr_find = std::make_shared(); - if( item_path.length() > CELL_SEARCH_PATH_LEN_MAX ) + if (item_path.length() > CELL_SEARCH_PATH_LEN_MAX) { // Create mapping which will be resolved to an actual hard link in VFS by cellSearchPrepareFile std::string link = "/.tmp/" + std::to_string(hash) + item.name.substr(ext_offset); @@ -929,29 +840,21 @@ error_code cellSearchStartContentSearch(CellSearchContentSearchType type, CellSe { strcpy_trunc(curr_find->infoPath.contentPath, item_path); } + // TODO - curr_find.infoPath.thumbnailPath + if (type == CELL_SEARCH_CONTENTSEARCHTYPE_MUSIC_ALL) { curr_find->type = CELL_SEARCH_CONTENTTYPE_MUSIC; - CellSearchMusicInfo& info = curr_find->data.music; - // TODO - Some kinda file music analysis and assign the values as such - info.duration = 0; - info.size = item.size; - info.importedDate = 0; - info.lastPlayedDate = 0; - info.releasedYear = 0; - info.trackNumber = 0; - info.bitrate = 0; - info.samplingRate = 0; - info.quantizationBitrate = 0; - info.playCount = 0; - info.drmEncrypted = 0; - info.codec = 0; // CellSearchCodec - info.status = 0; // CellSearchContentStatus - strcpy_trunc(info.title, item.name.substr(0, ext_offset)); // it'll do for the moment... - strcpy_trunc(info.albumTitle, "ALBUM TITLE"); - strcpy_trunc(info.artistName, "ARTIST NAME"); - strcpy_trunc(info.genreName, "GENRE NAME"); + + const std::string path = vfs::get(vpath) + "/" + item.name; + const auto [success, mi] = utils::get_media_info(path, 1); // AVMEDIA_TYPE_AUDIO + if (!success) + { + continue; + } + + populate_music_info(curr_find->data.music, mi, item); } else if (type == CELL_SEARCH_CONTENTSEARCHTYPE_PHOTO_ALL) { diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 402bcdce38..e1979f2df6 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -96,6 +96,7 @@ NotUsing + NotUsing Sync @@ -486,6 +487,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 2c6076d531..0c993c93ca 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -993,6 +993,9 @@ Emu\GPU\RSX\Overlays + + Utilities + @@ -1962,6 +1965,9 @@ Utilities + + Utilities + diff --git a/rpcs3/util/media_utils.cpp b/rpcs3/util/media_utils.cpp new file mode 100644 index 0000000000..7f54621c72 --- /dev/null +++ b/rpcs3/util/media_utils.cpp @@ -0,0 +1,127 @@ +#include "stdafx.h" +#include "media_utils.h" +#include "logs.hpp" +#include "Utilities/StrUtil.h" + +#ifdef _MSC_VER +#pragma warning(push, 0) +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +extern "C" { +#include "libavformat/avformat.h" +#include "libavutil/dict.h" +} +#ifdef _MSC_VER +#pragma warning(pop) +#else +#pragma GCC diagnostic pop +#endif + +LOG_CHANNEL(media_log, "Media"); + +namespace utils +{ + template <> + std::string media_info::get_metadata(const std::string& key, const std::string& def) const + { + if (metadata.contains(key)) + { + return metadata.at(key); + } + + return def; + } + + template <> + s64 media_info::get_metadata(const std::string& key, const s64& def) const + { + if (metadata.contains(key)) + { + s64 result{}; + if (try_to_int64(&result, metadata.at(key), smin, smax)) + { + return result; + } + } + + return def; + } + + std::pair get_media_info(const std::string& path, s32 av_media_type) + { + media_info info; + + // Only print FFMPEG errors, fatals and panics + av_log_set_level(AV_LOG_ERROR); + + AVDictionary* av_dict_opts = nullptr; + if (int err = av_dict_set(&av_dict_opts, "probesize", "96", 0); err < 0) + { + media_log.error("av_dict_set: returned with error=%d", err); + return { false, std::move(info) }; + } + + AVFormatContext* av_format_ctx = avformat_alloc_context(); + + // Open input file + if (int err = avformat_open_input(&av_format_ctx, path.c_str(), nullptr, &av_dict_opts); err < 0) + { + // Failed to open file + av_dict_free(&av_dict_opts); + avformat_free_context(av_format_ctx); + media_log.notice("avformat_open_input: could not open file. error=%d file='%s'", err, path); + return { false, std::move(info) }; + } + av_dict_free(&av_dict_opts); + + // Find stream information + if (int err = avformat_find_stream_info(av_format_ctx, nullptr); err < 0) + { + // Failed to load stream information + avformat_free_context(av_format_ctx); + media_log.notice("avformat_find_stream_info: could not load stream information. error=%d file='%s'", err, path); + return { false, std::move(info) }; + } + + // Derive first stream id and type from avformat context + // we are only interested in the first stream for now + int stream_index = -1; + for (uint i = 0; i < 1 && i < av_format_ctx->nb_streams; i++) + { + if (av_format_ctx->streams[i]->codecpar->codec_type == av_media_type) + { + stream_index = i; + break; + } + } + + if (stream_index == -1) + { + // Failed to find a stream + avformat_free_context(av_format_ctx); + media_log.notice("Could not find the desired stream of type %d in file='%s'", av_media_type, path); + return { false, std::move(info) }; + } + + AVStream* stream = av_format_ctx->streams[stream_index]; + AVCodecParameters* codec = stream->codecpar; + AVDictionaryEntry* tag = nullptr; + + info.av_codec_id = codec->codec_id; + info.bitrate_bps = codec->bit_rate; + info.sample_rate = codec->sample_rate; + info.duration_us = av_format_ctx->duration; + + while (tag = av_dict_get(av_format_ctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)) + { + info.metadata[tag->key] = tag->value; + } + + avformat_close_input(&av_format_ctx); + avformat_free_context(av_format_ctx); + + return { true, std::move(info) }; + } +} diff --git a/rpcs3/util/media_utils.h b/rpcs3/util/media_utils.h new file mode 100644 index 0000000000..cf1c63d1c2 --- /dev/null +++ b/rpcs3/util/media_utils.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace utils +{ + struct media_info + { + s32 av_codec_id = 0; // 0 = AV_CODEC_ID_NONE + s32 bitrate_bps = 0; // Bit rate in bit/s + s32 sample_rate; // Samples per second + s64 duration_us = 0; // in AV_TIME_BASE fractional seconds (= microseconds) + + std::unordered_map metadata; + + // Convenience function for metadata + template + T get_metadata(const std::string& key, const T& def) const; + }; + + std::pair get_media_info(const std::string& path, s32 av_media_type); +}