2022年2月28日 星期一

[開箱] Google Pixel Buds A-Series

心癢了買了一個新藍牙耳機 XD 一開始是看到 Google TW 在蝦皮的特價活動,隨手查了一下,原價 3290 元,在非 Google 官方的賣場大型網購平台可以 2500 左右購得(有些低方還低到 2200左右),就買啦!原本就一直想買一款當備品。

大概去年被朋友推坑買了 魔浪mifo O7 藍牙耳機,用起來都還滿方便的,特別是體積小這件事,很隨身攜帶。唯一的困擾是個人不小心按久按耳機會觸動 Siri (像是拿下來跟人對話,不小心就久按到),然後在 Siri 模式下,最常就是播打電話出去!雖然一年不會觸動個 3~5 次,但啟動還是很擾人。

這次體驗 Google Pixel Buds A-Series + iOS 手機,整體上無法把 Google 耳機的強項整合一起(Google Now) ,倒反而讓我覺得很不錯,整體上就是無線耳機+按一下暫停/播放等模式,也不怕喚起 Siri ... XD 此外,購買之前就看到一些評價說容易聽到環境音(有點像先天故意設計?),我反而滿愛這種像耳掛式耳機的感受,因為我最常聽 Podcast 等類,不需要極安靜環境,反而最怕跟環境完全隔離。

只能說,此時的我,對大家給他的負評價都剛好很能接受(甚至變成我喜愛的點),若真的要說缺點的話,大概是 iOS 搭配使用 Google Pixel Buds A-Series 時,不能靠觸碰耳機調整聲音大小,查手冊才發現是要靠音控控制。

2022年2月15日 星期二

[PHP] 使用 Built-in web server 和 Router Script 開發嵌入式產品的網頁介面

話說幾年前也寫過一篇筆記 [PHP] 使用 PHP built-in web server 及 PHP CodeIgniter framework 。現在則是使用的場景不同了,在筆記一下使用的情境。

不少嵌入式產品的 UI 是採用 Web 實作的,近幾年則是趨向於 App UI 。然而開發 embedded linux 產品時,其 Web UI 也是打包到 embedded linux 裡頭,這時要開發測時,就滿不方便。若只是單純改改裡頭 js code 則可以免強靠 Browser 做 JS Inject 來達成。

目前協助擴展嵌入式產品的 Web UI 維護團隊,過去曾嘗試把裡頭的 Web UI 使用 Vue.js 跟 webpack 打包機制,弄出個 webpack 自起 Web Server 並透過 devServer.proxy 把 CGI request 導向到實體 device ip。目前因為沒有啟用 Vue.js 所以也沒啟用 webpack 等機制,暫時就先用簡單的 PHP 內置Web Server 機制

$ php -S localhost:8000 -t PATH_DOCUMENT_ROOT

然而,在 fw build code 流程中,可能會有多處檔案搬移的設計,以 Web 來說,大概可以硬搞成 /js/main.js 其實擺在 /tmp/path1/js 中,而 /css/main.css 擺在 /tmp/path2/css 等等,這時就要靠 使用路由(Router)脚本 來處理

$ cat routing.php
<?php

$URL_PATH = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

if (!strncmp($URL_PATH, '/js/', 4)) {
$file_path = '/tmp/path1/js' . $URL_PATH;
if (file_exists($file_path)) {
echo file_get_contents($file_path);
return true;
}
}

if (!strncmp($URL_PATH, '/css/', 4)) {
$file_path = '/tmp/path2/css' . $URL_PATH;
if (file_exists($file_path)) {
echo file_get_contents($file_path);
return true;
}
}
return false;

使用:

$ php -S localhost:8000 -t /tmp routing.php

如此,若有一包 fw code 時,可透過 routing 機制指定某些檔案要從哪裡取得(例如需要 patch 的檔案等),因而避開 build code 後才能測試,便方便許多。另外,還須多補寫一下 /cgi-bin/ 的部分,在把 request 改發到對應的裝置上,如此在面對不需改動 CGI 問題時,可以輕鬆測試 Web UI。

另外,該 routing.php 也可以很結構化:

$ cat routing.php
<?php

