android: oboe audio driver. get rid of build variants

Issue #182
This commit is contained in:
Flyinghead 2021-04-30 19:57:11 +02:00
parent acd8620139
commit 6dfa9f7786
13 changed files with 334 additions and 192 deletions

View File

@ -23,9 +23,9 @@ jobs:
- name: Gradle
working-directory: shell/android-studio
run: ./gradlew assembleDreamcastDebug --parallel
run: ./gradlew assembleDebug --parallel
- uses: actions/upload-artifact@v2
with:
name: flycast-debug.apk
path: shell/android-studio/flycast/build/outputs/apk/dreamcast/debug/flycast-dreamcast-debug.apk
path: shell/android-studio/flycast/build/outputs/apk/debug/flycast-debug.apk

View File

@ -22,14 +22,14 @@ install:
script:
- cd shell/android-studio
- chmod +x gradlew
- ./gradlew assembleDreamcastDebug --console=plain --parallel
- ./gradlew assembleDebug --console=plain --parallel
before_deploy:
- cd ../../
- GIT_HASH=`git log --pretty=format:'%h' -n 1`
- GIT_BUILD=`git describe --all --always`-$GIT_HASH
- mkdir -p artifacts/$GIT_BUILD/
- cp shell/android-studio/flycast/build/outputs/apk/dreamcast/debug/flycast-dreamcast-debug.apk artifacts/$GIT_BUILD/flycast-debug-$GIT_HASH.apk
- cp shell/android-studio/flycast/build/outputs/apk/debug/flycast-debug.apk artifacts/$GIT_BUILD/flycast-debug-$GIT_HASH.apk
deploy:
provider: s3

View File

