2025年1月5日 星期日

Linux mint 玩 waydroid 一些心得

1. 目前使用 linux mint 22.1 作業系統可以順利跑起來, 可上官網去下載, 並安裝到硬碟.

2. 安裝 waydroid 可上網站  https://docs.waydro.id 參考看看:
   https://docs.waydro.id/usage/install-on-desktops

3. 硬體安裝條件,  CPU 必須能支援 SSSE3 指令, 可開啟終端機查看 flags 是否存在 ssse3 字眼 .
   cat /proc/cpuinfo  | egrep ssse3

4. waydroid 必須在 wayland 視窗環境(註 1.)才能跑, 目前版本的中文輸入法  gcin 似乎與 wayland 相衝突, 因此若要啟動登入
   wayland 視窗環境, 最好先移除 gcin. 替代方案是可以改用酷音輸入法, 但說實在, 酷音輸入法真的很不習慣,用起來超火大.

5. 非得用 gcin, 只好登入 x11 視窗環境, 加裝 weston
  sudo apt install curl ca-certificates -y    
  curl -s https://repo.waydro.id | sudo bash
  sudo apt install waydroid -y  
  sudo apt install weston

6.當 waydroid 下載完 android 的 system.img 及 vendor.img (註 3.),兩個映像檔後, 接著經由終端機, 執行以下命令,
  設定好 WAYLAND_DISPLAY(註 2.), 就能在 weston 視窗內讓 waydroid 顯示出來:
  waydroid session stop
  weston &
  export WAYLAND_DISPLAY=wayland-1
  waydroid session start &
  waydroid show-full-ui

7. 若顏色顯示不對時, 可以執行 waydroid shell, 再更改設定試試看:
   settings put secure accessibility_display_inversion_enabled 0  
   settings put secure accessibility_display_inversion_enabled 1

備註:
  1. 查看視窗環境
    echo $XDG_SESSION_TYPE

  2. 查看使用者環境
     ls $XDG_RUNTIME_DIR/

  3. waydroid 會將下載的資料存在 ~/.local/share/waydroid 目錄內,
      另外將 andorid 系統啟動資料存到 /var/lib/waydroid 目錄內

2024年9月16日 星期一

使用 pcie 轉接器連接 nvme SSD

之前 AM4 主機板使用 pcie ssd, 但主機板故障了沒辦法上網, 只好翻出以前買的 FM2 舊主機板, 想辦法讓老主機復活, 但舊主機板沒有 nvme 的界面, 因此上網買了 pcie 轉接器用來連接 nvme ssd, 遺憾的是 grub2 bootloader 無法識別 nvne ssd. 但幸運的是當 linux 開完機後, nvme ssd 就能正常運作了. 為了讓  nvme ssd 能在老舊主機上發揮功效, 我將 nvme ssd 添加一個分區(partition), 並將它的 label 命名為 casper-rw, 再將 persistent 參數傳給 linux mint livecd 的 linux kernel, 目的是利用 livecd iso 檔開機後, 可以讓 linux 作業系統操作 overlay file system 將所有資料寫進 nvme ssd 內, 底下是  grub.cfg 的內容:

menuentry "GPT linux mint livecd persistent" --hotkey=1 {
    search --no-floppy --fs-uuid --set=root c5bfc0ce-3f68-4bf5-9eb2-8344ff02cd03
    set isoFile=/backup/iso/linuxmint-22-cinnamon-64bit.iso
    loopback loop $isoFile
    linux  (loop)/casper/vmlinuz boot=casper iso-scan/filename=$isoFile locale=zh_TW.UTF-8 persistent
    initrd (loop)/casper/initrd.lz
}

後紀: 網路上有人用 clover bootloader 聽說可以讓 pcie 的 nvme 開機, 我試的結果是: 不行!, 也許是我的主機板 bios 不支援的關係, 更糟的是, clover bootloader 開機後, 無線鍵盤變得怪怪的, 變得很遲鈍, 重新開機也一樣, 只好進入 clover bootloader 後, 按下快速鍵 F11 清除 UEFI bios 設定, 鍵盤才勉強恢復可以用, 但感覺還是不正常, 沒多久, 鍵盤就故障完全不能用了, 有可能是巧合, 現在還是一頭霧水. 可以確定的是 clover bootloader 會更改 uefi 的 nvram, 即使關掉電源, 清除 CMOS 依然沒用, 設定依然會被保留, 因此對電腦沒經驗的最好不要亂試.

2024年8月29日 星期四

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

#include "stdio.h"
// 五行:
//               木2
//      水1           火3
//         金0     土4
//
// 先天八卦 vs 五行
//                         乾1->金0
//                兌2->金0          巽5->木2
//      離3->火3                              坎6->水1
//                震4->木2          艮7->土4
//                         坤8->土4
// const char *element5[5]   = {"金", "水", "木", "火", "土"};
const int  gossip8Mod5[9]  = {0, 0, 0, 3, 2, 2, 1, 4, 4};//(乾1,兌2)->金0, 離3->火3, (震4,巽5)->木2, 坎6->水1, (艮7,坤8)->土4
bool judgeResult(int change = 1, int same = 1) { // 用(變卦稱之用), 體(卦不變為體)
  int used = gossip8Mod5[change];// 轉五行 0 ~ 4 : "金", "水", "木", "火", "土"
  int body = gossip8Mod5[same];// 轉五行 0 ~ 4 : "金", "水", "木", "火", "土"
  if ((used + 4) % 5 == body || (used + 2) % 5 == body) return false;// 體 反生 用, 用 反剋 體 -> 凶
  return true; // 用 生 體, 體 剋 用, 用體 比 -> 吉  
}
int main() {
  int good = 0;
  for(int same = 1; same <= 8; same ++) { // 體 8 卦
    for(int change = 1; change <= 8; change ++) {   // 用 8 卦
      if (judgeResult(change, same)) {
        good ++;
        printf("吉 ");
      } else {
        printf("凶 ");
      }
    }
    printf("\n");
  }
  printf("吉/凶 = %d/%d\n", good, 64 - good);
  return 0;
}

2024年8月21日 星期三

使用 flutter 編寫簡單的萬年月曆

將以下程式碼複製貼到 main.dart 執行一遍

// main.dart

import 'package:flutter/material.dart';
// 判斷公(西)元是潤年的條件: (4 倍數且非 100 倍數) 或是 400 倍數
bool isLeapYear(int adYear) => (adYear % 4 == 0) && (adYear % 100 != 0) || (adYear % 400 == 0);

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override Widget build(BuildContext context) => MaterialApp(//    title: '月曆',
    debugShowCheckedModeBanner: false,
    theme: ThemeData.dark(),
    home: const Scaffold(body:  LunarCalender())        
  );
}

class LunarCalender extends StatefulWidget {
  const LunarCalender({super.key});
  @override State<LunarCalender> createState() => _LunarCalenderState();
}

class _LunarCalenderState extends State<LunarCalender> {
  final today = DateTime.now();
  final boxBorder = const BoxDecoration(border: Border(
       top: BorderSide(width: 1.0, color: Colors.grey),
      left: BorderSide(width: 1.0, color: Colors.grey),
     right: BorderSide(width: 1.0, color: Colors.grey),
    bottom: BorderSide(width: 1.0, color: Colors.grey),
  ));
  final cross = const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 7);
  //                            月份:  12,  1,    2,   3,   4,   5,   6,   7,   8,   9, 10, 11, 12 月
  List<int> daysofMonth = [31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];// 天數

  Widget monthView(int year, int month) { //todo: validate year and month
    daysofMonth[2] = isLeapYear(year) ? 29 : 28; //更新二月天數
    final y0 = year - 1;// 註: 公(西)元 1 年 1 月 1 日 是 星期日 (weekday = 0)
    int weekday = year + (y0 ~/ 4) + (y0 ~/ 400) - (y0 ~/ 100);// 潤年補天數
    for (int i = 1; i < month; i ++) { weekday += daysofMonth[i]; } // 直到上個月天數
    weekday %= 7;// 當月第 1 天的星期序, 0 是星期日, ..., 同時等於上個月尚需展示的天數
    final lastMonth = daysofMonth[month - 1];// 上個月天數
    final total = daysofMonth[month] + weekday;// 天數: 當月顯示天數 + 上個月在這個月顯示天數
    return GridView.builder(
      itemCount: total,
      padding: EdgeInsets.zero,
      gridDelegate: cross,
      itemBuilder: (BuildContext context, int i) {
        int atDay = i + 1 - weekday;// 擬顯示的日期, 位置同時與星期序相關
        int atMonth = month;// 擬顯示的月份
        if (atDay <= 0)  {// 修正為上個月的日期
          atDay += lastMonth;
          atMonth --;
        }
        return Container(
          decoration: boxBorder,
          child: Flex(
            direction: Axis.vertical,
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [Text("$atMonth 月"), Text("$atDay")]
          )
        );
      }
    );
  }

  @override Widget build(BuildContext context) {
    int year = today.year;
    int month = today.month;
    return Scaffold(
      appBar: AppBar(title: Center(child:Text("月曆 西元 $year 年 $month 月"))),
      body:  Flex(
        direction: Axis.vertical,
        children: [
          const Flex(
            direction: Axis.horizontal,
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              Text('星期日'),
              Text('星期一'),
              Text('星期二'),
              Text('星期三'),
              Text('星期四'),
              Text('星期五'),
              Text('星期六')
            ]
          ),
          Expanded(child: monthView(year, month))
        ]
      )
    );    
  }
}

2024年8月16日 星期五

簡單學易經

我相信易經.
但它是一部教人作人處事的道理, 而不是用來預測未來的神秘絕學.
若以時間序來看古/今/後(過去, 現在, 未來)時, 易經描述的應該是:
        因過去採取作為, 現在所產生的後果.
而不是
        現在採取作為, 未來將會產生的後果.

因此若有人自稱是易經神算, 他百分之百是騙子, 如果他無條件利用易經原則, 教人作人處事, 那才稱的上是易經大師.

解卦者往往口條好, 能言善道, 博學多聞, 旁徵博引, 引經據典, 甚至還需加以穿鑿附會, 解卦過程不斷用修辭, 一點一點引領信徒到卦辭的關注點上.當預測準確實時, 口耳相傳就能引發廣大迴響, 解卦準的就被當作神算看待, 不准時默默無名無人理會, 無傷大雅, 自然也無礙事件的發展.這就像中樂透時, 得主會成為焦點, 但沒中的比比皆是, 無人聞問. 隨著時間久遠, 事實已不可考, 但保留下來的卦辭, 一些江湖術士拿來當作算命方程式, 利用信徒的奉為圭臬的易經 64 卦, 憑著三寸不爛之舌, 將信徒唬的一愣一愣, 分不清現實. 以古今預測今後, 的確是一個很合理的過程, 俗語說是三分天註定, 但更重要的是七分要靠努力. 用易經來占卜, 只是一個隨機過程, 占卜結果可以安定人心, 這才是學習這門課所必需保持的心態.

單從機率分佈來看, 占筮用的數字只有 4 種 {6,7,8,9}, 可以用 2 位元來代表所有數字, 接著要重複 6 次占出不同數字, 也就是總共產生出 6 * 2 = 12 位元的組合, 換句話說總樣本數只有 2¹² = 4096 個, 周易用了 64 卦, 而 4096 = 64 * 64, 也就是會呈現出多對一的映射(mapping)方式. 卦辭及爻辭是用來解釋卦的, 每個卦內含 6 個爻辭, 乾卦和坤卦另外有一個用詞, 因此共有 64 + 6 *  64 + 2 = 450 條解釋所有的卦象, 用現在計算機來算機率簡直輕而一舉. 但文字和語言的巧妙之處, 在於言者無心, 聽者有意之下, 甚至說者加以潤飾, 活得也能說成死的, 死的竟然可以說成活的, 所有解釋都變成可能, 機率僅僅變成是一個參考數據而已.


2024年8月13日 星期二

推算 24 節氣日期

用來推算未來 24 節氣日期, 雖然推算出來的詳細時間不準, 但日期很準, 若改用今年的小寒時間當基底, 推算未 50 或過去 50 年內的 24 節氣日期, 應該堪用.

#include <stdio.h>
#include <time.h>
const char *climate24_terms[24]  = {
    "小寒", "大寒", "立春", "雨水", "驚蟄", "春分",//: 節氣索引  5, 日時 = 夜時
    "清明", "穀雨", "立夏", "小滿", "芒種", "夏至",//: 節氣索引 11, 日時 > 夜時
    "小暑", "大暑", "立秋", "處暑", "白露", "秋分",//: 節氣索引 17, 日時 = 夜時
    "寒露", "霜降", "立冬", "小雪", "大雪", "冬至" //: 節氣索引 23, 日時 < 夜時
};//24節氣始於年初小寒,北半球:4(X)季始於立(X), 春分秋分日夜等長, 夏至日長夜短, 冬至夜長日短

bool is_leap_year(int ad_year) { // 公(西)元是潤年的條件: (4 倍數且非 100 倍數) 或是 400 倍數
    return (ad_year % 4 == 0) && (ad_year % 100 != 0) || (ad_year % 400 == 0);
}

int *find_climate24(int ad_year) {// 推算當年農曆 24 節氣的陽曆日期
    static const int table_2024[24][4] = {
        { 1,  6,  4, 49}, { 1, 20, 22,  7}, { 2,  4, 16, 27}, { 2, 19, 12, 13}, { 3,  5, 10, 23}, { 3, 20, 11,  6},// [ 5][]: 春分
        { 4,  4, 15,  2}, { 4, 19, 22,  0}, { 5,  5,  8, 10}, { 5, 20, 21,  0}, { 6,  5, 12, 10}, { 6, 21,  4, 51},// [11][]: 夏至
        { 7,  6, 22, 20}, { 7, 22, 15, 44}, { 8,  7,  8,  9}, { 8, 22, 22, 55}, { 9,  7, 11, 11}, { 9, 22, 20, 44},// [17][]: 秋分
        {10,  8,  3,  0}, {10, 23,  6, 15}, {11,  7,  6, 20}, {11, 22,  3, 56}, {12,  6, 23, 17}, {12, 21, 17, 21} // [23][]: 冬至
    }; // 2024 年 24 節氣的時間點 {月, 日, 時, 分} 參考資料 https://www.hko.gov.hk/tc/gts/astronomy/Solar_Term.htm
    static long climate24_table[24] = {0}; // 24 節氣時間曲線, 需製表一次
    static int days_of_solar_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    static int climate24_date[24 * 4]; // 回傳陣列 [月, 日, 時, 分, ...]
    if (climate24_table[0] == 0)  {
        days_of_solar_month[1] = is_leap_year(2024) ? 29 : 28;// 更新 2 月天數
        for (int i = 0; i < 24 ; i ++) {
            int month  = table_2024[i][0]; // 月
            long days  = table_2024[i][1]; // 日
            for (int j = 1; j < month; j ++) days += days_of_solar_month[j - 1]; // 計算總天數
            climate24_table[i] = days * 1440 + table_2024[i][2] * 60 + table_2024[i][3];// 分鐘
        }
    }
    days_of_solar_month[1] = is_leap_year(ad_year) ? 29 : 28;// 更新當年 2 月天數
    int *target = climate24_date; // 填入 [月, 日, 時, 分, ...]
    for (int n = 0; n < 24; n ++) {
        int &solar_month = target[0];
        int &solar_day     = target[1];
        int &solar_hour    = target[2];
        int &solar_min      = target[3];
        long dT = climate24_table[n];
        if (ad_year >= 2024) { // 推算未來
            for (int year = 2024; year < ad_year; year ++) {
                dT += (365.2421990741f - (is_leap_year(year) ? 366 : 365)) * 1440;
            }
        } else { // 回推過往
            for (int year = ad_year; year < 2024; year ++) {
                dT -= (365.2421990741f - (is_leap_year(year) ? 366 : 365)) * 1440;
            }
        }
        solar_day    = dT / 1440;// 日
        solar_hour  = dT % 1440 / 60; // 小時
        solar_min    = dT % 1440 % 60; // 分鐘
        solar_month = 1;// 日期從 1 月 1 日開始運算, 準備轉回當年日期
        for (int i = 1; i <= 12; i ++) { // 一年共 12 月
            int month_days = days_of_solar_month[solar_month - 1];
            if (solar_day <= month_days) break;
            solar_day -= month_days;
            solar_month ++;
        }
        target += 4; // 下一節氣
    }
    return climate24_date;
}


int main() {
    time_t now = time(0);// 現在時間
    struct tm *today = localtime(&now); // 取得今天的資料, todo: GMT + 8
    int ad_year = today->tm_year + 1900;// 今年: 西元年
    int ad_month = today->tm_mon + 1;   // 當月
    int ad_day = today->tm_mday;    
    printf("今天 西元%d 年 %d 月 %d 日\n\n", ad_year, ad_month, ad_day);
    ad_year -= 50;
    int *date = find_climate24(ad_year);
    for(int i = 0; i < 24; i++) {
        printf("%d 年 %s %2d 月 %2d 日 %2d:%2d\n", ad_year, climate24_terms[i], date[0], date[1], date[2], date[3]);
        date += 4;
    }    
}

2024年7月30日 星期二

簡單利用查表法顯示農曆春節的月曆

