2026/8/31

tc (traffic control) in linux

tc(traffic control)是 Linux 裡用來做網路流量控制(QoS, Quality of Service)的工具。可針對網路卡,進行限速(rate limiting)、排程(traffic scheduling)、優先權控制(priority)、模擬網路狀況(延遲、丟包、抖動)、封包分類與整形(shaping)的網路封包控制。主要用途是用來模擬網際網路的封包狀況。

流量控制方式

  • shaping

    當流量被shaping限制後,它的傳輸速率就被控制在某個值以下。限制值可以遠小於有效頻寬,這樣可以平滑突發的資料流量,使網路更爲穩定。shaping(限制)只適用於向外的流量。shaping 只能用在 egress 對外的資料輸出。

  • scheduling

    通過調度封包的傳輸,可以在頻寬範圍內,按照優先等級分配頻寬。scheduling 只能用在 egress 對外的資料輸出。

  • policing

    shaping用於處理 egress 對外的流量,而 policing 用於處理 ingress 接收到的流量。

  • dropping

    如果流量超過某個設定的頻寬,就丟棄資料,可用在 egress 及 ingress。

主要元件

  1. qdisc (Queueing Discipline)

    佇列規則,決定封包排隊的送出規則。當kernel需要通過某個網絡port發送資料,它都需要按照爲這個port配置的qdisc排隊規則,把資料加入queue。然後kernel會儘可能地從qdisc裏面取出資料,交給網絡適配器驅動模塊。最簡單的qdisc是 pfifo,它不對進入的封包做任何的處理,採用先入先出的方式通過隊列。不過,它會暫存網絡接口短時間內無法處理的封包。

    常見的 qdisc:

    • pfifo_fast(預設)

    • htb(常用做限速)

    • fq_codel(改善 bufferbloat)

    • netem(模擬網路)

  2. class

    在 qdisc 底下建立不同流量分類,用來分配不同頻寬。不同的類別中可以包含更深入的QDisc(排隊規則),通過這些細分的QDisc還可以爲進入的隊列的封包排隊。通過設置各種類別封包的離隊次序,QDisc可以設定網路數據流量的優先等級。

  3. filter, policy 過濾器

    用來把封包分類到不同 class,決定它們按照何種QDisc進入隊列。分類的方法可以有多種,使用fileter(過濾器)就是其中之一。使用filter(過濾器)分類時,kernel會呼叫附屬於這個 class 的所有過濾器,直到回傳一個判決。如果沒有判決返回,就作進一步的處理,而處理方式和QDISC有關。filter(過濾器)是放在QDisc內部。

指令

tc [OPTIONS] OBJECT COMMAND

常見 OBJECT:

  • qdisc

  • class

  • filter

指令內可使用的資料單位

  • 頻寬或流量單位

    kbps
    mbps
    kbit
    mbit
    bps
  • 資料單位

    kb
    mb
    mbit
    kbit
    b
  • 時間

    s/sec/secs
    ms/msec/msecs
    us/usec/usecs

options

-s, -stats, -statistics: output more statistics about packet usage.
-d, -details: output more detailed information about rates and cell sizes.
-r, -raw: output raw hex values for handles.
-p, -pretty: decode filter offset and mask values to equivalent filter commands based on TCP/IP.
-iec: print rates in IEC units (ie. 1K = 1024).

Examples

tc 通常是控制 egress 的流量

查看設定

tc -s qdisc show dev eth0
tc class show dev eth0
tc filter show dev eth0
# tc -s qdisc show dev enp1s0
qdisc mq 0: root
 Sent 220264 bytes 431 pkt (dropped 0, overlimits 0 requeues 2)
 backlog 0b 0p requeues 2
qdisc fq_codel 0: parent :4 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
 Sent 20103 bytes 107 pkt (dropped 0, overlimits 0 requeues 0)
 backlog 0b 0p requeues 0
  maxpacket 0 drop_overlimit 0 new_flow_count 0 ecn_mark 0
  new_flows_len 0 old_flows_len 0
qdisc fq_codel 0: parent :3 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
 Sent 43974 bytes 148 pkt (dropped 0, overlimits 0 requeues 0)
 backlog 0b 0p requeues 0
  maxpacket 0 drop_overlimit 0 new_flow_count 0 ecn_mark 0
  new_flows_len 0 old_flows_len 0