@ -281,6 +281,12 @@ if(ASAN)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address -static-libasan")
endif()
if(ANDROID)
find_package(oboe REQUIRED CONFIG)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_OBOE)
target_link_libraries(${PROJECT_NAME} PRIVATE oboe::oboe)
endif()
target_sources(${PROJECT_NAME} PRIVATE
core/deps/chdpsr/cdipsr.cpp
core/deps/chdpsr/cdipsr.h)
@ -710,6 +716,7 @@ target_sources(${PROJECT_NAME} PRIVATE
core/oslib/audiobackend_directsound.cpp
core/oslib/audiobackend_libao.cpp
core/oslib/audiobackend_null.cpp
core/oslib/audiobackend_oboe.cpp
core/oslib/audiobackend_omx.cpp
core/oslib/audiobackend_oss.cpp
core/oslib/audiobackend_pulseaudio.cpp

View File

@ -26,66 +26,6 @@ static cResetEvent pushWait;
constexpr u32 SAMPLE_BYTES = SAMPLE_COUNT * 4;
class RingBuffer
{
std::vector<u8> buffer;
std::atomic_int readCursor { 0 };
std::atomic_int writeCursor { 0 };
u32 readSize() {
return (writeCursor - readCursor + buffer.size()) % buffer.size();
}
u32 writeSize() {
return (readCursor - writeCursor + buffer.size() - 1) % buffer.size();
}
public:
bool write(const u8 *data, u32 size)
{
if (size > writeSize())
return false;
u32 wc = writeCursor;
u32 chunkSize = std::min<u32>(size, buffer.size() - wc);
memcpy(&buffer[wc], data, chunkSize);
wc = (wc + chunkSize) % buffer.size();
size -= chunkSize;
if (size > 0)
{
data += chunkSize;
memcpy(&buffer[wc], data, size);
wc = (wc + size) % buffer.size();
}
writeCursor = wc;
return true;
}
bool read(u8 *data, u32 size)
{
if (size > readSize())
return false;
u32 rc = readCursor;
u32 chunkSize = std::min<u32>(size, buffer.size() - rc);
memcpy(data, &buffer[rc], chunkSize);
rc = (rc + chunkSize) % buffer.size();
size -= chunkSize;
if (size > 0)
{
data += chunkSize;
memcpy(data, &buffer[rc], size);
rc = (rc + size) % buffer.size();
}
readCursor = rc;
return true;
}
void setCapacity(size_t size)
{
std::fill(buffer.begin(), buffer.end(), 0);
buffer.resize(size);
readCursor = 0;
writeCursor = 0;
}
};
static RingBuffer ringBuffer;
static u32 notificationOffset(int index) {

View File

@ -0,0 +1,146 @@
/*
Copyright 2021 flyinghead
This file is part of Flycast.
Flycast 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 Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast 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 Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "audiostream.h"
#ifdef USE_OBOE
#include <oboe/Oboe.h>
#include <vector>
#include <algorithm>
#include <atomic>
#include <memory>
#include "stdclass.h"
static RingBuffer ringBuffer;
static cResetEvent pushWait;
static std::shared_ptr<oboe::AudioStream> stream;
static std::shared_ptr<oboe::AudioStream> recordStream;
class AudioCallback : public oboe::AudioStreamDataCallback
{
public:
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames)
{
if (!ringBuffer.read((u8 *)audioData, numFrames * 4))
// underrun
memset(audioData, 0, numFrames * 4);
else
pushWait.Set();
return oboe::DataCallbackResult::Continue;
}
};
static AudioCallback audioCallback;
static void audio_init()
{
// Actual capacity is size-1 to avoid overrun so add one buffer
ringBuffer.setCapacity((config::AudioBufferSize + SAMPLE_COUNT) * 4);
oboe::AudioStreamBuilder builder;
oboe::Result result = builder.setDirection(oboe::Direction::Output)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setSharingMode(oboe::SharingMode::Exclusive)
->setFormat(oboe::AudioFormat::I16)
->setChannelCount(oboe::ChannelCount::Stereo)
->setSampleRate(44100)
->setFramesPerCallback(SAMPLE_COUNT)
->setDataCallback(&audioCallback)
->openStream(stream);
if (result != oboe::Result::OK)
{
ERROR_LOG(AUDIO, "Oboe open stream failed: %s", oboe::convertToText(result));
return;
}
stream->requestStart();
NOTICE_LOG(AUDIO, "Oboe driver started. stream capacity: %d frames, frames/callback: %d, frames/burst: %d",
stream->getBufferCapacityInFrames(), stream->getFramesPerCallback(), stream->getFramesPerBurst());
}
static void audio_term()
{
NOTICE_LOG(AUDIO, "Oboe driver stopping");
if (stream != nullptr)
{
stream->stop();
stream->close();
stream.reset();
}
}
static u32 audio_push(const void* frame, u32 samples, bool wait) {
while (!ringBuffer.write((const u8 *)frame, samples * 4) && wait)
pushWait.Wait();
return 1;
}
static void term_record()
{
if (recordStream != nullptr)
{
recordStream->stop();
recordStream->close();
recordStream.reset();
}
NOTICE_LOG(AUDIO, "Oboe recorder stopped");
}
static bool init_record(u32 sampling_freq)
{
oboe::AudioStreamBuilder builder;
oboe::Result result = builder.setDirection(oboe::Direction::Input)
->setPerformanceMode(oboe::PerformanceMode::None)
->setSharingMode(oboe::SharingMode::Exclusive)
->setFormat(oboe::AudioFormat::I16)
->setChannelCount(oboe::ChannelCount::Mono)
->setSampleRate(sampling_freq)
->openStream(recordStream);
if (result != oboe::Result::OK)
{
ERROR_LOG(AUDIO, "Oboe open record stream failed: %s", oboe::convertToText(result));
return false;
}
recordStream->requestStart();
NOTICE_LOG(AUDIO, "Oboe recorder started. stream capacity: %d frames",
stream->getBufferCapacityInFrames());
return true;
}
static u32 record(void *data, u32 samples)
{
oboe::ResultWithValue<int32_t> result = recordStream->read(data, samples, 0);
return result.value();
}
static audiobackend_t audiobackend_oboe = {
"Oboe", // Slug
"Automatic AAudio / OpenSL selection", // Name
&audio_init,
&audio_push,
&audio_term,
NULL,
&init_record,
&record,
&term_record
};
static bool oboebe = RegisterAudioBackend(&audiobackend_oboe);
#endif

View File

@ -47,10 +47,10 @@ audiobackend_t* GetAudioBackend(const std::string& slug)
{
if (slug == "auto")
{
// Don't select the null driver
// Don't select the null or OpenSL/Oboe drivers
audiobackend_t *autoselection = nullptr;
for (auto backend : *audiobackends)
if (backend->slug != "null")
if (backend->slug != "null" && backend->slug != "OpenSL" && backend->slug != "Oboe")
{
autoselection = backend;
break;

View File

@ -1,6 +1,9 @@
#pragma once
#include "types.h"
#include "cfg/option.h"
#include <vector>
#include <algorithm>
#include <atomic>
typedef std::vector<std::string> (*audio_option_callback_t)();
enum audio_option_type
@ -53,3 +56,64 @@ audiobackend_t* GetAudioBackend(int num);
audiobackend_t* GetAudioBackend(const std::string& slug);
constexpr u32 SAMPLE_COUNT = 512; // push() is always called with that many frames
class RingBuffer
{
std::vector<u8> buffer;
std::atomic_int readCursor { 0 };
std::atomic_int writeCursor { 0 };
u32 readSize() {
return (writeCursor - readCursor + buffer.size()) % buffer.size();
}
u32 writeSize() {
return (readCursor - writeCursor + buffer.size() - 1) % buffer.size();
}
public:
bool write(const u8 *data, u32 size)
{
if (size > writeSize())
return false;
u32 wc = writeCursor;
u32 chunkSize = std::min<u32>(size, buffer.size() - wc);
memcpy(&buffer[wc], data, chunkSize);
wc = (wc + chunkSize) % buffer.size();
size -= chunkSize;
if (size > 0)
{
data += chunkSize;
memcpy(&buffer[wc], data, size);
wc = (wc + size) % buffer.size();
}
writeCursor = wc;
return true;
}
bool read(u8 *data, u32 size)
{
if (size > readSize())
return false;
u32 rc = readCursor;
u32 chunkSize = std::min<u32>(size, buffer.size() - rc);
memcpy(data, &buffer[rc], chunkSize);
rc = (rc + chunkSize) % buffer.size();
size -= chunkSize;
if (size > 0)
{
data += chunkSize;
memcpy(data, &buffer[rc], size);
rc = (rc + size) % buffer.size();
}
readCursor = rc;
return true;
}
void setCapacity(size_t size)
{
std::fill(buffer.begin(), buffer.end(), 0);
buffer.resize(size);
readCursor = 0;
writeCursor = 0;
}
};

View File

@ -1398,8 +1398,9 @@ static void gui_display_settings()
OptionCheckbox("Enable DSP", config::DSPEnabled,
"Enable the Dreamcast Digital Sound Processor. Only recommended on fast platforms");
#ifdef __ANDROID__
OptionCheckbox("Automatic Latency", config::AutoLatency,
"Automatically set audio latency. Recommended");
if (config::AudioBackend.get() == "auto" || config::AudioBackend.get() == "android")
OptionCheckbox("Automatic Latency", config::AutoLatency,
"Automatically set audio latency. Recommended");
#endif
if (!config::AutoLatency)
{

View File

@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.3'
classpath 'com.android.tools.build:gradle:4.1.2'
}
}

View File

@ -25,7 +25,6 @@ def getVersionName = { ->
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
defaultConfig {
applicationId "com.flycast.emulator"
@ -37,7 +36,7 @@ android {
externalNativeBuild {
cmake {
arguments '-DANDROID_ARM_MODE=arm'
arguments "-DANDROID_STL=c++_shared", "-DANDROID_ARM_MODE=arm"
}
}
@ -75,13 +74,6 @@ android {
}
}
flavorDimensions "systemtype"
productFlavors {
dreamcast {
}
}
externalNativeBuild {
cmake {
version '3.10.2+'
@ -92,13 +84,16 @@ android {
lintOptions {
abortOnError false
}
buildFeatures {
prefab true
}
}
afterEvaluate {
android.applicationVariants.all { v ->
if (v.buildType.name == "release") {
def hashtag = getVersionHash()
v.outputs[0].outputFileName = "flycast-android-" + hashtag + ".apk"
// def hashtag = getVersionHash()
// v.outputs[0].outputFileName = "flycast-android-" + hashtag + ".apk"
}
}
}
@ -115,4 +110,5 @@ dependencies {
exclude module: 'junit'
}
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.google.oboe:oboe:1.5.0'
}

View File

@ -1,110 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reicast.emulator"
xmlns:tools="http://schemas.android.com/tools">
<application android:name="com.reicast.emulator.Emulator">
<activity
android:name="com.reicast.emulator.NativeGLActivity"/>
<activity-alias
android:name="com.reicast.emulator.MainActivity"
android:targetActivity="com.reicast.emulator.NativeGLActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.GDI"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.gdi"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.CHD"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.chd"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.CDI"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.cdi"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.CUE"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.cue"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.LST"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.lst"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.BIN"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.bin"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.DAT"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.dat"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.ZIP"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.zip"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.7Z"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.7z"
android:scheme="file" />
</intent-filter>
</activity-alias>
</application>
</manifest>

View File

@ -64,6 +64,104 @@
<category android:name="tv.ouya.intent.category.GAME" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.GDI"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.gdi"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.CHD"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.chd"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.CDI"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.cdi"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.CUE"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.cue"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.LST"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.lst"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.BIN"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.bin"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.DAT"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.dat"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.ZIP"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.zip"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.7Z"
android:scheme="file" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.7z"
android:scheme="file" />
</intent-filter>
</activity-alias>
<provider
android:name="android.support.v4.content.FileProvider"

View File

@ -1,6 +1,6 @@
#Mon Apr 02 06:06:44 EDT 2018
#Wed Apr 07 11:28:33 CEST 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip