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

2024年4月7日 星期日

linux 使用 ALSA lib 播放 aac 檔

先安裝開發程式庫

        sudo   apt   install   libfdk-acc-dev   libasound-dev

下載一些測試 aac 檔案: https://espressif-docs.readthedocs-hosted.com/projects/esp-adf/en/latest/design-guide/audio-samples.html

// 主程式: aacplay.cpp
#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 char *path_to_close = "./close!";
auto stepDecoder = [](const void *path = nullptr, int repeate = 0) {
    static void *mmapFile = MAP_FAILED; // to management mmap file
    static HANDLE_AACDECODER 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) {
            aacDecoder_Close(decoder);
            decoder = nullptr;
            length = 0;
            position = 0;
            remainRepeate = 0;
        }
    } else {
        if (path) {// use non-null path to initialize mmap
            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) aacDecoder_Close(decoder);
                        decoder = aacDecoder_Open(TT_MP4_ADTS, 1);
                        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)) { // when repeate enable
            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 * 5];// distinct PCM16 buffer for 5 channels
            const uint32_t maxSteps = length - position;
            uint32_t tempSteps = maxSteps;// tempSteps will be updated by decoder
            aacDecoder_Fill(decoder, src, &maxSteps, &tempSteps);
            int result = (int)aacDecoder_DecodeFrame(decoder, pcmdist, sizeof(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 == AAC_DEC_OK) {               
                CStreamInfo *info = aacDecoder_GetStreamInfo(decoder);                         
                return PCM16 {
                    .data = pcmdist,
                    .length = info->frameSize,
                    .channel = info->numChannels,
                    .fs = info->sampleRate
                };
            }
        }
    }
    return zeroPCM16;
};

int main(int argc, char const *argv[]) {    
    PCM16 frame = stepDecoder((argc > 1) ? argv[1] : "test.aac");
     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;
}

編譯並執行:

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

2024年4月6日 星期六

linux 使用 ALSA lib 播放 mp3 檔

先上官網下載 minimp3 原始檔: https://github.com/lieff/minimp3

只需將檔案 minimp3.h 複製到專案目錄.  再編輯 mp3 播放主程式, 同樣透過 ALSA library 來播放:
// mp3play.cpp
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "alsa/asoundlib.h"
#define MINIMP3_IMPLEMENTATION
#include "minimp3.h"

int main(int argc, char *argv[]) {
    const char *filename = (argc > 1) ? argv[1] : "test.mp3";
    int fd = open(filename, O_RDONLY);
    if (fd < 0) {
        printf("%s => not found\n", filename);
        return -1;
    }
    int fileLength = lseek(fd, 0, SEEK_END);
    unsigned char *fileMMAP = (unsigned char *) mmap(0, fileLength, PROT_READ, MAP_PRIVATE, fd, 0);
    int16_t speaker[MINIMP3_MAX_SAMPLES_PER_FRAME];

    mp3dec_frame_info_t info;
    mp3dec_t mp3decoder;
    mp3dec_init(&mp3decoder);
    int uframes = mp3dec_decode_frame(&mp3decoder, fileMMAP, fileLength, speaker, &info);// 1st frame to get channels && sampleRate
    if (uframes > 0) {
        printf("play uframes = %d, offset =%d, channels=%d, sampleRate=%d, frame_bytes=%d\n", uframes, info.frame_offset, info.channels, info.hz, info.frame_bytes);
        unsigned int channels = info.channels;
        unsigned int sampleRate = info.hz;
        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, channels, sampleRate, 1, sampleRate / 4) == 0) {
                do {
                     const int err = snd_pcm_writei(handle, speaker, uframes);
                     if (err < 0) {
                         if (snd_pcm_recover(handle, err, 0) < 0) break;// alsa try to recover
                     }
                    fileMMAP += info.frame_bytes;// mp3 file position goes ahead
                    uframes = mp3dec_decode_frame(&mp3decoder, fileMMAP, uframes, speaker, &info);// is 1152 enough?
                } while (uframes > 0);
            }
            snd_pcm_close(handle);
        }
    }
    close(fd);
    return 0;
}
編譯並執行

      g++    mp3play.cpp    -lasound   &&   ./a.out

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

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