qdisc fq_codel 0: parent :2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
 Sent 146267 bytes 124 pkt (dropped 0, overlimits 0 requeues 2)
 backlog 0b 0p requeues 2
  maxpacket 8925 drop_overlimit 0 new_flow_count 1 ecn_mark 0
  new_flows_len 0 old_flows_len 0
qdisc fq_codel 0: parent :1 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
 Sent 9920 bytes 52 pkt (dropped 0, overlimits 0 requeues 0)
 backlog 0b 0p requeues 0
  maxpacket 0 drop_overlimit 0 new_flow_count 0 ecn_mark 0
  new_flows_len 0 old_flows_len 0
# tc -d qdisc show dev enp1s0
qdisc mq 0: root
qdisc fq_codel 0: parent :4 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
qdisc fq_codel 0: parent :3 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
qdisc fq_codel 0: parent :2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
qdisc fq_codel 0: parent :1 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
  • qdisc mq 0: root

    根節點 mq : multi-queue

    代表你的硬體網路卡有多個傳輸佇列(在這個例子中是 4 個,分別對應 parent :1:4)。通常發生在多核心 CPU 的伺服器上,系統會將網路流量分散到不同的 CPU 核心處理,以提升效能

  • fq_codel (Fair Queuing Controlled Delay)

    這是目前 Linux 系統非常推薦的排隊規則,它由兩個部分組成

    • FQ (Fair Queuing):確保每個「連線」(Flow)都能公平分配頻寬。例如,即使有人在下載大檔案,你的網頁瀏覽或視訊通話依然能順暢通過。

    • CoDel (Controlled Delay):這是一個智慧演算法,當它發現緩衝區(Buffer)塞車太久時,會主動丟棄封包或標記 ECN,強制降低延遲,解決「緩衝區膨脹」問題。

  • 以其中一個 fq_codel 為例:

    • limit 10240p: 佇列中最多可以存放 10,240 個封包。

    • flows 1024: 它最多可以同時追蹤並隔離 1024 個不同的連線。

    • target 5ms / interval 100ms: 這是 CoDel 的核心參數。它會嘗試將延遲控制在 5ms 以內;如果延遲超過這個數值持續 100ms,它就會開始干預流量。

    • ecn: 支援顯式擁塞通知。當網路塞車時,它會通知發送端減速,而不是直接丟棄封包。

  • -s 統計

    Sent 146267 bytes 124 pkt (dropped 0, overlimits 0 requeues 2)

    • Sent: 該佇列目前傳送的資料量與封包數。

    • dropped 0: 這是一個好現象。 代表目前沒有封包因為塞車而被強制丟棄。

    • requeues 2: 封包重新排隊的次數。通常發生在硬體驅動程式暫時無法處理封包時(例如網路卡發送視窗滿了),這在正常運作下是偶爾會發生的。

    • new_flow_count 1: 代表自從統計開始以來,有一個「新連線」被偵測到。fq_codel 會優先處理新連線(互動式流量),以保證反應速度。

刪除設定

清除所有設定

tc qdisc del dev eth0 root

備份/還原完整設定

# 備份
tc -dump qdisc show dev eth0 > tc-backup.txt
tc -dump class show dev eth0 >> tc-backup.txt
tc -dump filter show dev eth0 >> tc-backup.txt

# 還原前先清空
tc qdisc del dev eth0 root
tc qdisc del dev eth0 ingress
tc -batch tc-backup.txt

完整備份所有網卡

tc -dump qdisc show > tc-all.txt
tc -dump class show >> tc-all.txt
tc -dump filter show >> tc-all.txt

# 還原
tc -batch tc-all.txt

限制整個網卡頻寬

限制 eth0 最大 1Mbps

tc qdisc add dev eth0 root tbf rate 1mbit burst 32kbit latency 400ms
  • tbf = Token Bucket Filter(簡單限速)

  • rate = 頻寬

  • burst = 突發流量

  • latency = 最大延遲

使用 HTB 做分流

# 建立 root qdisc
tc qdisc add dev eth0 root handle 1: htb default 30

