Merge branch 'libretro:master' into gpu_screenshot_noshader

This commit is contained in:
ScoochAside 2025-04-29 07:44:49 -04:00 committed by GitHub
commit 75db1994d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
630 changed files with 67353 additions and 25105 deletions

View File

@ -42,7 +42,7 @@ body:
attributes:
label: Version/Commit
description: You can find this information under Information/System Information
placeholder: 1.19.1 (Git 0792144fe3)
placeholder: 1.20.0 (Git ab3b175)
validations:
required: true
@ -56,8 +56,8 @@ body:
- type: dropdown
id: nigthly
attributes:
label: Check in the nightly version
description: This issue is reproducible with [nightly builds](https://buildbot.libretro.com/nightly/)?
label: Present in the nightly version
description: Is the issue reproducible with current [nightly builds](https://buildbot.libretro.com/nightly/)?
options:
- I don't know
- Yes, this is reproduced in the nightly build

View File

@ -30,7 +30,7 @@ jobs:
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: retroarch-android-${{ steps.slug.outputs.sha8 }}
path: |

View File

@ -31,7 +31,7 @@ jobs:
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: RA-DOS-dummy-${{ steps.slug.outputs.sha8 }}
path: |

77
.github/workflows/MSYS2.yml vendored Normal file
View File

@ -0,0 +1,77 @@
name: CI Windows (MSYS2)
on:
push:
pull_request:
repository_dispatch:
types: [run_build]
permissions:
contents: read
jobs:
msys2-build-test:
strategy:
fail-fast: false
matrix:
sys: [MINGW64, UCRT64,CLANG64]
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.sys }}
update: true
install: base-devel git
pacboy: >-
gettext:p
gobject-introspection:p
graphite2:p
p11-kit:p
qt6:p
qt6-3d:p
qt6-charts:p
qt6-datavis3d:p
qt6-imageformats:p
qt6-location:p
qt6-lottie:p
qt6-networkauth:p
qt6-quick3dphysics:p
qt6-quicktimeline:p
qt6-remoteobjects:p
qt6-scxml:p
qt6-sensors:p
qt6-serialbus:p
qt6-speech:p
qt6-tools:p
qt6-translations:p
qt6-virtualkeyboard:p
qt6-webchannel:p
qt6-websockets:p
x264:p
cc:p
- name: Configure and build RetroArch
shell: msys2 {0}
run: |
echo "Building RetroArch in ${{ matrix.sys }} environment"
./configure
make -j$(nproc)
- name: Collect DLLs and binaries
shell: msys2 {0}
run: |
echo "Collecting DLLs and binaries"
mkdir -p dist
cp retroarch.exe dist/
ldd retroarch.exe|grep $MINGW_PREFIX |awk '{print $3}'|xargs -I {} cp {} dist/
- name: Archive build artifacts
if: success()
uses: actions/upload-artifact@v4
with:
name: retroarch-${{ matrix.sys }}
path: dist/

View File

@ -26,7 +26,7 @@ jobs:
id: slug
run: echo "sha8=$(echo ${GITHUB_SHA} | cut -c1-8)" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: RetroArch-${{ steps.slug.outputs.sha8 }}
path: |

View File

@ -31,7 +31,7 @@ jobs:
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: retroarch_miyoo_arm32${{ steps.slug.outputs.sha8 }}
path: |

View File

@ -36,7 +36,7 @@ jobs:
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: RA-PS2-dummy-${{ steps.slug.outputs.sha8 }}
path: |

44
.github/workflows/PS3-PSL1GHT.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: CI PS3/PSL1GHT
on:
push:
pull_request:
repository_dispatch:
types: [run_build]
permissions:
contents: read
env:
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
jobs:
build:
runs-on: ubuntu-latest
container:
image: git.libretro.com:5050/libretro-infrastructure/libretro-build-psl1ght:latest
options: --user root
steps:
- uses: actions/checkout@v3
- name: Compile Salamander
run: |
make -f Makefile.psl1ght.salamander -j$(getconf _NPROCESSORS_ONLN) clean
make -f Makefile.psl1ght.salamander -j$(getconf _NPROCESSORS_ONLN)
- name: Compile RA
run: |
make -f Makefile.psl1ght -j$(getconf _NPROCESSORS_ONLN) clean
make -f Makefile.psl1ght -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1
- name: Get short SHA
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- uses: actions/upload-artifact@v4
with:
name: RA-psl1ght-dummy-${{ steps.slug.outputs.sha8 }}
path: |
retroarch_psl1ght_salamander.elf
retroarch_psl1ght.elf

View File

@ -37,7 +37,7 @@ jobs:
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: bin-${{ steps.slug.outputs.sha8 }}
path: |

View File

@ -42,7 +42,7 @@ jobs:
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: RA-PSP-dummy-${{ steps.slug.outputs.sha8 }}
path: |

View File

@ -35,7 +35,7 @@ jobs:
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: RA-PSVita-dummy-${{ steps.slug.outputs.sha8 }}
path: |

View File

@ -31,7 +31,7 @@ jobs:
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: retroarch_rs90_mips32${{ steps.slug.outputs.sha8 }}
path: |

View File

@ -31,7 +31,7 @@ jobs:
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: retroarch_retrofw_mips32${{ steps.slug.outputs.sha8 }}
path: |

View File

@ -30,7 +30,7 @@ jobs:
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: RA-libnx-dummy-${{ steps.slug.outputs.sha8 }}
path: |

View File

@ -25,12 +25,12 @@ jobs:
- name: Compile Salamander
run: |
make -f Makefile.wiiu -j$(getconf _NPROCESSORS_ONLN) SALAMANDER_BUILD=1 clean
make -f Makefile.wiiu -j$(getconf _NPROCESSORS_ONLN) SALAMANDER_BUILD=1 V=1
make -f Makefile.wiiu -j$(getconf _NPROCESSORS_ONLN) SALAMANDER_BUILD=1
- name: Compile RA
run: |
make -f Makefile.wiiu -j$(getconf _NPROCESSORS_ONLN) clean
make -f Makefile.wiiu -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1 V=1
make -f Makefile.wiiu -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1
- name: Get short SHA
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"

2
.gitignore vendored
View File

@ -196,6 +196,8 @@ retroarch.js
retroarch.js.mem
*.bc
*.wasm
*.wasm.map
obj-emscripten/
# only ignore .js files in the repo root
/*.js

View File

@ -331,7 +331,7 @@ build-retroarch-linux-i686:
.build-retroarch-macos-xcode:
# Metal/Universal x86_64 arm64 is default
tags:
- macosx-packaging
- mac-apple-silicon
stage: build
variables:
XCARCHIVE_PATH: pkg/apple/build/RetroArchUniversal
@ -358,6 +358,8 @@ build-retroarch-osx-universal-metal:
build-retroarch-osx-opengl-x64:
extends: .build-retroarch-macos-xcode
tags:
- macosx-packaging
variables:
XCARCHIVE_PATH: pkg/apple/build/RetroArchOpenGL
XCPROJECT_NAME: RetroArch
@ -388,8 +390,6 @@ build-retroarch-osx-opengl-x64:
build-retroarch-ios-arm64:
extends: .build-retroarch-macos-xcode
tags:
- mac-apple-silicon
variables:
XCPROJECT_NAME: RetroArch_iOS13
XCCONFIG: GitLabCI.xcconfig
@ -408,6 +408,8 @@ build-retroarch-ios-arm64:
build-retroarch-ios9:
extends: .build-retroarch-macos-xcode
tags:
- macosx-packaging
variables:
XCPROJECT_NAME: RetroArch_iOS9
XCCONFIG: GitLabCI.xcconfig

View File

@ -1,191 +0,0 @@
{
"configurations": [
{
"name": "Mac",
"includePath": [
"/usr/include",
"/usr/local/include",
"${workspaceRoot}",
"${workspaceRoot}/libretro-common/include"
],
"defines": [],
"intelliSenseMode": "clang-x64",
"browse": {
"path": [
"/usr/include",
"/usr/local/include",
"${workspaceRoot}"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
},
"macFrameworkPath": [
"/System/Library/Frameworks",
"/Library/Frameworks"
]
},
{
"name": "Linux",
"includePath": [
"/usr/include",
"/usr/local/include",
"${workspaceRoot}",
"${workspaceFolder}/libretro-common/include",
"${workspaceRoot}/libretro-common/include"
],
"defines": [],
"intelliSenseMode": "clang-x64",
"browse": {
"path": [
"/usr/include",
"/usr/local/include",
"${workspaceRoot}"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
}
},
{
"name": "Win32",
"includePath": [
"C:/Program Files (x86)/Windows Kits/10/Include/10.0.15063.0/um",
"C:/Program Files (x86)/Windows Kits/10/Include/10.0.15063.0/ucrt",
"C:/Program Files (x86)/Windows Kits/10/Include/10.0.15063.0/shared",
"C:/Program Files (x86)/Windows Kits/10/Include/10.0.15063.0/winrt",
"${workspaceRoot}",
"${workspaceFolder}/libretro-common/include"
],
"defines": [
"_DEBUG",
"UNICODE"
],
"intelliSenseMode": "msvc-x64",
"browse": {
"path": [
"C:/Program Files (x86)/Windows Kits/10/Include/10.0.15063.0/um",
"C:/Program Files (x86)/Windows Kits/10/Include/10.0.15063.0/ucrt",
"C:/Program Files (x86)/Windows Kits/10/Include/10.0.15063.0/shared",
"C:/Program Files (x86)/Windows Kits/10/Include/10.0.15063.0/winrt",
"${workspaceRoot}"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
},
"cStandard": "c11",
"cppStandard": "c++17",
"configurationProvider": "ms-vscode.makefile-tools"
},
{
"name": "msys2-mingw32",
"includePath": [
"C:/msys64/mingw32/include",
"C:/msys64/mingw32/i686-w64-mingw32/include",
"${workspaceRoot}/libretro-common/include",
"${workspaceRoot}/include",
"${workspaceRoot}"
],
"defines": [
"_DEBUG",
"UNICODE"
],
"intelliSenseMode": "msvc-x64",
"browse": {
"path": [
"C:/msys64/mingw32/include",
"C:/msys64/mingw32/i686-w64-mingw32/include",
"${workspaceRoot}/libretro-common/include",
"${workspaceRoot}/include",
"${workspaceRoot}"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
}
},
{
"name": "msys2-mingw64",
"includePath": [
"C:/msys64/mingw64/include",
"C:/msys64/mingw64/x86_64-w64-mingw32/include",
"${workspaceRoot}/libretro-common/include",
"${workspaceRoot}/include",
"${workspaceRoot}"
],
"defines": [
"_DEBUG",
"UNICODE"
],
"intelliSenseMode": "msvc-x64",
"browse": {
"path": [
"C:/msys64/mingw64/include",
"C:/msys64/mingw64/x86_64-w64-mingw32/include",
"${workspaceRoot}/libretro-common/include",
"${workspaceRoot}/include",
"${workspaceRoot}"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
}
},
{
"name": "Switch",
"includePath": [
"/opt/devkitpro/devkitA64/aarch64-none-elf/include",
"/opt/devkitpro/devkitA64/lib/gcc/aarch64-none-elf/8.3.0/include",
"/opt/devkitpro/libnx/include",
"/opt/devkitpro/portlibs/switch/include",
"/opt/devkitpro/portlibs/switch/include/freetype2",
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE",
"__aarch64__",
"__SWITCH__",
"HAVE_LIBNX"
],
"windowsSdkVersion": "10.0.17763.0",
"compilerPath": "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-gcc",
"cStandard": "c11",
"cppStandard": "c++11",
"intelliSenseMode": "gcc-x64"
},
{
"name": "WiiU",
"includePath": [
"/opt/devkitpro/devkitPPC/powerpc-eabi/include",
"/opt/devkitpro/devkitPPC/lib/gcc/powerpc-eabi/6.3.0/include",
"/opt/devkitpro/portlibs/ppc/include",
"${workspaceFolder}/**"
],
"defines": [
"WIIU",
"WIIU_HID"
],
"windowsSdkVersion": "10.0.17763.0",
"compilerPath": "/opt/devkitpro/devkitPPC/bin/powerpc-eabi-gcc",
"cStandard": "c11",
"cppStandard": "c++11",
"intelliSenseMode": "gcc-x64"
},
{
"name": "ps2sdk-ee",
"includePath": [
"${env:PS2DEV}/ps2sdk/common/include",
"${env:PS2DEV}/ps2sdk/ee/include",
"${env:PS2DEV}/gsKit/include",
"${workspaceFolder}/**"
],
"defines": [
"PS2",
"_EE"
],
"compilerPath": "${env:PS2DEV}/ee/bin/mips64r5900el-ps2-elf-gcc",
"cStandard": "c11",
"cppStandard": "c++11",
"intelliSenseMode": "gcc-x64"
}
],
"version": 4
}

70
.vscode/launch.json vendored
View File

@ -1,70 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/retroarch.exe",
"args": ["-v"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": true,
"MIMode": "gdb",
"miDebuggerPath": "c:\\msys64\\mingw64\\bin\\gdb.exe",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "(gdb) Attach",
"type": "cppdbg",
"request": "attach",
"program": "${workspaceFolder}/retroarch.exe",
"processId": "${command:pickProcess}",
"MIMode": "gdb",
"miDebuggerPath": "c:\\msys64\\mingw64\\bin\\gdb.exe",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "PSP-GDB Debugger",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/retroarchpsp.elf",
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"setupCommands": [
{
"text": "symbol-file ${workspaceFolder}/retroarchpsp.elf",
"description": "read symbols for elf file",
"ignoreFailures": true
},
{
"description": "Enable all-exceptions",
"text": "-exec \"catch throw\"",
"ignoreFailures": true
}
],
"showDisplayString": true,
"targetArchitecture": "mips",
"MIMode": "gdb",
"miDebuggerPath": "/usr/local/pspdev/bin/psp-gdb",
"miDebuggerServerAddress": "127.0.0.1:10001",
}
]
}

43
.vscode/settings.json vendored
View File

@ -1,43 +0,0 @@
{
/*"terminal.integrated.shell.windows": "C:\\msys64\\usr\\bin\\bash.exe",
"terminal.integrated.env.windows": {
"PATH": "/mingw64/lib/ccache/bin:/mingw64/lib/ccache/bin:/mingw64/lib/ccache/bin:/mingw64/bin:/usr/local/bin:/usr/bin:/bin:$PATH",
"MSYSTEM": "MINGW64",
},*/
"terminal.integrated.cursorBlinking": true,
"editor.tabSize": 3,
"editor.detectIndentation": false,
"editor.renderWhitespace": "all",
"editor.insertSpaces": true,
"editor.formatOnSave": false,
"editor.ruler": [80],
"files.associations": {
"*.h": "c",
"*.in": "c",
"*.rh": "c",
"array": "c",
"iosfwd": "c",
"xlocbuf": "c",
"xmemory0": "c",
"ios": "c",
"list": "c",
"unordered_map": "c",
"unordered_set": "c",
"sstream": "cpp",
"hash_map": "c",
"hash_set": "c",
"initializer_list": "c",
"string_view": "c",
"utility": "c",
"thread": "c",
"xlocale": "c",
"deque": "c",
"vector": "c",
"xhash": "c",
"xiosbase": "c",
"xstring": "c",
"xtree": "c",
"xutility": "c"
},
"C_Cpp.dimInactiveRegions": false,
}

128
.vscode/tasks.json vendored
View File

@ -1,128 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "linux clean build",
"type": "shell",
"group": "build",
"command": "make clean && ./configure && make -j12"
},
{
"label": "linux clean",
"type": "shell",
"group": "build",
"command": "make clean"
},
{
"label": "linux build with debug symbols",
"type": "shell",
"group": "build",
"command": "DEBUG=1 make -j12"
},
{
"label": "linux build",
"type": "shell",
"group": "build",
"command": "make -j12"
},
{
"label": "linux build and run",
"type": "shell",
"group": "build",
"command": "make -j12 && ./retroarch -v"
},
{
"label": "linux build and run with debug symbols",
"type": "shell",
"group": "build",
"command": "DEBUG=1 make -j12 && ./retroarch -v"
},
{
"label": "msys2-mingw64 build",
"type": "shell",
"group": {
"kind": "build",
"isDefault": true
},
"command": "./configure; make -j2",
"options": {
"shell": {
"executable": "C:\\msys64\\usr\\bin\\bash.exe",
"args": [
"-c"
]
}
}
},
{
"label": "msys2-mingw64 build with debug symbols",
"type": "shell",
"group": "build",
"command": "./configure; DEBUG=1 make -j2",
"options": {
"shell": {
"executable": "C:\\msys64\\usr\\bin\\bash.exe",
"args": [
"-c"
]
}
}
},
{
"label": "msys2-mingw64 rebuild",
"type": "shell",
"group": "build",
"command": "make -j2",
"options": {
"shell": {
"executable": "C:\\msys64\\usr\\bin\\bash.exe",
"args": [
"-c"
]
}
}
},
{
"label": "msys2-mingw64 clean",
"type": "shell",
"group": "build",
"command": "make clean",
"options": {
"shell": {
"executable": "C:\\msys64\\usr\\bin\\bash.exe",
"args": [
"-c"
]
}
}
},
{
"label": "msys2-mingw64 run",
"type": "shell",
"group": {
"kind": "test",
"isDefault": true },
"command": "./retroarch -v",
"options": {
"shell": {
"executable": "C:\\msys64\\usr\\bin\\bash.exe",
"args": [
"-c"
]
}
}
}
]
}

View File

@ -315,7 +315,7 @@ Mike Swanson (chungy)
mikeOSX
minucce
misson20000
Mohmoud (esoptron) (Hedonium)
esoptron
Monroe88
Morgane (MorganeAD)
mprobinson

View File

@ -1,5 +1,126 @@
# Future
# 1.21.0
- 3DS: Fix unique IDs for newer cores
- 3DS: Enable TLS (SSL)
- 3DS: Fix UI freeze when threaded rendering is enabled
- 3DS: Fix crash on load content
- 3DS: Other minor fixes
- APPLE: Enable Vulkan emulated mailbox
- APPLE: Include b2 core in App Store builds
- APPLE: CoreMIDI driver for IOS/MacOS
- APPLE: CoreLocation driver for IOS/MacOS
- AUTOCONF: Enable alternative display name in autoconfig files
- AUTOCONF: Make autoconfig failure messages optional
- AUDIO: Option to mute on rewind
- AUDIO/PIPEWIRE: Fix app launch when pipewire service is stopped
- AUDIO/PIPEWIRE: Fix speedup with threaded video mode
- AUDIO/PIPEWIRE: Fix latency setting and microphone handling
- AUDIO/PIPEWIRE: Pass the new rate to the audio driver
- CAMERA: Add PipeWire camera driver
- CAMERA: Add ffmpeg camera driver
- CHEAT: Rewrite part of cheat_manager_load_cb_second_pass()
- CHEEVOS: Include achievement state in netplay states
- CHEEVOS: Fix crash when entering achievements in quick menu while client is not present
- CHEEVOS: Restore cheevos_badges_enable for HAVE_GFX_WIDGETS builds
- CLI: Allow --entryslot to fall back to normal states
- CLOUDSYNC: Fix Windows path issues
- CLOUDSYNC: Workaround for duplicated requests bug
- CLOUDSYNC: Workaround for 301 redirects
- CLOUDSYNC: Handle ignored directories properly
- EMSCRIPTEN: Added new AudioWorklet driver, a fast callback-based audio driver
- EMSCRIPTEN: Scale window to correct size
- EMSCRIPTEN: Additional platform functions
- EMSCRIPTEN: Add new default video context driver: emscriptenwebgl_ctx
- EMSCRIPTEN: Add new audio driver: AudioWorklet
- EMSCRIPTEN: Add new modernized web player which will eventually replace the existing one
- EMSCRIPTEN/RWEBINPUT: Add touch input support
- GAMECUBE: Fixes
- GENERAL: Fix save state auto increment
- GENERAL: Fix softpatching with periods/dots in the file name
- GENERAL: Fix compilation with --enable-videocore
- GENERAL: Allow asset directory redefinition and other directory overrides via environment variables
- GENERAL: Allow override of player 1/2 input with machine learning models (needs recompilation and external library)
- GENERAL: Fix performance counter option not remembered between sessions
- GENERAL: Create security statement
- GENERAL: Fix crash when core is not selected
- GENERAL: Use core fps instead of screen refresh for calculating dropped frames
- INPUT: Fix a crash when initializing illuminance sensor on Linux
- INPUT: Analog-to-digital refactor, fixing behavior when analogs are assigned to keys
- INPUT: Turbo fire overhaul. See https://github.com/libretro/RetroArch/pull/17633
- INPUT/ANDROID: Fix game focus and pause handling
- INPUT/COCOA: Include gravity in acceleration sensor values
- INPUT/COCOA: Fix relative mouse input
- INPUT/COCOA: Allow mouse input while mouse overlay is active
- INPUT/WINRAW: Invert mouse index order
- IOS: Ensure webserver notice can be dismissed
- IOS: Fix rescanning manual playlists after app update
- IOS: Fix clean playlist function
- IOS: Fix crash when scanning
- IOS: Fix jump back to selected item when closing content
- IOS: Fix shared GL context setup
- IOS: Update Launch Screen
- IOS: Screen orientation lock through display server
- IOS: Fix rescanning manual playlists after app update
- LAKKA: Remove bluetooth device after disconnection
- LINUX/X11: Extend X11 input driver with XInput2 extensions for multi-mouse
- MACOS: Fix some sandbox handling in App Store builds
- MACOS: Reset keyboard state when focus is lost
- MENU: Add SSL support to the information list
- MENU: Add warning to BFI and related menu items
- MENU: Fix latency statistics when using runahead
- MENU: Fix opening file inside archive with core selection
- MENU: Main menu unified between different menu drivers
- MENU: Visibility toggle for playlist tabs
- MENU: Color the notification icon by message category
- MENU: Gray Dark+Light theme adjustments
- MENU/GLUI: Menu back button switches tabs like in other menu drivers
- MENU/GLUI: Tab selection option is honored
- MENU/GLUI: Fix CD icon appearing when no icon is specified
- MENU/GLUI: Allow fullscreen thumbnail browsing
- MENU/GLUI: Save state thumbnails
- MENU/PLAYLISTS: Random selection/shuffle function
- MENU/QT: Fix desktop menu crash with Cheevos disabled
- MENU/RGUI: Cleanups of certain menu items
- MENU/RGUI: Thumbnail fixes
- MENU/OZONE: Fix messagebox background
- MENU/XMB: Fix Light theme, font shadow
- MENU/XMB: Appearance menu cleanup
- MENU/XMB: Icon thumbnail can be any of the existing types
- MISC: Guard nanosleep prototype behind _POSIX_TIMERS
- MISC: Use fabsf and intended threshold for refresh rate check
- MISC: Use platform-specific checks for invalid descriptors
- MIDI: Add dropdown items for midi device selection
- NETWORK: Refactor of net_http, improvements for task blocking and performance
- NETWORK: Follow http redirects in net_http
- NETWORK: Expire failed DNS lookups much faster
- NETWORK: Fix netplay when using netpacket interface with recent cheevos
- NETWORK/HTTP: Fix crash in net_http_resolve() in single-thread mode
- OVERLAY: Fix overlay lightgun, mouse & pointer
- OVERLAY: Preferred overlay loading is now default only on mobile platforms
- OVERLAY: Improve analog recentering when touching the area just outside the recentering zone
- QT: Enable non-png thumbnails also for Qt interface
- REPLAY: Fix issue when replaying old format input recordings in newer RetroArch
- TTS: Fix initial text-to-speech on Windows
- TVOS: Fix 720p display
- TVOS: Fix refresh rate fetching on tvOS 13/14
- TVOS: Update Top Shelf art
- SAVESTATES: Reset state index when loading new content
- UWP: Fix slang shader compilation
- VIDEO: Enable BFI setting for mobile platforms (mind the warnings)
- VIDEO/OpenGLES: Fix FP/sRGB FBO support
- VIDEO/SHADERS: Allow exact refresh rate sync with shader subframes
- VIDEO/SHADERS: FIX shader wildcards
- VIDEO/VULKAN: Enable adaptive vsync
- VIDEO/V4L2: Added resolution picker/forcing.
- VIDEO/V4L2: Rewrote logic for finding ALSA audio devices in enumerate_audio_devices function
- VIDEO/V4L2: Added a skip for some of the interface queries that fail and aren't required for magewell usb.
- VITA: Fixes
- WINDOWS: Win32 socket improvements
- WII: Fixes
- WIIU: Fixes
- WEBPLAYER: Update core list for 1.20.0
# 1.20.0
- AUDIO: Fix audio handling in case of RARCH_NETPLAY_CTL_USE_CORE_PACKET_INTERFACE
- AUDIO: Include missing audio filters on some platforms
@ -46,7 +167,7 @@
- INPUT/UDEV: Enable mouse buttons 4 and 5
- INPUT/WAYLAND: Enable horizontal scroll and mouse buttons 4 and 5
- INPUT/WAYLAND: Simulate lightgun input for cores
- INPUT/WAYLAND: Support for cursor-shape-v1 and content-type-v1 protocol
- INPUT/WAYLAND: Support for cursor-shape-v1 protocol
- INPUT/X11: Enable mouse buttons 4 and 5
- iOS: Enable vibration by default
- iOS: Better handling of physical mice/magic keyboard trackpad
@ -114,6 +235,7 @@
- VIDEO/VULKAN: Fix Vulkan window freezes when swapchain becomes suboptimal
- VIDEO/VULKAN: Prefer IMMEDIATE mode without vsync
- VIDEO/X11: Support inhibit of Xss screensaver
- VIDEO/WAYLAND: Support for content-type-v1 protocol
- VITA: Enable analog L2/R2 triggers when a DS3 controller is used with PS Vita
- WAYLAND: Fix segfault when relative pointer is not supported
- WAYLAND: Use reverse DNS name for desktop file and icon

View File

@ -171,7 +171,34 @@ ifneq ($(MOC_HEADERS),)
RARCH_OBJ += $(MOC_OBJ)
endif
all: $(TARGET) config.mk
all: info $(TARGET) config.mk
define INFO
ASFLAGS: $(ASFLAGS)
CC: $(CC)
CFLAGS: $(CFLAGS)
CPPFLAGS: $(CPPFLAGS)
CXX: $(CXX)
CXXFLAGS: $(CXXFLAGS)
DEFINES: $(DEFINES)
LDFLAGS: $(LDFLAGS)
LIBRARY_DIRS: $(LIBRARY_DIRS)
LIBS: $(LIBS)
LINK: $(LINK)
MD: $(MD)
MOC: $(MOC)
MOC_TMP: $(MOC_TMP)
OBJCFLAGS: $(OBJCFLAGS)
QT_VERSION: $(QT_VERSION)
RARCH_OBJ: $(RARCH_OBJ)
WINDRES: $(WINDRES)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
$(MOC_SRC):
@$(if $(Q), $(shell echo echo MOC $<),)
@ -285,9 +312,10 @@ uninstall:
rm -rf $(DESTDIR)$(ASSETS_DIR)
clean:
rm -rf $(OBJDIR_BASE)
rm -f $(TARGET)
rm -f *.d
@$(if $(Q), echo $@,)
$(Q)rm -rf $(OBJDIR_BASE)
$(Q)rm -f $(TARGET)
$(Q)rm -f *.d
.PHONY: all install uninstall clean

View File

@ -790,6 +790,9 @@ ifeq ($(HAVE_EMSCRIPTEN), 1)
ifeq ($(HAVE_RWEBAUDIO), 1)
OBJ += audio/drivers/rwebaudio.o
endif
ifeq ($(HAVE_AUDIOWORKLET), 1)
OBJ += audio/drivers/audioworklet.o
endif
endif
ifeq ($(HAVE_BLUETOOTH), 1)
@ -929,6 +932,10 @@ ifeq ($(HAVE_PIPEWIRE), 1)
OBJ += audio/drivers_microphone/pipewire.o
endif
ifeq ($(HAVE_PIPEWIRE_STABLE), 1)
OBJ += camera/drivers/pipewire.o
endif
LIBS += $(PIPEWIRE_LIBS)
DEF_FLAGS += $(PIPEWIRE_CFLAGS)
endif
@ -978,6 +985,12 @@ ifeq ($(HAVE_WINMM), 1)
LIBS += -lwinmm
endif
ifeq ($(HAVE_COREMIDI), 1)
OBJ += midi/drivers/coremidi.o
DEFINES += -DHAVE_COREMIDI
LIBS += -framework CoreMIDI
endif
# Audio Resamplers
ifeq ($(HAVE_NEON),1)
@ -1337,6 +1350,10 @@ ifeq ($(HAVE_X11), 1)
ifeq ($(HAVE_XCB),1)
LIBS += -lX11-xcb
endif
ifeq ($(HAVE_XI2),1)
LIBS += -lXi
DEFINES += -DHAVE_XI2
endif
ifeq ($(HAVE_XSCRNSAVER),1)
LIBS += -lXss
endif
@ -1494,6 +1511,7 @@ ifeq ($(HAVE_PLAIN_DRM), 1)
INCLUDE_DIRS += -I/usr/include/libdrm
endif
LIBS += -ldrm
HAVE_AND_WILL_USE_DRM = 1
endif
ifeq ($(HAVE_VITAGL), 1)
@ -1526,7 +1544,10 @@ ifeq ($(HAVE_GL_CONTEXT), 1)
endif
ifeq ($(HAVE_EMSCRIPTEN), 1)
OBJ += gfx/drivers_context/emscriptenegl_ctx.o
ifeq ($(HAVE_EGL), 1)
OBJ += gfx/drivers_context/emscriptenegl_ctx.o
endif
OBJ += gfx/drivers_context/emscriptenwebgl_ctx.o
endif
ifeq ($(HAVE_MALI_FBDEV), 1)
@ -1570,10 +1591,11 @@ ifeq ($(HAVE_GL_CONTEXT), 1)
DEF_FLAGS += $(OPENGLES_CFLAGS)
ifeq ($(HAVE_OPENGLES3), 1)
DEFINES += -DHAVE_OPENGLES3
OBJ += $(LIBRETRO_COMM_DIR)/glsym/glsym_es3.o
else
DEFINES += -DHAVE_OPENGLES2
OBJ += $(LIBRETRO_COMM_DIR)/glsym/glsym_es2.o
endif
OBJ += $(LIBRETRO_COMM_DIR)/glsym/glsym_es2.o
else
DEFINES += -DHAVE_GL_SYNC
OBJ += $(LIBRETRO_COMM_DIR)/glsym/glsym_gl.o
@ -1990,6 +2012,7 @@ ifeq ($(HAVE_BUILTINZLIB), 1)
HAVE_ZLIB_COMMON = 1
OBJ += $(DEPS_DIR)/libz/adler32.o \
$(DEPS_DIR)/libz/libz-crc32.o \
$(DEPS_DIR)/libz/compress.o \
$(DEPS_DIR)/libz/deflate.o \
$(DEPS_DIR)/libz/gzclose.o \
$(DEPS_DIR)/libz/gzlib.o \
@ -1999,6 +2022,7 @@ ifeq ($(HAVE_BUILTINZLIB), 1)
$(DEPS_DIR)/libz/inflate.o \
$(DEPS_DIR)/libz/inftrees.o \
$(DEPS_DIR)/libz/trees.o \
$(DEPS_DIR)/libz/uncompr.o \
$(DEPS_DIR)/libz/zutil.o
INCLUDE_DIRS += -I$(LIBRETRO_COMM_DIR)/include/compat/zlib
else ifeq ($(HAVE_ZLIB),1)
@ -2168,6 +2192,65 @@ ifeq ($(HAVE_V4L2),1)
LIBS += $(V4L2_LIBS)
endif
# FFmpeg
ifeq ($(HAVE_FFMPEG), 1)
DEFINES += -DHAVE_FFMPEG
INCLUDE_DIRS += -Iffmpeg
DEF_FLAGS += $(FFMPEG_CFLAGS) -Wno-deprecated-declarations
LIBS += $(FFMPEG_LIBS)
ifeq ($(HAVE_AVCODEC), 1)
DEFINES += -DHAVE_AVCODEC
DEF_FLAGS += $(AVCODEC_CFLAGS)
LIBS += $(AVCODEC_LIBS)
endif
ifeq ($(HAVE_AVDEVICE), 1)
DEFINES += -DHAVE_AVDEVICE
DEF_FLAGS += $(AVDEVICE_CFLAGS)
LIBS += $(AVDEVICE_LIBS)
endif
ifeq ($(HAVE_AVFORMAT), 1)
DEFINES += -DHAVE_AVFORMAT
DEF_FLAGS += $(AVFORMAT_CFLAGS)
LIBS += $(AVFORMAT_LIBS)
endif
ifeq ($(HAVE_AVUTIL), 1)
DEFINES += -DHAVE_AVUTIL
DEF_FLAGS += $(AVUTIL_CFLAGS)
LIBS += $(AVUTIL_LIBS)
endif
ifeq ($(HAVE_SWSCALE), 1)
DEFINES += -DHAVE_SWSCALE
DEF_FLAGS += $(SWSCALE_CFLAGS)
LIBS += $(SWSCALE_LIBS)
endif
ifeq ($(HAVE_SWRESAMPLE), 1)
DEFINES += -DHAVE_SWRESAMPLE
DEF_FLAGS += $(SWRESAMPLE_CFLAGS)
LIBS += $(SWRESAMPLE_LIBS)
endif
endif
ifeq ($(HAVE_FFMPEG), 1)
ifeq ($(HAVE_AVFORMAT), 1)
ifeq ($(HAVE_AVDEVICE), 1)
ifeq ($(HAVE_AVCODEC), 1)
ifeq ($(HAVE_AVUTIL), 1)
ifeq ($(HAVE_SWSCALE), 1)
OBJ += camera/drivers/ffmpeg.o
endif
endif
endif
endif
endif
endif
# Accessibility
ifeq ($(HAVE_ACCESSIBILITY), 1)
DEFINES += -DHAVE_ACCESSIBILITY
@ -2192,11 +2275,16 @@ ifeq ($(HAVE_NETWORKING), 1)
$(LIBRETRO_COMM_DIR)/net/net_socket.o \
core_updater_list.o \
network/natt.o \
tasks/task_http.o \
tasks/task_netplay_lan_scan.o \
tasks/task_netplay_nat_traversal.o \
tasks/task_netplay_find_content.o
ifeq ($(HAVE_EMSCRIPTEN), 1)
OBJ += tasks/task_http_emscripten.o
else
OBJ += tasks/task_http.o
endif
ifeq ($(HAVE_MENU), 1)
OBJ += tasks/task_pl_thumbnail_download.o
endif
@ -2403,7 +2491,7 @@ ifeq ($(HAVE_FFMPEG), 1)
cores/libretro-ffmpeg/video_buffer.o \
$(LIBRETRO_COMM_DIR)/rthreads/tpool.o
LIBS += $(AVCODEC_LIBS) $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(SWRESAMPLE_LIBS) $(FFMPEG_LIBS)
LIBS += $(AVCODEC_LIBS) $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(SWRESAMPLE_LIBS) $(FFMPEG_LIBS) $(AVDEVICE_LIBS)
DEFINES += -DHAVE_FFMPEG
DEF_FLAGS += $(AVCODEC_CFLAGS) $(AVFORMAT_CFLAGS) $(AVUTIL_CFLAGS) $(SWSCALE_CFLAGS) $(SWRESAMPLE_CFLAGS) \
-Wno-deprecated-declarations
@ -2648,4 +2736,36 @@ ifeq ($(HAVE_ODROIDGO2), 1)
gfx/drivers/oga_gfx.o
endif
ifeq ($(HAVE_GAME_AI),1)
DEFINES += -DHAVE_GAME_AI
OBJ += ai/game_ai.o
endif
# Detect the operating system
UNAME := $(shell uname -s)
# Check if the system is MSYS2 (MINGW64 or MINGW32)
ifneq ($(findstring MINGW,$(UNAME)),)
$(info Detected MSYS2 environment)
NT_VERSION := $(shell \
echo '#include <windows.h>' > temp.c; \
echo '#ifdef _WIN32_WINNT' >> temp.c; \
echo '#define GET_MACRO_VALUE(x) #x' >> temp.c; \
echo '#define EXPAND_MACRO_VALUE(x) GET_MACRO_VALUE(x)' >> temp.c; \
echo '#pragma message("_WIN32_WINNT=" EXPAND_MACRO_VALUE(_WIN32_WINNT))' >> temp.c; \
echo '#endif' >> temp.c; \
$(CC) -c temp.c 2>&1 | sed -n 's/^.*_WIN32_WINNT=\(0x[0-9A-Fa-f]\+\).*/\1/p'; \
rm -f temp.c temp.o)
ifneq ($(NT_VERSION),)
ifeq ($(shell [ $$(( $(NT_VERSION) )) -gt $$(( 0x602 )) ] && echo true),true)
LIBS += -lxaudio2_9
endif
else
$(warning Windows NT version macro (_WIN32_WINNT) is not defined.)
endif
endif
##################################

View File

@ -3,8 +3,8 @@ LIBRETRO =
DEBUG = 0
CONSOLE_LOG = 0
GRIFFIN_BUILD = 1
HAVE_STATIC_DUMMY ?= 0
GRIFFIN_BUILD = 0
HAVE_STATIC_DUMMY ?= 0
WHOLE_ARCHIVE_LINK = 0
BUILD_3DSX = 1
BUILD_3DS = 0
@ -89,21 +89,22 @@ else
HAVE_REWIND = 1
HAVE_AUDIOMIXER = 1
HAVE_RWAV = 1
#HAVE_NETWORKING = 1
#HAVE_IFINFO = 1
#HAVE_CHEEVOS = 1
#HAVE_SOCKET_LEGACY = 1
HAVE_CHEATS = 1
HAVE_VIDEO_FILTER = 1
HAVE_DSP_FILTER = 1
HAVE_CONFIGFILE = 1
HAVE_OVERLAY = 1
HAVE_GFX_WIDGETS = 1
HAVE_NETWORKING = 1
HAVE_IFINFO = 1
HAVE_CHEEVOS = 1
HAVE_THREADS = 1
#HAVE_SSL = 1
#HAVE_BUILTINMBEDTLS = 1
HAVE_BUILTINMBEDTLS = 1
HAVE_CORE_INFO_CACHE = 1
HAVE_CLOUDSYNC = 1
include Makefile.common
CFLAGS += $(DEF_FLAGS)
BLACKLIST :=
BLACKLIST += input/input_overlay.o
BLACKLIST += tasks/task_overlay.o
OBJ := $(filter-out $(BLACKLIST),$(OBJ))
endif
ifeq ($(strip $(DEVKITPRO)),)
@ -154,8 +155,7 @@ LIBDIRS := -L. -L$(CTRULIB)/lib
ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -marm -mfpu=vfp -mtp=soft
CFLAGS += -mword-relocations \
-fomit-frame-pointer -ffast-math \
-Werror=implicit-function-declaration \
-ffast-math \
$(ARCH)
#CFLAGS += -Wall
@ -168,7 +168,7 @@ endif
ifeq ($(DEBUG), 1)
CFLAGS += -O0 -g
else
CFLAGS += -O3
CFLAGS += -fomit-frame-pointer -O3
endif
ifeq ($(CONSOLE_LOG), 1)
@ -188,17 +188,17 @@ CFLAGS += -I. \
-Ideps \
-Ideps/7zip \
-Ideps/stb \
-Ideps/mbedtls \
-Ideps/rcheevos/include \
-Ilibretro-common/include \
-Ilibretro-common/include/compat/zlib
CFLAGS += -DRARCH_INTERNAL -DRARCH_CONSOLE
CFLAGS += -DHAVE_DSP_FILTER
CFLAGS += -DHAVE_VIDEO_FILTER
CFLAGS += -DHAVE_FILTERS_BUILTIN $(DEFINES)
CFLAGS += -DHAVE_CHEATS
CFLAGS += -DHAVE_ONLINE_UPDATER -DHAVE_UPDATE_CORES -DHAVE_UPDATE_CORE_INFO
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
CFLAGS += -Werror=implicit-function-declaration
ASFLAGS := -g $(ARCH) -O3
LDFLAGS += -specs=ctr/3dsx_custom.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
@ -222,6 +222,10 @@ else
LIBS += -lctru
endif
ifneq ($(V),1)
Q := @
endif
ifeq ($(BUILD_3DSX), 1)
TARGET_3DSX := $(TARGET).3dsx $(TARGET).smdh
endif
@ -234,16 +238,7 @@ ifeq ($(BUILD_CIA), 1)
TARGET_CIA := $(TARGET).cia
endif
.PHONY: $(BUILD) clean all
all: $(TARGET)
$(TARGET): $(TARGET_3DSX) $(TARGET_3DS) $(TARGET_CIA)
$(TARGET).3dsx: $(TARGET).elf
$(TARGET).elf: $(OBJ) $(LIB_CORE_FULL)
PREFIX := $(DEVKITARM)/bin/arm-none-eabi-
CC := $(PREFIX)gcc
CXX := $(PREFIX)g++
AS := $(PREFIX)as
@ -277,6 +272,36 @@ else
MAKEROM = $(CTRMAKEROM)
endif
.PHONY: $(BUILD) clean all
all: info $(TARGET)
define INFO
AR: $(AR)
ASFLAGS: $(ASFLAGS)
CC: $(CC)
CFLAGS: $(CFLAGS)
CXX: $(CXX)
CXXFLAGS: $(CXXFLAGS)
INCDIRS: $(INCDIRS)
LD: $(LD)
LDFLAGS: $(LDFLAGS)
LIBDIRS: $(LIBDIRS)
LIBS: $(LIBS)
OBJ: $(OBJ)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
$(TARGET): $(TARGET_3DSX) $(TARGET_3DS) $(TARGET_CIA)
$(TARGET).3dsx: $(TARGET).elf
$(TARGET).elf: $(OBJ) $(LIB_CORE_FULL)
%.o: %.vsh %.gsh
$(DEVKITTOOLS)/bin/picasso $^ -o $*.shbin
$(DEVKITTOOLS)/bin/bin2s $*.shbin | $(PREFIX)as -o $@
@ -288,19 +313,24 @@ endif
rm $*.shbin
%.o: %.cpp
$(CXX) -c -o $@ $< $(CXXFLAGS) $(INCDIRS)
@$(if $(Q), $(shell echo echo CXX $<),)
$(Q)$(CXX) -c -o $@ $< $(CXXFLAGS) $(INCDIRS)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS) $(INCDIRS)
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) -c -o $@ $< $(CFLAGS) $(INCDIRS)
%.o: %.s
$(CC) -c -o $@ $< $(ASFLAGS)
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) -c -o $@ $< $(ASFLAGS)
%.o: %.S
$(CC) -c -o $@ $< $(ASFLAGS)
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) -c -o $@ $< $(ASFLAGS)
%.a:
$(AR) -rc $@ $^
@$(if $(Q), $(shell echo echo AR $<),)
$(Q)$(AR) -rc $@ $^
%.vsh:
@ -316,7 +346,8 @@ endif
$(DEVKITTOOLS)/bin/3dsxtool $< $@ $(_3DSXFLAGS)
$(TARGET).elf: ctr/3dsx_custom_crt0.o
$(LD) $(LDFLAGS) $(OBJ) $(LIBDIRS) $(LIBS) -o $@
@$(if $(Q), $(shell echo echo LD $@),)
$(Q)$(LD) $(LDFLAGS) $(OBJ) $(LIBDIRS) $(LIBS) -o $@
$(NM) -CSn $@ > $(notdir $*.lst)
$(TARGET).bnr: $(TARGET).elf $(APP_BANNER) $(APP_AUDIO)
@ -332,15 +363,16 @@ $(TARGET).cia: $(TARGET).elf $(TARGET).bnr $(TARGET).icn $(APP_RSF)
$(MAKEROM) -f cia -o $@ $(MAKEROM_ARGS_COMMON) -DAPP_ENCRYPTED=false
clean:
rm -f $(OBJ)
rm -f $(TARGET).3dsx
rm -f $(TARGET).elf
rm -f $(TARGET).3ds
rm -f $(TARGET).cia
rm -f $(TARGET).smdh
rm -f $(TARGET).bnr
rm -f $(TARGET).icn
rm -f ctr/ctr_config_*.o
rm -f ctr/3dsx_custom_crt0.o
@$(if $(Q), echo $@,)
$(Q)rm -f $(OBJ)
$(Q)rm -f $(TARGET).3dsx
$(Q)rm -f $(TARGET).elf
$(Q)rm -f $(TARGET).3ds
$(Q)rm -f $(TARGET).cia
$(Q)rm -f $(TARGET).smdh
$(Q)rm -f $(TARGET).bnr
$(Q)rm -f $(TARGET).icn
$(Q)rm -f ctr/ctr_config_*.o
$(Q)rm -f ctr/3dsx_custom_crt0.o
.PHONY: clean

View File

@ -85,7 +85,6 @@ ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -marm -mfpu=vfp -mtp=so
CFLAGS += -mword-relocations \
-fomit-frame-pointer -ffast-math \
-Werror=implicit-function-declaration \
$(ARCH)
#CFLAGS += -Wall
@ -111,6 +110,7 @@ CFLAGS += -I. -Ideps/7zip -Ideps/stb -Ilibretro-common/include -Ilibretro-common
CFLAGS += -DRARCH_CONSOLE -DIS_SALAMANDER
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
CFLAGS += -Werror=implicit-function-declaration
ASFLAGS := -g $(ARCH) -O3
LDFLAGS += -specs=ctr/3dsx_custom.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)

View File

@ -195,6 +195,10 @@ else
LIB_CORE += -lretro_dos
endif
ifneq ($(V),1)
Q := @
endif
DEPENDS_TMP := $(OFILES:.o=.d)
DEPENDS := $(filter-out libretro_libnx.a,$(DEPENDS_TMP))
@ -203,19 +207,43 @@ DEPENDS := $(filter-out libretro_libnx.a,$(DEPENDS_TMP))
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(OUTPUT)
all: info $(OUTPUT)
define INFO
CC: $(CC)
CFLAGS: $(CFLAGS)
CXX: $(CXX)
DEPENDS: $(DEPENDS)
LDFLAGS: $(LDFLAGS)
LIBDIRS: $(LIBDIRS)
LIBS: $(LIBS)
LIB_CORE: $(LIB_CORE)
OBJ: $(OBJ)
OUTPUT: $(OUTPUT)
PLATEXTRA: $(PLATEXTRA)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
$(OUTPUT): $(OBJ)
$(CXX) -o $@ $(LDFLAGS) $(LIBDIRS) $(OBJ) $(PLATEXTRA) -L. $(LIB_CORE) $(LIBS)
@$(if $(Q), $(shell echo echo CXX $<),)
$(Q)$(CXX) -o $@ $(LDFLAGS) $(LIBDIRS) $(OBJ) $(PLATEXTRA) -L. $(LIB_CORE) $(LIBS)
%.o: %.c
$(CC) -c -o $@ $(CFLAGS) $<
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) -c -o $@ $(CFLAGS) $<
%.o: %.cpp
$(CXX) -c -o $@ $(CFLAGS) $<
@$(if $(Q), $(shell echo echo CXX $<),)
$(Q)$(CXX) -c -o $@ $(CFLAGS) $<
clean:
rm -f $(DEPENDS) $(OBJ) $(OUTPUT)
@$(if $(Q), $(shell echo echo RM),)
$(Q)rm -f $(DEPENDS) $(OBJ) $(OUTPUT)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data

View File

@ -7,29 +7,33 @@ else
TARGET := $(LIBRETRO)_libretro.js
endif
endif
EOPT = USE_ZLIB=1 # Emscripten specific options
EOPTS = $(addprefix -s $(EMPTY), $(EOPT)) # Add '-s ' to each option
TARGET_BASE := $(subst .js,,$(TARGET))
OS = Emscripten
OBJ :=
DEFINES := -DRARCH_INTERNAL -DHAVE_MAIN
DEFINES += -DHAVE_FILTERS_BUILTIN
DEFINES := -DRARCH_INTERNAL -DHAVE_MAIN -DEMSCRIPTEN -DNO_CANVAS_RESIZE
DEFINES += -DHAVE_FILTERS_BUILTIN -DHAVE_ONLINE_UPDATER -DHAVE_UPDATE_ASSETS -DHAVE_UPDATE_CORE_INFO
HAVE_PATCH = 1
HAVE_DSP_FILTER = 1
HAVE_VIDEO_FILTER = 1
HAVE_OVERLAY = 1
HAVE_NETWORKING ?= 1
HAVE_LIBRETRODB = 1
HAVE_COMPRESSION = 1
HAVE_UPDATE_ASSETS = 1
HAVE_ONLINE_UPDATER = 1
HAVE_GLSL = 1
HAVE_SCREENSHOTS = 1
HAVE_REWIND = 1
HAVE_AUDIOMIXER = 1
HAVE_CC_RESAMPLER = 1
HAVE_EGL = 1
HAVE_EGL ?= 0
HAVE_OPENGLES = 1
HAVE_RJPEG = 0
HAVE_RPNG = 1
HAVE_RJPEG = 0
HAVE_RPNG = 1
HAVE_EMSCRIPTEN = 1
HAVE_MENU = 1
HAVE_MENU ?= 1
HAVE_GFX_WIDGETS = 1
HAVE_RGUI = 1
HAVE_SDL = 0
@ -41,13 +45,23 @@ HAVE_STATIC_AUDIO_FILTERS = 1
HAVE_STB_FONT = 1
HAVE_CONFIGFILE = 1
HAVE_COMMAND = 1
HAVE_STDIN_CMD = 1
HAVE_STDIN_CMD ?= 1
HAVE_CHEATS = 1
HAVE_IBXM = 1
HAVE_CORE_INFO_CACHE = 1
HAVE_7ZIP = 1
HAVE_BSV_MOVIE = 1
HAVE_AL = 1
HAVE_CHD ?= 0
HAVE_NETPLAYDISCOVERY ?= 0
HAVE_AL ?= 1
# enables pthreads, requires special headers on the web server:
# see https://web.dev/articles/coop-coep
HAVE_THREADS ?= 0
# requires HAVE_THREADS
HAVE_AUDIOWORKLET ?= 0
# WARNING -- READ BEFORE ENABLING
# The rwebaudio driver is known to have several audio bugs, such as
@ -55,77 +69,206 @@ HAVE_AL = 1
# It works perfectly on chrome, but even firefox has really bad audio quality.
# I should also note, the driver on iOS is completely broken (crashes the page).
# You have been warned.
HAVE_RWEBAUDIO = 0
HAVE_RWEBAUDIO ?= 0
# whether the browser thread is allowed to block to wait for audio to play,
# may lead to the issues mentioned above.
# currently this variable is only used by audioworklet;
# rwebaudio will always busywait and openal will never busywait.
ALLOW_AUDIO_BUSYWAIT ?= 0
# minimal asyncify; better performance than full asyncify,
# but sleeping on the main thread is only possible in some places.
MIN_ASYNC ?= 0
# runs RetroArch on a pthread instead of the browser thread; requires HAVE_THREADS
PROXY_TO_PTHREAD ?= 0
# recommended FS when using HAVE_THREADS
HAVE_WASMFS ?= 0
# enables OPFS (origin private file system) and FETCHFS, requires PROXY_TO_PTHREAD
HAVE_EXTRA_WASMFS ?= 0
# enable javascript filesystem tracking, incompatible with HAVE_WASMFS
FS_DEBUG ?= 0
# help diagnose GL problems (can cause issues in normal operation)
GL_DEBUG ?= 0
# does nothing on its own, but automatically selected by some other options
WASM_WORKERS = 0
HAVE_OPENGLES ?= 1
HAVE_OPENGLES3 ?= 0
ASYNC ?= 0
ifeq ($(LIBRETRO), mupen64plus)
ASYNC = 1
endif
LTO ?= 0
ifeq ($(LIBRETRO), tyrquake)
LTO = 0
endif
PTHREAD_POOL_SIZE ?= 4
PTHREAD ?= 0
STACK_SIZE ?= 4194304
INITIAL_HEAP ?= 134217728
MEMORY ?= 134217728
PRECISE_F32 = 1
# 4194304 ----- 4 MiB (Stack: recommended)
# 8388608 ----- 8 MiB
# 16777216 ---- 16 MiB
# 33554432 ---- 32 MiB
# 67108864 ---- 64 MiB
# 134217728 --- 128 MiB (Heap: recommended) (Stack: recommended for some cores [mupen64plus_next])
# 268435456 --- 256 MiB (Heap: recommended for some cores [mupen64plus_next])
# 536870912 --- 512 MiB (Heap: needed for some cores [mednafen_psx(_hw)])
# 1073741824 -- 1 GiB
# 1610612736 -- 1.5 GiB
# 2147483648 -- 2 GiB
OBJDIR := obj-emscripten
#if you compile with SDL2 flag add this Emscripten flag "-s USE_SDL=2" to LDFLAGS:
EXPORTED_FUNCTIONS = _main,_malloc,_free,_cmd_savefiles,_cmd_save_state,_cmd_load_state,_cmd_undo_save_state,_cmd_undo_load_state,_cmd_take_screenshot,\
_cmd_toggle_menu,_cmd_reload_config,_cmd_toggle_grab_mouse,_cmd_toggle_game_focus,_cmd_reset,_cmd_toggle_pause,_cmd_pause,_cmd_unpause,\
_cmd_set_volume,_cmd_set_shader,_cmd_cheat_set_code,_cmd_cheat_get_code,_cmd_cheat_toggle_index,_cmd_cheat_get_code_state,_cmd_cheat_realloc,\
_cmd_cheat_get_size,_cmd_cheat_apply_cheats,EmscriptenSendCommand,EmscriptenReceiveCommandReply
LIBS := -s USE_ZLIB=1
LDFLAGS := -L. --no-heap-copy -s $(LIBS) -s TOTAL_MEMORY=$(MEMORY) -s NO_EXIT_RUNTIME=0 -s FULL_ES2=1 \
-s "EXPORTED_RUNTIME_METHODS=['callMain', 'FS', 'PATH', 'ERRNO_CODES']" \
-s ALLOW_MEMORY_GROWTH=1 -s "EXPORTED_FUNCTIONS=['_main', '_malloc', '_cmd_savefiles', '_cmd_save_state', '_cmd_load_state', '_cmd_take_screenshot']" \
-s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME="libretro_$(subst -,_,$(LIBRETRO))" \
-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 \
--js-library emscripten/library_errno_codes.js \
--js-library emscripten/library_rwebcam.js
EXPORTS := callMain,FS,PATH,ERRNO_CODES,ENV,stringToNewUTF8,UTF8ToString,Browser,EmscriptenSendCommand,EmscriptenReceiveCommandReply
ifeq ($(HAVE_RWEBAUDIO), 1)
LDFLAGS += --js-library emscripten/library_rwebaudio.js
DEFINES += -DHAVE_RWEBAUDIO
endif
ifeq ($(HAVE_AL), 1)
LDFLAGS += -lopenal
DEFINES += -DHAVE_AL
ASYNC = 1
endif
LIBS := -s USE_ZLIB=1
CFLAGS := -s USE_ZLIB=1
ifneq ($(PTHREAD), 0)
LDFLAGS += -s WASM_MEM_MAX=1073741824 -pthread -s PTHREAD_POOL_SIZE=$(PTHREAD)
CFLAGS += -pthread
HAVE_THREADS=1
else
HAVE_THREADS=0
endif
ifeq ($(ASYNC), 1)
LDFLAGS += -s ASYNCIFY=$(ASYNC) -s ASYNCIFY_STACK_SIZE=8192
ifeq ($(DEBUG), 1)
LDFLAGS += -s ASYNCIFY_DEBUG=1 # -s ASYNCIFY_ADVISE
ifeq ($(HAVE_EXTRA_WASMFS), 1)
LIBS += -lfetchfs.js -lopfs.js
DEFINES += -DHAVE_EXTRA_WASMFS
override HAVE_WASMFS = 1
ifeq ($(PROXY_TO_PTHREAD), 0)
$(error ERROR: HAVE_EXTRA_WASMFS requires PROXY_TO_PTHREAD)
endif
endif
ifeq ($(HAVE_WASMFS), 1)
LIBS += -s WASMFS -s FORCE_FILESYSTEM=1
endif
# note: real PROXY_TO_PTHREAD is not used here; we do the pthread management ourselves
ifeq ($(PROXY_TO_PTHREAD), 1)
LIBS += -s OFFSCREENCANVAS_SUPPORT
DEFINES += -DPROXY_TO_PTHREAD -DEMSCRIPTEN_STACK_SIZE=$(STACK_SIZE)
override HAVE_THREADS = 1
override WASM_WORKERS = 1
# use the default stack size for the browser thread; the RetroArch thread will be created with the specified stack size
override STACK_SIZE = 4194304
else ifeq ($(HAVE_AL), 1)
override ASYNC = 1
endif
ifeq ($(HAVE_SDL2), 1)
LIBS += -s USE_SDL=2
DEFINES += -DHAVE_SDL2
endif
LDFLAGS := -L. --no-heap-copy -s STACK_SIZE=$(STACK_SIZE) -s INITIAL_MEMORY=$(INITIAL_HEAP) \
-s EXPORTED_RUNTIME_METHODS=$(EXPORTS) \
-s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS="$(EXPORTED_FUNCTIONS)" \
-s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME="libretro_$(subst -,_,$(LIBRETRO))" \
-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0 \
-s ENVIRONMENT=web,worker \
--extern-pre-js emscripten/pre.js \
--js-library emscripten/library_rwebcam.js \
--js-library emscripten/library_platform_emscripten.js
ifeq ($(HAVE_OPENGLES), 1)
ifeq ($(HAVE_OPENGLES3), 1)
LDFLAGS += -s FULL_ES3=1 -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2
else
LDFLAGS += -s FULL_ES2=1 -s MIN_WEBGL_VERSION=1 -s MAX_WEBGL_VERSION=2
endif
endif
ifeq ($(GL_DEBUG), 1)
LDFLAGS += -s GL_ASSERTIONS=1 -s GL_DEBUG=1
DEFINES += -DHAVE_GL_DEBUG_ES=1
endif
ifeq ($(FS_DEBUG), 1)
LDFLAGS += -s FS_DEBUG=1
endif
ifeq ($(HAVE_RWEBAUDIO), 1)
LDFLAGS += --js-library emscripten/library_rwebaudio.js
DEFINES += -DHAVE_RWEBAUDIO
endif
ifeq ($(HAVE_AUDIOWORKLET), 1)
LDFLAGS += -s AUDIO_WORKLET=1
DEFINES += -DHAVE_AUDIOWORKLET
override WASM_WORKERS = 1
ifeq ($(HAVE_THREADS), 0)
$(error ERROR: AUDIOWORKLET requires HAVE_THREADS)
endif
ifeq ($(PROXY_TO_PTHREAD), 1)
else ifeq ($(ASYNC), 1)
else
DEFINES += -DEMSCRIPTEN_AUDIO_EXTERNAL_BLOCK
ifeq ($(MIN_ASYNC), 1)
DEFINES += -DEMSCRIPTEN_AUDIO_ASYNC_BLOCK
else
DEFINES += -DEMSCRIPTEN_AUDIO_FAKE_BLOCK
endif
ifneq ($(ALLOW_AUDIO_BUSYWAIT), 1)
DEFINES += -DEMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
endif
endif
endif
ifeq ($(ALLOW_AUDIO_BUSYWAIT), 1)
DEFINES += -DEMSCRIPTEN_AUDIO_BUSYWAIT
endif
# explanation of some of these defines:
# EMSCRIPTEN_AUDIO_EXTERNAL_BLOCK: audio blocking occurs in the main loop instead of in the audio driver functions.
# EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK: along with above, enables external blocking in the write function.
# ALLOW_AUDIO_BUSYWAIT: write function will busywait. init function may still use an external block.
# EMSCRIPTEN_AUDIO_ASYNC_BLOCK: external block uses emscripten_sleep (requires MIN_ASYNC).
# EMSCRIPTEN_AUDIO_FAKE_BLOCK: external block uses main loop timing (doesn't require asyncify).
# when building with either PROXY_TO_PTHREAD or ASYNC (full asyncify), none of the above are required.
ifeq ($(HAVE_AL), 1)
LDFLAGS += -lopenal
DEFINES += -DHAVE_AL
endif
ifeq ($(HAVE_THREADS), 1)
LDFLAGS += -pthread -s PTHREAD_POOL_SIZE=$(PTHREAD_POOL_SIZE)
CFLAGS += -pthread -s SHARED_MEMORY
endif
ifeq ($(WASM_WORKERS), 1)
LDFLAGS += -s WASM_WORKERS=2
endif
ifeq ($(ASYNC), 1)
DEFINES += -DEMSCRIPTEN_ASYNCIFY -DEMSCRIPTEN_FULL_ASYNCIFY
LDFLAGS += -s ASYNCIFY=1 -s ASYNCIFY_STACK_SIZE=8192
ifeq ($(DEBUG), 1)
#LDFLAGS += -s ASYNCIFY_DEBUG=1 # broken?
endif
else ifeq ($(MIN_ASYNC), 1)
DEFINES += -DEMSCRIPTEN_ASYNCIFY -DEMSCRIPTEN_MIN_ASYNCIFY
LDFLAGS += -s ASYNCIFY=1 -s ASYNCIFY_STACK_SIZE=8192 -s ASYNCIFY_IGNORE_INDIRECT=1 -s ASYNCIFY_ADD='dynCall_*,emscripten_mainloop' -s ASYNCIFY_REMOVE='threaded_worker'
ifeq ($(DEBUG), 1)
LDFLAGS += -s ASYNCIFY_ADVISE #-s ASYNCIFY_DEBUG=1
endif
endif
include Makefile.common
CFLAGS += $(DEF_FLAGS) -Ideps -Ideps/stb
libretro :=
libretro =
libretro_new =
ifeq ($(HAVE_STATIC_DUMMY),1)
DEFINES += -DHAVE_STATIC_DUMMY
else
libretro += libretro_emscripten.bc
libretro = libretro_emscripten.bc
libretro_new = libretro_emscripten.a
endif
ifneq ($(V), 1)
@ -133,46 +276,55 @@ ifneq ($(V), 1)
endif
ifeq ($(DEBUG), 1)
LDFLAGS += -O0 -g -gsource-map -s SAFE_HEAP=1 -s STACK_OVERFLOW_CHECK=2 -s ASSERTIONS=1
CFLAGS += -O0 -g -gsource-map -s SAFE_HEAP=1 -s SAFE_HEAP_LOG=1 -s STACK_OVERFLOW_CHECK=2 -s ASSERTIONS=1
LDFLAGS += -O0 -g -gsource-map -s SAFE_HEAP=2 -s STACK_OVERFLOW_CHECK=2 -s ASSERTIONS=1
# -O0 in cflags gives "too many locals" errors
CFLAGS += -O1 -g -gsource-map
else
LDFLAGS += -O3 -s WASM=1
LDFLAGS += -O3
# WARNING: some optimizations can break some cores (ex: LTO breaks tyrquake)
LDFLAGS += -s PRECISE_F32=$(PRECISE_F32)
ifeq ($(LTO), 1)
LDFLAGS += --llvm-lto 3
LDFLAGS += -flto
endif
CFLAGS += -O3
endif
# 128 * 1024, double the usual emscripten stack size
LDFLAGS += -s STACK_SIZE=131072
LDFLAGS += --extern-pre-js emscripten/pre.js
CFLAGS += -Wall -I. -Ilibretro-common/include -std=gnu99 #\
# -s EXPORTED_FUNCTIONS="['_main', '_malloc', '_cmd_savefiles', '_cmd_save_state', '_cmd_take_screenshot']"
CFLAGS += -Wall -I. -Ilibretro-common/include -Ideps/7zip -std=gnu99
RARCH_OBJ := $(addprefix $(OBJDIR)/,$(OBJ))
all: $(TARGET)
$(TARGET): $(RARCH_OBJ) $(libretro)
@$(if $(Q), $(shell echo echo LD $@),)
$(Q)$(LD) -o $@ $(RARCH_OBJ) $(libretro) $(LIBS) $(LDFLAGS)
$(libretro_new): ;
mv_libretro:
mv -f $(libretro) $(libretro_new) || true
# until emscripten adds something like WASM_WORKERS=2 but for audio worklets, DIY.
ifeq ($(HAVE_AUDIOWORKLET), 1)
$(TARGET): $(RARCH_OBJ) $(libretro_new) mv_libretro
@$(if $(Q), $(shell echo echo "LD $@ \<obj\> $(libretro_new) $(LIBS) $(LDFLAGS)"),)
$(Q)$(LD) -o $@ $(RARCH_OBJ) $(libretro_new) $(LIBS) $(LDFLAGS)
$(Q)tr -d '\n' < "$(TARGET_BASE).aw.js" | sed -e "s/[\/&]/\\\\&/g" -e "s/'/\\\\\\\\&/g" > _audioworklet.js
$(Q)sed -i.bak -e "s/\"$(TARGET_BASE)\.aw\.js\"/URL.createObjectURL(new Blob(['$$(cat _audioworklet.js)'],{type:'text\/javascript'}))/" -- "$@"
$(Q)rm -f "$(TARGET_BASE).aw.js" _audioworklet.js "$@".bak
else
$(TARGET): $(RARCH_OBJ) $(libretro_new) mv_libretro
@$(if $(Q), $(shell echo echo "LD $@ \<obj\> $(libretro_new) $(LIBS) $(LDFLAGS)"),)
$(Q)$(LD) -o $@ $(RARCH_OBJ) $(libretro_new) $(LIBS) $(LDFLAGS)
endif
$(OBJDIR)/%.o: %.c
@mkdir -p $(dir $@)
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) $(CFLAGS) $(DEFINES) $(EOPTS) -c -o $@ $<
$(Q)$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
$(OBJDIR)/%.o: %.cpp
@mkdir -p $(dir $@)
@$(if $(Q), $(shell echo echo CXX $<),)
$(Q)$(CXX) $(CXXFLAGS) $(DEFINES) $(EOPTS) -c -o $@ $<
$(Q)$(CXX) $(CXXFLAGS) $(DEFINES) -c -o $@ $<
clean:
rm -rf $(OBJDIR)
rm -f $(TARGET)
.PHONY: all clean
.PHONY: all clean mv_libretro

View File

@ -160,11 +160,35 @@ CXXFLAGS += $(DEF_FLAGS)
HEADERS = $(wildcard */*/*.h) $(wildcard */*.h) $(wildcard *.h)
Q := @
ifneq ($(V),1)
Q := @
endif
RARCH_OBJ := $(addprefix $(OBJDIR)/,$(OBJ))
all: $(TARGET)
all: info $(TARGET)
define INFO
ASFLAGS: $(ASFLAGS)
CC: $(CC)
CFLAGS: $(CFLAGS)
CPPFLAGS: $(CPPFLAGS)
CXX: $(CXX)
CXXFLAGS: $(CXXFLAGS)
DEFINES: $(DEFINES)
LDFLAGS: $(LDFLAGS)
LIBRARY_DIRS: $(LIBRARY_DIRS)
LIBS: $(LIBS)
LINK: $(LINK)
OBJCFLAGS: $(OBJCFLAGS)
RARCH_OBJ: $(RARCH_OBJ)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
-include $(RARCH_OBJ:.o=.d)
@ -199,9 +223,10 @@ $(OBJDIR)/%.o: %.S $(HEADERS)
$(Q)$(CC) $(CFLAGS) $(ASFLAGS) $(DEFINES) -c -o $@ $<
clean:
rm -rf $(OBJDIR_BASE)
rm -f $(TARGET)
rm -f *.d
@$(if $(Q), echo $@,)
$(Q)rm -rf $(OBJDIR_BASE)
$(Q)rm -f $(TARGET)
$(Q)rm -f *.d
.PHONY: all clean

View File

@ -233,34 +233,65 @@ else
CFLAGS += -O3
endif
ifneq ($(V),1)
Q := @
endif
OBJOUT = -o
LINKOUT = -o
LINK = $(CXX)
all: $(EXT_TARGET)
all: info $(EXT_TARGET)
define INFO
CC: $(CC)
CFLAGS: $(CFLAGS)
CXX: $(CXX)
LD: $(LD)
LDFLAGS: $(LDFLAGS)
LIBDIRS: $(LIBDIRS)
LIBS: $(LIBS)
LINK: $(LINK)
LINKOUT: $(LINKOUT)
OBJ: $(OBJ)
OBJOUT: $(OBJOUT)
PLATEXTRA: $(PLATEXTRA)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
%.dol: %.elf
$(ELF2DOL) $< $@
$(EXT_INTER_TARGET): $(OBJ)
$(LINK) $(LINKOUT)$@ $(LDFLAGS) $(LIBDIRS) $(OBJ) $(PLATEXTRA) $(LIBS)
@$(if $(Q), $(shell echo echo LINK $@),)
$(Q)$(LINK) $(LINKOUT)$@ $(LDFLAGS) $(LIBDIRS) $(OBJ) $(PLATEXTRA) $(LIBS)
%.o: %.c
$(CC) $(CFLAGS) -c $(OBJOUT)$@ $<
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) $(CFLAGS) -c $(OBJOUT)$@ $<
%.o: %.cpp
$(CXX) $(CFLAGS) -c $(OBJOUT)$@ $<
@$(if $(Q), $(shell echo echo CXX $<),)
$(Q)$(CXX) $(CFLAGS) -c $(OBJOUT)$@ $<
%.o: %.S
$(CC) $(CFLAGS) -c $(OBJOUT)$@ $<
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) $(CFLAGS) -c $(OBJOUT)$@ $<
%.binobj: %.bin
$(LD) -r -b binary $(OBJOUT)$@ $<
@$(if $(Q), $(shell echo echo LD $@),)
$(Q)$(LD) -r -b binary $(OBJOUT)$@ $<
clean:
rm -f $(EXT_TARGET)
rm -f $(EXT_INTER_TARGET)
rm -f $(OBJ)
@$(if $(Q), echo $@,)
$(Q)rm -f $(EXT_TARGET)
$(Q)rm -f $(EXT_INTER_TARGET)
$(Q)rm -f $(OBJ)
.PHONY: clean

View File

@ -176,38 +176,67 @@ else
CXXFLAGS += -O3
endif
ifneq ($(V),1)
Q := @
endif
TARGETS := $(TARGET).self
all: $(TARGETS)
all: info $(TARGETS)
define INFO
CC: $(CC)
CFLAGS: $(CFLAGS)
CXX: $(CXX)
CXXFLAGS: $(CXXFLAGS)
LD: $(LD)
LDFLAGS: $(LDFLAGS)
LIBS: $(LIBS)
OBJ: $(OBJ)
OBJOUT: $(OBJOUT)
ORBISDEV: $(ORBISDEV)
TARGET: $(TARGET)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
OBJOUT = -o
%.o: %.c
$(CC) $(CFLAGS) -c $(OBJOUT)$@ $<
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) $(CFLAGS) -c $(OBJOUT)$@ $<
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $(OBJOUT)$@ $<
@$(if $(Q), $(shell echo echo CXX $<),)
$(Q)$(CXX) $(CXXFLAGS) -c $(OBJOUT)$@ $<
%.o: %.S
$(CC) $(CFLAGS) -c $(OBJOUT)$@ $<
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) $(CFLAGS) -c $(OBJOUT)$@ $<
%.o: %.s
$(CC) -c $(OBJOUT)$@ $<
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) -c $(OBJOUT)$@ $<
$(TARGET).elf: $(OBJ)
$(LD) $(ORBISDEV)/usr/lib/crt0.o $(OBJ) $(LDFLAGS) $(LIBS) -o $(TARGET).elf
@$(if $(Q), $(shell echo echo LD $@),)
$(Q)$(LD) $(ORBISDEV)/usr/lib/crt0.o $(OBJ) $(LDFLAGS) $(LIBS) -o $(TARGET).elf
$(TARGET).oelf: $(TARGET).elf
@orbis-elf-create $(TARGET).elf $(TARGET).oelf
orbis-elf-create $(TARGET).elf $(TARGET).oelf
$(TARGET).self: $(TARGET).oelf
python $(ORBISDEV)/bin/make_fself.py --auth-info $(AUTH_INFO) $(TARGET).oelf $(TARGET).self
install:
@cp $(TARGET).self $(SELF_PATH_INSTALL)/homebrew.self
cp $(TARGET).self $(SELF_PATH_INSTALL)/homebrew.self
@echo "Installed!"
clean:
rm -f $(OBJ) $(TARGET).elf $(TARGET).oelf $(TARGET).self
$(Q)rm -f $(OBJ) $(TARGET).elf $(TARGET).oelf $(TARGET).self
.PHONY: clean all

View File

@ -83,6 +83,10 @@ ifeq ($(strip $(PS2SDK)),)
$(error "Please set PS2SDK in your environment. export PS2SDK=<path to>ps2sdk")
endif
ifneq ($(V),1)
Q := @
endif
INCDIR = -I$(PS2DEV)/gsKit/include -I$(PS2SDK)/ports/include
INCDIR += -Ilibretro-common/include -Ideps -Ideps/stb -Ideps/7zip
@ -104,10 +108,27 @@ EE_INCS = $(INCDIR)
EE_BIN = $(TARGET).elf
EE_GPVAL = $(GPVAL)
all: $(EE_BIN)
all: info $(EE_BIN)
define INFO
EE_BIN: $(EE_BIN)
EE_CC: $(EE_CC)
EE_CFLAGS: $(EE_CFLAGS)
EE_CXX: $(EE_CXX)
EE_CXXFLAGS: $(EE_CXXFLAGS)
EE_INCS: $(EE_INCS)
EE_OBJS: $(EE_OBJS)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
clean:
rm -f $(EE_BIN) $(EE_OBJS)
@$(if $(Q), $(shell echo echo RM $<),)
$(Q)rm -f $(EE_BIN) $(EE_OBJS)
prepare:
ps2client -h $(PS2_IP) reset
@ -131,3 +152,15 @@ release: all
#Include preferences
include $(PS2SDK)/samples/Makefile.pref
include $(PS2SDK)/samples/Makefile.eeglobal_cpp
%.o: %.c
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(EE_CC) $(EE_CFLAGS) $(EE_INCS) -c $< -o $@
%.o: %.cc
@$(if $(Q), $(shell echo echo CXX $<),)
$(Q)$(EE_CXX) $(EE_CXXFLAGS) $(EE_INCS) -c $< -o $@
%.o: %.cpp
@$(if $(Q), $(shell echo echo CXX $<),)
$(Q)$(EE_CXX) $(EE_CXXFLAGS) $(EE_INCS) -c $< -o $@

View File

@ -6,7 +6,7 @@
ifeq ($(strip $(PSL1GHT)),)
$(error "Please set PSL1GHT in your environment. export PSL1GHT=<path>")
endif
include $(PSL1GHT)/ppu_rules
include version.all
@ -30,10 +30,17 @@ CORE_PATH = pkg/psl1ght/pkg/USRDIR/cores/CORE.SELF
INCLUDE += -I. -Ideps -Ideps/stb -Ilibretro-common/include/compat/zlib -Ilibretro-common/include $(LIBPSL1GHT_INC) -Iinclude -Idefines -I$(PORTLIBS)/include -I$(PORTLIBS)/include/freetype2
LIBDIRS += -L. -L$(PORTLIBS)/lib
ifeq ($(HAVE_STATIC_DUMMY),1)
DEFINES += -DHAVE_STATIC_DUMMY
LIBS :=
else
LIBS := -lretro_psl1ght
endif
MACHDEP := -D__PSL1GHT__ -D__PS3__ -mcpu=cell
CFLAGS += -Wall $(MACHDEP) $(INCLUDE)
CFLAGS += -Wall $(DEFINES) $(MACHDEP) $(INCLUDE)
LDFLAGS := $(MACHDEP)
LIBS := -lretro_psl1ght -lrt -laudio -lrsx -lgcm_sys -lnet -lio -lsysutil -lsysmodule -lm -ljpgdec -lpngdec -llv2 -lnet -lnetctl -lsysfs -lfreetype -lcamera -lgem -lspurs
LIBS += -lrt -laudio -lrsx -lgcm_sys -lnet -lio -lsysutil -lsysmodule -lm -ljpgdec -lpngdec -llv2 -lnet -lnetctl -lsysfs -lfreetype -lcamera -lgem -lspurs
# system platform
system_platform = unix
@ -119,13 +126,32 @@ else
CXXFLAGS += -03 -g
endif
all: $(SELF_TARGET)
ifneq ($(V),1)
Q := @
endif
all: info $(SELF_TARGET)
define INFO
CXX: $(CXX)
LDFLAGS: $(LDFLAGS)
LIBDIRS: $(LIBDIRS)
LIBS: $(LIBS)
OBJ: $(OBJ)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
$(ELF_TARGET): $(OBJ)
$(CXX) -o $@ $(LDFLAGS) $(LIBDIRS) $(OBJ) $(LIBS)
@$(if $(Q), $(shell echo echo CXX $<),)
$(Q)$(CXX) -o $@ $(LDFLAGS) $(LIBDIRS) $(OBJ) $(LIBS)
create-core: $(SELF_TARGET)
cp $(SELF_TARGET) $(CORE_PATH)
cp $(SELF_TARGET) $(CORE_PATH)
pkg: create-core
$(PKG) --contentid $(CONTENTID) pkg/psl1ght/pkg/ $(PACKAGE_BASENAME).pkg
@ -133,7 +159,11 @@ pkg: create-core
# $(PACKAGE_FINALIZE) $(PACKAGE_BASENAME).gnpdrm.pkg
clean:
ifneq ($(V),1)
@echo RM
else
rm -f $(ELF_TARGET)
rm -f $(OBJ)
endif
.PHONY: clean

View File

@ -164,7 +164,9 @@ CXXFLAGS += $(DEF_FLAGS)
HEADERS = $(wildcard */*/*.h) $(wildcard */*.h) $(wildcard *.h)
Q := @
ifneq ($(V),1)
Q := @
endif
RARCH_OBJ := $(addprefix $(OBJDIR)/,$(OBJ))
@ -182,8 +184,29 @@ X-OD-NeedsDownscaling=true
endef
export DESKTOP_ENTRY
all: info $(TARGET) opk
all: $(TARGET) opk
define INFO
ASFLAGS: $(ASFLAGS)
CC: $(CC)
CFLAGS: $(CFLAGS)
CPPFLAGS: $(CPPFLAGS)
CXX: $(CXX)
CXXFLAGS: $(CXXFLAGS)
DEFINES: $(DEFINES)
LDFLAGS: $(LDFLAGS)
LIBRARY_DIRS: $(LIBRARY_DIRS)
LIBS: $(LIBS)
LINK: $(LINK)
OBJCFLAGS: $(OBJCFLAGS)
RARCH_OBJ: $(RARCH_OBJ)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
-include $(RARCH_OBJ:.o=.d)
@ -214,10 +237,11 @@ $(OBJDIR)/%.o: %.S $(HEADERS)
$(Q)$(CC) $(CFLAGS) $(ASFLAGS) $(DEFINES) -c -o $@ $<
clean:
rm -rf $(OBJDIR_BASE)
rm -f $(TARGET)
rm -f *.d
rm -rf $(OPK_NAME)
@$(if $(Q), echo $@,)
$(Q)rm -rf $(OBJDIR_BASE)
$(Q)rm -f $(TARGET)
$(Q)rm -f *.d
$(Q)rm -rf $(OPK_NAME)
opk: $(TARGET)
echo "$$DESKTOP_ENTRY" > default.retrofw.desktop

View File

@ -166,7 +166,9 @@ CXXFLAGS += $(DEF_FLAGS)
HEADERS = $(wildcard */*/*.h) $(wildcard */*.h) $(wildcard *.h)
Q := @
ifneq ($(V),1)
Q := @
endif
RARCH_OBJ := $(addprefix $(OBJDIR)/,$(OBJ))
@ -184,7 +186,29 @@ X-OD-NeedsDownscaling=true
endef
export DESKTOP_ENTRY
all: $(TARGET) opk
all: info $(TARGET) opk
define INFO
ASFLAGS: $(ASFLAGS)
CC: $(CC)
CFLAGS: $(CFLAGS)
CPPFLAGS: $(CPPFLAGS)
CXX: $(CXX)
CXXFLAGS: $(CXXFLAGS)
DEFINES: $(DEFINES)
LDFLAGS: $(LDFLAGS)
LIBRARY_DIRS: $(LIBRARY_DIRS)
LIBS: $(LIBS)
LINK: $(LINK)
OBJCFLAGS: $(OBJCFLAGS)
RARCH_OBJ: $(RARCH_OBJ)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
-include $(RARCH_OBJ:.o=.d)
@ -215,10 +239,11 @@ $(OBJDIR)/%.o: %.S $(HEADERS)
$(Q)$(CC) $(CFLAGS) $(ASFLAGS) $(DEFINES) -c -o $@ $<
clean:
rm -rf $(OBJDIR_BASE)
rm -f $(TARGET)
rm -f *.d
rm -rf $(OPK_NAME)
@$(if $(Q), echo $@,)
$(Q)rm -rf $(OBJDIR_BASE)
$(Q)rm -f $(TARGET)
$(Q)rm -f *.d
$(Q)rm -rf $(OPK_NAME)
opk: $(TARGET)
echo "$$DESKTOP_ENTRY" > default.rs90.desktop

View File

@ -138,7 +138,7 @@ ifeq ($(HAVE_VITAGLES), 1)
ARCHFLAGS += -DSCE_LIBC_SIZE=$(SCE_LIBC_SIZE)
endif
CFLAGS += $(ARCHFLAGS) -mword-relocations -fno-optimize-sibling-calls
CFLAGS += $(ARCHFLAGS) -mword-relocations
ifeq ($(DEBUG), 1)
CFLAGS += -g -Og
@ -147,7 +147,7 @@ else
endif
ASFLAGS := $(CFLAGS)
LDFLAGS := -Wl,-q
LDFLAGS := -Wl,-q,--pic-veneer
CFLAGS += -Wall -ffast-math
CFLAGS += -DRARCH_INTERNAL -DHAVE_SCREENSHOTS -DRARCH_CONSOLE
@ -190,6 +190,10 @@ else
LIBS += -lretro_vita
endif
ifneq ($(V),1)
Q := @
endif
LIBS += $(WHOLE_END) $(VITA_LIBS) -lm -lc
TARGETS := $(TARGET).vpk
@ -197,27 +201,53 @@ TARGETS := $(TARGET).vpk
DEPFLAGS = -MT $@ -MMD -MP -MF $*.Tdepend
POSTCOMPILE = mv -f $*.Tdepend $*.depend
all: $(TARGETS)
all: info $(TARGETS)
define INFO
ASFLAGS: $(ASFLAGS)
CC: $(CC)
CFLAGS: $(CFLAGS)
CXX: $(CXX)
CXXFLAGS: $(CXXFLAGS)
DEPFLAGS: $(DEPFLAGS)
INCDIRS: $(INCDIRS)
LD: $(LD)
LDFLAGS: $(LDFLAGS)
LIBDIRS: $(LIBDIRS)
LIBS: $(LIBS)
OBJ: $(OBJ)
POSTCOMPILE: $(POSTCOMPILE)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
%.o: %.cpp
%.o: %.cpp %.depend
$(CXX) -c -o $@ $< $(CXXFLAGS) $(INCDIRS) $(DEPFLAGS)
$(POSTCOMPILE)
@$(if $(Q), $(shell echo echo CXX $<),)
$(Q)$(CXX) -c -o $@ $< $(CXXFLAGS) $(INCDIRS) $(DEPFLAGS)
$(Q)$(POSTCOMPILE)
%.o: %.c
%.o: %.c %.depend
$(CC) -c -o $@ $< $(CFLAGS) $(INCDIRS) $(DEPFLAGS)
$(POSTCOMPILE)
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) -c -o $@ $< $(CFLAGS) $(INCDIRS) $(DEPFLAGS)
$(Q)$(POSTCOMPILE)
%.o: %.S
%.o: %.S %.depend
$(CC) -c -o $@ $< $(ASFLAGS) $(INCDIRS) $(DEPFLAGS)
$(POSTCOMPILE)
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) -c -o $@ $< $(ASFLAGS) $(INCDIRS) $(DEPFLAGS)
$(Q)$(POSTCOMPILE)
%.o: %.s
%.o: %.s %.depend
$(CC) -c -o $@ $< $(ASFLAGS) $(INCDIRS) $(DEPFLAGS)
$(POSTCOMPILE)
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) -c -o $@ $< $(ASFLAGS) $(INCDIRS) $(DEPFLAGS)
$(Q)$(POSTCOMPILE)
%.depend: ;
@ -228,7 +258,8 @@ liblibScePiglet_stub.a:
cp deps/Pigs-In-A-Blanket/piglet_stub/libScePiglet/liblibScePiglet_stub.a .
$(TARGET).elf: $(OBJ) liblibScePiglet_stub.a
$(LD) $(OBJ) $(LDFLAGS) $(LIBDIRS) $(LIBS) -o $@
@$(if $(Q), $(shell echo echo LD $@),)
$(Q)$(LD) $(OBJ) $(LDFLAGS) $(LIBDIRS) $(LIBS) -o $@
%.velf: %.elf
cp $< $<.unstripped.elf
@ -243,9 +274,11 @@ $(TARGET).elf: $(OBJ) liblibScePiglet_stub.a
vita-pack-vpk -s param.sfo -b $< $@
clean:
ifneq ($(V),1)
rm -f $(OBJ) $(TARGET).elf $(TARGET).elf.unstripped.elf $(TARGET).velf $(TARGET).self param.sfo $(TARGET).vpk
rm -rf deps/Pigs-In-A-Blanket/piglet_stub/libScePiglet
rm -f $(OBJ:.o=.depend)
endif
# Useful for developers
vpksend: $(TARGET).vpk

View File

@ -183,7 +183,9 @@ CXXFLAGS += $(DEF_FLAGS)
HEADERS = $(wildcard */*/*.h) $(wildcard */*.h) $(wildcard *.h)
Q := @
ifneq ($(V),1)
Q := @
endif
RARCH_OBJ := $(addprefix $(OBJDIR)/,$(OBJ))
@ -202,7 +204,29 @@ define APPINFO
endef
export APPINFO
all: $(TARGET) ipk
all: info $(TARGET) ipk
define INFO
ASFLAGS: $(ASFLAGS)
CC: $(CC)
CFLAGS: $(CFLAGS)
CPPFLAGS: $(CPPFLAGS)
CXX: $(CXX)
CXXFLAGS: $(CXXFLAGS)
DEFINES: $(DEFINES)
LDFLAGS: $(LDFLAGS)
LIBRARY_DIRS: $(LIBRARY_DIRS)
LIBS: $(LIBS)
LINK: $(LINK)
OBJCFLAGS: $(OBJCFLAGS)
RARCH_OBJ: $(RARCH_OBJ)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
-include $(RARCH_OBJ:.o=.d)
@ -233,12 +257,13 @@ $(OBJDIR)/%.o: %.S $(HEADERS)
$(Q)$(CC) $(CFLAGS) $(ASFLAGS) $(DEFINES) -c -o $@ $<
clean:
rm -rf $(OBJDIR_BASE)
rm -f $(TARGET)
rm -f *.d
rm -rf SDL
rm -rf webos/*.ipk
rm -rf webos/dist
@$(if $(Q), echo $@,)
$(Q)rm -rf $(OBJDIR_BASE)
$(Q)rm -f $(TARGET)
$(Q)rm -f *.d
$(Q)rm -rf SDL
$(Q)rm -rf webos/*.ipk
$(Q)rm -rf webos/dist
sdl2: $(TARGET)
ifeq ($(ADD_SDL2_LIB), 1)

View File

@ -265,11 +265,27 @@ ifeq ($(LOAD_WITHOUT_CORE_INFO),1)
CFLAGS += -DLOAD_WITHOUT_CORE_INFO
endif
ifneq ($(V),1)
Q := @
endif
OBJOUT = -o
LINKOUT = -o
LINK = $(CXX)
all: $(EXT_TARGET)
all: info $(EXT_TARGET)
define INFO
CC: $(CC)
CFLAGS: $(CFLAGS)
OBJOUT: $(OBJOUT)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
%.dol: %.elf
$(ELF2DOL) $< $@
@ -278,13 +294,15 @@ $(EXT_INTER_TARGET): $(OBJ)
$(LINK) $(LINKOUT)$@ $(LDFLAGS) $(LIBDIRS) $(OBJ) $(PLATEXTRA) $(LIBS)
%.o: %.c
$(CC) $(CFLAGS) -c $(OBJOUT)$@ $<
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) $(CFLAGS) -c $(OBJOUT)$@ $<
%.o: %.cpp
$(CXX) $(CFLAGS) -c $(OBJOUT)$@ $<
%.o: %.S
$(CC) $(CFLAGS) -c $(OBJOUT)$@ $<
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) $(CFLAGS) -c $(OBJOUT)$@ $<
%.binobj: %.bin
$(LD) -r -b binary $(OBJOUT)$@ $<
@ -294,10 +312,11 @@ $(APP_BOOTER_DIR)/app_booter.bin:
$(MAKE) -C $(APP_BOOTER_DIR)
clean:
rm -f $(EXT_TARGET)
rm -f $(EXT_INTER_TARGET)
rm -f $(OBJ)
$(MAKE) -C $(APP_BOOTER_DIR) clean
@$(if $(Q), echo $@,)
$(Q)rm -f $(EXT_TARGET)
$(Q)rm -f $(EXT_INTER_TARGET)
$(Q)rm -f $(OBJ)
$(Q)$(MAKE) -C $(APP_BOOTER_DIR) clean
.PHONY: clean

View File

@ -302,7 +302,38 @@ endif
DEPFLAGS = -MT $@ -MMD -MP -MF $(BUILD_DIR)/$*.depend
all: $(TARGETS)
all: info $(TARGETS)
define INFO
AR: $(AR)
ASFLAGS: $(ASFLAGS)
BUILD_DIR: $(BUILD_DIR)
CC: $(CC)
CFLAGS: $(CFLAGS)
CXX: $(CXX)
CXXFLAGS: $(CXXFLAGS)
DEFINES: $(DEFINES)
DEPFLAGS: $(DEPFLAGS)
ELF2RPL: $(ELF2RPL)
HBL_ELF_LDFLAGS: $(HBL_ELF_LDFLAGS)
HBL_ELF_OBJ: $(HBL_ELF_OBJ)
INCDIRS: $(INCDIRS)
LD: $(LD)
LDFLAGS: $(LDFLAGS)
LIBDIRS: $(LIBDIRS)
LIBS: $(LIBS)
MAKE: $(MAKE)
OBJ: $(OBJ)
RPX_LDFLAGS: $(RPX_LDFLAGS)
RPX_OBJ: $(RPX_OBJ)
TARGET: $(TARGET)
endef
export INFO
info:
ifneq ($(V),1)
@echo "$$INFO"
endif
%: $(BUILD_DIR)/%
cp $< $@

View File

@ -89,6 +89,7 @@ AVUTIL_LIBS := -lavutil
SWSCALE_LIBS := -lswscale
AVFORMAT_LIBS := -lavformat
SWRESAMPLE_LIBS := -lswresample
AVDEVICE_LIBS := -lavdevice
FFMPEG_LIBS := -lws2_32 -lz
endif

26
SECURITY.md Normal file
View File

@ -0,0 +1,26 @@
# Security Policy
## Reasonable expectations
RetroArch is a frontend for the libretro API. The main functionality is fulfilled by invoking other binary libraries ("cores") which are not restricted by RetroArch in any way. Cores are able to read/write/delete files, spawn processes, communicate over the network. Also, source for cores is not necessarily in control by libretro team, and core binaries / RetroArch binaries are not signed. For this reason, it is a bad idea to use RetroArch or any other libretro frontend on security critical systems.
Also, RetroArch and cores have been packaged in several ways. Content on the [official download site](https://buildbot.libretro.com/) is built from a direct mirror of the original RetroArch and core repositories, no binaries are reused. Note that source for the core repositories may be outside libretro team control.
## Supported Versions
For most delivery channels, libretro team does not have control over the version. The exceptions are:
- [official download site](https://buildbot.libretro.com/)
- Steam release
- Apple App Store release
- various Android app store releases
- note that Google Play Store version is years behind and can not be updated
You may report vulnerability against any recent version, but be reasonable.
## Reporting a Vulnerability
Please report security vulnerabilities at libretro@gmail.com
## Possible remediation
Due to the variety of delivery channels, RetroArch team can not recall any given version universally. Security fixes are accepted for next release, and notice may be posted in the channels controlled by RetroArch team, depending on the severity assessed by RetroArch team.

216
ai/game_ai.c Normal file
View File

@ -0,0 +1,216 @@
#include "game_ai.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#include <retro_assert.h>
#include <compat/strl.h>
#include "../deps/game_ai_lib/GameAI.h"
#define GAME_AI_MAX_PLAYERS 2
void * ga = NULL;
volatile void * g_ram_ptr = NULL;
volatile int g_ram_size = 0;
volatile signed short int g_buttons_bits[GAME_AI_MAX_PLAYERS] = {0};
volatile int g_frameCount = 0;
volatile char game_ai_lib_path[1024] = {0};
volatile char g_game_name[1024] = {0};
retro_log_printf_t g_log = NULL;
#ifdef _WIN32
HINSTANCE g_lib_handle = NULL;
#else
void * g_lib_handle = NULL;
#endif
/* GameAI Lib API*/
create_game_ai_t create_game_ai = NULL;
destroy_game_ai_t destroy_game_ai = NULL;
game_ai_lib_init_t game_ai_lib_init = NULL;
game_ai_lib_think_t game_ai_lib_think = NULL;
game_ai_lib_set_show_debug_t game_ai_lib_set_show_debug = NULL;
game_ai_lib_set_debug_log_t game_ai_lib_set_debug_log = NULL;
/* Helper functions */
void game_ai_debug_log(int level, const char *fmt, ...)
{
va_list vp;
va_start(vp, fmt);
if (g_log)
g_log((enum retro_log_level)level, fmt, vp);
va_end(vp);
}
void array_to_bits_16(volatile signed short *result, const bool b[16])
{
for (int bit = 0; bit <= 15; bit++)
*result |= b[bit] ? (1 << bit) : 0;
}
/* Interface to RA */
signed short int game_ai_input(unsigned int port, unsigned int device,
unsigned int idx, unsigned int id, signed short int result)
{
if (ga && (port < GAME_AI_MAX_PLAYERS))
return g_buttons_bits[port];
return 0;
}
void game_ai_init(void)
{
if (!create_game_ai)
{
#ifdef _WIN32
BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;
g_lib_handle = LoadLibrary(TEXT("game_ai.dll"));
retro_assert(hinstLib);
char full_module_path[MAX_PATH];
DWORD dwLen = GetModuleFileNameA(g_lib_handle, static_cast<char*>(&full_module_path), MAX_PATH);
if (hinstLib)
{
create_game_ai = (create_game_ai_t) GetProcAddress(hinstLib, "create_game_ai");
retro_assert(create_game_ai);
destroy_game_ai = (destroy_game_ai_t) GetProcAddress(hinstLib, "destroy_game_ai");
retro_assert(destroy_game_ai);
game_ai_lib_init = (game_ai_lib_init_t) GetProcAddress(hinstLib, "game_ai_lib_init");
retro_assert(game_ai_lib_init);
game_ai_lib_think = (game_ai_lib_think_t) GetProcAddress(hinstLib, "game_ai_lib_think");
retro_assert(game_ai_lib_think);
game_ai_lib_set_show_debug = (game_ai_lib_set_show_debug_t) GetProcAddress(hinstLib, "game_ai_lib_set_show_debug");
retro_assert(game_ai_lib_set_show_debug);
game_ai_lib_set_debug_log = (game_ai_lib_set_debug_log_t) GetProcAddress(hinstLib, "game_ai_lib_set_debug_log");
retro_assert(game_ai_lib_set_debug_log);
}
#else
g_lib_handle = dlopen("./libgame_ai.so", RTLD_NOW);
retro_assert(g_lib_handle);
if (g_lib_handle)
{
dlinfo(g_lib_handle, RTLD_DI_ORIGIN, (void *) &game_ai_lib_path);
create_game_ai = (create_game_ai_t)(dlsym(g_lib_handle, "create_game_ai"));
retro_assert(create_game_ai);
destroy_game_ai = (destroy_game_ai_t)(dlsym(g_lib_handle, "destroy_game_ai"));
retro_assert(destroy_game_ai);
game_ai_lib_init = (game_ai_lib_init_t)(dlsym(g_lib_handle, "game_ai_lib_init"));
retro_assert(game_ai_lib_init);
game_ai_lib_think = (game_ai_lib_think_t)(dlsym(g_lib_handle, "game_ai_lib_think"));
retro_assert(game_ai_lib_think);
game_ai_lib_set_show_debug = (game_ai_lib_set_show_debug_t)(dlsym(g_lib_handle, "game_ai_lib_set_show_debug"));
retro_assert(game_ai_lib_set_show_debug);
game_ai_lib_set_debug_log = (game_ai_lib_set_debug_log_t)(dlsym(g_lib_handle, "game_ai_lib_set_debug_log"));
retro_assert(game_ai_lib_set_debug_log);
}
#endif
}
}
void game_ai_shutdown(void)
{
if (g_lib_handle)
{
if (ga)
{
destroy_game_ai(ga);
ga = NULL;
}
#ifdef _WIN32
FreeLibrary(g_lib_handle);
#else
dlclose(g_lib_handle);
#endif
}
}
void game_ai_load(const char * name, void * ram_ptr, int ram_size, retro_log_printf_t log)
{
strcpy((char *) &g_game_name[0], name);
g_ram_ptr = ram_ptr;
g_ram_size = ram_size;
g_log = log;
if (ga)
{
destroy_game_ai(ga);
ga = NULL;
}
}
void game_ai_think(bool override_p1, bool override_p2, bool show_debug,
const void *frame_data, unsigned int frame_width, unsigned int frame_height,
unsigned int frame_pitch, unsigned int pixel_format)
{
if (ga)
game_ai_lib_set_show_debug(ga, show_debug);
if (!ga && g_ram_ptr)
{
ga = create_game_ai((char *) &g_game_name[0]);
retro_assert(ga);
if (ga)
{
char data_path[1024] = {0};
strcpy(&data_path[0], (char *)game_ai_lib_path);
strcat(&data_path[0], "/data/");
strcat(&data_path[0], (char *)g_game_name);
game_ai_lib_init(ga, (void *) g_ram_ptr, g_ram_size);
game_ai_lib_set_debug_log(ga, game_ai_debug_log);
}
}
if (g_frameCount >= (GAMEAI_SKIPFRAMES - 1))
{
if (ga)
{
bool b[GAMEAI_MAX_BUTTONS] = {0};
g_buttons_bits[0]=0;
g_buttons_bits[1]=0;
if (override_p1)
{
game_ai_lib_think(ga, b, 0, frame_data, frame_width, frame_height, frame_pitch, pixel_format);
array_to_bits_16(&g_buttons_bits[0], b);
}
if (override_p2)
{
game_ai_lib_think(ga, b, 1, frame_data, frame_width, frame_height, frame_pitch, pixel_format);
array_to_bits_16(&g_buttons_bits[1], b);
}
}
g_frameCount=0;
}
else
g_frameCount++;
}

44
ai/game_ai.h Normal file
View File

@ -0,0 +1,44 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2016 - Daniel De Matteis
* Copyright (C) 2021 - David G.F.
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RARCH_GAME_AI_H__
#define RARCH_GAME_AI_H__
#include <boolean.h>
#include <retro_common_api.h>
#include <libretro.h>
RETRO_BEGIN_DECLS
signed short int game_ai_input(unsigned int port, unsigned int device,
unsigned int idx, unsigned int id, signed short int result);
void game_ai_init(void);
void game_ai_shutdown(void);
void game_ai_load(const char * name, void * ram_ptr,
int ram_size, retro_log_printf_t log);
void game_ai_think(bool override_p1, bool override_p2, bool show_debug,
const void *frame_data, unsigned int frame_w, unsigned int frame_h,
unsigned int frame_pitch, unsigned int pixel_format);
RETRO_END_DECLS
#endif

View File

@ -161,7 +161,13 @@ enum audio_driver_state_flags
* @see audio_driver_t::write_avail
* @see audio_driver_t::buffer_size
*/
AUDIO_FLAG_CONTROL = (1 << 5)
AUDIO_FLAG_CONTROL = (1 << 5),
/**
* Indicates that the audio driver is forcing gain to 0.
* Used for temporary rewind and fast-forward muting.
*/
AUDIO_FLAG_MUTED = (1 << 6)
};
typedef struct audio_statistics

View File

@ -147,9 +147,12 @@ audio_driver_t *audio_drivers[] = {
#ifdef WIIU
&audio_ax,
#endif
#if defined(EMSCRIPTEN) && defined(HAVE_RWEBAUDIO)
#if defined(HAVE_RWEBAUDIO)
&audio_rwebaudio,
#endif
#if defined(HAVE_AUDIOWORKLET)
&audio_audioworklet,
#endif
#if defined(PSP) || defined(VITA) || defined(ORBIS)
&audio_psp,
#endif
@ -216,14 +219,6 @@ bool audio_driver_is_ai_service_speech_running(void)
}
#endif
static enum resampler_quality audio_driver_get_resampler_quality(
settings_t *settings)
{
if (settings)
return (enum resampler_quality)settings->uints.audio_resampler_quality;
return RESAMPLER_QUALITY_DONTCARE;
}
static bool audio_driver_free_devices_list(void)
{
audio_driver_state_t *audio_st = &audio_driver_st;
@ -344,24 +339,17 @@ static void audio_driver_mixer_deinit(void)
bool audio_driver_deinit(void)
{
settings_t *settings = config_get_ptr();
#ifdef HAVE_AUDIOMIXER
audio_driver_mixer_deinit();
#endif
audio_driver_free_devices_list();
return audio_driver_deinit_internal(
settings->bools.audio_enable);
return audio_driver_deinit_internal(config_get_ptr()->bools.audio_enable);
}
bool audio_driver_find_driver(
void *settings_data,
const char *prefix,
bool verbosity_enabled)
bool audio_driver_find_driver(const char *audio_drv,
const char *prefix, bool verbosity_enabled)
{
settings_t *settings = (settings_t*)settings_data;
int i = (int)driver_find_index(
"audio_driver",
settings->arrays.audio_driver);
int i = (int)driver_find_index("audio_driver", audio_drv);
if (i >= 0)
audio_driver_st.current_audio = (const audio_driver_t*)
@ -372,8 +360,7 @@ bool audio_driver_find_driver(
if (verbosity_enabled)
{
unsigned d;
RARCH_ERR("Couldn't find any %s named \"%s\"\n", prefix,
settings->arrays.audio_driver);
RARCH_ERR("Couldn't find any %s named \"%s\"\n", prefix, audio_drv);
RARCH_LOG_OUTPUT("Available %ss are:\n", prefix);
for (d = 0; audio_drivers[d]; d++)
{
@ -400,22 +387,20 @@ bool audio_driver_find_driver(
* @param audio_st The overall state of the audio driver.
* @param slowmotion_ratio The factor by which slow motion extends the core's runtime
* (e.g. a value of 2 means the core is running at half speed).
* @param audio_fastforward_mute True if no audio should be output while the game is in fast-forward.
* @param data Audio output data that was most recently provided by the core.
* @param samples The size of \c data, in samples.
* @param is_slowmotion True if the player is currently running the game in slow motion.
* @param is_fastmotion True if the player is currently running the game in fast-forward.
* @param is_slowmotion True if the core is currently running in slow motion.
* @param is_fastmotion True if the core is currently running in fast-forward.
**/
static void audio_driver_flush(
audio_driver_state_t *audio_st,
float slowmotion_ratio,
bool audio_fastforward_mute,
const int16_t *data, size_t samples,
bool is_slowmotion, bool is_fastforward)
{
struct resampler_data src_data;
float audio_volume_gain = (audio_st->mute_enable ||
(audio_fastforward_mute && is_fastforward))
float audio_volume_gain =
(audio_st->mute_enable || audio_st->flags & AUDIO_FLAG_MUTED)
? 0.0f
: audio_st->volume_gain;
@ -481,19 +466,19 @@ static void audio_driver_flush(
= avail;
audio_st->source_ratio_current
= audio_st->source_ratio_original * adjust;
}
#if 0
if (verbosity_is_enabled())
{
RARCH_LOG_OUTPUT("[Audio]: Audio buffer is %u%% full\n",
(unsigned)(100 - (avail * 100) /
audio_st->buffer_size));
RARCH_LOG_OUTPUT("[Audio]: New rate: %lf, Orig rate: %lf\n",
audio_st->source_ratio_current,
audio_st->source_ratio_original);
}
if (verbosity_is_enabled())
{
RARCH_LOG_OUTPUT("[Audio]: Audio buffer is %u%% full\n",
(unsigned)(100 - (avail * 100) /
audio_st->buffer_size));
RARCH_LOG_OUTPUT("[Audio]: New rate: %lf, Orig rate: %lf\n",
audio_st->source_ratio_current,
audio_st->source_ratio_original);
}
#endif
}
}
src_data.ratio = audio_st->source_ratio_current;
@ -598,9 +583,7 @@ const char *audio_driver_mixer_get_stream_name(unsigned i)
#endif
bool audio_driver_init_internal(
void *settings_data,
bool audio_cb_inited)
bool audio_driver_init_internal(void *settings_data, bool audio_cb_inited)
{
unsigned new_rate = 0;
float *out_samples_buf = NULL;
@ -612,8 +595,8 @@ bool audio_driver_init_internal(
float slowmotion_ratio = settings->floats.slowmotion_ratio;
unsigned setting_audio_latency = settings->uints.audio_latency;
unsigned runloop_audio_latency = runloop_state_get_ptr()->audio_latency;
unsigned audio_latency = (runloop_audio_latency > setting_audio_latency) ?
runloop_audio_latency : setting_audio_latency;
unsigned audio_latency = (runloop_audio_latency > setting_audio_latency)
? runloop_audio_latency : setting_audio_latency;
#ifdef HAVE_REWIND
int16_t *rewind_buf = NULL;
#endif
@ -658,7 +641,7 @@ bool audio_driver_init_internal(
else
audio_driver_st.flags |= AUDIO_FLAG_ACTIVE;
if (!(audio_driver_find_driver(settings,
if (!(audio_driver_find_driver(settings->arrays.audio_driver,
"audio driver", verbosity_enabled)))
{
RARCH_ERR("Failed to initialize audio driver.\n");
@ -679,7 +662,7 @@ bool audio_driver_init_internal(
if (!audio_init_thread(
&audio_driver_st.current_audio,
&audio_driver_st.context_audio_data,
*settings->arrays.audio_device
*settings->arrays.audio_device
? settings->arrays.audio_device : NULL,
settings->uints.audio_output_sample_rate, &new_rate,
audio_latency,
@ -694,8 +677,8 @@ bool audio_driver_init_internal(
#endif
{
audio_driver_st.context_audio_data =
audio_driver_st.current_audio->init(*settings->arrays.audio_device ?
settings->arrays.audio_device : NULL,
audio_driver_st.current_audio->init(*settings->arrays.audio_device
? settings->arrays.audio_device : NULL,
settings->uints.audio_output_sample_rate,
audio_latency,
settings->uints.audio_block_frames,
@ -704,7 +687,8 @@ bool audio_driver_init_internal(
}
if (new_rate != 0)
configuration_set_int(settings, settings->uints.audio_output_sample_rate, new_rate);
configuration_set_int(settings,
settings->uints.audio_output_sample_rate, new_rate);
if (!audio_driver_st.context_audio_data)
{
@ -735,7 +719,8 @@ bool audio_driver_init_internal(
/* Should never happen. */
RARCH_WARN("[Audio]: Input rate is invalid (%.3f Hz)."
" Using output rate (%u Hz).\n",
audio_driver_st.input, settings->uints.audio_output_sample_rate);
audio_driver_st.input,
settings->uints.audio_output_sample_rate);
audio_driver_st.input = settings->uints.audio_output_sample_rate;
}
@ -751,8 +736,7 @@ bool audio_driver_init_internal(
else
audio_driver_st.resampler_ident[0] = '\0';
audio_driver_st.resampler_quality =
audio_driver_get_resampler_quality(settings);
audio_driver_st.resampler_quality = (enum resampler_quality)settings->uints.audio_resampler_quality;
if (!retro_resampler_realloc(
&audio_driver_st.resampler_data,
@ -850,7 +834,6 @@ void audio_driver_sample(int16_t left, int16_t right)
|| !(audio_st->output_samples_buf)))
audio_driver_flush(audio_st,
config_get_ptr()->floats.slowmotion_ratio,
config_get_ptr()->bools.audio_fastforward_mute,
audio_st->output_samples_conv_buf,
audio_st->data_ptr,
(runloop_flags & RUNLOOP_FLAG_SLOWMOTION) ? true : false,
@ -900,7 +883,6 @@ size_t audio_driver_sample_batch(const int16_t *data, size_t frames)
|| !(audio_st->output_samples_buf)))
audio_driver_flush(audio_st,
config_get_ptr()->floats.slowmotion_ratio,
config_get_ptr()->bools.audio_fastforward_mute,
data,
frames_to_write << 1,
(runloop_flags & RUNLOOP_FLAG_SLOWMOTION) ? true : false,
@ -1487,7 +1469,8 @@ void audio_driver_mixer_play_scroll_sound(bool direction_up)
bool audio_enable_menu = settings->bools.audio_enable_menu;
bool audio_enable_menu_scroll = settings->bools.audio_enable_menu_scroll;
if (audio_enable_menu && audio_enable_menu_scroll)
audio_driver_mixer_play_menu_sound(direction_up ? AUDIO_MIXER_SYSTEM_SLOT_UP : AUDIO_MIXER_SYSTEM_SLOT_DOWN);
audio_driver_mixer_play_menu_sound(direction_up
? AUDIO_MIXER_SYSTEM_SLOT_UP : AUDIO_MIXER_SYSTEM_SLOT_DOWN);
}
void audio_driver_mixer_play_stream_looped(unsigned i)
@ -1615,18 +1598,18 @@ bool audio_driver_disable_callback(void)
bool audio_driver_callback(void)
{
settings_t *settings = config_get_ptr();
bool menu_pause_libretro = config_get_ptr()->bools.menu_pause_libretro;
uint32_t runloop_flags = runloop_get_flags();
bool runloop_paused = (runloop_flags & RUNLOOP_FLAG_PAUSED) ? true : false;
#ifdef HAVE_MENU
#ifdef HAVE_NETWORKING
bool core_paused = runloop_paused
|| (settings->bools.menu_pause_libretro
|| (menu_pause_libretro
&& (menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE)
&& netplay_driver_ctl(RARCH_NETPLAY_CTL_ALLOW_PAUSE, NULL));
#else
bool core_paused = runloop_paused
|| (settings->bools.menu_pause_libretro
|| (menu_pause_libretro
&& (menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE));
#endif
#else
@ -1737,18 +1720,14 @@ void audio_driver_frame_is_reverse(void)
|| !(audio_st->flags & AUDIO_FLAG_ACTIVE)
|| !(audio_st->output_samples_buf)))
if (!(audio_st->flags & AUDIO_FLAG_SUSPENDED))
{
settings_t *settings = config_get_ptr();
audio_driver_flush(audio_st,
settings->floats.slowmotion_ratio,
settings->bools.audio_fastforward_mute,
config_get_ptr()->floats.slowmotion_ratio,
audio_st->rewind_buf +
audio_st->rewind_ptr,
audio_st->rewind_size -
audio_st->rewind_ptr,
(runloop_flags & RUNLOOP_FLAG_SLOWMOTION) ? true : false,
(runloop_flags & RUNLOOP_FLAG_FASTMOTION) ? true : false);
}
}
#endif
@ -1880,6 +1859,7 @@ void audio_driver_menu_sample(void)
{
static int16_t samples_buf[1024] = {0};
settings_t *settings = config_get_ptr();
float slowmotion_ratio = settings->floats.slowmotion_ratio;
video_driver_state_t *video_st = video_state_get_ptr();
uint32_t runloop_flags = runloop_get_flags();
recording_state_t *recording_st = recording_state_get_ptr();
@ -1910,17 +1890,16 @@ void audio_driver_menu_sample(void)
}
if (check_flush)
audio_driver_flush(audio_st,
settings->floats.slowmotion_ratio,
settings->bools.audio_fastforward_mute,
slowmotion_ratio,
samples_buf,
1024,
(runloop_flags & RUNLOOP_FLAG_SLOWMOTION) ? true : false,
(runloop_flags & RUNLOOP_FLAG_FASTMOTION) ? true : false);
sample_count -= 1024;
}
if ( recording_st->data &&
recording_st->driver &&
recording_st->driver->push_audio)
if ( recording_st->data
&& recording_st->driver
&& recording_st->driver->push_audio)
{
struct record_audio_data ffemu_data;
@ -1932,10 +1911,7 @@ void audio_driver_menu_sample(void)
}
if (check_flush)
audio_driver_flush(audio_st,
settings->floats.slowmotion_ratio,
settings->bools.audio_fastforward_mute,
samples_buf,
sample_count,
slowmotion_ratio, samples_buf, sample_count,
(runloop_flags & RUNLOOP_FLAG_SLOWMOTION) ? true : false,
(runloop_flags & RUNLOOP_FLAG_FASTMOTION) ? true : false);
}

View File

@ -110,7 +110,7 @@ typedef struct audio_driver
* Unless said otherwise with set_nonblock_state(), all writes
* are blocking, and it should block till it has written all frames.
*/
ssize_t (*write)(void *data, const void *buf, size_t size);
ssize_t (*write)(void *data, const void *s, size_t len);
/**
* Temporarily pauses the audio driver.
@ -350,10 +350,8 @@ bool audio_driver_init_internal(
bool audio_driver_deinit(void);
bool audio_driver_find_driver(
void *settings_data,
const char *prefix,
bool verbosity_enabled);
bool audio_driver_find_driver(const char *audio_drv,
const char *prefix, bool verbosity_enabled);
/**
* audio_driver_sample:
@ -441,6 +439,7 @@ extern audio_driver_t audio_switch_thread;
extern audio_driver_t audio_switch_libnx_audren;
extern audio_driver_t audio_switch_libnx_audren_thread;
extern audio_driver_t audio_rwebaudio;
extern audio_driver_t audio_audioworklet;
audio_driver_state_t *audio_state_get_ptr(void);

View File

@ -99,7 +99,7 @@ static void audio_thread_loop(void *data)
thr->driver->stop(thr->driver_data);
while (thr->stopped)
{
/* If we stop right after start,
/* If we stop right after start,
* we might not be able to properly ack.
* Signal in the loop instead. */
thr->stopped_ack = true;
@ -249,7 +249,7 @@ static bool audio_thread_use_float(void *data)
return thr->use_float;
}
static ssize_t audio_thread_write(void *data, const void *buf, size_t size)
static ssize_t audio_thread_write(void *data, const void *s, size_t len)
{
ssize_t ret;
audio_thread_t *thr = (audio_thread_t*)data;
@ -257,7 +257,7 @@ static ssize_t audio_thread_write(void *data, const void *buf, size_t size)
if (!thr)
return 0;
ret = thr->driver->write(thr->driver_data, buf, size);
ret = thr->driver->write(thr->driver_data, s, len);
if (ret < 0)
{

View File

@ -20,10 +20,6 @@
/* Fix for MSYS2 increasing _WIN32_WINNT to 0x0603*/
#if defined(__MINGW32__) || defined(__MINGW64__)
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#define _WIN32_WINNT 0x0600
#define WIN32_LEAN_AND_MEAN
#else
typedef enum EDataFlow EDataFlow;

View File

@ -16,12 +16,11 @@
#include "pipewire.h"
#include <spa/utils/result.h>
#include <pipewire/pipewire.h>
#include <retro_assert.h>
#include "verbosity.h"
#include "../../verbosity.h"
static void core_error_cb(void *data, uint32_t id, int seq, int res, const char *message)
@ -31,7 +30,6 @@ static void core_error_cb(void *data, uint32_t id, int seq, int res, const char
RARCH_ERR("[PipeWire]: error id:%u seq:%d res:%d (%s): %s\n",
id, seq, res, spa_strerror(res), message);
/* stop and exit the thread loop */
pw_thread_loop_stop(pw->thread_loop);
}
@ -42,11 +40,9 @@ static void core_done_cb(void *data, uint32_t id, int seq)
retro_assert(id == PW_ID_CORE);
pw->last_seq = seq;
if (pw->pending_seq == seq)
{
/* stop and exit the thread loop */
pw_thread_loop_signal(pw->thread_loop, false);
}
}
static const struct pw_core_events core_events = {
@ -55,7 +51,7 @@ static const struct pw_core_events core_events = {
.error = core_error_cb,
};
size_t calc_frame_size(enum spa_audio_format fmt, uint32_t nchannels)
size_t pipewire_calc_frame_size(enum spa_audio_format fmt, uint32_t nchannels)
{
uint32_t sample_size = 1;
switch (fmt)
@ -85,7 +81,7 @@ size_t calc_frame_size(enum spa_audio_format fmt, uint32_t nchannels)
return sample_size * nchannels;
}
void set_position(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS])
void pipewire_set_position(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS])
{
memcpy(position, (uint32_t[SPA_AUDIO_MAX_CHANNELS]) { SPA_AUDIO_CHANNEL_UNKNOWN, },
sizeof(uint32_t) * SPA_AUDIO_MAX_CHANNELS);
@ -114,7 +110,7 @@ void set_position(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS])
}
}
void pipewire_wait_resync(pipewire_core_t *pw)
void pipewire_core_wait_resync(pipewire_core_t *pw)
{
retro_assert(pw);
pw->pending_seq = pw_core_sync(pw->core, PW_ID_CORE, pw->pending_seq);
@ -127,7 +123,7 @@ void pipewire_wait_resync(pipewire_core_t *pw)
}
}
bool pipewire_set_active(struct pw_thread_loop *loop, struct pw_stream *stream, bool active)
bool pipewire_stream_set_active(struct pw_thread_loop *loop, struct pw_stream *stream, bool active)
{
enum pw_stream_state st;
const char *error;
@ -144,31 +140,89 @@ bool pipewire_set_active(struct pw_thread_loop *loop, struct pw_stream *stream,
return active ? st == PW_STREAM_STATE_STREAMING : st == PW_STREAM_STATE_PAUSED;
}
bool pipewire_core_init(pipewire_core_t *pw, const char *loop_name)
bool pipewire_core_init(pipewire_core_t **pw, const char *loop_name, const struct pw_registry_events *events)
{
retro_assert(pw);
retro_assert(!*pw);
pw->thread_loop = pw_thread_loop_new(loop_name, NULL);
if (!pw->thread_loop)
*pw = (pipewire_core_t*)calloc(1, sizeof(pipewire_core_t));
if (!*pw)
return false;
pw->ctx = pw_context_new(pw_thread_loop_get_loop(pw->thread_loop), NULL, 0);
if (!pw->ctx)
(*pw)->devicelist = string_list_new();
if (!(*pw)->devicelist)
{
free(*pw);
*pw = NULL;
return false;
}
pw_init(NULL, NULL);
(*pw)->thread_loop = pw_thread_loop_new(loop_name, NULL);
if (!(*pw)->thread_loop)
return false;
if (pw_thread_loop_start(pw->thread_loop) < 0)
(*pw)->ctx = pw_context_new(pw_thread_loop_get_loop((*pw)->thread_loop), NULL, 0);
if (!(*pw)->ctx)
return false;
pw_thread_loop_lock(pw->thread_loop);
pw->core = pw_context_connect(pw->ctx, NULL, 0);
if(!pw->core)
if (pw_thread_loop_start((*pw)->thread_loop) < 0)
return false;
if (pw_core_add_listener(pw->core,
&pw->core_listener,
&core_events, pw) < 0)
return false;
pw_thread_loop_lock((*pw)->thread_loop);
(*pw)->core = pw_context_connect((*pw)->ctx, NULL, 0);
if (!(*pw)->core)
goto unlock;
if (pw_core_add_listener((*pw)->core,
&(*pw)->core_listener,
&core_events, *pw) < 0)
goto unlock;
if (events)
{
(*pw)->registry = pw_core_get_registry((*pw)->core, PW_VERSION_REGISTRY, 0);
spa_zero((*pw)->registry_listener);
pw_registry_add_listener((*pw)->registry, &(*pw)->registry_listener, events, *pw);
}
return true;
unlock:
pw_thread_loop_unlock((*pw)->thread_loop);
return false;
}
void pipewire_core_deinit(pipewire_core_t *pw)
{
if (!pw)
return pw_deinit();
if (pw->thread_loop)
pw_thread_loop_stop(pw->thread_loop);
if (pw->registry)
{
spa_hook_remove(&pw->registry_listener);
pw_proxy_destroy((struct pw_proxy*)pw->registry);
}
if (pw->core)
{
spa_hook_remove(&pw->core_listener);
pw_core_disconnect(pw->core);
}
if (pw->ctx)
pw_context_destroy(pw->ctx);
if (pw->thread_loop)
pw_thread_loop_destroy(pw->thread_loop);
if (pw->devicelist)
string_list_free(pw->devicelist);
free(pw);
pw_deinit();
}

View File

@ -21,9 +21,11 @@
#include <spa/param/audio/format-utils.h>
#include <spa/utils/ringbuffer.h>
#include <pipewire/pipewire.h>
#include <lists/string_list.h>
#define PW_RARCH_APPNAME "RetroArch"
/* String literals are part of the PipeWire specification */
@ -31,34 +33,35 @@
#define PW_RARCH_MEDIA_TYPE_VIDEO "Video"
#define PW_RARCH_MEDIA_TYPE_MIDI "Midi"
#define PW_RARCH_MEDIA_CATEGORY_PLAYBACK "Playback"
#define PW_RARCH_MEDIA_CATEGORY_RECORD "Capture"
#define PW_RARCH_MEDIA_CATEGORY_RECORD "Capture"
#define PW_RARCH_MEDIA_ROLE "Game"
typedef struct pipewire_core
{
struct pw_thread_loop *thread_loop;
struct pw_context *ctx;
struct pw_core *core;
struct spa_hook core_listener;
int last_seq, pending_seq;
struct pw_registry *registry;
struct spa_hook registry_listener;
struct pw_client *client;
struct spa_hook client_listener;
bool nonblock;
struct string_list *devicelist;
bool nonblock;
} pipewire_core_t;
size_t calc_frame_size(enum spa_audio_format fmt, uint32_t nchannels);
size_t pipewire_calc_frame_size(enum spa_audio_format fmt, uint32_t nchannels);
void set_position(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS]);
void pipewire_set_position(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS]);
void pipewire_wait_resync(pipewire_core_t *pipewire);
bool pipewire_core_init(pipewire_core_t **pw, const char *loop_name, const struct pw_registry_events *events);
bool pipewire_set_active(struct pw_thread_loop *loop, struct pw_stream *stream, bool active);
void pipewire_core_deinit(pipewire_core_t *pw);
bool pipewire_core_init(pipewire_core_t *pipewire, const char *loop_name);
void pipewire_core_wait_resync(pipewire_core_t *pw);
bool pipewire_stream_set_active(struct pw_thread_loop *loop, struct pw_stream *stream, bool active);
#endif /* _RETROARCH_PIPEWIRE */

View File

@ -73,14 +73,36 @@ error:
#define BYTES_TO_FRAMES(bytes, frame_bits) ((bytes) * 8 / frame_bits)
#define FRAMES_TO_BYTES(frames, frame_bits) ((frames) * frame_bits / 8)
static bool alsa_start(void *data, bool is_shutdown);
static ssize_t alsa_write(void *data, const void *buf_, size_t size_)
static bool alsa_start(void *data, bool is_shutdown)
{
alsa_t *alsa = (alsa_t*)data;
const uint8_t *buf = (const uint8_t*)buf_;
snd_pcm_sframes_t written = 0;
snd_pcm_sframes_t size = BYTES_TO_FRAMES(size_, alsa->stream_info.frame_bits);
size_t frames_size = alsa->stream_info.has_float ? sizeof(float) : sizeof(int16_t);
alsa_t *alsa = (alsa_t*)data;
if (!alsa->is_paused)
return true;
if ( alsa->stream_info.can_pause
&& alsa->is_paused)
{
int ret = snd_pcm_pause(alsa->pcm, 0);
if (ret < 0)
{
RARCH_ERR("[ALSA]: Failed to unpause: %s.\n",
snd_strerror(ret));
return false;
}
alsa->is_paused = false;
}
return true;
}
static ssize_t alsa_write(void *data, const void *buf_, size_t len)
{
ssize_t written = 0;
alsa_t *alsa = (alsa_t*)data;
const uint8_t *buf = (const uint8_t*)buf_;
snd_pcm_sframes_t size = BYTES_TO_FRAMES(len, alsa->stream_info.frame_bits);
size_t frames_size = alsa->stream_info.has_float ? sizeof(float) : sizeof(int16_t);
/* Workaround buggy menu code.
* If a write happens while we're paused, we might never progress. */
@ -192,29 +214,6 @@ static void alsa_set_nonblock_state(void *data, bool state)
alsa->nonblock = state;
}
static bool alsa_start(void *data, bool is_shutdown)
{
alsa_t *alsa = (alsa_t*)data;
if (!alsa->is_paused)
return true;
if (alsa->stream_info.can_pause
&& alsa->is_paused)
{
int ret = snd_pcm_pause(alsa->pcm, 0);
if (ret < 0)
{
RARCH_ERR("[ALSA]: Failed to unpause: %s.\n",
snd_strerror(ret));
return false;
}
alsa->is_paused = false;
}
return true;
}
static void alsa_free(void *data)
{
alsa_t *alsa = (alsa_t*)data;

View File

@ -219,14 +219,14 @@ static int check_pcm_status(void *data, int channel_type)
return ret;
}
static ssize_t alsa_qsa_write(void *data, const void *buf, size_t size)
static ssize_t alsa_qsa_write(void *data, const void *buf, size_t len)
{
alsa_qsa_t *alsa = (alsa_qsa_t*)data;
snd_pcm_sframes_t written = 0;
alsa_qsa_t *alsa = (alsa_qsa_t*)data;
ssize_t written = 0;
while (size)
{
size_t avail_write = MIN(alsa->buf_size - alsa->buffer_ptr, size);
size_t avail_write = MIN(alsa->buf_size - alsa->buffer_ptr, len);
if (avail_write)
{
@ -235,7 +235,7 @@ static ssize_t alsa_qsa_write(void *data, const void *buf, size_t size)
alsa->buffer_ptr += avail_write;
buf = (void*)((uint8_t*)buf + avail_write);
size -= avail_write;
len -= avail_write;
written += avail_write;
}
@ -260,7 +260,6 @@ static ssize_t alsa_qsa_write(void *data, const void *buf, size_t size)
return -1;
}
}
}
return written;

View File

@ -157,8 +157,9 @@ error:
return NULL;
}
static ssize_t alsa_thread_write(void *data, const void *buf, size_t size)
static ssize_t alsa_thread_write(void *data, const void *s, size_t len)
{
ssize_t written = 0;
alsa_thread_t *alsa = (alsa_thread_t*)data;
if (alsa->info.thread_dead)
@ -167,21 +168,17 @@ static ssize_t alsa_thread_write(void *data, const void *buf, size_t size)
if (alsa->nonblock)
{
size_t avail;
size_t write_amt;
slock_lock(alsa->info.fifo_lock);
avail = FIFO_WRITE_AVAIL(alsa->info.buffer);
write_amt = MIN(avail, size);
written = MIN(avail, len);
fifo_write(alsa->info.buffer, buf, write_amt);
fifo_write(alsa->info.buffer, s, written);
slock_unlock(alsa->info.fifo_lock);
return write_amt;
}
else
{
size_t written = 0;
while (written < size && !alsa->info.thread_dead)
while (written < (ssize_t)len && !alsa->info.thread_dead)
{
size_t avail;
slock_lock(alsa->info.fifo_lock);
@ -197,15 +194,15 @@ static ssize_t alsa_thread_write(void *data, const void *buf, size_t size)
}
else
{
size_t write_amt = MIN(size - written, avail);
size_t write_amt = MIN(len - written, avail);
fifo_write(alsa->info.buffer,
(const char*)buf + written, write_amt);
(const char*)s + written, write_amt);
slock_unlock(alsa->info.fifo_lock);
written += write_amt;
}
}
return written;
}
return written;
}
static bool alsa_thread_alive(void *data)

View File

@ -82,15 +82,15 @@ error:
return NULL;
}
static ssize_t audioio_write(void *data, const void *buf, size_t size)
static ssize_t audioio_write(void *data, const void *s, size_t len)
{
ssize_t ret;
ssize_t written;
int *fd = (int*)data;
if (size == 0)
if (len == 0)
return 0;
if ((ret = write(*fd, buf, size)) < 0)
if ((written = write(*fd, s, len)) < 0)
{
if (errno == EAGAIN && (fcntl(*fd, F_GETFL) & O_NONBLOCK))
return 0;
@ -98,7 +98,7 @@ static ssize_t audioio_write(void *data, const void *buf, size_t size)
return -1;
}
return ret;
return written;
}
static bool audioio_stop(void *data)

View File

@ -0,0 +1,521 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2025 - OlyB
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <boolean.h>
#include <emscripten/wasm_worker.h>
#include <emscripten/webaudio.h>
#include <emscripten/atomic.h>
#include "../../frontend/drivers/platform_emscripten.h"
#include <queues/fifo_queue.h>
#include <retro_timers.h>
#include "../audio_driver.h"
#include "../../verbosity.h"
#define WORKLET_STACK_SIZE 4096
/* additional buffer size (for EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK only) */
/* if this is too small, frames may be dropped and content could run too fast. */
/* very large slow-motion rate values may be too large for this; avoid anything higher than 6 or 7. */
#define EXTERNAL_BLOCK_BUFFER_MS 128
typedef struct audioworklet_data
{
uint8_t *worklet_stack;
uint32_t write_avail_bytes; /* atomic */
size_t visible_buffer_size;
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
size_t write_avail_diff;
#endif
#ifdef PROXY_TO_PTHREAD
emscripten_lock_t trywrite_lock;
emscripten_condvar_t trywrite_cond;
#endif
emscripten_lock_t buffer_lock;
EMSCRIPTEN_WEBAUDIO_T context;
float *tmpbuf;
fifo_buffer_t *buffer;
unsigned rate;
unsigned latency;
bool nonblock;
bool initing;
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
bool block_requested;
#endif
volatile bool running; /* currently only used by RetroArch */
volatile bool driver_running; /* whether the driver is running (buffer allocated) */
volatile bool context_running; /* whether the AudioContext is running */
volatile bool init_done;
volatile bool init_error;
} audioworklet_data_t;
/* We only ever want to create 1 worklet, so we need to keep its data even if the driver is inactive. */
static audioworklet_data_t *audioworklet_static_data = NULL;
/* Note that we cannot allocate any heap in here. */
static bool audioworklet_process_cb(int numInputs, const AudioSampleFrame *inputs,
int numOutputs, AudioSampleFrame *outputs,
int numParams, const AudioParamFrame *params,
void *data)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
size_t avail;
size_t max_read;
unsigned writing_frames = 0;
int i;
/* TODO: do we need to pay attention to audioworklet->running here too? */
if (audioworklet->driver_running)
{
/* can't use Atomics.wait in AudioWorklet */
/* busyspin is safe as of emscripten 4.0.4 */
if (!emscripten_lock_busyspin_wait_acquire(&audioworklet->buffer_lock, 2.5))
{
printf("[WARN] [AudioWorklet] Worklet: could not acquire lock\n");
return true;
}
avail = FIFO_READ_AVAIL(audioworklet->buffer);
max_read = MIN(avail, outputs[0].samplesPerChannel * 2 * sizeof(float));
if (max_read)
{
fifo_read(audioworklet->buffer, audioworklet->tmpbuf, max_read);
emscripten_atomic_add_u32(&audioworklet->write_avail_bytes, max_read);
}
emscripten_lock_release(&audioworklet->buffer_lock);
#ifdef PROXY_TO_PTHREAD
emscripten_condvar_signal(&audioworklet->trywrite_cond, 1);
#endif
writing_frames = max_read / 2 / sizeof(float);
for (i = 0; i < writing_frames; i++)
{
outputs[0].data[i] = audioworklet->tmpbuf[i * 2];
outputs[0].data[outputs[0].samplesPerChannel + i] = audioworklet->tmpbuf[i * 2 + 1];
}
}
if (writing_frames < outputs[0].samplesPerChannel)
{
int zero_frames = outputs[0].samplesPerChannel - writing_frames;
memset(outputs[0].data + writing_frames, 0, zero_frames * sizeof(float));
memset(outputs[0].data + writing_frames + outputs[0].samplesPerChannel, 0, zero_frames * sizeof(float));
}
return true;
}
static void audioworklet_processor_inited_cb(EMSCRIPTEN_WEBAUDIO_T context, bool success, void *data)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
int outputChannelCounts[1] = { 2 };
EmscriptenAudioWorkletNodeCreateOptions opts = { 0, 1, outputChannelCounts };
EMSCRIPTEN_AUDIO_WORKLET_NODE_T worklet_node;
if (!success)
{
RARCH_ERR("[AudioWorklet] Failed to init AudioWorkletProcessor!\n");
audioworklet->init_error = true;
audioworklet->init_done = true;
return;
}
worklet_node = emscripten_create_wasm_audio_worklet_node(context, "retroarch", &opts, audioworklet_process_cb, audioworklet);
emscripten_audio_node_connect(worklet_node, context, 0, 0);
audioworklet->init_done = true;
}
static void audioworklet_thread_inited_cb(EMSCRIPTEN_WEBAUDIO_T context, bool success, void *data)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
WebAudioWorkletProcessorCreateOptions opts = { "retroarch", 0 };
if (!success)
{
RARCH_ERR("[AudioWorklet] Failed to init worklet thread! Is the worklet file in the right place?\n");
audioworklet->init_error = true;
audioworklet->init_done = true;
return;
}
emscripten_create_wasm_audio_worklet_processor_async(context, &opts, audioworklet_processor_inited_cb, audioworklet);
}
static void audioworklet_ctx_statechange_cb(void *data, bool state)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
audioworklet->context_running = state;
}
static void audioworklet_ctx_create(void *data)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
audioworklet->context = emscripten_create_audio_context(0);
audioworklet->tmpbuf = memalign(16, emscripten_audio_context_quantum_size(audioworklet->context) * 2 * sizeof(float));
audioworklet->rate = EM_ASM_INT({
return emscriptenGetAudioObject($0).sampleRate;
}, audioworklet->context);
audioworklet->context_running = EM_ASM_INT({
let ac = emscriptenGetAudioObject($0);
ac.addEventListener("statechange", function() {
getWasmTableEntry($2)($1, ac.state == "running");
});
return ac.state == "running";
}, audioworklet->context, audioworklet, audioworklet_ctx_statechange_cb);
emscripten_start_wasm_audio_worklet_thread_async(audioworklet->context,
audioworklet->worklet_stack, WORKLET_STACK_SIZE, audioworklet_thread_inited_cb, audioworklet);
}
static void audioworklet_alloc_buffer(void *data)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
size_t buffer_size;
audioworklet->visible_buffer_size = (audioworklet->latency * audioworklet->rate * 2 * sizeof(float)) / 1000;
buffer_size = audioworklet->visible_buffer_size;
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
audioworklet->write_avail_diff = (EXTERNAL_BLOCK_BUFFER_MS * audioworklet->rate * 2 * sizeof(float)) / 1000;
buffer_size += audioworklet->write_avail_diff;
#endif
audioworklet->buffer = fifo_new(buffer_size);
emscripten_atomic_store_u32(&audioworklet->write_avail_bytes, buffer_size);
RARCH_LOG("[AudioWorklet] Buffer size: %lu bytes.\n", audioworklet->visible_buffer_size);
}
static void audioworklet_init_error(void *data)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
RARCH_ERR("[AudioWorklet] Failed to initialize driver!\n");
free(audioworklet->worklet_stack);
free(audioworklet->tmpbuf);
free(audioworklet);
}
static bool audioworklet_resume_ctx(void *data)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
if (!audioworklet->context_running)
{
MAIN_THREAD_ASYNC_EM_ASM({
emscriptenGetAudioObject($0).resume();
}, audioworklet->context);
}
return audioworklet->context_running;
}
static void *audioworklet_init(const char *device, unsigned rate,
unsigned latency, unsigned block_frames, unsigned *new_rate)
{
audioworklet_data_t *audioworklet;
if (audioworklet_static_data)
{
if (audioworklet_static_data->driver_running || audioworklet_static_data->initing)
{
RARCH_ERR("[AudioWorklet] Tried to start already running driver!\n");
return NULL;
}
RARCH_LOG("[AudioWorklet] Reusing old context.\n");
audioworklet = audioworklet_static_data;
audioworklet->latency = latency;
*new_rate = audioworklet->rate;
RARCH_LOG("[AudioWorklet] Device rate: %d Hz.\n", *new_rate);
audioworklet_alloc_buffer(audioworklet);
audioworklet_resume_ctx(audioworklet);
audioworklet->driver_running = true;
return audioworklet;
}
audioworklet = (audioworklet_data_t*)calloc(1, sizeof(audioworklet_data_t));
if (!audioworklet)
return NULL;
audioworklet->worklet_stack = memalign(16, WORKLET_STACK_SIZE);
if (!audioworklet->worklet_stack)
return NULL;
audioworklet_static_data = audioworklet;
audioworklet->latency = latency;
platform_emscripten_run_on_browser_thread_sync(audioworklet_ctx_create, audioworklet);
*new_rate = audioworklet->rate;
RARCH_LOG("[AudioWorklet] Device rate: %d Hz.\n", *new_rate);
audioworklet->initing = true;
audioworklet_alloc_buffer(audioworklet);
emscripten_lock_init(&audioworklet->buffer_lock);
#ifdef PROXY_TO_PTHREAD
emscripten_lock_init(&audioworklet->trywrite_lock);
emscripten_condvar_init(&audioworklet->trywrite_cond);
#endif
#ifndef EMSCRIPTEN_AUDIO_EXTERNAL_BLOCK
/* TODO: can MIN_ASYNCIFY block here too? */
while (!audioworklet->init_done)
retro_sleep(1);
audioworklet->initing = false;
if (audioworklet->init_error)
{
audioworklet_init_error(audioworklet);
return NULL;
}
audioworklet->driver_running = true;
#elif defined(EMSCRIPTEN_AUDIO_FAKE_BLOCK)
audioworklet->block_requested = true;
platform_emscripten_enter_fake_block(1);
#endif
/* external block: will be handled later */
return audioworklet;
}
static ssize_t audioworklet_write(void *data, const void *s, size_t ss)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
const float *samples = (const float*)s;
size_t num_frames = ss / 2 / sizeof(float);
size_t written = 0;
size_t to_write_frames;
size_t to_write_bytes;
size_t avail;
size_t max_write;
/* too early! might happen with external blocking */
if (!audioworklet->driver_running)
return 0;
/* don't write audio if the context isn't running, just try to start it */
if (!audioworklet_resume_ctx(audioworklet))
return 0;
while (num_frames)
{
#ifdef PROXY_TO_PTHREAD
if (!emscripten_lock_wait_acquire(&audioworklet->buffer_lock, 2500000))
#else
if (!emscripten_lock_busyspin_wait_acquire(&audioworklet->buffer_lock, 2.5))
#endif
{
RARCH_WARN("[AudioWorklet] Main thread: could not acquire lock\n");
break;
}
avail = FIFO_WRITE_AVAIL(audioworklet->buffer);
max_write = avail;
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
/* make sure we don't write into the blocking buffer for nonblock */
if (audioworklet->nonblock)
{
if (max_write > audioworklet->write_avail_diff)
max_write -= audioworklet->write_avail_diff;
else
max_write = 0;
}
#endif
to_write_frames = MIN(num_frames, max_write / 2 / sizeof(float));
if (to_write_frames)
{
to_write_bytes = to_write_frames * 2 * sizeof(float);
avail -= to_write_bytes;
fifo_write(audioworklet->buffer, samples, to_write_bytes);
emscripten_atomic_store_u32(&audioworklet->write_avail_bytes, (uint32_t)avail);
num_frames -= to_write_frames;
samples += (to_write_frames * 2);
written += to_write_frames;
}
emscripten_lock_release(&audioworklet->buffer_lock);
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
/* see if we're over the threshold to go to fake block */
if (avail < audioworklet->write_avail_diff)
{
audioworklet->block_requested = true;
platform_emscripten_enter_fake_block(1);
}
#endif
if (num_frames && !audioworklet->nonblock)
RARCH_WARN("[AudioWorklet] Dropping %lu frames.\n", num_frames);
break;
#endif
if (audioworklet->nonblock || !num_frames)
break;
#if defined(PROXY_TO_PTHREAD)
emscripten_condvar_wait(&audioworklet->trywrite_cond, &audioworklet->trywrite_lock, 3000000);
#elif defined(EMSCRIPTEN_FULL_ASYNCIFY)
retro_sleep(1);
#else /* equivalent to defined(EMSCRIPTEN_AUDIO_BUSYWAIT) */
while (emscripten_atomic_load_u32(&audioworklet->write_avail_bytes) < 2 * sizeof(float))
audioworklet_resume_ctx(audioworklet);
#endif
/* try resuming, on the off chance that the context was interrupted while blocking */
audioworklet_resume_ctx(audioworklet);
}
return written;
}
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_BLOCK
/* returns true if fake block should continue */
bool audioworklet_external_block(void)
{
audioworklet_data_t *audioworklet = audioworklet_static_data;
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
if (!audioworklet->block_requested)
return false;
#endif
while (audioworklet->initing && !audioworklet->init_done)
#ifdef EMSCRIPTEN_AUDIO_ASYNC_BLOCK
retro_sleep(1);
#else
return true;
#endif
if (audioworklet->init_done && !audioworklet->driver_running)
{
audioworklet->initing = false;
if (audioworklet->init_error)
{
audioworklet_init_error(audioworklet);
abort();
return false;
}
audioworklet->driver_running = true;
}
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
if (!audioworklet->driver_running)
return false;
while (emscripten_atomic_load_u32(&audioworklet->write_avail_bytes) < audioworklet->write_avail_diff)
{
audioworklet_resume_ctx(audioworklet);
#ifdef EMSCRIPTEN_AUDIO_ASYNC_BLOCK
retro_sleep(1);
#else
return true;
#endif
}
#endif
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
audioworklet->block_requested = false;
platform_emscripten_exit_fake_block();
return true; /* return to RAF if needed */
#endif
return false;
}
#endif
static bool audioworklet_stop(void *data)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
audioworklet->running = false;
return true;
}
static bool audioworklet_start(void *data, bool is_shutdown)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
audioworklet->running = true;
return true;
}
static bool audioworklet_alive(void *data)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
return audioworklet->running;
}
static void audioworklet_set_nonblock_state(void *data, bool state)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
audioworklet->nonblock = state;
}
static void audioworklet_free(void *data)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
/* that's not good... this shouldn't happen? */
if (!audioworklet->driver_running)
{
RARCH_ERR("[AudioWorklet] Tried to free before done initing!\n");
return;
}
#ifdef PROXY_TO_PTHREAD
if (!emscripten_lock_wait_acquire(&audioworklet->buffer_lock, 10000000))
#else
if (!emscripten_lock_busyspin_wait_acquire(&audioworklet->buffer_lock, 10))
#endif
{
RARCH_ERR("[AudioWorklet] Main thread: could not acquire lock to free buffer!\n");
return;
}
audioworklet->driver_running = false;
fifo_free(audioworklet->buffer);
emscripten_lock_release(&audioworklet->buffer_lock);
MAIN_THREAD_ASYNC_EM_ASM({
emscriptenGetAudioObject($0).suspend();
}, audioworklet->context);
}
static size_t audioworklet_write_avail(void *data)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
size_t avail = emscripten_atomic_load_u32(&audioworklet->write_avail_bytes);
if (avail > audioworklet->write_avail_diff)
return avail - audioworklet->write_avail_diff;
return 0;
#else
return emscripten_atomic_load_u32(&audioworklet->write_avail_bytes);
#endif
}
static size_t audioworklet_buffer_size(void *data)
{
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
return audioworklet->visible_buffer_size;
}
static bool audioworklet_use_float(void *data) { return true; }
audio_driver_t audio_audioworklet = {
audioworklet_init,
audioworklet_write,
audioworklet_stop,
audioworklet_start,
audioworklet_alive,
audioworklet_set_nonblock_state,
audioworklet_free,
audioworklet_use_float,
"audioworklet",
NULL,
NULL,
audioworklet_write_avail,
audioworklet_buffer_size
};

View File

@ -312,26 +312,26 @@ error:
return NULL;
}
static ssize_t coreaudio_write(void *data, const void *buf_, size_t size)
static ssize_t coreaudio_write(void *data, const void *buf_, size_t len)
{
coreaudio_t *dev = (coreaudio_t*)data;
const uint8_t *buf = (const uint8_t*)buf_;
size_t written = 0;
while (!dev->is_paused && size > 0)
while (!dev->is_paused && len > 0)
{
size_t write_avail;
slock_lock(dev->lock);
write_avail = FIFO_WRITE_AVAIL(dev->buffer);
if (write_avail > size)
write_avail = size;
if (write_avail > len)
write_avail = len;
fifo_write(dev->buffer, buf, write_avail);
buf += write_avail;
written += write_avail;
size -= write_avail;
len -= write_avail;
if (dev->nonblock)
{
@ -409,16 +409,9 @@ static size_t coreaudio_buffer_size(void *data)
return dev->buffer_size;
}
static void *coreaudio_device_list_new(void *data)
{
/* TODO/FIXME */
return NULL;
}
static void coreaudio_device_list_free(void *data, void *array_list_data)
{
/* TODO/FIXME */
}
/* TODO/FIXME - implement */
static void *coreaudio_device_list_new(void *data) { return NULL; }
static void coreaudio_device_list_free(void *data, void *array_list_data) { }
audio_driver_t audio_coreaudio = {
coreaudio_init,

View File

@ -307,12 +307,11 @@ static void *coreaudio3_init(const char *device,
return (__bridge_retained void *)dev;
}
static ssize_t coreaudio3_write(void *data,
const void *buf_, size_t size)
static ssize_t coreaudio3_write(void *data, const void *buf_, size_t len)
{
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
return [dev writeFloat:(const float *)
buf_ samples:size/sizeof(float)] * sizeof(float);
buf_ samples:len / sizeof(float)] * sizeof(float);
}
static void coreaudio3_set_nonblock_state(void *data, bool state)

View File

@ -162,19 +162,19 @@ static void ctr_csnd_audio_free(void *data)
free(ctr);
}
static ssize_t ctr_csnd_audio_write(void *data, const void *buf, size_t size)
static ssize_t ctr_csnd_audio_write(void *data, const void *buf, size_t len)
{
int i;
uint32_t samples_played = 0;
uint64_t current_tick = 0;
const uint16_t *src = buf;
ctr_csnd_audio_t *ctr = (ctr_csnd_audio_t*)data;
unsigned int i;
uint32_t samples_played = 0;
uint64_t current_tick = 0;
const uint16_t *src = buf;
ctr_csnd_audio_t *ctr = (ctr_csnd_audio_t*)data;
ctr_csnd_audio_update_playpos(ctr);
if ((((ctr->playpos - ctr->pos) & CTR_CSND_AUDIO_COUNT_MASK) < (CTR_CSND_AUDIO_COUNT >> 2)) ||
(((ctr->pos - ctr->playpos ) & CTR_CSND_AUDIO_COUNT_MASK) < (CTR_CSND_AUDIO_COUNT >> 4)) ||
(((ctr->playpos - ctr->pos) & CTR_CSND_AUDIO_COUNT_MASK) < (size >> 2)))
if ( (((ctr->playpos - ctr->pos) & CTR_CSND_AUDIO_COUNT_MASK) < (CTR_CSND_AUDIO_COUNT >> 2))
|| (((ctr->pos - ctr->playpos) & CTR_CSND_AUDIO_COUNT_MASK) < (CTR_CSND_AUDIO_COUNT >> 4))
|| (((ctr->playpos - ctr->pos) & CTR_CSND_AUDIO_COUNT_MASK) < (len >> 2)))
{
if (ctr->nonblock)
ctr->pos = (ctr->playpos + (CTR_CSND_AUDIO_COUNT >> 1)) & CTR_CSND_AUDIO_COUNT_MASK;
@ -189,7 +189,7 @@ static ssize_t ctr_csnd_audio_write(void *data, const void *buf, size_t size)
}
}
for (i = 0; i < (size >> 1); i += 2)
for (i = 0; i < (len >> 1); i += 2)
{
ctr->l[ctr->pos] = src[i];
ctr->r[ctr->pos] = src[i + 1];
@ -200,7 +200,7 @@ static ssize_t ctr_csnd_audio_write(void *data, const void *buf, size_t size)
GSPGPU_FlushDataCache(ctr->l, CTR_CSND_AUDIO_SIZE);
GSPGPU_FlushDataCache(ctr->r, CTR_CSND_AUDIO_SIZE);
return size;
return len;
}
static bool ctr_csnd_audio_stop(void *data)
@ -264,11 +264,7 @@ static void ctr_csnd_audio_set_nonblock_state(void *data, bool state)
ctr->nonblock = state;
}
static bool ctr_csnd_audio_use_float(void *data)
{
(void)data;
return false;
}
static bool ctr_csnd_audio_use_float(void *data) { return false; }
static size_t ctr_csnd_audio_write_avail(void *data)
{

View File

@ -19,6 +19,8 @@
#include "../audio_driver.h"
#include "../../ctr/ctr_debug.h"
#include "../../retroarch.h"
#include "../../verbosity.h"
typedef struct
{
@ -91,15 +93,15 @@ static void ctr_dsp_audio_free(void *data)
ndspExit();
}
static ssize_t ctr_dsp_audio_write(void *data, const void *buf, size_t size)
static ssize_t ctr_dsp_audio_write(void *data, const void *buf, size_t len)
{
u32 pos;
ctr_dsp_audio_t * ctr = (ctr_dsp_audio_t*)data;
uint32_t sample_pos = ndspChnGetSamplePos(ctr->channel);
ctr_dsp_audio_t *ctr = (ctr_dsp_audio_t*)data;
uint32_t sample_pos = ndspChnGetSamplePos(ctr->channel);
if ((((sample_pos - ctr->pos) & CTR_DSP_AUDIO_COUNT_MASK) < (CTR_DSP_AUDIO_COUNT >> 2)) ||
(((ctr->pos - sample_pos ) & CTR_DSP_AUDIO_COUNT_MASK) < (CTR_DSP_AUDIO_COUNT >> 4)) ||
(((sample_pos - ctr->pos) & CTR_DSP_AUDIO_COUNT_MASK) < (size >> 2)))
if ( (((sample_pos - ctr->pos) & CTR_DSP_AUDIO_COUNT_MASK) < (CTR_DSP_AUDIO_COUNT >> 2))
|| (((ctr->pos - sample_pos) & CTR_DSP_AUDIO_COUNT_MASK) < (CTR_DSP_AUDIO_COUNT >> 4))
|| (((sample_pos - ctr->pos) & CTR_DSP_AUDIO_COUNT_MASK) < (len >> 2)))
{
if (ctr->nonblock)
ctr->pos = (sample_pos + (CTR_DSP_AUDIO_COUNT >> 1)) & CTR_DSP_AUDIO_COUNT_MASK;
@ -113,38 +115,38 @@ static ssize_t ctr_dsp_audio_write(void *data, const void *buf, size_t size)
* changed, this prevents a hang on sleep. */
if (!aptMainLoop())
{
command_event(CMD_EVENT_QUIT, NULL);
retroarch_main_quit();
return true;
}
sample_pos = ndspChnGetSamplePos(ctr->channel);
}while ( ((sample_pos - (ctr->pos + (size >>2))) & CTR_DSP_AUDIO_COUNT_MASK) > (CTR_DSP_AUDIO_COUNT >> 1)
}while ( ((sample_pos - (ctr->pos + (len >>2))) & CTR_DSP_AUDIO_COUNT_MASK) > (CTR_DSP_AUDIO_COUNT >> 1)
|| (((ctr->pos - (CTR_DSP_AUDIO_COUNT >> 4) - sample_pos) & CTR_DSP_AUDIO_COUNT_MASK) > (CTR_DSP_AUDIO_COUNT >> 1)));
}
}
pos = ctr->pos << 2;
if ((pos + size) > CTR_DSP_AUDIO_SIZE)
if ((pos + len) > CTR_DSP_AUDIO_SIZE)
{
memcpy(ctr->dsp_buf.data_pcm8 + pos, buf,
(CTR_DSP_AUDIO_SIZE - pos));
DSP_FlushDataCache(ctr->dsp_buf.data_pcm8 + pos, (CTR_DSP_AUDIO_SIZE - pos));
memcpy(ctr->dsp_buf.data_pcm8, (uint8_t*) buf + (CTR_DSP_AUDIO_SIZE - pos),
(pos + size - CTR_DSP_AUDIO_SIZE));
DSP_FlushDataCache(ctr->dsp_buf.data_pcm8, (pos + size - CTR_DSP_AUDIO_SIZE));
(pos + len - CTR_DSP_AUDIO_SIZE));
DSP_FlushDataCache(ctr->dsp_buf.data_pcm8, (pos + len - CTR_DSP_AUDIO_SIZE));
}
else
{
memcpy(ctr->dsp_buf.data_pcm8 + pos, buf, size);
DSP_FlushDataCache(ctr->dsp_buf.data_pcm8 + pos, size);
memcpy(ctr->dsp_buf.data_pcm8 + pos, buf, len);
DSP_FlushDataCache(ctr->dsp_buf.data_pcm8 + pos, len);
}
ctr->pos += size >> 2;
ctr->pos += len >> 2;
ctr->pos &= CTR_DSP_AUDIO_COUNT_MASK;
return size;
return len;
}
static bool ctr_dsp_audio_stop(void *data)
@ -185,11 +187,7 @@ static void ctr_dsp_audio_set_nonblock_state(void *data, bool state)
ctr->nonblock = state;
}
static bool ctr_dsp_audio_use_float(void *data)
{
(void)data;
return false;
}
static bool ctr_dsp_audio_use_float(void *data) { return false; }
static size_t ctr_dsp_audio_write_avail(void *data)
{

View File

@ -16,11 +16,13 @@
#include <3ds.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <queues/fifo_queue.h>
#include <rthreads/rthreads.h>
#include "../audio_driver.h"
#include "../../ctr/ctr_debug.h"
#include "../../verbosity.h"
typedef struct
{
@ -198,7 +200,7 @@ static void ctr_dsp_thread_audio_free(void *data)
ctr = NULL;
}
static ssize_t ctr_dsp_thread_audio_write(void *data, const void *buf, size_t size)
static ssize_t ctr_dsp_thread_audio_write(void *data, const void *buf, size_t len)
{
size_t avail, written;
ctr_dsp_thread_audio_t * ctr = (ctr_dsp_thread_audio_t*)data;
@ -210,7 +212,7 @@ static ssize_t ctr_dsp_thread_audio_write(void *data, const void *buf, size_t si
{
slock_lock(ctr->fifo_lock);
avail = FIFO_WRITE_AVAIL(ctr->fifo);
written = MIN(avail, size);
written = MIN(avail, len);
if (written > 0)
{
fifo_write(ctr->fifo, buf, written);
@ -221,7 +223,7 @@ static ssize_t ctr_dsp_thread_audio_write(void *data, const void *buf, size_t si
else
{
written = 0;
while (written < size && ctr->running)
while (written < len && ctr->running)
{
slock_lock(ctr->fifo_lock);
avail = FIFO_WRITE_AVAIL(ctr->fifo);
@ -240,7 +242,7 @@ static ssize_t ctr_dsp_thread_audio_write(void *data, const void *buf, size_t si
}
else
{
size_t write_amt = MIN(size - written, avail);
size_t write_amt = MIN(len - written, avail);
fifo_write(ctr->fifo, (const char*)buf + written, write_amt);
scond_signal(ctr->fifo_avail);
slock_unlock(ctr->fifo_lock);

View File

@ -205,7 +205,7 @@ static DWORD CALLBACK dsound_thread(PVOID data)
IDirectSoundBuffer_Unlock(ds->dsb, region.chunk1,
region.size1, region.chunk2, region.size2);
write_ptr = (write_ptr + region.size1 + region.size2)
write_ptr = (write_ptr + region.size1 + region.size2)
% ds->buffer_size;
if (is_pull)
@ -501,7 +501,7 @@ static void dsound_set_nonblock_state(void *data, bool state)
ds->nonblock = state;
}
static ssize_t dsound_write(void *data, const void *buf_, size_t size)
static ssize_t dsound_write(void *data, const void *buf_, size_t len)
{
size_t written = 0;
dsound_t *ds = (dsound_t*)data;
@ -512,39 +512,39 @@ static ssize_t dsound_write(void *data, const void *buf_, size_t size)
if (ds->nonblock)
{
if (size > 0)
if (len > 0)
{
size_t avail;
EnterCriticalSection(&ds->crit);
avail = FIFO_WRITE_AVAIL(ds->buffer);
if (avail > size)
avail = size;
if (avail > len)
avail = len;
fifo_write(ds->buffer, buf, avail);
LeaveCriticalSection(&ds->crit);
buf += avail;
size -= avail;
len -= avail;
written += avail;
}
}
else
{
while (size > 0)
while (len > 0)
{
size_t avail;
EnterCriticalSection(&ds->crit);
avail = FIFO_WRITE_AVAIL(ds->buffer);
if (avail > size)
avail = size;
if (avail > len)
avail = len;
fifo_write(ds->buffer, buf, avail);
LeaveCriticalSection(&ds->crit);
buf += avail;
size -= avail;
len -= avail;
written += avail;
if (!ds->thread_alive)

View File

@ -16,6 +16,7 @@
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#ifdef GEKKO
#include <gccore.h>
@ -79,7 +80,7 @@ static void *gx_audio_init(const char *device,
AIInit(NULL);
AIRegisterDMACallback(dma_callback);
/* Ranges 0-32000 (default low) and 40000-47999
/* Ranges 0-32000 (default low) and 40000-47999
(in settings going down from 48000) -> set to 32000 hz */
if (rate <= 32000 || (rate >= 40000 && rate < 48000))
{
@ -103,18 +104,18 @@ static void *gx_audio_init(const char *device,
/* Wii uses silly R, L, R, L interleaving. */
static INLINE void copy_swapped(uint32_t * restrict dst,
const uint32_t * restrict src, size_t size)
const uint32_t * restrict src, size_t len)
{
do
{
uint32_t s = *src++;
*dst++ = (s >> 16) | (s << 16);
} while (--size);
} while (--len);
}
static ssize_t gx_audio_write(void *data, const void *buf_, size_t size)
static ssize_t gx_audio_write(void *data, const void *buf_, size_t len)
{
size_t frames = size >> 2;
size_t frames = len >> 2;
const uint32_t *buf = buf_;
gx_audio_t *wa = data;
@ -142,8 +143,7 @@ static ssize_t gx_audio_write(void *data, const void *buf_, size_t size)
wa->dma_write = (wa->dma_write + 1) & (BLOCKS - 1);
}
}
return size;
return len;
}
static bool gx_audio_stop(void *data)
@ -209,18 +209,9 @@ static size_t gx_audio_write_avail(void *data)
& (BLOCKS - 1)) * CHUNK_SIZE;
}
static size_t gx_audio_buffer_size(void *data)
{
(void)data;
return BLOCKS * CHUNK_SIZE;
}
static bool gx_audio_use_float(void *data)
{
/* TODO/FIXME - verify */
(void)data;
return false;
}
static size_t gx_audio_buffer_size(void *data) { return BLOCKS * CHUNK_SIZE; }
/* TODO/FIXME - implement/verify? */
static bool gx_audio_use_float(void *data) { return false; }
audio_driver_t audio_gx = {
gx_audio_init,

View File

@ -252,13 +252,13 @@ error:
return NULL;
}
static ssize_t ja_write(void *data, const void *buf_, size_t size)
static ssize_t ja_write(void *data, const void *buf_, size_t len)
{
jack_t *jd = (jack_t*)data;
const char *buf = (const char *)buf_;
size_t written = 0;
while (size > 0)
while (len > 0)
{
size_t avail, to_write;
@ -267,7 +267,7 @@ static ssize_t ja_write(void *data, const void *buf_, size_t size)
avail = jack_ringbuffer_write_space(jd->buffer);
to_write = size < avail ? size : avail;
to_write = (len < avail) ? len : avail;
/* make sure to only write multiples of the sample size */
to_write = (to_write / sizeof(float)) * sizeof(float);
@ -275,7 +275,7 @@ static ssize_t ja_write(void *data, const void *buf_, size_t size)
{
jack_ringbuffer_write(jd->buffer, buf, to_write);
buf += to_write;
size -= to_write;
len -= to_write;
written += to_write;
}
else if (!jd->nonblock)
@ -350,11 +350,7 @@ static void ja_free(void *data)
free(jd);
}
static bool ja_use_float(void *data)
{
(void)data;
return true;
}
static bool ja_use_float(void *data) { return true; }
static size_t ja_write_avail(void *data)
{

View File

@ -163,29 +163,29 @@ static bool al_get_buffer(al_t *al, ALuint *buffer)
return true;
}
static size_t al_fill_internal_buf(al_t *al, const void *buf, size_t size)
static size_t al_fill_internal_buf(al_t *al, const void *s, size_t len)
{
size_t read_size = MIN(OPENAL_BUFSIZE - al->tmpbuf_ptr, size);
memcpy(al->tmpbuf + al->tmpbuf_ptr, buf, read_size);
size_t read_size = MIN(OPENAL_BUFSIZE - al->tmpbuf_ptr, len);
memcpy(al->tmpbuf + al->tmpbuf_ptr, s, read_size);
al->tmpbuf_ptr += read_size;
return read_size;
}
static ssize_t al_write(void *data, const void *buf_, size_t size)
static ssize_t al_write(void *data, const void *s, size_t len)
{
al_t *al = (al_t*)data;
const uint8_t *buf = (const uint8_t*)buf_;
const uint8_t *buf = (const uint8_t*)s;
size_t written = 0;
while (size)
while (len)
{
ALint val;
ALuint buffer;
size_t rc = al_fill_internal_buf(al, buf, size);
size_t rc = al_fill_internal_buf(al, buf, len);
written += rc;
buf += rc;
size -= rc;
len -= rc;
if (al->tmpbuf_ptr != OPENAL_BUFSIZE)
break;
@ -254,11 +254,7 @@ static size_t al_buffer_size(void *data)
return (al->num_buffers + 1) * OPENAL_BUFSIZE; /* Also got tmpbuf. */
}
static bool al_use_float(void *data)
{
(void)data;
return false;
}
static bool al_use_float(void *data) { return false; }
audio_driver_t audio_openal = {
al_init,

View File

@ -232,13 +232,13 @@ static bool sl_start(void *data, bool is_shutdown)
return sl->is_paused ? false : true;
}
static ssize_t sl_write(void *data, const void *buf_, size_t size)
static ssize_t sl_write(void *data, const void *s, size_t len)
{
sl_t *sl = (sl_t*)data;
size_t written = 0;
const uint8_t *buf = (const uint8_t*)buf_;
const uint8_t *buf = (const uint8_t*)s;
while (size)
while (len)
{
size_t avail_write;
@ -255,14 +255,14 @@ static ssize_t sl_write(void *data, const void *buf_, size_t size)
slock_unlock(sl->lock);
}
avail_write = MIN(sl->buf_size - sl->buffer_ptr, size);
avail_write = MIN(sl->buf_size - sl->buffer_ptr, len);
if (avail_write)
{
memcpy(sl->buffer[sl->buffer_index] + sl->buffer_ptr, buf, avail_write);
sl->buffer_ptr += avail_write;
buf += avail_write;
size -= avail_write;
len -= avail_write;
written += avail_write;
}
@ -287,8 +287,7 @@ static ssize_t sl_write(void *data, const void *buf_, size_t size)
static size_t sl_write_avail(void *data)
{
sl_t *sl = (sl_t*)data;
size_t avail = (sl->buf_count - (int)sl->buffered_blocks - 1) * sl->buf_size + (sl->buf_size - (int)sl->buffer_ptr);
return avail;
return ((sl->buf_count - (int)sl->buffered_blocks - 1) * sl->buf_size + (sl->buf_size - (int)sl->buffer_ptr));
}
static size_t sl_buffer_size(void *data)
@ -297,11 +296,7 @@ static size_t sl_buffer_size(void *data)
return sl->buf_size * sl->buf_count;
}
static bool sl_use_float(void *data)
{
(void)data;
return false;
}
static bool sl_use_float(void *data) { return false; }
audio_driver_t audio_opensl = {
sl_init,

View File

@ -103,15 +103,15 @@ error:
return NULL;
}
static ssize_t oss_write(void *data, const void *buf, size_t size)
static ssize_t oss_write(void *data, const void *s, size_t len)
{
ssize_t ret;
oss_audio_t *ossaudio = (oss_audio_t*)data;
if (size == 0)
if (len == 0)
return 0;
if ((ret = write(ossaudio->fd, buf, size)) < 0)
if ((ret = write(ossaudio->fd, s, len)) < 0)
{
if (errno == EAGAIN && (fcntl(ossaudio->fd, F_GETFL) & O_NONBLOCK))
return 0;
@ -169,7 +169,7 @@ static void oss_free(void *data)
{
oss_audio_t *ossaudio = (oss_audio_t*)data;
/*RETROFW IOCTL always returns EINVAL*/
/*RETROFW IOCTL always returns EINVAL*/
#if !defined(RETROFW)
if (ioctl(ossaudio->fd, SNDCTL_DSP_RESET, 0) < 0)
return;
@ -209,7 +209,6 @@ static size_t oss_buffer_size(void *data)
static bool oss_use_float(void *data)
{
(void)data;
return false;
}

View File

@ -1,5 +1,5 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2024 - Viachaslau Khalikin
* Copyright (C) 2024-2025 - Viachaslau Khalikin
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
@ -13,48 +13,38 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include <string.h>
#include <lists/string_list.h>
#include <retro_assert.h>
#include <spa/param/audio/format-utils.h>
#include <spa/utils/ringbuffer.h>
#include <spa/utils/result.h>
#include <spa/param/props.h>
#include <pipewire/pipewire.h>
#include <boolean.h>
#include <retro_assert.h>
#include <retro_miscellaneous.h>
#include <retro_endianness.h>
#include "audio/common/pipewire.h"
#include "audio/audio_driver.h"
#include "verbosity.h"
#include "../common/pipewire.h"
#include "../audio_driver.h"
#include "../../verbosity.h"
#define DEFAULT_CHANNELS 2
#define QUANTUM 1024 /* TODO: detect */
#define RINGBUFFER_SIZE (1u << 22)
#define RINGBUFFER_MASK (RINGBUFFER_SIZE - 1)
typedef struct pipewire_audio
{
pipewire_core_t *pw;
struct pw_stream *stream;
struct spa_hook stream_listener;
struct spa_audio_info_raw info;
uint32_t highwater_mark;
uint32_t frame_size;
uint32_t req;
struct spa_ringbuffer ring;
uint8_t buffer[RINGBUFFER_SIZE];
} pipewire_audio_t;
static void stream_destroy_cb(void *data)
{
pipewire_audio_t *audio = (pipewire_audio_t*)data;
@ -68,32 +58,28 @@ static void playback_process_cb(void *data)
void *p;
struct pw_buffer *b;
struct spa_buffer *buf;
uint32_t req, index, n_bytes;
uint32_t req, idx, n_bytes;
int32_t avail;
retro_assert(audio);
retro_assert(audio->stream);
if ((b = pw_stream_dequeue_buffer(audio->stream)) == NULL)
{
RARCH_WARN("[PipeWire]: Out of buffers: %s\n", strerror(errno));
return;
RARCH_WARN("[Audio] [PipeWire]: Out of buffers: %s\n", strerror(errno));
return pw_thread_loop_signal(audio->pw->thread_loop, false);
}
buf = b->buffer;
p = buf->datas[0].data;
if (p == NULL)
return;
if ((p = buf->datas[0].data) == NULL)
return pw_thread_loop_signal(audio->pw->thread_loop, false);
/* calculate the total no of bytes to read data from buffer */
req = b->requested * audio->frame_size;
n_bytes = buf->datas[0].maxsize;
if (b->requested)
n_bytes = MIN(b->requested * audio->frame_size, n_bytes);
if (req == 0)
req = audio->req;
n_bytes = SPA_MIN(req, buf->datas[0].maxsize);
/* get no of available bytes to read data from buffer */
avail = spa_ringbuffer_get_read_index(&audio->ring, &index);
avail = spa_ringbuffer_get_read_index(&audio->ring, &idx);
if (avail <= 0)
/* fill rest buffer with silence */
@ -105,18 +91,18 @@ static void playback_process_cb(void *data)
spa_ringbuffer_read_data(&audio->ring,
audio->buffer, RINGBUFFER_SIZE,
index & RINGBUFFER_MASK, p, n_bytes);
idx & RINGBUFFER_MASK, p, n_bytes);
index += n_bytes;
spa_ringbuffer_read_update(&audio->ring, index);
idx += n_bytes;
spa_ringbuffer_read_update(&audio->ring, idx);
}
buf->datas[0].chunk->offset = 0;
buf->datas[0].chunk->stride = audio->frame_size;
buf->datas[0].chunk->size = n_bytes;
/* queue the buffer for playback */
pw_stream_queue_buffer(audio->stream, b);
pw_thread_loop_signal(audio->pw->thread_loop, false);
}
static void pipewire_free(void *data);
@ -126,27 +112,11 @@ static void stream_state_changed_cb(void *data,
{
pipewire_audio_t *audio = (pipewire_audio_t*)data;
RARCH_DBG("[PipeWire]: New state for Sink Node %d : %s\n",
pw_stream_get_node_id(audio->stream),
RARCH_DBG("[Audio] [PipeWire]: Stream state changed %s -> %s\n",
pw_stream_state_as_string(old),
pw_stream_state_as_string(state));
switch(state)
{
case PW_STREAM_STATE_ERROR:
RARCH_ERR("[PipeWire]: Stream error\n");
pw_thread_loop_signal(audio->pw->thread_loop, false);
break;
case PW_STREAM_STATE_UNCONNECTED:
RARCH_WARN("[PipeWire]: Stream unconnected\n");
pw_thread_loop_stop(audio->pw->thread_loop);
break;
case PW_STREAM_STATE_STREAMING:
case PW_STREAM_STATE_PAUSED:
pw_thread_loop_signal(audio->pw->thread_loop, false);
break;
default:
break;
}
pw_thread_loop_signal(audio->pw->thread_loop, false);
}
static const struct pw_stream_events playback_stream_events = {
@ -156,60 +126,30 @@ static const struct pw_stream_events playback_stream_events = {
.state_changed = stream_state_changed_cb,
};
static void client_info_cb(void *data, const struct pw_client_info *info)
{
const struct spa_dict_item *item;
pipewire_core_t *pw = (pipewire_core_t*)data;
RARCH_DBG("[PipeWire]: client: id:%u\n", info->id);
RARCH_DBG("[PipeWire]: \tprops:\n");
spa_dict_for_each(item, info->props)
RARCH_DBG("[PipeWire]: \t\t%s: \"%s\"\n", item->key, item->value);
pw_thread_loop_signal(pw->thread_loop, false);
}
static const struct pw_client_events client_events = {
PW_VERSION_CLIENT_EVENTS,
.info = client_info_cb,
};
static void registry_event_global(void *data, uint32_t id,
uint32_t permissions, const char *type, uint32_t version,
const struct spa_dict *props)
{
union string_list_elem_attr attr;
const struct spa_dict_item *item;
pipewire_core_t *pw = (pipewire_core_t*)data;
const char *media = NULL;
const char *sink = NULL;
if (!pw->client && spa_streq(type, PW_TYPE_INTERFACE_Client))
if (spa_streq(type, PW_TYPE_INTERFACE_Node)
&& spa_streq("Audio/Sink", spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)))
{
pw->client = pw_registry_bind(pw->registry,
id, type, PW_VERSION_CLIENT, 0);
pw_client_add_listener(pw->client,
&pw->client_listener,
&client_events, pw);
}
else if (spa_streq(type, PW_TYPE_INTERFACE_Node))
{
media = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
if (media && strcmp(media, "Audio/Sink") == 0)
sink = spa_dict_lookup(props, PW_KEY_NODE_NAME);
if (sink && pw->devicelist)
{
sink = spa_dict_lookup(props, PW_KEY_NODE_NAME);
if (sink && pw->devicelist)
{
attr.i = id;
string_list_append(pw->devicelist, sink, attr);
RARCH_LOG("[PipeWire]: Found Sink Node: %s\n", sink);
}
attr.i = id;
string_list_append(pw->devicelist, sink, attr);
RARCH_LOG("[Audio] [PipeWire]: Found Sink Node: %s\n", sink);
}
}
const struct spa_dict_item *item;
RARCH_DBG("[PipeWire]: Object: id:%u Type:%s/%d\n", id, type, version);
spa_dict_for_each(item, props)
RARCH_DBG("[PipeWire]: \t\t%s: \"%s\"\n", item->key, item->value);
RARCH_DBG("[Audio] [PipeWire]: Object: id:%u Type:%s/%d\n", id, type, version);
spa_dict_for_each(item, props)
RARCH_DBG("[Audio] [PipeWire]: \t\t%s: \"%s\"\n", item->key, item->value);
}
}
static const struct pw_registry_events registry_events = {
@ -229,26 +169,22 @@ static void *pipewire_init(const char *device, unsigned rate,
struct pw_properties *props = NULL;
const char *error = NULL;
pipewire_audio_t *audio = (pipewire_audio_t*)calloc(1, sizeof(*audio));
pipewire_core_t *pw = NULL;
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
pw_init(NULL, NULL);
if (!audio)
goto error;
pw = audio->pw = (pipewire_core_t*)calloc(1, sizeof(*audio->pw));
pw->devicelist = string_list_new();
if (!pipewire_core_init(pw, "audio_driver"))
if (!pipewire_core_init(&audio->pw, "audio_driver", &registry_events))
goto error;
/* unlock, run the loop and wait, this will trigger the callbacks */
pipewire_core_wait_resync(audio->pw);
audio->info.format = is_little_endian() ? SPA_AUDIO_FORMAT_F32_LE : SPA_AUDIO_FORMAT_F32_BE;
audio->info.channels = DEFAULT_CHANNELS;
set_position(DEFAULT_CHANNELS, audio->info.position);
pipewire_set_position(DEFAULT_CHANNELS, audio->info.position);
audio->info.rate = rate;
audio->frame_size = calc_frame_size(audio->info.format, DEFAULT_CHANNELS);
audio->req = QUANTUM * rate * 1 / 2 / 100000 * audio->frame_size;
audio->frame_size = pipewire_calc_frame_size(audio->info.format, DEFAULT_CHANNELS);
props = pw_properties_new(PW_KEY_MEDIA_TYPE, PW_RARCH_MEDIA_TYPE_AUDIO,
PW_KEY_MEDIA_CATEGORY, PW_RARCH_MEDIA_CATEGORY_PLAYBACK,
@ -260,22 +196,18 @@ static void *pipewire_init(const char *device, unsigned rate,
PW_KEY_APP_ICON_NAME, PW_RARCH_APPNAME,
NULL);
if (!props)
goto error;
goto unlock_error;
if (device)
pw_properties_set(props, PW_KEY_TARGET_OBJECT, device);
buf_samples = QUANTUM * rate * 3 / 4 / 100000;
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%" PRIu64 "/%u",
buf_samples, rate);
buf_samples = latency * rate / 1000;
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%" PRIu64 "/%u", buf_samples, rate);
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", rate);
audio->stream = pw_stream_new(pw->core, PW_RARCH_APPNAME, props);
audio->stream = pw_stream_new(audio->pw->core, PW_RARCH_APPNAME, props);
if (!audio->stream)
goto error;
goto unlock_error;
pw_stream_add_listener(audio->stream, &audio->stream_listener, &playback_stream_events, audio);
@ -290,99 +222,149 @@ static void *pipewire_init(const char *device, unsigned rate,
PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS,
params, 1);
if (res < 0)
goto error;
goto unlock_error;
audio->highwater_mark = MIN(RINGBUFFER_SIZE,
latency? (latency * 1000): 46440 * (uint64_t)rate / 1000000 * audio->frame_size);
RARCH_DBG("[PipeWire]: Bufer size: %u, RingBuffer size: %u\n", audio->highwater_mark, RINGBUFFER_SIZE);
latency * (uint64_t)rate / 1000 * audio->frame_size);
pw->registry = pw_core_get_registry(pw->core, PW_VERSION_REGISTRY, 0);
pw_thread_loop_wait(audio->pw->thread_loop);
pw_thread_loop_unlock(audio->pw->thread_loop);
spa_zero(pw->registry_listener);
pw_registry_add_listener(pw->registry, &pw->registry_listener, &registry_events, pw);
/* unlock, run the loop and wait, this will trigger the callbacks */
pipewire_wait_resync(pw);
pw_thread_loop_unlock(pw->thread_loop);
*new_rate = audio->info.rate;
return audio;
unlock_error:
pw_thread_loop_unlock(audio->pw->thread_loop);
error:
RARCH_ERR("[PipeWire]: Failed to initialize audio\n");
RARCH_ERR("[Audio] [PipeWire]: Failed to initialize audio\n");
pipewire_free(audio);
return NULL;
}
static ssize_t pipewire_write(void *data, const void *buf_, size_t size)
static ssize_t pipewire_write(void *data, const void *buf_, size_t len)
{
int32_t writable;
int32_t avail;
uint32_t index;
int32_t filled, avail;
uint32_t idx;
pipewire_audio_t *audio = (pipewire_audio_t*)data;
const char *error = NULL;
if (pw_stream_get_state(audio->stream, &error) != PW_STREAM_STATE_STREAMING)
return 0; /* wait for stream to become ready */
if (len > audio->highwater_mark)
{
RARCH_ERR("[Audio] [PipeWire]: Buffer too small! Please try increasing the latency.\n");
return 0;
}
pw_thread_loop_lock(audio->pw->thread_loop);
writable = spa_ringbuffer_get_write_index(&audio->ring, &index);
avail = audio->highwater_mark - writable;
for (;;)
{
filled = spa_ringbuffer_get_write_index(&audio->ring, &idx);
avail = audio->highwater_mark - filled;
#if 0 /* Useful for tracing */
RARCH_DBG("[PipeWire]: Playback progress: written %d, avail %d, index %d, size %d\n",
writable, avail, index, size);
RARCH_DBG("[Audio] [PipeWire]: Ringbuffer utilization: filled %d, avail %d, index %d, size %d\n",
filled, avail, idx, len);
#endif
if (size > (size_t)avail)
size = avail;
/* in non-blocking mode we play as much as we can
* in blocking mode we expect a freed buffer of at least the given size */
if (len > (size_t)avail)
{
if (audio->pw->nonblock)
{
len = avail;
break;
}
if (writable < 0)
RARCH_ERR("%p: underrun write:%u filled:%d\n", audio, index, writable);
pw_thread_loop_wait(audio->pw->thread_loop);
if (pw_stream_get_state(audio->stream, &error) != PW_STREAM_STATE_STREAMING)
{
pw_thread_loop_unlock(audio->pw->thread_loop);
return -1;
}
}
else
break;
}
if (filled < 0)
RARCH_ERR("[Audio] [Pipewire]: %p: underrun write:%u filled:%d\n", audio, idx, filled);
else
{
if ((uint32_t) writable + size > RINGBUFFER_SIZE)
if ((uint32_t) filled + len > RINGBUFFER_SIZE)
{
RARCH_ERR("%p: overrun write:%u filled:%d + size:%zu > max:%u\n",
audio, index, writable, size, RINGBUFFER_SIZE);
RARCH_ERR("[Audio] [PipeWire]: %p: overrun write:%u filled:%d + size:%zu > max:%u\n",
audio, idx, filled, len, RINGBUFFER_SIZE);
}
}
spa_ringbuffer_write_data(&audio->ring,
audio->buffer, RINGBUFFER_SIZE,
index & RINGBUFFER_MASK, buf_, size);
index += size;
spa_ringbuffer_write_update(&audio->ring, index);
idx & RINGBUFFER_MASK, buf_, len);
idx += len;
spa_ringbuffer_write_update(&audio->ring, idx);
pw_thread_loop_unlock(audio->pw->thread_loop);
return size;
return len;
}
static bool pipewire_stop(void *data)
{
pipewire_audio_t *audio = (pipewire_audio_t*)data;
const char *error = NULL;
if (pw_stream_get_state(audio->stream, &error) == PW_STREAM_STATE_PAUSED)
return true;
bool res = false;
return pipewire_set_active(audio->pw->thread_loop, audio->stream, false);
if (!audio || !audio->pw)
return false;
if (pw_stream_get_state(audio->stream, &error) == PW_STREAM_STATE_STREAMING)
res = pipewire_stream_set_active(audio->pw->thread_loop, audio->stream, false);
else
/* For other states we assume that the stream is inactive */
res = true;
spa_ringbuffer_read_update(&audio->ring, 0);
spa_ringbuffer_write_update(&audio->ring, 0);
return res;
}
static bool pipewire_start(void *data, bool is_shutdown)
{
enum pw_stream_state st;
pipewire_audio_t *audio = (pipewire_audio_t*)data;
const char *error = NULL;
if (pw_stream_get_state(audio->stream, &error) == PW_STREAM_STATE_STREAMING)
return true;
bool res = false;
return pipewire_set_active(audio->pw->thread_loop, audio->stream, true);
if (!audio || !audio->pw)
return false;
st = pw_stream_get_state(audio->stream, &error);
switch (st)
{
case PW_STREAM_STATE_STREAMING:
res = true;
break;
case PW_STREAM_STATE_PAUSED:
res = pipewire_stream_set_active(audio->pw->thread_loop, audio->stream, true);
break;
default:
break;
}
return res;
}
static bool pipewire_alive(void *data)
{
pipewire_audio_t *audio = (pipewire_audio_t*)data;
const char *error = NULL;
if (!audio)
return false;
@ -401,48 +383,20 @@ static void pipewire_free(void *data)
pipewire_audio_t *audio = (pipewire_audio_t*)data;
if (!audio)
return pw_deinit();
if (audio->pw->thread_loop)
pw_thread_loop_stop(audio->pw->thread_loop);
return;
if (audio->stream)
{
pw_thread_loop_lock(audio->pw->thread_loop);
pw_stream_destroy(audio->stream);
audio->stream = NULL;
pw_thread_loop_unlock(audio->pw->thread_loop);
}
if (audio->pw->client)
pw_proxy_destroy((struct pw_proxy *)audio->pw->client);
if (audio->pw->registry)
pw_proxy_destroy((struct pw_proxy*)audio->pw->registry);
if (audio->pw->core)
{
spa_hook_remove(&audio->pw->core_listener);
spa_zero(audio->pw->core_listener);
pw_core_disconnect(audio->pw->core);
}
if (audio->pw->ctx)
pw_context_destroy(audio->pw->ctx);
pw_thread_loop_destroy(audio->pw->thread_loop);
if (audio->pw->devicelist)
string_list_free(audio->pw->devicelist);
free(audio->pw);
pipewire_core_deinit(audio->pw);
free(audio);
pw_deinit();
}
static bool pipewire_use_float(void *data)
{
(void)data;
return true;
}
static bool pipewire_use_float(void *data) { return true; }
static void *pipewire_device_list_new(void *data)
{
@ -464,7 +418,7 @@ static void pipewire_device_list_free(void *data, void *array_list_data)
static size_t pipewire_write_avail(void *data)
{
uint32_t index, written, length;
uint32_t idx, written, length;
pipewire_audio_t *audio = (pipewire_audio_t*)data;
const char *error = NULL;
@ -475,7 +429,7 @@ static size_t pipewire_write_avail(void *data)
return 0; /* wait for stream to become ready */
pw_thread_loop_lock(audio->pw->thread_loop);
written = spa_ringbuffer_get_write_index(&audio->ring, &index);
written = spa_ringbuffer_get_write_index(&audio->ring, &idx);
length = audio->highwater_mark - written;
pw_thread_loop_unlock(audio->pw->thread_loop);

View File

@ -71,14 +71,12 @@ static void ps2_audio_free(void *data)
free(ps2);
}
static ssize_t ps2_audio_write(void *data, const void *buf, size_t size)
static ssize_t ps2_audio_write(void *data, const void *s, size_t len)
{
ps2_audio_t* ps2 = (ps2_audio_t*)data;
if (!ps2->running)
return -1;
return audsrv_play_audio(buf, size);
return audsrv_play_audio(s, len);
}
static bool ps2_audio_alive(void *data)

View File

@ -142,24 +142,23 @@ static void *ps3_audio_init(const char *device,
return data;
}
static ssize_t ps3_audio_write(void *data, const void *buf, size_t size)
static ssize_t ps3_audio_write(void *data, const void *s, size_t len)
{
ps3_audio_t *aud = data;
if (aud->nonblock)
{
if (FIFO_WRITE_AVAIL(aud->buffer) < size)
if (FIFO_WRITE_AVAIL(aud->buffer) < len)
return 0;
}
while (FIFO_WRITE_AVAIL(aud->buffer) < size)
while (FIFO_WRITE_AVAIL(aud->buffer) < len)
sysLwCondWait(&aud->cond, 0);
sysLwMutexLock(&aud->lock, PS3_SYS_NO_TIMEOUT);
fifo_write(aud->buffer, buf, size);
fifo_write(aud->buffer, s, len);
sysLwMutexUnlock(&aud->lock);
return size;
return len;
}
static bool ps3_audio_stop(void *data)
@ -220,10 +219,7 @@ static void ps3_audio_free(void *data)
free(data);
}
static bool ps3_audio_use_float(void *data)
{
return true;
}
static bool ps3_audio_use_float(void *data) { return true; }
static size_t ps3_audio_write_avail(void *data)
{

View File

@ -202,11 +202,11 @@ static void psp_audio_free(void *data)
}
static ssize_t psp_audio_write(void *data, const void *buf, size_t size)
static ssize_t psp_audio_write(void *data, const void *s, size_t len)
{
psp_audio_t* psp = (psp_audio_t*)data;
uint16_t write_pos = psp->write_pos;
uint16_t sampleCount = size / sizeof(uint32_t);
uint16_t sampleCount = len / sizeof(uint32_t);
if (!psp->running)
return -1;
@ -214,35 +214,34 @@ static ssize_t psp_audio_write(void *data, const void *buf, size_t size)
if (psp->nonblock)
{
if (AUDIO_BUFFER_SIZE - ((uint16_t)
(psp->write_pos - psp->read_pos) & AUDIO_BUFFER_SIZE_MASK) < size)
(psp->write_pos - psp->read_pos) & AUDIO_BUFFER_SIZE_MASK) < len)
return 0;
}
slock_lock(psp->cond_lock);
while (AUDIO_BUFFER_SIZE - ((uint16_t)
(psp->write_pos - psp->read_pos) & AUDIO_BUFFER_SIZE_MASK) < size)
(psp->write_pos - psp->read_pos) & AUDIO_BUFFER_SIZE_MASK) < len)
scond_wait(psp->cond, psp->cond_lock);
slock_unlock(psp->cond_lock);
slock_lock(psp->fifo_lock);
if ((write_pos + sampleCount) > AUDIO_BUFFER_SIZE)
{
memcpy(psp->buffer + write_pos, buf,
memcpy(psp->buffer + write_pos, s,
(AUDIO_BUFFER_SIZE - write_pos) * sizeof(uint32_t));
memcpy(psp->buffer, (uint32_t*) buf +
memcpy(psp->buffer, (uint32_t*)s +
(AUDIO_BUFFER_SIZE - write_pos),
(write_pos + sampleCount - AUDIO_BUFFER_SIZE) * sizeof(uint32_t));
}
else
memcpy(psp->buffer + write_pos, buf, size);
memcpy(psp->buffer + write_pos, s, len);
write_pos += sampleCount;
write_pos &= AUDIO_BUFFER_SIZE_MASK;
psp->write_pos = write_pos;
slock_unlock(psp->fifo_lock);
return size;
return len;
}
static bool psp_audio_alive(void *data)

View File

@ -142,7 +142,7 @@ static void stream_state_cb(pa_stream *s, void *data)
}
}
static void stream_request_cb(pa_stream *s, size_t length, void *data)
static void stream_request_cb(pa_stream *s, size_t len, void *data)
{
pa_t *pa = (pa_t*)data;
pa_threaded_mainloop_signal(pa->mainloop, 0);
@ -276,11 +276,31 @@ error:
return NULL;
}
static bool pulse_start(void *data, bool is_shutdown);
static ssize_t pulse_write(void *data, const void *buf_, size_t size)
static bool pulse_start(void *data, bool is_shutdown)
{
bool ret;
pa_t *pa = (pa_t*)data;
if (!pa->is_ready)
return false;
if (!pa->is_paused)
return true;
pa->success = true; /* In case of spurious wakeup. Not critical. */
pa_threaded_mainloop_lock(pa->mainloop);
pa_stream_cork(pa->stream, false, stream_success_cb, pa);
pa_threaded_mainloop_wait(pa->mainloop);
ret = pa->success;
pa_threaded_mainloop_unlock(pa->mainloop);
pa->is_paused = false;
return ret;
}
static ssize_t pulse_write(void *data, const void *s, size_t len)
{
pa_t *pa = (pa_t*)data;
const uint8_t *buf = (const uint8_t*)buf_;
const uint8_t *buf = (const uint8_t*)s;
size_t written = 0;
/* Workaround buggy menu code.
@ -293,15 +313,15 @@ static ssize_t pulse_write(void *data, const void *buf_, size_t size)
return 0;
pa_threaded_mainloop_lock(pa->mainloop);
while (size)
while (len)
{
size_t writable = MIN(size, pa_stream_writable_size(pa->stream));
size_t writable = MIN(len, pa_stream_writable_size(pa->stream));
if (writable)
{
pa_stream_write(pa->stream, buf, writable, NULL, 0, PA_SEEK_RELATIVE);
buf += writable;
size -= writable;
buf += writable;
len -= writable;
written += writable;
}
else if (!pa->nonblock)
@ -344,26 +364,6 @@ static bool pulse_alive(void *data)
return !pa->is_paused;
}
static bool pulse_start(void *data, bool is_shutdown)
{
bool ret;
pa_t *pa = (pa_t*)data;
if (!pa->is_ready)
return false;
if (!pa->is_paused)
return true;
pa->success = true; /* In case of spurious wakeup. Not critical. */
pa_threaded_mainloop_lock(pa->mainloop);
pa_stream_cork(pa->stream, false, stream_success_cb, pa);
pa_threaded_mainloop_wait(pa->mainloop);
ret = pa->success;
pa_threaded_mainloop_unlock(pa->mainloop);
pa->is_paused = false;
return ret;
}
static void pulse_set_nonblock_state(void *data, bool state)
{
pa_t *pa = (pa_t*)data;
@ -371,11 +371,7 @@ static void pulse_set_nonblock_state(void *data, bool state)
pa->nonblock = state;
}
static bool pulse_use_float(void *data)
{
(void)data;
return true;
}
static bool pulse_use_float(void *data) { return true; }
static size_t pulse_write_avail(void *data)
{

View File

@ -56,19 +56,19 @@ static void *ra_init(const char *device, unsigned rate, unsigned latency,
return roar;
}
static ssize_t ra_write(void *data, const void *buf, size_t size)
static ssize_t ra_write(void *data, const void *buf, size_t len)
{
int err;
size_t written = 0;
roar_t *roar = (roar_t*)data;
if (size == 0)
if (len == 0)
return 0;
while (written < size)
while (written < len)
{
ssize_t rc;
size_t write_amt = size - written;
size_t write_amt = len - written;
if ((rc = roar_vs_write(roar->vss,
(const char*)buf + written, write_amt, &err)) < (ssize_t)write_amt)
@ -81,7 +81,7 @@ static ssize_t ra_write(void *data, const void *buf, size_t size)
written += rc;
}
return size;
return len;
}
static bool ra_stop(void *data)

View File

@ -101,7 +101,7 @@ error:
return NULL;
}
static ssize_t rs_write(void *data, const void *buf, size_t size)
static ssize_t rs_write(void *data, const void *buf, size_t len)
{
rsd_t *rsd = (rsd_t*)data;
@ -115,7 +115,7 @@ static ssize_t rs_write(void *data, const void *buf, size_t size)
rsd_callback_lock(rsd->rd);
avail = FIFO_WRITE_AVAIL(rsd->buffer);
write_amt = avail > size ? size : avail;
write_amt = avail > len ? len : avail;
fifo_write(rsd->buffer, buf, write_amt);
rsd_callback_unlock(rsd->rd);
@ -124,7 +124,7 @@ static ssize_t rs_write(void *data, const void *buf, size_t size)
else
{
size_t written = 0;
while (written < size && !rsd->has_error)
while (written < len && !rsd->has_error)
{
size_t avail;
rsd_callback_lock(rsd->rd);
@ -143,7 +143,7 @@ static ssize_t rs_write(void *data, const void *buf, size_t size)
}
else
{
size_t write_amt = size - written > avail ? avail : size - written;
size_t write_amt = len - written > avail ? avail : len - written;
fifo_write(rsd->buffer, (const char*)buf + written, write_amt);
rsd_callback_unlock(rsd->rd);
written += write_amt;

View File

@ -286,7 +286,7 @@ int rsd_stop (rsound_t *rd);
or there was an unexpected error. This function will block until all data has
been written to the buffer. This function will return the number of bytes written to the buffer,
or 0 should it fail (disconnection from server). You will have to restart the stream again should this occur. */
size_t rsd_write (rsound_t *rd, const void* buf, size_t size);
size_t rsd_write (rsound_t *rd, const void *s, size_t len);
/* Gets the position of the buffer pointer.
Not really interesting for normal applications.

View File

@ -23,7 +23,7 @@
/* forward declarations */
unsigned RWebAudioSampleRate(void);
void *RWebAudioInit(unsigned latency);
ssize_t RWebAudioWrite(const void *buf, size_t size);
ssize_t RWebAudioWrite(const void *s, size_t len);
bool RWebAudioStop(void);
bool RWebAudioStart(void);
void RWebAudioSetNonblockState(bool state);
@ -54,9 +54,9 @@ static void *rwebaudio_init(const char *device, unsigned rate, unsigned latency,
return rwebaudio;
}
static ssize_t rwebaudio_write(void *data, const void *buf, size_t size)
static ssize_t rwebaudio_write(void *data, const void *s, size_t len)
{
return RWebAudioWrite(buf, size);
return RWebAudioWrite(s, len);
}
static bool rwebaudio_stop(void *data)

View File

@ -82,30 +82,25 @@ static void sdl_audio_playback_cb(void *data, Uint8 *stream, int len)
{
sdl_audio_t *sdl = (sdl_audio_t*)data;
size_t avail = FIFO_READ_AVAIL(sdl->speaker_buffer);
size_t write_size = (len > (int)avail) ? avail : (size_t)len;
fifo_read(sdl->speaker_buffer, stream, write_size);
size_t _len = (len > (int)avail) ? avail : (size_t)len;
fifo_read(sdl->speaker_buffer, stream, _len);
#ifdef HAVE_THREADS
scond_signal(sdl->cond);
#endif
/* If underrun, fill rest with silence. */
memset(stream + write_size, 0, len - write_size);
memset(stream + _len, 0, len - _len);
}
static INLINE int sdl_audio_find_num_frames(int rate, int latency)
{
int frames = (rate * latency) / 1000;
/* SDL only likes 2^n sized buffers. */
return next_pow2(frames);
}
static void *sdl_audio_init(const char *device,
unsigned rate, unsigned latency,
unsigned block_frames,
unsigned *new_rate)
unsigned block_frames, unsigned *new_rate)
{
int frames;
size_t bufsize;
@ -115,8 +110,6 @@ static void *sdl_audio_init(const char *device,
sdl_audio_t *sdl = NULL;
uint32_t sdl_subsystem_flags = SDL_WasInit(0);
(void)device;
/* Initialise audio subsystem, if required */
if (sdl_subsystem_flags == 0)
{
@ -215,53 +208,53 @@ error:
return NULL;
}
static ssize_t sdl_audio_write(void *data, const void *buf, size_t size)
static ssize_t sdl_audio_write(void *data, const void *s, size_t len)
{
ssize_t ret = 0;
sdl_audio_t *sdl = (sdl_audio_t*)data;
/* If we shouldn't wait for space in a full outgoing sample queue... */
if (sdl->nonblock)
{ /* If we shouldn't wait for space in a full outgoing sample queue... */
{
size_t avail, write_amt;
SDL_LockAudioDevice(sdl->speaker_device); /* Stop the SDL speaker thread from running */
avail = FIFO_WRITE_AVAIL(sdl->speaker_buffer);
write_amt = avail > size ? size : avail; /* Enqueue as much data as we can */
fifo_write(sdl->speaker_buffer, buf, write_amt);
avail = FIFO_WRITE_AVAIL(sdl->speaker_buffer);
write_amt = (avail > len) ? len : avail; /* Enqueue as much data as we can */
fifo_write(sdl->speaker_buffer, s, write_amt);
SDL_UnlockAudioDevice(sdl->speaker_device); /* Let the speaker thread run again */
ret = write_amt; /* If the queue was full...well, too bad. */
ret = write_amt; /* If the queue was full...well, too bad. */
}
else
{
size_t written = 0;
while (written < size)
{ /* Until we've written all the sample data we have available... */
/* Until we've written all the sample data we have available... */
while (written < len)
{
size_t avail;
SDL_LockAudioDevice(sdl->speaker_device); /* Stop the SDL speaker thread from running */
/* Stop the SDL speaker thread from running */
SDL_LockAudioDevice(sdl->speaker_device);
avail = FIFO_WRITE_AVAIL(sdl->speaker_buffer);
/* If the outgoing sample queue is full... */
if (avail == 0)
{ /* If the outgoing sample queue is full... */
{
SDL_UnlockAudioDevice(sdl->speaker_device);
/* Let the SDL speaker thread run so it can play the enqueued samples,
* which will free up space for us to write new ones. */
#ifdef HAVE_THREADS
slock_lock(sdl->lock);
/* Let *only* the SDL speaker thread touch the outgoing sample queue */
scond_wait(sdl->cond, sdl->lock);
/* Block until SDL tells us that it's made room for new samples */
slock_unlock(sdl->lock);
/* Now let this thread use the outgoing sample queue (which we'll do next iteration) */
#endif
}
else
{
size_t write_amt = size - written > avail ? avail : size - written;
fifo_write(sdl->speaker_buffer, (const char*)buf + written, write_amt);
size_t write_amt = len - written > avail ? avail : len - written;
fifo_write(sdl->speaker_buffer, (const char*)s + written, write_amt);
/* Enqueue as many samples as we have available without overflowing the queue */
SDL_UnlockAudioDevice(sdl->speaker_device); /* Let the SDL speaker thread run again */
written += write_amt;
@ -276,9 +269,8 @@ static ssize_t sdl_audio_write(void *data, const void *buf, size_t size)
static bool sdl_audio_stop(void *data)
{
sdl_audio_t *sdl = (sdl_audio_t*)data;
sdl->is_paused = true;
sdl->is_paused = true;
SDL_PauseAudioDevice(sdl->speaker_device, true);
return true;
}
@ -293,10 +285,8 @@ static bool sdl_audio_alive(void *data)
static bool sdl_audio_start(void *data, bool is_shutdown)
{
sdl_audio_t *sdl = (sdl_audio_t*)data;
sdl->is_paused = false;
sdl->is_paused = false;
SDL_PauseAudioDevice(sdl->speaker_device, false);
return true;
}
@ -319,9 +309,7 @@ static void sdl_audio_free(void *data)
}
if (sdl->speaker_buffer)
{
fifo_free(sdl->speaker_buffer);
}
#ifdef HAVE_THREADS
slock_free(sdl->lock);
@ -333,18 +321,9 @@ static void sdl_audio_free(void *data)
free(sdl);
}
static bool sdl_audio_use_float(void *data)
{
(void)data;
return false;
}
static size_t sdl_audio_write_avail(void *data)
{
/* stub */
(void)data;
return 0;
}
/* TODO/FIXME - implement */
static bool sdl_audio_use_float(void *data) { return false; }
static size_t sdl_audio_write_avail(void *data) { return 0; }
audio_driver_t audio_sdl = {
sdl_audio_init,

View File

@ -73,10 +73,10 @@ static size_t switch_audio_buffer_size(void *data)
#endif
}
static ssize_t switch_audio_write(void *data, const void *buf, size_t size)
static ssize_t switch_audio_write(void *data, const void *s, size_t len)
{
size_t to_write = size;
switch_audio_t *swa = (switch_audio_t*) data;
size_t to_write = len;
switch_audio_t *swa = (switch_audio_t*)data;
if (!swa)
return -1;
@ -125,9 +125,9 @@ static ssize_t switch_audio_write(void *data, const void *buf, size_t size)
to_write = switch_audio_buffer_size(NULL) - swa->current_buffer->data_size;
#ifndef HAVE_LIBNX
memcpy(((uint8_t*) swa->current_buffer->sample_data) + swa->current_buffer->data_size, buf, to_write);
memcpy(((uint8_t*) swa->current_buffer->sample_data) + swa->current_buffer->data_size, s, to_write);
#else
memcpy(((uint8_t*) swa->current_buffer->buffer) + swa->current_buffer->data_size, buf, to_write);
memcpy(((uint8_t*) swa->current_buffer->buffer) + swa->current_buffer->data_size, s, to_write);
#endif
swa->current_buffer->data_size += to_write;
swa->current_buffer->buffer_size = switch_audio_buffer_size(NULL);

View File

@ -186,7 +186,7 @@ static ssize_t libnx_audren_audio_get_free_wavebuf_idx(libnx_audren_t* aud)
}
static size_t libnx_audren_audio_append(
libnx_audren_t* aud, const void *buf, size_t size)
libnx_audren_t* aud, const void *s, size_t len)
{
void *dstbuf = NULL;
ssize_t free_idx = -1;
@ -202,14 +202,14 @@ static size_t libnx_audren_audio_append(
aud->current_size = 0;
}
if (size > aud->buffer_size - aud->current_size)
size = aud->buffer_size - aud->current_size;
if (len > aud->buffer_size - aud->current_size)
len = aud->buffer_size - aud->current_size;
dstbuf = aud->current_pool_ptr + aud->current_size;
memcpy(dstbuf, buf, size);
armDCacheFlush(dstbuf, size);
memcpy(dstbuf, s, len);
armDCacheFlush(dstbuf, len);
aud->current_size += size;
aud->current_size += len;
if (aud->current_size == aud->buffer_size)
{
@ -227,11 +227,11 @@ static size_t libnx_audren_audio_append(
aud->current_wavebuf = NULL;
}
return size;
return len;
}
static ssize_t libnx_audren_audio_write(void *data,
const void *buf, size_t size)
const void *s, size_t len)
{
libnx_audren_t *aud = (libnx_audren_t*)data;
size_t written = 0;
@ -241,21 +241,21 @@ static ssize_t libnx_audren_audio_write(void *data,
if (aud->nonblock)
{
while (written < size)
while (written < len)
{
written += libnx_audren_audio_append(
aud, buf + written, size - written);
if (written != size)
aud, s + written, len - written);
if (written != len)
break;
}
}
else
{
while (written < size)
while (written < len)
{
written += libnx_audren_audio_append(
aud, buf + written, size - written);
if (written != size)
aud, s + written, len - written);
if (written != len)
{
mutexLock(&aud->update_lock);
audrvUpdate(&aud->drv);

View File

@ -166,7 +166,7 @@ static void *libnx_audren_thread_audio_init(const char *device, unsigned rate, u
aud->buffer_size = (real_latency * sample_rate / 1000);
aud->samples = (aud->buffer_size / num_channels / sizeof(int16_t));
mempool_size = (aud->buffer_size * BUFFER_COUNT +
mempool_size = (aud->buffer_size * BUFFER_COUNT +
(AUDREN_MEMPOOL_ALIGNMENT-1)) &~ (AUDREN_MEMPOOL_ALIGNMENT-1);
aud->mempool = memalign(AUDREN_MEMPOOL_ALIGNMENT, mempool_size);
@ -282,7 +282,7 @@ static size_t libnx_audren_thread_audio_buffer_size(void *data)
}
static ssize_t libnx_audren_thread_audio_write(void *data,
const void *buf, size_t size)
const void *s, size_t len)
{
libnx_audren_thread_t *aud = (libnx_audren_thread_t*)data;
size_t available, written, written_tmp;
@ -297,22 +297,22 @@ static ssize_t libnx_audren_thread_audio_write(void *data,
{
mutexLock(&aud->fifo_lock);
available = FIFO_WRITE_AVAIL(aud->fifo);
written = MIN(available, size);
written = MIN(available, len);
if (written > 0)
fifo_write(aud->fifo, buf, written);
fifo_write(aud->fifo, s, written);
mutexUnlock(&aud->fifo_lock);
}
else
{
written = 0;
while (written < size && aud->running)
while (written < len && aud->running)
{
mutexLock(&aud->fifo_lock);
available = FIFO_WRITE_AVAIL(aud->fifo);
if (available)
{
written_tmp = MIN(size - written, available);
fifo_write(aud->fifo, (const char*)buf + written, written_tmp);
written_tmp = MIN(len - written, available);
fifo_write(aud->fifo, (const char*)s + written, written_tmp);
mutexUnlock(&aud->fifo_lock);
written += written_tmp;
}

View File

@ -338,7 +338,7 @@ static void switch_thread_audio_free(void *data)
swa = NULL;
}
static ssize_t switch_thread_audio_write(void *data, const void *buf, size_t size)
static ssize_t switch_thread_audio_write(void *data, const void *s, size_t len)
{
size_t avail, written;
switch_thread_audio_t *swa = (switch_thread_audio_t *)data;
@ -350,15 +350,15 @@ static ssize_t switch_thread_audio_write(void *data, const void *buf, size_t siz
{
compat_mutex_lock(&swa->fifoLock);
avail = FIFO_WRITE_AVAIL(swa->fifo);
written = MIN(avail, size);
written = MIN(avail, len);
if (written > 0)
fifo_write(swa->fifo, buf, written);
fifo_write(swa->fifo, s, written);
compat_mutex_unlock(&swa->fifoLock);
}
else
{
written = 0;
while (written < size && swa->running)
while (written < len && swa->running)
{
compat_mutex_lock(&swa->fifoLock);
avail = FIFO_WRITE_AVAIL(swa->fifo);
@ -372,8 +372,8 @@ static ssize_t switch_thread_audio_write(void *data, const void *buf, size_t siz
}
else
{
size_t write_amt = MIN(size - written, avail);
fifo_write(swa->fifo, (const char*)buf + written, write_amt);
size_t write_amt = MIN(len - written, avail);
fifo_write(swa->fifo, (const char*)s + written, write_amt);
compat_mutex_unlock(&swa->fifoLock);
written += write_amt;
}

View File

@ -2275,12 +2275,12 @@ error:
}
static ssize_t
tinyalsa_write(void *data, const void *buf_, size_t size_)
tinyalsa_write(void *data, const void *buf_, size_t len)
{
tinyalsa_t *tinyalsa = (tinyalsa_t*)data;
const uint8_t *buf = (const uint8_t*)buf_;
snd_pcm_sframes_t written = 0;
snd_pcm_sframes_t size = BYTES_TO_FRAMES(size_, tinyalsa->frame_bits);
snd_pcm_sframes_t size = BYTES_TO_FRAMES(len, tinyalsa->frame_bits);
size_t frames_size = tinyalsa->has_float ? sizeof(float) : sizeof(int16_t);
if (tinyalsa->nonblock)

View File

@ -59,6 +59,7 @@ static void *wasapi_init(const char *dev_id, unsigned rate, unsigned latency,
settings_t *settings = config_get_ptr();
bool float_format = settings->bools.audio_wasapi_float_format;
bool exclusive_mode = settings->bools.audio_wasapi_exclusive_mode;
bool audio_sync = settings->bools.audio_sync;
unsigned sh_buffer_length = settings->uints.audio_wasapi_sh_buffer_length;
wasapi_t *w = (wasapi_t*)calloc(1, sizeof(wasapi_t));
@ -140,7 +141,7 @@ static void *wasapi_init(const char *dev_id, unsigned rate, unsigned latency,
goto error;
w->flags |= WASAPI_FLG_RUNNING;
if (settings->bools.audio_sync)
if (audio_sync)
w->flags &= ~(WASAPI_FLG_NONBLOCK);
else
w->flags |= (WASAPI_FLG_NONBLOCK);
@ -163,7 +164,7 @@ error:
return NULL;
}
static ssize_t wasapi_write(void *wh, const void *data, size_t size)
static ssize_t wasapi_write(void *wh, const void *data, size_t len)
{
size_t written = 0;
wasapi_t *w = (wasapi_t*)wh;
@ -193,16 +194,16 @@ static ssize_t wasapi_write(void *wh, const void *data, size_t size)
return -1;
write_avail = w->engine_buffer_size;
}
written = (size < write_avail) ? size : write_avail;
written = (len < write_avail) ? len : write_avail;
fifo_write(w->buffer, data, written);
}
else
{
ssize_t ir;
for (ir = -1; written < size; written += ir)
for (ir = -1; written < len; written += ir)
{
const void *_data = (char*)data + written;
size_t __size = size - written;
size_t __len = len - written;
size_t write_avail = FIFO_WRITE_AVAIL(w->buffer);
if (!write_avail)
{
@ -222,7 +223,7 @@ static ssize_t wasapi_write(void *wh, const void *data, size_t size)
write_avail = w->engine_buffer_size;
}
}
ir = (__size < write_avail) ? __size : write_avail;
ir = (__len < write_avail) ? __len : write_avail;
fifo_write(w->buffer, _data, ir);
}
}
@ -257,7 +258,7 @@ static ssize_t wasapi_write(void *wh, const void *data, size_t size)
}
}
write_avail = FIFO_WRITE_AVAIL(w->buffer);
written = size < write_avail ? size : write_avail;
written = len < write_avail ? len : write_avail;
if (written)
fifo_write(w->buffer, data, written);
}
@ -267,7 +268,7 @@ static ssize_t wasapi_write(void *wh, const void *data, size_t size)
return -1;
if (!(write_avail = w->engine_buffer_size - padding * w->frame_size))
return 0;
written = size < write_avail ? size : write_avail;
written = (len < write_avail) ? len : write_avail;
if (written)
{
BYTE *dest = NULL;
@ -285,10 +286,10 @@ static ssize_t wasapi_write(void *wh, const void *data, size_t size)
else if (w->buffer)
{
ssize_t ir;
for (ir = -1; written < size; written += ir)
for (ir = -1; written < len; written += ir)
{
const void *_data = (char*)data + written;
size_t _size = size - written;
size_t _len = len - written;
size_t write_avail = FIFO_WRITE_AVAIL(w->buffer);
UINT32 padding = 0;
if (!write_avail)
@ -315,7 +316,7 @@ static ssize_t wasapi_write(void *wh, const void *data, size_t size)
}
}
write_avail = FIFO_WRITE_AVAIL(w->buffer);
ir = (_size < write_avail) ? _size : write_avail;
ir = (_len < write_avail) ? _len : write_avail;
if (ir)
fifo_write(w->buffer, _data, ir);
}
@ -323,10 +324,10 @@ static ssize_t wasapi_write(void *wh, const void *data, size_t size)
else
{
ssize_t ir;
for (ir = -1; written < size; written += ir)
for (ir = -1; written < len; written += ir)
{
const void *_data = (char*)data + written;
size_t _size = size - written;
size_t _len = len - written;
size_t write_avail = 0;
UINT32 padding = 0;
if (!(WaitForSingleObject(w->write_event, WASAPI_TIMEOUT) == WAIT_OBJECT_0))
@ -337,7 +338,7 @@ static ssize_t wasapi_write(void *wh, const void *data, size_t size)
ir = 0;
else
{
ir = (_size < write_avail) ? _size : write_avail;
ir = (_len < write_avail) ? _len : write_avail;
if (ir)
{
BYTE *dest = NULL;
@ -427,8 +428,7 @@ static void wasapi_free(void *wh)
static bool wasapi_use_float(void *wh)
{
wasapi_t *w = (wasapi_t*)wh;
return w->frame_size == 8;
return (w->frame_size == 8);
}
static void wasapi_device_list_free(void *u, void *slp)

View File

@ -194,23 +194,23 @@ static bool ax_audio_start(void* data, bool is_shutdown)
return true;
}
static ssize_t ax_audio_write(void* data, const void* buf, size_t size)
static ssize_t ax_audio_write(void* data, const void* buf, size_t len)
{
uint32_t i;
size_t count_avail = 0;
ax_audio_t* ax = (ax_audio_t*)data;
const uint16_t* src = buf;
size_t count = size >> 2;
size_t count = len >> 2;
if (!size || (size & 0x3))
if (!len || (len & 0x3))
return 0;
if (count > AX_AUDIO_MAX_FREE)
count = AX_AUDIO_MAX_FREE;
count_avail = (
(ax->written > AX_AUDIO_MAX_FREE)
? 0
(ax->written > AX_AUDIO_MAX_FREE)
? 0
: (AX_AUDIO_MAX_FREE - ax->written));
if (ax->nonblock)

View File

@ -192,7 +192,7 @@ static void xaudio2_free(xaudio2_t *handle)
}
static xaudio2_t *xaudio2_new(unsigned samplerate, unsigned channels,
size_t size, const char *device)
size_t len, const char *device)
{
int32_t idx_found = -1;
WAVEFORMATEX wfx = {0};
@ -292,7 +292,7 @@ static xaudio2_t *xaudio2_new(unsigned samplerate, unsigned channels,
if (!handle->hEvent)
goto error;
handle->bufsize = size / MAX_BUFFERS;
handle->bufsize = len / MAX_BUFFERS;
handle->buf = (uint8_t*)calloc(1, handle->bufsize * MAX_BUFFERS);
if (!handle->buf)
goto error;
@ -341,12 +341,12 @@ static void *xa_init(const char *device, unsigned rate, unsigned latency,
return xa;
}
static ssize_t xa_write(void *data, const void *buf, size_t size)
static ssize_t xa_write(void *data, const void *s, size_t len)
{
unsigned bytes = size;
unsigned bytes = len;
xa_t *xa = (xa_t*)data;
xaudio2_t *handle = xa->xa;
const uint8_t *buffer = (const uint8_t*)buf;
const uint8_t *buffer = (const uint8_t*)s;
if (xa->flags & XA2_FLAG_NONBLOCK)
{
@ -354,8 +354,8 @@ static ssize_t xa_write(void *data, const void *buf, size_t size)
if (avail == 0)
return 0;
if (avail < size)
bytes = size = avail;
if (avail < len)
bytes = len = avail;
}
while (bytes)
@ -391,7 +391,7 @@ static ssize_t xa_write(void *data, const void *buf, size_t size)
if (FAILED(IXAudio2SourceVoice_SubmitSourceBuffer(
handle->pSourceVoice, &xa2buffer, NULL)))
{
if (size > 0)
if (len > 0)
return -1;
return 0;
}
@ -402,7 +402,7 @@ static ssize_t xa_write(void *data, const void *buf, size_t size)
}
}
return size;
return len;
}
static bool xa_stop(void *data)

View File

@ -57,21 +57,21 @@ static INLINE uint32_t bswap_32(uint32_t val)
((val >> 8) & 0xff00) | ((val << 8) & 0xff0000);
}
static ssize_t xenon360_audio_write(void *data, const void *buf, size_t size)
static ssize_t xenon360_audio_write(void *data, const void *s, size_t len)
{
size_t written = 0, i;
const uint32_t *in_buf = buf;
xenon_audio_t *xa = data;
const uint32_t *in_buf = s;
xenon_audio_t *xa = data;
for (i = 0; i < (size >> 2); i++)
for (i = 0; i < (len >> 2); i++)
xa->buffer[i] = bswap_32(in_buf[i]);
if (xa->nonblock)
{
if (xenon_sound_get_unplayed() < MAX_BUFFER)
{
xenon_sound_submit(xa->buffer, size);
written = size;
xenon_sound_submit(xa->buffer, len);
written = len;
}
}
else
@ -83,8 +83,8 @@ static ssize_t xenon360_audio_write(void *data, const void *buf, size_t size)
udelay(50);
}
xenon_sound_submit(xa->buffer, size);
written = size;
xenon_sound_submit(xa->buffer, len);
written = len;
}
return written;

View File

@ -21,7 +21,6 @@
#include "audio/microphone_driver.h"
#include "verbosity.h"
#define BYTES_TO_FRAMES(bytes, frame_bits) ((bytes) * 8 / frame_bits)
#define FRAMES_TO_BYTES(frames, frame_bits) ((frames) * frame_bits / 8)
@ -51,7 +50,7 @@ static void *alsa_microphone_init(void)
return alsa;
}
static void alsa_microphone_close_mic(void *driver_context, void *microphone_context);
static void alsa_microphone_close_mic(void *driver_context, void *mic_context);
static void alsa_microphone_free(void *driver_context)
{
alsa_microphone_t *alsa = (alsa_microphone_t*)driver_context;
@ -64,36 +63,37 @@ static void alsa_microphone_free(void *driver_context)
}
}
static bool alsa_microphone_start_mic(void *driver_context, void *microphone_context);
static int alsa_microphone_read(void *driver_context, void *microphone_context, void *buf_, size_t size_)
static bool alsa_microphone_start_mic(void *driver_context, void *mic_context);
static int alsa_microphone_read(void *driver_context, void *mic_context, void *s, size_t len)
{
alsa_microphone_t *alsa = (alsa_microphone_t*)driver_context;
alsa_microphone_handle_t *microphone = (alsa_microphone_handle_t*)microphone_context;
uint8_t *buf = (uint8_t*)buf_;
size_t frames_size;
snd_pcm_sframes_t size;
snd_pcm_state_t state;
alsa_microphone_t *alsa = (alsa_microphone_t*)driver_context;
alsa_microphone_handle_t *mic = (alsa_microphone_handle_t*)mic_context;
uint8_t *buf = (uint8_t*)s;
snd_pcm_sframes_t read = 0;
int errnum = 0;
snd_pcm_sframes_t size;
size_t frames_size;
snd_pcm_state_t state;
if (!alsa || !microphone || !buf)
if (!alsa || !mic || !buf)
return -1;
size = BYTES_TO_FRAMES(size_, microphone->stream_info.frame_bits);
frames_size = microphone->stream_info.has_float ? sizeof(float) : sizeof(int16_t);
size = BYTES_TO_FRAMES(len, mic->stream_info.frame_bits);
frames_size = mic->stream_info.has_float ? sizeof(float) : sizeof(int16_t);
state = snd_pcm_state(microphone->pcm);
state = snd_pcm_state(mic->pcm);
if (state != SND_PCM_STATE_RUNNING)
{
RARCH_WARN("[ALSA]: Expected microphone \"%s\" to be in state RUNNING, was in state %s\n",
snd_pcm_name(microphone->pcm),
snd_pcm_name(mic->pcm),
snd_pcm_state_name(state));
errnum = snd_pcm_start(microphone->pcm);
errnum = snd_pcm_start(mic->pcm);
if (errnum < 0)
{
RARCH_ERR("[ALSA]: Failed to start microphone \"%s\": %s\n",
snd_pcm_name(microphone->pcm),
snd_pcm_name(mic->pcm),
snd_strerror(errnum));
return -1;
@ -104,11 +104,11 @@ static int alsa_microphone_read(void *driver_context, void *microphone_context,
{
while (size)
{
snd_pcm_sframes_t frames = snd_pcm_readi(microphone->pcm, buf, size);
snd_pcm_sframes_t frames = snd_pcm_readi(mic->pcm, buf, size);
if (frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE)
{
errnum = snd_pcm_recover(microphone->pcm, frames, 0);
errnum = snd_pcm_recover(mic->pcm, frames, 0);
if (errnum < 0)
{
RARCH_ERR("[ALSA]: Failed to read from microphone: %s\n", snd_strerror(frames));
@ -135,20 +135,20 @@ static int alsa_microphone_read(void *driver_context, void *microphone_context,
while (size)
{
snd_pcm_sframes_t frames;
int rc = snd_pcm_wait(microphone->pcm, -1);
int rc = snd_pcm_wait(mic->pcm, -1);
if (rc == -EPIPE || rc == -ESTRPIPE || rc == -EINTR)
{
if (snd_pcm_recover(microphone->pcm, rc, 1) < 0)
if (snd_pcm_recover(mic->pcm, rc, 1) < 0)
return -1;
continue;
}
frames = snd_pcm_readi(microphone->pcm, buf, size);
frames = snd_pcm_readi(mic->pcm, buf, size);
if (frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE)
{
if (snd_pcm_recover(microphone->pcm, frames, 1) < 0)
if (snd_pcm_recover(mic->pcm, frames, 1) < 0)
return -1;
break;
@ -172,18 +172,18 @@ static int alsa_microphone_read(void *driver_context, void *microphone_context,
}
}
return FRAMES_TO_BYTES(read, microphone->stream_info.frame_bits);
return FRAMES_TO_BYTES(read, mic->stream_info.frame_bits);
}
static bool alsa_microphone_mic_alive(const void *driver_context, const void *microphone_context)
static bool alsa_microphone_mic_alive(const void *driver_context, const void *mic_context)
{
alsa_microphone_handle_t *microphone = (alsa_microphone_handle_t*)microphone_context;
alsa_microphone_handle_t *mic = (alsa_microphone_handle_t*)mic_context;
(void)driver_context;
if (!microphone)
if (!mic)
return false;
return snd_pcm_state(microphone->pcm) == SND_PCM_STATE_RUNNING;
return snd_pcm_state(mic->pcm) == SND_PCM_STATE_RUNNING;
}
static void alsa_microphone_set_nonblock_state(void *driver_context, bool nonblock)
@ -194,7 +194,6 @@ static void alsa_microphone_set_nonblock_state(void *driver_context, bool nonblo
static struct string_list *alsa_microphone_device_list_new(const void *data)
{
(void)data;
return alsa_device_list_type_new("Input");
}
@ -211,73 +210,64 @@ static void *alsa_microphone_open_mic(void *driver_context,
unsigned latency,
unsigned *new_rate)
{
alsa_microphone_t *alsa = (alsa_microphone_t*)driver_context;
alsa_microphone_handle_t *microphone = NULL;
alsa_microphone_t *alsa = (alsa_microphone_t*)driver_context;
alsa_microphone_handle_t *mic = NULL;
if (!alsa) /* If we weren't given a valid ALSA context... */
return NULL;
microphone = calloc(1, sizeof(alsa_microphone_handle_t));
if (!microphone) /* If the microphone context couldn't be allocated... */
/* If the microphone context couldn't be allocated... */
if (!(mic = calloc(1, sizeof(alsa_microphone_handle_t))))
return NULL;
/* channels hardcoded to 1, because we only support mono mic input */
if (alsa_init_pcm(&microphone->pcm, device, SND_PCM_STREAM_CAPTURE, rate, latency, 1, &microphone->stream_info, new_rate, SND_PCM_NONBLOCK) < 0)
{
if (alsa_init_pcm(&mic->pcm, device, SND_PCM_STREAM_CAPTURE, rate, latency, 1,
&mic->stream_info, new_rate, SND_PCM_NONBLOCK) < 0)
goto error;
}
return microphone;
return mic;
error:
RARCH_ERR("[ALSA]: Failed to initialize microphone...\n");
alsa_microphone_close_mic(alsa, microphone);
alsa_microphone_close_mic(alsa, mic);
return NULL;
}
static void alsa_microphone_close_mic(void *driver_context, void *microphone_context)
static void alsa_microphone_close_mic(void *driver_context, void *mic_context)
{
alsa_microphone_handle_t *microphone = (alsa_microphone_handle_t*)microphone_context;
alsa_microphone_handle_t *mic = (alsa_microphone_handle_t*)mic_context;
(void)driver_context;
if (microphone)
if (mic)
{
alsa_free_pcm(microphone->pcm);
free(microphone);
alsa_free_pcm(mic->pcm);
free(mic);
}
}
static bool alsa_microphone_start_mic(void *driver_context, void *microphone_context)
static bool alsa_microphone_start_mic(void *driver_context, void *mic_context)
{
alsa_microphone_handle_t *microphone = (alsa_microphone_handle_t*)microphone_context;
(void)driver_context;
if (!microphone)
alsa_microphone_handle_t *mic = (alsa_microphone_handle_t*)mic_context;
if (!mic)
return false;
return alsa_start_pcm(microphone->pcm);
return alsa_start_pcm(mic->pcm);
}
static bool alsa_microphone_stop_mic(void *driver_context, void *microphone_context)
static bool alsa_microphone_stop_mic(void *driver_context, void *mic_context)
{
alsa_microphone_handle_t *microphone = (alsa_microphone_handle_t*)microphone_context;
(void)driver_context;
if (!microphone)
alsa_microphone_handle_t *mic = (alsa_microphone_handle_t*)mic_context;
if (!mic)
return false;
return alsa_stop_pcm(microphone->pcm);
return alsa_stop_pcm(mic->pcm);
}
static bool alsa_microphone_mic_use_float(const void *driver_context, const void *microphone_context)
static bool alsa_microphone_mic_use_float(const void *driver_context, const void *mic_context)
{
alsa_microphone_handle_t *microphone = (alsa_microphone_handle_t*)microphone_context;
(void)driver_context;
return microphone->stream_info.has_float;
alsa_microphone_handle_t *mic = (alsa_microphone_handle_t*)mic_context;
return mic->stream_info.has_float;
}
microphone_driver_t microphone_alsa = {

View File

@ -49,27 +49,27 @@ static void *alsa_thread_microphone_init(void)
return alsa;
}
static void alsa_thread_microphone_close_mic(void *driver_context, void *microphone_context);
/* Forward declaration */
static void alsa_thread_microphone_close_mic(void *driver_context, void *mic_context);
static void alsa_thread_microphone_free(void *driver_context)
{
alsa_thread_microphone_t *alsa = (alsa_thread_microphone_t*)driver_context;
if (alsa)
{
free(alsa);
}
}
/** @see alsa_thread_read_microphone() */
static void alsa_microphone_worker_thread(void *microphone_context)
static void alsa_microphone_worker_thread(void *mic_context)
{
alsa_thread_microphone_handle_t *microphone = (alsa_thread_microphone_handle_t*)microphone_context;
uint8_t *buf = NULL;
uintptr_t thread_id = sthread_get_current_thread_id();
alsa_thread_microphone_handle_t *mic = (alsa_thread_microphone_handle_t*)mic_context;
uint8_t *buf = NULL;
uintptr_t thread_id = sthread_get_current_thread_id();
retro_assert(microphone != NULL);
buf = (uint8_t *)calloc(1, microphone->info.stream_info.period_size);
if (!buf)
retro_assert(mic != NULL);
if (!(buf = (uint8_t *)calloc(1, mic->info.stream_info.period_size)))
{
RARCH_ERR("[ALSA] [capture thread %p]: Failed to allocate audio buffer\n", thread_id);
goto end;
@ -78,34 +78,35 @@ static void alsa_microphone_worker_thread(void *microphone_context)
RARCH_DBG("[ALSA] [capture thread %p]: Beginning microphone worker thread\n", thread_id);
RARCH_DBG("[ALSA] [capture thread %p]: Microphone \"%s\" is in state %s\n",
thread_id,
snd_pcm_name(microphone->info.pcm),
snd_pcm_state_name(snd_pcm_state(microphone->info.pcm)));
snd_pcm_name(mic->info.pcm),
snd_pcm_state_name(snd_pcm_state(mic->info.pcm)));
while (!microphone->info.thread_dead)
{ /* Until we're told to stop... */
/* Until we're told to stop... */
while (!mic->info.thread_dead)
{
size_t avail;
size_t fifo_size;
snd_pcm_sframes_t frames;
int errnum = 0;
/* Lock the incoming sample queue (the main thread may block) */
slock_lock(microphone->info.fifo_lock);
slock_lock(mic->info.fifo_lock);
/* Fill the incoming sample queue with whatever we recently read */
avail = FIFO_WRITE_AVAIL(microphone->info.buffer);
fifo_size = MIN(microphone->info.stream_info.period_size, avail);
fifo_write(microphone->info.buffer, buf, fifo_size);
avail = FIFO_WRITE_AVAIL(mic->info.buffer);
fifo_size = MIN(mic->info.stream_info.period_size, avail);
fifo_write(mic->info.buffer, buf, fifo_size);
/* Tell the main thread that it's okay to query the mic again */
scond_signal(microphone->info.cond);
scond_signal(mic->info.cond);
/* Unlock the incoming sample queue (the main thread may resume) */
slock_unlock(microphone->info.fifo_lock);
slock_unlock(mic->info.fifo_lock);
/* If underrun, fill rest with silence. */
memset(buf + fifo_size, 0, microphone->info.stream_info.period_size - fifo_size);
memset(buf + fifo_size, 0, mic->info.stream_info.period_size - fifo_size);
errnum = snd_pcm_wait(microphone->info.pcm, 33);
errnum = snd_pcm_wait(mic->info.pcm, 33);
if (errnum == 0)
{
@ -118,7 +119,7 @@ static void alsa_microphone_worker_thread(void *microphone_context)
thread_id,
snd_strerror(errnum));
if ((errnum = snd_pcm_recover(microphone->info.pcm, errnum, false)) < 0)
if ((errnum = snd_pcm_recover(mic->info.pcm, errnum, false)) < 0)
{
RARCH_ERR("[ALSA] [capture thread %p]: Failed to recover from prior wait error: %s\n",
thread_id,
@ -130,7 +131,7 @@ static void alsa_microphone_worker_thread(void *microphone_context)
continue;
}
frames = snd_pcm_readi(microphone->info.pcm, buf, microphone->info.stream_info.period_frames);
frames = snd_pcm_readi(mic->info.pcm, buf, mic->info.stream_info.period_frames);
if (frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE)
{
@ -138,7 +139,7 @@ static void alsa_microphone_worker_thread(void *microphone_context)
thread_id,
snd_strerror(frames));
if ((errnum = snd_pcm_recover(microphone->info.pcm, frames, false)) < 0)
if ((errnum = snd_pcm_recover(mic->info.pcm, frames, false)) < 0)
{
RARCH_ERR("[ALSA] [capture thread %p]: Failed to recover from prior read error: %s\n",
thread_id,
@ -158,103 +159,104 @@ static void alsa_microphone_worker_thread(void *microphone_context)
}
end:
slock_lock(microphone->info.cond_lock);
microphone->info.thread_dead = true;
scond_signal(microphone->info.cond);
slock_unlock(microphone->info.cond_lock);
slock_lock(mic->info.cond_lock);
mic->info.thread_dead = true;
scond_signal(mic->info.cond);
slock_unlock(mic->info.cond_lock);
free(buf);
RARCH_DBG("[ALSA] [capture thread %p]: Ending microphone worker thread\n", thread_id);
}
static int alsa_thread_microphone_read(void *driver_context, void *microphone_context, void *buf, size_t size)
static int alsa_thread_microphone_read(void *driver_context, void *mic_context, void *s, size_t len)
{
alsa_thread_microphone_t *alsa = (alsa_thread_microphone_t*)driver_context;
alsa_thread_microphone_handle_t *microphone = (alsa_thread_microphone_handle_t*)microphone_context;
alsa_thread_microphone_t *alsa = (alsa_thread_microphone_t*)driver_context;
alsa_thread_microphone_handle_t *mic = (alsa_thread_microphone_handle_t*)mic_context;
snd_pcm_state_t state;
if (!alsa || !microphone || !buf) /* If any of the parameters were invalid... */
if (!alsa || !mic || !s) /* If any of the parameters were invalid... */
return -1;
if (microphone->info.thread_dead) /* If the mic thread is shutting down... */
if (mic->info.thread_dead) /* If the mic thread is shutting down... */
return -1;
state = snd_pcm_state(microphone->info.pcm);
state = snd_pcm_state(mic->info.pcm);
if (state != SND_PCM_STATE_RUNNING)
{
int errnum;
RARCH_WARN("[ALSA]: Expected microphone \"%s\" to be in state RUNNING, was in state %s\n",
snd_pcm_name(microphone->info.pcm),
snd_pcm_state_name(state));
snd_pcm_name(mic->info.pcm), snd_pcm_state_name(state));
errnum = snd_pcm_start(microphone->info.pcm);
errnum = snd_pcm_start(mic->info.pcm);
if (errnum < 0)
{
RARCH_ERR("[ALSA]: Failed to start microphone \"%s\": %s\n",
snd_pcm_name(microphone->info.pcm),
snd_strerror(errnum));
snd_pcm_name(mic->info.pcm), snd_strerror(errnum));
return -1;
}
}
/* If driver interactions shouldn't block... */
if (alsa->nonblock)
{ /* If driver interactions shouldn't block... */
{
size_t avail;
size_t write_amt;
/* "Hey, I'm gonna borrow the queue." */
slock_lock(microphone->info.fifo_lock);
slock_lock(mic->info.fifo_lock);
avail = FIFO_READ_AVAIL(microphone->info.buffer);
write_amt = MIN(avail, size);
avail = FIFO_READ_AVAIL(mic->info.buffer);
write_amt = MIN(avail, len);
/* "It's okay if you don't have any new samples, I'll just check in on you later." */
fifo_read(microphone->info.buffer, buf, write_amt);
fifo_read(mic->info.buffer, s, write_amt);
/* "Here, take this queue back." */
slock_unlock(microphone->info.fifo_lock);
slock_unlock(mic->info.fifo_lock);
return (int)write_amt;
}
else
{
size_t read = 0;
while (read < size && !microphone->info.thread_dead)
{ /* Until we've read all requested samples (or we're told to stop)... */
/* Until we've read all requested samples (or we're told to stop)... */
while (read < len && !mic->info.thread_dead)
{
size_t avail;
/* "Hey, I'm gonna borrow the queue." */
slock_lock(microphone->info.fifo_lock);
slock_lock(mic->info.fifo_lock);
avail = FIFO_READ_AVAIL(microphone->info.buffer);
avail = FIFO_READ_AVAIL(mic->info.buffer);
if (avail == 0)
{ /* "Oh, wait, it's empty." */
/* "Here, take it back..." */
slock_unlock(microphone->info.fifo_lock);
slock_unlock(mic->info.fifo_lock);
/* "...I'll just wait right here." */
slock_lock(microphone->info.cond_lock);
slock_lock(mic->info.cond_lock);
/* "Unless we're closing up shop..." */
if (!microphone->info.thread_dead)
if (!mic->info.thread_dead)
/* "...let me know when you've produced some samples." */
scond_wait(microphone->info.cond, microphone->info.cond_lock);
scond_wait(mic->info.cond, mic->info.cond_lock);
/* "Oh, you're ready? Okay, I'm gonna continue." */
slock_unlock(microphone->info.cond_lock);
slock_unlock(mic->info.cond_lock);
}
else
{
size_t read_amt = MIN(size - read, avail);
size_t read_amt = MIN(len - read, avail);
/* "I'll just go ahead and consume all these samples..."
* (As many as will fit in buf, or as many as are available.) */
fifo_read(microphone->info.buffer,buf + read, read_amt);
* (As many as will fit in s, or as many as are available.) */
fifo_read(mic->info.buffer,s + read, read_amt);
/* "I'm done, you can take the queue back now." */
slock_unlock(microphone->info.fifo_lock);
slock_unlock(mic->info.fifo_lock);
read += read_amt;
}
@ -264,87 +266,75 @@ static int alsa_thread_microphone_read(void *driver_context, void *microphone_co
}
}
static bool alsa_thread_microphone_mic_alive(const void *driver_context, const void *microphone_context);
static bool alsa_thread_microphone_mic_alive(const void *driver_context, const void *mic_context);
static void *alsa_thread_microphone_open_mic(void *driver_context,
const char *device,
unsigned rate,
unsigned latency,
unsigned *new_rate)
const char *device, unsigned rate, unsigned latency, unsigned *new_rate)
{
alsa_thread_microphone_t *alsa = (alsa_thread_microphone_t*)driver_context;
alsa_thread_microphone_handle_t *microphone = NULL;
alsa_thread_microphone_t *alsa = (alsa_thread_microphone_t*)driver_context;
alsa_thread_microphone_handle_t *mic = NULL;
if (!alsa) /* If we weren't given a valid ALSA context... */
return NULL;
microphone = calloc(1, sizeof(alsa_thread_microphone_handle_t));
if (!microphone)
{ /* If the microphone context couldn't be allocated... */
/* If the microphone context couldn't be allocated... */
if (!(mic = calloc(1, sizeof(alsa_thread_microphone_handle_t))))
{
RARCH_ERR("[ALSA] Failed to allocate microphone context\n");
return NULL;
}
if (alsa_init_pcm(&microphone->info.pcm, device, SND_PCM_STREAM_CAPTURE, rate, latency, 1, &microphone->info.stream_info, new_rate, 0) < 0)
{
goto error;
}
microphone->info.fifo_lock = slock_new();
microphone->info.cond_lock = slock_new();
microphone->info.cond = scond_new();
microphone->info.buffer = fifo_new(microphone->info.stream_info.buffer_size);
if (!microphone->info.fifo_lock || !microphone->info.cond_lock || !microphone->info.cond || !microphone->info.buffer || !microphone->info.pcm)
if (alsa_init_pcm(&mic->info.pcm, device, SND_PCM_STREAM_CAPTURE, rate, latency,
1, &mic->info.stream_info, new_rate, 0) < 0)
goto error;
microphone->info.worker_thread = sthread_create(alsa_microphone_worker_thread, microphone);
if (!microphone->info.worker_thread)
mic->info.fifo_lock = slock_new();
mic->info.cond_lock = slock_new();
mic->info.cond = scond_new();
mic->info.buffer = fifo_new(mic->info.stream_info.buffer_size);
if (!mic->info.fifo_lock || !mic->info.cond_lock || !mic->info.cond || !mic->info.buffer || !mic->info.pcm)
goto error;
mic->info.worker_thread = sthread_create(alsa_microphone_worker_thread, mic);
if (!mic->info.worker_thread)
{
RARCH_ERR("[ALSA]: Failed to initialize microphone worker thread\n");
goto error;
}
RARCH_DBG("[ALSA]: Initialized microphone worker thread\n");
return microphone;
return mic;
error:
RARCH_ERR("[ALSA]: Failed to initialize microphone...\n");
if (microphone)
if (mic)
{
if (microphone->info.pcm)
{
snd_pcm_close(microphone->info.pcm);
}
if (mic->info.pcm)
snd_pcm_close(mic->info.pcm);
alsa_thread_microphone_close_mic(alsa, microphone);
alsa_thread_microphone_close_mic(alsa, mic);
}
return NULL;
}
static void alsa_thread_microphone_close_mic(void *driver_context, void *microphone_context)
static void alsa_thread_microphone_close_mic(void *driver_context, void *mic_context)
{
alsa_thread_microphone_handle_t *microphone = (alsa_thread_microphone_handle_t*)microphone_context;
(void)driver_context;
if (microphone)
alsa_thread_microphone_handle_t *mic = (alsa_thread_microphone_handle_t*)mic_context;
if (mic)
{
alsa_thread_free_info_members(&microphone->info);
free(microphone);
alsa_thread_free_info_members(&mic->info);
free(mic);
}
}
static bool alsa_thread_microphone_mic_alive(const void *driver_context, const void *microphone_context)
static bool alsa_thread_microphone_mic_alive(const void *driver_context, const void *mic_context)
{
alsa_thread_microphone_handle_t *microphone = (alsa_thread_microphone_handle_t *)microphone_context;
(void)driver_context;
if (!microphone)
alsa_thread_microphone_handle_t *mic = (alsa_thread_microphone_handle_t *)mic_context;
if (!mic)
return false;
return snd_pcm_state(microphone->info.pcm) == SND_PCM_STATE_RUNNING;
return snd_pcm_state(mic->info.pcm) == SND_PCM_STATE_RUNNING;
}
static void alsa_thread_microphone_set_nonblock_state(void *driver_context, bool state)
@ -355,44 +345,35 @@ static void alsa_thread_microphone_set_nonblock_state(void *driver_context, bool
static struct string_list *alsa_thread_microphone_device_list_new(const void *data)
{
(void)data;
return alsa_device_list_type_new("Input");
}
static void alsa_thread_microphone_device_list_free(const void *driver_context, struct string_list *devices)
{
(void)driver_context;
string_list_free(devices);
/* Does nothing if devices is NULL */
}
static bool alsa_thread_microphone_start_mic(void *driver_context, void *microphone_context)
static bool alsa_thread_microphone_start_mic(void *driver_context, void *mic_context)
{
alsa_thread_microphone_handle_t *microphone = (alsa_thread_microphone_handle_t*)microphone_context;
(void)driver_context;
if (!microphone)
alsa_thread_microphone_handle_t *mic = (alsa_thread_microphone_handle_t*)mic_context;
if (!mic)
return false;
return alsa_start_pcm(microphone->info.pcm);
return alsa_start_pcm(mic->info.pcm);
}
static bool alsa_thread_microphone_stop_mic(void *driver_context, void *microphone_context)
static bool alsa_thread_microphone_stop_mic(void *driver_context, void *mic_context)
{
alsa_thread_microphone_handle_t *microphone = (alsa_thread_microphone_handle_t*)microphone_context;
(void)driver_context;
if (!microphone)
alsa_thread_microphone_handle_t *mic = (alsa_thread_microphone_handle_t*)mic_context;
if (!mic)
return false;
return alsa_stop_pcm(microphone->info.pcm);
return alsa_stop_pcm(mic->info.pcm);
}
static bool alsa_thread_microphone_mic_use_float(const void *driver_context, const void *microphone_context)
static bool alsa_thread_microphone_mic_use_float(const void *driver_context, const void *mic_context)
{
alsa_thread_microphone_handle_t *microphone = (alsa_thread_microphone_handle_t*)microphone_context;
return microphone->info.stream_info.has_float;
alsa_thread_microphone_handle_t *mic = (alsa_thread_microphone_handle_t*)mic_context;
return mic->info.stream_info.has_float;
}
microphone_driver_t microphone_alsathread = {

View File

@ -13,119 +13,98 @@
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <lists/string_list.h>
#include <retro_assert.h>
#include <spa/param/audio/format-utils.h>
#include <spa/utils/ringbuffer.h>
#include <spa/utils/result.h>
#include <spa/param/props.h>
#include <pipewire/pipewire.h>
#include <boolean.h>
#include <retro_assert.h>
#include <retro_miscellaneous.h>
#include <retro_endianness.h>
#include "audio/common/pipewire.h"
#include "audio/microphone_driver.h"
#include "verbosity.h"
#include "../common/pipewire.h"
#include "../microphone_driver.h"
#include "../../verbosity.h"
#define DEFAULT_CHANNELS 1
#define QUANTUM 1024 /* TODO: detect */
#define RINGBUFFER_SIZE (1u << 22)
#define RINGBUFFER_MASK (RINGBUFFER_SIZE - 1)
typedef struct pipewire_microphone
{
pipewire_core_t *pw;
struct pw_stream *stream;
struct spa_hook stream_listener;
struct spa_audio_info_raw info;
uint32_t frame_size;
struct spa_ringbuffer ring;
uint8_t buffer[RINGBUFFER_SIZE];
bool is_ready;
} pipewire_microphone_t;
static void stream_state_changed_cb(void *data,
enum pw_stream_state old, enum pw_stream_state state, const char *error)
{
pipewire_microphone_t *microphone = (pipewire_microphone_t*)data;
pipewire_microphone_t *mic = (pipewire_microphone_t*)data;
RARCH_DBG("[PipeWire]: New state for Source Node %d : %s\n",
pw_stream_get_node_id(microphone->stream),
RARCH_DBG("[Microphone] [PipeWire]: Stream state changed %s -> %s\n",
pw_stream_state_as_string(old),
pw_stream_state_as_string(state));
switch(state)
{
case PW_STREAM_STATE_UNCONNECTED:
microphone->is_ready = false;
pw_thread_loop_stop(microphone->pw->thread_loop);
break;
case PW_STREAM_STATE_STREAMING:
case PW_STREAM_STATE_ERROR:
case PW_STREAM_STATE_PAUSED:
pw_thread_loop_signal(microphone->pw->thread_loop, false);
break;
default:
break;
}
pw_thread_loop_signal(mic->pw->thread_loop, false);
}
static void stream_destroy_cb(void *data)
{
pipewire_microphone_t *microphone = (pipewire_microphone_t*)data;
spa_hook_remove(&microphone->stream_listener);
microphone->stream = NULL;
pipewire_microphone_t *mic = (pipewire_microphone_t*)data;
spa_hook_remove(&mic->stream_listener);
mic->stream = NULL;
}
static void capture_process_cb(void *data)
{
pipewire_microphone_t *microphone = (pipewire_microphone_t *)data;
void *p;
int32_t filled;
struct pw_buffer *b;
struct spa_buffer *buf;
int32_t filled;
uint32_t index, offs, n_bytes;
uint32_t idx, offs, n_bytes;
pipewire_microphone_t *mic = (pipewire_microphone_t*)data;
assert(microphone->stream);
retro_assert(mic);
retro_assert(mic->stream);
b = pw_stream_dequeue_buffer(microphone->stream);
if (b == NULL)
if (!(b = pw_stream_dequeue_buffer(mic->stream)))
{
RARCH_ERR("[PipeWire]: out of buffers: %s\n", strerror(errno));
return;
RARCH_ERR("[Microphone] [PipeWire]: Out of buffers: %s\n", strerror(errno));
return pw_thread_loop_signal(mic->pw->thread_loop, false);
}
buf = b->buffer;
p = buf->datas[0].data;
if (p == NULL)
return;
if ((p = buf->datas[0].data) == NULL)
return pw_thread_loop_signal(mic->pw->thread_loop, false);
offs = SPA_MIN(buf->datas[0].chunk->offset, buf->datas[0].maxsize);
n_bytes = SPA_MIN(buf->datas[0].chunk->size, buf->datas[0].maxsize - offs);
offs = MIN(buf->datas[0].chunk->offset, buf->datas[0].maxsize);
n_bytes = MIN(buf->datas[0].chunk->size, buf->datas[0].maxsize - offs);
filled = spa_ringbuffer_get_write_index(&microphone->ring, &index);
if (filled < 0)
RARCH_ERR("[PipeWire]: %p: underrun write:%u filled:%d\n", p, index, filled);
if ((filled = spa_ringbuffer_get_write_index(&mic->ring, &idx)) < 0)
RARCH_ERR("[Microphone] [PipeWire]: %p: underrun write:%u filled:%d\n", p, idx, filled);
else
{
if ((uint32_t)filled + n_bytes > RINGBUFFER_SIZE)
RARCH_ERR("[PipeWire]: %p: overrun write:%u filled:%d + size:%u > max:%u\n",
p, index, filled, n_bytes, RINGBUFFER_SIZE);
RARCH_ERR("[Microphone] [PipeWire]: %p: overrun write:%u filled:%d + size:%u > max:%u\n",
p, idx, filled, n_bytes, RINGBUFFER_SIZE);
}
spa_ringbuffer_write_data(&microphone->ring,
microphone->buffer, RINGBUFFER_SIZE,
index & RINGBUFFER_MASK,
SPA_PTROFF(p, offs, void), n_bytes);
index += n_bytes;
spa_ringbuffer_write_update(&microphone->ring, index);
spa_ringbuffer_write_data(&mic->ring,
mic->buffer, RINGBUFFER_SIZE,
idx & RINGBUFFER_MASK,
SPA_PTROFF(p, offs, void), n_bytes);
idx += n_bytes;
spa_ringbuffer_write_update(&mic->ring, idx);
pw_stream_queue_buffer(microphone->stream, b);
pw_stream_queue_buffer(mic->stream, b);
pw_thread_loop_signal(mic->pw->thread_loop, false);
}
static const struct pw_stream_events capture_stream_events = {
@ -136,29 +115,31 @@ static const struct pw_stream_events capture_stream_events = {
};
static void registry_event_global(void *data, uint32_t id,
uint32_t permissions, const char *type, uint32_t version,
const struct spa_dict *props)
uint32_t permissions, const char *type, uint32_t version,
const struct spa_dict *props)
{
union string_list_elem_attr attr;
const struct spa_dict_item *item;
pipewire_core_t *pw = (pipewire_core_t*)data;
const char *media = NULL;
const char *sink = NULL;
if (!pw)
return;
if (spa_streq(type, PW_TYPE_INTERFACE_Node))
if ( spa_streq(type, PW_TYPE_INTERFACE_Node)
&& spa_streq("Audio/Source", spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)))
{
media = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
if (media && strcmp(media, "Audio/Source") == 0)
sink = spa_dict_lookup(props, PW_KEY_NODE_NAME);
if (sink && pw->devicelist)
{
if ((sink = spa_dict_lookup(props, PW_KEY_NODE_NAME)) != NULL)
{
attr.i = id;
string_list_append(pw->devicelist, sink, attr);
RARCH_LOG("[PipeWire]: Found Source Node: %s\n", sink);
}
attr.i = id;
string_list_append(pw->devicelist, sink, attr);
RARCH_LOG("[Microphone] [PipeWire]: Found Source Node: %s\n", sink);
}
RARCH_DBG("[Microphone] [PipeWire]: Object: id:%u Type:%s/%d\n", id, type, version);
spa_dict_for_each(item, props)
RARCH_DBG("[Microphone] [PipeWire]: \t\t%s: \"%s\"\n", item->key, item->value);
}
}
@ -167,121 +148,105 @@ static const struct pw_registry_events registry_events = {
.global = registry_event_global,
};
static void pipewire_microphone_free(void *driver_context);
static void pipewire_microphone_free(void *driver_context)
{
pipewire_core_deinit((pipewire_core_t*)driver_context);
}
static void *pipewire_microphone_init(void)
{
int res;
uint64_t buf_samples;
int res;
uint8_t buffer[1024];
uint64_t buf_samples;
const struct spa_pod *params[1];
uint8_t buffer[1024];
struct pw_properties *props = NULL;
const char *error = NULL;
pipewire_core_t *pw = (pipewire_core_t*)calloc(1, sizeof(*pw));
pipewire_core_t *pw = NULL;
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
if (!pw)
if (!pipewire_core_init(&pw, "microphone_driver", &registry_events))
goto error;
pw_init(NULL, NULL);
pw->devicelist = string_list_new();
if (!pw->devicelist)
goto error;
if (!pipewire_core_init(pw, "microphone_driver"))
goto error;
pw->registry = pw_core_get_registry(pw->core, PW_VERSION_REGISTRY, 0);
spa_zero(pw->registry_listener);
pw_registry_add_listener(pw->registry, &pw->registry_listener, &registry_events, pw);
pipewire_wait_resync(pw);
pipewire_core_wait_resync(pw);
pw_thread_loop_unlock(pw->thread_loop);
return pw;
error:
RARCH_ERR("[PipeWire]: Failed to initialize microphone\n");
RARCH_ERR("[Microphone] [PipeWire]: Failed to initialize microphone\n");
pipewire_microphone_free(pw);
return NULL;
}
static void pipewire_microphone_close_mic(void *driver_context, void *microphone_context);
static void pipewire_microphone_free(void *driver_context)
static void pipewire_microphone_close_mic(void *driver_context, void *mic_context)
{
pipewire_core_t *pw = (pipewire_core_t*)driver_context;
pipewire_core_t *pw = (pipewire_core_t*)driver_context;
pipewire_microphone_t *mic = (pipewire_microphone_t*)mic_context;
if (!pw)
return pw_deinit();
if (pw->thread_loop)
pw_thread_loop_stop(pw->thread_loop);
if (pw->client)
pw_proxy_destroy((struct pw_proxy *)pw->client);
if (pw->registry)
pw_proxy_destroy((struct pw_proxy*)pw->registry);
if (pw->core)
if (pw && mic)
{
spa_hook_remove(&pw->core_listener);
spa_zero(pw->core_listener);
pw_core_disconnect(pw->core);
pw_thread_loop_lock(pw->thread_loop);
pw_stream_destroy(mic->stream);
mic->stream = NULL;
pw_thread_loop_unlock(pw->thread_loop);
free(mic);
}
if (pw->ctx)
pw_context_destroy(pw->ctx);
pw_thread_loop_destroy(pw->thread_loop);
if (pw->devicelist)
string_list_free(pw->devicelist);
free(pw);
pw_deinit();
}
static int pipewire_microphone_read(void *driver_context, void *microphone_context, void *buf_, size_t size_)
static int pipewire_microphone_read(void *driver_context, void *mic_context, void *s, size_t len)
{
int32_t readable;
uint32_t index;
const char *error = NULL;
pipewire_core_t *pw = (pipewire_core_t*)driver_context;
pipewire_microphone_t *microphone = (pipewire_microphone_t*)microphone_context;
uint32_t idx;
int32_t readable;
const char *error = NULL;
pipewire_core_t *pw = (pipewire_core_t*)driver_context;
pipewire_microphone_t *mic = (pipewire_microphone_t*)mic_context;
if (!microphone->is_ready || pw_stream_get_state(microphone->stream, &error) != PW_STREAM_STATE_STREAMING)
if (pw_stream_get_state(mic->stream, &error) != PW_STREAM_STATE_STREAMING)
return -1;
pw_thread_loop_lock(pw->thread_loop);
/* get no of available bytes to read data from buffer */
readable = spa_ringbuffer_get_read_index(&microphone->ring, &index);
if (readable < (int32_t)size_)
size_ = readable;
for (;;)
{
/* get no of available bytes to read data from buffer */
readable = spa_ringbuffer_get_read_index(&mic->ring, &idx);
spa_ringbuffer_read_data(&microphone->ring,
microphone->buffer, RINGBUFFER_SIZE,
index & RINGBUFFER_MASK, buf_, size_);
index += size_;
spa_ringbuffer_read_update(&microphone->ring, index);
if (readable < (int32_t)len)
{
if (pw->nonblock)
{
len = readable;
break;
}
pw_thread_loop_wait(pw->thread_loop);
if (pw_stream_get_state(mic->stream, &error) != PW_STREAM_STATE_STREAMING)
{
pw_thread_loop_unlock(mic->pw->thread_loop);
return -1;
}
}
else
break;
}
spa_ringbuffer_read_data(&mic->ring,
mic->buffer, RINGBUFFER_SIZE,
idx & RINGBUFFER_MASK, s, len);
idx += len;
spa_ringbuffer_read_update(&mic->ring, idx);
pw_thread_loop_unlock(pw->thread_loop);
return size_;
return len;
}
static bool pipewire_microphone_mic_alive(const void *driver_context, const void *microphone_context)
static bool pipewire_microphone_mic_alive(const void *driver_context, const void *mic_context)
{
const char *error = NULL;
pipewire_microphone_t *microphone = (pipewire_microphone_t*)microphone_context;
(void)driver_context;
if (!microphone)
const char *error = NULL;
pipewire_microphone_t *mic = (pipewire_microphone_t*)mic_context;
if (!mic)
return false;
return pw_stream_get_state(microphone->stream, &error) == PW_STREAM_STATE_STREAMING;
return pw_stream_get_state(mic->stream, &error) == PW_STREAM_STATE_STREAMING;
}
static void pipewire_microphone_set_nonblock_state(void *driver_context, bool nonblock)
@ -303,40 +268,35 @@ static struct string_list *pipewire_microphone_device_list_new(const void *drive
static void pipewire_microphone_device_list_free(const void *driver_context, struct string_list *devices)
{
(void)driver_context;
if (devices)
string_list_free(devices);
}
static void *pipewire_microphone_open_mic(void *driver_context,
const char *device,
unsigned rate,
unsigned latency,
const char *device, unsigned rate, unsigned latency,
unsigned *new_rate)
{
int res;
uint64_t buf_samples;
const struct spa_pod *params[1];
uint8_t buffer[1024];
struct pw_properties *props = NULL;
const char *error = NULL;
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
pipewire_microphone_t *microphone = calloc(1, sizeof(pipewire_microphone_t));
int res;
uint64_t buf_samples;
uint8_t buffer[1024];
const struct spa_pod *params[1];
struct pw_properties *props = NULL;
const char *error = NULL;
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
pipewire_microphone_t *mic = NULL;
retro_assert(driver_context);
if (!microphone)
if (!driver_context || (mic = calloc(1, sizeof(pipewire_microphone_t))) == NULL)
goto error;
microphone->pw = (pipewire_core_t*)driver_context;
mic->pw = (pipewire_core_t*)driver_context;
pw_thread_loop_lock(microphone->pw->thread_loop);
pw_thread_loop_lock(mic->pw->thread_loop);
microphone->info.format = is_little_endian() ? SPA_AUDIO_FORMAT_F32_LE : SPA_AUDIO_FORMAT_F32_BE;
microphone->info.channels = DEFAULT_CHANNELS;
set_position(DEFAULT_CHANNELS, microphone->info.position);
microphone->info.rate = rate;
microphone->frame_size = calc_frame_size(microphone->info.format, DEFAULT_CHANNELS);
mic->info.format = is_little_endian() ? SPA_AUDIO_FORMAT_F32_LE : SPA_AUDIO_FORMAT_F32_BE;
mic->info.channels = DEFAULT_CHANNELS;
pipewire_set_position(DEFAULT_CHANNELS, mic->info.position);
mic->info.rate = rate;
mic->frame_size = pipewire_calc_frame_size(mic->info.format, DEFAULT_CHANNELS);
props = pw_properties_new(PW_KEY_MEDIA_TYPE, PW_RARCH_MEDIA_TYPE_AUDIO,
PW_KEY_MEDIA_CATEGORY, PW_RARCH_MEDIA_CATEGORY_RECORD,
@ -353,98 +313,95 @@ static void *pipewire_microphone_open_mic(void *driver_context,
if (device)
pw_properties_set(props, PW_KEY_TARGET_OBJECT, device);
buf_samples = QUANTUM * rate * 3 / 4 / 100000;
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%" PRIu64 "/%u",
buf_samples, rate);
buf_samples = latency * rate / 1000;
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%" PRIu64 "/%u", buf_samples, rate);
pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", rate);
microphone->stream = pw_stream_new(microphone->pw->core, PW_RARCH_APPNAME, props);
if (!microphone->stream)
if (!(mic->stream = pw_stream_new(mic->pw->core, PW_RARCH_APPNAME, props)))
goto unlock_error;
pw_stream_add_listener(microphone->stream, &microphone->stream_listener, &capture_stream_events, microphone);
pw_stream_add_listener(mic->stream, &mic->stream_listener, &capture_stream_events, mic);
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &microphone->info);
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &mic->info);
/* Now connect this stream. We ask that our process function is
* called in a realtime thread. */
res = pw_stream_connect(microphone->stream,
PW_DIRECTION_INPUT,
PW_ID_ANY,
PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS,
params, 1);
res = pw_stream_connect(mic->stream, PW_DIRECTION_INPUT, PW_ID_ANY,
PW_STREAM_FLAG_AUTOCONNECT
| PW_STREAM_FLAG_INACTIVE
| PW_STREAM_FLAG_MAP_BUFFERS
| PW_STREAM_FLAG_RT_PROCESS,
params, 1);
if (res < 0)
goto unlock_error;
pw_thread_loop_wait(microphone->pw->thread_loop);
pw_thread_loop_wait(mic->pw->thread_loop);
pw_thread_loop_unlock(mic->pw->thread_loop);
pw_thread_loop_unlock(microphone->pw->thread_loop);
*new_rate = microphone->info.rate;
microphone->is_ready = true;
*new_rate = mic->info.rate;
return microphone;
return mic;
unlock_error:
pw_thread_loop_unlock(microphone->pw->thread_loop);
pw_thread_loop_unlock(mic->pw->thread_loop);
error:
RARCH_ERR("[PipeWire]: Failed to initialize microphone...\n");
pipewire_microphone_close_mic(microphone->pw, microphone);
RARCH_ERR("[Microphone] [PipeWire]: Failed to initialize microphone...\n");
pipewire_microphone_close_mic(mic->pw, mic);
return NULL;
}
static void pipewire_microphone_close_mic(void *driver_context, void *microphone_context)
static bool pipewire_microphone_start_mic(void *driver_context, void *mic_context)
{
pipewire_core_t *pw = (pipewire_core_t*)driver_context;
pipewire_microphone_t *microphone = (pipewire_microphone_t*)microphone_context;
enum pw_stream_state st;
pipewire_core_t *pw = (pipewire_core_t*)driver_context;
pipewire_microphone_t *mic = (pipewire_microphone_t*)mic_context;
const char *error = NULL;
bool res = false;
if (pw && microphone)
if (!pw || !mic)
return false;
st = pw_stream_get_state(mic->stream, &error);
switch (st)
{
pw_thread_loop_lock(pw->thread_loop);
pw_stream_destroy(microphone->stream);
microphone->stream = NULL;
pw_thread_loop_unlock(pw->thread_loop);
free(microphone);
case PW_STREAM_STATE_STREAMING:
res = true;
break;
case PW_STREAM_STATE_PAUSED:
res = pipewire_stream_set_active(pw->thread_loop, mic->stream, true);
break;
default:
break;
}
return res;
}
static bool pipewire_microphone_start_mic(void *driver_context, void *microphone_context)
static bool pipewire_microphone_stop_mic(void *driver_context, void *mic_context)
{
pipewire_core_t *pw = (pipewire_core_t*)driver_context;
pipewire_microphone_t *microphone = (pipewire_microphone_t*)microphone_context;
const char *error = NULL;
pipewire_core_t *pw = (pipewire_core_t*)driver_context;
pipewire_microphone_t *mic = (pipewire_microphone_t*)mic_context;
const char *error = NULL;
bool res = false;
if (!microphone->is_ready)
if (!pw || !mic)
return false;
if (pw_stream_get_state(microphone->stream, &error) == PW_STREAM_STATE_STREAMING)
return true;
return pipewire_set_active(pw->thread_loop, microphone->stream, true);
if (pw_stream_get_state(mic->stream, &error) == PW_STREAM_STATE_STREAMING)
res = pipewire_stream_set_active(pw->thread_loop, mic->stream, false);
else
/* For other states we assume that the stream is inactive */
res = true;
spa_ringbuffer_read_update(&mic->ring, 0);
spa_ringbuffer_write_update(&mic->ring, 0);
return res;
}
static bool pipewire_microphone_stop_mic(void *driver_context, void *microphone_context)
static bool pipewire_microphone_mic_use_float(const void *a, const void *b)
{
pipewire_core_t *pw = (pipewire_core_t*)driver_context;
pipewire_microphone_t *microphone = (pipewire_microphone_t*)microphone_context;
const char *error = NULL;
if (!microphone->is_ready)
return false;
if (pw_stream_get_state(microphone->stream, &error) == PW_STREAM_STATE_PAUSED)
return true;
return pipewire_set_active(pw->thread_loop, microphone->stream, false);
}
static bool pipewire_microphone_mic_use_float(const void *driver_context, const void *microphone_context)
{
(void)driver_context;
(void)microphone_context;
return true;
}

View File

@ -45,9 +45,7 @@ typedef struct sdl_microphone
static INLINE int sdl_microphone_find_num_frames(int rate, int latency)
{
int frames = (rate * latency) / 1000;
/* SDL only likes 2^n sized buffers. */
return next_pow2(frames);
}
@ -55,7 +53,6 @@ static void *sdl_microphone_init(void)
{
sdl_microphone_t *sdl = NULL;
uint32_t sdl_subsystem_flags = SDL_WasInit(0);
/* Initialise audio subsystem, if required */
if (sdl_subsystem_flags == 0)
{
@ -67,32 +64,30 @@ static void *sdl_microphone_init(void)
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
return NULL;
}
if (!(sdl = (sdl_microphone_t*)calloc(1, sizeof(*sdl))))
return NULL;
return sdl;
}
static void sdl_microphone_close_mic(void *driver_context, void *microphone_context)
static void sdl_microphone_close_mic(void *driver_context, void *mic_context)
{
sdl_microphone_handle_t *microphone = (sdl_microphone_handle_t *)microphone_context;
sdl_microphone_handle_t *mic = (sdl_microphone_handle_t *)mic_context;
if (microphone)
if (mic)
{
/* If the microphone was originally initialized successfully... */
if (microphone->device_id > 0)
SDL_CloseAudioDevice(microphone->device_id);
if (mic->device_id > 0)
SDL_CloseAudioDevice(mic->device_id);
fifo_free(microphone->sample_buffer);
fifo_free(mic->sample_buffer);
#ifdef HAVE_THREADS
slock_free(microphone->lock);
scond_free(microphone->cond);
slock_free(mic->lock);
scond_free(mic->cond);
#endif
RARCH_LOG("[SDL audio]: Freed microphone with former device ID %u\n", microphone->device_id);
free(microphone);
RARCH_LOG("[SDL audio]: Freed microphone with former device ID %u\n", mic->device_id);
free(mic);
}
}
@ -109,28 +104,24 @@ static void sdl_microphone_free(void *data)
static void sdl_audio_record_cb(void *data, Uint8 *stream, int len)
{
sdl_microphone_handle_t *microphone = (sdl_microphone_handle_t*)data;
size_t avail = FIFO_WRITE_AVAIL(microphone->sample_buffer);
size_t read_size = MIN(len, (int)avail);
sdl_microphone_handle_t *mic = (sdl_microphone_handle_t*)data;
size_t avail = FIFO_WRITE_AVAIL(mic->sample_buffer);
size_t read_size = MIN(len, (int)avail);
/* If the sample buffer is almost full, just write as much as we can into it*/
fifo_write(microphone->sample_buffer, stream, read_size);
fifo_write(mic->sample_buffer, stream, read_size);
#ifdef HAVE_THREADS
scond_signal(microphone->cond);
scond_signal(mic->cond);
#endif
}
static void *sdl_microphone_open_mic(void *driver_context,
const char *device,
unsigned rate,
unsigned latency,
unsigned *new_rate)
static void *sdl_microphone_open_mic(void *driver_context, const char *device,
unsigned rate, unsigned latency, unsigned *new_rate)
{
int frames;
size_t bufsize;
sdl_microphone_handle_t *microphone = NULL;
SDL_AudioSpec desired_spec = {0};
void *tmp = NULL;
void *tmp = NULL;
sdl_microphone_handle_t *mic = NULL;
SDL_AudioSpec desired_spec = {0};
#if __APPLE__
if (!string_is_equal(audio_driver_get_ident(), "sdl2"))
@ -150,7 +141,7 @@ static void *sdl_microphone_open_mic(void *driver_context,
return NULL;
}
if (!(microphone = (sdl_microphone_handle_t *)
if (!(mic = (sdl_microphone_handle_t *)
calloc(1, sizeof(sdl_microphone_handle_t))))
return NULL;
@ -174,30 +165,31 @@ static void *sdl_microphone_open_mic(void *driver_context,
desired_spec.format = AUDIO_F32SYS;
desired_spec.channels = 1; /* Microphones only usually provide input in mono */
desired_spec.samples = frames;
desired_spec.userdata = microphone;
desired_spec.userdata = mic;
desired_spec.callback = sdl_audio_record_cb;
microphone->device_id = SDL_OpenAudioDevice(
mic->device_id = SDL_OpenAudioDevice(
NULL,
true,
&desired_spec,
&microphone->device_spec,
SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_FORMAT_CHANGE);
&mic->device_spec,
SDL_AUDIO_ALLOW_FREQUENCY_CHANGE
| SDL_AUDIO_ALLOW_FORMAT_CHANGE);
if (microphone->device_id == 0)
if (mic->device_id == 0)
{
RARCH_ERR("[SDL mic]: Failed to open SDL audio input device: %s\n", SDL_GetError());
goto error;
}
RARCH_DBG("[SDL mic]: Opened SDL audio input device with ID %u\n",
microphone->device_id);
mic->device_id);
RARCH_DBG("[SDL mic]: Requested a microphone frequency of %u Hz, got %u Hz\n",
desired_spec.freq, microphone->device_spec.freq);
desired_spec.freq, mic->device_spec.freq);
RARCH_DBG("[SDL mic]: Requested %u channels for microphone, got %u\n",
desired_spec.channels, microphone->device_spec.channels);
desired_spec.channels, mic->device_spec.channels);
RARCH_DBG("[SDL mic]: Requested a %u-sample microphone buffer, got %u samples (%u bytes)\n",
frames, microphone->device_spec.samples, microphone->device_spec.size);
RARCH_DBG("[SDL mic]: Got a microphone silence value of %u\n", microphone->device_spec.silence);
frames, mic->device_spec.samples, mic->device_spec.size);
RARCH_DBG("[SDL mic]: Got a microphone silence value of %u\n", mic->device_spec.silence);
RARCH_DBG("[SDL mic]: Requested microphone audio format: %u-bit %s %s %s endian\n",
SDL_AUDIO_BITSIZE(desired_spec.format),
SDL_AUDIO_ISSIGNED(desired_spec.format) ? "signed" : "unsigned",
@ -211,87 +203,85 @@ static void *sdl_microphone_open_mic(void *driver_context,
SDL_AUDIO_ISBIGENDIAN(desired_spec.format) ? "big" : "little");
if (new_rate)
*new_rate = microphone->device_spec.freq;
*new_rate = mic->device_spec.freq;
#ifdef HAVE_THREADS
microphone->lock = slock_new();
microphone->cond = scond_new();
mic->lock = slock_new();
mic->cond = scond_new();
#endif
RARCH_LOG("[SDL audio]: Requested %u ms latency for input device, got %d ms\n",
latency, (int)(microphone->device_spec.samples * 4 * 1000 / microphone->device_spec.freq));
latency, (int)(mic->device_spec.samples * 4 * 1000 / mic->device_spec.freq));
/* Create a buffer twice as big as needed and prefill the buffer. */
bufsize = microphone->device_spec.samples * 2 * (SDL_AUDIO_BITSIZE(microphone->device_spec.format) / 8);
tmp = calloc(1, bufsize);
microphone->sample_buffer = fifo_new(bufsize);
bufsize = mic->device_spec.samples * 2 * (SDL_AUDIO_BITSIZE(mic->device_spec.format) / 8);
tmp = calloc(1, bufsize);
mic->sample_buffer = fifo_new(bufsize);
RARCH_DBG("[SDL audio]: Initialized microphone sample queue with %u bytes\n", bufsize);
if (tmp)
{
fifo_write(microphone->sample_buffer, tmp, bufsize);
fifo_write(mic->sample_buffer, tmp, bufsize);
free(tmp);
}
RARCH_LOG("[SDL audio]: Initialized microphone with device ID %u\n", microphone->device_id);
return microphone;
RARCH_LOG("[SDL audio]: Initialized microphone with device ID %u\n", mic->device_id);
return mic;
error:
free(microphone);
free(mic);
return NULL;
}
static bool sdl_microphone_mic_alive(const void *data, const void *microphone_context)
static bool sdl_microphone_mic_alive(const void *data, const void *mic_context)
{
const sdl_microphone_handle_t *microphone = (const sdl_microphone_handle_t*)microphone_context;
if (!microphone)
const sdl_microphone_handle_t *mic = (const sdl_microphone_handle_t*)mic_context;
if (!mic)
return false;
/* Both params must be non-null */
return SDL_GetAudioDeviceStatus(microphone->device_id) == SDL_AUDIO_PLAYING;
return SDL_GetAudioDeviceStatus(mic->device_id) == SDL_AUDIO_PLAYING;
}
static bool sdl_microphone_start_mic(void *driver_context, void *microphone_context)
static bool sdl_microphone_start_mic(void *driver_context, void *mic_context)
{
sdl_microphone_handle_t *microphone = (sdl_microphone_handle_t*)microphone_context;
if (!microphone)
sdl_microphone_handle_t *mic = (sdl_microphone_handle_t*)mic_context;
if (!mic)
return false;
SDL_PauseAudioDevice(microphone->device_id, false);
if (SDL_GetAudioDeviceStatus(microphone->device_id) != SDL_AUDIO_PLAYING)
SDL_PauseAudioDevice(mic->device_id, false);
if (SDL_GetAudioDeviceStatus(mic->device_id) != SDL_AUDIO_PLAYING)
{
RARCH_ERR("[SDL mic]: Failed to start microphone %u: %s\n", microphone->device_id, SDL_GetError());
RARCH_ERR("[SDL mic]: Failed to start microphone %u: %s\n", mic->device_id, SDL_GetError());
return false;
}
RARCH_DBG("[SDL mic]: Started microphone %u\n", microphone->device_id);
RARCH_DBG("[SDL mic]: Started microphone %u\n", mic->device_id);
return true;
}
static bool sdl_microphone_stop_mic(void *driver_context, void *microphone_context)
static bool sdl_microphone_stop_mic(void *driver_context, void *mic_context)
{
sdl_microphone_t *sdl = (sdl_microphone_t*)driver_context;
sdl_microphone_handle_t *microphone = (sdl_microphone_handle_t*)microphone_context;
sdl_microphone_t *sdl = (sdl_microphone_t*)driver_context;
sdl_microphone_handle_t *mic = (sdl_microphone_handle_t*)mic_context;
if (!sdl || !microphone)
if (!sdl || !mic)
return false;
SDL_PauseAudioDevice(microphone->device_id, true);
SDL_PauseAudioDevice(mic->device_id, true);
switch (SDL_GetAudioDeviceStatus(microphone->device_id))
switch (SDL_GetAudioDeviceStatus(mic->device_id))
{
case SDL_AUDIO_PLAYING:
RARCH_ERR("[SDL mic]: Microphone %u failed to pause\n", microphone->device_id);
RARCH_ERR("[SDL mic]: Microphone %u failed to pause\n", mic->device_id);
return false;
case SDL_AUDIO_STOPPED:
RARCH_WARN("[SDL mic]: Microphone %u is in state STOPPED; it may not start again\n", microphone->device_id);
RARCH_WARN("[SDL mic]: Microphone %u is in state STOPPED; it may not start again\n",
mic->device_id);
/* fall-through */
case SDL_AUDIO_PAUSED:
break;
default:
RARCH_ERR("[SDL mic]: Microphone %u is in unknown state\n", microphone->device_id);
RARCH_ERR("[SDL mic]: Microphone %u is in unknown state\n",
mic->device_id);
return false;
}
@ -305,64 +295,68 @@ static void sdl_microphone_set_nonblock_state(void *driver_context, bool state)
sdl->nonblock = state;
}
static int sdl_microphone_read(void *driver_context, void *microphone_context, void *buf, size_t size)
static int sdl_microphone_read(void *driver_context, void *mic_context, void *s, size_t len)
{
int ret = 0;
sdl_microphone_t *sdl = (sdl_microphone_t*)driver_context;
sdl_microphone_handle_t *microphone = (sdl_microphone_handle_t*)microphone_context;
int ret = 0;
sdl_microphone_t *sdl = (sdl_microphone_t*)driver_context;
sdl_microphone_handle_t *mic = (sdl_microphone_handle_t*)mic_context;
if (!sdl || !microphone || !buf)
if (!sdl || !mic || !s)
return -1;
/* If we shouldn't block on an empty queue... */
if (sdl->nonblock)
{ /* If we shouldn't block on an empty queue... */
{
size_t avail, read_amt;
SDL_LockAudioDevice(microphone->device_id); /* Stop the SDL mic thread */
avail = FIFO_READ_AVAIL(microphone->sample_buffer);
read_amt = avail > size ? size : avail;
SDL_LockAudioDevice(mic->device_id); /* Stop the SDL mic thread */
avail = FIFO_READ_AVAIL(mic->sample_buffer);
read_amt = avail > len ? len : avail;
if (read_amt > 0)
{ /* If the incoming queue isn't empty... */
fifo_read(microphone->sample_buffer, buf, read_amt);
fifo_read(mic->sample_buffer, s, read_amt);
/* ...then read as much data as will fit in buf */
}
SDL_UnlockAudioDevice(microphone->device_id); /* Let the mic thread run again */
SDL_UnlockAudioDevice(mic->device_id); /* Let the mic thread run again */
ret = (int)read_amt;
}
else
{
size_t read = 0;
while (read < size)
{ /* Until we've given the caller as much data as they've asked for... */
/* Until we've given the caller as much data as they've asked for... */
while (read < len)
{
size_t avail;
SDL_LockAudioDevice(microphone->device_id);
SDL_LockAudioDevice(mic->device_id);
/* Stop the SDL microphone thread from running */
avail = FIFO_READ_AVAIL(microphone->sample_buffer);
avail = FIFO_READ_AVAIL(mic->sample_buffer);
if (avail == 0)
{ /* If the incoming sample queue is empty... */
SDL_UnlockAudioDevice(microphone->device_id);
/* Let the SDL microphone thread run so it can push some incoming samples */
SDL_UnlockAudioDevice(mic->device_id);
/* Let the SDL microphone thread run so it can
* push some incoming samples */
#ifdef HAVE_THREADS
slock_lock(microphone->lock);
/* Let *only* the SDL microphone thread access the incoming sample queue. */
scond_wait(microphone->cond, microphone->lock);
/* Wait until the SDL microphone thread tells us it's added some samples. */
slock_unlock(microphone->lock);
/* Allow this thread to access the incoming sample queue, which we'll do next iteration */
slock_lock(mic->lock);
/* Let *only* the SDL microphone thread access
* the incoming sample queue. */
scond_wait(mic->cond, mic->lock);
/* Wait until the SDL microphone thread tells us
* it's added some samples. */
slock_unlock(mic->lock);
/* Allow this thread to access the incoming sample queue,
* which we'll do next iteration */
#endif
}
else
{
size_t read_amt = MIN(size - read, avail);
fifo_read(microphone->sample_buffer, buf + read, read_amt);
/* Read as many samples as we have available without underflowing the queue */
SDL_UnlockAudioDevice(microphone->device_id);
size_t read_amt = MIN(len - read, avail);
fifo_read(mic->sample_buffer, s + read, read_amt);
/* Read as many samples as we have available without
* underflowing the queue */
SDL_UnlockAudioDevice(mic->device_id);
/* Let the SDL microphone thread run again */
read += read_amt;
}
@ -373,10 +367,10 @@ static int sdl_microphone_read(void *driver_context, void *microphone_context, v
return ret;
}
static bool sdl_microphone_mic_use_float(const void *driver_context, const void *microphone_context)
static bool sdl_microphone_mic_use_float(const void *driver_context, const void *mic_context)
{
sdl_microphone_handle_t *microphone = (sdl_microphone_handle_t*)microphone_context;
return SDL_AUDIO_ISFLOAT(microphone->device_spec.format);
sdl_microphone_handle_t *mic = (sdl_microphone_handle_t*)mic_context;
return SDL_AUDIO_ISFLOAT(mic->device_spec.format);
}
microphone_driver_t microphone_sdl = {

View File

@ -51,34 +51,61 @@ typedef struct wasapi_microphone
bool nonblock;
} wasapi_microphone_t;
static void wasapi_microphone_close_mic(void *driver_context, void *mic_context)
{
DWORD ir;
HANDLE write_event;
wasapi_microphone_t *wasapi = (wasapi_microphone_t*)driver_context;
wasapi_microphone_handle_t *mic = (wasapi_microphone_handle_t*)mic_context;
if (!wasapi || !mic)
return;
write_event = mic->read_event;
IFACE_RELEASE(mic->capture);
if (mic->client)
_IAudioClient_Stop(mic->client);
IFACE_RELEASE(mic->client);
IFACE_RELEASE(mic->device);
if (mic->buffer)
fifo_free(mic->buffer);
if (mic->device_name)
free(mic->device_name);
free(mic);
ir = WaitForSingleObject(write_event, 20);
if (ir == WAIT_FAILED)
{
RARCH_ERR("[WASAPI mic]: WaitForSingleObject failed: %s\n", wasapi_error(GetLastError()));
}
/* If event isn't signaled log and leak */
if (ir != WAIT_OBJECT_0)
return;
CloseHandle(write_event);
}
static void wasapi_microphone_close_mic(void *driver_context, void *microphone_context);
static void *wasapi_microphone_init(void)
{
settings_t *settings = config_get_ptr();
wasapi_microphone_t *wasapi = (wasapi_microphone_t*)calloc(1, sizeof(wasapi_microphone_t));
if (!wasapi)
{
RARCH_ERR("[WASAPI mic]: Failed to allocate microphone driver context\n");
return NULL;
}
wasapi->nonblock = !settings->bools.audio_sync;
wasapi->nonblock = !config_get_ptr()->bools.audio_sync;
RARCH_DBG("[WASAPI mic]: Initialized microphone driver context.\n");
return wasapi;
}
static void wasapi_microphone_free(void *driver_context)
{
wasapi_microphone_t *wasapi = (wasapi_microphone_t*)driver_context;
if (!wasapi)
return;
free(wasapi);
if (wasapi)
free(wasapi);
}
/**
@ -90,7 +117,7 @@ static void wasapi_microphone_free(void *driver_context)
* @return The number of bytes in the queue after fetching input,
* or -1 if there was an error.
*/
static int wasapi_microphone_fetch_fifo(wasapi_microphone_handle_t *microphone)
static int wasapi_microphone_fetch_fifo(wasapi_microphone_handle_t *mic)
{
UINT32 next_packet_size = 0;
/* Shared-mode capture streams split their input buffer into multiple packets,
@ -106,21 +133,20 @@ static int wasapi_microphone_fetch_fifo(wasapi_microphone_handle_t *microphone)
UINT32 frames_read = 0;
UINT32 bytes_read = 0;
DWORD buffer_status_flags = 0;
HRESULT hr = _IAudioCaptureClient_GetBuffer(microphone->capture,
HRESULT hr = _IAudioCaptureClient_GetBuffer(mic->capture,
&mic_input, &frames_read, &buffer_status_flags, NULL, NULL);
if (FAILED(hr))
{
RARCH_ERR("[WASAPI]: Failed to get capture device \"%s\"'s buffer: %s\n",
microphone->device_name,
hresult_name(hr));
mic->device_name, hresult_name(hr));
return -1;
}
bytes_read = frames_read * microphone->frame_size;
bytes_read = frames_read * mic->frame_size;
/* If the queue has room for the packets we just got... */
if (FIFO_WRITE_AVAIL(microphone->buffer) >= bytes_read && bytes_read > 0)
if (FIFO_WRITE_AVAIL(mic->buffer) >= bytes_read && bytes_read > 0)
{
fifo_write(microphone->buffer, mic_input, bytes_read);
fifo_write(mic->buffer, mic_input, bytes_read);
/* ...then enqueue the bytes directly from the mic's buffer */
}
else /* Not enough space for new frames, so we can't consume this packet right now */
@ -128,25 +154,23 @@ static int wasapi_microphone_fetch_fifo(wasapi_microphone_handle_t *microphone)
/* If there's insufficient room in the queue, then we can't read the packet.
* In that case, we leave the packet for next time. */
hr = _IAudioCaptureClient_ReleaseBuffer(microphone->capture, frames_read);
hr = _IAudioCaptureClient_ReleaseBuffer(mic->capture, frames_read);
if (FAILED(hr))
{
RARCH_ERR("[WASAPI]: Failed to release capture device \"%s\"'s buffer after consuming %u frames: %s\n",
microphone->device_name,
frames_read,
hresult_name(hr));
mic->device_name, frames_read, hresult_name(hr));
return -1;
}
/* If this is a shared-mode stream and we didn't run out of room in the sample queue... */
if (!microphone->exclusive && frames_read > 0)
if (!mic->exclusive && frames_read > 0)
{
hr = _IAudioCaptureClient_GetNextPacketSize(microphone->capture, &next_packet_size);
hr = _IAudioCaptureClient_GetNextPacketSize(mic->capture, &next_packet_size);
/* Get the number of frames that the mic has for us. */
if (FAILED(hr))
{
RARCH_ERR("[WASAPI]: Failed to get capture device \"%s\"'s next packet size: %s\n",
microphone->device_name, hresult_name(hr));
mic->device_name, hresult_name(hr));
return -1;
}
}
@ -156,7 +180,7 @@ static int wasapi_microphone_fetch_fifo(wasapi_microphone_handle_t *microphone)
}
while (next_packet_size != 0);
return FIFO_READ_AVAIL(microphone->buffer);
return FIFO_READ_AVAIL(mic->buffer);
}
/**
@ -167,20 +191,20 @@ static int wasapi_microphone_fetch_fifo(wasapi_microphone_handle_t *microphone)
* @return \c true if the event was signalled,
* \c false if it timed out or there was an error.
*/
static bool wasapi_microphone_wait_for_capture_event(wasapi_microphone_handle_t *microphone, DWORD timeout)
static bool wasapi_microphone_wait_for_capture_event(wasapi_microphone_handle_t *mic, DWORD timeout)
{
/*...then let's wait for the mic to tell us that samples are ready. */
switch (WaitForSingleObject(microphone->read_event, timeout))
switch (WaitForSingleObject(mic->read_event, timeout))
{
case WAIT_OBJECT_0:
/* Okay, there's data available. */
return true;
case WAIT_TIMEOUT:
/* Time out; there's nothing here for us. */
RARCH_ERR("[WASAPI]: Failed to wait for capture device \"%s\" event: Timeout after %ums\n", microphone->device_name, timeout);
RARCH_ERR("[WASAPI]: Failed to wait for capture device \"%s\" event: Timeout after %ums\n", mic->device_name, timeout);
break;
default:
RARCH_ERR("[WASAPI]: Failed to wait for capture device \"%s\" event: %s\n", microphone->device_name, wasapi_error(GetLastError()));
RARCH_ERR("[WASAPI]: Failed to wait for capture device \"%s\" event: %s\n", mic->device_name, wasapi_error(GetLastError()));
break;
}
return false;
@ -202,22 +226,20 @@ static bool wasapi_microphone_wait_for_capture_event(wasapi_microphone_handle_t
* or -1 if there was an error (including timeout).
*/
static int wasapi_microphone_read_buffered(
wasapi_microphone_handle_t *microphone,
void *buffer,
size_t buffer_size,
wasapi_microphone_handle_t *mic, void *s, size_t len,
DWORD timeout)
{
int bytes_read = 0; /* Number of bytes sent to the core */
int bytes_available = FIFO_READ_AVAIL(microphone->buffer);
int bytes_available = FIFO_READ_AVAIL(mic->buffer);
/* If we don't have any queued samples to give to the core... */
if (!bytes_available)
{
/* If we couldn't wait for the microphone to signal a capture event... */
if (!wasapi_microphone_wait_for_capture_event(microphone, timeout))
if (!wasapi_microphone_wait_for_capture_event(mic, timeout))
return -1;
bytes_available = wasapi_microphone_fetch_fifo(microphone);
bytes_available = wasapi_microphone_fetch_fifo(mic);
/* If we couldn't fetch samples from the microphone... */
if (bytes_available < 0)
return -1;
@ -225,34 +247,33 @@ static int wasapi_microphone_read_buffered(
/* Now that we have samples available, let's give them to the core */
bytes_read = MIN((int)buffer_size, bytes_available);
fifo_read(microphone->buffer, buffer, bytes_read);
bytes_read = MIN((int)len, bytes_available);
fifo_read(mic->buffer, s, bytes_read);
/* Read data from the sample queue and store it in the provided buffer */
return bytes_read;
}
static int wasapi_microphone_read(void *driver_context, void *mic_context, void *buffer, size_t buffer_size)
static int wasapi_microphone_read(void *driver_context, void *mic_context, void *s, size_t len)
{
int bytes_read = 0;
wasapi_microphone_t *wasapi = (wasapi_microphone_t *)driver_context;
wasapi_microphone_handle_t *microphone = (wasapi_microphone_handle_t*)mic_context;
int bytes_read = 0;
wasapi_microphone_t *wasapi = (wasapi_microphone_t *)driver_context;
wasapi_microphone_handle_t *mic = (wasapi_microphone_handle_t*)mic_context;
if (!wasapi || !microphone || !buffer)
if (!wasapi || !mic || !s)
return -1;
/* If microphones shouldn't block... */
if (wasapi->nonblock)
return wasapi_microphone_read_buffered(microphone, buffer, buffer_size, 0);
return wasapi_microphone_read_buffered(mic, s, len, 0);
if (microphone->exclusive)
if (mic->exclusive)
{
int read;
for (read = -1; (size_t)bytes_read < buffer_size; bytes_read += read)
for (read = -1; (size_t)bytes_read < len; bytes_read += read)
{
read = wasapi_microphone_read_buffered(microphone,
(char *)buffer + bytes_read,
buffer_size - bytes_read,
read = wasapi_microphone_read_buffered(mic,
(char *)s + bytes_read,
len - bytes_read,
INFINITE);
if (read == -1)
return -1;
@ -261,11 +282,11 @@ static int wasapi_microphone_read(void *driver_context, void *mic_context, void
else
{
int read;
for (read = -1; (size_t)bytes_read < buffer_size; bytes_read += read)
for (read = -1; (size_t)bytes_read < len; bytes_read += read)
{
read = wasapi_microphone_read_buffered(microphone,
(char *)buffer + bytes_read,
buffer_size - bytes_read,
read = wasapi_microphone_read_buffered(mic,
(char *)s + bytes_read,
len - bytes_read,
INFINITE);
if (read == -1)
return -1;
@ -278,7 +299,6 @@ static int wasapi_microphone_read(void *driver_context, void *mic_context, void
static void wasapi_microphone_set_nonblock_state(void *driver_context, bool nonblock)
{
wasapi_microphone_t *wasapi = (wasapi_microphone_t*)driver_context;
wasapi->nonblock = nonblock;
}
@ -286,81 +306,80 @@ static void *wasapi_microphone_open_mic(void *driver_context, const char *device
unsigned latency, unsigned *new_rate)
{
HRESULT hr;
settings_t *settings = config_get_ptr();
DWORD flags = 0;
UINT32 frame_count = 0;
REFERENCE_TIME dev_period = 0;
BYTE *dest = NULL;
bool float_format = settings->bools.microphone_wasapi_float_format;
bool exclusive_mode = settings->bools.microphone_wasapi_exclusive_mode;
unsigned sh_buffer_length = settings->uints.microphone_wasapi_sh_buffer_length;
wasapi_microphone_handle_t *microphone = (wasapi_microphone_handle_t*)calloc(
settings_t *settings = config_get_ptr();
DWORD flags = 0;
UINT32 frame_count = 0;
REFERENCE_TIME dev_period = 0;
BYTE *dest = NULL;
bool float_format = settings->bools.microphone_wasapi_float_format;
bool exclusive_mode = settings->bools.microphone_wasapi_exclusive_mode;
unsigned sh_buffer_length = settings->uints.microphone_wasapi_sh_buffer_length;
wasapi_microphone_handle_t *mic = (wasapi_microphone_handle_t*)calloc(
1, sizeof(wasapi_microphone_handle_t));
if (!microphone)
if (!mic)
return NULL;
microphone->exclusive = exclusive_mode;
microphone->device = wasapi_init_device(device, eCapture);
mic->exclusive = exclusive_mode;
mic->device = wasapi_init_device(device, eCapture);
/* If we requested a particular capture device, but couldn't open it... */
if (device && !microphone->device)
if (device && !mic->device)
{
RARCH_WARN("[WASAPI]: Failed to open requested capture device \"%s\", attempting to open default device\n", device);
microphone->device = wasapi_init_device(NULL, eCapture);
mic->device = wasapi_init_device(NULL, eCapture);
}
if (!microphone->device)
if (!mic->device)
{
RARCH_ERR("[WASAPI]: Failed to open capture device\n");
goto error;
}
microphone->device_name = mmdevice_name(microphone->device);
if (!microphone->device_name)
if (!(mic->device_name = mmdevice_name(mic->device)))
{
RARCH_ERR("[WASAPI]: Failed to get friendly name of capture device\n");
goto error;
}
microphone->client = wasapi_init_client(microphone->device,
&microphone->exclusive, &float_format, &rate, latency, 1);
if (!microphone->client)
mic->client = wasapi_init_client(mic->device,
&mic->exclusive, &float_format, &rate, latency, 1);
if (!mic->client)
{
RARCH_ERR("[WASAPI]: Failed to open client for capture device \"%s\"\n", microphone->device_name);
RARCH_ERR("[WASAPI]: Failed to open client for capture device \"%s\"\n", mic->device_name);
goto error;
}
hr = _IAudioClient_GetBufferSize(microphone->client, &frame_count);
hr = _IAudioClient_GetBufferSize(mic->client, &frame_count);
if (FAILED(hr))
{
RARCH_ERR("[WASAPI]: Failed to get buffer size of IAudioClient for capture device \"%s\": %s\n",
microphone->device_name, hresult_name(hr));
mic->device_name, hresult_name(hr));
goto error;
}
microphone->frame_size = float_format ? sizeof(float) : sizeof(int16_t);
microphone->engine_buffer_size = frame_count * microphone->frame_size;
mic->frame_size = float_format ? sizeof(float) : sizeof(int16_t);
mic->engine_buffer_size = frame_count * mic->frame_size;
/* If this mic should be used *exclusively* by RetroArch... */
if (microphone->exclusive)
if (mic->exclusive)
{
microphone->buffer = fifo_new(microphone->engine_buffer_size);
if (!microphone->buffer)
mic->buffer = fifo_new(mic->engine_buffer_size);
if (!mic->buffer)
{
RARCH_ERR("[WASAPI]: Failed to initialize FIFO queue for capture device.\n");
goto error;
}
RARCH_LOG("[WASAPI]: Intermediate exclusive-mode capture buffer length is %u frames (%.1fms, %u bytes).\n",
frame_count, (double)frame_count * 1000.0 / rate, microphone->engine_buffer_size);
frame_count, (double)frame_count * 1000.0 / rate, mic->engine_buffer_size);
}
else
{
/* If the user selected the "default" shared buffer length... */
if (sh_buffer_length <= 0)
{
hr = _IAudioClient_GetDevicePeriod(microphone->client, &dev_period, NULL);
hr = _IAudioClient_GetDevicePeriod(mic->client, &dev_period, NULL);
if (FAILED(hr))
goto error;
@ -369,30 +388,29 @@ static void *wasapi_microphone_open_mic(void *driver_context, const char *device
* Doubling it seems to work okay. Dunno why. */
}
microphone->buffer = fifo_new(sh_buffer_length * microphone->frame_size);
if (!microphone->buffer)
mic->buffer = fifo_new(sh_buffer_length * mic->frame_size);
if (!mic->buffer)
goto error;
RARCH_LOG("[WASAPI]: Intermediate shared-mode capture buffer length is %u frames (%.1fms, %u bytes).\n",
sh_buffer_length, (double)sh_buffer_length * 1000.0 / rate, sh_buffer_length * microphone->frame_size);
sh_buffer_length, (double)sh_buffer_length * 1000.0 / rate, sh_buffer_length * mic->frame_size);
}
microphone->read_event = CreateEventA(NULL, FALSE, FALSE, NULL);
if (!microphone->read_event)
if (!(mic->read_event = CreateEventA(NULL, FALSE, FALSE, NULL)))
{
RARCH_ERR("[WASAPI]: Failed to allocate capture device's event handle\n");
goto error;
}
hr = _IAudioClient_SetEventHandle(microphone->client, microphone->read_event);
hr = _IAudioClient_SetEventHandle(mic->client, mic->read_event);
if (FAILED(hr))
{
RARCH_ERR("[WASAPI]: Failed to set capture device's event handle: %s\n", hresult_name(hr));
goto error;
}
hr = _IAudioClient_GetService(microphone->client,
IID_IAudioCaptureClient, (void**)&microphone->capture);
hr = _IAudioClient_GetService(mic->client,
IID_IAudioCaptureClient, (void**)&mic->capture);
if (FAILED(hr))
{
RARCH_ERR("[WASAPI]: Failed to get capture device's IAudioCaptureClient service: %s\n", hresult_name(hr));
@ -400,132 +418,81 @@ static void *wasapi_microphone_open_mic(void *driver_context, const char *device
}
/* Get and release the buffer, just to ensure that we can. */
hr = _IAudioCaptureClient_GetBuffer(microphone->capture, &dest, &frame_count, &flags, NULL, NULL);
hr = _IAudioCaptureClient_GetBuffer(mic->capture, &dest, &frame_count, &flags, NULL, NULL);
if (FAILED(hr))
{
RARCH_ERR("[WASAPI]: Failed to get capture client buffer: %s\n", hresult_name(hr));
goto error;
}
hr = _IAudioCaptureClient_ReleaseBuffer(microphone->capture, 0);
hr = _IAudioCaptureClient_ReleaseBuffer(mic->capture, 0);
if (FAILED(hr))
{
RARCH_ERR("[WASAPI]: Failed to release capture client buffer: %s\n", hresult_name(hr));
goto error;
}
/* The rate was (possibly) modified when we initialized the client */
if (new_rate)
{ /* The rate was (possibly) modified when we initialized the client */
*new_rate = rate;
}
return microphone;
return mic;
error:
IFACE_RELEASE(microphone->capture);
IFACE_RELEASE(microphone->client);
IFACE_RELEASE(microphone->device);
if (microphone->read_event)
CloseHandle(microphone->read_event);
if (microphone->buffer)
fifo_free(microphone->buffer);
if (microphone->device_name)
free(microphone->device_name);
free(microphone);
IFACE_RELEASE(mic->capture);
IFACE_RELEASE(mic->client);
IFACE_RELEASE(mic->device);
if (mic->read_event)
CloseHandle(mic->read_event);
if (mic->buffer)
fifo_free(mic->buffer);
if (mic->device_name)
free(mic->device_name);
free(mic);
return NULL;
}
static void wasapi_microphone_close_mic(void *driver_context, void *microphone_context)
static bool wasapi_microphone_start_mic(void *driver_context, void *mic_context)
{
DWORD ir;
wasapi_microphone_t *wasapi = (wasapi_microphone_t*)driver_context;
wasapi_microphone_handle_t *microphone = (wasapi_microphone_handle_t*)microphone_context;
HANDLE write_event;
if (!wasapi || !microphone)
return;
write_event = microphone->read_event;
IFACE_RELEASE(microphone->capture);
if (microphone->client)
_IAudioClient_Stop(microphone->client);
IFACE_RELEASE(microphone->client);
IFACE_RELEASE(microphone->device);
if (microphone->buffer)
fifo_free(microphone->buffer);
if (microphone->device_name)
free(microphone->device_name);
free(microphone);
ir = WaitForSingleObject(write_event, 20);
if (ir == WAIT_FAILED)
{
RARCH_ERR("[WASAPI mic]: WaitForSingleObject failed: %s\n", wasapi_error(GetLastError()));
}
/* If event isn't signaled log and leak */
if (ir != WAIT_OBJECT_0)
return;
CloseHandle(write_event);
}
static bool wasapi_microphone_start_mic(void *driver_context, void *microphone_context)
{
wasapi_microphone_handle_t *microphone = (wasapi_microphone_handle_t*)microphone_context;
wasapi_microphone_handle_t *mic = (wasapi_microphone_handle_t*)mic_context;
HRESULT hr;
(void)driver_context;
if (!microphone)
if (!mic)
return false;
hr = _IAudioClient_Start(mic->client);
hr = _IAudioClient_Start(microphone->client);
/* Starting an already-active microphone is not an error */
if (SUCCEEDED(hr) || hr == AUDCLNT_E_NOT_STOPPED)
{ /* Starting an already-active microphone is not an error */
microphone->running = true;
}
mic->running = true;
else
{
RARCH_ERR("[WASAPI mic]: Failed to start capture device \"%s\"'s IAudioClient: %s\n",
microphone->device_name, hresult_name(hr));
microphone->running = false;
mic->device_name, hresult_name(hr));
mic->running = false;
}
return microphone->running;
return mic->running;
}
static bool wasapi_microphone_stop_mic(void *driver_context, void *microphone_context)
static bool wasapi_microphone_stop_mic(void *driver_context, void *mic_context)
{
wasapi_microphone_handle_t *microphone = (wasapi_microphone_handle_t*)microphone_context;
wasapi_microphone_handle_t *mic = (wasapi_microphone_handle_t*)mic_context;
HRESULT hr;
(void)driver_context;
if (!microphone)
if (!mic)
return false;
hr = _IAudioClient_Stop(microphone->client);
hr = _IAudioClient_Stop(mic->client);
if (FAILED(hr))
{
RARCH_ERR("[WASAPI mic]: Failed to stop capture device \"%s\"'s IAudioClient: %s\n",
microphone->device_name, hresult_name(hr));
mic->device_name, hresult_name(hr));
return false;
}
RARCH_LOG("[WASAPI mic]: Stopped capture device \"%s\".\n", microphone->device_name);
microphone->running = false;
RARCH_LOG("[WASAPI mic]: Stopped capture device \"%s\".\n", mic->device_name);
mic->running = false;
return true;
}
static bool wasapi_microphone_mic_alive(const void *driver_context, const void *mic_context)
{
wasapi_microphone_handle_t *microphone = (wasapi_microphone_handle_t *)mic_context;
(void)driver_context;
return microphone && microphone->running;
wasapi_microphone_handle_t *mic = (wasapi_microphone_handle_t *)mic_context;
return mic && mic->running;
}
static struct string_list *wasapi_microphone_device_list_new(const void *driver_context)
@ -536,20 +503,14 @@ static struct string_list *wasapi_microphone_device_list_new(const void *driver_
static void wasapi_microphone_device_list_free(const void *driver_context, struct string_list *devices)
{
struct string_list *sl = (struct string_list*)devices;
if (sl)
string_list_free(sl);
}
static bool wasapi_microphone_use_float(const void *driver_context, const void *microphone_context)
static bool wasapi_microphone_use_float(const void *driver_context, const void *mic_context)
{
wasapi_microphone_handle_t *microphone = (wasapi_microphone_handle_t *)microphone_context;
(void)driver_context;
if (!microphone)
return false;
return microphone->frame_size == sizeof(float);
wasapi_microphone_handle_t *mic = (wasapi_microphone_handle_t *)mic_context;
return (mic && (mic->frame_size == sizeof(float)));
}
microphone_driver_t microphone_wasapi = {

File diff suppressed because it is too large Load Diff

View File

@ -143,9 +143,7 @@ const char *config_get_microphone_driver_options(void)
return char_list_new_special(STRING_LIST_MICROPHONE_DRIVERS, NULL);
}
bool microphone_driver_find_driver(
void *settings_data,
const char *prefix,
bool microphone_driver_find_driver(void *settings_data, const char *prefix,
bool verbosity_enabled)
{
settings_t *settings = (settings_t*)settings_data;
@ -184,21 +182,23 @@ bool microphone_driver_find_driver(
return true;
}
static void mic_driver_microphone_handle_init(retro_microphone_t *microphone, const retro_microphone_params_t *params)
static void mic_driver_microphone_handle_init(retro_microphone_t *microphone,
const retro_microphone_params_t *params)
{
if (microphone)
{
const settings_t *settings = config_get_ptr();
microphone->microphone_context = NULL;
microphone->flags = MICROPHONE_FLAG_ACTIVE;
microphone->sample_buffer = NULL;
microphone->sample_buffer_length = 0;
const settings_t *settings = config_get_ptr();
unsigned microphone_sample_rate = settings->uints.microphone_sample_rate;
microphone->microphone_context = NULL;
microphone->flags = MICROPHONE_FLAG_ACTIVE;
microphone->sample_buffer = NULL;
microphone->sample_buffer_length = 0;
microphone->requested_params.rate = params ? params->rate : settings->uints.microphone_sample_rate;
microphone->actual_params.rate = 0;
microphone->requested_params.rate = params ? params->rate : microphone_sample_rate;
microphone->actual_params.rate = 0;
/* We don't set the actual parameters until we actually open the mic.
* (Remember, the core can request one before the driver is ready.) */
microphone->effective_params.rate = params ? params->rate : settings->uints.microphone_sample_rate;
microphone->effective_params.rate = params ? params->rate : microphone_sample_rate;
/* We set the effective parameters because
* the frontend has to do what it can
* to give the core what it asks for. */
@ -253,14 +253,6 @@ static void mic_driver_microphone_handle_free(retro_microphone_t *microphone, bo
/* Do NOT free the microphone handle itself! It's allocated statically! */
}
static enum resampler_quality microphone_driver_get_resampler_quality(
settings_t *settings)
{
if (settings)
return (enum resampler_quality)settings->uints.microphone_resampler_quality;
return RESAMPLER_QUALITY_DONTCARE;
}
bool microphone_driver_init_internal(void *settings_data)
{
settings_t *settings = (settings_t*)settings_data;
@ -329,7 +321,7 @@ bool microphone_driver_init_internal(void *settings_data)
else
mic_st->resampler_ident[0] = '\0';
mic_st->resampler_quality = microphone_driver_get_resampler_quality(settings);
mic_st->resampler_quality = (enum resampler_quality)settings->uints.microphone_resampler_quality;
RARCH_LOG("[Microphone]: Initialized microphone driver.\n");
@ -710,15 +702,13 @@ retro_microphone_t *microphone_driver_open_mic(const retro_microphone_params_t *
void *driver_context = mic_st->driver_context;
if (!settings)
{
RARCH_ERR("[Microphone]: Failed to open microphone due to uninitialized config.\n");
return NULL;
}
/* Not checking mic_st->flags because they might not be set yet;
* don't forget, the core can ask for a mic
* before the audio driver is ready to create one. */
if (!settings->bools.microphone_enable)
{ /* Not checking mic_st->flags because they might not be set yet;
* don't forget, the core can ask for a mic
* before the audio driver is ready to create one. */
{
RARCH_DBG("[Microphone]: Refused to open microphone because it's disabled in the settings.\n");
return NULL;
}
@ -729,8 +719,8 @@ retro_microphone_t *microphone_driver_open_mic(const retro_microphone_params_t *
return NULL;
}
if (!mic_driver &&
(string_is_equal(settings->arrays.microphone_driver, "null")
if ( !mic_driver
&& (string_is_equal(settings->arrays.microphone_driver, "null")
|| string_is_empty(settings->arrays.microphone_driver)))
{ /* If the mic driver hasn't been initialized, but it's not going to be... */
RARCH_ERR("[Microphone]: Cannot open microphone as the driver won't be initialized.\n");
@ -756,8 +746,9 @@ retro_microphone_t *microphone_driver_open_mic(const retro_microphone_params_t *
* But the user still wants a handle, so we'll give them one.
*/
mic_driver_microphone_handle_init(&mic_st->microphone, params);
/* If driver_context is NULL, the handle won't have a valid microphone context (but we'll create one later) */
/* If driver_context is NULL, the handle won't have
* a valid microphone context (but we'll create one later) */
if (driver_context)
{
/* If the microphone driver is ready to open a microphone... */

View File

@ -37,7 +37,7 @@ void autosave_lock(void);
**/
void autosave_unlock(void);
bool autosave_init(void);
bool autosave_init(bool compress_files, unsigned autosave_interval);
void autosave_deinit(void);

View File

@ -64,9 +64,9 @@ static void bluetoothctl_scan(void *data)
while (fgets(line, 512, dev_file))
{
size_t len = strlen(line);
if (len > 0 && line[len-1] == '\n')
line[--len] = '\0';
size_t _len = strlen(line);
if (_len > 0 && line[_len - 1] == '\n')
line[--_len] = '\0';
string_list_append(btctl->lines, line, attr);
}
@ -225,8 +225,14 @@ static bool bluetoothctl_remove_device(void *data, unsigned idx)
string_list_free(list);
snprintf(btctl->command, sizeof(btctl->command), "\
echo -e \"disconnect %s\\nremove %s\\n\" | bluetoothctl",
device, device);
bluetoothctl -- disconnect %s",
device);
pclose(popen(btctl->command, "r"));
snprintf(btctl->command, sizeof(btctl->command), "\
bluetoothctl -- remove %s",
device);
pclose(popen(btctl->command, "r"));

View File

@ -27,6 +27,12 @@
#include "camera_driver.h"
#if defined(HAVE_FFMPEG) && defined(HAVE_AVFORMAT) && defined(HAVE_AVCODEC) && \
defined(HAVE_AVDEVICE) && defined(HAVE_AVUTIL) && defined(HAVE_SWSCALE)
/* FFMPEG consists of several libraries, and the camera driver needs most of them */
#define HAVE_FFMPEG_CAMERA
#endif
static void *nullcamera_init(const char *device, uint64_t caps,
unsigned width, unsigned height) { return (void*)-1; }
static void nullcamera_free(void *data) { }
@ -49,11 +55,20 @@ const camera_driver_t *camera_drivers[] = {
#ifdef HAVE_V4L2
&camera_v4l2,
#endif
#ifdef HAVE_PIPEWIRE_STABLE
&camera_pipewire,
#endif
#ifdef EMSCRIPTEN
&camera_rwebcam,
#endif
#ifdef ANDROID
&camera_android,
#endif
#ifdef HAVE_AVF
&camera_avfoundation,
#endif
#ifdef HAVE_FFMPEG_CAMERA
&camera_ffmpeg,
#endif
&camera_null,
NULL,

View File

@ -28,6 +28,8 @@
RETRO_BEGIN_DECLS
struct string_list;
typedef struct camera_driver
{
/* FIXME: params for initialization - queries for resolution,
@ -62,9 +64,11 @@ extern const camera_driver_t *camera_drivers[];
extern camera_driver_t camera_v4l2;
extern camera_driver_t camera_pipewire;
extern camera_driver_t camera_android;
extern camera_driver_t camera_rwebcam;
extern camera_driver_t camera_avfoundation;
extern camera_driver_t camera_ffmpeg;
/**
* config_get_camera_driver_options:

View File

@ -0,0 +1,725 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2025 - Joseph Mattiello
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <TargetConditionals.h>
#include <Foundation/Foundation.h>
#include <AVFoundation/AVFoundation.h>
#include <libretro.h>
#include "../camera/camera_driver.h"
#include "../verbosity.h"
/// For image scaling and color space DSP
#import <Accelerate/Accelerate.h>
#if TARGET_OS_IOS
/// For camera rotation detection
#import <UIKit/UIKit.h>
#endif
// TODO: Add an API to retroarch to allow selection of camera
#ifndef CAMERA_PREFER_FRONTFACING
#define CAMERA_PREFER_FRONTFACING 1 /// Default to front camera
#endif
#ifndef CAMERA_MIRROR_FRONT_CAMERA
#define CAMERA_MIRROR_FRONT_CAMERA 1
#endif
@interface AVCameraManager : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate>
@property (strong, nonatomic) AVCaptureSession *session;
@property (strong, nonatomic) AVCaptureDeviceInput *input;
@property (strong, nonatomic) AVCaptureVideoDataOutput *output;
@property (assign) uint32_t *frameBuffer;
@property (assign) size_t width;
@property (assign) size_t height;
- (bool)setupCameraSession;
@end
@implementation AVCameraManager
+ (AVCameraManager *)sharedInstance {
static AVCameraManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[AVCameraManager alloc] init];
});
return instance;
}
- (void)requestCameraAuthorizationWithCompletion:(void (^)(BOOL granted))completion {
RARCH_LOG("[Camera]: Checking camera authorization status\n");
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
switch (status) {
case AVAuthorizationStatusAuthorized: {
RARCH_LOG("[Camera]: Camera access already authorized\n");
completion(YES);
break;
}
case AVAuthorizationStatusNotDetermined: {
RARCH_LOG("[Camera]: Requesting camera authorization...\n");
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
completionHandler:^(BOOL granted) {
RARCH_LOG("[Camera]: Authorization %s\n", granted ? "granted" : "denied");
completion(granted);
}];
break;
}
case AVAuthorizationStatusDenied: {
RARCH_ERR("[Camera]: Camera access denied by user\n");
completion(NO);
break;
}
case AVAuthorizationStatusRestricted: {
RARCH_ERR("[Camera]: Camera access restricted (parental controls?)\n");
completion(NO);
break;
}
default: {
RARCH_ERR("[Camera]: Unknown authorization status\n");
completion(NO);
break;
}
}
}
- (void)captureOutput:(AVCaptureOutput *)output
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
@autoreleasepool {
if (!self.frameBuffer)
return;
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if (!imageBuffer) {
RARCH_ERR("[Camera]: Failed to get image buffer\n");
return;
}
CVPixelBufferLockBaseAddress(imageBuffer, 0);
size_t sourceWidth = CVPixelBufferGetWidth(imageBuffer);
size_t sourceHeight = CVPixelBufferGetHeight(imageBuffer);
OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer);
#ifdef DEBUG
RARCH_LOG("[Camera]: Processing frame %zux%zu format: %u\n", sourceWidth, sourceHeight, (unsigned int)pixelFormat);
#endif
// Create intermediate buffer for full-size converted image
uint32_t *intermediateBuffer = (uint32_t*)malloc(sourceWidth * sourceHeight * 4);
if (!intermediateBuffer) {
RARCH_ERR("[Camera]: Failed to allocate intermediate buffer\n");
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
vImage_Buffer srcBuffer = {}, intermediateVBuffer = {}, dstBuffer = {};
vImage_Error err = kvImageNoError;
// Setup intermediate buffer
intermediateVBuffer.data = intermediateBuffer;
intermediateVBuffer.width = sourceWidth;
intermediateVBuffer.height = sourceHeight;
intermediateVBuffer.rowBytes = sourceWidth * 4;
// Setup destination buffer
dstBuffer.data = self.frameBuffer;
dstBuffer.width = self.width;
dstBuffer.height = self.height;
dstBuffer.rowBytes = self.width * 4;
// Convert source format to RGBA
switch (pixelFormat) {
case kCVPixelFormatType_32BGRA: {
srcBuffer.data = CVPixelBufferGetBaseAddress(imageBuffer);
srcBuffer.width = sourceWidth;
srcBuffer.height = sourceHeight;
srcBuffer.rowBytes = CVPixelBufferGetBytesPerRow(imageBuffer);
uint8_t permuteMap[4] = {2, 1, 0, 3}; // BGRA -> RGBA
err = vImagePermuteChannels_ARGB8888(&srcBuffer, &intermediateVBuffer, permuteMap, kvImageNoFlags);
break;
}
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange: {
// YUV to RGB conversion
vImage_Buffer srcY = {}, srcCbCr = {};
srcY.data = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
srcY.width = sourceWidth;
srcY.height = sourceHeight;
srcY.rowBytes = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
srcCbCr.data = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);
srcCbCr.width = sourceWidth / 2;
srcCbCr.height = sourceHeight / 2;
srcCbCr.rowBytes = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
vImage_YpCbCrToARGB info;
vImage_YpCbCrPixelRange pixelRange =
(pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) ?
(vImage_YpCbCrPixelRange){16, 128, 235, 240} : // Video range
(vImage_YpCbCrPixelRange){0, 128, 255, 255}; // Full range
err = vImageConvert_YpCbCrToARGB_GenerateConversion(kvImage_YpCbCrToARGBMatrix_ITU_R_601_4,
&pixelRange,
&info,
kvImage420Yp8_CbCr8,
kvImageARGB8888,
kvImageNoFlags);
if (err == kvImageNoError) {
err = vImageConvert_420Yp8_CbCr8ToARGB8888(&srcY,
&srcCbCr,
&intermediateVBuffer,
&info,
NULL,
255,
kvImageNoFlags);
}
break;
}
default:
RARCH_ERR("[Camera]: Unsupported pixel format: %u\n", (unsigned int)pixelFormat);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
if (err != kvImageNoError) {
RARCH_ERR("[Camera]: Error converting color format: %ld\n", err);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
// Determine rotation based on platform and camera type
#if TARGET_OS_OSX
int rotationDegrees = 0; // Default 180-degree rotation for most cases
bool shouldMirror = true;
#else
int rotationDegrees = 180; // Default 180-degree rotation for most cases
bool shouldMirror = false;
/// For camera rotation detection
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (orientation == UIDeviceOrientationPortrait ||
orientation == UIDeviceOrientationPortraitUpsideDown) {
// In portrait mode, adjust rotation based on camera type
if (self.input.device.position == AVCaptureDevicePositionFront) {
rotationDegrees = 270;
#if CAMERA_MIRROR_FRONT_CAMERA
// TODO: Add an API to retroarch to allow for mirroring of front camera
shouldMirror = true; // Mirror front camera
#endif
RARCH_LOG("[Camera]: Using 270-degree rotation with mirroring for front camera in portrait mode\n");
}
}
#endif
// Rotate image
vImage_Buffer rotatedBuffer = {};
rotatedBuffer.data = malloc(sourceWidth * sourceHeight * 4);
if (!rotatedBuffer.data) {
RARCH_ERR("[Camera]: Failed to allocate rotation buffer\n");
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
// Set dimensions based on rotation angle
if (rotationDegrees == 90 || rotationDegrees == 270) {
rotatedBuffer.width = sourceHeight;
rotatedBuffer.height = sourceWidth;
} else {
rotatedBuffer.width = sourceWidth;
rotatedBuffer.height = sourceHeight;
}
rotatedBuffer.rowBytes = rotatedBuffer.width * 4;
const Pixel_8888 backgroundColor = {0, 0, 0, 255};
err = vImageRotate90_ARGB8888(&intermediateVBuffer,
&rotatedBuffer,
rotationDegrees / 90,
backgroundColor,
kvImageNoFlags);
if (err != kvImageNoError) {
RARCH_ERR("[Camera]: Error rotating image: %ld\n", err);
free(rotatedBuffer.data);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
// Mirror the image if needed
if (shouldMirror) {
vImage_Buffer mirroredBuffer = {};
mirroredBuffer.data = malloc(rotatedBuffer.height * rotatedBuffer.rowBytes);
if (!mirroredBuffer.data) {
RARCH_ERR("[Camera]: Failed to allocate mirror buffer\n");
free(rotatedBuffer.data);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
mirroredBuffer.width = rotatedBuffer.width;
mirroredBuffer.height = rotatedBuffer.height;
mirroredBuffer.rowBytes = rotatedBuffer.rowBytes;
err = vImageHorizontalReflect_ARGB8888(&rotatedBuffer, &mirroredBuffer, kvImageNoFlags);
if (err == kvImageNoError) {
// Free rotated buffer and use mirrored buffer for scaling
free(rotatedBuffer.data);
rotatedBuffer = mirroredBuffer;
} else {
RARCH_ERR("[Camera]: Error mirroring image: %ld\n", err);
free(mirroredBuffer.data);
}
}
// Calculate aspect fill scaling
float sourceAspect = (float)rotatedBuffer.width / rotatedBuffer.height;
float targetAspect = (float)self.width / self.height;
vImage_Buffer scaledBuffer = {};
size_t scaledWidth, scaledHeight;
if (sourceAspect > targetAspect) {
// Source is wider - scale to match height
scaledHeight = self.height;
scaledWidth = (size_t)(self.height * sourceAspect);
} else {
// Source is taller - scale to match width
scaledWidth = self.width;
scaledHeight = (size_t)(self.width / sourceAspect);
}
RARCH_LOG("[Camera]: Aspect fill scaling from %zux%zu to %zux%zu\n",
rotatedBuffer.width, rotatedBuffer.height, scaledWidth, scaledHeight);
scaledBuffer.data = malloc(scaledWidth * scaledHeight * 4);
if (!scaledBuffer.data) {
RARCH_ERR("[Camera]: Failed to allocate scaled buffer\n");
free(rotatedBuffer.data);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
scaledBuffer.width = scaledWidth;
scaledBuffer.height = scaledHeight;
scaledBuffer.rowBytes = scaledWidth * 4;
// Scale maintaining aspect ratio
err = vImageScale_ARGB8888(&rotatedBuffer, &scaledBuffer, NULL, kvImageHighQualityResampling);
if (err != kvImageNoError) {
RARCH_ERR("[Camera]: Error scaling image: %ld\n", err);
free(scaledBuffer.data);
free(rotatedBuffer.data);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return;
}
// Center crop the scaled image into the destination buffer
size_t xOffset = (scaledWidth > self.width) ? (scaledWidth - self.width) / 2 : 0;
size_t yOffset = (scaledHeight > self.height) ? (scaledHeight - self.height) / 2 : 0;
// Copy the centered portion to the destination buffer
uint32_t *srcPtr = (uint32_t *)scaledBuffer.data;
uint32_t *dstPtr = (uint32_t *)self.frameBuffer;
for (size_t y = 0; y < self.height; y++) {
memcpy(dstPtr + y * self.width,
srcPtr + (y + yOffset) * scaledWidth + xOffset,
self.width * 4);
}
// Clean up
free(scaledBuffer.data);
free(rotatedBuffer.data);
free(intermediateBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
} // End of autorelease pool
}
- (AVCaptureDevice *)selectCameraDevice {
RARCH_LOG("[Camera]: Selecting camera device\n");
NSArray<AVCaptureDevice *> *devices;
#if TARGET_OS_OSX
// On macOS, use default discovery method
// Could probably due the same as iOS but need to test.
devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
#else
// On iOS/tvOS use modern discovery session
NSArray<AVCaptureDeviceType> *deviceTypes;
if (@available(iOS 17.0, *)) {
deviceTypes = @[
AVCaptureDeviceTypeExternal,
AVCaptureDeviceTypeBuiltInWideAngleCamera,
AVCaptureDeviceTypeBuiltInTelephotoCamera,
AVCaptureDeviceTypeBuiltInUltraWideCamera,
// AVCaptureDeviceTypeBuiltInDualCamera,
// AVCaptureDeviceTypeBuiltInDualWideCamera,
// AVCaptureDeviceTypeBuiltInTripleCamera,
// AVCaptureDeviceTypeBuiltInTrueDepthCamera,
// AVCaptureDeviceTypeBuiltInLiDARDepthCamera,
// AVCaptureDeviceTypeContinuityCamera,
];
} else {
deviceTypes = @[
AVCaptureDeviceTypeBuiltInWideAngleCamera,
AVCaptureDeviceTypeBuiltInTelephotoCamera,
AVCaptureDeviceTypeBuiltInUltraWideCamera,
// AVCaptureDeviceTypeBuiltInDualCamera,
// AVCaptureDeviceTypeBuiltInDualWideCamera,
// AVCaptureDeviceTypeBuiltInTripleCamera,
// AVCaptureDeviceTypeBuiltInTrueDepthCamera,
// AVCaptureDeviceTypeBuiltInLiDARDepthCamera,
// AVCaptureDeviceTypeContinuityCamera,
];
}
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
discoverySessionWithDeviceTypes:deviceTypes
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionUnspecified];
devices = discoverySession.devices;
#endif
if (devices.count == 0) {
RARCH_ERR("[Camera]: No camera devices found\n");
return nil;
}
// Log available devices
for (AVCaptureDevice *device in devices) {
RARCH_LOG("[Camera]: Found device: %s - Position: %d\n",
[device.localizedName UTF8String],
(int)device.position);
}
#if TARGET_OS_OSX
// macOS: Just use the first available camera if only one exists
if (devices.count == 1) {
RARCH_LOG("[Camera]: Using only available camera: %s\n",
[devices.firstObject.localizedName UTF8String]);
return devices.firstObject;
}
// Try to match by name for built-in cameras
for (AVCaptureDevice *device in devices) {
BOOL isFrontFacing = [device.localizedName containsString:@"FaceTime"] ||
[device.localizedName containsString:@"Front"];
if (CAMERA_PREFER_FRONTFACING == isFrontFacing) {
RARCH_LOG("[Camera]: Selected macOS camera: %s\n",
[device.localizedName UTF8String]);
return device;
}
}
#else
// iOS: Use position property
AVCaptureDevicePosition preferredPosition = CAMERA_PREFER_FRONTFACING ?
AVCaptureDevicePositionFront : AVCaptureDevicePositionBack;
// Try to find preferred camera
for (AVCaptureDevice *device in devices) {
if (device.position == preferredPosition) {
RARCH_LOG("[Camera]: Selected iOS camera position: %d\n",
(int)preferredPosition);
return device;
}
}
#endif
// Fallback to first available camera
RARCH_LOG("[Camera]: Using fallback camera: %s\n",
[devices.firstObject.localizedName UTF8String]);
return devices.firstObject;
}
- (bool)setupCameraSession {
// Initialize capture session
self.session = [[AVCaptureSession alloc] init];
// Get camera device
AVCaptureDevice *device = [self selectCameraDevice];
if (!device) {
RARCH_ERR("[Camera]: No camera device found\n");
return false;
}
// Create device input
NSError *error = nil;
self.input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (error) {
RARCH_ERR("[Camera]: Failed to create device input: %s\n",
[error.localizedDescription UTF8String]);
return false;
}
if ([self.session canAddInput:self.input]) {
[self.session addInput:self.input];
RARCH_LOG("[Camera]: Added camera input to session\n");
}
// Create and configure video output
self.output = [[AVCaptureVideoDataOutput alloc] init];
self.output.videoSettings = @{
(NSString*)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)
};
[self.output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
if ([self.session canAddOutput:self.output]) {
[self.session addOutput:self.output];
RARCH_LOG("[Camera]: Added video output to session\n");
}
return true;
}
@end
typedef struct
{
AVCameraManager *manager;
unsigned width;
unsigned height;
} avfoundation_t;
static void generateColorBars(uint32_t *buffer, size_t width, size_t height) {
const uint32_t colors[] = {
0xFFFFFFFF, // White
0xFFFFFF00, // Yellow
0xFF00FFFF, // Cyan
0xFF00FF00, // Green
0xFFFF00FF, // Magenta
0xFFFF0000, // Red
0xFF0000FF, // Blue
0xFF000000 // Black
};
size_t barWidth = width / 8;
for (size_t y = 0; y < height; y++) {
for (size_t x = 0; x < width; x++) {
size_t colorIndex = x / barWidth;
buffer[y * width + x] = colors[colorIndex];
}
}
}
static void *avfoundation_init(const char *device, uint64_t caps,
unsigned width, unsigned height)
{
RARCH_LOG("[Camera]: Initializing AVFoundation camera %ux%u\n", width, height);
avfoundation_t *avf = (avfoundation_t*)calloc(1, sizeof(avfoundation_t));
if (!avf) {
RARCH_ERR("[Camera]: Failed to allocate avfoundation_t\n");
return NULL;
}
avf->manager = [AVCameraManager sharedInstance];
avf->width = width;
avf->height = height;
avf->manager.width = width;
avf->manager.height = height;
// Check if we're on the main thread
if ([NSThread isMainThread]) {
RARCH_LOG("[Camera]: Initializing on main thread\n");
// Direct initialization on main thread
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (status != AVAuthorizationStatusAuthorized) {
RARCH_ERR("[Camera]: Camera access not authorized (status: %d)\n", (int)status);
free(avf);
return;
}
}];
} else {
RARCH_LOG("[Camera]: Initializing on background thread\n");
// Use dispatch_sync to run authorization check on main thread
__block AVAuthorizationStatus status;
dispatch_sync(dispatch_get_main_queue(), ^{
status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
});
if (status != AVAuthorizationStatusAuthorized) {
RARCH_ERR("[Camera]: Camera access not authorized (status: %d)\n", (int)status);
free(avf);
return NULL;
}
}
// Allocate frame buffer
avf->manager.frameBuffer = (uint32_t*)calloc(width * height, sizeof(uint32_t));
if (!avf->manager.frameBuffer) {
RARCH_ERR("[Camera]: Failed to allocate frame buffer\n");
free(avf);
return NULL;
}
// Initialize capture session on main thread
__block bool setupSuccess = false;
if ([NSThread isMainThread]) {
@autoreleasepool {
setupSuccess = [avf->manager setupCameraSession];
if (setupSuccess) {
[avf->manager.session startRunning];
RARCH_LOG("[Camera]: Started camera session\n");
}
}
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
@autoreleasepool {
setupSuccess = [avf->manager setupCameraSession];
if (setupSuccess) {
[avf->manager.session startRunning];
RARCH_LOG("[Camera]: Started camera session\n");
}
}
});
}
if (!setupSuccess) {
RARCH_ERR("[Camera]: Failed to setup camera\n");
free(avf->manager.frameBuffer);
free(avf);
return NULL;
}
// Add a check to verify the session is actually running
if (!avf->manager.session.isRunning) {
RARCH_ERR("[Camera]: Failed to start camera session\n");
free(avf->manager.frameBuffer);
free(avf);
return NULL;
}
RARCH_LOG("[Camera]: AVFoundation camera initialized and started successfully\n");
return avf;
}
static void avfoundation_free(void *data)
{
avfoundation_t *avf = (avfoundation_t*)data;
if (!avf)
return;
RARCH_LOG("[Camera]: Freeing AVFoundation camera\n");
if (avf->manager.session) {
[avf->manager.session stopRunning];
}
if (avf->manager.frameBuffer) {
free(avf->manager.frameBuffer);
avf->manager.frameBuffer = NULL;
}
free(avf);
RARCH_LOG("[Camera]: AVFoundation camera freed\n");
}
static bool avfoundation_start(void *data)
{
avfoundation_t *avf = (avfoundation_t*)data;
if (!avf || !avf->manager.session) {
RARCH_ERR("[Camera]: Cannot start - invalid data\n");
return false;
}
RARCH_LOG("[Camera]: Starting AVFoundation camera\n");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[avf->manager.session startRunning];
RARCH_LOG("[Camera]: Camera session started on background thread\n");
});
// Give the session a moment to start
usleep(100000); // 100ms
bool isRunning = avf->manager.session.isRunning;
RARCH_LOG("[Camera]: Camera session running: %s\n", isRunning ? "YES" : "NO");
return isRunning;
}
static void avfoundation_stop(void *data)
{
avfoundation_t *avf = (avfoundation_t*)data;
if (!avf || !avf->manager.session)
return;
RARCH_LOG("[Camera]: Stopping AVFoundation camera\n");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[avf->manager.session stopRunning];
RARCH_LOG("[Camera]: Camera session stopped on background thread\n");
});
}
static bool avfoundation_poll(void *data,
retro_camera_frame_raw_framebuffer_t frame_raw_cb,
retro_camera_frame_opengl_texture_t frame_gl_cb)
{
avfoundation_t *avf = (avfoundation_t*)data;
if (!avf || !frame_raw_cb) {
RARCH_ERR("[Camera]: Cannot poll - invalid data or callback\n");
return false;
}
if (!avf->manager.session.isRunning) {
RARCH_LOG("[Camera]: Camera not running, generating color bars\n");
uint32_t *tempBuffer = (uint32_t*)calloc(avf->width * avf->height, sizeof(uint32_t));
if (tempBuffer) {
generateColorBars(tempBuffer, avf->width, avf->height);
frame_raw_cb(tempBuffer, avf->width, avf->height, avf->width * 4);
free(tempBuffer);
return true;
}
return false;
}
#ifdef DEBUG
RARCH_LOG("[Camera]: Delivering camera frame\n");
#endif
frame_raw_cb(avf->manager.frameBuffer, avf->width, avf->height, avf->width * 4);
return true;
}
camera_driver_t camera_avfoundation = {
avfoundation_init,
avfoundation_free,
avfoundation_start,
avfoundation_stop,
avfoundation_poll,
"avfoundation"
};

625
camera/drivers/ffmpeg.c Normal file
View File

@ -0,0 +1,625 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2023 - Hans-Kristian Arntzen
* Copyright (C) 2023 - Jesse Talavera-Greenberg
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <libretro.h>
#include <libavdevice/avdevice.h>
#include "../camera_driver.h"
#include "lists/string_list.h"
#include "verbosity.h"
#include <configuration.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <memalign.h>
#include <retro_assert.h>
#include <rthreads/rthreads.h>
#include <string/stdstring.h>
#ifdef ANDROID
#define FFMPEG_CAMERA_DEFAULT_BACKEND "android_camera"
#elif defined(__linux__)
#define FFMPEG_CAMERA_DEFAULT_BACKEND "v4l2"
#elif defined(__APPLE__)
#define FFMPEG_CAMERA_DEFAULT_BACKEND "avfoundation"
#elif defined(__WIN32__)
#define FFMPEG_CAMERA_DEFAULT_BACKEND "dshow"
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__)
#define FFMPEG_CAMERA_DEFAULT_BACKEND "bktr"
#endif
typedef struct ffmpeg_camera
{
sthread_t *poll_thread;
AVFormatContext *format_context;
AVCodecContext *decoder_context;
const AVCodec *decoder;
const AVInputFormat *input_format; /* owned by ffmpeg, don't free it */
AVDictionary *options;
AVPacket *packet;
AVFrame *camera_frame;
unsigned requested_width;
unsigned requested_height;
uint8_t *target_planes[4];
int target_linesizes[4];
unsigned target_width;
unsigned target_height;
struct SwsContext *scale_context;
/* "name" for the camera device.
* Not just the reported name, there may be a bit of extra syntax.
* See https://ffmpeg.org/ffmpeg-devices.html#Input-Devices for details. */
char url[512];
uint8_t *target_buffers[2];
size_t target_buffer_length;
slock_t *target_buffer_lock;
volatile bool done;
uint8_t *active_buffer;
} ffmpeg_camera_t;
static void ffmpeg_camera_free(void *data);
static int ffmpeg_camera_get_initial_options(
const AVInputFormat *backend,
AVDictionary **options,
uint64_t caps,
unsigned width,
unsigned height
)
{
int result = 0;
char dimensions[128];
if (width != 0 && height != 0)
{ /* If the core is letting the frontend pick the size... */
snprintf(dimensions, sizeof(dimensions), "%ux%u", width, height);
result = av_dict_set(options, "video_size", dimensions, 0);
if (result < 0)
{
RARCH_ERR("[FFMPEG]: Failed to set option: %s\n", av_err2str(result));
goto error;
}
}
/* I wanted to list supported formats and pick the most appropriate size
* if the requested size isn't available,
* but ffmpeg doesn't seem to offer a way to do that.
*/
if (!options)
{
RARCH_DBG("[FFMPEG]: No options set, not allocating a dict (this isn't an error)");
}
return result;
error:
av_dict_free(options);
return result;
}
/* Device URL syntax varies by backend.
* See https://ffmpeg.org/ffmpeg-devices.html for details. */
static void ffmpeg_camera_get_source_url(ffmpeg_camera_t *ffmpeg, const AVDeviceInfo *device)
{
#ifdef __WIN32__
if (string_is_equal(ffmpeg->input_format->name, "dshow"))
{
snprintf(ffmpeg->url, sizeof(ffmpeg->url), "video=%s", device->device_description);
return;
}
#elif defined(__APPLE__)
if (string_is_equal(ffmpeg->input_format->name, "avfoundation"))
{
/* we only want video, not audio */
snprintf(ffmpeg->url, sizeof(ffmpeg->url), "%s:none", device->device_description);
return;
}
#endif
/* Other backends that support listing available sources use the name as-is;
* some advanced backends have extra syntax (e.g. gdigrab)
* but they don't support listing available sources,
* so players will have to enter them manually.
*/
strlcpy(ffmpeg->url, device->device_description, sizeof(ffmpeg->url));
}
static int ffmpeg_camera_open_device(ffmpeg_camera_t *ffmpeg)
{
AVDictionaryEntry *e = NULL;
AVDictionary *options = NULL;
int result = ffmpeg->options ? av_dict_copy(&options, ffmpeg->options, 0) : 0;
/* copy the options dict so that other steps in start() can use it,
* as avformat_open_input clears it and adds unrecognized settings */
if (result < 0)
{
RARCH_ERR("[FFMPEG]: Failed to copy options: %s\n", av_err2str(result));
goto done;
}
result = avformat_open_input(&ffmpeg->format_context, ffmpeg->url, ffmpeg->input_format, &options);
if (result < 0)
{
RARCH_WARN("[FFMPEG]: Failed to open video input device \"%s\": %s\n", ffmpeg->url, av_err2str(result));
if (ffmpeg->options)
{ /* If we're not already requesting the default format... */
result = avformat_open_input(&ffmpeg->format_context, ffmpeg->url, ffmpeg->input_format, NULL);
if (result < 0)
{
RARCH_ERR("[FFMPEG]: Failed to open the same device in its default format: %s\n", av_err2str(result));
goto done;
}
}
}
done:
if (options)
{
const AVDictionaryEntry prev;
while ((e = av_dict_get(options, "", &prev, AV_DICT_IGNORE_SUFFIX))) {
/* av_dict_iterate isn't always available, so we use av_dict_get's legacy behavior instead */
RARCH_WARN("[FFMPEG]: Unrecognized option on video input device: %s=%s\n", e->key, e->value);
}
}
av_dict_free(&options); /* noop if NULL */
if (result == 0)
{
RARCH_LOG("[FFMPEG]: Opened video input device \"%s\".\n", ffmpeg->url);
}
return result;
}
static void *ffmpeg_camera_init(const char *device, uint64_t caps, unsigned width, unsigned height)
{
ffmpeg_camera_t *ffmpeg = NULL;
AVDeviceInfoList *device_list = NULL;
int result = 0;
int num_sources = 0;
if ((caps & (UINT64_C(1) << RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER)) == 0)
{ /* If the core didn't ask for raw framebuffers... */
RARCH_ERR("[FFMPEG]: Camera driver only supports raw framebuffer output.\n");
return NULL;
}
ffmpeg = (ffmpeg_camera_t*)calloc(1, sizeof(*ffmpeg));
if (!ffmpeg)
{
RARCH_ERR("[FFMPEG]: Failed to allocate memory for camera driver.\n");
return NULL;
}
ffmpeg->requested_width = width;
ffmpeg->requested_height = height;
avdevice_register_all();
RARCH_LOG("[FFMPEG]: Initialized libavdevice.\n");
ffmpeg->input_format = av_find_input_format(FFMPEG_CAMERA_DEFAULT_BACKEND);
if (!ffmpeg->input_format)
{
RARCH_ERR("[FFMPEG]: No suitable video input backend found.\n");
goto error;
}
RARCH_LOG("[FFMPEG]: Using camera backend: %s (%s, flags=0x%x)\n", ffmpeg->input_format->name, ffmpeg->input_format->long_name, ffmpeg->input_format->flags);
result = ffmpeg_camera_get_initial_options(ffmpeg->input_format, &ffmpeg->options, caps, width, height);
if (result < 0)
{
RARCH_ERR("[FFMPEG]: Failed to get initial options: %s\n", av_err2str(result));
goto error;
}
num_sources = avdevice_list_input_sources(ffmpeg->input_format, NULL, ffmpeg->options, &device_list);
if (num_sources == 0)
{
RARCH_ERR("[FFMPEG]: No video input sources found.\n");
goto error;
}
if (num_sources < 0)
{
RARCH_ERR("[FFMPEG]: Failed to list video input sources: %s\n", av_err2str(num_sources));
goto error;
}
ffmpeg_camera_get_source_url(ffmpeg, device_list->devices[0]);
RARCH_LOG("[FFMPEG]: Using video input device: %s (%s, flags=0x%x)\n", ffmpeg->input_format->name, ffmpeg->input_format->long_name, ffmpeg->input_format->flags);
avdevice_free_list_devices(&device_list);
return ffmpeg;
error:
avdevice_free_list_devices(&device_list);
ffmpeg_camera_free(ffmpeg);
return NULL;
}
static void ffmpeg_camera_stop(void *data);
static void ffmpeg_camera_free(void *data)
{
ffmpeg_camera_t *ffmpeg = data;
if (!ffmpeg)
return;
ffmpeg_camera_stop(ffmpeg);
av_dict_free(&ffmpeg->options);
free(ffmpeg);
}
static void ffmpeg_camera_poll_thread(void *data);
static bool ffmpeg_camera_start(void *data)
{
ffmpeg_camera_t *ffmpeg = data;
int result = 0;
AVStream *stream = NULL;
AVDictionary *options = NULL;
const AVDictionaryEntry *e = NULL;
const AVDictionaryEntry prev;
int target_buffer_length = 0;
if (ffmpeg->format_context)
{ // TODO: Check the actual format context, not just the pointer
RARCH_LOG("[FFMPEG]: Camera %s is already started, no action needed.\n", ffmpeg->format_context->url);
return true;
}
result = ffmpeg_camera_open_device(ffmpeg);
if (result < 0)
goto error;
result = av_dict_copy(&options, ffmpeg->options, 0);
if (result < 0)
{
RARCH_ERR("[FFMPEG]: Failed to copy options: %s\n", av_err2str(result));
goto error;
}
result = avformat_find_stream_info(ffmpeg->format_context, &options);
if (result < 0)
{
RARCH_ERR("[FFMPEG]: Failed to find stream info: %s\n", av_err2str(result));
goto error;
}
while ((e = av_dict_get(options, "", &prev, AV_DICT_IGNORE_SUFFIX))) {
RARCH_WARN("[FFMPEG]: Unrecognized option on video input device: %s=%s\n", e->key, e->value);
}
result = av_find_best_stream(ffmpeg->format_context, AVMEDIA_TYPE_VIDEO, -1, -1, &ffmpeg->decoder, 0);
if (result < 0)
{
RARCH_ERR("[FFMPEG]: Failed to find video stream: %s\n", av_err2str(result));
goto error;
}
stream = ffmpeg->format_context->streams[result];
RARCH_LOG("[FFMPEG]: Using video stream #%d with decoder \"%s\" (%s).\n", result, ffmpeg->decoder->name, ffmpeg->decoder->long_name);
ffmpeg->decoder_context = avcodec_alloc_context3(ffmpeg->decoder);
if (!ffmpeg->decoder_context)
{
RARCH_ERR("[FFMPEG]: Failed to allocate decoder context.\n");
goto error;
}
result = avcodec_parameters_to_context(ffmpeg->decoder_context, stream->codecpar);
if (result < 0)
{
RARCH_ERR("[FFMPEG]: Failed to copy codec parameters to decoder context: %s\n", av_err2str(result));
goto error;
}
if (!sws_isSupportedInput(ffmpeg->decoder_context->pix_fmt))
{
RARCH_ERR("[FFMPEG]: Unsupported scaler input pixel format: %s\n", av_get_pix_fmt_name(ffmpeg->decoder_context->pix_fmt));
goto error;
}
result = av_dict_copy(&ffmpeg->options, options, 0);
if (result < 0)
{
RARCH_ERR("[FFMPEG]: Failed to copy options: %s\n", av_err2str(result));
goto error;
}
result = avcodec_open2(ffmpeg->decoder_context, ffmpeg->decoder, &options);
if (result < 0)
{
RARCH_ERR("[FFMPEG]: Failed to open decoder: %s\n", av_err2str(result));
goto error;
}
while ((e = av_dict_get(options, "", &prev, AV_DICT_IGNORE_SUFFIX))) {
RARCH_WARN("[FFMPEG]: Unrecognized option on video input device: %s=%s\n", e->key, e->value);
}
av_dict_free(&options);
ffmpeg->packet = av_packet_alloc();
if (!ffmpeg->packet)
{
RARCH_ERR("[FFMPEG]: Failed to allocate packet.\n");
goto error;
}
ffmpeg->camera_frame = av_frame_alloc();
if (!ffmpeg->camera_frame)
{
RARCH_ERR("[FFMPEG]: Failed to allocate camera frame.\n");
goto error;
}
ffmpeg->target_width = ffmpeg->requested_width ? ffmpeg->requested_width : (unsigned)ffmpeg->decoder_context->width;
ffmpeg->target_height = ffmpeg->requested_height ? ffmpeg->requested_height : (unsigned)ffmpeg->decoder_context->height;
target_buffer_length = av_image_alloc(
ffmpeg->target_planes,
ffmpeg->target_linesizes,
ffmpeg->target_width,
ffmpeg->target_height,
AV_PIX_FMT_BGRA,
1
);
if (target_buffer_length < 0)
{
RARCH_ERR("[FFMPEG]: Failed to allocate target plane: %s\n", av_err2str(target_buffer_length));
goto error;
}
/* target buffer aligned to 4 bytes because it's exposed to the core as a uint32_t[] */
ffmpeg->target_buffer_length = target_buffer_length;
ffmpeg->target_buffers[0] = memalign_alloc(4, target_buffer_length);
ffmpeg->target_buffers[1] = memalign_alloc(4, target_buffer_length);
ffmpeg->active_buffer = ffmpeg->target_buffers[0];
if (!ffmpeg->target_buffers[0] || !ffmpeg->target_buffers[1])
{
RARCH_ERR("[FFMPEG]: Failed to allocate target %d-byte buffer for %dx%d %s-formatted video data.\n",
target_buffer_length,
ffmpeg->decoder_context->width,
ffmpeg->decoder_context->height,
av_get_pix_fmt_name(AV_PIX_FMT_BGRA)
);
goto error;
}
RARCH_LOG("[FFMPEG]: Allocated %d bytes for %dx%d %s-formatted video data.\n",
target_buffer_length,
ffmpeg->decoder_context->width,
ffmpeg->decoder_context->height,
av_get_pix_fmt_name(ffmpeg->decoder_context->pix_fmt)
);
ffmpeg->scale_context = sws_getContext(
ffmpeg->decoder_context->width,
ffmpeg->decoder_context->height,
ffmpeg->decoder_context->pix_fmt,
ffmpeg->target_width,
ffmpeg->target_height,
AV_PIX_FMT_BGRA,
SWS_BILINEAR,
NULL, NULL, NULL
);
if (!ffmpeg->scale_context)
{
RARCH_ERR("[FFMPEG]: Failed to create scale context.\n");
goto error;
}
ffmpeg->target_buffer_lock = slock_new();
if (!ffmpeg->target_buffer_lock)
{
RARCH_ERR("[FFMPEG]: Failed to create target buffer lock.\n");
goto error;
}
ffmpeg->poll_thread = sthread_create(ffmpeg_camera_poll_thread, ffmpeg);
if (!ffmpeg->poll_thread)
{
RARCH_ERR("[FFMPEG]: Failed to create poll thread.\n");
goto error;
}
return true;
error:
ffmpeg_camera_stop(ffmpeg);
return false;
}
static void ffmpeg_camera_stop(void *data)
{
ffmpeg_camera_t *ffmpeg = data;
if (!ffmpeg->format_context)
{
RARCH_LOG("[FFMPEG]: Camera %s is already stopped, no flush needed.\n", ffmpeg->url);
}
else
{
int result = avcodec_send_packet(ffmpeg->decoder_context, NULL);
if (result < 0)
{
RARCH_ERR("[FFMPEG]: Failed to flush decoder: %s\n", av_err2str(result));
}
}
/* these functions are noops for NULL pointers */
ffmpeg->done = true;
sthread_join(ffmpeg->poll_thread); /* wait for the thread to finish, then free it */
ffmpeg->poll_thread = NULL;
slock_free(ffmpeg->target_buffer_lock);
ffmpeg->target_buffer_lock = NULL;
sws_freeContext(ffmpeg->scale_context);
ffmpeg->scale_context = NULL;
memalign_free(ffmpeg->target_buffers[0]);
memalign_free(ffmpeg->target_buffers[1]);
ffmpeg->active_buffer = NULL;
ffmpeg->target_buffers[0] = NULL;
ffmpeg->target_buffers[1] = NULL;
ffmpeg->target_buffer_length = 0;
ffmpeg->target_width = 0;
ffmpeg->target_height = 0;
av_frame_free(&ffmpeg->camera_frame);
av_freep(&ffmpeg->target_buffers[0]);
memset(ffmpeg->target_linesizes, 0, sizeof(ffmpeg->target_linesizes));
av_packet_free(&ffmpeg->packet);
avcodec_free_context(&ffmpeg->decoder_context);
avformat_close_input(&ffmpeg->format_context);
RARCH_LOG("[FFMPEG]: Closed video input device %s.\n", ffmpeg->url);
}
static void ffmpeg_camera_poll_thread(void *data)
{
ffmpeg_camera_t *ffmpeg = data;
if (!ffmpeg)
return;
while (!ffmpeg->done)
{
int result = av_read_frame(ffmpeg->format_context, ffmpeg->packet);
if (result < 0)
{ /* Read the raw data from the camera. If that fails... */
RARCH_ERR("[FFMPEG]: Failed to read frame: %s\n", av_err2str(result));
continue;
}
result = avcodec_send_packet(ffmpeg->decoder_context, ffmpeg->packet);
if (result < 0)
{ /* Send the raw data to the decoder. If that fails... */
if (result == AVERROR_EOF)
{
RARCH_DBG("[FFMPEG]: Video capture device closed\n");
}
else
{
RARCH_ERR("[FFMPEG]: Failed to send packet to decoder: %s\n", av_err2str(result));
}
goto done_loop;
}
/* video streams consist of exactly one frame per packet */
result = avcodec_receive_frame(ffmpeg->decoder_context, ffmpeg->camera_frame);
if (result < 0)
{ /* Send the decoded data to the camera frame. If that fails... */
if (!(result == AVERROR_EOF || result == AVERROR(EAGAIN)))
{ /* these error codes mean no new frame, but not necessarily a problem */
RARCH_ERR("[FFMPEG]: Failed to receive camera frame from decoder: %s\n", av_err2str(result));
}
goto done_loop;
}
retro_assert(ffmpeg->decoder->type == AVMEDIA_TYPE_VIDEO);
/* sws_scale_frame is tidier but isn't as widely available */
result = sws_scale(
ffmpeg->scale_context,
(const uint8_t *const *)ffmpeg->camera_frame->data,
ffmpeg->camera_frame->linesize,
0,
ffmpeg->camera_frame->height,
ffmpeg->target_planes,
ffmpeg->target_linesizes
);
if (result < 0)
{ /* Scale and convert the frame to the target format. If that fails... */
RARCH_ERR("[FFMPEG]: Failed to scale frame: %s\n", av_err2str(result));
goto done_loop;
}
slock_lock(ffmpeg->target_buffer_lock);
result = av_image_copy_to_buffer(
ffmpeg->active_buffer,
ffmpeg->target_buffer_length,
(const uint8_t *const *)ffmpeg->target_planes,
ffmpeg->target_linesizes,
AV_PIX_FMT_BGRA,
ffmpeg->target_width,
ffmpeg->target_height,
1
);
if (result >= 0) {
ffmpeg->active_buffer = ffmpeg->active_buffer == ffmpeg->target_buffers[0] ? ffmpeg->target_buffers[1] : ffmpeg->target_buffers[0];
}
slock_unlock(ffmpeg->target_buffer_lock);
if (result < 0)
{
RARCH_ERR("[FFMPEG]: Failed to copy frame to buffer: %s\n", av_err2str(result));
goto done_loop;
}
done_loop:
/* must be called when we're done with it */
av_frame_unref(ffmpeg->camera_frame);
}
/* every operation in this function needs this packet */
av_packet_unref(ffmpeg->packet);
}
static bool ffmpeg_camera_poll(
void *data,
retro_camera_frame_raw_framebuffer_t frame_raw_cb,
retro_camera_frame_opengl_texture_t frame_gl_cb)
{
ffmpeg_camera_t *ffmpeg = data;
if (!ffmpeg->format_context)
{
RARCH_ERR("[FFMPEG]: Camera is not started, cannot poll.\n");
return false;
}
if (!frame_raw_cb)
{
RARCH_ERR("[FFMPEG]: No callback provided, cannot poll.\n");
return false;
}
slock_lock(ffmpeg->target_buffer_lock);
frame_raw_cb((uint32_t*)ffmpeg->active_buffer, ffmpeg->target_width, ffmpeg->target_height, ffmpeg->target_linesizes[0]);
slock_unlock(ffmpeg->target_buffer_lock);
return true;
}
camera_driver_t camera_ffmpeg = {
ffmpeg_camera_init,
ffmpeg_camera_free,
ffmpeg_camera_start,
ffmpeg_camera_stop,
ffmpeg_camera_poll,
"ffmpeg",
};

466
camera/drivers/pipewire.c Normal file
View File

@ -0,0 +1,466 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2025 - Viachaslau Khalikin
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <spa/utils/result.h>
#include <spa/param/video/format-utils.h>
#include <spa/param/tag-utils.h>
#include <spa/param/props.h>
#include <spa/param/latency-utils.h>
#include <spa/debug/format.h>
#include <spa/debug/pod.h>
#include <pipewire/pipewire.h>
#include <gfx/scaler/scaler.h>
#include <gfx/video_frame.h>
#include <libretro.h>
#include <retro_assert.h>
#include <retro_miscellaneous.h>
#include "../camera_driver.h"
#include "../../audio/common/pipewire.h"
#include "../../verbosity.h"
/* TODO/FIXME: detect size */
#define WIDTH 640
#define HEIGHT 480
#define MAX_BUFFERS 64
typedef struct pipewire_camera
{
pipewire_core_t *pw;
struct pw_stream *stream;
struct spa_hook stream_listener;
struct scaler_ctx scaler;
uint32_t *buffer_output;
struct spa_io_position *position;
struct spa_video_info format;
struct spa_rectangle size;
} pipewire_camera_t;
static struct
{
uint32_t format;
uint32_t id;
} scaler_video_formats[] = {
{ SCALER_FMT_ARGB8888, SPA_VIDEO_FORMAT_ARGB },
{ SCALER_FMT_ABGR8888, SPA_VIDEO_FORMAT_ABGR },
{ SCALER_FMT_0RGB1555, SPA_VIDEO_FORMAT_UNKNOWN },
{ SCALER_FMT_RGB565, SPA_VIDEO_FORMAT_UNKNOWN },
{ SCALER_FMT_BGR24, SPA_VIDEO_FORMAT_BGR },
{ SCALER_FMT_YUYV, SPA_VIDEO_FORMAT_YUY2 },
{ SCALER_FMT_RGBA4444, SPA_VIDEO_FORMAT_UNKNOWN },
};
#if 0
{
SPA_FOR_EACH_ELEMENT_VAR(scaler_video_formats, f)
{
if (f->format == format)
return f->id;
}
return SPA_VIDEO_FORMAT_UNKNOWN;
}
#endif
static uint32_t id_to_scaler_format(uint32_t id)
{
SPA_FOR_EACH_ELEMENT_VAR(scaler_video_formats, f)
{
if (f->id == id)
return f->format;
}
return SCALER_FMT_ARGB8888;
}
static int build_format(struct spa_pod_builder *b, const struct spa_pod **params,
uint32_t width, uint32_t height)
{
struct spa_pod_frame frame[2];
/* make an object of type SPA_TYPE_OBJECT_Format and id SPA_PARAM_EnumFormat.
* The object type is important because it defines the properties that are
* acceptable. The id gives more context about what the object is meant to
* contain. In this case we enumerate supported formats. */
spa_pod_builder_push_object(b, &frame[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
/* add media type and media subtype properties */
spa_pod_builder_prop(b, SPA_FORMAT_mediaType, 0);
spa_pod_builder_id(b, SPA_MEDIA_TYPE_video);
spa_pod_builder_prop(b, SPA_FORMAT_mediaSubtype, 0);
spa_pod_builder_id(b, SPA_MEDIA_SUBTYPE_raw);
/* build an enumeration of formats */
spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_format, 0);
spa_pod_builder_push_choice(b, &frame[1], SPA_CHOICE_Enum, 0);
spa_pod_builder_id(b, SPA_VIDEO_FORMAT_YUY2); /* default */
SPA_FOR_EACH_ELEMENT_VAR(scaler_video_formats, f)
{
uint32_t id = f->id;
if (id != SPA_VIDEO_FORMAT_UNKNOWN)
spa_pod_builder_id(b, id); /* alternative */
}
spa_pod_builder_pop(b, &frame[1]);
/* add size and framerate ranges */
spa_pod_builder_add(b,
SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
&SPA_RECTANGLE(MAX(width, 1), MAX(height, 1)),
&SPA_RECTANGLE(1, 1),
&SPA_RECTANGLE(MAX(width, WIDTH), MAX(height, HEIGHT))),
SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(
&SPA_FRACTION(25, 1),
&SPA_FRACTION(0, 1),
&SPA_FRACTION(30, 1)),
0);
params[0] = spa_pod_builder_pop(b, &frame[0]);
RARCH_LOG("[Camera] [PipeWire]: Supported raw formats:\n");
spa_debug_format(2, NULL, params[0]);
return 1;
}
static bool preprocess_image(pipewire_camera_t *camera)
{
struct spa_buffer *buf;
struct spa_meta_header *h;
struct pw_buffer *b = NULL;
struct scaler_ctx *ctx = NULL;
for (;;)
{
if ((b = pw_stream_dequeue_buffer(camera->stream)))
break;
}
if (b == NULL)
{
RARCH_DBG("[Camera] [PipeWire]: Out of buffers\n");
return false;
}
buf = b->buffer;
if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h))))
{
if (h->flags & SPA_META_HEADER_FLAG_CORRUPTED) {
RARCH_LOG("[Camera] [PipeWire] Dropping corruped frame.\n");
pw_stream_queue_buffer(camera->stream, b);
return false;
}
uint64_t now = pw_stream_get_nsec(camera->stream);
RARCH_DBG("[Camera] [PipeWire]: now:%"PRIu64" pts:%"PRIu64" diff:%"PRIi64"\n",
now, h->pts, now - h->pts);
}
if (buf->datas[0].data == NULL)
return false;
ctx = &camera->scaler;
scaler_ctx_scale_direct(ctx, camera->buffer_output, (const uint8_t*)buf->datas[0].data);
pw_stream_queue_buffer(camera->stream, b);
return true;
}
static void stream_state_changed_cb(void *data, enum pw_stream_state old,
enum pw_stream_state state, const char *error)
{
pipewire_camera_t *camera = (pipewire_camera_t*)data;
RARCH_DBG("[Camera] [PipeWire]: Stream state changed %s -> %s\n",
pw_stream_state_as_string(old),
pw_stream_state_as_string(state));
pw_thread_loop_signal(camera->pw->thread_loop, false);
}
static void stream_io_changed_cb(void *data, uint32_t id, void *area, uint32_t size)
{
pipewire_camera_t *camera = (pipewire_camera_t*)data;
switch (id)
{
case SPA_IO_Position:
camera->position = area;
break;
}
}
static void stream_param_changed_cb(void *data, uint32_t id, const struct spa_pod *param)
{
uint8_t params_buffer[1024];
const struct spa_pod *params[5];
pipewire_camera_t *camera = (pipewire_camera_t*)data;
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
if (param && id == SPA_PARAM_Tag)
{
spa_debug_pod(0, NULL, param);
return;
}
if (param && id == SPA_PARAM_Latency)
{
struct spa_latency_info info;
if (spa_latency_parse(param, &info) >= 0)
RARCH_DBG("[Camera] [PipeWire]: Got latency: %"PRIu64"\n", (info.min_ns + info.max_ns) / 2);
return;
}
/* NULL means to clear the format */
if (param == NULL || id != SPA_PARAM_Format)
return;
RARCH_DBG("[Camera] [PipeWire]: Got format:\n");
spa_debug_format(2, NULL, param);
if (spa_format_parse(param, &camera->format.media_type, &camera->format.media_subtype) < 0)
{
RARCH_DBG("[Camera] [PipeWire]: Failed to parse video format.\n");
return;
}
if (camera->format.media_type != SPA_MEDIA_TYPE_video)
return;
switch (camera->format.media_subtype)
{
case SPA_MEDIA_SUBTYPE_raw:
spa_format_video_raw_parse(param, &camera->format.info.raw);
camera->scaler.in_fmt = id_to_scaler_format(camera->format.info.raw.format);
camera->size = SPA_RECTANGLE(camera->format.info.raw.size.width, camera->format.info.raw.size.height);
RARCH_DBG("[Camera] [PipeWire]: Configured capture format = %d\n", camera->format.info.raw.format);
break;
default:
RARCH_WARN("[Camera] [PipeWire]: Unsupported video format: %d\n", camera->format.media_subtype);
return;
}
if (!camera->size.width || !camera->size.height)
{
pw_stream_set_error(camera->stream, -EINVAL, "invalid size");
return;
}
struct spa_pod_frame frame;
spa_pod_builder_push_object(&b, &frame, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers);
spa_pod_builder_add(&b,
SPA_PARAM_BUFFERS_stride, SPA_POD_Int(camera->size.width * 2),
0);
spa_pod_builder_add(&b,
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 1, MAX_BUFFERS),
SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1 << SPA_DATA_MemPtr),
0);
params[0] = spa_pod_builder_pop(&b, &frame);
params[1] = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
#if 0
params[2] = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform),
SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_videotransform)));
#endif
camera->buffer_output = (uint32_t *)
malloc(camera->size.width * camera->size.height * sizeof(uint32_t));
if (!camera->buffer_output)
{
RARCH_ERR("[Camera] [PipeWire]: Failed to allocate output buffer.\n");
return;
}
camera->scaler.in_width = camera->scaler.out_width = camera->size.width;
camera->scaler.in_height = camera->scaler.out_height = camera->size.height;
camera->scaler.out_fmt = SCALER_FMT_ARGB8888;
camera->scaler.in_stride = camera->size.width * 2;
camera->scaler.out_stride = camera->size.width * 4;
if (!scaler_ctx_gen_filter(&camera->scaler))
{
RARCH_ERR("[Camera] [PipeWire]: Failed to create scaler.\n");
return;
}
pw_stream_update_params(camera->stream, params, 2);
}
static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS,
.state_changed = stream_state_changed_cb,
.io_changed = stream_io_changed_cb,
.param_changed = stream_param_changed_cb,
.process = NULL,
};
static void pipewire_stop(void *data)
{
pipewire_camera_t *camera = (pipewire_camera_t*)data;
const char *error = NULL;
retro_assert(camera);
retro_assert(camera->stream);
retro_assert(camera->pw);
if (pw_stream_get_state(camera->stream, &error) == PW_STREAM_STATE_PAUSED)
pipewire_stream_set_active(camera->pw->thread_loop, camera->stream, false);
}
static bool pipewire_start(void *data)
{
pipewire_camera_t *camera = (pipewire_camera_t*)data;
const char *error = NULL;
retro_assert(camera);
retro_assert(camera->stream);
retro_assert(camera->pw);
if (pw_stream_get_state(camera->stream, &error) == PW_STREAM_STATE_STREAMING)
return true;
return pipewire_stream_set_active(camera->pw->thread_loop, camera->stream, true);
}
static void pipewire_free(void *data)
{
pipewire_camera_t *camera = (pipewire_camera_t*)data;
if (!camera)
return;
if (camera->stream)
{
pw_thread_loop_lock(camera->pw->thread_loop);
pw_stream_destroy(camera->stream);
camera->stream = NULL;
pw_thread_loop_unlock(camera->pw->thread_loop);
}
pipewire_core_deinit(camera->pw);
if (camera->buffer_output)
free(camera->buffer_output);
scaler_ctx_gen_reset(&camera->scaler);
free(camera);
}
static void *pipewire_init(const char *device, uint64_t caps,
unsigned width, unsigned height)
{
int res, n_params;
const struct spa_pod *params[3];
struct pw_properties *props;
uint8_t buffer[1024];
pipewire_camera_t *camera = (pipewire_camera_t*)calloc(1, sizeof(*camera));
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
if (!camera)
goto error;
if (!pipewire_core_init(&camera->pw, "camera_driver", NULL))
goto error;
pipewire_core_wait_resync(camera->pw);
props = pw_properties_new(PW_KEY_MEDIA_TYPE, PW_RARCH_MEDIA_TYPE_VIDEO,
PW_KEY_MEDIA_CATEGORY, PW_RARCH_MEDIA_CATEGORY_RECORD,
PW_KEY_MEDIA_ROLE, PW_RARCH_MEDIA_ROLE,
NULL);
if (!props)
goto error;
if (device)
pw_properties_set(props, PW_KEY_TARGET_OBJECT, device);
camera->stream = pw_stream_new(camera->pw->core, PW_RARCH_APPNAME, props);
pw_stream_add_listener(camera->stream, &camera->stream_listener, &stream_events, camera);
/* build the extra parameters to connect with. To connect, we can provide
* a list of supported formats. We use a builder that writes the param
* object to the stack. */
n_params = build_format(&b, params, width, height);
{
struct spa_pod_frame f;
struct spa_dict_item items[1];
/* send a tag, input tags travel upstream */
spa_tag_build_start(&b, &f, SPA_PARAM_Tag, SPA_DIRECTION_INPUT);
items[0] = SPA_DICT_ITEM_INIT("my-tag-other-key", "my-special-other-tag-value");
spa_tag_build_add_dict(&b, &SPA_DICT_INIT(items, 1));
params[n_params++] = spa_tag_build_end(&b, &f);
}
res = pw_stream_connect(camera->stream,
PW_DIRECTION_INPUT,
PW_ID_ANY,
PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_INACTIVE |
PW_STREAM_FLAG_MAP_BUFFERS,
params, n_params);
if (res < 0)
{
RARCH_ERR("[Camera] [PipeWire]: can't connect: %s\n", spa_strerror(res));
goto error;
}
pw_thread_loop_unlock(camera->pw->thread_loop);
return camera;
error:
RARCH_ERR("[Camera] [PipeWire]: Failed to initialize camera\n");
pipewire_free(camera);
return NULL;
}
static bool pipewire_poll(void *data,
retro_camera_frame_raw_framebuffer_t frame_raw_cb,
retro_camera_frame_opengl_texture_t frame_gl_cb)
{
pipewire_camera_t *camera = (pipewire_camera_t*)data;
const char *error = NULL;
(void)frame_gl_cb;
retro_assert(camera);
if (pw_stream_get_state(camera->stream, &error) != PW_STREAM_STATE_STREAMING)
return false;
if (!frame_raw_cb || !preprocess_image(camera))
return false;
frame_raw_cb(camera->buffer_output, camera->size.width,
camera->size.height, camera->size.width * 4);
return true;
}
camera_driver_t camera_pipewire = {
pipewire_init,
pipewire_free,
pipewire_start,
pipewire_stop,
pipewire_poll,
"pipewire",
};

Some files were not shown because too many files have changed in this diff Show More