diff --git a/CMakeLists.txt b/CMakeLists.txt
index 51a45b2..75be96a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -60,7 +60,7 @@ set (oboe_sources
     src/common/Version.cpp
     )
 
-add_library(oboe ${oboe_sources})
+  add_library(oboe STATIC ${oboe_sources})
 
 # Specify directories which the compiler should look for headers
 target_include_directories(oboe
@@ -91,4 +91,4 @@ install(TARGETS oboe
         ARCHIVE DESTINATION lib/${ANDROID_ABI})
 
 # Also install the headers
-install(DIRECTORY include/oboe DESTINATION include)
\ No newline at end of file
+install(DIRECTORY include/oboe DESTINATION include)
diff --git a/src/common/AudioStreamBuilder.cpp b/src/common/AudioStreamBuilder.cpp
index dffcd75..79984a4 100644
--- a/src/common/AudioStreamBuilder.cpp
+++ b/src/common/AudioStreamBuilder.cpp
@@ -215,3 +215,122 @@ Result AudioStreamBuilder::openStream(std::shared_ptr<AudioStream> &sharedStream
 }
 
 } // namespace oboe
+
+#include <oboe/Oboe.h>
+#include <math.h>
+#include <deque>
+#include <pthread.h>
+
+// I got link problem with std::mutex, so use pthread instead
+class CThreadLock
+{
+public:
+    CThreadLock();
+    virtual ~CThreadLock();
+
+    void Lock();
+    void Unlock();
+
+private:
+    pthread_mutex_t mutexlock;
+};
+
+CThreadLock::CThreadLock()
+{
+    // init lock here
+    pthread_mutex_init(&mutexlock, 0);
+}
+
+CThreadLock::~CThreadLock()
+{
+    // deinit lock here
+    pthread_mutex_destroy(&mutexlock);
+}
+void CThreadLock::Lock()
+{
+    // lock
+    pthread_mutex_lock(&mutexlock);
+}
+void CThreadLock::Unlock()
+{
+    // unlock
+    pthread_mutex_unlock(&mutexlock);
+}
+
+class Player : public oboe::AudioStreamDataCallback
+{
+public:
+    ~Player() {
+        outStream->requestStop();
+    }
+
+    Player(int channels, int sample_rate)
+    {
+        this->channels = channels;
+        oboe::AudioStreamBuilder builder;
+        // The builder set methods can be chained for convenience.
+        builder.setSharingMode(oboe::SharingMode::Exclusive)
+            ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
+            ->setChannelCount(channels)
+            ->setSampleRate(sample_rate)
+            ->setFormat(oboe::AudioFormat::Float)
+            ->setDataCallback(this)
+            ->openManagedStream(outStream);
+        // Typically, start the stream after querying some stream information, as well as some input from the user
+        outStream->requestStart();
+    }
+
+    oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override
+    {
+        float *floatData = (float *)audioData;
+        int i = 0;
+        mtx.Lock();
+        auto n = channels * numFrames;
+        for (; i < n && i < (int)buffer.size(); ++i, ++floatData)
+        {
+            *floatData = buffer.front();
+            buffer.pop_front();
+        }
+        mtx.Unlock();
+        for (; i < n; ++i, ++floatData)
+        {
+            *floatData = 0;
+        }
+        return oboe::DataCallbackResult::Continue;
+    }
+
+    void push(const float *v, int n)
+    {
+        mtx.Lock();
+        for (auto i = 0; i < n; ++i, ++v)
+            buffer.push_back(*v);
+        // in case memory overuse
+        if (buffer.size() > 48 * 1024 * 120)
+            buffer.clear();
+        mtx.Unlock();
+    }
+
+private:
+    oboe::ManagedStream outStream;
+    int channels;
+    std::deque<float> buffer;
+    CThreadLock mtx;
+};
+
+extern "C"
+{
+    void *create_oboe_player(int channels, int sample_rate)
+    {
+        return new Player(channels, sample_rate);
+    }
+
+    void push_oboe_data(void *player, const float* v, int n)
+    {
+        static_cast<Player *>(player)->push(v, n);
+    }
+
+    void destroy_oboe_player(void *player)
+    {
+        delete static_cast<Player *>(player);
+    }
+}
