diff --git a/orbis-kernel/include/orbis/AudioOut.hpp b/orbis-kernel/include/orbis/AudioOut.hpp new file mode 100644 index 000000000..3839bcc69 --- /dev/null +++ b/orbis-kernel/include/orbis/AudioOut.hpp @@ -0,0 +1,280 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sys/sysproto.hpp" +#include +#include + +struct args { + int32_t audioPort; + int32_t idControl; + int32_t idAudio; + orbis::Thread *thread; + int32_t evfId; +}; + +int msleep(long msec) +{ + struct timespec ts; + int res; + + if (msec < 0) + { + errno = EINVAL; + return -1; + } + + ts.tv_sec = msec / 1000; + ts.tv_nsec = (msec % 1000) * 1000000; + + do { + res = nanosleep(&ts, &ts); + } while (res && errno == EINTR); + + return res; +} + +void DumpHex(const void* data, size_t size) { + char ascii[17]; + size_t i, j; + ascii[16] = '\0'; + for (i = 0; i < size; ++i) { + printf("%02X ", ((unsigned char*)data)[i]); + if (((unsigned char*)data)[i] >= ' ' && ((unsigned char*)data)[i] <= '~') { + ascii[i % 16] = ((unsigned char*)data)[i]; + } else { + ascii[i % 16] = '.'; + } + if ((i+1) % 8 == 0 || i+1 == size) { + printf(" "); + if ((i+1) % 16 == 0) { + printf("| %s \n", ascii); + } else if (i+1 == size) { + ascii[(i+1) % 16] = '\0'; + if ((i+1) % 16 <= 8) { + printf(" "); + } + for (j = (i+1) % 16; j < 16; ++j) { + printf(" "); + } + printf("| %s \n", ascii); + } + } + } +} + +void * loop(void *vargp) +{ + size_t control_shm_size = 0x10000; + size_t audio_shm_size = 65536; + + char control_shm_name[32]; + char audio_shm_name[32]; + + sprintf(control_shm_name, "/rpcsx-shm_%d_C", ((struct args*)vargp)->idControl); + sprintf(audio_shm_name, "/rpcsx-shm_%d_%d_A", ((struct args*)vargp)->idAudio, ((struct args*)vargp)->audioPort); + + int controlFd = shm_open(control_shm_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + if (controlFd == -1) { + perror("shm_open"); + exit(EXIT_FAILURE); + } + void *controlPtr = mmap(NULL, control_shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, controlFd, 0); + if (controlPtr == MAP_FAILED) { + perror("mmap"); + exit(EXIT_FAILURE); + } + + int audioFd = shm_open(audio_shm_name, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (audioFd == -1) { + perror("open"); + exit(EXIT_FAILURE); + } + void *audioPtr = mmap(NULL, audio_shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, audioFd, 0); + + int64_t controlPtrWithOffset = (int64_t)(controlPtr + 8); + + int32_t bitPattern = 1 << ((struct args*)vargp)->audioPort; + + int firstNonEmptyByteIndex; + + for (size_t i = 24; i < control_shm_size; ++i) { + if (*((char *)controlPtr + i) > 0) { + firstNonEmptyByteIndex = i - 8; + break; + } + } + + int outParamFirstByte = *((char *)controlPtr + firstNonEmptyByteIndex + 8); + int isFloatByte = *((char *)controlPtr + firstNonEmptyByteIndex + 44); + // int outParamThirdByte = *((char *)controlPtr + firstNonEmptyByteIndex + 44); // need to find the third index + int in_channels = 2, in_samples = 256, sample_rate = 48000; // probably there is no point to parse frequency, because it's always 48000 + if (outParamFirstByte == 2 && isFloatByte == 0) { + in_channels = 1; + printf("outputParam is ORBIS_AUDIO_OUT_PARAM_FORMAT_S16_MONO\n"); + } + if (outParamFirstByte == 4 && isFloatByte == 0) { + in_channels = 2; + printf("outputParam is ORBIS_AUDIO_OUT_PARAM_FORMAT_S16_STEREO\n"); + } + if (outParamFirstByte == 16 && isFloatByte == 0) { + in_channels = 8; + printf("outputParam is ORBIS_AUDIO_OUT_PARAM_FORMAT_S16_8CH\n"); + } + if (outParamFirstByte == 4 && isFloatByte == 1) { + in_channels = 1; + printf("outputParam is ORBIS_AUDIO_OUT_PARAM_FORMAT_FLOAT_MONO\n"); + } + if (outParamFirstByte == 8 && isFloatByte == 1) { + in_channels = 2; + printf("outputParam is ORBIS_AUDIO_OUT_PARAM_FORMAT_FLOAT_STEREO\n"); + } + if (outParamFirstByte == 32 && isFloatByte == 1) { + in_channels = 8; + printf("outputParam is ORBIS_AUDIO_OUT_PARAM_FORMAT_FLOAT_8CH\n"); + } + // // it's need third byte + // if (outParamFirstByte == 16 && outParamSecondByte == 0 && outParamThirdByte == 1) { + // printf("outputParam is ORBIS_AUDIO_OUT_PARAM_FORMAT_S16_8CH_STD"); + // } + // if (outParamFirstByte == 32 && outParamSecondByte == 1 && outParamThirdByte == 1) { + // printf("outputParam is ORBIS_AUDIO_OUT_PARAM_FORMAT_FLOAT_8CH_STD"); + // } + + // length byte will be inited after some time, so we wait for it + int samplesLengthByte; + while(true) { + samplesLengthByte = *((char *)controlPtr + firstNonEmptyByteIndex + 97); + if (samplesLengthByte > 0) { + break; + } + } + + in_samples = samplesLengthByte * 256; + + if (sox_init() != SOX_SUCCESS) { + exit(1); + } + + sox_signalinfo_t out_si = {}; + out_si.rate = sample_rate; + out_si.channels = in_channels; + out_si.precision = SOX_SAMPLE_PRECISION; + + sox_format_t* output + = sox_open_write("default", &out_si, NULL, "alsa", NULL, NULL); + if (!output) { + exit(1); + } + + sox_sample_t samples[in_samples * in_channels]; + + size_t clips = 0; SOX_SAMPLE_LOCALS; + size_t n_samples; + int size; + if (isFloatByte == 0) { + size = in_samples * in_channels * sizeof(int16_t); + n_samples = size / sizeof(int16_t); + } else if (isFloatByte == 1) { + size = in_samples * in_channels * sizeof(float); + n_samples = size / sizeof(float); + } + while(true) { + // skip sceAudioOutMix%x event + sys_evf_set(((struct args*)vargp)->thread, ((struct args*)vargp)->evfId, bitPattern); + // set zero to freeing audiooutput + for (size_t i = 0; i < 8; ++i) { + *((char *)controlPtr + firstNonEmptyByteIndex + i) = 0x00; + } + + // DumpHex(audioPtr, 1000); + // sleep 1ms + msleep(1); + if (isFloatByte == 0) { + int16_t data[size]; + memcpy(data, audioPtr, size); + for (size_t n = 0; n < n_samples; n++) { + samples[n] = SOX_SIGNED_16BIT_TO_SAMPLE(data[n], clips); + } + // free(data); + } + if (isFloatByte == 1) { + float data[size]; + memcpy(data, audioPtr, size); + for (size_t n = 0; n < n_samples; n++) { + samples[n] = SOX_FLOAT_32BIT_TO_SAMPLE(data[n], clips); + } + // free(data); + } + + if (sox_write(output, samples, n_samples) != n_samples) { + exit(1); + } + } + pthread_exit(NULL); +} + +namespace orbis { +class AudioOut { +public: + int32_t audioPort; + int32_t idControl; + int32_t idAudio; + int32_t evfId; + AudioOut() { + } + + ~AudioOut() { + } + + static AudioOut& getInstance() { + static AudioOut instance; + return instance; + } + + void setPortId(int32_t port) { + this->audioPort = port; + } + + void setControlId(int32_t id) { + this->idControl = id; + } + + void setAudioId(int32_t id) { + this->idAudio = id; + } + + void setEvfId(int32_t evfId) { + this->evfId = evfId; + } + + void start(orbis::Thread *thread) { + Ref file; + // probably need to close + auto result = thread->tproc->ops->open(thread, "/dev/audioHack", 0, 0, &file); + if (result.value() == 0) { + struct args *threadArgs = (struct args *)malloc(sizeof(struct args)); + threadArgs->audioPort = this->audioPort; + threadArgs->idControl = this->idControl; + threadArgs->idAudio = this->idAudio; + threadArgs->thread = thread; + threadArgs->evfId = this->evfId; + + pthread_t thread_id; + pthread_create(&thread_id, NULL, loop, (void *)threadArgs); + } + } + private: + AudioOut( const AudioOut&); + AudioOut& operator=( AudioOut& ); +}; +} // namespace orbis diff --git a/orbis-kernel/src/sys/sys_sce.cpp b/orbis-kernel/src/sys/sys_sce.cpp index 74628e46c..b39ba2c7b 100644 --- a/orbis-kernel/src/sys/sys_sce.cpp +++ b/orbis-kernel/src/sys/sys_sce.cpp @@ -8,6 +8,10 @@ #include "osem.hpp" #include "sys/sysproto.hpp" #include "utils/Logs.hpp" +#include +#include +#include +#include "orbis/AudioOut.hpp" orbis::SysResult orbis::sys_netcontrol(Thread *thread, sint fd, uint op, ptr buf, uint nbuf) { @@ -1136,6 +1140,103 @@ orbis::SysResult orbis::sys_ipmimgr_call(Thread *thread, uint op, uint kid, } return uwrite(result, 0); } + } else if (client->name == "SceSysAudioSystemIpc") { + if (syncCallParams.method == 0x12340000) { // check shared memory control + struct SceSysAudioSystemIpcCheckSharedMemoryControlMethodArgs { + uint32_t channelId; + }; + + static_assert(sizeof(SceSysAudioSystemIpcCheckSharedMemoryControlMethodArgs) == 0x4); + + if (dataInfo.size != sizeof(SceSysAudioSystemIpcCheckSharedMemoryControlMethodArgs)) { + return ErrorCode::INVAL; + } + + SceSysAudioSystemIpcCheckSharedMemoryControlMethodArgs args; + uread(args, ptr(dataInfo.data)); + + ORBIS_LOG_TODO("impi: SceSysAudioSystemIpcCheckSharedMemoryControlMethodArgs", args.channelId); + AudioOut& ao = AudioOut::getInstance(); + ao.setControlId(args.channelId); + } + if (syncCallParams.method == 0x1234000f) { // create event flag + struct SceSysAudioSystemIpcCreateEventFlagMethodArgs { + uint32_t channelId; + }; + + static_assert(sizeof(SceSysAudioSystemIpcCreateEventFlagMethodArgs) == 0x4); + + if (dataInfo.size != sizeof(SceSysAudioSystemIpcCreateEventFlagMethodArgs)) { + return ErrorCode::INVAL; + } + + SceSysAudioSystemIpcCreateEventFlagMethodArgs args; + uread(args, ptr(dataInfo.data)); + + // very bad + char buffer[32]; + sprintf(buffer, "sceAudioOutMix%x", args.channelId); + // const char* eventName = &buffer; + int32_t attrs = 0x100; + EventFlag *eventFlag; + if (attrs & kEvfAttrShared) { + auto [insertedEvf, inserted] = + thread->tproc->context->createEventFlag(buffer, attrs, 0); + + if (!inserted) { + return ErrorCode::EXIST; // FIXME: verify + } + + eventFlag = insertedEvf; + } else { + eventFlag = knew(attrs, 0); + std::strncpy(eventFlag->name, buffer, 32); + } + + int32_t audioOutMixId = thread->tproc->evfMap.insert(eventFlag); + AudioOut& ao = AudioOut::getInstance(); + ao.setEvfId(audioOutMixId); + } + if (syncCallParams.method == 0x12340001) { // check shared memory audio + struct SceSysAudioSystemIpcCheckSharedMemoryAudioMethodArgs { + uint32_t audioPort; + uint32_t channelId; + }; + + static_assert(sizeof(SceSysAudioSystemIpcCheckSharedMemoryAudioMethodArgs) == 0x8); + + if (dataInfo.size != sizeof(SceSysAudioSystemIpcCheckSharedMemoryAudioMethodArgs)) { + return ErrorCode::INVAL; + } + + SceSysAudioSystemIpcCheckSharedMemoryAudioMethodArgs args; + uread(args, ptr(dataInfo.data)); + + ORBIS_LOG_TODO("impi: SceSysAudioSystemIpcCheckSharedMemoryAudioMethodArgs", args.audioPort, args.channelId); + AudioOut& ao = AudioOut::getInstance(); + ao.setPortId(args.audioPort); + ao.setAudioId(args.channelId); + } + if (syncCallParams.method == 0x12340002) { // something something open + struct SceSysAudioSystemIpcSomethingMethodArgs { + uint32_t arg1; + uint32_t arg2; + }; + + static_assert(sizeof(SceSysAudioSystemIpcSomethingMethodArgs) == 0x8); + + if (dataInfo.size != sizeof(SceSysAudioSystemIpcSomethingMethodArgs)) { + return ErrorCode::INVAL; + } + + SceSysAudioSystemIpcSomethingMethodArgs args; + uread(args, ptr(dataInfo.data)); + + ORBIS_LOG_TODO("impi: SceSysAudioSystemIpcSomethingMethodArgs", args.arg1, args.arg2); + // here startToListen + AudioOut& ao = AudioOut::getInstance(); + ao.start(thread); + } } if (result != nullptr) { diff --git a/orbis-kernel/src/sys/sys_vm_mmap.cpp b/orbis-kernel/src/sys/sys_vm_mmap.cpp index 40fb1e697..fa0748419 100644 --- a/orbis-kernel/src/sys/sys_vm_mmap.cpp +++ b/orbis-kernel/src/sys/sys_vm_mmap.cpp @@ -11,6 +11,10 @@ orbis::SysResult orbis::sys_sstk(Thread *, sint) { orbis::SysResult orbis::sys_mmap(Thread *thread, caddr_t addr, size_t len, sint prot, sint flags, sint fd, off_t pos) { if (auto impl = thread->tproc->ops->mmap) { + // hack for audio control shared memory + if (len == 3880) { + return impl(thread, addr, 0x10000, prot, flags, fd, pos); + } return impl(thread, addr, len, prot, flags, fd, pos); } diff --git a/rpcsx-os/CMakeLists.txt b/rpcsx-os/CMakeLists.txt index a79f94f92..221fafc0b 100644 --- a/rpcsx-os/CMakeLists.txt +++ b/rpcsx-os/CMakeLists.txt @@ -32,7 +32,7 @@ add_executable(rpcsx-os ) target_include_directories(rpcsx-os PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(rpcsx-os PUBLIC orbis::kernel amdgpu::bridge libcrypto unwind unwind-x86_64 xbyak) +target_link_libraries(rpcsx-os PUBLIC orbis::kernel amdgpu::bridge libcrypto unwind unwind-x86_64 xbyak sox) target_link_options(rpcsx-os PUBLIC "LINKER:-Ttext-segment,0x0000010000000000") target_compile_options(rpcsx-os PRIVATE "-march=native") diff --git a/rpcsx-os/io-device.cpp b/rpcsx-os/io-device.cpp index aebbe2e55..dce268e63 100644 --- a/rpcsx-os/io-device.cpp +++ b/rpcsx-os/io-device.cpp @@ -210,7 +210,12 @@ static orbis::ErrorCode host_stat(orbis::File *file, orbis::Stat *sb, static orbis::ErrorCode host_truncate(orbis::File *file, std::uint64_t len, orbis::Thread *thread) { auto hostFile = static_cast(file); - if (::ftruncate(hostFile->hostFd, len)) { + // hack for audio control shared memory + std::uint64_t realLen = len; + if (len == 3880) { + realLen = 0x10000; + } + if (::ftruncate(hostFile->hostFd, realLen)) { return convertErrno(); } diff --git a/rpcsx-os/main.cpp b/rpcsx-os/main.cpp index 89b8c5402..bd55297fd 100644 --- a/rpcsx-os/main.cpp +++ b/rpcsx-os/main.cpp @@ -239,6 +239,7 @@ struct StackWriter { }; static bool g_traceSyscalls = false; +static bool g_enableAudio = false; static const char *getSyscallName(orbis::Thread *thread, int sysno) { auto sysvec = thread->tproc->sysent; @@ -341,6 +342,9 @@ static int ps4Exec(orbis::Thread *mainThread, rx::vfs::mount("/dev/rng", createRngCharacterDevice()); rx::vfs::mount("/dev/sbl_srv", createSblSrvCharacterDevice()); rx::vfs::mount("/dev/ajm", createAjmCharacterDevice()); + if (g_enableAudio) { + rx::vfs::mount("/dev/audioHack", createNullCharacterDevice()); + } orbis::Ref stdinFile; orbis::Ref stdoutFile; @@ -435,6 +439,7 @@ static void usage(const char *argv0) { std::printf("%s [...] [args...]\n", argv0); std::printf(" options:\n"); std::printf(" -m, --mount \n"); + std::printf(" -a, --enable-audio\n"); std::printf(" -o, --override \n"); std::printf(" --trace\n"); @@ -567,6 +572,13 @@ int main(int argc, const char *argv[]) { continue; } + if (argv[argIndex] == std::string_view("--enable-audio") || + argv[argIndex] == std::string_view("-a")) { + argIndex++; + g_enableAudio = true; + continue; + } + break; }