2020年2月19日 星期三

dart 使用 ffi 和 c 語言溝通

參考官網文章: https://dart.dev/guides/libraries/c-interop
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

Firefox 69 或是 Chrome 76 以後的版本, blob 透過擴展函數 arrayBuffer( ) 可以取得其中的內容, arrayBuffer( ) 可以用在像是透過 webSocket 取得的 blob 資料, 或是放入陣列中(_blobArray)再用 Blob 建構式產生的新 blob, 若是舊版本只能透過 FileReader 去讀取, 以下程式碼搭配 Promise 物件模擬 arrayBuffer 的讀取行為:
        // 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 播放器

Chrome 與 Firefox 都支援 HTML5 的 MediaSource, 但餵進去的資料必須是 fmp4 格式的資料結構, 伺服器可以透過 mp4fragment 將 mp4 檔案一一轉換成 fmp4 格式檔再傳給客戶端. 客戶端的播放程式(javascript)利用  html 的 video 標籤播放該影音檔, 將以下程式碼存成 html 文字檔, 放在伺服器讓客戶端(Chrome 或是  Firefox)來連線:

<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>

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

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