mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2025-10-19 21:47:54 +00:00
Move dead submodules in-tree
Signed-off-by: swurl <swurl@swurl.xyz>
This commit is contained in:
parent
c0cceff365
commit
6c655321e6
4081 changed files with 1185566 additions and 45 deletions
241
externals/oboe/samples/RhythmGame/src/main/cpp/Game.cpp
vendored
Normal file
241
externals/oboe/samples/RhythmGame/src/main/cpp/Game.cpp
vendored
Normal file
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <utils/logging.h>
|
||||
#include <thread>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "Game.h"
|
||||
|
||||
Game::Game(AAssetManager &assetManager): mAssetManager(assetManager) {
|
||||
}
|
||||
|
||||
void Game::load() {
|
||||
|
||||
if (!openStream()) {
|
||||
mGameState = GameState::FailedToLoad;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!setupAudioSources()) {
|
||||
mGameState = GameState::FailedToLoad;
|
||||
return;
|
||||
}
|
||||
|
||||
scheduleSongEvents();
|
||||
|
||||
Result result = mAudioStream->requestStart();
|
||||
if (result != Result::OK){
|
||||
LOGE("Failed to start stream. Error: %s", convertToText(result));
|
||||
mGameState = GameState::FailedToLoad;
|
||||
return;
|
||||
}
|
||||
|
||||
mGameState = GameState::Playing;
|
||||
}
|
||||
|
||||
void Game::start() {
|
||||
|
||||
// async returns a future, we must store this future to avoid blocking. It's not sufficient
|
||||
// to store this in a local variable as its destructor will block until Game::load completes.
|
||||
mLoadingResult = std::async(&Game::load, this);
|
||||
}
|
||||
|
||||
void Game::stop(){
|
||||
|
||||
if (mAudioStream){
|
||||
mAudioStream->stop();
|
||||
mAudioStream->close();
|
||||
mAudioStream.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Game::tap(int64_t eventTimeAsUptime) {
|
||||
|
||||
if (mGameState != GameState::Playing){
|
||||
LOGW("Game not in playing state, ignoring tap event");
|
||||
} else {
|
||||
mClap->setPlaying(true);
|
||||
|
||||
int64_t nextClapWindowTimeMs;
|
||||
if (mClapWindows.pop(nextClapWindowTimeMs)){
|
||||
|
||||
// Convert the tap time to a song position
|
||||
int64_t tapTimeInSongMs = mSongPositionMs + (eventTimeAsUptime - mLastUpdateTime);
|
||||
TapResult result = getTapResult(tapTimeInSongMs, nextClapWindowTimeMs);
|
||||
mUiEvents.push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Game::tick(){
|
||||
|
||||
switch (mGameState){
|
||||
case GameState::Playing:
|
||||
TapResult r;
|
||||
if (mUiEvents.pop(r)) {
|
||||
renderEvent(r);
|
||||
} else {
|
||||
SetGLScreenColor(kPlayingColor);
|
||||
}
|
||||
break;
|
||||
|
||||
case GameState::Loading:
|
||||
SetGLScreenColor(kLoadingColor);
|
||||
break;
|
||||
|
||||
case GameState::FailedToLoad:
|
||||
SetGLScreenColor(kLoadingFailedColor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Game::onSurfaceCreated() {
|
||||
SetGLScreenColor(kLoadingColor);
|
||||
}
|
||||
|
||||
void Game::onSurfaceChanged(int widthInPixels, int heightInPixels) {
|
||||
}
|
||||
|
||||
void Game::onSurfaceDestroyed() {
|
||||
}
|
||||
|
||||
DataCallbackResult Game::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) {
|
||||
|
||||
auto *outputBuffer = static_cast<float *>(audioData);
|
||||
|
||||
int64_t nextClapEventMs;
|
||||
|
||||
for (int i = 0; i < numFrames; ++i) {
|
||||
|
||||
mSongPositionMs = convertFramesToMillis(
|
||||
mCurrentFrame,
|
||||
mAudioStream->getSampleRate());
|
||||
|
||||
if (mClapEvents.peek(nextClapEventMs) && mSongPositionMs >= nextClapEventMs){
|
||||
mClap->setPlaying(true);
|
||||
mClapEvents.pop(nextClapEventMs);
|
||||
}
|
||||
mMixer.renderAudio(outputBuffer+(oboeStream->getChannelCount()*i), 1);
|
||||
mCurrentFrame++;
|
||||
}
|
||||
|
||||
mLastUpdateTime = nowUptimeMillis();
|
||||
|
||||
return DataCallbackResult::Continue;
|
||||
}
|
||||
|
||||
void Game::onErrorAfterClose(AudioStream *audioStream, Result error) {
|
||||
if (error == Result::ErrorDisconnected){
|
||||
mGameState = GameState::Loading;
|
||||
mAudioStream.reset();
|
||||
mMixer.removeAllTracks();
|
||||
mCurrentFrame = 0;
|
||||
mSongPositionMs = 0;
|
||||
mLastUpdateTime = 0;
|
||||
start();
|
||||
} else {
|
||||
LOGE("Stream error: %s", convertToText(error));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result of a tap
|
||||
*
|
||||
* @param tapTimeInMillis - The time the tap occurred in milliseconds
|
||||
* @param tapWindowInMillis - The time at the middle of the "tap window" in milliseconds
|
||||
* @return TapResult can be Early, Late or Success
|
||||
*/
|
||||
TapResult Game::getTapResult(int64_t tapTimeInMillis, int64_t tapWindowInMillis){
|
||||
LOGD("Tap time %" PRId64 ", tap window time: %" PRId64, tapTimeInMillis, tapWindowInMillis);
|
||||
if (tapTimeInMillis <= tapWindowInMillis + kWindowCenterOffsetMs) {
|
||||
if (tapTimeInMillis >= tapWindowInMillis - kWindowCenterOffsetMs) {
|
||||
return TapResult::Success;
|
||||
} else {
|
||||
return TapResult::Early;
|
||||
}
|
||||
} else {
|
||||
return TapResult::Late;
|
||||
}
|
||||
}
|
||||
|
||||
bool Game::openStream() {
|
||||
|
||||
// Create an audio stream
|
||||
AudioStreamBuilder builder;
|
||||
builder.setFormat(AudioFormat::Float);
|
||||
builder.setFormatConversionAllowed(true);
|
||||
builder.setPerformanceMode(PerformanceMode::LowLatency);
|
||||
builder.setSharingMode(SharingMode::Exclusive);
|
||||
builder.setSampleRate(48000);
|
||||
builder.setSampleRateConversionQuality(
|
||||
SampleRateConversionQuality::Medium);
|
||||
builder.setChannelCount(2);
|
||||
builder.setDataCallback(this);
|
||||
builder.setErrorCallback(this);
|
||||
Result result = builder.openStream(mAudioStream);
|
||||
if (result != Result::OK){
|
||||
LOGE("Failed to open stream. Error: %s", convertToText(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
mMixer.setChannelCount(mAudioStream->getChannelCount());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Game::setupAudioSources() {
|
||||
|
||||
// Set the properties of our audio source(s) to match that of our audio stream
|
||||
AudioProperties targetProperties {
|
||||
.channelCount = mAudioStream->getChannelCount(),
|
||||
.sampleRate = mAudioStream->getSampleRate()
|
||||
};
|
||||
|
||||
// Create a data source and player for the clap sound
|
||||
std::shared_ptr<AAssetDataSource> mClapSource {
|
||||
AAssetDataSource::newFromCompressedAsset(mAssetManager, kClapFilename, targetProperties)
|
||||
};
|
||||
if (mClapSource == nullptr){
|
||||
LOGE("Could not load source data for clap sound");
|
||||
return false;
|
||||
}
|
||||
mClap = std::make_unique<Player>(mClapSource);
|
||||
|
||||
// Create a data source and player for our backing track
|
||||
std::shared_ptr<AAssetDataSource> backingTrackSource {
|
||||
AAssetDataSource::newFromCompressedAsset(mAssetManager, kBackingTrackFilename, targetProperties)
|
||||
};
|
||||
if (backingTrackSource == nullptr){
|
||||
LOGE("Could not load source data for backing track");
|
||||
return false;
|
||||
}
|
||||
mBackingTrack = std::make_unique<Player>(backingTrackSource);
|
||||
mBackingTrack->setPlaying(true);
|
||||
mBackingTrack->setLooping(true);
|
||||
|
||||
// Add both players to a mixer
|
||||
mMixer.addTrack(mClap.get());
|
||||
mMixer.addTrack(mBackingTrack.get());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Game::scheduleSongEvents() {
|
||||
|
||||
for (auto t : kClapEvents) mClapEvents.push(t);
|
||||
for (auto t : kClapWindows) mClapWindows.push(t);
|
||||
}
|
85
externals/oboe/samples/RhythmGame/src/main/cpp/Game.h
vendored
Normal file
85
externals/oboe/samples/RhythmGame/src/main/cpp/Game.h
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef RHYTHMGAME_GAME_H
|
||||
#define RHYTHMGAME_GAME_H
|
||||
|
||||
#include <future>
|
||||
|
||||
#include <android/asset_manager.h>
|
||||
#include <oboe/Oboe.h>
|
||||
|
||||
#include "shared/Mixer.h"
|
||||
|
||||
#include "audio/Player.h"
|
||||
#include "audio/AAssetDataSource.h"
|
||||
#include "ui/OpenGLFunctions.h"
|
||||
#include "utils/LockFreeQueue.h"
|
||||
#include "utils/UtilityFunctions.h"
|
||||
#include "GameConstants.h"
|
||||
|
||||
using namespace oboe;
|
||||
|
||||
|
||||
enum class GameState {
|
||||
Loading,
|
||||
Playing,
|
||||
FailedToLoad
|
||||
};
|
||||
|
||||
class Game : public AudioStreamDataCallback, AudioStreamErrorCallback {
|
||||
public:
|
||||
explicit Game(AAssetManager&);
|
||||
void start();
|
||||
void stop();
|
||||
void onSurfaceCreated();
|
||||
void onSurfaceDestroyed();
|
||||
void onSurfaceChanged(int widthInPixels, int heightInPixels);
|
||||
void tick();
|
||||
void tap(int64_t eventTimeAsUptime);
|
||||
|
||||
// Inherited from oboe::AudioStreamDataCallback.
|
||||
DataCallbackResult
|
||||
onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override;
|
||||
|
||||
// Inherited from oboe::AudioStreamErrorCallback.
|
||||
void onErrorAfterClose(AudioStream *oboeStream, Result error) override;
|
||||
|
||||
private:
|
||||
AAssetManager& mAssetManager;
|
||||
std::shared_ptr<AudioStream> mAudioStream;
|
||||
std::unique_ptr<Player> mClap;
|
||||
std::unique_ptr<Player> mBackingTrack;
|
||||
Mixer mMixer;
|
||||
|
||||
LockFreeQueue<int64_t, kMaxQueueItems> mClapEvents;
|
||||
std::atomic<int64_t> mCurrentFrame { 0 };
|
||||
std::atomic<int64_t> mSongPositionMs { 0 };
|
||||
LockFreeQueue<int64_t, kMaxQueueItems> mClapWindows;
|
||||
LockFreeQueue<TapResult, kMaxQueueItems> mUiEvents;
|
||||
std::atomic<int64_t> mLastUpdateTime { 0 };
|
||||
std::atomic<GameState> mGameState { GameState::Loading };
|
||||
std::future<void> mLoadingResult;
|
||||
|
||||
void load();
|
||||
TapResult getTapResult(int64_t tapTimeInMillis, int64_t tapWindowInMillis);
|
||||
bool openStream();
|
||||
bool setupAudioSources();
|
||||
void scheduleSongEvents();
|
||||
};
|
||||
|
||||
|
||||
#endif //RHYTHMGAME_GAME_H
|
60
externals/oboe/samples/RhythmGame/src/main/cpp/GameConstants.h
vendored
Normal file
60
externals/oboe/samples/RhythmGame/src/main/cpp/GameConstants.h
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef SAMPLES_GAMECONSTANTS_H
|
||||
#define SAMPLES_GAMECONSTANTS_H
|
||||
|
||||
#include "ui/OpenGLFunctions.h"
|
||||
|
||||
constexpr int kBufferSizeInBursts = 2; // Use 2 bursts as the buffer size (double buffer)
|
||||
constexpr int kMaxQueueItems = 4; // Must be power of 2
|
||||
|
||||
// Colors for game states and visual feedback for taps
|
||||
constexpr ScreenColor kPlayingColor = GREY;
|
||||
constexpr ScreenColor kLoadingColor = YELLOW;
|
||||
constexpr ScreenColor kLoadingFailedColor = RED;
|
||||
constexpr ScreenColor kTapSuccessColor = GREEN;
|
||||
constexpr ScreenColor kTapEarlyColor = ORANGE;
|
||||
constexpr ScreenColor kTapLateColor = PURPLE;
|
||||
|
||||
// This defines the size of the tap window in milliseconds. For example, if defined at 100ms the
|
||||
// player will have 100ms before and after the centre of the tap window to tap on the screen and
|
||||
// be successful
|
||||
constexpr int kWindowCenterOffsetMs = 100;
|
||||
|
||||
// Filename for clap sound asset (in assets folder)
|
||||
constexpr char kClapFilename[] { "CLAP.mp3" };
|
||||
|
||||
// Filename for the backing track asset (in assets folder)
|
||||
constexpr char kBackingTrackFilename[] { "FUNKY_HOUSE.mp3" };
|
||||
|
||||
// The game will first demonstrate the pattern which the user should copy. It does this by
|
||||
// "clapping" (playing a clap sound) at certain times during the song. We can specify these times
|
||||
// here in milliseconds. Our backing track has a tempo of 120 beats per minute, which is 2 beats per
|
||||
// second. This means a pattern of 3 claps starting on the first beat of the first bar would mean
|
||||
// playing claps at 0ms, 500ms and 1000ms
|
||||
constexpr int64_t kClapEvents[] { 0, 500, 1000 };
|
||||
|
||||
// We then want the user to tap on the screen exactly 4 beats after the first clap so we add clap
|
||||
// windows at 2000ms, 2500ms and 3000ms (or 2, 2.5 and 3 seconds). @see getTapResult for more info.
|
||||
constexpr int64_t kClapWindows[] { 2000, 2500, 3000 };
|
||||
|
||||
struct AudioProperties {
|
||||
int32_t channelCount;
|
||||
int32_t sampleRate;
|
||||
};
|
||||
|
||||
#endif //SAMPLES_GAMECONSTANTS_H
|
86
externals/oboe/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.cpp
vendored
Normal file
86
externals/oboe/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.cpp
vendored
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#include <utils/logging.h>
|
||||
#include <oboe/Oboe.h>
|
||||
|
||||
#include "AAssetDataSource.h"
|
||||
|
||||
#if !defined(USE_FFMPEG)
|
||||
#error USE_FFMPEG should be defined in app.gradle
|
||||
#endif
|
||||
|
||||
#if USE_FFMPEG==1
|
||||
#include "FFMpegExtractor.h"
|
||||
#else
|
||||
#include "NDKExtractor.h"
|
||||
#endif
|
||||
|
||||
|
||||
constexpr int kMaxCompressionRatio { 12 };
|
||||
|
||||
AAssetDataSource* AAssetDataSource::newFromCompressedAsset(
|
||||
AAssetManager &assetManager,
|
||||
const char *filename,
|
||||
const AudioProperties targetProperties) {
|
||||
|
||||
AAsset *asset = AAssetManager_open(&assetManager, filename, AASSET_MODE_UNKNOWN);
|
||||
if (!asset) {
|
||||
LOGE("Failed to open asset %s", filename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
off_t assetSize = AAsset_getLength(asset);
|
||||
LOGD("Opened %s, size %ld", filename, assetSize);
|
||||
|
||||
// Allocate memory to store the decompressed audio. We don't know the exact
|
||||
// size of the decoded data until after decoding so we make an assumption about the
|
||||
// maximum compression ratio and the decoded sample format (float for FFmpeg, int16 for NDK).
|
||||
#if USE_FFMPEG==true
|
||||
const long maximumDataSizeInBytes = kMaxCompressionRatio * assetSize * sizeof(float);
|
||||
auto decodedData = new uint8_t[maximumDataSizeInBytes];
|
||||
|
||||
int64_t bytesDecoded = FFMpegExtractor::decode(asset, decodedData, targetProperties);
|
||||
auto numSamples = bytesDecoded / sizeof(float);
|
||||
#else
|
||||
const long maximumDataSizeInBytes = kMaxCompressionRatio * assetSize * sizeof(int16_t);
|
||||
auto decodedData = new uint8_t[maximumDataSizeInBytes];
|
||||
|
||||
int64_t bytesDecoded = NDKExtractor::decode(asset, decodedData, targetProperties);
|
||||
auto numSamples = bytesDecoded / sizeof(int16_t);
|
||||
#endif
|
||||
|
||||
// Now we know the exact number of samples we can create a float array to hold the audio data
|
||||
auto outputBuffer = std::make_unique<float[]>(numSamples);
|
||||
|
||||
#if USE_FFMPEG==1
|
||||
memcpy(outputBuffer.get(), decodedData, (size_t)bytesDecoded);
|
||||
#else
|
||||
// The NDK decoder can only decode to int16, we need to convert to floats
|
||||
oboe::convertPcm16ToFloat(
|
||||
reinterpret_cast<int16_t*>(decodedData),
|
||||
outputBuffer.get(),
|
||||
bytesDecoded / sizeof(int16_t));
|
||||
#endif
|
||||
|
||||
delete[] decodedData;
|
||||
AAsset_close(asset);
|
||||
|
||||
return new AAssetDataSource(std::move(outputBuffer),
|
||||
numSamples,
|
||||
targetProperties);
|
||||
}
|
50
externals/oboe/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.h
vendored
Normal file
50
externals/oboe/samples/RhythmGame/src/main/cpp/audio/AAssetDataSource.h
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef RHYTHMGAME_AASSETDATASOURCE_H
|
||||
#define RHYTHMGAME_AASSETDATASOURCE_H
|
||||
|
||||
#include <android/asset_manager.h>
|
||||
#include <GameConstants.h>
|
||||
#include "DataSource.h"
|
||||
|
||||
class AAssetDataSource : public DataSource {
|
||||
|
||||
public:
|
||||
int64_t getSize() const override { return mBufferSize; }
|
||||
AudioProperties getProperties() const override { return mProperties; }
|
||||
const float* getData() const override { return mBuffer.get(); }
|
||||
|
||||
static AAssetDataSource* newFromCompressedAsset(
|
||||
AAssetManager &assetManager,
|
||||
const char *filename,
|
||||
AudioProperties targetProperties);
|
||||
|
||||
private:
|
||||
|
||||
AAssetDataSource(std::unique_ptr<float[]> data, size_t size,
|
||||
const AudioProperties properties)
|
||||
: mBuffer(std::move(data))
|
||||
, mBufferSize(size)
|
||||
, mProperties(properties) {
|
||||
}
|
||||
|
||||
const std::unique_ptr<float[]> mBuffer;
|
||||
const int64_t mBufferSize;
|
||||
const AudioProperties mProperties;
|
||||
|
||||
};
|
||||
#endif //RHYTHMGAME_AASSETDATASOURCE_H
|
32
externals/oboe/samples/RhythmGame/src/main/cpp/audio/DataSource.h
vendored
Normal file
32
externals/oboe/samples/RhythmGame/src/main/cpp/audio/DataSource.h
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef RHYTHMGAME_AUDIOSOURCE_H
|
||||
#define RHYTHMGAME_AUDIOSOURCE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <GameConstants.h>
|
||||
|
||||
class DataSource {
|
||||
public:
|
||||
virtual ~DataSource(){};
|
||||
virtual int64_t getSize() const = 0;
|
||||
virtual AudioProperties getProperties() const = 0;
|
||||
virtual const float* getData() const = 0;
|
||||
};
|
||||
|
||||
|
||||
#endif //RHYTHMGAME_AUDIOSOURCE_H
|
318
externals/oboe/samples/RhythmGame/src/main/cpp/audio/FFMpegExtractor.cpp
vendored
Normal file
318
externals/oboe/samples/RhythmGame/src/main/cpp/audio/FFMpegExtractor.cpp
vendored
Normal file
|
@ -0,0 +1,318 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <oboe/Definitions.h>
|
||||
#include "FFMpegExtractor.h"
|
||||
#include "utils/logging.h"
|
||||
|
||||
constexpr int kInternalBufferSize = 1152; // Use MP3 block size. https://wiki.hydrogenaud.io/index.php?title=MP3
|
||||
|
||||
int read(void *opaque, uint8_t *buf, int buf_size) {
|
||||
|
||||
auto asset = (AAsset *) opaque;
|
||||
int bytesRead = AAsset_read(asset, buf, (size_t)buf_size);
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
int64_t seek(void *opaque, int64_t offset, int whence){
|
||||
|
||||
auto asset = (AAsset*)opaque;
|
||||
|
||||
// See https://www.ffmpeg.org/doxygen/3.0/avio_8h.html#a427ff2a881637b47ee7d7f9e368be63f
|
||||
if (whence == AVSEEK_SIZE) return AAsset_getLength(asset);
|
||||
if (AAsset_seek(asset, offset, whence) == -1){
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool FFMpegExtractor::createAVIOContext(AAsset *asset, uint8_t *buffer, uint32_t bufferSize,
|
||||
AVIOContext **avioContext) {
|
||||
|
||||
constexpr int isBufferWriteable = 0;
|
||||
|
||||
*avioContext = avio_alloc_context(
|
||||
buffer, // internal buffer for FFmpeg to use
|
||||
bufferSize, // For optimal decoding speed this should be the protocol block size
|
||||
isBufferWriteable,
|
||||
asset, // Will be passed to our callback functions as a (void *)
|
||||
read, // Read callback function
|
||||
nullptr, // Write callback function (not used)
|
||||
seek); // Seek callback function
|
||||
|
||||
if (*avioContext == nullptr){
|
||||
LOGE("Failed to create AVIO context");
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
FFMpegExtractor::createAVFormatContext(AVIOContext *avioContext, AVFormatContext **avFormatContext) {
|
||||
|
||||
*avFormatContext = avformat_alloc_context();
|
||||
(*avFormatContext)->pb = avioContext;
|
||||
|
||||
if (*avFormatContext == nullptr){
|
||||
LOGE("Failed to create AVFormatContext");
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool FFMpegExtractor::openAVFormatContext(AVFormatContext *avFormatContext) {
|
||||
|
||||
int result = avformat_open_input(&avFormatContext,
|
||||
"", /* URL is left empty because we're providing our own I/O */
|
||||
nullptr /* AVInputFormat *fmt */,
|
||||
nullptr /* AVDictionary **options */
|
||||
);
|
||||
|
||||
if (result == 0) {
|
||||
return true;
|
||||
} else {
|
||||
LOGE("Failed to open file. Error code %s", av_err2str(result));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool FFMpegExtractor::getStreamInfo(AVFormatContext *avFormatContext) {
|
||||
|
||||
int result = avformat_find_stream_info(avFormatContext, nullptr);
|
||||
if (result == 0 ){
|
||||
return true;
|
||||
} else {
|
||||
LOGE("Failed to find stream info. Error code %s", av_err2str(result));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AVStream *FFMpegExtractor::getBestAudioStream(AVFormatContext *avFormatContext) {
|
||||
|
||||
int streamIndex = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
|
||||
|
||||
if (streamIndex < 0){
|
||||
LOGE("Could not find stream");
|
||||
return nullptr;
|
||||
} else {
|
||||
return avFormatContext->streams[streamIndex];
|
||||
}
|
||||
}
|
||||
|
||||
int64_t FFMpegExtractor::decode(
|
||||
AAsset *asset,
|
||||
uint8_t *targetData,
|
||||
AudioProperties targetProperties) {
|
||||
|
||||
LOGI("Decoder: FFMpeg");
|
||||
|
||||
int returnValue = -1; // -1 indicates error
|
||||
|
||||
// Create a buffer for FFmpeg to use for decoding (freed in the custom deleter below)
|
||||
auto buffer = reinterpret_cast<uint8_t*>(av_malloc(kInternalBufferSize));
|
||||
|
||||
// Create an AVIOContext with a custom deleter
|
||||
std::unique_ptr<AVIOContext, void(*)(AVIOContext *)> ioContext {
|
||||
nullptr,
|
||||
[](AVIOContext *c) {
|
||||
av_free(c->buffer);
|
||||
avio_context_free(&c);
|
||||
}
|
||||
};
|
||||
{
|
||||
AVIOContext *tmp = nullptr;
|
||||
if (!createAVIOContext(asset, buffer, kInternalBufferSize, &tmp)){
|
||||
LOGE("Could not create an AVIOContext");
|
||||
return returnValue;
|
||||
}
|
||||
ioContext.reset(tmp);
|
||||
}
|
||||
|
||||
// Create an AVFormatContext using the avformat_free_context as the deleter function
|
||||
std::unique_ptr<AVFormatContext, decltype(&avformat_free_context)> formatContext {
|
||||
nullptr,
|
||||
&avformat_free_context
|
||||
};
|
||||
{
|
||||
AVFormatContext *tmp;
|
||||
if (!createAVFormatContext(ioContext.get(), &tmp)) return returnValue;
|
||||
formatContext.reset(tmp);
|
||||
}
|
||||
|
||||
if (!openAVFormatContext(formatContext.get())) return returnValue;
|
||||
|
||||
if (!getStreamInfo(formatContext.get())) return returnValue;
|
||||
|
||||
// Obtain the best audio stream to decode
|
||||
AVStream *stream = getBestAudioStream(formatContext.get());
|
||||
if (stream == nullptr || stream->codecpar == nullptr){
|
||||
LOGE("Could not find a suitable audio stream to decode");
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
printCodecParameters(stream->codecpar);
|
||||
|
||||
// Find the codec to decode this stream
|
||||
AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id);
|
||||
if (!codec){
|
||||
LOGE("Could not find codec with ID: %d", stream->codecpar->codec_id);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
// Create the codec context, specifying the deleter function
|
||||
std::unique_ptr<AVCodecContext, void(*)(AVCodecContext *)> codecContext {
|
||||
nullptr,
|
||||
[](AVCodecContext *c) { avcodec_free_context(&c); }
|
||||
};
|
||||
{
|
||||
AVCodecContext *tmp = avcodec_alloc_context3(codec);
|
||||
if (!tmp){
|
||||
LOGE("Failed to allocate codec context");
|
||||
return returnValue;
|
||||
}
|
||||
codecContext.reset(tmp);
|
||||
}
|
||||
|
||||
// Copy the codec parameters into the context
|
||||
if (avcodec_parameters_to_context(codecContext.get(), stream->codecpar) < 0){
|
||||
LOGE("Failed to copy codec parameters to codec context");
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
// Open the codec
|
||||
if (avcodec_open2(codecContext.get(), codec, nullptr) < 0){
|
||||
LOGE("Could not open codec");
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
// prepare resampler
|
||||
int32_t outChannelLayout = (1 << targetProperties.channelCount) - 1;
|
||||
LOGD("Channel layout %d", outChannelLayout);
|
||||
|
||||
SwrContext *swr = swr_alloc();
|
||||
av_opt_set_int(swr, "in_channel_count", stream->codecpar->channels, 0);
|
||||
av_opt_set_int(swr, "out_channel_count", targetProperties.channelCount, 0);
|
||||
av_opt_set_int(swr, "in_channel_layout", stream->codecpar->channel_layout, 0);
|
||||
av_opt_set_int(swr, "out_channel_layout", outChannelLayout, 0);
|
||||
av_opt_set_int(swr, "in_sample_rate", stream->codecpar->sample_rate, 0);
|
||||
av_opt_set_int(swr, "out_sample_rate", targetProperties.sampleRate, 0);
|
||||
av_opt_set_int(swr, "in_sample_fmt", stream->codecpar->format, 0);
|
||||
av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
|
||||
av_opt_set_int(swr, "force_resampling", 1, 0);
|
||||
|
||||
// Check that resampler has been inited
|
||||
int result = swr_init(swr);
|
||||
if (result != 0){
|
||||
LOGE("swr_init failed. Error: %s", av_err2str(result));
|
||||
return returnValue;
|
||||
};
|
||||
if (!swr_is_initialized(swr)) {
|
||||
LOGE("swr_is_initialized is false\n");
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
// Prepare to read data
|
||||
int bytesWritten = 0;
|
||||
AVPacket avPacket; // Stores compressed audio data
|
||||
av_init_packet(&avPacket);
|
||||
AVFrame *decodedFrame = av_frame_alloc(); // Stores raw audio data
|
||||
int bytesPerSample = av_get_bytes_per_sample((AVSampleFormat)stream->codecpar->format);
|
||||
|
||||
LOGD("Bytes per sample %d", bytesPerSample);
|
||||
|
||||
LOGD("DECODE START");
|
||||
|
||||
// While there is more data to read, read it into the avPacket
|
||||
while (av_read_frame(formatContext.get(), &avPacket) == 0){
|
||||
|
||||
if (avPacket.stream_index == stream->index && avPacket.size > 0) {
|
||||
|
||||
// Pass our compressed data into the codec
|
||||
result = avcodec_send_packet(codecContext.get(), &avPacket);
|
||||
if (result != 0) {
|
||||
LOGE("avcodec_send_packet error: %s", av_err2str(result));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Retrieve our raw data from the codec
|
||||
result = avcodec_receive_frame(codecContext.get(), decodedFrame);
|
||||
if (result == AVERROR(EAGAIN)) {
|
||||
// The codec needs more data before it can decode
|
||||
LOGI("avcodec_receive_frame returned EAGAIN");
|
||||
av_packet_unref(&avPacket);
|
||||
continue;
|
||||
} else if (result != 0) {
|
||||
LOGE("avcodec_receive_frame error: %s", av_err2str(result));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// DO RESAMPLING
|
||||
auto dst_nb_samples = (int32_t) av_rescale_rnd(
|
||||
swr_get_delay(swr, decodedFrame->sample_rate) + decodedFrame->nb_samples,
|
||||
targetProperties.sampleRate,
|
||||
decodedFrame->sample_rate,
|
||||
AV_ROUND_UP);
|
||||
|
||||
short *buffer1;
|
||||
av_samples_alloc(
|
||||
(uint8_t **) &buffer1,
|
||||
nullptr,
|
||||
targetProperties.channelCount,
|
||||
dst_nb_samples,
|
||||
AV_SAMPLE_FMT_FLT,
|
||||
0);
|
||||
int frame_count = swr_convert(
|
||||
swr,
|
||||
(uint8_t **) &buffer1,
|
||||
dst_nb_samples,
|
||||
(const uint8_t **) decodedFrame->data,
|
||||
decodedFrame->nb_samples);
|
||||
|
||||
int64_t bytesToWrite = frame_count * sizeof(float) * targetProperties.channelCount;
|
||||
memcpy(targetData + bytesWritten, buffer1, (size_t)bytesToWrite);
|
||||
bytesWritten += bytesToWrite;
|
||||
av_freep(&buffer1);
|
||||
|
||||
av_packet_unref(&avPacket);
|
||||
}
|
||||
}
|
||||
|
||||
av_frame_free(&decodedFrame);
|
||||
LOGD("DECODE END");
|
||||
|
||||
returnValue = bytesWritten;
|
||||
|
||||
cleanup:
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
void FFMpegExtractor::printCodecParameters(AVCodecParameters *params) {
|
||||
|
||||
LOGD("Stream properties");
|
||||
LOGD("Channels: %d", params->channels);
|
||||
LOGD("Channel layout: %" PRId64, params->channel_layout);
|
||||
LOGD("Sample rate: %d", params->sample_rate);
|
||||
LOGD("Format: %s", av_get_sample_fmt_name((AVSampleFormat)params->format));
|
||||
LOGD("Frame size: %d", params->frame_size);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
55
externals/oboe/samples/RhythmGame/src/main/cpp/audio/FFMpegExtractor.h
vendored
Normal file
55
externals/oboe/samples/RhythmGame/src/main/cpp/audio/FFMpegExtractor.h
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FFMPEG_FFMPEGEXTRACTOR_H
|
||||
#define FFMPEG_FFMPEGEXTRACTOR_H
|
||||
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libavutil/opt.h>
|
||||
}
|
||||
|
||||
#include <cstdint>
|
||||
#include <android/asset_manager.h>
|
||||
#include <GameConstants.h>
|
||||
|
||||
class FFMpegExtractor {
|
||||
public:
|
||||
static int64_t decode(AAsset *asset, uint8_t *targetData, AudioProperties targetProperties);
|
||||
|
||||
private:
|
||||
static bool createAVIOContext(AAsset *asset, uint8_t *buffer, uint32_t bufferSize,
|
||||
AVIOContext **avioContext);
|
||||
|
||||
static bool createAVFormatContext(AVIOContext *avioContext, AVFormatContext **avFormatContext);
|
||||
|
||||
static bool openAVFormatContext(AVFormatContext *avFormatContext);
|
||||
|
||||
static int32_t cleanup(AVIOContext *avioContext, AVFormatContext *avFormatContext);
|
||||
|
||||
static bool getStreamInfo(AVFormatContext *avFormatContext);
|
||||
|
||||
static AVStream *getBestAudioStream(AVFormatContext *avFormatContext);
|
||||
|
||||
static AVCodec *findCodec(AVCodecID id);
|
||||
|
||||
static void printCodecParameters(AVCodecParameters *params);
|
||||
};
|
||||
|
||||
|
||||
#endif //FFMPEG_FFMPEGEXTRACTOR_H
|
199
externals/oboe/samples/RhythmGame/src/main/cpp/audio/NDKExtractor.cpp
vendored
Normal file
199
externals/oboe/samples/RhythmGame/src/main/cpp/audio/NDKExtractor.cpp
vendored
Normal file
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <media/NdkMediaExtractor.h>
|
||||
#include <utils/logging.h>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "NDKExtractor.h"
|
||||
|
||||
int32_t NDKExtractor::decode(AAsset *asset, uint8_t *targetData, AudioProperties targetProperties) {
|
||||
|
||||
LOGD("Using NDK decoder");
|
||||
|
||||
// open asset as file descriptor
|
||||
off_t start, length;
|
||||
int fd = AAsset_openFileDescriptor(asset, &start, &length);
|
||||
|
||||
// Extract the audio frames
|
||||
AMediaExtractor *extractor = AMediaExtractor_new();
|
||||
media_status_t amresult = AMediaExtractor_setDataSourceFd(extractor, fd,
|
||||
static_cast<off64_t>(start),
|
||||
static_cast<off64_t>(length));
|
||||
if (amresult != AMEDIA_OK){
|
||||
LOGE("Error setting extractor data source, err %d", amresult);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Specify our desired output format by creating it from our source
|
||||
AMediaFormat *format = AMediaExtractor_getTrackFormat(extractor, 0);
|
||||
|
||||
int32_t sampleRate;
|
||||
if (AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &sampleRate)){
|
||||
LOGD("Source sample rate %d", sampleRate);
|
||||
if (sampleRate != targetProperties.sampleRate){
|
||||
LOGE("Input (%d) and output (%d) sample rates do not match. "
|
||||
"NDK decoder does not support resampling.",
|
||||
sampleRate,
|
||||
targetProperties.sampleRate);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
LOGE("Failed to get sample rate");
|
||||
return 0;
|
||||
};
|
||||
|
||||
int32_t channelCount;
|
||||
if (AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &channelCount)){
|
||||
LOGD("Got channel count %d", channelCount);
|
||||
if (channelCount != targetProperties.channelCount){
|
||||
LOGE("NDK decoder does not support different "
|
||||
"input (%d) and output (%d) channel counts",
|
||||
channelCount,
|
||||
targetProperties.channelCount);
|
||||
}
|
||||
} else {
|
||||
LOGE("Failed to get channel count");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *formatStr = AMediaFormat_toString(format);
|
||||
LOGD("Output format %s", formatStr);
|
||||
|
||||
const char *mimeType;
|
||||
if (AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mimeType)) {
|
||||
LOGD("Got mime type %s", mimeType);
|
||||
} else {
|
||||
LOGE("Failed to get mime type");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Obtain the correct decoder
|
||||
AMediaCodec *codec = nullptr;
|
||||
AMediaExtractor_selectTrack(extractor, 0);
|
||||
codec = AMediaCodec_createDecoderByType(mimeType);
|
||||
AMediaCodec_configure(codec, format, nullptr, nullptr, 0);
|
||||
AMediaCodec_start(codec);
|
||||
|
||||
// DECODE
|
||||
|
||||
bool isExtracting = true;
|
||||
bool isDecoding = true;
|
||||
int32_t bytesWritten = 0;
|
||||
|
||||
while(isExtracting || isDecoding){
|
||||
|
||||
if (isExtracting){
|
||||
|
||||
// Obtain the index of the next available input buffer
|
||||
ssize_t inputIndex = AMediaCodec_dequeueInputBuffer(codec, 2000);
|
||||
//LOGV("Got input buffer %d", inputIndex);
|
||||
|
||||
// The input index acts as a status if its negative
|
||||
if (inputIndex < 0){
|
||||
if (inputIndex == AMEDIACODEC_INFO_TRY_AGAIN_LATER){
|
||||
// LOGV("Codec.dequeueInputBuffer try again later");
|
||||
} else {
|
||||
LOGE("Codec.dequeueInputBuffer unknown error status");
|
||||
}
|
||||
} else {
|
||||
|
||||
// Obtain the actual buffer and read the encoded data into it
|
||||
size_t inputSize;
|
||||
uint8_t *inputBuffer = AMediaCodec_getInputBuffer(codec, inputIndex, &inputSize);
|
||||
//LOGV("Sample size is: %d", inputSize);
|
||||
|
||||
ssize_t sampleSize = AMediaExtractor_readSampleData(extractor, inputBuffer, inputSize);
|
||||
auto presentationTimeUs = AMediaExtractor_getSampleTime(extractor);
|
||||
|
||||
if (sampleSize > 0){
|
||||
|
||||
// Enqueue the encoded data
|
||||
AMediaCodec_queueInputBuffer(codec, inputIndex, 0, sampleSize,
|
||||
presentationTimeUs,
|
||||
0);
|
||||
AMediaExtractor_advance(extractor);
|
||||
|
||||
} else {
|
||||
LOGD("End of extractor data stream");
|
||||
isExtracting = false;
|
||||
|
||||
// We need to tell the codec that we've reached the end of the stream
|
||||
AMediaCodec_queueInputBuffer(codec, inputIndex, 0, 0,
|
||||
presentationTimeUs,
|
||||
AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isDecoding){
|
||||
// Dequeue the decoded data
|
||||
AMediaCodecBufferInfo info;
|
||||
ssize_t outputIndex = AMediaCodec_dequeueOutputBuffer(codec, &info, 0);
|
||||
|
||||
if (outputIndex >= 0){
|
||||
|
||||
// Check whether this is set earlier
|
||||
if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM){
|
||||
LOGD("Reached end of decoding stream");
|
||||
isDecoding = false;
|
||||
}
|
||||
|
||||
// Valid index, acquire buffer
|
||||
size_t outputSize;
|
||||
uint8_t *outputBuffer = AMediaCodec_getOutputBuffer(codec, outputIndex, &outputSize);
|
||||
|
||||
/*LOGV("Got output buffer index %d, buffer size: %d, info size: %d writing to pcm index %d",
|
||||
outputIndex,
|
||||
outputSize,
|
||||
info.size,
|
||||
m_writeIndex);*/
|
||||
|
||||
// copy the data out of the buffer
|
||||
memcpy(targetData + bytesWritten, outputBuffer, info.size);
|
||||
bytesWritten+=info.size;
|
||||
AMediaCodec_releaseOutputBuffer(codec, outputIndex, false);
|
||||
} else {
|
||||
|
||||
// The outputIndex doubles as a status return if its value is < 0
|
||||
switch(outputIndex){
|
||||
case AMEDIACODEC_INFO_TRY_AGAIN_LATER:
|
||||
LOGD("dequeueOutputBuffer: try again later");
|
||||
break;
|
||||
case AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED:
|
||||
LOGD("dequeueOutputBuffer: output buffers changed");
|
||||
break;
|
||||
case AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED:
|
||||
LOGD("dequeueOutputBuffer: output outputFormat changed");
|
||||
format = AMediaCodec_getOutputFormat(codec);
|
||||
LOGD("outputFormat changed to: %s", AMediaFormat_toString(format));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
AMediaFormat_delete(format);
|
||||
AMediaCodec_delete(codec);
|
||||
AMediaExtractor_delete(extractor);
|
||||
|
||||
return bytesWritten;
|
||||
}
|
33
externals/oboe/samples/RhythmGame/src/main/cpp/audio/NDKExtractor.h
vendored
Normal file
33
externals/oboe/samples/RhythmGame/src/main/cpp/audio/NDKExtractor.h
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FFMPEG_NDKMEDIAEXTRACTOR_H
|
||||
#define FFMPEG_NDKMEDIAEXTRACTOR_H
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
#include <android/asset_manager.h>
|
||||
#include <GameConstants.h>
|
||||
|
||||
|
||||
class NDKExtractor {
|
||||
|
||||
public:
|
||||
static int32_t decode(AAsset *asset, uint8_t *targetData, AudioProperties targetProperties);
|
||||
};
|
||||
|
||||
|
||||
#endif //FFMPEG_NDKMEDIAEXTRACTOR_H
|
59
externals/oboe/samples/RhythmGame/src/main/cpp/audio/Player.cpp
vendored
Normal file
59
externals/oboe/samples/RhythmGame/src/main/cpp/audio/Player.cpp
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Player.h"
|
||||
#include "utils/logging.h"
|
||||
|
||||
void Player::renderAudio(float *targetData, int32_t numFrames){
|
||||
|
||||
const AudioProperties properties = mSource->getProperties();
|
||||
|
||||
if (mIsPlaying){
|
||||
|
||||
int64_t framesToRenderFromData = numFrames;
|
||||
int64_t totalSourceFrames = mSource->getSize() / properties.channelCount;
|
||||
const float *data = mSource->getData();
|
||||
|
||||
// Check whether we're about to reach the end of the recording
|
||||
if (!mIsLooping && mReadFrameIndex + numFrames >= totalSourceFrames){
|
||||
framesToRenderFromData = totalSourceFrames - mReadFrameIndex;
|
||||
mIsPlaying = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < framesToRenderFromData; ++i) {
|
||||
for (int j = 0; j < properties.channelCount; ++j) {
|
||||
targetData[(i*properties.channelCount)+j] = data[(mReadFrameIndex*properties.channelCount)+j];
|
||||
}
|
||||
|
||||
// Increment and handle wraparound
|
||||
if (++mReadFrameIndex >= totalSourceFrames) mReadFrameIndex = 0;
|
||||
}
|
||||
|
||||
if (framesToRenderFromData < numFrames){
|
||||
// fill the rest of the buffer with silence
|
||||
renderSilence(&targetData[framesToRenderFromData], numFrames * properties.channelCount);
|
||||
}
|
||||
|
||||
} else {
|
||||
renderSilence(targetData, numFrames * properties.channelCount);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::renderSilence(float *start, int32_t numSamples){
|
||||
for (int i = 0; i < numSamples; ++i) {
|
||||
start[i] = 0;
|
||||
}
|
||||
}
|
60
externals/oboe/samples/RhythmGame/src/main/cpp/audio/Player.h
vendored
Normal file
60
externals/oboe/samples/RhythmGame/src/main/cpp/audio/Player.h
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef RHYTHMGAME_SOUNDRECORDING_H
|
||||
#define RHYTHMGAME_SOUNDRECORDING_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
|
||||
#include <android/asset_manager.h>
|
||||
|
||||
#include "shared/IRenderableAudio.h"
|
||||
#include "DataSource.h"
|
||||
|
||||
class Player : public IRenderableAudio{
|
||||
|
||||
public:
|
||||
/**
|
||||
* Construct a new Player from the given DataSource. Players can share the same data source.
|
||||
* For example, you could play two identical sounds concurrently by creating 2 Players with the
|
||||
* same data source.
|
||||
*
|
||||
* @param source
|
||||
*/
|
||||
Player(std::shared_ptr<DataSource> source)
|
||||
: mSource(source)
|
||||
{};
|
||||
|
||||
void renderAudio(float *targetData, int32_t numFrames);
|
||||
void resetPlayHead() { mReadFrameIndex = 0; };
|
||||
void setPlaying(bool isPlaying) { mIsPlaying = isPlaying; resetPlayHead(); };
|
||||
void setLooping(bool isLooping) { mIsLooping = isLooping; };
|
||||
|
||||
private:
|
||||
int32_t mReadFrameIndex = 0;
|
||||
std::atomic<bool> mIsPlaying { false };
|
||||
std::atomic<bool> mIsLooping { false };
|
||||
std::shared_ptr<DataSource> mSource;
|
||||
|
||||
void renderSilence(float*, int32_t);
|
||||
};
|
||||
|
||||
#endif //RHYTHMGAME_SOUNDRECORDING_H
|
93
externals/oboe/samples/RhythmGame/src/main/cpp/native-lib.cpp
vendored
Normal file
93
externals/oboe/samples/RhythmGame/src/main/cpp/native-lib.cpp
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <jni.h>
|
||||
#include <memory>
|
||||
|
||||
#include <android/asset_manager_jni.h>
|
||||
|
||||
#include "utils/logging.h"
|
||||
#include "Game.h"
|
||||
|
||||
|
||||
extern "C" {
|
||||
|
||||
std::unique_ptr<Game> game;
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_oboe_samples_rhythmgame_MainActivity_native_1onStart(JNIEnv *env, jobject instance,
|
||||
jobject jAssetManager) {
|
||||
|
||||
AAssetManager *assetManager = AAssetManager_fromJava(env, jAssetManager);
|
||||
if (assetManager == nullptr) {
|
||||
LOGE("Could not obtain the AAssetManager");
|
||||
return;
|
||||
}
|
||||
|
||||
game = std::make_unique<Game>(*assetManager);
|
||||
game->start();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_oboe_samples_rhythmgame_RendererWrapper_native_1onSurfaceCreated(JNIEnv *env,
|
||||
jobject instance) {
|
||||
game->onSurfaceCreated();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_oboe_samples_rhythmgame_RendererWrapper_native_1onSurfaceChanged(JNIEnv *env,
|
||||
jclass type,
|
||||
jint width,
|
||||
jint height) {
|
||||
game->onSurfaceChanged(width, height);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_oboe_samples_rhythmgame_RendererWrapper_native_1onDrawFrame(JNIEnv *env,
|
||||
jclass type) {
|
||||
game->tick();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_oboe_samples_rhythmgame_GameSurfaceView_native_1onTouchInput(JNIEnv *env,
|
||||
jclass type,
|
||||
jint event_type,
|
||||
jlong time_since_boot_ms,
|
||||
jint pixel_x,
|
||||
jint pixel_y) {
|
||||
game->tap(time_since_boot_ms);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_oboe_samples_rhythmgame_GameSurfaceView_native_1surfaceDestroyed__(JNIEnv *env,
|
||||
jclass type) {
|
||||
game->onSurfaceDestroyed();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_oboe_samples_rhythmgame_MainActivity_native_1onStop(JNIEnv *env, jobject instance) {
|
||||
|
||||
game->stop();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_oboe_samples_rhythmgame_MainActivity_native_1setDefaultStreamValues(JNIEnv *env,
|
||||
jclass type,
|
||||
jint sampleRate,
|
||||
jint framesPerBurst) {
|
||||
oboe::DefaultStreamValues::SampleRate = (int32_t) sampleRate;
|
||||
oboe::DefaultStreamValues::FramesPerBurst = (int32_t) framesPerBurst;
|
||||
}
|
||||
} // extern "C"
|
33
externals/oboe/samples/RhythmGame/src/main/cpp/ui/OpenGLFunctions.cpp
vendored
Normal file
33
externals/oboe/samples/RhythmGame/src/main/cpp/ui/OpenGLFunctions.cpp
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "OpenGLFunctions.h"
|
||||
#include "utils/logging.h"
|
||||
|
||||
void CheckOpenGLError(const char* stmt, const char* fname, int line)
|
||||
{
|
||||
GLenum err = glGetError();
|
||||
if (err != GL_NO_ERROR)
|
||||
{
|
||||
LOGW("OpenGL error %08x, at %s:%i - for %s\n", err, fname, line, stmt);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SetGLScreenColor(ScreenColor color){
|
||||
glClearColor(color.red, color.green, color.blue, color.alpha);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
58
externals/oboe/samples/RhythmGame/src/main/cpp/ui/OpenGLFunctions.h
vendored
Normal file
58
externals/oboe/samples/RhythmGame/src/main/cpp/ui/OpenGLFunctions.h
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_OGL_H
|
||||
#define ANDROID_OGL_H
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
struct ScreenColor {
|
||||
float red;
|
||||
float green;
|
||||
float blue;
|
||||
float alpha;
|
||||
};
|
||||
|
||||
constexpr ScreenColor RED { 1.0f, 0.0f, 0.0f, 1.0f };
|
||||
constexpr ScreenColor GREEN { 0.0f, 1.0f, 0.0f, 1.0f };
|
||||
constexpr ScreenColor BLUE { 0.0f, 0.0f, 1.0f, 1.0f };
|
||||
constexpr ScreenColor PURPLE { 1.0f, 0.0f, 1.0f, 1.0f };
|
||||
constexpr ScreenColor ORANGE { 1.0f, 0.5f, 0.0f, 1.0f };
|
||||
constexpr ScreenColor GREY { 0.3f, 0.3f, 0.3f, 0.3f };
|
||||
constexpr ScreenColor YELLOW { 1.0f, 1.0f, 0.0f, 1.0f };
|
||||
|
||||
#ifdef GL3
|
||||
#include <GLES3/gl3.h>
|
||||
#elif GL3_2
|
||||
#include <GLES3/gl32.h>
|
||||
#else
|
||||
#include <GLES2/gl2.h>
|
||||
#include <GLES2/gl2ext.h>
|
||||
#endif
|
||||
|
||||
void CheckOpenGLError(const char* stmt, const char* fname, int line);
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define GL_CHECK(stmt) \
|
||||
stmt;\
|
||||
CheckOpenGLError(#stmt, __FILE__, __LINE__);
|
||||
#else
|
||||
#define GL_CHECK(stmt) stmt
|
||||
#endif
|
||||
|
||||
void SetGLScreenColor(ScreenColor color);
|
||||
|
||||
#endif //ANDROID_OGL_H
|
154
externals/oboe/samples/RhythmGame/src/main/cpp/utils/LockFreeQueue.h
vendored
Normal file
154
externals/oboe/samples/RhythmGame/src/main/cpp/utils/LockFreeQueue.h
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef RHYTHMGAME_LOCKFREEQUEUE_H
|
||||
#define RHYTHMGAME_LOCKFREEQUEUE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
|
||||
/**
|
||||
* A lock-free queue for single consumer, single producer. Not thread-safe when using multiple
|
||||
* consumers or producers.
|
||||
*
|
||||
* Example code:
|
||||
*
|
||||
* LockFreeQueue<int, 1024> myQueue;
|
||||
* int value = 123;
|
||||
* myQueue.push(value);
|
||||
* myQueue.pop(value);
|
||||
*
|
||||
* @tparam T - The item type
|
||||
* @tparam CAPACITY - Maximum number of items which can be held in the queue. Must be a power of 2.
|
||||
* Must be less than the maximum value permissible in INDEX_TYPE
|
||||
* @tparam INDEX_TYPE - The internal index type, defaults to uint32_t. Changing this will affect
|
||||
* the maximum capacity. Included for ease of unit testing because testing queue lengths of
|
||||
* UINT32_MAX can be time consuming and is not always possible.
|
||||
*/
|
||||
|
||||
template <typename T, uint32_t CAPACITY, typename INDEX_TYPE = uint32_t>
|
||||
class LockFreeQueue {
|
||||
public:
|
||||
|
||||
/**
|
||||
* Implementation details:
|
||||
*
|
||||
* We have 2 counters: readCounter and writeCounter. Each will increment until it reaches
|
||||
* INDEX_TYPE_MAX, then wrap to zero. Unsigned integer overflow is defined behaviour in C++.
|
||||
*
|
||||
* Each time we need to access our data array we call mask() which gives us the index into the
|
||||
* array. This approach avoids having a "dead item" in the buffer to distinguish between full
|
||||
* and empty states. It also allows us to have a size() method which is easily calculated.
|
||||
*
|
||||
* IMPORTANT: This implementation is only thread-safe with a single reader thread and a single
|
||||
* writer thread. Have more than one of either will result in Bad Things™.
|
||||
*/
|
||||
|
||||
static constexpr bool isPowerOfTwo(uint32_t n) { return (n & (n - 1)) == 0; }
|
||||
static_assert(isPowerOfTwo(CAPACITY), "Capacity must be a power of 2");
|
||||
static_assert(std::is_unsigned<INDEX_TYPE>::value, "Index type must be unsigned");
|
||||
|
||||
/**
|
||||
* Pop a value off the head of the queue
|
||||
*
|
||||
* @param val - element will be stored in this variable
|
||||
* @return true if value was popped successfully, false if the queue is empty
|
||||
*/
|
||||
bool pop(T &val) {
|
||||
if (isEmpty()){
|
||||
return false;
|
||||
} else {
|
||||
val = buffer[mask(readCounter)];
|
||||
++readCounter;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the back of the queue
|
||||
*
|
||||
* @param item - The item to add
|
||||
* @return true if item was added, false if the queue was full
|
||||
*/
|
||||
bool push(const T& item) {
|
||||
if (isFull()){
|
||||
return false;
|
||||
} else {
|
||||
buffer[mask(writeCounter)] = item;
|
||||
++writeCounter;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item at the front of the queue but do not remove it
|
||||
*
|
||||
* @param item - item will be stored in this variable
|
||||
* @return true if item was stored, false if the queue was empty
|
||||
*/
|
||||
bool peek(T &item) const {
|
||||
if (isEmpty()){
|
||||
return false;
|
||||
} else {
|
||||
item = buffer[mask(readCounter)];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of items in the queue
|
||||
*
|
||||
* @return number of items in the queue
|
||||
*/
|
||||
INDEX_TYPE size() const {
|
||||
|
||||
/**
|
||||
* This is worth some explanation:
|
||||
*
|
||||
* Whilst writeCounter is greater than readCounter the result of (write - read) will always
|
||||
* be positive. Simple.
|
||||
*
|
||||
* But when writeCounter is equal to INDEX_TYPE_MAX (e.g. UINT32_MAX) the next push will
|
||||
* wrap it around to zero, the start of the buffer, making writeCounter less than
|
||||
* readCounter so the result of (write - read) will be negative.
|
||||
*
|
||||
* But because we're returning an unsigned type return value will be as follows:
|
||||
*
|
||||
* returnValue = INDEX_TYPE_MAX - (write - read)
|
||||
*
|
||||
* e.g. if write is 0, read is 150 and the INDEX_TYPE is uint8_t where the max value is
|
||||
* 255 the return value will be (255 - (0 - 150)) = 105.
|
||||
*
|
||||
*/
|
||||
return writeCounter - readCounter;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
bool isEmpty() const { return readCounter == writeCounter; }
|
||||
|
||||
bool isFull() const { return size() == CAPACITY; }
|
||||
|
||||
INDEX_TYPE mask(INDEX_TYPE n) const { return static_cast<INDEX_TYPE>(n & (CAPACITY - 1)); }
|
||||
|
||||
T buffer[CAPACITY];
|
||||
std::atomic<INDEX_TYPE> writeCounter { 0 };
|
||||
std::atomic<INDEX_TYPE> readCounter { 0 };
|
||||
|
||||
};
|
||||
|
||||
#endif //RHYTHMGAME_LOCKFREEQUEUE_H
|
40
externals/oboe/samples/RhythmGame/src/main/cpp/utils/UtilityFunctions.cpp
vendored
Normal file
40
externals/oboe/samples/RhythmGame/src/main/cpp/utils/UtilityFunctions.cpp
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <ui/OpenGLFunctions.h>
|
||||
#include <GameConstants.h>
|
||||
#include "UtilityFunctions.h"
|
||||
#include "logging.h"
|
||||
|
||||
int64_t nowUptimeMillis() {
|
||||
using namespace std::chrono;
|
||||
return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
void renderEvent(TapResult r){
|
||||
switch (r) {
|
||||
case TapResult::Success:
|
||||
SetGLScreenColor(kTapSuccessColor);
|
||||
break;
|
||||
case TapResult::Early:
|
||||
SetGLScreenColor(kTapEarlyColor);
|
||||
break;
|
||||
case TapResult::Late:
|
||||
SetGLScreenColor(kTapLateColor);
|
||||
break;
|
||||
}
|
||||
}
|
42
externals/oboe/samples/RhythmGame/src/main/cpp/utils/UtilityFunctions.h
vendored
Normal file
42
externals/oboe/samples/RhythmGame/src/main/cpp/utils/UtilityFunctions.h
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef RHYTHMGAME_UTILITYFUNCTIONS_H
|
||||
#define RHYTHMGAME_UTILITYFUNCTIONS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
constexpr int64_t kMillisecondsInSecond = 1000;
|
||||
constexpr int64_t kNanosecondsInMillisecond = 1000000;
|
||||
|
||||
enum class TapResult {
|
||||
Early,
|
||||
Late,
|
||||
Success
|
||||
};
|
||||
|
||||
int64_t nowUptimeMillis();
|
||||
|
||||
constexpr int64_t convertFramesToMillis(const int64_t frames, const int sampleRate){
|
||||
return static_cast<int64_t>((static_cast<double>(frames)/ sampleRate) * kMillisecondsInSecond);
|
||||
}
|
||||
|
||||
TapResult getTapResult(int64_t tapTimeInMillis, int64_t tapWindowInMillis);
|
||||
|
||||
void renderEvent(TapResult r);
|
||||
|
||||
|
||||
#endif //RHYTHMGAME_UTILITYFUNCTIONS_H
|
32
externals/oboe/samples/RhythmGame/src/main/cpp/utils/logging.h
vendored
Normal file
32
externals/oboe/samples/RhythmGame/src/main/cpp/utils/logging.h
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_LOGGING_H
|
||||
#define ANDROID_LOGGING_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <android/log.h>
|
||||
#include <vector>
|
||||
|
||||
#define APP_NAME "RhythmGame"
|
||||
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, APP_NAME, __VA_ARGS__))
|
||||
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, APP_NAME, __VA_ARGS__))
|
||||
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, APP_NAME, __VA_ARGS__))
|
||||
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, APP_NAME, __VA_ARGS__))
|
||||
|
||||
|
||||
|
||||
#endif //ANDROID_LOGGING_H
|
Loading…
Add table
Add a link
Reference in a new issue