# 建立 class 做頻寬分配
tc class add dev eth0 parent 1: classid 1:1 htb rate 10mbit
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 2mbit
1: (root)
 └── 1:1 (10mbit)
      ├── 1:10 (5mbit)
      └── 1:20 (2mbit)

依 IP 分流

ex

192.168.1.100 → 5Mbps
其他 → 2Mbps
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip dst 192.168.1.100 flowid 1:10

模擬網路延遲

增加 100ms 延遲

tc qdisc add dev eth0 root netem delay 100ms

模擬丟包

10% 封包丟失

tc qdisc add dev eth0 root netem loss 10%

延遲 + 抖動 + 丟包

tc qdisc add dev eth0 root netem delay 100ms 20ms loss 5%
  • 100ms = 基本延遲

  • 20ms = 抖動(jitter)

  • loss 5% = 丟包

控制 ingress

tc qdisc add dev eth0 handle ffff: ingress

ingress shaping ifb

tc 原生只能控制 egress(送出流量) ,要控制 ingress(進來的流量),就要「轉送」到 ifb 再做 shaping

eth0 (ingress)
   ↓ redirect
ifb0 (egress)
   ↓ tc 控制

限制下載速度(ingress 10Mbps)

# 載入 ifb 模組
modprobe ifb

# 指定數量
modprobe ifb numifbs=1

# 啟用 ifb0
ip link add ifb0 type ifb
ip link set dev ifb0 up

# 在 eth0 加 ingress qdisc
tc qdisc add dev eth0 handle ffff: ingress

# 把 ingress 流量導到 ifb0
# mirred = mirror/redirect
tc filter add dev eth0 parent ffff: protocol ip u32 \
match u32 0 0 action mirred egress redirect dev ifb0

# 在 ifb0 上做限速
tc qdisc add dev ifb0 root tbf rate 10mbit burst 32k latency 400ms

# show
tc -s qdisc show dev ifb0

clear

tc qdisc del dev eth0 ingress
tc qdisc del dev ifb0 root
ip link delete ifb0

不同 IP 不同下載速度

ex

192.168.1.100 → 5 Mbps
其他 → 2 Mbps

用 HTB

tc qdisc add dev ifb0 root handle 1: htb default 20

tc class add dev ifb0 parent 1: classid 1:1 htb rate 10mbit

tc class add dev ifb0 parent 1:1 classid 1:10 htb rate 5mbit
tc class add dev ifb0 parent 1:1 classid 1:20 htb rate 2mbit

# filter 分流
# ingress → ifb 後.  source IP 才是外部來源 IP
tc filter add dev ifb0 protocol ip parent 1: prio 1 u32 \
match ip src 192.168.1.100 flowid 1:10

note: ingress 只能有一個 qdisc

References

程式扎記: [Linux 文章收集] TC 入門 (traffic control)

TC (Traffic Control) - HackMD

2026/8/24

Java Multimap

一般基本的 Map 是唯一的 Key,搭配一個 Value 的資料結構,實務上,有時候會遇到,一個 Key,搭配對應的 Value,在 Value 是有多種可能的,例如在公司部門分類下,每個部門都有各自的員工。

這種資料結構,用標準 JDK 提供的 Map 搭配 ArrayList 就可以完成,但還有其他幾個封裝比較好的函式庫,有提供這種資料結構的類別實作。

  • Apache Commons Collecitons 的 MultiValuedMap

  • Guava 的 Multimap

  • Spring Framework 的 MultiValueMap

如果要測試這幾種實作方式,要先引用函式庫

    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>33.5.0-jre</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>7.0.6</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.5.0</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

Java 實例

import com.google.common.collect.Multimap;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.*;

public class ArrayListMultimap {
    public void main(String[] args) {
        ArrayListMultimap.jdk();
        ArrayListMultimap.apacheCommons();
        ArrayListMultimap.guava();
        ArrayListMultimap.spring();
    }

    public static void addEmployee(Map<String, List<String>> map, String department, String employee) {

        if (!map.containsKey(department)) {
            map.put(department, new ArrayList<>());
        }

        map.get(department).add(employee);
    }