function log_info($message) {
$stderr = fopen('php://stderr', 'w');
fprintf($stderr, '=> '.$message."\n");
fclose($stderr);
}
function tree_files( $input_file ) {
if (!file_exists($input_file))
return array();

if (is_dir($input_file)) {
$output = array();
if ($handle = opendir($input_file)) {
while (false !== ($file = readdir($handle))) {
if ($file == '.' || $file == '..')
continue;
$path = $input_file . '/' . $file;
if (!file_exists($path))
continue;
if (is_dir($path)) {
$sub_output = tree_files( $path );
foreach($sub_output as $f) {
array_push($output, $f);
}
} else {
array_push($output, $path);
}
}
closedir($handle);
}
return $output;
}
return array( $input_file );
}
$request_handler = array(
'routing' => array(
'file' => array(
'/index.html' => '/tmp/study/haha.html',
'/world.html' => '/tmp/study/hello/',
),
'dir' => array(
'/js/' => '/tmp/study',
'/css/' => '/tmp/study',
),
),
'content_type' => array(
'html' => 'text/html',
'htm' => 'text/html',
'css' => 'text/css',
'js' => 'text/javascript',
'svg' => 'image/svg+xml',
),
);

$URL_PATH = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path_parts = pathinfo($URL_PATH);

log_info('routing-init, URL_PATH:['.$URL_PATH.'], REQUEST_URI:['.$_SERVER['REQUEST_URI'].']');
log_info('in routing-file mode');
foreach($request_handler['routing']['file'] as $pattern => $target ) {
if (!strncmp($URL_PATH, $pattern, strlen($pattern))) {
log_info('in routing-file mode: match pattern: ['.$pattern.'], target: ['.$target.']');
$path = is_dir($target) ? $target . $URL_PATH : $target;
if (file_exists($path)) {
$path_parts = pathinfo($URL_PATH);
$content_type = isset($request_handler['content_type'][$path_parts['extension']]) ? $request_handler['content_type'][$path_parts['extension']]: mime_content_type($path);

header('Content-Type: ' . $content_type);
$size = filesize($path);
header('Content-Length: '.$size);
echo file_get_contents($path);
log_info('routing-file, response: content-type:['.$content_type.'], content-length:['.$size.'], path:['.$path.']');
return true;
}
}
}
log_info('in routing-dir mode');
foreach($request_handler['routing']['dir'] as $pattern => $target ) {
if (!strncmp($URL_PATH, $pattern, strlen($pattern))) {
log_info('in routing-dir mode: match pattern: ['.$pattern.'], target: ['.$target.']');
$path = $target.$URL_PATH;
if (file_exists($path)) {
$path_parts = pathinfo($URL_PATH);
$content_type = isset($request_handler['content_type'][$path_parts['extension']]) ? $request_handler['content_type'][$path_parts['extension']]: mime_content_type($path);

header('Content-Type: ' . $content_type);
$size = filesize($path);
header('Content-Length: '.$size);
echo file_get_contents($path);
log_info('routing-dir, response: content-type:['.$content_type.'], content-length:['.$size.'], path:['.$path.']');
return true;
}
}
}

return false;

如此一來,在 /tmp/study 又建立一個檔案結構:

% tree /tmp/study 
/tmp/study
├── css
│   └── main.css
├── haha.html
├── hello
│   └── world.html
└── js
    └── main.js

3 directories, 4 files

使用 php -S localhost:8000 routing.php 時,就會一些目錄切換的對應機制。

2022年2月9日 星期三

[開箱] UVC Video Capture Card / 視頻采集卡 / 影像擷取卡 - HAGiBiS


繼上次買了不到500元的 UVC Video Capture Card 測試 小米4K 電視棒,這次換價格高一點的 Hagibis 海備思 Type-C 影像擷取卡,他多了 Type-C 跟 USB-A 轉接頭,使用上應當會方便不少。看了下核心的硬體規格,都是輸出最高是 1080p 30Hz 的影像格式。

實際測試 Chromecast 2018 版,使用 VLC 或 QuickTime Player 的確也正常可以取得,雖然我覺得這件事應該算異常?三年前試過圓剛 4500 元左右(現在跌到不用1500元)等級的影像擷取盒,則是會阻擋下來:Chromecast 與 AVerMedia ExtremeCap UVC BU110


看來小廠的 UVC Video Capture Card 可能在 DRM 放行?!此例用 NETFLIX 當作例子。雖說用 Browser 看 NETFLIX 本來也有類似螢幕錄影的機制,這時只能感謝 macOS / Safari 在 DRM 這塊做得很出色,連截圖都是黑壓壓的。

由於工作上要處理很多帶有 HDMI 的設備,未來就靠這 UVC 影像擷取卡來幫忙省螢幕,還可以搭配 Python OpenCV 做影像處理,像是自動化設備測試、影像分析服務等等。

2022年1月30日 星期日

[開箱] 小米 4K 電視棒 / Android TV OS 11 / 國際版 2021 年 12 月發表


