學習讓生命更豐富
2024年8月29日 星期四
簡單 c 程式碼, 根據五行八卦相生相剋推斷吉凶
// 五行:
// 木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
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日 星期二
簡單利用查表法顯示農曆春節的月曆
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 程式碼
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 五行 // ...
-
市面上便宜的時鐘, 內部機芯大多使用晶體振盪器產生固定頻率的脈沖去驅動 Lavet-type 步進馬達, 它只要一個 1.5V 電池就足以推動, 而馬達扭力主要是靠線圈內的電流產生磁力所致, 因此電流才是動力來源. 電壓太高會造成線圈電流飽和導致發熱,可能影響震盪頻率甚至燒毀...
-
參考資料: https://developer.android.com/training/connect-devices-wirelessly/nsd linux mint 上針對區域網路的名稱服務協定可以使用 avahi, 透過 avahi-browse -a -r 就能...