    public static void jdk() {

        Map<String, List<String>> departmentEmployees = new HashMap<>();

        addEmployee(departmentEmployees, "RD", "Thomas");
        addEmployee(departmentEmployees, "RD", "James");
        addEmployee(departmentEmployees, "MA", "Emma");
        addEmployee(departmentEmployees, "MA", "Dave");
        addEmployee(departmentEmployees, "HR", "Carol");

        System.out.println("=== JDK HashMap + List<String> ===");
        System.out.println(departmentEmployees);
    }
    public static void apacheCommons() {
        MultiValuedMap<String, String> departmentEmployees = new ArrayListValuedHashMap<>();

        departmentEmployees.put("RD", "Thomas");
        departmentEmployees.put("RD", "James");
        departmentEmployees.put("MA", "Emma");
        departmentEmployees.put("MA", "Dave");
        departmentEmployees.put("HR", "Carol");

        System.out.println("=== Apache Commons ArrayListValuedHashMap ===");
        System.out.println(departmentEmployees);
    }
    public static void guava() {
        List<String> rdlist = Arrays.asList("Thomas", "James");
        List<String> malist = Arrays.asList("Emma", "Dave");
        List<String> hrlist = Arrays.asList("Carol");

        Multimap<String, List<String>> listMultimap = com.google.common.collect.ArrayListMultimap.create();
        listMultimap.put("RD", rdlist);
        listMultimap.put("MA", malist);
        listMultimap.put("HR", hrlist);

        System.out.println("=== guava ArrayListMultimap ===");
        System.out.println(listMultimap);
    }

    public static void spring() {

        MultiValueMap<String, String> departmentEmployees = new LinkedMultiValueMap<>();

        departmentEmployees.add("RD", "Thomas");
        departmentEmployees.add("RD", "James");
        departmentEmployees.add("MA", "Emma");
        departmentEmployees.add("MA", "Dave");
        departmentEmployees.add("HR", "Carol");

        System.out.println("=== Spring LinkedMultiValueMap ===");
        System.out.println(departmentEmployees);
    }
}

執行結果

=== JDK HashMap + List<String> ===
{RD=[Thomas, James], MA=[Emma, Dave], HR=[Carol]}
=== Apache Commons ArrayListValuedHashMap ===
{RD=[Thomas, James], MA=[Emma, Dave], HR=[Carol]}
=== guava ArrayListMultimap ===
{RD=[[Thomas, James]], MA=[[Emma, Dave]], HR=[[Carol]]}
=== Spring LinkedMultiValueMap ===
{RD=[Thomas, James], MA=[Emma, Dave], HR=[Carol]}

2026/8/17

erlang 的 == 與 =:=

erlang 的相等比較有兩種 == 與 =:= 。同樣都是相等,但比較的嚴格等級不同。

==

這是等值比較,如果比較的是整數跟浮點數,兩種不同的資料型別,在比較之前,會自動做型別轉換,然後再根據數值進行比較。

> 1 == 1.0.
true
> 1 == 1.
true

=:=

全等比較,除了數值要一樣,資料型別也必須要一樣。

> 1 =:= 1.0.
false
> 1 =:= 1.
true

pattern matching

erlang 的 pattern matching 行為比較接近 =:=

假設有這樣的一個 function

check_num(1) -> integer;
check_num(1.0) -> float.

在呼叫 chech_num(1.0) 的時候,會進入第二個 clause,不會進入第一個。因為 1 跟 1.0 是不同的資料型別

/= 與 =/=

不相等,也一樣有兩種寫法

  • /=:對應於 ==。若值在算術上不相等,則為 true

  • =/=:對應於 =:=。若值或型別任一不同,則為 true

>  1 /= 1.0.
false
> 1 =/= 1.0.
true

Term Ordering

在 erlang 的所有資料型別,都能夠直接比較大小,這是因為在處理集合的排序時,能夠得到一個固定的結果。資料型別的比較順序如下

number < atom < reference < fun < port < pid < tuple < map < list < bitstring

sample

> 999999 < apple.
true
> Mixed = [apple, 10, {test, 1}, [1,2,3], 5.5, "hello"].
[apple,10,{test,1},[1,2,3],5.5,"hello"]
> lists:sort(Mixed).
[5.5,10,apple,{test,1},[1,2,3],"hello"]
% 順序:數字(5.5, 10) < atom(apple) < tuple({test,1}) < list([1,2,3], "hello")

