2024年3月12日 星期二

簡單的 flutter app, 透過 ffi 呼叫 oboe 播放 dtmf 聲音

參考文章:     

    1. https://github.com/google/oboe/blob/main/docs/FullGuide.md 

    2. https://chromium.googlesource.com/external/github.com/google/oboe/+/io18codelab/GettingStarted.md

用  flutter   create   projectDir 產生新專案, 並切換到該專案目錄

   cd  projectDir

擬使用 oboe 的原始程式碼, oboe 是用 c 語言寫的, 用 git 把整個目錄下載回來, 放到專案的 lib/cpp 目錄下面

   cd  lib   &&   mkdir  cpp   &&   cd  cpp   &&   git  clone  https://github.com/google/oboe

針對 Android 系統, 要建好程式庫目錄及做好目錄連結:

   cd  android   &&  mkdir  lib  &&   cd  lib   &&  ln   -sf   ../../lib/cpp  .

接著編輯 android/app/build.gradle , 加入 cmake 的支援, 添加下面綠色部份就可:

    android  {

        ...

        externalNativeBuild {
            cmake {
                path "../lib/CMakeLists.txt"
            }
        

        ...    

  }

再來編輯 android/lib/CMakeLists.txt, 加入要編譯的項目:

cmake_minimum_required(VERSION 3.16.3)
    project("oboedtmf")   
    set (OBOE_DIR  cpp/oboe)
    include_directories(${OBOE_DIR}/include)
    add_subdirectory (${OBOE_DIR}  cpp/oboe)   
    add_library(native SHARED  cpp/native.cpp)
    target_link_libraries(native android  oboe  jnigraphics)

編輯程式  lib/cpp/native.cpp, 讓 flutter 應用程式, 可以透過 dart: ffi 去呼叫它

include <math.h>
#include <oboe/Oboe.h>
// DTMF   1209     1336     1477     1633
// 697       1 ->1     2 ->2     3 ->3      A ->4
// 770       4 ->5     5 ->6     6 ->7      B ->8
// 852       7 ->9     8 ->10   9 ->11    C ->12
// 941       * ->13    0 ->14   # ->15    D ->16
int dtmfTone[16][2]= {
  {1209, 697}, {1336, 697}, {1477, 697}, {1633, 697},
  {1209, 770}, {1336, 770}, {1477, 770}, {1633, 770},
  {1209, 852}, {1336, 852}, {1477, 852}, {1633, 852},
  {1209, 941}, {1336, 941}, {1477, 941}, {1633, 941}
};
const double pi2 = 2 * M_PI;

class DTMFplayer : public oboe::AudioStreamCallback {
  private:
    double radian[16][2];//  θ: phase in radians
    double dT[16][2];    // dθ: delta θ
  public:
    int channels = 0;
    int repeat = 0;
    int tone = 0;
    int fs = 0;
    oboe::DataCallbackResult onAudioReady(oboe::AudioStream *sink, void *data, int32_t num) override {
      if (tone > 16 || tone <= 0) return oboe::DataCallbackResult::Stop;// do NOT call read() or write() on the stream in this callback !!!
      float *yt = static_cast<float *>(data);// time dependent data y(t) = Σ sin(2π*fk*t/fs)
      int i = tone - 1;// vallid tone index: 1 ~ 16
      for (int t = 0, offset = 0; t < num; t ++, offset += channels) {
        float amplitude = 0.0;
        for (int k = 0; k < 2; k ++) { // superposition: Σ sin(2π*fk*t/fs)
          amplitude += sinf(radian[i][k]);
          radian[i][k] += dT[i][k];
          if (radian[i][k] >= pi2) radian[i][k] -= pi2;
        }
        for (int ch = 0; ch < channels; ch ++) { // normalize amplitude for all channels: -1 ~ 1
          yt[offset + ch] = amplitude / 2; // todo: speed up divide by 2
        }
      }
      return (++ repeat <= 32) ?
            oboe::DataCallbackResult::Continue : oboe::DataCallbackResult::Stop; // auto stop
    }
    void update(int index) {
      if (dtmfOut == nullptr) return;
      tone = (1 <= index && index <= 16) ? index : 0;
      if (tone == 0) {
        dtmfOut->pause(0);
      } else {
        repeat = 0;
        for (int j = 0; j < 16; j ++)  {
          for (int k = 0; k < 2; k ++) { radian[j][k] = 0.0; }// phase reset
        }
        dtmfOut->start(0);
      }
    }
    std::shared_ptr<oboe::AudioStream> dtmfOut;// oboe::ManagedStream dtmfOut;// output stream
    DTMFplayer() {
      oboe::AudioStreamBuilder builder;
      builder.setFormat(oboe::AudioFormat::Float); // float data
      builder.setDirection(oboe::Direction::Output);
      builder.setSharingMode(oboe::SharingMode::Shared);// Shared, Exclusive
      builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
      builder.setCallback(this);    
      if (builder.openStream(dtmfOut) == oboe::Result::OK) {// if (builder.openManagedStream(dtmfOut) == oboe::Result::OK) {
        channels = dtmfOut->getChannelCount();
        fs = dtmfOut->getSampleRate(); // sample rate
        if (fs > 0) {
          for (int j = 0; j < 16; j ++) {
            for (int k = 0; k < 2; k++) {
              dT[j][k] = pi2 * dtmfTone[j][k] / fs; // dθ = 2π*fk/fs
            }
          }
        }
      }
    }
};
static DTMFplayer *player;
extern "C" { // export C function
  int create( ) {
    player = new DTMFplayer();
    return player ? 1 : 0;
  }
  void destroy( ) {
    if (player) {
      delete player;
      player = nullptr;
    }
  }
  void sound(int tone) {// 0: turn off sound, otherwise turn dtmf on when tone > 0
    if (player) player->update(tone);
  }
}


備註:   若編譯有問題, 像是 error:   assert  .... 什麼的, 若 assert 不影響程式的運作, 就把該行用 // 註解掉, 存檔再重新編譯.

簡單 c 程式碼, 根據五行八卦相生相剋推斷吉凶

#include "stdio.h" // 五行: //               木2 //      水1           火3 //         金0     土4 // // 先天八卦 vs 五行 //                    ...