#include <stdio.h>
const char *title_weekday[7] = {"  星期日", "  星期一", "  星期二", "  星期三", "  星期四", "  星期五", "  星期六"};
const char *sky_terms[10]    = {"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"};// 天干
const char *gnd_terms[12]    = {"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"};// 地支
const char *animal_terms[12] = {"鼠", "牛", "虎", "兔", "龍", "蛇", "馬", "羊", "猴", "雞", "狗", "豬"};// 生肖
const unsigned int y1900_2099[] = {
    0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2,
    0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977,
    0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,
    0x06566,0x0d4a0,0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950,
    0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557,
    0x06ca0,0x0b550,0x15355,0x04da0,0x0a5b0,0x14573,0x052b0,0x0a9a8,0x0e950,0x06aa0,
    0x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0,
    0x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b6a0,0x195a6,
    0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570,
    0x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x055c0,0x0ab60,0x096d5,0x092e0,
    0x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5,
    0x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930,
    0x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,0x0ea65,0x0d530,
    0x05aa0,0x076a3,0x096d0,0x04afb,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45,
    0x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0,
    0x14b63,0x09370,0x049f8,0x04970,0x064b0,0x168a6,0x0ea50,0x06b20,0x1a6c4,0x0aae0,
    0x0a2e0,0x0d2e3,0x0c960,0x0d557,0x0d4a0,0x0da50,0x05d55,0x056a0,0x0a6d0,0x055d4,
    0x052d0,0x0a9b8,0x0a950,0x0b4a0,0x0b6a6,0x0ad50,0x055a0,0x0aba4,0x0a5b0,0x052b0,
    0x0b273,0x06930,0x07337,0x06aa0,0x0ad50,0x14b55,0x04b60,0x0a570,0x054e4,0x0d160,
    0x0e968,0x0d520,0x0daa0,0x16aa6,0x056d0,0x04ae0,0x0a9d4,0x0a2d0,0x0d150,0x0f252,
    0x0d520
};  // 西元 1900 - 2100 年農曆資料表
    // 位元:  16       ,  15:4    ,   3:0
    // 編碼: 閏月 大/小, 各大/小月, 潤月月份
const int lunar_base_y1900[4]   = {5, 11, 1, 31}; // 1900 年初(1/31前)是己(5)亥(11)年, 1/31 開始是庚子年正月初一
const int  year_normal_days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};// 常(規)年 = 365 天
const int  year_leap_days[12]   = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};// 閏(補)年 = 366 天
bool is_leap_year(int ad_year) { // 判斷公(西)元是潤年的條件: (4 倍數且非 100 倍數) 或是 400 倍數
    return (ad_year % 4 == 0) && (ad_year % 100 != 0) || (ad_year % 400 == 0);
}
int days_of_lunar_year(int ad_year) {// 當年農曆天數
    if (ad_year < 1900 || ad_year > 2100) return -1;
    unsigned int idx  = ad_year - 1900;
    unsigned int info = y1900_2099[idx];// 查表
    int lunar_days = 29 * 12 + (info & 0xf ? (info & 0x10000 ? 30 : 29) : 0); // 小月 29 天, 共 12 個月, 外加閏月天數
    for (unsigned int bit_test = 0x8000; bit_test >= 0x10; bit_test >>= 1) {// 補大月的天數
        if (info & bit_test) lunar_days ++;
    }
    return lunar_days;
}
bool next_lunar_new_year(int ad_year, int *ad_month, int *ad_day) { // 明年的春節日
    if (ad_year < 1900 || ad_year > 2100) return false;
    *ad_day += days_of_lunar_year(ad_year) - (is_leap_year(ad_year) ? 366 : 365); // 新的春節日, 只需補足陰陽曆日差天數
    if (*ad_day > 31) {// 當日期超過 31 天(1月)需修正
        *ad_day -= 31;
        *ad_month = 2;// 春節會跨到 2 月
    } else if (*ad_day <= 0) {// 或是日期不足時也需修正
        *ad_day += 31;
        *ad_month = 1;// 春節回落至 1 月
    }
    return true;
}
int *find_lunar_new_year(int ad_year) {
    if (ad_year < 1900 || ad_year > 2100) return nullptr;
    static int day_info[2] = {1, 31};
    int month = lunar_base_y1900[2];
    int day = lunar_base_y1900[3];
    for (int year = 1900; year < ad_year; year++) {
        if (! next_lunar_new_year(year, &month, &day)) break;
    }
    day_info[0] = month;
    day_info[1] = day;
    return day_info;
}
int main() {
    int year = 2024;
    int *day_info = find_lunar_new_year(year);
    if (day_info == nullptr) return -1;
    int month = day_info[0];
    unsigned int idx  = year - 1900;
    int sky = (idx + 1 + lunar_base_y1900[0]) % 10;
    int gnd = (idx + 1 + lunar_base_y1900[1]) % 12;
    printf("  %s%s(%s)年-月曆\t\t\t\t%8d 月\t\t\t\t西元%8d 年\n",
        sky_terms[sky], gnd_terms[gnd], animal_terms[gnd],
        month, year
    );
    int y0 = year - 1;// 註: 公(西)元 1 年 1 月 1 日 是 星期日 (weekday = 0)
    int weekday = year + y0/4 + y0/400 - y0/100;// 潤年補天數
    const int *days_of_month = is_leap_year(year) ? year_leap_days : year_normal_days;
    for (int i = 0; i + 1 < month; i ++) weekday += days_of_month[i];
    weekday %= 7;
    for (int i = 0; i < 7; i++) {
        printf("%s\t", title_weekday[i]);
    }
    if (weekday != 0) {
        printf("\n");
    }
    int position = 0;
    while (position < weekday) {
        if (++ position % 7 == 0) printf("\n");
        printf("          \t");
    }
    for (int day = 1; day <= days_of_month[month - 1]; day ++) {
        if (position ++ % 7 == 0) printf("\n");
        printf(day == day_info[1] ? "春節%4d\t" :  "%8d\t", day);
    }
    printf("\n\n");
    return 0;
}

2024年7月22日 星期一

簡單的萬年曆 c 程式碼

#include <stdio.h>
const char *title_weekday[7]= {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
const int  year_normal_days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};// 常(規)年
const int  year_leap_days[12]   = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};// 閏(補)年
bool is_leap_year(int ad_year) { // 公(西)元是潤年的條件: (4 倍數且非 100 倍數) 或是 400 倍數
    return (ad_year % 4 == 0) && (ad_year % 100 != 0) || (ad_year % 400 == 0);
}