Summary

要確保資料比對正確,大部分的狀況都是要寫成 =:=,僅有少部分,遇到了整數跟浮點數必須混合比較時,才需要用到 ==。

2026/8/10

RPi 設定使用 camera

camera

更新套件

sudo apt update
sudo apt install ffmpeg v4l-utils libcamera-apps -y

使用一般的 web camera

用這個指令列出所有 devices,通常外接的 camera 會在 /dev/video0

v4l2-ctl --list-devices

用這個指令查詢 video0 支援的 video 格式

v4l2-ctl --device=/dev/video0 --list-formats-ext

如果有 RPi 官方提供的 camera 模組。

安裝時

  1. 找到 CSI 相機接口,將拉起連接埠的黑色或白色塑膠卡榫

  2. 有藍色膠帶的一面要朝向 HDMI/USB 埠,金屬接點面朝向 CSI 接口內部

  3. 插入排線後,將卡榫壓回固定

測試拍照

如果是用 web camera,可直接用 ffmpeg 拍照。

ffmpeg -f v4l2 -video_size 640x480 -i /dev/video0 -frames:v 1 test.jpg

如果是 RPI camera 模組,用 ffmpeg 會產生 error,要改用 rpi-cam-* 指令

rpicam-jpeg -o test.jpg

# video
rpicam-vid -t 5000 -o test.h264

將 RPi 改為 HotSpot AP

# 將 wifi 改為 hotspot AP
sudo nmcli device wifi hotspot ssid crpi4 password wifipassword ifname wlan0

# 設定可自動連線
sudo nmcli connection modify Hotspot connection.autoconnect yes

# 修改為 2.4G
sudo nmcli connection modify Hotspot 802-11-wireless.band bg

# 設定可從 wifi 轉發到 ethernet
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

音效卡

使用 UPTech 161 USB 音效卡測試

$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 004: ID 0d8c:0014 C-Media Electronics, Inc. Audio Adapter (Unitek Y-247A)
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub

要出現 C-Media 的那一行

$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: Headphones [bcm2835 Headphones], device 0: bcm2835 Headphones [bcm2835 Headphones]
  Subdevices: 8/8
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
  Subdevice #4: subdevice #4
  Subdevice #5: subdevice #5
  Subdevice #6: subdevice #6
  Subdevice #7: subdevice #7
