參考文章:
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 不影響程式的運作, 就把該行用 // 註解掉, 存檔再重新編譯試試看.
沒有留言:
張貼留言