int main() {
    int year  = 2024;
    int month = 1;

    int y0 = year - 1;// 註: 公(西)元 1 年 1 月 1 日 是 星期日 (weekday = 0)
    int leap_weekday = year + y0/4 + y0/400 - y0/100;// 潤年補天數
    const int *days_of_month = is_leap_year(year) ? year_leap_days : year_normal_days;
    for (int i = 0; i + 1 < month; i ++) leap_weekday += days_of_month[i];
    leap_weekday %= 7;

    for (int to_show = month; to_show <= 12; to_show ++) {
        printf("\t\t%6d 月\t\t西元 %6d 年\n", to_show, year);
        for (int i = 0; i < 7; i++) printf("%s\t", title_weekday[i]);
        
        int position = 0;
        if (leap_weekday != 0) printf("\n");
        while (position < leap_weekday) {
            if (++ position % 7 == 0) printf("\n");
            printf("      \t");
        }

        for (int day = 1; day <= days_of_month[to_show - 1]; day ++) {
            printf(position ++ % 7 == 0 ? "\n%6d\t" : "%6d\t", day);
        }
        printf("\n\n");
        leap_weekday = position % 7;
    }
}

2024年4月23日 星期二

linux 上使用 libuvc 開啟 usb camera

先安裝 libx11, libuvc, libusb 程式庫:

    sudo  apt   install   libx11-dev  libuvc-dev   libusb-1.0-0-dev

//  原始程式 xwin.cpp
#include "libuvc/libuvc.h"
#include <locale.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/cursorfont.h>
#include <signal.h>
class XwinClient {
    private:
        const char *title = "usbCamera";
        int         x, y, width, height;
        Display     *display= nullptr;
        XImage         *bg = nullptr;
        Window      winX;
        GC          gCtx;
        Colormap    cmap;
        XColor      fgColor;
    public:
        XColor     greyColor, blackColor, whiteColor ,redColor, greenColor, blueColor, yellowColor;
    ~XwinClient(){
        if (bg) XDestroyImage(bg);
        if (display) {
            XFreeColormap(display, cmap);
            XFreeGC(display, gCtx);
            XDestroyWindow(display, winX);
            XCloseDisplay(display);   
        }
    }
    XwinClient(int w = 800, int h = 600) {
        width = w;
        height = h;
        display = XOpenDisplay(getenv("DISPLAY"));
        winX = XCreateSimpleWindow(display,
            XDefaultRootWindow(display), 0, 0, w, h, 0,
            WhitePixel(display, 0),
            BlackPixel(display, 0)
        );
        gCtx = XCreateGC(display, winX, 0, 0);
        cmap = DefaultColormap(display, 0);        
        auto setColorMap = [this] (XColor &c, const char *str){
            XParseColor(display, cmap, str, &c);
            XAllocColor(display, cmap, &c);
        };
        setColorMap(whiteColor , "#FFFFFF");
        setColorMap(blackColor , "#000000");
        setColorMap(redColor   , "#FF0000");
        setColorMap(greenColor , "#00FF00");
        setColorMap(blueColor  , "#0000FF");
        setColorMap(greyColor  , "#7f7f7f");
        setColorMap(yellowColor, "#FFFF00");
        fgColor = redColor;
        XSetForeground(display, gCtx, fgColor.pixel);
        XSetStandardProperties(display, winX, title,
            "Icon", None, nullptr, 0, nullptr
        );
        XSelectInput(display, winX,
            StructureNotifyMask |
            ExposureMask         |
            ButtonPressMask     |
            KeyPressMask        |
            PointerMotionMask
        );
        XMapWindow(display, winX);
        XEvent         evt;
        while (true) {
            usleep(1000);
            XEvent evt;
            XNextEvent(display, &evt);
            if(evt.type == Expose && evt.xexpose.count == 0) break;
        }
        XFlush(display);
        XClearWindow(display, winX);
        XSync(display, false);
    }

    void setImageBGR(void *rgb888, int sw, int sh, int sch) {
        while (true) {
            if (bg) XDestroyImage(bg);
            bg = nullptr;
            XWindowAttributes info;
            XGetWindowAttributes(display, winX, &info);
            Visual *v  = info.visual;
            width  = info.width;
            height = info.height;
            int depth  = info.depth;
            int dline= 4 * width;
            void *fb_bg = malloc(dline * height);
            if (fb_bg == nullptr) break;
            bg = XCreateImage(display, v, depth, ZPixmap, 0, (char *)fb_bg, width, height, 32, 0);
            if (bg == nullptr) {
                free(fb_bg);
                break;
            }
            uint8_t *dst = (uint8_t *)fb_bg;
               uint8_t *src = (uint8_t *)rgb888;
            float xs = (float)sw / width ;
            float ys = (float)sh / height;
            int  sline = sch   * sw;
            int  pSize = sline * sh;
            int  zch = (sch > 4) ? 4 : sch;
            int   vk = 0;
            float fy = 0;
            for(int y = 0; y < height && vk < pSize; y ++) {
              int ld = 0;
              int ls = 0;
              float fx = 0;
              for (int x = 0; x < width && ls < sline; x ++) {
                dst[ld] = src[ls];
                for(int z = 1; z < zch; z ++) {
                  dst[ld + z] = src[ls + z];
                }
                ld += 4;
                fx += xs;
                ls = sch * (int)fx;
              }
              dst += dline;
              fy += ys;
              vk = sline * (int)fy;
              src = (uint8_t *)rgb888 + vk;
            }
            XPutImage(display, winX, gCtx, bg, 0, 0, 0, 0, width, height);
            break;
        }
        XSync(display, false);
    }
};

XwinClient x11 = XwinClient(800, 600);
 