雖然 2021 年初也買了個 Chromecast with Google TV 體驗,這次小米 4K 電視棒在 2022年01月下旬,在台灣電商推出販售且特價便宜一百元,只要 1795元就買了一隻來嚐鮮 XD 老家的舊電視太多台,基本上都可以買個支援 DRM 的電視棒來補強一下。至於為何不買 Chromecast with Google TV?單純有台灣可以買到的通路,且又通過 Google 認證的產品,這樣就不需要一直等 Google 牌了(2021年黑色星期五美國 Google Store 也有特價過,有門路的都可以搶到一波便宜的 Chromecast with Google TV,只要40美金即可,但原價也不貴才 50美金)


此外,這次試一下 UVC 擷取卡,之前也忘了測試 Chromecast with Google TV 有沒做好保護,但確定 Chromecast 其他代則是有做保護,當發現的 HDMI 接收器可能有被側錄的機會,就會做不輸出訊號的設計。我覺得小米 4K電視棒貌似沒有很詳細的保護?但用不到 500元的截取卡,其實也不順,卡到幾乎不能用。可能是擷取卡太便宜(?)或是運行的筆電太舊(2011 年的筆電),慢到不能擷取,也稱得上是一種保護的。當初擷取卡只是讓我方便測試不用 DRM 保護的產品啦。這是測試是 VLC + HDMI Video Capture (HDMI -> USB) + macOS 10.13.6 (Macbook Pro, 13-inch Late 2011)。

這次使用小米 4K 電視棒電視棒的體驗不錯,跟 Chromecast with Google TV 沒啥兩樣,且過程也不用登入什麼小米帳號,因此人在台灣的話,就直接買小米 4K 電視棒。唯一的缺點大概是初次啟動後,可以升級系統,可能是老鄉網路差,光升級就等了半小時以上。其他的好處就是 Disney+ 跟 Netflix 都很家喻戶曉了,登陸一下就可以在老電視上享受了。

至於其他人可能會在意 小米盒子 跟 小米4K電視棒 的差異,有興趣可以再找找討論去比較囉,個人是覺得 小米4K電視棒 就很夠用了,包括使用最新的 Android TV OS 11 ,以及遙控器支援音控呼叫,據說小米盒子有支援 Airplay 啦,但我太少用 Airplay 了,幾乎可以略過它。

2021年11月14日 星期日

起秋了



哈,前陣子跟好友哈拉一些心情,得到的回應就如標題三個字。最近心情的確很三溫暖,但過程都是滿不錯的,在天冷的時段可以靜下心來欣賞周邊的一切。

最近認識了 Youtuber 嘟嘟人 ,是一對年紀落在三十一二的年輕人,從台灣到矽谷求學(國小跟高中起),接著取得不錯的就業成就後,可能因事件體悟人生,選擇類似 GAP YEAR 的機制,想到處旅遊拍片增加人生閱歷,在 2020 年初上傳了第一支影片,並在該年秋天都辭職了。因疫情打亂步調而回台灣創作,又因美國疫情回穩而回去。近期感謝他們拍了一些美國大學,如 Harvard University, UCLA 和 Brown University,倒讓我羨慕起美式教育,恰恰好最近在看 modern family 練練口語英文,有那種更深的感受美國文化,看幾集後也開始懂些美式幽默 XD

綜合著近兩個月新多認識的人,反而感受到自己的步伐緩慢跟沈重,感嘆了一下後,倒是被摯友提醒一下,先看個阿德勒的自卑與超越吧 XD 時間就這般的累積著,大家都有各自的成果及包袱,真的是各處開花的,還是要記得回穩一下心情,繼續珍惜身邊的一切。

恰逢 Disney+ 終於可以在台灣看了,順手看了 尚氣脫稿玩家 (Free Guy) ,後者立馬讓我想起 一級玩家 (Ready Player One),只是格局是不一樣的電影。然而,Free Guy 倒也呼應的一些心境,在一成不變的生活中去追求突破框架,小品電影也非常有趣的。像周邊也有三十多歲的學弟翻山越嶺順利進入了 FB 工作,薪酬的確跟嘟嘟人 Ian 所提的水準差不多,台灣人飛過去起薪會差,但隔1, 2年後就轉成當地水準的(不然公司留不住人才),甚至隨著公司半年審查制度,不進步也難(考績不佳就被fire)。

隨著年紀的增長跟機緣,認識了很多有趣的人,然而,大家的足跡,都建立在不同時空背景的資源跟風險努力而來的,沒有人可以完美複製別人的足跡,也千萬不要小看自己珍惜環境成長的過程。

回過頭來,還是留意 影響圈 和 關注圈 的變化,把時間留給影響圈吧!