diff --git a/CMakeLists.txt b/CMakeLists.txt index 7decc89c36..509831913a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2363,7 +2363,7 @@ endif() include_directories(ext/libchdr/include) -target_link_libraries(${CoreLibName} Common native chdr kirk cityhash sfmt19937 xbrz xxhash rcheevos ${GlslangLibs} +target_link_libraries(${CoreLibName} Common native chdr kirk cityhash sfmt19937 xbrz xxhash rcheevos minimp3 ${GlslangLibs} ${CoreExtraLibs} ${OPENGL_LIBRARIES} ${X11_LIBRARIES} ${CMAKE_DL_LIBS}) if(NOT HTTPS_NOT_AVAILABLE) diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index f3a3ab22f7..1bd39b7f62 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -399,6 +399,7 @@ + @@ -832,6 +833,7 @@ + @@ -1039,4 +1041,4 @@ - + \ No newline at end of file diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 6d82c03c04..8345e4d18b 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -521,6 +521,9 @@ Math + + ext\minimp3 + @@ -975,6 +978,9 @@ GPU\Vulkan + + ext\minimp3 + @@ -1085,6 +1091,9 @@ {b681797d-7747-487f-b448-5ef5b2d2805b} + + {83cd76d0-d1ac-4ed1-9bdc-11fb5a20e5d3} + diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index 235856423a..c9b3251970 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -316,7 +316,7 @@ bool MainUI::HandleCustomEvent(QEvent *e) { filter = "DB files (*.db)"; break; case BrowseFileType::SOUND_EFFECT: - filter = "WAVE files (*.wav)"; + filter = "WAVE files (*.wav *.mp3)"; break; case BrowseFileType::ZIP: filter = "ZIP files (*.zip)"; diff --git a/UI/BackgroundAudio.cpp b/UI/BackgroundAudio.cpp index 742e0c4cc4..32cbefc0a0 100644 --- a/UI/BackgroundAudio.cpp +++ b/UI/BackgroundAudio.cpp @@ -1,6 +1,8 @@ #include #include +#include "ext/minimp3/minimp3_ex.h" + #include "Common/File/VFS/VFS.h" #include "Common/UI/Root.h" @@ -36,7 +38,7 @@ struct WavData { int raw_data_size = 0; u8 at3_extradata[16]; - void Read(RIFFReader &riff); + bool Read(RIFFReader &riff); ~WavData() { free(raw_data); @@ -49,7 +51,7 @@ struct WavData { } }; -void WavData::Read(RIFFReader &file_) { +bool WavData::Read(RIFFReader &file_) { // If we have no loop start info, we'll just loop the entire audio. raw_offset_loop_start = 0; raw_offset_loop_end = 0; @@ -72,7 +74,7 @@ void WavData::Read(RIFFReader &file_) { break; default: ERROR_LOG(SCEAUDIO, "Unexpected wave format %04x", format); - return; + return false; } num_channels = temp >> 16; @@ -97,7 +99,7 @@ void WavData::Read(RIFFReader &file_) { } else { ERROR_LOG(AUDIO, "Error - no format chunk in wav"); file_.Ascend(); - return; + return false; } if (file_.Descend('smpl')) { @@ -157,20 +159,21 @@ void WavData::Read(RIFFReader &file_) { ERROR_LOG(AUDIO, "Error - bad blockalign or channels"); free(raw_data); raw_data = nullptr; - return; + return false; } file_.Ascend(); } else { ERROR_LOG(AUDIO, "Error - no data chunk in wav"); file_.Ascend(); - return; + return false; } file_.Ascend(); } else { ERROR_LOG(AUDIO, "Could not descend into RIFF file."); - return; + return false; } sample_rate = samplesPerSec; + return true; } // Really simple looping in-memory AT3 player that also takes care of reading the file format. @@ -380,40 +383,60 @@ inline int16_t ConvertU8ToI16(uint8_t value) { } Sample *Sample::Load(const std::string &path) { - size_t bytes = 0; - uint8_t *data = g_VFS.ReadFile(path.c_str(), &bytes); - if (!data || bytes > 100000000) { + size_t data_size = 0; + uint8_t *data = g_VFS.ReadFile(path.c_str(), &data_size); + if (!data || data_size > 100000000) { WARN_LOG(AUDIO, "Failed to load sample '%s'", path.c_str()); return nullptr; } - RIFFReader reader(data, (int)bytes); - - WavData wave; - wave.Read(reader); - - delete[] data; - - if (!wave.IsSimpleWAV()) { - ERROR_LOG(AUDIO, "Wave format not supported for mixer playback. Must be 8-bit or 16-bit raw mono or stereo. '%s'", path.c_str()); - return nullptr; - } - - int16_t *samples = new int16_t[wave.num_channels * wave.numFrames]; - if (wave.raw_bytes_per_frame == wave.num_channels * 2) { - // 16-bit - memcpy(samples, wave.raw_data, wave.numFrames * wave.raw_bytes_per_frame); - } else if (wave.raw_bytes_per_frame == wave.num_channels) { - // 8-bit. Convert. - for (int i = 0; i < wave.num_channels * wave.numFrames; i++) { - samples[i] = ConvertU8ToI16(wave.raw_data[i]); + const char *mp3_magic = "ID3\03"; + const char *wav_magic = "RIFF"; + if (!memcmp(data, wav_magic, 4)) { + RIFFReader reader(data, (int)data_size); + WavData wave; + if (!wave.Read(reader)) { + delete[] data; + return nullptr; } + // A wav file. + delete[] data; + + if (!wave.IsSimpleWAV()) { + ERROR_LOG(AUDIO, "Wave format not supported for mixer playback. Must be 8-bit or 16-bit raw mono or stereo. '%s'", path.c_str()); + return nullptr; + } + + int16_t *samples = new int16_t[wave.num_channels * wave.numFrames]; + if (wave.raw_bytes_per_frame == wave.num_channels * 2) { + // 16-bit + memcpy(samples, wave.raw_data, wave.numFrames * wave.raw_bytes_per_frame); + } else if (wave.raw_bytes_per_frame == wave.num_channels) { + // 8-bit. Convert. + for (int i = 0; i < wave.num_channels * wave.numFrames; i++) { + samples[i] = ConvertU8ToI16(wave.raw_data[i]); + } + } + + // Protect against bad metadata. + int actualFrames = std::min(wave.numFrames, wave.raw_data_size / wave.raw_bytes_per_frame); + + return new Sample(samples, wave.num_channels, actualFrames, wave.sample_rate); } - // Protect against bad metadata. - int actualFrames = std::min(wave.numFrames, wave.raw_data_size / wave.raw_bytes_per_frame); - - return new Sample(samples, wave.num_channels, actualFrames, wave.sample_rate); + // Something else. + // Let's see if minimp3 can read it. + mp3dec_t mp3d; + mp3dec_init(&mp3d); + mp3dec_file_info_t mp3_info; + int samples = mp3dec_load_buf(&mp3d, data, data_size, &mp3_info, nullptr, nullptr); + // mp3_info contains the decoded data. + int16_t *sample_data = new int16_t[mp3_info.samples]; + memcpy(sample_data, mp3_info.buffer, mp3_info.samples * sizeof(int16_t)); + Sample *sample = new Sample(sample_data, mp3_info.channels, mp3_info.samples, mp3_info.hz); + free(mp3_info.buffer); + delete[] data; + return sample; } static inline int16_t Clamp16(int32_t sample) { diff --git a/UI/BackgroundAudio.h b/UI/BackgroundAudio.h index 3c869485dc..4f2952fd73 100644 --- a/UI/BackgroundAudio.h +++ b/UI/BackgroundAudio.h @@ -11,7 +11,7 @@ class AT3PlusReader; struct Sample { - // data must be new-ed. + // data must be new[]-ed. Sample(int16_t *data, int channels, int length, int rateInHz) : channels_(channels), data_(data), length_(length), rateInHz_(rateInHz) {} ~Sample() { delete[] data_; diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index ccd510d4a9..41fa63bf29 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -514,7 +514,7 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string supportedExtensions = { ".db" }; break; case BrowseFileType::SOUND_EFFECT: - supportedExtensions = { ".wav" }; + supportedExtensions = { ".wav", ".mp3" }; break; case BrowseFileType::ANY: // 'ChooseFile' will added '*' by default when there are no extensions assigned diff --git a/Windows/main.cpp b/Windows/main.cpp index 5e273b423e..87b99608d0 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -578,7 +578,7 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string filter = MakeFilter(L"Cheat db files (*.db)|*.db|All files (*.*)|*.*||"); break; case BrowseFileType::SOUND_EFFECT: - filter = MakeFilter(L"WAVE files (*.wav)|*.wav|All files (*.*)|*.*||"); + filter = MakeFilter(L"Sound effect files (*.wav *.mp3)|*.wav;*.mp3|All files (*.*)|*.*||"); break; case BrowseFileType::ANY: filter = MakeFilter(L"All files (*.*)|*.*||"); diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 315e51f581..202a40dbcc 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -109,6 +109,9 @@ SPIRV_CROSS_FILES := \ NAETT_FILES := \ ${SRC}/ext/naett/naett.c +MINIMP3_FILES := \ + ${SRC}/ext/minimp3/minimp3.cpp + RCHEEVOS_FILES := \ ${SRC}/ext/rcheevos/src/rapi/rc_api_common.c \ ${SRC}/ext/rcheevos/src/rapi/rc_api_editor.c \ @@ -217,6 +220,7 @@ EXEC_AND_LIB_FILES := \ $(SPIRV_CROSS_FILES) \ $(RCHEEVOS_FILES) \ $(NAETT_FILES) \ + $(MINIMP3_FILES) \ $(EXT_FILES) \ $(NATIVE_FILES) \ $(SRC)/Common/Buffer.cpp \ diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt index fe6b714516..dbd8417791 100644 --- a/ext/CMakeLists.txt +++ b/ext/CMakeLists.txt @@ -28,6 +28,7 @@ set(SKIP_GLSLANG_INSTALL ON CACHE BOOL "" FORCE) set(ENABLE_GLSLANG_INSTALL OFF) add_subdirectory(glslang EXCLUDE_FROM_ALL) add_subdirectory(snappy) +add_subdirectory(minimp3) add_subdirectory(udis86) add_subdirectory(SPIRV-Cross-build) add_subdirectory(rcheevos-build)