void screenUpdate(uvc_frame_t *frame, void *user) {
    static bool active = false;
    int length = 2 * frame->width * frame->height;
    if (active || frame->frame_format != UVC_FRAME_FORMAT_YUYV || length != frame->data_bytes) return;
    active = true;
    auto int8bits = [](float v) { return  (v < 0) ? 0 : (v > 255) ? 0xff : (uint8_t)v; };
    uint8_t *yuyv = (uint8_t *)(frame->data);
    uint8_t *dest = (uint8_t *)user;
    for (int i = 0; i < length; i += 4) {
        int y0  = *yuyv;
        int u   = yuyv[1];
        int y1  = yuyv[2];
        int v   = yuyv[3];
        dest[2] = int8bits(y0 + 1.13983 * (v - 128));// R -> b0
        dest[1] = int8bits(y0 - 0.39465 * (u - 128) - 0.5806 * (v - 128));//G -> g0
        dest[0] = int8bits(y0 + 2.03211 * (u - 128));// B -> r0
        dest[5] = int8bits(y1 + 1.13983 * (v - 128));// R -> b1
        dest[4] = int8bits(y1 - 0.39465 * (u - 128) - 0.5806 * (v - 128));//G -> g1
        dest[3] = int8bits(y1 + 2.03211 * (u - 128));// B -> r1
        yuyv += 4;
        dest += 6;
    }
    x11.setImageBGR(user, frame->width , frame->height, 3);
    active = false;
}

int main(int argc, char **argv) {
      printf("press Ctrl+C to exit.\n");
    signal(SIGINT, [](int sig) { exit(1); });
      uvc_context_t *ctx;
      printf("uvc init ...\n");
      if (uvc_init(&ctx, NULL) == UVC_SUCCESS) {
        uvc_device_t *dev;
        printf("find device ...\n");
        if (uvc_find_device(ctx, &dev, 0, 0, NULL) == UVC_SUCCESS) {
          uvc_device_handle_t *handle;
              printf("open device ...\n");
              if (uvc_open(dev, &handle) == UVC_SUCCESS) { 
                const uvc_format_desc_t *descriptor = uvc_get_format_descs(handle); 
                const uvc_frame_desc_t *hFrame = descriptor->frame_descs;
                int fps = 10000000 / hFrame->dwDefaultFrameInterval;
                  uvc_stream_ctrl_t controller;
                  uvc_frame_format fmt = UVC_FRAME_FORMAT_YUYV;
                  uvc_error_t result = uvc_get_stream_ctrl_format_size(handle, &controller, fmt, hFrame->wWidth, hFrame->wHeight, 10);       
                  if (result == UVC_SUCCESS) {
                      uint8_t *image = new uint8_t[hFrame->wWidth * hFrame->wHeight * 3];
                      uvc_start_streaming(handle, &controller, screenUpdate, image, 0);
                    sleep(10);
                        uvc_stop_streaming(handle);
                    delete [] image;
                  }
                    uvc_close(handle);
              }
            uvc_unref_device(dev);
        }
        uvc_exit(ctx);
      }
}

編譯並執行, 因為開啟 /dev/video 需要權限, 暫時用 sudo 來執行:

     g++  xwin.cpp  -lX11  -luvc  -lusb-1.0  &&  sudo ./a.out


後記:使用原始 libuvc 及 libusb 程式庫編譯
   1. 先到官網下載 libusb 及 libuvc 原始程式庫
         https://github.com/libuvc/libuvc/tags
         https://github.com/libusb/libusb/releases
   2. 在專案根目錄內, 編輯一個檔案: CMakeLists.txt
   3. 在專案根目錄內, 另建三個目錄: lib/    src/    build/ 
   4. 將上述原始程式庫解壓縮, 放到專案 lib/ 目錄內
   5. 在專案根目錄 src/ 目錄內, 再建一個目錄命名為  libuvc/, 只要放入 libuvc_config.h 檔案
   6. 建一個空檔案命名為   libuvc_config.h 放到  src/libuvc/ 目錄內:  src/libuvc/libuvc_config.h
   7. 編輯主程式放到專案 src/ 目錄內:  src/xwin.cpp
   8. 切換到專案目錄下 build/  執行 cmake .. 產生 Makefile
   9. 編譯時, 在專案 build/ 目錄內執行 make 就會產生可執行檔 xwin, 暫時可用 root 權限或用 sudo  ./xwin 來執行
# 檔案 CMakeLists.txt 內容
cmake_minimum_required(VERSION 3.16)
project(xwin)
set(topDir ${PROJECT_SOURCE_DIR})
set(uvcDir ${topDir}/lib/libuvc-0.0.7)
set(usbDir ${topDir}/lib/libusb-1.0.27)
set(uvcSource 
  ${uvcDir}/src/device.c
  ${uvcDir}/src/init.c
  ${uvcDir}/src/stream.c
)
set(usbSource  
  ${usbDir}/libusb/core.c
  ${usbDir}/libusb/descriptor.c
  ${usbDir}/libusb/hotplug.c
  ${usbDir}/libusb/io.c
  ${usbDir}/libusb/strerror.c
  ${usbDir}/libusb/sync.c
  ${usbDir}/libusb/os/events_posix.c
  ${usbDir}/libusb/os/threads_posix.c
  ${usbDir}/libusb/os/linux_usbfs.c
  ${usbDir}/libusb/os/linux_netlink.c
)
include_directories(
    $(topDir)/src
    ${uvcDir}/include
    ${usbDir}/android
    ${usbDir}/libusb
    ${usbDir}/libusb/os
)
add_library(uvcdriver
    ${uvcSource}
    ${usbSource}
)
add_executable(xwin
    ${topDir}/src/xwin.cpp
)
target_link_libraries(xwin  
    X11
    uvcdriver
)

2024年4月17日 星期三

利用 smart pointer 播放 *.wav 與 *.aac 檔案的整合程式

