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)