diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index c2885ebb3..14d90d48a 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -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 diff --git a/.travis.yml b/.travis.yml index ce2ea42c2..79804b4ad 100755 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index a983374b8..591f134d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/core/oslib/audiobackend_directsound.cpp b/core/oslib/audiobackend_directsound.cpp index 4a2cb5ee0..ca29aaa06 100644 --- a/core/oslib/audiobackend_directsound.cpp +++ b/core/oslib/audiobackend_directsound.cpp @@ -26,66 +26,6 @@ static cResetEvent pushWait; constexpr u32 SAMPLE_BYTES = SAMPLE_COUNT * 4; -class RingBuffer -{ - std::vector 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(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(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) { diff --git a/core/oslib/audiobackend_oboe.cpp b/core/oslib/audiobackend_oboe.cpp new file mode 100644 index 000000000..12ffd36df --- /dev/null +++ b/core/oslib/audiobackend_oboe.cpp @@ -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 . +*/ +#include "audiostream.h" +#ifdef USE_OBOE +#include +#include +#include +#include +#include +#include "stdclass.h" + +static RingBuffer ringBuffer; +static cResetEvent pushWait; + +static std::shared_ptr stream; +static std::shared_ptr 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 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 diff --git a/core/oslib/audiostream.cpp b/core/oslib/audiostream.cpp index 7b7d29287..506bd07a7 100644 --- a/core/oslib/audiostream.cpp +++ b/core/oslib/audiostream.cpp @@ -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; diff --git a/core/oslib/audiostream.h b/core/oslib/audiostream.h index 3fe7a297f..2f9131683 100644 --- a/core/oslib/audiostream.h +++ b/core/oslib/audiostream.h @@ -1,6 +1,9 @@ #pragma once #include "types.h" #include "cfg/option.h" +#include +#include +#include typedef std::vector (*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 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(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(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; + } +}; diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index 6730cd9b8..a42ee392e 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -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) { diff --git a/shell/android-studio/build.gradle b/shell/android-studio/build.gradle index 8e0dd8301..c8274d0b5 100644 --- a/shell/android-studio/build.gradle +++ b/shell/android-studio/build.gradle @@ -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' } } diff --git a/shell/android-studio/flycast/build.gradle b/shell/android-studio/flycast/build.gradle index 8c1bbdd4b..dc205b36c 100644 --- a/shell/android-studio/flycast/build.gradle +++ b/shell/android-studio/flycast/build.gradle @@ -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' } diff --git a/shell/android-studio/flycast/src/dreamcast/AndroidManifest.xml b/shell/android-studio/flycast/src/dreamcast/AndroidManifest.xml deleted file mode 100644 index 017147011..000000000 --- a/shell/android-studio/flycast/src/dreamcast/AndroidManifest.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/shell/android-studio/flycast/src/main/AndroidManifest.xml b/shell/android-studio/flycast/src/main/AndroidManifest.xml index bc93a562f..f0cba352c 100644 --- a/shell/android-studio/flycast/src/main/AndroidManifest.xml +++ b/shell/android-studio/flycast/src/main/AndroidManifest.xml @@ -64,6 +64,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +