2020年3月4日 星期三
用 grub2 boot loader 啟動 LMDE4
menuentry "Boot LMDE4 iso" {
set root=(hd0,2)
set iso=/boot/lmde4.iso
loopback loop $iso
linux (loop)/live/vmlinuz boot=live findiso=$iso
initrd (loop)/live/initrd.lz
}
2020年2月19日 星期三
dart 使用 ffi 和 c 語言溝通
1. 使用 linux 作業系統, 安裝好dart sdk , c 語言編譯程式加上 cmake
2. 新增一個 dart 專案, 專案內新增一個 hello 目錄 , 進入 hello 目錄, 準備要編譯成 hello 模組(libhello.so):
2.1 先在 hello 目錄裡面編輯一個檔案 CMakeLists.txt:
cmake_minimum_required(VERSION 3.7 FATAL_ERROR)
project(hello_library VERSION 1.0.0 LANGUAGES C)
add_library(hello_library SHARED hello.c hello.def)
add_executable(hello_test hello.c)
set_target_properties(hello_library PROPERTIES
PUBLIC_HEADER hello.h
VERSION ${PROJECT_VERSION}
SOVERSION 1
OUTPUT_NAME "hello"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Hex_Identity_ID_Goes_Here"
)
2.2. 為了指示要將函數(testWork, add, reverse 等等) 導出來(export)使用, 要編輯一個文字檔案 hello.def:
LIBRARY hello
EXPORTS
testWork
add
reverse
2.3. 編輯一個 c 程式,用來宣告函式原型的標頭檔 hello.h:
void testWork( );
int add(int a, double b);
char *reverse(char *str);
2.4. 編輯 c 模組程式的原始碼 hello.c:
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include "hello.h"
int main( ) {
testWork( );
printf("3 + 5 = %d\n", add(3, 5));
printf("%s\n",reverse("Hello-String") );
return 0;
}
void testWork( ) {
printf("It works!\n");
}
int add(int a, double b) {
return a + b;
}
char *_bufferPointer = NULL;
int _buffrLength = 0;
char *reverse(char *src) {
int size = strlen(src);
if(size == 0) return NULL;
else if(size > _buffrLength) { // allocate if space not enough
if(_bufferPointer != NULL) free(_bufferPointer);
_bufferPointer = (char *)malloc(size + 1);//from heap
_buffrLength = size;// maximum characters to hold
}
_bufferPointer[size] = '\0';// eos
for (int i = 0; i < size; i++) _bufferPointer[size - 1 - i] = src[i];// reverse
return _bufferPointer;
}
2.5 上述檔案都編輯完成後就能執行 make 作業, 製作成動態程式庫 (hello/libhello.so)
cmake .
make
3. 在專案內新增 pubspec.yaml 檔案, 完成後, 開啟終端機執行 pub get 準備使用 ffi:
name: cinterop
version: 0.0.1
description: >-
An example of calling C code from Dart through FFI
author: MyName<email@google.com>
environment:
sdk: '>=2.6.0 <3.0.0'
dependencies:
ffi: ^0.1.3
dev_dependencies:
test: ^1.5.3
4.編輯一個透過 ffi 界面呼叫 c 語言的 dart 範例程式 main.dart:
import 'dart:ffi';
import 'package:ffi/ffi.dart';
main( ) {
final libso = DynamicLibrary.open('hello/libhello.so');
final testFun= libso.lookup<NativeFunction<Void Function( )>>('testWork').
asFunction<void Function()>();
final addFun = libso.lookup<NativeFunction<Int32 Function(Int32, Double)>>('add').
asFunction<int Function(int,double)>( );
final revFun = libso.lookup<NativeFunction<Pointer<Utf8> Function(Pointer<Utf8>)>>('reverse').
asFunction<Pointer<Utf8> Function(Pointer<Utf8>)>( );
final c2dartString = (Pointer<Utf8> _pointer) => Utf8.fromUtf8(_pointer);
final dartstring2c = (String _string) => Utf8.toUtf8(_string);
final reverse = (String _string) => c2dartString(revFun(dartstring2c(_string)));
testFun( );
print('adder: 334 + 5 = ${addFun(334, 5.0)}');
for(int i=0; i<20 ; i++) print('${reverse("$i.Hello-string")}');
}
5. 用 dart VM 執行上述 main.dart 看看: /pathToDartSDK/bin/dart main.dart
It works!
adder: 334 + 5 = 339
gnirts-olleH.0
gnirts-olleH.1
gnirts-olleH.2
gnirts-olleH.3
gnirts-olleH.4
gnirts-olleH.5
gnirts-olleH.6
gnirts-olleH.7
gnirts-olleH.8
gnirts-olleH.9
gnirts-olleH.01
gnirts-olleH.11
gnirts-olleH.21
gnirts-olleH.31
gnirts-olleH.41
gnirts-olleH.51
gnirts-olleH.61
gnirts-olleH.71
gnirts-olleH.81
gnirts-olleH.91
後記: dart SDK 的 dart2native 可以將原始碼轉成可執行檔, 不用 dart VM, 直接就能執行:
/pathToDartSDK/bin/dart2native main.dart && ./main.exe
2020年2月7日 星期五
Javascript 讀取 blob
// arrayBuffer reader
async function blobReader(_blobArray) {
const blobTemp = new Blob(_blobArray);
if( blobTemp.arrayBuffer ) return await blobTemp.arrayBuffer( );
else { // use FileReader & Promise to read
var _ready = null;
const _arrayBuffer = new Promise((_resolv, _) => _ready=_resolv);
const _reader = new FileReader( );
_reader.onloadend = ( ) => _ready && _ready(_reader.result);
_reader.readAsArrayBuffer(blobTemp);
return await _arrayBuffer;
}
}
2020年2月5日 星期三
Javascript 使用 html5 的 MediaSource 播放器
<html><head><meta charset='utf-8'></head><body><script>
const codec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
class HttpPlayer {
constructor (width, height) {
const video = document.createElement('video');
const mediaSource = new MediaSource( );
mediaSource.onsourceopen = _evt => {
var completeFile = null;
var number = 1; // total number to get
const sourceBuffer = mediaSource.addSourceBuffer(codec);
const httpGet = _filename => {
const url = `https://${location.origin.split("//").pop( )}${_filename}`;
const https = new XMLHttpRequest( );
https.responseType = 'arraybuffer';
https.onload = ( ) => sourceBuffer.appendBuffer(https.response);
https.onloadend = ( ) => completeFile = _duration => {
completeFile = null;
number -- <= 0 ? mediaSource.endOfStream( ) : (( ) => {
sourceBuffer.timestampOffset = _duration - 2;
httpGet("/streamN.fmp4"); // next fmp4
})( );
}
https.open('get', url);
https.send( );
}
sourceBuffer.onupdateend = _evt =>
completeFile && completeFile(mediaSource.duration);
httpGet("/stream0.fmp4"); // initial fmp4
}
video.width = width;
video.height = height;
video.muted = true;
video.controls = "controls";
video.autoplay = "autoplay";
video.src = URL.createObjectURL(mediaSource);
document.body.append(video);
}
};
new HttpPlayer(1024, 768);
</script></body></html>
2019年12月25日 星期三
用 android ndk 編譯 bento4
bento4 官方網站原始碼: https://www.bento4.com/downloads/
android ndk官方網站: https://developer.android.com/ndk/downloads
到上述官網下載 bento4 原始碼, 及程式編譯工具 android ndk, 分別將它解壓縮. 例如我將它解壓縮, 另外再改名, 分別放在目錄:
export path_to_bento4=/home/mint/Downloads/bento4
export path_to_ndk=/home/mint/Downloads/ndk20
1. 稍微修改 bento4 原始碼的 Ap4Config.h, (位在 $path_to_bento4/Sourece/C++/core/Ap4Config.h), 找出並修改 AP4_fseek 及 AP4_ftell 的定義:
#define AP4_fseek fseek
#define AP4_ftell ftell
1. 確認自己下載的 ndk 版本(最少要 24), 現在用 29, 用來設定 ANDROID_PLATFORM 環境變數, 目的是讓 AP4_fseek 及 AP4_ftell 支援 64 位元的 ftello , fseeko, 若只使用 ftell 及 fseek 則開啟檔案只能支援 32 位元長度 2^32 = 4G
2. 在 linux shell 命令列模式要先設定好 ndk 的環境變數:
export ANDROID_NDK=$path_to_ndk
3. 切換到 betnto4 主目錄, 首先用 cmake 產生 CMakeLists.txt, 接著用 make 下指令(-j3 用 3 個 cpu 加速編譯速度), 讓 ndk 開始編譯 c 程式:
cd $path_to_bento4 && mkdir build && cd build
cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake -DANDROID_PLATFORM=29 ..
make -j3
4. 許多有用的工具程式都已編譯成 android 下可直接執行的 binary 檔, 放在目錄:
$path_to_bento4/build
像是 mp4Fragment 可以將 mp4 轉換為 fmp4. fmp4 格式檔是一個可以透過 javascript 餵給 html5 MediaSource 播放的片段串流影音檔:
mp4Fragment path_to_source.mp4 path_to_taget.fmp4
5.在 Android app 透過一些技巧, 例如將上述編譯完的執行檔包在 assets, 執行 app 時再將它解開,放進 app cache 目錄內. 透過 ProcessBuilder 修改成可執行權限, 就像在終端機執行一樣, http 伺服器再將轉成的 fmp4 透過網路傳給客戶端, 就成了名符其實的媒體串流伺服器 app.
2019年11月7日 星期四
實現簡單的 Motion JPEG 改用 WebSocket
2. 客戶端收到內含 Javascript 可以開啟 WebSocket 連線的 html 檔案
3. 伺服端收到 WebSocket 升級請求, 連線後,依照時間順序,不停將 JPEG (binary)傳給客戶端.
4. 客戶端的 Javascript, 從 WebSocket 收到的 JPEG 資料, 製成 url, 將圖貼至 canvas 就可以了
放在伺服器上的 mjpeg.html 客戶端程式範例:
<html><head><meta charset='UTF-8'></head>
<body><script>
class ViewImageWS {
constructor(width, height) {
this._canvas = document.createElement('canvas');
this._canvas.width = width;
this._canvas.height = height;
this._canvas.style.setProperty('border', '1px solid #000000');
this._context = this._canvas.getContext('2d');
this._height = this._canvas.height;
this._width = this._canvas.width;
document.body.append(this._canvas);
}
set drawImage (_img) {
const scaley = this._height/_img.height;
const scalex = this._width/_img.width;
this._context.save( );
this._context.scale(scalex, scaley);
this._context.drawImage(_img, 0, 0, this._width, this._height);
this._context.restore( );
}
set host(_ip) {
const ws = new WebSocket(`ws://${_ip}`);
ws.onmessage = _jpg => {// jpg binary
const url = URL.createObjectURL(new Blob(
[_jpg.data],
{type: 'image/jpeg'}
));
const img = new Image( );
const release = ( )=> {
URL.revokeObjectURL(url);
img.remove( );
}
img.style= 'transform:rotate(90deg);';// rotate 90 deg if need
img.onerror = release;
img.onload = ( ) => {
this.drawImage = img;
release( );
};
img.src = url;
}
}
};
var viewer = new ViewImageWS(1024, 768);
viewer.host = location.origin.split('//').pop( );
</script></body>
</html>
2019年10月30日 星期三
Dart 與 Java 資料互傳
2019年10月24日 星期四
用 Javascript 實現簡單的 motion jpeg
<html><head><meta charset='utf-8'></head>
<body> <script type='application/javascript' src='mjpeg.js'></script></body>
</html>
底下是一個 Javascript 檔案 mjpeg.js 範例程式, 全部放到 http 伺服器同 jpeg 檔案的附錄內, 讓它執行, 用瀏覽下抓上述 mjpeg.htm, 就會定時從伺服器下載 jpeg 檔, 再繪製貼圖到 canvas, 這就是一個超簡單又名符其實的 motion jpeg 伺服器:
class MjpegViewer {
constructor(fps, width, height) {
this._fps = fps;
this._running = false;
this._canvas = document.createElement('canvas');
this._canvas.width = width;
this._canvas.height = height;
this._canvas.style.setProperty('border', '1px solid #000000');
document.body.append(this._canvas);
}
get _duration( ){ return 0 < this._fps && this._fps < 32 ? 1000/this._fps : 100 }
get _context( ) { return this._canvas.getContext('2d') }
get stop( ) { this._running = false }
set host(_ip) {
var _counter = 0;
this._running = true;
const _period = this._duration;
const _draw = (img)=> { this._context.drawImage(img, 0, 0) }
const _periodRun = ( )=> {
if(! this._running) return;
const _img = document.createElement('img');
_img.onerror = ( )=> { _img.remove( ) }
_img.onload = (_)=> {
_counter ++;
_draw(_.target);
_img.remove();
};
_img.src = _ip + '/mjpeg' + (_counter);
setTimeout(_periodRun, _period);
};
_periodRun( ); // fire the process
}
};
const viewer = new MjpegViewer(1, 1024, 768) // fps = 1 frame/sec, canvas: 1024x768
viewer.host = 'http://192.168.0.1:8080';
setTimeout( ( )=>{ viewer.stop } , 10000);
2019年10月13日 星期日
Flutter 使用 PlatformView
vscode 開啟終端先執行以下 1 ~ 3 命令, 確認範例程式是可上傳至手機並執行的:1. flutter create -i swift -a kotlin textview2. cd textview3. flutter run接著 vscode 開啟 textview 專案夾, 修改以下檔案: 1. 開啟檔案 lib/main.dart, 將以下程式碼複製貼上:import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override build(BuildContext context) => MaterialApp(
theme: ThemeData.dark(),
home : NdoWidgetExample('native android widget!')
);
}
class ViewController {
final MethodChannel _channel;
ViewController(int id): _channel = MethodChannel('_channel$id');
Future<void> setText(String text) async => await _channel.invokeMethod('setText', text);
}
typedef void NdoWidgetInit(ViewController _);// function type
class NdoWidgetExample extends StatelessWidget {
final NdoWidgetInit _init;
NdoWidgetExample._(this._init);
factory NdoWidgetExample(String text) => NdoWidgetExample._(
(ViewController _) => _.setText(text) );
@override build(BuildContext context) => Scaffold(
appBar: AppBar(title: Text('Android TextView')),
body : Column(children: [Text("Flutter!"), Container(
width: 320.0,
height:240.0,
color: Colors.grey,
child: AndroidView(
viewType: 'ndoView',
onPlatformViewCreated: (int id) => _init(ViewController(id))
)
)])
);
}2.開啟檔案 android/app/src/main/kotlin/com/example/textview/MainActivity.kt, 將以下程式碼複製貼上:package com.example.textview;
import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.PluginRegistry.Registrar
class MainActivity: FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
registrarFor("com.example.textview").apply { val ndoView = Factory_NdoWidget(messenger()) platformViewRegistry().registerViewFactory("ndoView",ndoView) }
}
}3. 新增 android/app/src/main/kotlin/com/example/textview/TextViewPlugin.kt, 將以下程式碼複製貼上:package com.example.textview
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
import android.widget.TextView
import android.content.Context
class Factory_NdoWidget(private val messenger: BinaryMessenger)
: PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context, id: Int, o: Any?)
= _NdoWidget(context, id, messenger)
}
class _NdoWidget(context: Context, id: Int, messenger: BinaryMessenger)
: PlatformView, MethodCallHandler { // interface to implement
private val _channel = MethodChannel(messenger, "_channel$id")
private val _view = TextView(context)
init { _channel.setMethodCallHandler(this) }
override fun getView() = _view override fun dispose() { } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"setText" -> {
_view.setText(call.arguments as String);
result.success(null)
}
else -> result.notImplemented()
}
}
}
4.重新編譯並上傳到手機執行
2019年10月8日 星期二
Flutter 與原生插件溝通
// 原生 flutter plugin 端 kotlin 原始碼
import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugin.common.StringCodec
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugins.GeneratedPluginRegistrant // ...
var stream : EventChannel.EventSink? = null
EventChannel(flutterView , "com.example.event").setStreamHandler(//事件通道註冊
object: EventChannel.StreamHandler { // 繼承 StreamHandler 並實現
override fun onCancel(arguments: Any?) { stream = null }
override fun onListen(arguments: Any?, sink: EventChannel.EventSink?) {
stream = sink
stream?.success("init complete") // 資料流非同步發送一個事件
}
})
//... async send event
stream?.success("event !") // 資料流非同步再發送一個事件
val msg = BasicMessageChannel<String>(flutterView, "channel", StringCodec.INSTANCE)//簡單訊息通道註冊
msg.send("Hello!") { reply: String? -> println(reply); } // 簡單訊息通道發送一則訊息
msg.setMessageHandler { req, reply -> reply.reply("$req: plugin") }// 同時能回應訊息
// ...
// Flutter 端 dart 原始碼
final _event = EventChannel('com.example.event'); //事件通道註冊
_event.receiveBroadcastStream().listen((onData) { //監聽資料流所發生的事件
print('$onData');
});
final _msg = BasicMessageChannel<String>('channel', StringCodec());//簡單訊息通道
_msg.setMessageHandler( (String onData) async { // 簡單訊息通道等待接收訊息
print('receive: $onData');
return 'hi'; // 回應資料
});
_msg.send('who').then(( x) => print('answer:$x')); // 簡單訊息通道同時能傳送訊息
總結來說, EventChannel 專用於插件的廣播頻道, MethodChannel 專用於插件的回應需求通道, 而 MessageChannel 則是一個通用的全雙工溝通管道.
使用 BinaryMessenger 通道傳輸, 參考文章:
https://medium.com/flutter/flutter-platform-channels-ce7f540a104e
底下使用 android 端的 Kotlin 原始碼: android platform -> flutter 溝通:
import android.os.Bundle
import io.flutter.app.FlutterActivity
io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StringCodec
import java.nio.ByteBuffer
import java.nio.ByteOrder
// import io.flutter.plugin.common.BinaryCodec
// import io.flutter.plugin.common.StandardMessageCodec
// 若要接收監聽處理範例:
flutterView.setMessageHandler("binary") { buffer: ByteBuffer?, reply ->
buffer?.order(ByteOrder.nativeOrder())
buffer?.putDouble(3.1415)
buffer?.putInt(123456789)
reply.reply(buffer)
}
// 若要傳送並等待處理範例
val buffer = ByteBuffer.allocateDirect(12)
buffer.putDouble(3.1415)
buffer.putInt(123456789)
flutterView.send("binary", buffer) { _ -> } // 忽略回傳值
// { x: ByteBuffer? -> val y = BinaryCodec.INSTANCE.decodeMessage(x) }//處理回傳值
底下使用 Flutter 端的 dart 原始碼: flutter -> android platform 溝通:
// 若要接收監聽處理範例:
BinaryMessages.setMessageHandler('binary', (ByteData buffer) async {
final readBuffer = ReadBuffer(buffer);
final x = readBuffer.getFloat64( );
final n = readBuffer.getInt32( );
print('Received $x and $n');
return null;
});
// 若要傳送並等待處理範例
final writeBuffer = WriteBuffer( ) ..
putFloat64(3.1415) ..
putInt32(12345678);
final buffer = writeBuffer.done();
await BinaryMessages.send('binary', buffer);
上述 BinaryMessages 通道若是透過 StringCodec( ), 就能用字串加以溝通:
// 若要接收監聽處理範例:
final codec = StringCodec( );
BinaryMessages.setMessageHandler('binary', (ByteData buffer) async {
print('${codec.decodeMessage(buffer)}');
return codec.encodeMessage('Hi');
});
// 若要傳送並等待處理範例:
final reply = await BinaryMessages.send('binary', codec.encodeMessage('Hello'))
print(codec.decodeMessage(reply));
2019年10月2日 星期三
Flutter 透過高階函式(high orfer function)溝通 Widget
import 'package:flutter/material.dart';
void main() => runApp(MyApp( ));
class MyApp extends StatelessWidget {
@override build(BuildContext context) => MaterialApp(
theme : ThemeData.dark(),
home : MyHomePage()
);
}
class MyHomePage extends StatefulWidget {
@override createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final colors = <Color>[Colors.red, Colors.orange, Colors.yellow, Colors.green, Colors.blue, Colors.cyan,Colors.purple];
String title = '按下按鍵更改標題';
int index = 0; // 顏色索引
Color color( ) { // callback function to send color
index = (index +1) % colors.length;
return colors[index];
}
void changeTitle(String _) { // callback function to change title
setState( ( ) => title =_ ); // setState to rebuild Scaffold widget
}
@override build(BuildContext context) => Scaffold(
appBar: AppBar(title: Center(child:Text(title))),
body : Center(child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[MyButton(color, changeTitle)],
))
);
}
class MyButton extends StatelessWidget {
final Color Function( ) colorIn;
final void Function(String) titleOut;
MyButton(this.colorIn, this.titleOut);
@override build(BuildContext context) => FlatButton.icon(
onPressed : ( ) => titleOut('這是新標題'),
color : colorIn(), // get color
label : Text('按下按鍵更改顏色'),
icon : Icon(Icons.color_lens)
);
}
用 python 解簡單的常微分方程式
# sudo apt install python3-pip # python3 -m venv venv # cd venv # . bin/activate # pip3 install numpy matplotlib import numpy as np import m...
-
1. 目前使用 linux mint 22.1 作業系統可以順利跑起來, 可上官網去下載, 並安裝到硬碟. 2. 安裝 waydroid 可上網站 https://docs.waydro.id 參考看看: https://docs.waydro.id/usage/inst...
-
1. 就字面意義來說 Solder Mask (防焊層)就是要把焊接(電氣)層遮掉, Paste Mask 則是要把護膠層遮掉,兩者使用時機並不同, 前者用在 PCB 製程, 後者用於主機板組立製程(PCBA)開鋼板. 2. 在 PCB 的製程中,一塊銅箔基板,為了要線路化,...