// 主程式: play.cpp
#include <memory>
#include <stdio.h>
#include <sys/mman.h>
#include <fdk-aac/aacdecoder_lib.h>
#include "alsa/asoundlib.h"
struct PCM16 {
    int16_t *data;
    int length;
    int channel;
    int fs;
};
const PCM16 zeroPCM16 = {.data = (int16_t *)0, .length = 0, .channel = 0, .fs = 0};
const uint32_t fsHz[16] = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 0, 0, 0};
const uint32_t multiChannel[16] = {0, 1, 2, 3, 4, 5 + 1, 7 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0};
struct ChunkWAV { // sizeof(ChunkWAV) = 8
    char id[4];
    int32_t length;
    char *textID() {
        static char str[5];
        memcpy(str, id, 4);
        return str;
    }
    bool is_id(const char *name) {
        if (*name == 0) return false;
        for (int i = 0; i < 4; i ++) {
            if (name[i] == 0) break; // end of String
            if (name[i] != id[i]) return false;
        }
        return true;
    }
};
struct StructWAV { // sizeof(StructWAV) = 36
    ChunkWAV title;
    char wave[4];
    ChunkWAV fmt;
    int16_t  pcmType   , channels;
    int32_t  sampleRate, byteRate;
    int16_t  frameBytes, bitsData;
    bool is_wav() {
        if (! title.is_id("RIFF")) return false;
        static const char *waveID = "WAVE";
        for (int i = 0; i < 4; i ++) { if (wave[i] != waveID[i]) return false; }
        return fmt.is_id("fmt");
    }
};

struct WavMMAP {
    void *mmapFile = MAP_FAILED; // to management mmap file
    int32_t length = 0;// mmap file length
    int32_t position = 0; // trace file position
    int32_t audioStart = 0; // start position of audio data
    bool syncFound = false;
    int fs = 8000;
    int channel = 2;
    int frameBytes = 4;
    virtual ~WavMMAP() { if (mmapFile != MAP_FAILED) munmap(mmapFile, length); }// auto unmap
    virtual PCM16 decode() { return wavDecode(); }
    PCM16 wavDecode() {
        if (position < length) {
            auto src = (unsigned char *)mmapFile + position;// byte pointer
            const uint32_t maxSteps = length - position;// availabe bytes in file
            int tempSteps = 1024 * frameBytes;// number of byte to return
            if (tempSteps > maxSteps) tempSteps = maxSteps;
            position += tempSteps;
            return PCM16 {
                .data =  (int16_t *)src,
                .length = tempSteps / frameBytes,
                .channel = channel,
                .fs = fs
            };
        }
        return zeroPCM16;
    }
    WavMMAP(){ }// basic constructor
    WavMMAP(const char *filename) {
        if (mmapFile != MAP_FAILED) munmap(mmapFile, length);
        int fd = open(filename, O_RDONLY);
        if (fd < 0) {
            printf("%s => not found.\n", filename);
        } else {
            int fileLength = lseek(fd, 0l, SEEK_END);
            mmapFile = mmap(0, fileLength, PROT_READ, MAP_PRIVATE, fd, 0);
            close(fd);
            length = fileLength;

            auto header = (StructWAV *)mmapFile;
            if (header->is_wav()) { // wav file
                fs = header->sampleRate;
                channel   = header->channels;
                frameBytes = header->frameBytes;
                audioStart = sizeof(StructWAV);
                int offset = sizeof(ChunkWAV);
                while (audioStart < length) {
                    auto *chunkList = (ChunkWAV *)((unsigned char *)mmapFile + audioStart);
                    if (chunkList->is_id("data")) {
                        audioStart += offset;
                        break;
                    }
                    audioStart += offset + chunkList->length;
                }
            } else {
                auto uid = (unsigned char *)mmapFile;
                if (uid[0] == 0xff && (uid[1] >> 4) == 0xf) {
                    syncFound = true;
                    if (uid[1] == 0xf1 || uid[1] == 0xf9) {// aac file
                        fs = fsHz[(uid[2] & 0x3e) >> 2];
                        channel = multiChannel[((uid[2] & 1) << 2) | (uid[3] >> 6)];
                        frameBytes = channel * 2; // 16bits
                    }
                }
            }
        }
        position = audioStart;
    }
};

struct FdkAAC: WavMMAP {
    int16_t pcmdist[1152 * 5];
    HANDLE_AACDECODER decoder = aacDecoder_Open(TT_MP4_ADTS, 1);
    PCM16 decode( ) override {
        if (! syncFound) {// fall back to use super.wavDecoder()
            return wavDecode();
        }
        if (position < length) {
            unsigned char *src[] = { (unsigned char *)mmapFile + position };
            const uint32_t maxSteps = length - position;
            uint32_t tempSteps = maxSteps;
            aacDecoder_Fill(decoder, src, &maxSteps, &tempSteps);
            int result = (int)aacDecoder_DecodeFrame(decoder, pcmdist, sizeof(pcmdist), 0);
            position += maxSteps - tempSteps;
            if (result == AAC_DEC_OK) {                
                CStreamInfo *info = aacDecoder_GetStreamInfo(decoder);                        
                return PCM16 {
                    .data = pcmdist,
                    .length = info->frameSize,
                    .channel = info->numChannels,
                    .fs = info->sampleRate
                };
            }
        }
        return zeroPCM16;
    }
    ~FdkAAC() { aacDecoder_Close(decoder); }
    FdkAAC(const char *filename): WavMMAP(filename) { }
};
typedef std::unique_ptr<WavMMAP> MyDecoder;

int main(int argc, char const *argv[]) {
    const char* filename[2] = {"test.aac", "test.wav"};
    int len = sizeof(filename) / sizeof(filename[0]);
    for (int i = 0; i < len; i++) {
        auto play = MyDecoder(new FdkAAC(filename[i]));
        auto frame = play->decode();
          if (frame.length == 0) break;
        snd_pcm_t *handle;
        if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) == 0) { // hw:0,0 , default
            if (snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, frame.channel, frame.fs, 1, frame.fs / 4) == 0) {     
                do {
                     int err = snd_pcm_writei(handle, frame.data, frame.length);
                     if (err < 0) {// alsa try to recover
                         if (snd_pcm_recover(handle, err, 0) < 0) {
                            printf("alsa can't recover\n");
                            break;
                        }
                     }
                    frame = play->decode();
                } while (frame.length > 0);
            }
            snd_pcm_close(handle);
        }
    }
    return 0;
}

