CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/HLE/__sceAudio.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <atomic>18#include <mutex>1920#include "Common/Common.h"21#include "Common/File/Path.h"22#include "Common/Serialize/Serializer.h"23#include "Common/Serialize/SerializeFuncs.h"24#include "Common/Data/Collections/FixedSizeQueue.h"25#include "Common/System/System.h"2627#ifdef _M_SSE28#include <emmintrin.h>29#endif3031#include "Core/Config.h"32#include "Core/CoreTiming.h"33#include "Core/MemMapHelpers.h"34#include "Core/Reporting.h"35#include "Core/System.h"36#ifndef MOBILE_DEVICE37#include "Core/WaveFile.h"38#include "Core/ELF/ParamSFO.h"39#include "Core/HLE/sceKernelTime.h"40#include "StringUtils.h"41#endif42#include "Core/HLE/__sceAudio.h"43#include "Core/HLE/sceAudio.h"44#include "Core/HLE/sceKernel.h"45#include "Core/HLE/sceKernelThread.h"46#include "Core/Util/AudioFormat.h"4748// Should be used to lock anything related to the outAudioQueue.49// atomic locks are used on the lock. TODO: make this lock-free50std::atomic_flag atomicLock_;5152// We copy samples as they are written into this simple ring buffer.53// Might try something more efficient later.54FixedSizeQueue<s16, 32768 * 8> chanSampleQueues[PSP_AUDIO_CHANNEL_MAX + 1];5556int eventAudioUpdate = -1;5758// TODO: This is now useless and should be removed. Just scared of breaking states.59int eventHostAudioUpdate = -1;6061int mixFrequency = 44100;62int srcFrequency = 0;6364const int hwSampleRate = 44100;65const int hwBlockSize = 64;6667static int audioIntervalCycles;68static int audioHostIntervalCycles;6970static s32 *mixBuffer;71static s16 *clampedMixBuffer;72#ifndef MOBILE_DEVICE73WaveFileWriter g_wave_writer;74static bool m_logAudio;75#endif7677// High and low watermarks, basically. For perfect emulation, the correct values are 0 and 1, respectively.78// TODO: Tweak. Hm, there aren't actually even used currently...79static int chanQueueMaxSizeFactor;80static int chanQueueMinSizeFactor;8182static void hleAudioUpdate(u64 userdata, int cyclesLate) {83// Schedule the next cycle first. __AudioUpdate() may consume cycles.84CoreTiming::ScheduleEvent(audioIntervalCycles - cyclesLate, eventAudioUpdate, 0);8586__AudioUpdate();87}8889static void hleHostAudioUpdate(u64 userdata, int cyclesLate) {90CoreTiming::ScheduleEvent(audioHostIntervalCycles - cyclesLate, eventHostAudioUpdate, 0);91}9293static void __AudioCPUMHzChange() {94audioIntervalCycles = (int)(usToCycles(1000000ULL) * hwBlockSize / hwSampleRate);9596// Soon to be removed.97audioHostIntervalCycles = (int)(usToCycles(1000000ULL) * 512 / hwSampleRate);98}99100void __AudioInit() {101System_AudioResetStatCounters();102mixFrequency = 44100;103srcFrequency = 0;104105chanQueueMaxSizeFactor = 2;106chanQueueMinSizeFactor = 1;107108__AudioCPUMHzChange();109110eventAudioUpdate = CoreTiming::RegisterEvent("AudioUpdate", &hleAudioUpdate);111eventHostAudioUpdate = CoreTiming::RegisterEvent("AudioUpdateHost", &hleHostAudioUpdate);112113CoreTiming::ScheduleEvent(audioIntervalCycles, eventAudioUpdate, 0);114CoreTiming::ScheduleEvent(audioHostIntervalCycles, eventHostAudioUpdate, 0);115for (u32 i = 0; i < PSP_AUDIO_CHANNEL_MAX + 1; i++) {116chans[i].index = i;117chans[i].clear();118}119120mixBuffer = new s32[hwBlockSize * 2];121clampedMixBuffer = new s16[hwBlockSize * 2];122memset(mixBuffer, 0, hwBlockSize * 2 * sizeof(s32));123124System_AudioClear();125CoreTiming::RegisterMHzChangeCallback(&__AudioCPUMHzChange);126}127128void __AudioDoState(PointerWrap &p) {129auto s = p.Section("sceAudio", 1, 2);130if (!s)131return;132133Do(p, eventAudioUpdate);134CoreTiming::RestoreRegisterEvent(eventAudioUpdate, "AudioUpdate", &hleAudioUpdate);135Do(p, eventHostAudioUpdate);136CoreTiming::RestoreRegisterEvent(eventHostAudioUpdate, "AudioUpdateHost", &hleHostAudioUpdate);137138Do(p, mixFrequency);139if (s >= 2) {140Do(p, srcFrequency);141} else {142// Assume that it was actually the SRC channel frequency.143srcFrequency = mixFrequency;144mixFrequency = 44100;145}146147if (s >= 2) {148// TODO: Next time we bump, get rid of this. It's kinda useless.149auto s = p.Section("resampler", 1);150if (p.mode == p.MODE_READ) {151System_AudioClear();152}153} else {154// Only to preserve the previous file format. Might cause a slight audio glitch on upgrades?155FixedSizeQueue<s16, 512 * 16> outAudioQueue;156outAudioQueue.DoState(p);157158System_AudioClear();159}160161int chanCount = ARRAY_SIZE(chans);162Do(p, chanCount);163if (chanCount != ARRAY_SIZE(chans))164{165ERROR_LOG(Log::sceAudio, "Savestate failure: different number of audio channels.");166p.SetError(p.ERROR_FAILURE);167return;168}169for (int i = 0; i < chanCount; ++i) {170chans[i].index = i;171chans[i].DoState(p);172}173174__AudioCPUMHzChange();175}176177void __AudioShutdown() {178delete [] mixBuffer;179delete [] clampedMixBuffer;180181mixBuffer = 0;182for (u32 i = 0; i < PSP_AUDIO_CHANNEL_MAX + 1; i++) {183chans[i].index = i;184chans[i].clear();185}186187#ifndef MOBILE_DEVICE188if (g_Config.bDumpAudio) {189__StopLogAudio();190}191#endif192}193194u32 __AudioEnqueue(AudioChannel &chan, int chanNum, bool blocking) {195u32 ret = chan.sampleCount;196197if (chan.sampleAddress == 0) {198// For some reason, multichannel audio lies and returns the sample count here.199if (chanNum == PSP_AUDIO_CHANNEL_SRC || chanNum == PSP_AUDIO_CHANNEL_OUTPUT2) {200ret = 0;201}202}203204// If there's anything on the queue at all, it should be busy, but we try to be a bit lax.205//if (chanSampleQueues[chanNum].size() > chan.sampleCount * 2 * chanQueueMaxSizeFactor || chan.sampleAddress == 0) {206if (chanSampleQueues[chanNum].size() > 0) {207if (blocking) {208// TODO: Regular multichannel audio seems to block for 64 samples less? Or enqueue the first 64 sync?209int blockSamples = (int)chanSampleQueues[chanNum].size() / 2 / chanQueueMinSizeFactor;210211if (__KernelIsDispatchEnabled()) {212AudioChannelWaitInfo waitInfo = {__KernelGetCurThread(), blockSamples};213chan.waitingThreads.push_back(waitInfo);214// Also remember the value to return in the waitValue.215__KernelWaitCurThread(WAITTYPE_AUDIOCHANNEL, (SceUID)chanNum + 1, ret, 0, false, "blocking audio");216} else {217// TODO: Maybe we shouldn't take this audio after all?218ret = SCE_KERNEL_ERROR_CAN_NOT_WAIT;219}220221// Fall through to the sample queueing, don't want to lose the samples even though222// we're getting full. The PSP would enqueue after blocking.223} else {224// Non-blocking doesn't even enqueue, but it's not commonly used.225return SCE_ERROR_AUDIO_CHANNEL_BUSY;226}227}228229if (chan.sampleAddress == 0) {230return ret;231}232233int leftVol = chan.leftVolume;234int rightVol = chan.rightVolume;235236if (leftVol == (1 << 15) && rightVol == (1 << 15) && chan.format == PSP_AUDIO_FORMAT_STEREO && IS_LITTLE_ENDIAN) {237// TODO: Add mono->stereo conversion to this path.238239// Good news: the volume doesn't affect the values at all.240// We can just do a direct memory copy.241const u32 totalSamples = chan.sampleCount * (chan.format == PSP_AUDIO_FORMAT_STEREO ? 2 : 1);242s16 *buf1 = 0, *buf2 = 0;243size_t sz1, sz2;244chanSampleQueues[chanNum].pushPointers(totalSamples, &buf1, &sz1, &buf2, &sz2);245246if (Memory::IsValidAddress(chan.sampleAddress + (totalSamples - 1) * sizeof(s16_le))) {247Memory::Memcpy(buf1, chan.sampleAddress, (u32)sz1 * sizeof(s16));248if (buf2)249Memory::Memcpy(buf2, chan.sampleAddress + (u32)sz1 * sizeof(s16), (u32)sz2 * sizeof(s16));250}251} else {252// Remember that maximum volume allowed is 0xFFFFF so left shift is no issue.253// This way we can optimally shift by 16.254leftVol <<=1;255rightVol <<=1;256257if (chan.format == PSP_AUDIO_FORMAT_STEREO) {258const u32 totalSamples = chan.sampleCount * 2;259260s16_le *sampleData = (s16_le *) Memory::GetPointer(chan.sampleAddress);261262// Walking a pointer for speed. But let's make sure we wouldn't trip on an invalid ptr.263if (Memory::IsValidAddress(chan.sampleAddress + (totalSamples - 1) * sizeof(s16_le))) {264s16 *buf1 = 0, *buf2 = 0;265size_t sz1, sz2;266chanSampleQueues[chanNum].pushPointers(totalSamples, &buf1, &sz1, &buf2, &sz2);267AdjustVolumeBlock(buf1, sampleData, sz1, leftVol, rightVol);268if (buf2) {269AdjustVolumeBlock(buf2, sampleData + sz1, sz2, leftVol, rightVol);270}271}272} else if (chan.format == PSP_AUDIO_FORMAT_MONO) {273// Rare, so unoptimized. Expands to stereo.274for (u32 i = 0; i < chan.sampleCount; i++) {275s16 sample = (s16)Memory::Read_U16(chan.sampleAddress + 2 * i);276chanSampleQueues[chanNum].push(ApplySampleVolume(sample, leftVol));277chanSampleQueues[chanNum].push(ApplySampleVolume(sample, rightVol));278}279}280}281return ret;282}283284inline void __AudioWakeThreads(AudioChannel &chan, int result, int step) {285u32 error;286bool wokeThreads = false;287for (size_t w = 0; w < chan.waitingThreads.size(); ++w) {288AudioChannelWaitInfo &waitInfo = chan.waitingThreads[w];289waitInfo.numSamples -= step;290291// If it's done (there will still be samples on queue) and actually still waiting, wake it up.292u32 waitID = __KernelGetWaitID(waitInfo.threadID, WAITTYPE_AUDIOCHANNEL, error);293if (waitInfo.numSamples <= 0 && waitID != 0) {294// DEBUG_LOG(Log::sceAudio, "Woke thread %i for some buffer filling", waitingThread);295u32 ret = result == 0 ? __KernelGetWaitValue(waitInfo.threadID, error) : SCE_ERROR_AUDIO_CHANNEL_NOT_RESERVED;296__KernelResumeThreadFromWait(waitInfo.threadID, ret);297wokeThreads = true;298299chan.waitingThreads.erase(chan.waitingThreads.begin() + w--);300}301// This means the thread stopped waiting, so stop trying to wake it.302else if (waitID == 0)303chan.waitingThreads.erase(chan.waitingThreads.begin() + w--);304}305306if (wokeThreads) {307__KernelReSchedule("audio drain");308}309}310311void __AudioWakeThreads(AudioChannel &chan, int result) {312__AudioWakeThreads(chan, result, 0x7FFFFFFF);313}314315void __AudioSetOutputFrequency(int freq) {316if (freq != 44100) {317WARN_LOG_REPORT(Log::sceAudio, "Switching audio frequency to %i", freq);318} else {319DEBUG_LOG(Log::sceAudio, "Switching audio frequency to %i", freq);320}321mixFrequency = freq;322}323324void __AudioSetSRCFrequency(int freq) {325srcFrequency = freq;326}327328// Mix samples from the various audio channels into a single sample queue, managed by the backend implementation.329void __AudioUpdate(bool resetRecording) {330// AUDIO throttle doesn't really work on the PSP since the mixing intervals are so closely tied331// to the CPU. Much better to throttle the frame rate on frame display and just throw away audio332// if the buffer somehow gets full.333bool firstChannel = true;334const int16_t srcBufferSize = hwBlockSize * 2;335int16_t srcBuffer[srcBufferSize];336337for (u32 i = 0; i < PSP_AUDIO_CHANNEL_MAX + 1; i++) {338if (!chans[i].reserved)339continue;340341__AudioWakeThreads(chans[i], 0, hwBlockSize);342343if (!chanSampleQueues[i].size()) {344continue;345}346347bool needsResample = i == PSP_AUDIO_CHANNEL_SRC && srcFrequency != 0 && srcFrequency != mixFrequency;348size_t sz = needsResample ? (srcBufferSize * srcFrequency) / mixFrequency : srcBufferSize;349if (sz > chanSampleQueues[i].size()) {350ERROR_LOG(Log::sceAudio, "Channel %i buffer underrun at %i of %i", i, (int)chanSampleQueues[i].size() / 2, (int)sz / 2);351}352353const s16 *buf1 = 0, *buf2 = 0;354size_t sz1, sz2;355356chanSampleQueues[i].popPointers(sz, &buf1, &sz1, &buf2, &sz2);357358if (needsResample) {359auto read = [&](size_t i) {360if (i < sz1)361return buf1[i];362if (i < sz1 + sz2)363return buf2[i - sz1];364if (buf2)365return buf2[sz2 - 1];366return buf1[sz1 - 1];367};368369// TODO: This is terrible, since it's doing it by small chunk and discarding frac.370const uint32_t ratio = (uint32_t)(65536.0 * srcFrequency / (double)mixFrequency);371uint32_t frac = 0;372size_t readIndex = 0;373for (size_t outIndex = 0; readIndex < sz && outIndex < srcBufferSize; outIndex += 2) {374size_t readIndex2 = readIndex + 2;375int16_t l1 = read(readIndex);376int16_t r1 = read(readIndex + 1);377int16_t l2 = read(readIndex2);378int16_t r2 = read(readIndex2 + 1);379int sampleL = ((l1 << 16) + (l2 - l1) * (uint16_t)frac) >> 16;380int sampleR = ((r1 << 16) + (r2 - r1) * (uint16_t)frac) >> 16;381srcBuffer[outIndex] = sampleL;382srcBuffer[outIndex + 1] = sampleR;383frac += ratio;384readIndex += 2 * (uint16_t)(frac >> 16);385frac &= 0xffff;386}387388buf1 = srcBuffer;389sz1 = srcBufferSize;390buf2 = nullptr;391sz2 = 0;392}393394if (firstChannel) {395for (size_t s = 0; s < sz1; s++)396mixBuffer[s] = buf1[s];397if (buf2) {398for (size_t s = 0; s < sz2; s++)399mixBuffer[s + sz1] = buf2[s];400}401firstChannel = false;402} else {403// Surprisingly hard to SIMD efficiently on SSE2 due to lack of 16-to-32-bit sign extension. NEON should be straight-forward though, and SSE4.1 can do it nicely.404// Actually, the cmple/pack trick should work fine...405for (size_t s = 0; s < sz1; s++)406mixBuffer[s] += buf1[s];407if (buf2) {408for (size_t s = 0; s < sz2; s++)409mixBuffer[s + sz1] += buf2[s];410}411}412}413414if (firstChannel) {415// Nothing was written above, let's memset.416memset(mixBuffer, 0, hwBlockSize * 2 * sizeof(s32));417}418419if (g_Config.bEnableSound) {420System_AudioPushSamples(mixBuffer, hwBlockSize);421#ifndef MOBILE_DEVICE422if (g_Config.bSaveLoadResetsAVdumping && resetRecording) {423__StopLogAudio();424std::string discID = g_paramSFO.GetDiscID();425Path audio_file_name = GetSysDirectory(DIRECTORY_AUDIO) / StringFromFormat("%s_%s.wav", discID.c_str(), KernelTimeNowFormatted().c_str()).c_str();426INFO_LOG(Log::Common, "Restarted audio recording to: %s", audio_file_name.c_str());427if (!File::Exists(GetSysDirectory(DIRECTORY_AUDIO)))428File::CreateDir(GetSysDirectory(DIRECTORY_AUDIO));429File::CreateEmptyFile(audio_file_name);430__StartLogAudio(audio_file_name);431}432if (!m_logAudio) {433if (g_Config.bDumpAudio) {434// Use gameID_EmulatedTimestamp for filename435std::string discID = g_paramSFO.GetDiscID();436Path audio_file_name = GetSysDirectory(DIRECTORY_AUDIO) / StringFromFormat("%s_%s.wav", discID.c_str(), KernelTimeNowFormatted().c_str());437INFO_LOG(Log::Common,"Recording audio to: %s", audio_file_name.c_str());438// Create the path just in case it doesn't exist439if (!File::Exists(GetSysDirectory(DIRECTORY_AUDIO)))440File::CreateDir(GetSysDirectory(DIRECTORY_AUDIO));441File::CreateEmptyFile(audio_file_name);442__StartLogAudio(audio_file_name);443}444} else {445if (g_Config.bDumpAudio) {446for (int i = 0; i < hwBlockSize * 2; i++) {447clampedMixBuffer[i] = clamp_s16(mixBuffer[i]);448}449g_wave_writer.AddStereoSamples(clampedMixBuffer, hwBlockSize);450} else {451__StopLogAudio();452}453}454#endif455}456}457458#ifndef MOBILE_DEVICE459void __StartLogAudio(const Path& filename) {460if (!m_logAudio) {461m_logAudio = true;462g_wave_writer.Start(filename, 44100);463g_wave_writer.SetSkipSilence(false);464NOTICE_LOG(Log::sceAudio, "Starting Audio logging");465} else {466WARN_LOG(Log::sceAudio, "Audio logging has already been started");467}468}469470void __StopLogAudio() {471if (m_logAudio) {472m_logAudio = false;473g_wave_writer.Stop();474NOTICE_LOG(Log::sceAudio, "Stopping Audio logging");475} else {476WARN_LOG(Log::sceAudio, "Audio logging has already been stopped");477}478}479#endif480481void WAVDump::Reset() {482__AudioUpdate(true);483}484485486