mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-06-08 16:27:49 +00:00
Compare commits
7 Commits
4654686a26
...
e685c30f1d
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e685c30f1d | ||
![]() |
24b0bf01d5 | ||
![]() |
19fbbf0dba | ||
![]() |
7b496b2f5b | ||
![]() |
4b65cc9a4c | ||
![]() |
f99d3dbd5c | ||
![]() |
34e8fb068f |
@ -0,0 +1,28 @@
|
|||||||
|
package org.dolphinemu.dolphinemu.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.media.AudioManager
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import org.dolphinemu.dolphinemu.DolphinApplication
|
||||||
|
|
||||||
|
object AudioUtils {
|
||||||
|
@JvmStatic @Keep
|
||||||
|
fun getSampleRate(): Int =
|
||||||
|
getAudioServiceProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE, 48000)
|
||||||
|
|
||||||
|
@JvmStatic @Keep
|
||||||
|
fun getFramesPerBuffer(): Int =
|
||||||
|
getAudioServiceProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER, 256)
|
||||||
|
|
||||||
|
private fun getAudioServiceProperty(property: String, fallback: Int): Int {
|
||||||
|
return try {
|
||||||
|
val context = DolphinApplication.getAppContext()
|
||||||
|
val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
|
Integer.parseUnsignedInt(am.getProperty(property))
|
||||||
|
} catch (e: NullPointerException) {
|
||||||
|
fallback
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -122,6 +122,10 @@ static jmethodID s_permission_handler_request_record_audio_permission;
|
|||||||
|
|
||||||
static jmethodID s_runnable_run;
|
static jmethodID s_runnable_run;
|
||||||
|
|
||||||
|
static jclass s_audio_utils_class;
|
||||||
|
static jmethodID s_audio_utils_get_sample_rate;
|
||||||
|
static jmethodID s_audio_utils_get_frames_per_buffer;
|
||||||
|
|
||||||
namespace IDCache
|
namespace IDCache
|
||||||
{
|
{
|
||||||
JNIEnv* GetEnvForThread()
|
JNIEnv* GetEnvForThread()
|
||||||
@ -562,6 +566,21 @@ jmethodID GetRunnableRun()
|
|||||||
return s_runnable_run;
|
return s_runnable_run;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jclass GetAudioUtilsClass()
|
||||||
|
{
|
||||||
|
return s_audio_utils_class;
|
||||||
|
}
|
||||||
|
|
||||||
|
jmethodID GetAudioUtilsGetSampleRate()
|
||||||
|
{
|
||||||
|
return s_audio_utils_get_sample_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
jmethodID GetAudioUtilsGetFramesPerBuffer()
|
||||||
|
{
|
||||||
|
return s_audio_utils_get_frames_per_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace IDCache
|
} // namespace IDCache
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -798,6 +817,13 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
|||||||
s_runnable_run = env->GetMethodID(runnable_class, "run", "()V");
|
s_runnable_run = env->GetMethodID(runnable_class, "run", "()V");
|
||||||
env->DeleteLocalRef(runnable_class);
|
env->DeleteLocalRef(runnable_class);
|
||||||
|
|
||||||
|
const jclass audio_utils_class = env->FindClass("org/dolphinemu/dolphinemu/utils/AudioUtils");
|
||||||
|
s_audio_utils_class = reinterpret_cast<jclass>(env->NewGlobalRef(audio_utils_class));
|
||||||
|
s_audio_utils_get_sample_rate = env->GetStaticMethodID(audio_utils_class, "getSampleRate", "()I");
|
||||||
|
s_audio_utils_get_frames_per_buffer =
|
||||||
|
env->GetStaticMethodID(audio_utils_class, "getFramesPerBuffer", "()I");
|
||||||
|
env->DeleteLocalRef(audio_utils_class);
|
||||||
|
|
||||||
return JNI_VERSION;
|
return JNI_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -834,5 +860,6 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
|
|||||||
env->DeleteGlobalRef(s_core_device_control_class);
|
env->DeleteGlobalRef(s_core_device_control_class);
|
||||||
env->DeleteGlobalRef(s_input_detector_class);
|
env->DeleteGlobalRef(s_input_detector_class);
|
||||||
env->DeleteGlobalRef(s_permission_handler_class);
|
env->DeleteGlobalRef(s_permission_handler_class);
|
||||||
|
env->DeleteGlobalRef(s_audio_utils_class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,4 +121,8 @@ jmethodID GetPermissionHandlerRequestRecordAudioPermission();
|
|||||||
|
|
||||||
jmethodID GetRunnableRun();
|
jmethodID GetRunnableRun();
|
||||||
|
|
||||||
|
jclass GetAudioUtilsClass();
|
||||||
|
jmethodID GetAudioUtilsGetSampleRate();
|
||||||
|
jmethodID GetAudioUtilsGetFramesPerBuffer();
|
||||||
|
|
||||||
} // namespace IDCache
|
} // namespace IDCache
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "AudioCommon/WASAPIStream.h"
|
#include "AudioCommon/WASAPIStream.h"
|
||||||
#include "Common/FileUtil.h"
|
#include "Common/FileUtil.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
|
#include "Common/TimeUtil.h"
|
||||||
#include "Core/Config/MainSettings.h"
|
#include "Core/Config/MainSettings.h"
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
#include "Core/System.h"
|
#include "Core/System.h"
|
||||||
@ -219,8 +220,11 @@ void StartAudioDump(Core::System& system)
|
|||||||
|
|
||||||
std::string path_prefix = File::GetUserPath(D_DUMPAUDIO_IDX) + SConfig::GetInstance().GetGameID();
|
std::string path_prefix = File::GetUserPath(D_DUMPAUDIO_IDX) + SConfig::GetInstance().GetGameID();
|
||||||
|
|
||||||
std::string base_name =
|
const auto local_time = Common::LocalTime(start_time);
|
||||||
fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}", path_prefix, fmt::localtime(start_time));
|
if (!local_time)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string base_name = fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}", path_prefix, *local_time);
|
||||||
|
|
||||||
const std::string audio_file_name_dtk = fmt::format("{}_dtkdump.wav", base_name);
|
const std::string audio_file_name_dtk = fmt::format("{}_dtkdump.wav", base_name);
|
||||||
const std::string audio_file_name_dsp = fmt::format("{}_dspdump.wav", base_name);
|
const std::string audio_file_name_dsp = fmt::format("{}_dspdump.wav", base_name);
|
||||||
|
@ -8,40 +8,27 @@
|
|||||||
|
|
||||||
#include <SLES/OpenSLES.h>
|
#include <SLES/OpenSLES.h>
|
||||||
#include <SLES/OpenSLES_Android.h>
|
#include <SLES/OpenSLES_Android.h>
|
||||||
|
#include <jni.h>
|
||||||
|
|
||||||
#include "Common/Assert.h"
|
#include "Common/Assert.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
|
#include "jni/AndroidCommon/IDCache.h"
|
||||||
|
|
||||||
// engine interfaces
|
void OpenSLESStream::BQPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
|
||||||
static SLObjectItf engineObject;
|
|
||||||
static SLEngineItf engineEngine;
|
|
||||||
static SLObjectItf outputMixObject;
|
|
||||||
|
|
||||||
// buffer queue player interfaces
|
|
||||||
static SLObjectItf bqPlayerObject = nullptr;
|
|
||||||
static SLPlayItf bqPlayerPlay;
|
|
||||||
static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
|
|
||||||
static SLVolumeItf bqPlayerVolume;
|
|
||||||
static Mixer* g_mixer;
|
|
||||||
#define BUFFER_SIZE 512
|
|
||||||
#define BUFFER_SIZE_IN_SAMPLES (BUFFER_SIZE / 2)
|
|
||||||
|
|
||||||
// Double buffering.
|
|
||||||
static short buffer[2][BUFFER_SIZE];
|
|
||||||
static int curBuffer = 0;
|
|
||||||
|
|
||||||
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
|
|
||||||
{
|
{
|
||||||
ASSERT(bq == bqPlayerBufferQueue);
|
reinterpret_cast<OpenSLESStream*>(context)->PushSamples(bq);
|
||||||
ASSERT(nullptr == context);
|
}
|
||||||
|
|
||||||
|
void OpenSLESStream::PushSamples(SLAndroidSimpleBufferQueueItf bq)
|
||||||
|
{
|
||||||
|
ASSERT(bq == m_bq_player_buffer_queue);
|
||||||
|
|
||||||
// Render to the fresh buffer
|
// Render to the fresh buffer
|
||||||
g_mixer->Mix(reinterpret_cast<short*>(buffer[curBuffer]), BUFFER_SIZE_IN_SAMPLES);
|
m_mixer->Mix(m_buffer[m_current_buffer].data(), m_frames_per_buffer);
|
||||||
SLresult result =
|
SLresult result = (*bq)->Enqueue(bq, m_buffer[m_current_buffer].data(), m_bytes_per_buffer);
|
||||||
(*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[curBuffer], sizeof(buffer[0]));
|
m_current_buffer ^= 1; // Switch buffer
|
||||||
curBuffer ^= 1; // Switch buffer
|
|
||||||
|
|
||||||
// Comment from sample code:
|
// Comment from sample code:
|
||||||
// the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT,
|
// the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT,
|
||||||
@ -51,61 +38,78 @@ static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
|
|||||||
|
|
||||||
bool OpenSLESStream::Init()
|
bool OpenSLESStream::Init()
|
||||||
{
|
{
|
||||||
|
JNIEnv* env = IDCache::GetEnvForThread();
|
||||||
|
jclass audio_utils = IDCache::GetAudioUtilsClass();
|
||||||
|
const SLuint32 sample_rate =
|
||||||
|
env->CallStaticIntMethod(audio_utils, IDCache::GetAudioUtilsGetSampleRate());
|
||||||
|
m_frames_per_buffer =
|
||||||
|
env->CallStaticIntMethod(audio_utils, IDCache::GetAudioUtilsGetFramesPerBuffer());
|
||||||
|
|
||||||
|
INFO_LOG_FMT(AUDIO, "OpenSLES configuration: {} Hz, {} frames per buffer", sample_rate,
|
||||||
|
m_frames_per_buffer);
|
||||||
|
|
||||||
|
constexpr SLuint32 channels = 2;
|
||||||
|
const SLuint32 samples_per_buffer = m_frames_per_buffer * channels;
|
||||||
|
m_bytes_per_buffer = m_frames_per_buffer * channels * sizeof(m_buffer[0][0]);
|
||||||
|
|
||||||
|
for (std::vector<short>& buffer : m_buffer)
|
||||||
|
buffer.resize(samples_per_buffer);
|
||||||
|
|
||||||
SLresult result;
|
SLresult result;
|
||||||
// create engine
|
// create engine
|
||||||
result = slCreateEngine(&engineObject, 0, nullptr, 0, nullptr, nullptr);
|
result = slCreateEngine(&m_engine_object, 0, nullptr, 0, nullptr, nullptr);
|
||||||
ASSERT(SL_RESULT_SUCCESS == result);
|
ASSERT(SL_RESULT_SUCCESS == result);
|
||||||
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
|
result = (*m_engine_object)->Realize(m_engine_object, SL_BOOLEAN_FALSE);
|
||||||
ASSERT(SL_RESULT_SUCCESS == result);
|
ASSERT(SL_RESULT_SUCCESS == result);
|
||||||
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
|
result = (*m_engine_object)->GetInterface(m_engine_object, SL_IID_ENGINE, &m_engine_engine);
|
||||||
ASSERT(SL_RESULT_SUCCESS == result);
|
ASSERT(SL_RESULT_SUCCESS == result);
|
||||||
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0);
|
result = (*m_engine_engine)->CreateOutputMix(m_engine_engine, &m_output_mix_object, 0, 0, 0);
|
||||||
ASSERT(SL_RESULT_SUCCESS == result);
|
ASSERT(SL_RESULT_SUCCESS == result);
|
||||||
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
|
result = (*m_output_mix_object)->Realize(m_output_mix_object, SL_BOOLEAN_FALSE);
|
||||||
ASSERT(SL_RESULT_SUCCESS == result);
|
ASSERT(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
|
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
|
||||||
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,
|
SLDataFormat_PCM format_pcm = {
|
||||||
2,
|
SL_DATAFORMAT_PCM, channels,
|
||||||
m_mixer->GetSampleRate() * 1000,
|
sample_rate * 1000, SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
|
||||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
SL_BYTEORDER_LITTLEENDIAN};
|
||||||
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
|
|
||||||
SL_BYTEORDER_LITTLEENDIAN};
|
|
||||||
|
|
||||||
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
|
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
|
||||||
|
|
||||||
// configure audio sink
|
// configure audio sink
|
||||||
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
|
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, m_output_mix_object};
|
||||||
SLDataSink audioSnk = {&loc_outmix, nullptr};
|
SLDataSink audioSnk = {&loc_outmix, nullptr};
|
||||||
|
|
||||||
// create audio player
|
// create audio player
|
||||||
const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
|
const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
|
||||||
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
||||||
result =
|
result = (*m_engine_engine)
|
||||||
(*engineEngine)
|
->CreateAudioPlayer(m_engine_engine, &m_bq_player_object, &audioSrc, &audioSnk, 2,
|
||||||
->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req);
|
ids, req);
|
||||||
ASSERT(SL_RESULT_SUCCESS == result);
|
ASSERT(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
|
result = (*m_bq_player_object)->Realize(m_bq_player_object, SL_BOOLEAN_FALSE);
|
||||||
ASSERT(SL_RESULT_SUCCESS == result);
|
ASSERT(SL_RESULT_SUCCESS == result);
|
||||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
|
result = (*m_bq_player_object)->GetInterface(m_bq_player_object, SL_IID_PLAY, &m_bq_player_play);
|
||||||
|
ASSERT(SL_RESULT_SUCCESS == result);
|
||||||
|
result = (*m_bq_player_object)
|
||||||
|
->GetInterface(m_bq_player_object, SL_IID_BUFFERQUEUE, &m_bq_player_buffer_queue);
|
||||||
ASSERT(SL_RESULT_SUCCESS == result);
|
ASSERT(SL_RESULT_SUCCESS == result);
|
||||||
result =
|
result =
|
||||||
(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);
|
(*m_bq_player_object)->GetInterface(m_bq_player_object, SL_IID_VOLUME, &m_bq_player_volume);
|
||||||
ASSERT(SL_RESULT_SUCCESS == result);
|
ASSERT(SL_RESULT_SUCCESS == result);
|
||||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
|
result = (*m_bq_player_buffer_queue)
|
||||||
|
->RegisterCallback(m_bq_player_buffer_queue, BQPlayerCallback, this);
|
||||||
ASSERT(SL_RESULT_SUCCESS == result);
|
ASSERT(SL_RESULT_SUCCESS == result);
|
||||||
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, nullptr);
|
result = (*m_bq_player_play)->SetPlayState(m_bq_player_play, SL_PLAYSTATE_PLAYING);
|
||||||
ASSERT(SL_RESULT_SUCCESS == result);
|
|
||||||
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
|
||||||
ASSERT(SL_RESULT_SUCCESS == result);
|
ASSERT(SL_RESULT_SUCCESS == result);
|
||||||
|
|
||||||
// Render and enqueue a first buffer.
|
// Render and enqueue a first buffer.
|
||||||
curBuffer ^= 1;
|
m_current_buffer ^= 1;
|
||||||
g_mixer = m_mixer.get();
|
|
||||||
|
|
||||||
result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[0], sizeof(buffer[0]));
|
result = (*m_bq_player_buffer_queue)
|
||||||
|
->Enqueue(m_bq_player_buffer_queue, m_buffer[0].data(), m_bytes_per_buffer);
|
||||||
if (SL_RESULT_SUCCESS != result)
|
if (SL_RESULT_SUCCESS != result)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -114,39 +118,39 @@ bool OpenSLESStream::Init()
|
|||||||
|
|
||||||
OpenSLESStream::~OpenSLESStream()
|
OpenSLESStream::~OpenSLESStream()
|
||||||
{
|
{
|
||||||
if (bqPlayerObject != nullptr)
|
if (m_bq_player_object != nullptr)
|
||||||
{
|
{
|
||||||
(*bqPlayerObject)->Destroy(bqPlayerObject);
|
(*m_bq_player_object)->Destroy(m_bq_player_object);
|
||||||
bqPlayerObject = nullptr;
|
m_bq_player_object = nullptr;
|
||||||
bqPlayerPlay = nullptr;
|
m_bq_player_play = nullptr;
|
||||||
bqPlayerBufferQueue = nullptr;
|
m_bq_player_buffer_queue = nullptr;
|
||||||
bqPlayerVolume = nullptr;
|
m_bq_player_volume = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputMixObject != nullptr)
|
if (m_output_mix_object != nullptr)
|
||||||
{
|
{
|
||||||
(*outputMixObject)->Destroy(outputMixObject);
|
(*m_output_mix_object)->Destroy(m_output_mix_object);
|
||||||
outputMixObject = nullptr;
|
m_output_mix_object = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (engineObject != nullptr)
|
if (m_engine_object != nullptr)
|
||||||
{
|
{
|
||||||
(*engineObject)->Destroy(engineObject);
|
(*m_engine_object)->Destroy(m_engine_object);
|
||||||
engineObject = nullptr;
|
m_engine_object = nullptr;
|
||||||
engineEngine = nullptr;
|
m_engine_engine = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenSLESStream::SetRunning(bool running)
|
bool OpenSLESStream::SetRunning(bool running)
|
||||||
{
|
{
|
||||||
SLuint32 new_state = running ? SL_PLAYSTATE_PLAYING : SL_PLAYSTATE_PAUSED;
|
SLuint32 new_state = running ? SL_PLAYSTATE_PLAYING : SL_PLAYSTATE_PAUSED;
|
||||||
return (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, new_state) == SL_RESULT_SUCCESS;
|
return (*m_bq_player_play)->SetPlayState(m_bq_player_play, new_state) == SL_RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenSLESStream::SetVolume(int volume)
|
void OpenSLESStream::SetVolume(int volume)
|
||||||
{
|
{
|
||||||
const SLmillibel attenuation =
|
const SLmillibel attenuation =
|
||||||
volume <= 0 ? SL_MILLIBEL_MIN : static_cast<SLmillibel>(2000 * std::log10(volume / 100.0f));
|
volume <= 0 ? SL_MILLIBEL_MIN : static_cast<SLmillibel>(2000 * std::log10(volume / 100.0f));
|
||||||
(*bqPlayerVolume)->SetVolumeLevel(bqPlayerVolume, attenuation);
|
(*m_bq_player_volume)->SetVolumeLevel(m_bq_player_volume, attenuation);
|
||||||
}
|
}
|
||||||
#endif // HAVE_OPENSL_ES
|
#endif // HAVE_OPENSL_ES
|
||||||
|
@ -3,10 +3,15 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <thread>
|
#ifdef HAVE_OPENSL_ES
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <SLES/OpenSLES.h>
|
||||||
|
#include <SLES/OpenSLES_Android.h>
|
||||||
|
#endif // HAVE_OPENSL_ES
|
||||||
|
|
||||||
#include "AudioCommon/SoundStream.h"
|
#include "AudioCommon/SoundStream.h"
|
||||||
#include "Common/Event.h"
|
|
||||||
|
|
||||||
class OpenSLESStream final : public SoundStream
|
class OpenSLESStream final : public SoundStream
|
||||||
{
|
{
|
||||||
@ -19,7 +24,25 @@ public:
|
|||||||
static bool IsValid() { return true; }
|
static bool IsValid() { return true; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::thread thread;
|
static void BQPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context);
|
||||||
Common::Event soundSyncEvent;
|
void PushSamples(SLAndroidSimpleBufferQueueItf bq);
|
||||||
|
|
||||||
|
// engine interfaces
|
||||||
|
SLObjectItf m_engine_object;
|
||||||
|
SLEngineItf m_engine_engine;
|
||||||
|
SLObjectItf m_output_mix_object;
|
||||||
|
|
||||||
|
// buffer queue player interfaces
|
||||||
|
SLObjectItf m_bq_player_object = nullptr;
|
||||||
|
SLPlayItf m_bq_player_play;
|
||||||
|
SLAndroidSimpleBufferQueueItf m_bq_player_buffer_queue;
|
||||||
|
SLVolumeItf m_bq_player_volume;
|
||||||
|
|
||||||
|
SLuint32 m_frames_per_buffer;
|
||||||
|
SLuint32 m_bytes_per_buffer;
|
||||||
|
|
||||||
|
// Double buffering.
|
||||||
|
std::array<std::vector<short>, 2> m_buffer;
|
||||||
|
int m_current_buffer = 0;
|
||||||
#endif // HAVE_OPENSL_ES
|
#endif // HAVE_OPENSL_ES
|
||||||
};
|
};
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/ScopeGuard.h"
|
#include "Common/ScopeGuard.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
#include "Common/TimeUtil.h"
|
||||||
|
|
||||||
#include "Core/Config/MainSettings.h"
|
#include "Core/Config/MainSettings.h"
|
||||||
|
|
||||||
@ -95,12 +96,7 @@ int SDCardDiskIOCtl(File::IOFile* image, u8 pdrv, u8 cmd, void* buff)
|
|||||||
u32 GetSystemTimeFAT()
|
u32 GetSystemTimeFAT()
|
||||||
{
|
{
|
||||||
const std::time_t time = std::time(nullptr);
|
const std::time_t time = std::time(nullptr);
|
||||||
std::tm tm;
|
std::tm tm = *Common::LocalTime(time);
|
||||||
#ifdef _WIN32
|
|
||||||
localtime_s(&tm, &time);
|
|
||||||
#else
|
|
||||||
localtime_r(&time, &tm);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
DWORD fattime = 0;
|
DWORD fattime = 0;
|
||||||
fattime |= (tm.tm_year - 80) << 25;
|
fattime |= (tm.tm_year - 80) << 25;
|
||||||
|
@ -122,7 +122,6 @@ std::string SettingsWriter::GenerateSerialNumber()
|
|||||||
|
|
||||||
// Must be 9 characters at most; otherwise the serial number will be rejected by SDK libraries,
|
// Must be 9 characters at most; otherwise the serial number will be rejected by SDK libraries,
|
||||||
// as there is a check to ensure the string length is strictly lower than 10.
|
// as there is a check to ensure the string length is strictly lower than 10.
|
||||||
// 3 for %j, 2 for %H, 2 for %M, 2 for %S.
|
return fmt::format("{:09}", t % 1000000000);
|
||||||
return fmt::format("{:%j%H%M%S}", fmt::localtime(t));
|
|
||||||
}
|
}
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
@ -2,23 +2,25 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "Common/TimeUtil.h"
|
#include "Common/TimeUtil.h"
|
||||||
|
#include "Common/Logging/Log.h"
|
||||||
|
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace Common
|
namespace Common
|
||||||
{
|
{
|
||||||
std::optional<std::tm> Localtime(std::time_t time)
|
std::optional<std::tm> LocalTime(std::time_t time)
|
||||||
{
|
{
|
||||||
std::tm local_time;
|
std::tm local_time;
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
if (localtime_s(&local_time, &time) != 0)
|
if (localtime_s(&local_time, &time) != 0)
|
||||||
return std::nullopt;
|
|
||||||
#else
|
#else
|
||||||
std::tm* result = localtime_r(&time, &local_time);
|
if (localtime_r(&time, &local_time) == NULL)
|
||||||
if (result != &local_time)
|
|
||||||
return std::nullopt;
|
|
||||||
#endif
|
#endif
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(COMMON, "Failed to convert time to local time: {}", std::strerror(errno));
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
return local_time;
|
return local_time;
|
||||||
}
|
}
|
||||||
} // Namespace Common
|
} // Namespace Common
|
||||||
|
@ -9,5 +9,5 @@
|
|||||||
namespace Common
|
namespace Common
|
||||||
{
|
{
|
||||||
// Threadsafe and error-checking variant of std::localtime()
|
// Threadsafe and error-checking variant of std::localtime()
|
||||||
std::optional<std::tm> Localtime(std::time_t time);
|
std::optional<std::tm> LocalTime(std::time_t time);
|
||||||
} // Namespace Common
|
} // Namespace Common
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
@ -34,6 +35,7 @@
|
|||||||
#include "Common/ScopeGuard.h"
|
#include "Common/ScopeGuard.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
#include "Common/Thread.h"
|
#include "Common/Thread.h"
|
||||||
|
#include "Common/TimeUtil.h"
|
||||||
#include "Common/Version.h"
|
#include "Common/Version.h"
|
||||||
|
|
||||||
#include "Core/AchievementManager.h"
|
#include "Core/AchievementManager.h"
|
||||||
@ -733,15 +735,17 @@ static std::string GenerateScreenshotFolderPath()
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string GenerateScreenshotName()
|
static std::optional<std::string> GenerateScreenshotName()
|
||||||
{
|
{
|
||||||
// append gameId, path only contains the folder here.
|
// append gameId, path only contains the folder here.
|
||||||
const std::string path_prefix =
|
const std::string path_prefix =
|
||||||
GenerateScreenshotFolderPath() + SConfig::GetInstance().GetGameID();
|
GenerateScreenshotFolderPath() + SConfig::GetInstance().GetGameID();
|
||||||
|
|
||||||
const std::time_t cur_time = std::time(nullptr);
|
const std::time_t cur_time = std::time(nullptr);
|
||||||
const std::string base_name =
|
const auto local_time = Common::LocalTime(cur_time);
|
||||||
fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}", path_prefix, fmt::localtime(cur_time));
|
if (!local_time)
|
||||||
|
return std::nullopt;
|
||||||
|
const std::string base_name = fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}", path_prefix, *local_time);
|
||||||
|
|
||||||
// First try a filename without any suffixes, if already exists then append increasing numbers
|
// First try a filename without any suffixes, if already exists then append increasing numbers
|
||||||
std::string name = fmt::format("{}.png", base_name);
|
std::string name = fmt::format("{}.png", base_name);
|
||||||
@ -757,7 +761,9 @@ static std::string GenerateScreenshotName()
|
|||||||
void SaveScreenShot()
|
void SaveScreenShot()
|
||||||
{
|
{
|
||||||
const Core::CPUThreadGuard guard(Core::System::GetInstance());
|
const Core::CPUThreadGuard guard(Core::System::GetInstance());
|
||||||
g_frame_dumper->SaveScreenshot(GenerateScreenshotName());
|
std::optional<std::string> name = GenerateScreenshotName();
|
||||||
|
if (name)
|
||||||
|
g_frame_dumper->SaveScreenshot(*name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveScreenShot(std::string_view name)
|
void SaveScreenShot(std::string_view name)
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "Common/Network.h"
|
#include "Common/Network.h"
|
||||||
#include "Common/PcapFile.h"
|
#include "Common/PcapFile.h"
|
||||||
#include "Common/ScopeGuard.h"
|
#include "Common/ScopeGuard.h"
|
||||||
|
#include "Common/TimeUtil.h"
|
||||||
#include "Core/Config/MainSettings.h"
|
#include "Core/Config/MainSettings.h"
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ PCAPSSLCaptureLogger::PCAPSSLCaptureLogger()
|
|||||||
{
|
{
|
||||||
const std::string filepath =
|
const std::string filepath =
|
||||||
fmt::format("{}{} {:%Y-%m-%d %Hh%Mm%Ss}.pcap", File::GetUserPath(D_DUMPSSL_IDX),
|
fmt::format("{}{} {:%Y-%m-%d %Hh%Mm%Ss}.pcap", File::GetUserPath(D_DUMPSSL_IDX),
|
||||||
SConfig::GetInstance().GetGameID(), fmt::localtime(std::time(nullptr)));
|
SConfig::GetInstance().GetGameID(), *Common::LocalTime(std::time(nullptr)));
|
||||||
m_file = std::make_unique<Common::PCAP>(
|
m_file = std::make_unique<Common::PCAP>(
|
||||||
new File::IOFile(filepath, "wb", File::SharedAccess::Read), Common::PCAP::LinkType::Ethernet);
|
new File::IOFile(filepath, "wb", File::SharedAccess::Read), Common::PCAP::LinkType::Ethernet);
|
||||||
}
|
}
|
||||||
|
@ -281,7 +281,7 @@ static std::string SystemTimeAsDoubleToString(double time)
|
|||||||
{
|
{
|
||||||
// revert adjustments from GetSystemTimeAsDouble() to get a normal Unix timestamp again
|
// revert adjustments from GetSystemTimeAsDouble() to get a normal Unix timestamp again
|
||||||
const time_t seconds = static_cast<time_t>(time) + DOUBLE_TIME_OFFSET;
|
const time_t seconds = static_cast<time_t>(time) + DOUBLE_TIME_OFFSET;
|
||||||
const auto local_time = Common::Localtime(seconds);
|
const auto local_time = Common::LocalTime(seconds);
|
||||||
if (!local_time)
|
if (!local_time)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
|
@ -1445,7 +1445,7 @@ WIARVZFileReader<RVZ>::ProcessAndCompress(CompressThreadState* state, CompressPa
|
|||||||
ASSERT(hash_offset <= std::numeric_limits<u16>::max());
|
ASSERT(hash_offset <= std::numeric_limits<u16>::max());
|
||||||
|
|
||||||
HashExceptionEntry& exception = exception_lists[exception_list_index].emplace_back();
|
HashExceptionEntry& exception = exception_lists[exception_list_index].emplace_back();
|
||||||
exception.offset = static_cast<u16>(Common::swap16(hash_offset));
|
exception.offset = Common::swap16(static_cast<u16>(hash_offset));
|
||||||
std::memcpy(exception.hash.data(), desired_hash, Common::SHA1::DIGEST_LEN);
|
std::memcpy(exception.hash.data(), desired_hash, Common::SHA1::DIGEST_LEN);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -60,7 +60,7 @@ public:
|
|||||||
std::string GetCompressionMethod() const override;
|
std::string GetCompressionMethod() const override;
|
||||||
std::optional<int> GetCompressionLevel() const override
|
std::optional<int> GetCompressionLevel() const override
|
||||||
{
|
{
|
||||||
return static_cast<int>(static_cast<s32>(Common::swap32(m_header_2.compression_level)));
|
return static_cast<int>(Common::swap32(m_header_2.compression_level));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Read(u64 offset, u64 size, u8* out_ptr) override;
|
bool Read(u64 offset, u64 size, u8* out_ptr) override;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "VideoCommon/FrameDumpFFMpeg.h"
|
#include "VideoCommon/FrameDumpFFMpeg.h"
|
||||||
|
#include "Common/TimeUtil.h"
|
||||||
|
|
||||||
#if defined(__FreeBSD__)
|
#if defined(__FreeBSD__)
|
||||||
#define __STDC_CONSTANT_MACROS 1
|
#define __STDC_CONSTANT_MACROS 1
|
||||||
@ -124,11 +125,15 @@ std::string GetDumpPath(const std::string& extension, std::time_t time, u32 inde
|
|||||||
if (!dump_path.empty())
|
if (!dump_path.empty())
|
||||||
return dump_path;
|
return dump_path;
|
||||||
|
|
||||||
|
const auto local_time = Common::LocalTime(time);
|
||||||
|
if (!local_time)
|
||||||
|
return "";
|
||||||
|
|
||||||
const std::string path_prefix =
|
const std::string path_prefix =
|
||||||
File::GetUserPath(D_DUMPFRAMES_IDX) + SConfig::GetInstance().GetGameID();
|
File::GetUserPath(D_DUMPFRAMES_IDX) + SConfig::GetInstance().GetGameID();
|
||||||
|
|
||||||
const std::string base_name =
|
const std::string base_name =
|
||||||
fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}_{}", path_prefix, fmt::localtime(time), index);
|
fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}_{}", path_prefix, *local_time, index);
|
||||||
|
|
||||||
const std::string path = fmt::format("{}.{}", base_name, extension);
|
const std::string path = fmt::format("{}.{}", base_name, extension);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user