From 01f746ec35ebd3832bb8559646e684b828e872e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 27 Sep 2019 23:37:53 +0200 Subject: [PATCH] Fix Android Audio hard --- android/app/CMakeLists.txt | 5 +- android/app/src/main/cpp/audio-decoder.c | 7 +- android/app/src/main/cpp/audio-output.cpp | 84 ++--------- android/app/src/main/cpp/circular-buf.hpp | 153 +++++++++++++++++++++ android/app/src/main/cpp/circular-fifo.hpp | 108 +++++++++++++++ 5 files changed, 284 insertions(+), 73 deletions(-) create mode 100644 android/app/src/main/cpp/circular-buf.hpp create mode 100644 android/app/src/main/cpp/circular-fifo.hpp diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt index 212ca9b..21db99c 100644 --- a/android/app/CMakeLists.txt +++ b/android/app/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.2) +set(CMAKE_CXX_STANDARD 14) + add_library(chiaki-jni SHARED src/main/cpp/chiaki-jni.c src/main/cpp/video-decoder.h @@ -8,7 +10,8 @@ add_library(chiaki-jni SHARED src/main/cpp/audio-decoder.h src/main/cpp/audio-decoder.c src/main/cpp/audio-output.h - src/main/cpp/audio-output.cpp) + src/main/cpp/audio-output.cpp + src/main/cpp/circular-fifo.hpp) target_link_libraries(chiaki-jni chiaki-lib) find_library(ANDROID_LIB_LOG log) diff --git a/android/app/src/main/cpp/audio-decoder.c b/android/app/src/main/cpp/audio-decoder.c index 1eb16df..8d52a12 100644 --- a/android/app/src/main/cpp/audio-decoder.c +++ b/android/app/src/main/cpp/audio-decoder.c @@ -66,14 +66,15 @@ static void *android_chiaki_audio_decoder_output_thread_func(void *user) ssize_t codec_buf_index = AMediaCodec_dequeueOutputBuffer(decoder->codec, &info, -1); if(codec_buf_index >= 0) { - CHIAKI_LOGD(decoder->log, "Got decoded audio of size %d", (int)info.size); if(decoder->settings_cb) { size_t codec_buf_size; uint8_t *codec_buf = AMediaCodec_getOutputBuffer(decoder->codec, (size_t)codec_buf_index, &codec_buf_size); - decoder->frame_cb((int16_t *)codec_buf, codec_buf_size / 2, decoder->cb_user); + size_t samples_count = info.size / sizeof(int16_t); + //CHIAKI_LOGD(decoder->log, "Got %llu samples => %f ms of audio", (unsigned long long)samples_count, 1000.0f * (float)(samples_count / 2) / (float)decoder->audio_header.rate); + decoder->frame_cb((int16_t *)codec_buf, samples_count, decoder->cb_user); } - AMediaCodec_releaseOutputBuffer(decoder->codec, (size_t)codec_buf_index, info.size != 0); + AMediaCodec_releaseOutputBuffer(decoder->codec, (size_t)codec_buf_index, false); if(info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) { CHIAKI_LOGI(decoder->log, "AMediaCodec for Audio Decoder reported EOS"); diff --git a/android/app/src/main/cpp/audio-output.cpp b/android/app/src/main/cpp/audio-output.cpp index f9f3ad1..1961ff8 100644 --- a/android/app/src/main/cpp/audio-output.cpp +++ b/android/app/src/main/cpp/audio-output.cpp @@ -17,12 +17,17 @@ #include "audio-output.h" +#include "circular-buf.hpp" + #include #include #include -#define BUFFER_SIZE_DEFAULT 409600 +#define BUFFER_CHUNK_SIZE 512 +#define BUFFER_CHUNKS_COUNT 128 + +using AudioBuffer = CircularBuffer; class AudioOutput; @@ -43,12 +48,7 @@ struct AudioOutput ChiakiLog *log; oboe::ManagedStream stream; AudioOutputCallback stream_callback; - - ChiakiMutex buffer_mutex; // TODO: make lockless - uint8_t *buffer; - size_t buffer_size; // total size - size_t buffer_start; - size_t buffer_length; // filled size + AudioBuffer buf; AudioOutput() : stream_callback(this) {} }; @@ -57,23 +57,6 @@ extern "C" void *android_chiaki_audio_output_new(ChiakiLog *log) { auto r = new AudioOutput(); r->log = log; - - if(chiaki_mutex_init(&r->buffer_mutex, false) != CHIAKI_ERR_SUCCESS) - { - delete r; - return nullptr; - } - r->buffer_size = BUFFER_SIZE_DEFAULT; - r->buffer = (uint8_t *)malloc(r->buffer_size); - if(!r->buffer) - { - chiaki_mutex_fini(&r->buffer_mutex); - delete r; - return nullptr; - } - r->buffer_start = 0; - r->buffer_length = 0; - return r; } @@ -83,8 +66,6 @@ extern "C" void android_chiaki_audio_output_free(void *audio_output) return; auto ao = reinterpret_cast(audio_output); ao->stream = nullptr; - free(ao->buffer); - chiaki_mutex_fini(&ao->buffer_mutex); delete ao; } @@ -116,29 +97,11 @@ extern "C" void android_chiaki_audio_output_settings(uint32_t channels, uint32_t extern "C" void android_chiaki_audio_output_frame(int16_t *buf, size_t samples_count, void *audio_output) { auto ao = reinterpret_cast(audio_output); - ChiakiMutexLock lock(&ao->buffer_mutex); size_t buf_size = samples_count * sizeof(int16_t); - if(ao->buffer_size - ao->buffer_length < buf_size) - { + size_t pushed = ao->buf.Push(reinterpret_cast(buf), buf_size); + if(pushed < buf_size) CHIAKI_LOGW(ao->log, "Audio Output Buffer Overflow!"); - buf_size = ao->buffer_size - ao->buffer_length; - } - - if(!buf_size) - return; - - size_t end = (ao->buffer_start + ao->buffer_length) % ao->buffer_size; - ao->buffer_length += buf_size; - if(end + buf_size > ao->buffer_size) - { - size_t part = ao->buffer_size - end; - memcpy(ao->buffer + end, buf, part); - buf_size -= part; - buf = (int16_t *)(((uint8_t *)buf) + part); - end = 0; - } - memcpy(ao->buffer + end, buf, buf_size); } oboe::DataCallbackResult AudioOutputCallback::onAudioReady(oboe::AudioStream *stream, void *audio_data, int32_t num_frames) @@ -151,33 +114,16 @@ oboe::DataCallbackResult AudioOutputCallback::onAudioReady(oboe::AudioStream *st int32_t bytes_per_frame = stream->getBytesPerFrame(); size_t buf_size_requested = static_cast(bytes_per_frame * num_frames); + auto buf = reinterpret_cast(audio_data); - size_t buf_size_delivered = buf_size_requested; - uint8_t *buf = (uint8_t *)audio_data; - - { - ChiakiMutexLock lock(&audio_output->buffer_mutex); - if(audio_output->buffer_length < buf_size_delivered) - { - CHIAKI_LOGW(audio_output->log, "Audio Output Buffer Underflow!"); - buf_size_delivered = audio_output->buffer_length; - } - - if(audio_output->buffer_start + buf_size_delivered > audio_output->buffer_size) - { - size_t part = audio_output->buffer_size - audio_output->buffer_start; - memcpy(buf, audio_output->buffer + audio_output->buffer_start, part); - memcpy(buf + part, audio_output->buffer, buf_size_delivered - part); - } - else - memcpy(buf, audio_output->buffer + audio_output->buffer_start, buf_size_delivered); - - audio_output->buffer_start = (audio_output->buffer_start + buf_size_delivered) % audio_output->buffer_size; - audio_output->buffer_length -= buf_size_delivered; - } + size_t buf_size_delivered = audio_output->buf.Pop(buf, buf_size_requested); + //CHIAKI_LOGW(audio_output->log, "Delivered %llu", (unsigned long long)buf_size_delivered); if(buf_size_delivered < buf_size_requested) + { + CHIAKI_LOGW(audio_output->log, "Audio Output Buffer Underflow!"); memset(buf + buf_size_delivered, 0, buf_size_requested - buf_size_delivered); + } return oboe::DataCallbackResult::Continue; } diff --git a/android/app/src/main/cpp/circular-buf.hpp b/android/app/src/main/cpp/circular-buf.hpp new file mode 100644 index 0000000..73aa38f --- /dev/null +++ b/android/app/src/main/cpp/circular-buf.hpp @@ -0,0 +1,153 @@ +/* + * This file is part of Chiaki. + * + * Chiaki 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 3 of the License, or + * (at your option) any later version. + * + * Chiaki 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 Chiaki. If not, see . + */ + +#ifndef CHIAKI_JNI_CIRCULARBUF_HPP +#define CHIAKI_JNI_CIRCULARBUF_HPP + +#include "circular-fifo.hpp" + +#include +#include + +#include + +template +class CircularBuffer +{ + static_assert(ChunksCount > 0); + static_assert(ChunkSize > 0); + + private: + using Queue = memory_relaxed_aquire_release::CircularFifo; + Queue full_queue; + Queue free_queue; + uint8_t *buffer; + + uint8_t *push_chunk; + size_t push_chunk_size; // written bytes from the start of the chunk + + uint8_t *pop_chunk; + size_t pop_chunk_size; // remaining bytes until the end of the chunk + + void FlushChunks() + { + for(size_t i=1; i remaining_space) + to_push = remaining_space; + memcpy(push_chunk + push_chunk_size, buf + pushed, to_push); + pushed += to_push; + push_chunk_size += to_push; + + if(push_chunk_size == ChunkSize) + { + bool success = full_queue.push(push_chunk); + assert(success); // We should have made the queues big enough + push_chunk = nullptr; + } + } + + return pushed; + } + + /** + * @return bytes that were popped + */ + size_t Pop(uint8_t *buf, size_t buf_size) + { + size_t popped = 0; + while(popped < buf_size) + { + if(!pop_chunk) + { + if(!full_queue.pop(pop_chunk)) + pop_chunk = nullptr; + if(!pop_chunk) + return popped; + pop_chunk_size = ChunkSize; + } + + size_t to_pop = buf_size - popped; + if(to_pop > pop_chunk_size) + to_pop = pop_chunk_size; + memcpy(buf + popped, pop_chunk + (ChunkSize - pop_chunk_size), to_pop); + popped += to_pop; + pop_chunk_size -= to_pop; + + if(pop_chunk_size == 0) + { + bool success = free_queue.push(pop_chunk); + assert(success);// We should have made the queues big enough + pop_chunk = nullptr; + } + } + + return popped; + } +}; + + +#endif //CHIAKI_JNI_CIRCULARBUF_HPP diff --git a/android/app/src/main/cpp/circular-fifo.hpp b/android/app/src/main/cpp/circular-fifo.hpp new file mode 100644 index 0000000..f4e0e61 --- /dev/null +++ b/android/app/src/main/cpp/circular-fifo.hpp @@ -0,0 +1,108 @@ +/* +* Not any company's property but Public-Domain +* Do with source-code as you will. No requirement to keep this +* header if need to use it/change it/ or do whatever with it +* +* Note that there is No guarantee that this code will work +* and I take no responsibility for this code and any problems you +* might get if using it. +* +* Code & platform dependent issues with it was originally +* published at http://www.kjellkod.cc/threadsafecircularqueue +* 2012-16-19 @author Kjell Hedström, hedstrom@kjellkod.cc */ + +// should be mentioned the thinking of what goes where +// it is a "controversy" whether what is tail and what is head +// http://en.wikipedia.org/wiki/FIFO#Head_or_tail_first + +#ifndef CIRCULARFIFO_AQUIRE_RELEASE_H_ +#define CIRCULARFIFO_AQUIRE_RELEASE_H_ + +#include +#include +namespace memory_relaxed_aquire_release { +template +class CircularFifo{ +public: + enum { Capacity = Size+1 }; + + CircularFifo() : _tail(0), _head(0){} + virtual ~CircularFifo() {} + + bool push(const Element& item); // pushByMOve? + bool pop(Element& item); + + bool wasEmpty() const; + bool wasFull() const; + bool isLockFree() const; + +private: + size_t increment(size_t idx) const; + + std::atomic _tail; // tail(input) index + Element _array[Capacity]; + std::atomic _head; // head(output) index +}; + +template +bool CircularFifo::push(const Element& item) +{ + const auto current_tail = _tail.load(std::memory_order_relaxed); + const auto next_tail = increment(current_tail); + if(next_tail != _head.load(std::memory_order_acquire)) + { + _array[current_tail] = item; + _tail.store(next_tail, std::memory_order_release); + return true; + } + + return false; // full queue + +} + + +// Pop by Consumer can only update the head (load with relaxed, store with release) +// the tail must be accessed with at least aquire +template +bool CircularFifo::pop(Element& item) +{ + const auto current_head = _head.load(std::memory_order_relaxed); + if(current_head == _tail.load(std::memory_order_acquire)) + return false; // empty queue + + item = _array[current_head]; + _head.store(increment(current_head), std::memory_order_release); + return true; +} + +template +bool CircularFifo::wasEmpty() const +{ + // snapshot with acceptance of that this comparison operation is not atomic + return (_head.load() == _tail.load()); +} + + +// snapshot with acceptance that this comparison is not atomic +template +bool CircularFifo::wasFull() const +{ + const auto next_tail = increment(_tail.load()); // aquire, we dont know who call + return (next_tail == _head.load()); +} + + +template +bool CircularFifo::isLockFree() const +{ + return (_tail.is_lock_free() && _head.is_lock_free()); +} + +template +size_t CircularFifo::increment(size_t idx) const +{ + return (idx + 1) % Capacity; +} + +} // memory_relaxed_aquire_release +#endif /* CIRCULARFIFO_AQUIRE_RELEASE_H_ */