card 1: vc4hdmi0 [vc4-hdmi-0], device 0: MAI PCM i2s-hifi-0 [MAI PCM i2s-hifi-0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 2: vc4hdmi1 [vc4-hdmi-1], device 0: MAI PCM i2s-hifi-0 [MAI PCM i2s-hifi-0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 3: Device [USB Audio Device], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

有出現 card 3: USB Audio

設定 USB 音效卡為預設輸出

sudo raspi-config
  • 進入 System Options -> Audio

  • 選擇你的 USB Audio Device,然後按下 Finish

調整音量

alsamixer

按下 F6 鍵,選擇你的 USB Sound Device

使用方向鍵將 SpeakerMic 的音量推高

 $ arecord -l
**** List of CAPTURE Hardware Devices ****
card 3: Device [USB Audio Device], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

card 3device 0 組合起來就是硬體代號 hw:3,0

讓系統預設就抓這張卡,我們建立一個 ALSA 設定檔

sudo nano /etc/asound.conf

defaults.ctl.card 3
defaults.pcm.card 3
defaults.timer.card 3

測試

$ aplay -D hw:3,0 /usr/share/sounds/alsa/Front_Center.wav
Playing WAVE '/usr/share/sounds/alsa/Front_Center.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono
aplay: set_params:1398: Channels count non available

因為這張 USB 音效卡(或是 ALSA 驅動)在處理 Mono(單聲道) 檔案時,無法直接對應到硬體的 Stereo(雙聲道) 輸出。

改為以下指令

$ aplay -D plughw:3,0 /usr/share/sounds/alsa/Front_Center.wav
Playing WAVE '/usr/share/sounds/alsa/Front_Center.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono

測試錄音 10秒

arecord -D plughw:3,0 -d 10 -f cd -vv test_mic.wav

設定為 0dB

amixer -c 3 sset Mic 0dB

錄影+錄音

透過 rpicam-vid 錄影後,轉發給 ffmpeg 增加錄音

  • -f segment: 自動分割

  • -segment_time 600: 每 600 秒 (10分鐘) 分割一次

  • -reset_timestamps 1: 每個片段的時間戳從 0 開始,避免播放器錯誤

  • -strftime 1: 允許檔名使用時間格式

rpicam-vid -t 0 --inline --width 1920 --height 1080 --framerate 30 \
    --bitrate 8000000 --profile high \
    --saturation 1.4 --contrast 1.2 --sharpness 1.5 --awb auto \
    -o - 2>/dev/null | \
ffmpeg -y -r 30 -i pipe:0 \
    -f alsa -i plughw:3,0 \
    -c:v copy -c:a aac -ac 1 \
    -af "volume=2.0" \
    -f segment -segment_time 300 -segment_format mp4 \
    /home/charley/Videos/test_%04d.mp4 > /dev/null 2>&1

錄影 mp4 格式,有可能會因為中途中斷而打不開檔案。改使用 ts 格式。iMovie 可讀取 mp4 及 ts 格式

rpicam-vid -t 0 --inline --width 1920 --height 1080 --framerate 30 \
    --bitrate 8000000 --profile high \
    --saturation 1.4 --contrast 1.2 --sharpness 1.5 --awb auto \
    -o - 2>/dev/null | \
ffmpeg -y -r 30 -i pipe:0 \
    -f alsa -i plughw:3,0 \
    -c:v copy -c:a aac -ac 1 \
    -af "volume=2.0" \
    -f segment -segment_time 300 -segment_format mpegts \
    /home/charley/Videos/test_%04d.ts > /dev/null 2>&1

調整畫質

AWB 模式

如果你覺得自動模式不準,可以嘗試適合的模式:

  • --awb auto: 預設自動。

  • --awb indoor: 適合一般室內燈光。

  • --awb tungsten: 適合黃光燈泡環境(這會強力補償藍色,消除黃感)。

  • --awb fluorescent: 適合辦公室日光燈環境。

或是使用 manual mode

  • --awbgains R,B: 第一個數字是紅增益,第二個是藍增益。

  • 調低第一個數 (紅)調高第二個數 (藍) 可以讓畫面變冷色調。

rpicam-jpeg --width 1920 --height 1080 --awbgains 1.2,1.8 -o test_cool.jpg

2026/8/3

pdf OCR

當遇到 pdf 複製文字是無法辨識的內容時,這是 pdf 保護文字資料的方法,可透過轉成圖片,再做 OCR 解決。

安裝軟體

# ghostscript. pdf -> 圖片
dnf install ghostscript

# 圖片
dnf install ImageMagick

# Tesseract OCR(圖片 → 文字)
sudo dnf install tesseract
tesseract --list-langs
# 預設已安裝英文
#sudo dnf install tesseract-langpack-chi-sim  # 簡體中文,視需求
#sudo dnf install tesseract-langpack-chi-tra  # 繁體中文

# 合併 pdf
dnf install poppler-utils

轉成圖片

gs -dNOPAUSE -dBATCH -sDEVICE=png16m -r300 -sOutputFile=page_%03d.png input.pdf

ocr 中文

tesseract page_001.png output_001 -l chi-tra pdf

ocr 英文

tesseract page_001.png output_001 -l eng pdf

--psdm 參數,目前測試調整後, table 換行還是有問題

--psm 常用模式:

  • 3 → 全頁自動檢測文本

  • 6 → 單塊文本

  • 11 → 密集文字或多欄

tesseract page_001.png output_001 -l eng --psm 3 pdf

多頁

for f in page_*.png; do
  base=$(basename $f .png)
  tesseract "$f" "${base}_ocr" -l eng pdf
done

合併

pdfunite page_*_ocr.pdf final_output.pdf

壓縮

gs -sDEVICE=pdfwrite -dPDFSETTINGS=/ebook -o compressed.pdf final_output.pdf