編譯並執行:

        g++ play.cpp -lfdk-aac -lasound && ./a.out 

2024年4月10日 星期三

簡單的 CMakeLists.txt 用來編譯並連結程式庫

專案 mp3play 的目錄結構:
mp3play/
    ./CMakeList.txt
    ./lib/
        ./*.c
    ./src/
        ./main.cpp    
    ./include/
        ./*.h

# 檔案 CMakeLists.txt 內容
cmake_minimum_required(VERSION 3.16)
project(mp3play)
set(mp3Lib  "${PROJECT_SOURCE_DIR}/lib")
set(decoder "${PROJECT_SOURCE_DIR}/src")
include_directories($(decoder)  ${PROJECT_SOURCE_DIR}/include)
file(GLOB_RECURSE  cLib  ${mp3Lib}/*.c)
add_library (myLib ${cLib})
add_executable(mp3play
    ${decoder}/main.cpp
)
target_link_libraries(mp3play  myLib  -lasound)

要編譯時, 先執行以下命令產生 Makefile 檔案
    cd  mp3play &&  make build  &&  cd build  &&  cmake  ..
接著再執行 make 就能產生執行擋了
    cd  mp3play/build   &&  make   &&  ./mp3play

// 主程式: mp3play/src/main.cpp
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include "alsa/asoundlib.h"
#include "mp3dec.h"
struct PCM16 {
    int16_t *data;
    int length;
    int channel;
    int fs;
};
const PCM16 zeroPCM16 = {.data = (int16_t *)0, .length = 0, .channel = 0, .fs = 0};
const char *path_to_close = "./close!";
auto stepDecoder = [](const void *path = nullptr, int repeate = 0) {
    static void *mmapFile = MAP_FAILED; // to management mmap file
    static HMP3Decoder decoder = nullptr;
    static int32_t length = 0;// mmap file length
    static int32_t position = 0; // trace file position
    static int32_t remainRepeate = 0;
    if (path == path_to_close) { // to close decoder
        if (decoder) {
            MP3FreeDecoder(decoder);
            decoder = nullptr;
            length = 0;
            position = 0;
            remainRepeate = 0;
        }
    } else {
        if (path) {
            const char *filename = (const char *)path;
            int fd = open(filename, O_RDONLY);
            if (mmapFile != MAP_FAILED) munmap(mmapFile, length);
            if (fd < 0) {
                printf("%s => not found.\n", filename);
            } else {
                int fileLength = lseek(fd, 0l, SEEK_END);
                if (fileLength >= 512) {
                    mmapFile = mmap(0, fileLength, PROT_READ, MAP_PRIVATE, fd, 0);
                    if (mmapFile == MAP_FAILED)  {
                        printf("%s mmap fail.\n", filename);
                    } else {
                        if (decoder) MP3FreeDecoder(decoder);
                        decoder = MP3InitDecoder();
                        printf("%s file length = %d\n", filename, fileLength);
                        length = fileLength;// mmap sucess
                        position = 0;
                        remainRepeate = repeate;                   
                    }
                }
                close(fd);// After the mmap() call, fd can be closed immediately.
            }
        }
        if (remainRepeate > 0 && (position >= length)) {
            printf("End of file, position = %d, wrap arond to repeate again. remainRepeate = %d\n", position, remainRepeate);
            remainRepeate --;
            position = 0;
        }
        if (position < length) {
            unsigned char *src = (unsigned char *)mmapFile + position;
            static int16_t pcmdist[1152 * 2];
            const uint32_t maxSteps = length - position;
            int tempSteps = maxSteps;// tempSteps will be updated by decoder
            for (int i = 0; i < maxSteps; i ++) {
                if (*src == 0xff && (src[1] >> 4) == 0xf) break;// find sync word
                tempSteps --; // size shrink
                src ++;  // seek one by one
            }
             int result = MP3Decode(decoder, &src, &tempSteps, pcmdist, 0);
            position += maxSteps - tempSteps; // go ahead, and back off by tempSteps
            printf("maxSteps = %8d, tempSteps = %8d, position = %8d, steps = %8d, err = %4x:\t\t\n",
                maxSteps,
                tempSteps,
                position,
                maxSteps - tempSteps,
                (unsigned)result
            );            
            if (result == ERR_MP3_NONE) {
                MP3FrameInfo frameinfo;
                MP3GetLastFrameInfo(decoder, &frameinfo);
                int nChans = frameinfo.nChans;
                return PCM16 {
                    .data = pcmdist,
                    .length = (int)frameinfo.outputSamps / nChans,
                    .channel = nChans,
                    .fs = frameinfo.samprate
                };
            }
        }
    }
    return zeroPCM16;
};
int main(int argc, char const *argv[]) {    
    PCM16 frame = stepDecoder((argc > 1) ? argv[1] : "test.mp3");
     if (frame.length==0) return 0;
    snd_pcm_t *handle;
    if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) == 0) {
        if (snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, frame.channel, frame.fs, 1, frame.fs / 4) == 0) {    
            do {
                 int err = snd_pcm_writei(handle, frame.data, frame.length);
                 if (err < 0) {// try to recover
                     if (snd_pcm_recover(handle, err, 0) < 0) {
                        printf("alsa can't recover\n");
                        break;
                    }
                 }
                frame = stepDecoder();
            } while (frame.length);
        }
        snd_pcm_close(handle);
    }   
    stepDecoder(path_to_close);
    return 0;
}

Linux mint 玩 waydroid 一些心得

1. 目前使用 linux mint 22.1 作業系統可以順利跑起來, 可上官網去下載, 並安裝到硬碟. 2. 安裝 waydroid 可上網站  https://docs.waydro.id 參考看看:    https://docs.waydro.id/usage/inst...