コグノスケ


2025年10月1日

V4L2でUSBカメラのキャプチャをする方法

目次: Linux

V4L2(Video for Linux 2)は多才で全ての機能は知らないんですが、最低限の動作確認(デバイス一覧、フォーマット一覧、キャプチャ)を紹介します。ツールはvl42-ctlを使います。

v4l2-ctlを使えるようにする
$ sudo apt-get install v4l-utils

Debian系であればv4l-utilsパッケージに入っています。

デバイス、フォーマット一覧

デバイスの一覧を見るにはlist-devicesを、フォーマットの一覧を見るにはlist-formats-extを使います。

デバイスの一覧(-Aもしくは--list-devices)
$ v4l2-ctl --list-devices

USB 2.0 Camera: USB 2.0 Camera (usb-0000:07:00.3-3.1):
        /dev/video0
        /dev/video1
        /dev/media0
フォーマットの一覧
$ v4l2-ctl -d /dev/video0 --list-formats-ext

ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'MJPG' (Motion-JPEG, compressed)
                Size: Discrete 1920x1080
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1280x800
                        Interval: Discrete 0.033s (30.000 fps)
                Size: Discrete 1280x720
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 800x600
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
...
        [1]: 'YUYV' (YUYV 4:2:2)
                Size: Discrete 1920x1080
                        Interval: Discrete 0.200s (5.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1280x800
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1280x720
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 800x600
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
...

この一覧に出てくる組み合わせを次のフォーマット設定で使いますので、どこかにメモしておきます。

フォーマット設定、取得

取得したい画像サイズとフォーマットの組み合わせをデバイスに指定します。例では1920x1080のMotion JPEG(MJPG)を指定していますが、お使いのカメラに合わせて適宜変えてください。

フォーマット設定(-vもしくは--set-fmt-video)と取得(-Vもしくは--get-fmt-video)
$ v4l2-ctl -d /dev/video0 -V

Format Video Capture:
        Width/Height      : 1920/1080
        Pixel Format      : 'YUYV' (YUYV 4:2:2)
        Field             : None
        Bytes per Line    : 3840
        Size Image        : 4147200
        Colorspace        : sRGB
        Transfer Function : Rec. 709
        YCbCr/HSV Encoding: ITU-R 601
        Quantization      : Default (maps to Limited Range)
        Flags             :

$ v4l2-ctl -d /dev/video0 -v width=1920,height=1080,pixelformat=MJPG

$ v4l2-ctl -d /dev/video0 -V --get-fmt-video

Format Video Capture:
        Width/Height      : 1920/1080
        Pixel Format      : 'MJPG' (Motion-JPEG)
        Field             : None
        Bytes per Line    : 0
        Size Image        : 4147789
        Colorspace        : sRGB
        Transfer Function : Default (maps to sRGB)
        YCbCr/HSV Encoding: Default (maps to ITU-R 601)
        Quantization      : Default (maps to Full Range)
        Flags             :

フォーマット設定後は-Vでフォーマット取得して、意図通りに設定されたか確認することをお勧めします。特にフレームサイズは間違って変な値を指定しても(幅1920のつもりで幅192と書いた、など)、エラーにならず近しい設定が選ばれていることがあります。

キャプチャと再生

キャプチャしてファイルに書き出すにはstream-toを使います。stream-countでフレーム数を100フレームにしています(100フレームキャプチャしたら終了)。フレーム数を指定しないと永遠にキャプチャし続けますが、適当なところでCtrl-cで止めればOKです。

キャプチャしてファイルに書き出し
$ v4l2-ctl -d /dev/video0 --stream-mmap=3 --stream-count=100 --stream-to=test.mjpg

<<<<<<<<<<<<<<<<<<<< 19.84 fps, dropped buffers: 1
<<<<<<<<<<<<<<<<<<<< 19.88 fps
<<<<<<<<<<<<<<<<<<<< 19.92 fps
<<<<<<<<<<<<<<<<<<<< 19.94 fps
<<<<<<<<<<<<<<<<<<<< 19.95 fps

再生は何を使っても良いですが、ffplayを使った例をご紹介します。

MJPGとYUYVで5秒間キャプチャ、再生
#### MJPGの場合

$ v4l2-ctl -d /dev/video0 -v width=1920,height=1080,pixelformat=MJPG
$ v4l2-ctl -d /dev/video0 --stream-mmap=3 --stream-count=100 --stream-to=test.mjpg

$ ffplay -framerate 20 test.mjpg


#### YUYVの場合

$ v4l2-ctl -d /dev/video0 -v width=1920,height=1080,pixelformat=YUYV
$ v4l2-ctl -d /dev/video0 --stream-mmap=3 --stream-count=25 --stream-to=test.raw

$ ffplay -f rawvideo -video_size 1920x1080 -pixel_format yuyv422 -framerate 5 test.raw

フレームレートの指定(framerate)はカメラの性能に合わせて適宜変更してください。

編集者:すずき(2025/10/05 15:24)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年10月3日

udevでデバイスの属性を調べる方法

目次: Linux

いつも忘れるudevの使い方メモです。udevadmでデバイス属性を調べる方法です。

デバイス属性を調べる
$ sudo udevadm info --attribute-walk /dev/video0

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device '/devices/pci0000:00/0000:00:08.1/0000:07:00.3/usb3/3-3/3-3.1/3-3.1:1.0/video4linux/video0':
    KERNEL=="video0"
    SUBSYSTEM=="video4linux"
    DRIVER==""
    ATTR{dev_debug}=="0"
    ATTR{index}=="0"
    ATTR{name}=="USB 2.0 Camera: USB 2.0 Camera"
    ATTR{power/async}=="disabled"
    ATTR{power/control}=="auto"
    ATTR{power/runtime_active_kids}=="0"
    ATTR{power/runtime_active_time}=="0"
    ATTR{power/runtime_enabled}=="disabled"
    ATTR{power/runtime_status}=="unsupported"
    ATTR{power/runtime_suspended_time}=="0"
    ATTR{power/runtime_usage}=="0"

  looking at parent device '/devices/pci0000:00/0000:00:08.1/0000:07:00.3/usb3/3-3/3-3.1/3-3.1:1.0':
    KERNELS=="3-3.1:1.0"
    SUBSYSTEMS=="usb"
    DRIVERS=="uvcvideo"
    ATTRS{authorized}=="1"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bInterfaceClass}=="0e"
    ATTRS{bInterfaceNumber}=="00"
    ATTRS{bInterfaceProtocol}=="00"
    ATTRS{bInterfaceSubClass}=="01"
    ATTRS{bNumEndpoints}=="01"
    ATTRS{iad_bFirstInterface}=="00"
    ATTRS{iad_bFunctionClass}=="0e"
    ATTRS{iad_bFunctionProtocol}=="00"
    ATTRS{iad_bFunctionSubClass}=="03"
    ATTRS{iad_bInterfaceCount}=="02"
    ATTRS{interface}=="USB 2.0 Camera"
    ATTRS{power/async}=="enabled"
    ATTRS{supports_autosuspend}=="1"

...

調べた属性をudevのrulesに書くときは、そのままATTRS{AAA}=="00"のように書けば良いです。他の例や詳細については、Arch Linuxのドキュメント(udev - ArchWiki)がとても詳しいです。

編集者:すずき(2025/10/05 01:34)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年10月4日

Linuxのprocファイルシステムの実装 - /proc/pid/ioのopenとread

目次: Linux

以前、LinuxのI/O統計情報を得る/proc/[pid]/ioの実装を調べました。あのときは深追いしませんでしたが、/procには/proc/[pid]/以下のファイルから何が読めるのか、pid_entryの配列で指定する仕組みがありました。

tgid_base_stuff(pid_entryの配列)の定義

// fs/proc/base.c

/*
 * Thread groups
 */
static const struct pid_entry tgid_base_stuff[] = {
	DIR("task",       S_IRUGO|S_IXUGO, proc_task_inode_operations, proc_task_operations),
	DIR("fd",         S_IRUSR|S_IXUSR, proc_fd_inode_operations, proc_fd_operations),

//...

#ifdef CONFIG_TASK_IO_ACCOUNTING
	ONE("io",	S_IRUSR, proc_tgid_io_accounting),    //★★★★これ
#endif

なぜこの配列に要素を足すと/proc/[pid]/以下のファイルが定義できるのでしょう?まずはopen/readに限定して調べてみます。調査対象はLinux-5.15です。

/proc/(tgid)/のエントリ

/proc/(tgid)/以下のファイルを定義しているtgid_base_stuffの中身は、DIR()もしくはONE()マクロを使って定義します。ONE()マクロを見てみます。

ONE()マクロの実装

// fs/proc/base.c

#define ONE(NAME, MODE, show)				\
	NOD(NAME, (S_IFREG|(MODE)),			\
		NULL, &proc_single_file_operations,	\
		{ .proc_show = show } )

#define NOD(NAME, MODE, IOP, FOP, OP) {			\
	.name = (NAME),					\
	.len  = sizeof(NAME) - 1,			\
	.mode = MODE,					\
	.iop  = IOP,					\
	.fop  = FOP,					\
	.op   = OP,					\
}

ネストしたマクロはわかりにくいです。何か1つ(ここではio)を展開すると多少はわかりやすいと思います。

ioの定義を展開

// fs/proc/base.c

// ★★★★NODを展開

#define ONE(NAME, MODE, show) {				\
	.name = (NAME),					\
	.len  = sizeof(NAME) - 1,			\
	.mode = (S_IFREG|(MODE)),			\
	.iop  = NULL,					\
	.fop  = &proc_single_file_operations,		\
	.op   = { .proc_show = show },			\
}

static const struct file_operations proc_single_file_operations = {
	.open		= proc_single_open,    //★★open時に呼ばれる関数
	.read		= seq_read,            //★★read時に呼ばれる関数
	.llseek		= seq_lseek,
	.release	= single_release,
};


// ★★★★ioの定義を当てはめる

	ONE("io",	S_IRUSR, proc_tgid_io_accounting),

#define ONE(NAME, MODE, show) {				\
	.name = ("io"),					\
	.len  = sizeof("io") - 1,			\
	.mode = (S_IFREG|(S_IRUSR)),			\
	.iop  = NULL,					\
	.fop  = &proc_single_file_operations,		\
	.op   = { .proc_show = proc_tgid_io_accounting },	\
}

ファイルopen時にproc_single_open()、read時にseq_read()が呼ばれるように、あとはop.proc_showにproc_tgid_io_accounting()を指定しています。

open時の動作

どうやってproc_single_open()が呼ばれるか?は一旦無視して、open時に呼ばれるものとして調べます。

open周りの実装

// linux/fs/proc/base.c

static int proc_single_open(struct inode *inode, struct file *filp)
{
	return single_open(filp, proc_single_show, inode);
}


// linux/fs/seq_file.c

int single_open(struct file *file, int (*show)(struct seq_file *, void *),
		void *data)
{
	struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL_ACCOUNT);
	int res = -ENOMEM;

	if (op) {
		op->start = single_start;
		op->next = single_next;
		op->stop = single_stop;
		op->show = show;          //★★show = proc_single_show()
		res = seq_open(file, op);
		if (!res)
			((struct seq_file *)file->private_data)->private = data;
		else
			kfree(op);
	}
	return res;
}
EXPORT_SYMBOL(single_open);

int seq_open(struct file *file, const struct seq_operations *op)
{
	struct seq_file *p;

	WARN_ON(file->private_data);

	p = kmem_cache_zalloc(seq_file_cache, GFP_KERNEL);
	if (!p)
		return -ENOMEM;

	file->private_data = p;

	mutex_init(&p->lock);
	p->op = op;           //★★op = struct seq_operations, op->show = proc_single_show()

	// No refcounting: the lifetime of 'p' is constrained
	// to the lifetime of the file.
	p->file = file;

	/*
	 * seq_files support lseek() and pread().  They do not implement
	 * write() at all, but we clear FMODE_PWRITE here for historical
	 * reasons.
	 *
	 * If a client of seq_files a) implements file.write() and b) wishes to
	 * support pwrite() then that client will need to implement its own
	 * file.open() which calls seq_open() and then sets FMODE_PWRITE.
	 */
	file->f_mode &= ~FMODE_PWRITE;
	return 0;
}
EXPORT_SYMBOL(seq_open);

ここで出てくるseq_で始まる関数群とsingle_で始まる関数群は、seq_fileと呼ばれる仕組みThe seq_file interface - The Linux Kernel Documentation)です。変な名前だなあ?と思うのはごもっともですけど正式名称なのです……。

仮想ファイルシステム上にファイルを定義するときは、openやreadを実装しなければなりません。特にread()の実装は曲者で読み出し位置の管理が面倒です。しかしseq_fileを使うと内部にバッファを勝手に確保し、読み出し位置を管理してくれます。もっとも簡単な実装だと、seq_printf()で内部バッファに書き出すだけで済みます。/procのような何か情報を返すファイルシステムを実装するときに便利です。

read時の動作

次にread時の動作を見ます。open同様にどうやってseq_read()が呼ばれるか?は一旦無視して、read時に呼ばれるものとして調べます。

read周りの実装

// linux/fs/seq_file.c

ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	struct iovec iov = { .iov_base = buf, .iov_len = size};
	struct kiocb kiocb;
	struct iov_iter iter;
	ssize_t ret;

	init_sync_kiocb(&kiocb, file);
	iov_iter_init(&iter, READ, &iov, 1, size);

	kiocb.ki_pos = *ppos;
	ret = seq_read_iter(&kiocb, &iter);    //★★★★
	*ppos = kiocb.ki_pos;
	return ret;
}
EXPORT_SYMBOL(seq_read);

ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
	struct seq_file *m = iocb->ki_filp->private_data;
	size_t copied = 0;
	size_t n;
	void *p;
	int err = 0;

//...略...

	// get a non-empty record in the buffer
	m->from = 0;
	p = m->op->start(m, &m->index);
	while (1) {
		err = PTR_ERR(p);
		if (!p || IS_ERR(p))	// EOF or an error
			break;
		err = m->op->show(m, p);    //★★ここでshow = proc_single_show()を呼ぶ
		if (err < 0)		// hard error
			break;
		if (unlikely(err))	// ->show() says "skip it"
			m->count = 0;
		if (unlikely(!m->count)) { // empty record
			p = m->op->next(m, p, &m->index);
			continue;
		}
		if (!seq_has_overflowed(m)) // got it
			goto Fill;
		// need a bigger buffer
		m->op->stop(m, p);
		kvfree(m->buf);
		m->count = 0;
		m->buf = seq_buf_alloc(m->size <<= 1);
		if (!m->buf)
			goto Enomem;
		p = m->op->start(m, &m->index);
	}
	// EOF or an error
	m->op->stop(m, p);
	m->count = 0;
	goto Done;

//...略...


// linux/fs/proc/base.c

static int proc_single_show(struct seq_file *m, void *v)
{
	struct inode *inode = m->private;
	struct pid_namespace *ns = proc_pid_ns(inode->i_sb);
	struct pid *pid = proc_pid(inode);
	struct task_struct *task;
	int ret;

	task = get_pid_task(pid, PIDTYPE_PID);
	if (!task)
		return -ESRCH;

	ret = PROC_I(inode)->op.proc_show(m, ns, pid, task);  //★★ここでproc_show = proc_tgid_io_accounting()を呼ぶ

	put_task_struct(task);
	return ret;
}

やっとONE()に渡したproc_tgid_io_accountingが呼ばれる場所がわかりました。長かった……。

編集者:すずき(2025/10/06 02:52)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年10月6日

makeのデフォルトルールのリンクはLDを使わない

目次: Linux

Makefileの達人には常識かもしれませんが、makeがリンクを行うときのデフォルトルール(LINK.o)が使うコマンドはLDではなくCCです。私はリンク時にldが直接呼ばれるのは見たことがないにも関わらず、ずっとLDが使われると思いこんでいました。

話は以上で終わり……だとつまらないので、実験もしましょう。Makefileとコンパイラの代わりにスクリプトを呼びます。makeはGNU Make 4.4.1を使いました。

実験用のMakefile

CC  = ./cc-dummy
CXX = ./cxx-dummy
CPP = ./cpp-dummy
LD  = ./ld-dummy
CFLAGS   = cflags-dummy
CXXFLAGS = cxxflags-dummy
CPPFLAGS = cppflags-dummy
LDFLAGS  = ldflags-dummy
LOADLIBES = loadlibes-dummy
LDLIBS    = ldlibs-dummy

all: target-c target-cxx

target-c: target-c.o
target-cxx: target-cxx.o
コンパイラの代わりに呼び出すスクリプト
$ cat ./cc-dummy

#!/bin/sh

touch target-c.o


$ cat ./cxx-dummy

#!/bin/sh

touch target-cxx.o

コマンドが見つからないエラーを防ぐため、CCやCXXに指定した名前のスクリプトも作ります。スクリプトではコンパイラがオブジェクトファイル(*.o)を作成する動きのみを模しておけば十分です。

実験

オブジェクトファイルの生成でデフォルトのコンパイルのルール、バイナリファイルの生成でリンクのルールが呼ばれます。このときCCやCXXが使われるか?LDが使われるか?がわかるはずです。実行します。

実行結果
$ make

#### C言語プログラムのコンパイルとリンク、どちらもCCを使う

./cc-dummy cflags-dummy cppflags-dummy  -c -o target-c.o target-c.c
./cc-dummy ldflags-dummy  target-c.o loadlibes-dummy ldlibs-dummy -o target-c


#### C++言語プログラムのコンパイルとリンク、コンパイルはCXX、リンクはCCを使う

./cxx-dummy cxxflags-dummy cppflags-dummy  -c -o target-cxx.o target-cxx.cc
./cc-dummy ldflags-dummy  target-cxx.o loadlibes-dummy ldlibs-dummy -o target-cxx

C言語でもC++言語でもリンクは一緒で、CCを呼び出しました。LDは使われません。

デフォルトルールを見る

オプション-pでmakeのデフォルトルールを見ることができます。"%: %.o"はオブジェクトファイルからバイナリを生成するときに当てはまるデフォルトルールです。コロンの左が生成ファイル、右が依存ファイルです。%はワイルドカードです。

UNIX系の慣例で、実行ファイル名は拡張子なし(例えばhoge)、オブジェクトファイル名は*.o(例えばhoge.o)にします。そのペアにうまく当てはまるルールになっています。

GNU Make 4.4.1のデフォルトルール
# デフォルト
LINK.o = $(CC) $(LDFLAGS) $(TARGET_ARCH)

#...(略)...

%: %.o
#  実行するレシピ (ビルトイン):
	$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

当たり前ですが、実験で見た結果と一緒です。

ソースコードも見る

GNU Makeのソースコードも眺めてみました。おそらくdefault.cがデフォルトルールの定義だと思います。デフォルトルールと一緒です。そりゃそうですね。

makeのソースコードのデフォルトルール

// make/src/default.c

static const char *default_variables[] =
  {
#ifdef VMS

//...

#else /* !VMS */

//...

    "LINK.o", "$(CC) $(LDFLAGS) $(TARGET_ARCH)",
    "COMPILE.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c",
    "LINK.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
    "COMPILE.m", "$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c",
    "LINK.m", "$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
    "COMPILE.cc", "$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c",

//...

ソースコードを見て初めて知ったことが、VAX用のVMSというOSだとCCではなくLDを使うことです。しかし今の時代にVMS使っている人はいないでしょう……たぶん。

編集者:すずき(2025/10/16 03:20)

コメント一覧

  • hdkさん(2025/10/10 08:27)
    ただのHello, worldでも試してみるとわかりますが、標準Cライブラリやスタートアップをリンクするためですよね。ldコマンドを直接使うと全部指定する必要がありますので... VMSはどうなっていたのかわかりませんが。
  • すずきさん(2025/10/10 13:14)
    ですね。ccはもはやコンパイラというよりコンパイル関係のプログラムのランチャーって感じですね。gccはcc1やcc1plusが狭義のコンパイラだと思います。
    VMSはどうなっているのか私もわかりません……。
open/close この記事にコメントする



2025年10月11日

PipeWireをPulseAudioサーバーの代わりにする

目次: ALSA

最近はPulseAudioがPipeWireに置き換わっているそうです。今はPulseAudioをリモートオーディオ再生機として使っているので、PipeWireで同じことをする方法を紹介します。

やりたいことはLinuxデスクトップからARM SBC上のPulseAudioへオーディオを飛ばすこと(2022年5月27日の日記参照)です。PulseAudioの場合はmodule-native-protocol-tcpを有効にして、TCPでオーディオを送るようにしました。

PipeWireのSinkデバイス設定

まずSinkデバイスの設定を行います。大抵HDMI、ヘッドフォン端子、あるいは別の出力端子があるはずですので、どこから音を鳴らすかを決めます。オーディオデバイスが1つしかない場合は設定不要かもしれませんが、一応チェックしておくと良いと思います。

設定にはWirePlumberの設定ツールwpctlを使います。wpctl statusで現在の様子を見ることができます。

wpctl statusの結果
$ wpctl status

Audio
 ├─ Devices:
 │      48. Built-in Audio                      [alsa]
 │      49. Built-in Audio                      [alsa]
 │      50. Built-in Audio                      [alsa]
 │      51. Built-in Audio                      [alsa]
 │
 ├─ Sinks:
 │      35. Built-in Audio Stereo               [vol: 1.00]
 │      46. Built-in Audio Stereo               [vol: 1.00]
 │  *   56. Built-in Audio Stereo               [vol: 1.00]
 │
 ├─ Sources:
 │      44. Built-in Audio Stereo               [vol: 1.00]
 │  *   57. Built-in Audio Stereo               [vol: 1.00]
 │
 ├─ Filters:
 │
 └─ Streams:

SourcesとSinksのデフォルトデバイスを変更して、音声の入出力先を変更するにはset-defaultを使用します。wpctlはDevice、Source、Sinkの全てがユニークなIDを持っているので、SourceだとかSinkだとか指定する必要はなくて、IDのみ指定します。入出力先を変更後、もう一度statusを見るとSettingsの内容が変わるはずです。

例えば上記のstatusなら、set-defaultに指定できるのはSink系(35, 46, 56)かSource系(44, 57)のIDのうちどれか1つです。もし35か46か56を指定した場合は、IDからSinkデバイスだとわかるのでデフォルトSinkデバイスが変更されます。44か57を指定した場合はデフォルトSourceデバイスが変更されます。直感的じゃない、辛い……。

set-defaultと結果
$ wpctl set-default 56
$ wpctl set-default 57


$ wpctl status

...

Settings
 └─ Default Configured Devices:
         0. Audio/Sink    alsa_output.platform-es8316-sound.stereo-fallback
         1. Audio/Source  alsa_input.platform-es8316-sound.stereo-fallback

余談でWirePlumberが出てくる理由を紹介します。PipeWireは音声の入出力経路を管理しないため、PipeWire以外のソフトウェアをインストールして音声の経路管理しなければならないからです。WirePlumberもPipeWireもこだわりというか思想の強さがにじみ出ていて、私は仲良くなれなさそうです……。

Sinkの確認

動作確認は音を鳴らしてみて鳴れば方法は問いませんが、一例としてALSAを使った方法を紹介します。PipeWireとALSAやV4L2を繋ぐためのモジュールとalsa-utilsをインストールします。

ALSAのテストツールで音が鳴るかテスト
$ sudo apt-get install pipewire-alsa pipewire-audio pipewire-v4l2 alsa-utils

$ speaker-test -c 2 -r 48000 -D pipewire

speaker-test 1.2.14

Playback device is pipewire
Stream parameters are 48000Hz, S16_LE, 2 channels
Using 16 octaves of pink noise
Rate set to 48000Hz (requested 48000Hz)
Buffer size range from 64 to 1048576
Period size range from 32 to 524288
Periods = 4
was set period_size = 12000
was set buffer_size = 48000
 0 - Front Left

左右のスピーカーから交互にノイズ音(サーー)がなれば成功です。鳴らない場合は設定とボリューム(wpctlで確認できます)、もしくはALSAデバイス側のボリューム、ミュート設定の問題も有り得るので、alsamixerで確認してみてください。

PulseAudioモジュールの設定

次はPulseAudioモジュールの設定変更です。PipeWireと一緒にインストールされるはずですが、もし下記の設定ファイルが存在しなければpipewire-pulseをインストールしてください。

PipeWireのPulseAudioモジュール設定
# /usr/share/pipewire/pipewire-pulse.conf

...

pulse.properties = {
    # the addresses this server listens on
    server.address = [
        "unix:native"
        #"unix:/tmp/something"              # absolute paths may be used

        #★★この行を有効にする
        "tcp:4713"                          # IPv4 and IPv6 on all addresses

        #"tcp:[::]:9999"                    # IPv6 on all addresses
        #"tcp:127.0.0.1:8888"               # IPv4 on a single address
        #
        #{ address = "tcp:4713"             # address
        #  max-clients = 64                 # maximum number of clients
        #  listen-backlog = 32              # backlog in the server listen queue
        #  client.access = "restricted"     # permissions for clients
        #}
    ] 

...
PipeWireのPulseAudioモジュール再起動
$ systemctl --user restart pipewire-pulse

設定を変更したら再起動をお忘れなく。

動作確認
#### クライアント側で操作

$ PULSE_SERVER=192.168.1.x speaker-test -c 2 -r 48000 -D pulse

リモートオーディオ再生のクライアント側でPULSE_SERVERにサーバーのIPアドレスを指定して音声を再生します。サーバー側から音が鳴れば成功です。

編集者:すずき(2025/10/14 05:13)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年10月12日

ROCK 5BのDebianをアップデート

目次: ARM

Debian StableがTrixieに変わったことをすっかり忘れており、ROCK 5Bをapt-get dist-upgradeしたところ環境が壊れました。ARM SBC(SBC = Single Board Computer)のベンダーがDebianを提供してくれるのは大変ありがたいですが、大抵変なパッケージか変な改造が入っていて年単位で運用すると困ったことが起きがちです。

SBCはアップデートしない派もいるでしょうけど、ネットワークに繋いでいるし可能な限りアップデートを適用したいです。取れる選択肢は下記の二つでしょうか。

Releaseを固定(例えばBookworm)してセキュリティアップデートのみ適用
アップデートの悩みはないもののサポート切れになったときにどうしようもなくなります。
Stableを指定
今回の私のようにStableがバージョンアップしたときに高確率で環境が壊れます。

正直、良し悪しは互角ですね……。

環境を修復

Debianは多少壊れても元に戻せますが、今回は盛大に壊れましてapt --fix-broken installはエラーになります。致命的なのはsystemdとlibsystemd0のバージョン不一致でsystemdが消えたことで、bookwormにも戻してもsystemdがインストールできなくなりました。もしこの状態で再起動したら二度と起動しないでしょう。

systemdがインストールエラーになる
Preparing to unpack .../systemd_257.8-1~deb13u2_arm64.deb ...
Unpacking systemd (257.8-1~deb13u2) over (252.38-1~deb12u1) ...
dpkg: error processing archive /var/cache/apt/archives/systemd_257.8-1~deb13u2_arm64.deb (--unpack):
 trying to overwrite '/usr/lib/systemd/system/serial-getty@.service', which is also in package rockchip-overlay 4.1
dpkg: considering removing systemd in favour of udev ...
dpkg: systemd is not properly installed; ignoring any dependencies on it
dpkg: yes, will remove systemd in favour of udev
Preparing to unpack .../udev_257.8-1~deb13u2_arm64.deb ...
Unpacking udev (257.8-1~deb13u2) over (252.38-1~deb12u1) ...
Removing systemd (252.38-1~deb12u1), to allow configuration of udev (257.8-1~deb13u2) ...
Preparing to unpack .../libudev1_257.8-1~deb13u2_arm64.deb ...
Unpacking libudev1:arm64 (257.8-1~deb13u2) over (252.38-1~deb12u1) ...
Errors were encountered while processing:
 /var/cache/apt/archives/systemd_257.8-1~deb13u2_arm64.deb
E: Sub-process /usr/bin/dpkg returned an error code (1)

エラーメッセージを見るとrockchip-overlayなるパッケージが邪魔しているようです。試行錯誤の結果、

  • trixie → bookwormに戻してapt-get update
  • rockchip-なんちゃらとradxa-なんちゃらパッケージをremove
  • bookworm → trixieに戻してapt-get updateとapt-get dist-upgrade
  • systemdその他の吹き飛んでしまったパッケージをinstall

としたところ、元気に動き始めました。代償として画面が出なくなりましたが、今のところ画面を使っていないので問題ありません。面倒なSDカードイメージの書き直しを避けることができてラッキーでした。

編集者:すずき(2025/10/16 03:14)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年10月13日

PipeWireをPulseAudioサーバーの代わりにする、その2 - 音切れ多発

目次: ALSA

先日(2025年10月11日の日記参照)の設定により、x86_64なPCからROCK 5Bにて動作しているPipeWireに対し、リモートオーディオ再生ができるようになりました。無事動いたまでは良いのですが、PipeWireにしてから音切れがひどくなり困ってしまいました。

クライアント側はx86_64、mplayer v0.40.0(今はmpvですね)+ libpipewire 1.4.8で、サーバー側はaarch64、libpipewire 1.4.2です。

クライアント側の設定

一般的に音が切れるのはクライアントかサーバーかどちらかの音声用のバッファが足りない可能性が高いです。クライアント側の設定を確認するためにmpvに-vオプションを付けて実行します。

mplayer -vの出力結果
$ PULSE_SERVER=192.168.1.x mplayer --audio-device=pulse -v a.mp4

...(略)...

[ao] Trying audio driver 'pulse'
[ao/pulse] requested format: 48000 Hz, stereo channels, floatp
[ao/pulse] Library version: 17.0.0
[ao/pulse] Proto: 35
[ao/pulse] Server proto: 35
[ao/pulse] Channel layouts:
[ao/pulse]  - #fl
[ao/pulse]  - #fr
[ao/pulse]  - #fc
[ao/pulse]  - #lfe
[ao/pulse]  - #bl
[ao/pulse]  - #br
[ao/pulse]  - #flc
[ao/pulse]  - #frc
[ao/pulse]  - #bc
[ao/pulse]  - #sl
[ao/pulse]  - #sr
[ao/pulse]  - #tc
[ao/pulse]  - #tfl
[ao/pulse]  - #tfc
[ao/pulse]  - #tfr
[ao/pulse]  - #tbl
[ao/pulse]  - #tbc
[ao/pulse]  - #tbr
[ao/pulse] result: stereo
[ao/pulse] device buffer: 4800 samples.        ★デバイスバッファが4800/48000 = 0.1秒
[ao/pulse] using soft-buffer of 9600 samples.  ★バッファが9600/48000 = 0.2秒

...

ログからバッファサイズをチェックしたところデフォルト100ms + 200msのようです。変更にはpulse-bufferオプションを使用します。値の単位はミリ秒です。

ログをみてバッファサイズ設定が反映されているか確認
$ PULSE_SERVER=192.168.1.x mplayer --audio-device=pulse --pulse-buffer=1000 -v a.mp4

...

[ao/pulse] result: stereo
[ao/pulse] device buffer: 48000 samples.
[ao/pulse] using soft-buffer of 48000 samples.

...

ひとまず1000msくらいに変更してしばらく様子を見ようと思います。

サーバー側の設定

今回は特に変えていませんが、サーバー側の設定は/usr/share/pipewire/pipewire-pulse.confにあります。設定を変更したらpipewire-pulseを再起動で反映されるはずです。ユーザー権限で動いているのでsudoは不要です。

pipewire-pulseの再起動
$ systemctl --user restart pipewire-pulse

このファイルを書き換えると次のアップデートで上書きされてしまうらしいですが、他の場所にある設定ファイルが見当たらないんですよね。どこにあるんだろう。。。

編集者:すずき(2025/10/16 03:19)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年10月15日

PipeWireの音切れ問題 - サーバー側の設定確認と反映

目次: ALSA

PipeWireに変えてから音切れがなくなりません。PulseAudioのときも音切れはしてましたが、PipeWireになってから頻度が段違いに多いです。なぜー?今回はサーバー側PipeWireのレイテンシ調整に備えて、設定反映方法と設定確認方法を紹介します。

PipeWireの設定反映、デバッグログを出す方法

設定を反映するにはpipewire-pulse.socket, pipewire-pulse.service, pipewire.socket, pipewire.service, wireplumber.serviceのうち設定を変更したサービスを再起動すれば良いはずです。良くわからないときは全部再起動しましょう。

PipeWireたちの再起動
$ systemctl --user daemon-reload
$ systemctl --user restart pipewire.service


#### もし何かおかしかったら下記もやってみると吉

$ systemctl --user restart pipewire.socket
$ systemctl --user restart wireplumber.service
$ systemctl --user restart pipewire-pulse.service
$ systemctl --user restart pipewire-pulse.socket

デバッグ出力を見たければ、上記の5つのサービスを全部stopしてからPipeWireと仲間たちを直接起動すると良いでしょう。

PipeWireサービスの停止、直接起動
#### サービスを全部止める

$ systemctl --user stop pipewire.socket
$ systemctl --user stop pipewire.service
$ systemctl --user stop wireplumber.service
$ systemctl --user stop pipewire-pulse.socket
$ systemctl --user stop pipewire-pulse.service

#### 直接起動する

$ /usr/bin/pipewire -v
$ /usr/bin/wireplumber
$ /usr/bin/pipewire-pulse -v

ちなみにデバッグログを出すとわかりますが、めちゃくちゃ大量に出ます……私には意味がわかりません。

PipeWireの設定確認

PipeWireは設定確認にはリモート音声再生している状態でpw-topコマンドを起動して確認すると便利です。

PipeWireの状態(変更前)
S   ID  QUANT   RATE    WAIT    BUSY   W/Q   B/Q  ERR FORMAT           NAME
S   30      0      0    ---     ---   ---   ---     0                  Dummy-Driver
S   31      0      0    ---     ---   ---   ---     0                  Freewheel-Driver
S   47      0      0    ---     ---   ---   ---     0                  Midi-Bridge
R   51   2048  48000 594.1us 320.5us  0.01  0.01    1   S24_32 2 48000 alsa_output.platform-es8316-s
R   56   8192  48000 245.6us 253.7us  0.01  0.01    1    S32LE 2 48000  + mpv
S   52      0      0    ---     ---   ---   ---     0                  alsa_input.platform-es8316-so
S   53      0      0    ---     ---   ---   ---     0                  alsa_output.platform-hdmi0-so
S   54      0      0    ---     ---   ---   ---     0                  alsa_output.platform-hdmi1-so
S   55      0      0    ---     ---   ---   ---     0                  alsa_input.platform-hdmiin-so

RATEがサンプリング周波数で、QUANT(※)がバッファサイズのようです。ALSAデバイス側は2048サンプル、PulseAudioモジュール(mpvの話し相手)は8192サンプルです。mpvはpulse-buffer=500を指定したので、500msつまり24000サンプルになるはずですが、なぜか8192です。

(※)QUANT = quantumのこと。バッファサイズを決めるための数値らしく、単位はサンプルだと思われます。PipeWireのドキュメントを見てもquantumについて説明している部分が見当たらなくて困りました。

編集者:すずき(2025/10/19 16:54)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年10月17日

Linux版Firefoxアドレスバーの黒線

目次: ブラウザー/メーラー

LinuxデスクトップマシンのFirefox(140.3.1esr 64bit)をアップデートしたところ、アドレスバーの上下に黒い線が表示されるようになりました。何も害はないですが、目立つなあ。Windows版でこのような線は見たことがありません。


Firefoxのアドレスバー

昨今のブラウザってシステムのデザインはガン無視で、オレオレGUIコンポーネントを描画してるはずで。それでもデザイン統一できないのはバグなのか、Linux版に何か特有の事情があるのか……?

編集者:すずき(2025/10/19 14:53)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年10月18日

PipeWireの音切れ問題 - サーバー側PipeWireの設定

目次: ALSA

PipeWireに変えてから音切れがなくなりません。PulseAudioのときも音切れはしてましたが、PipeWireになってから頻度が段違いに多いです。なぜー?結論から書いておくと、音切れは多少はマシになった気がしますが、完全には解決できていません。今回はサーバー側PipeWireのレイテンシ調整をしました。

PipeWireの設定ファイル

PipeWireの設定ファイルはいくつかの場所を認識するみたいです。straceで追ってみると、

  • ~/.config/pipewire/pipewire.conf
  • /etc/pipewire/pipewire.conf
  • /usr/share/pipewire/pipewire.conf

この3つが認識されるみたいで、私の環境では/usr/share/pipewire/pipewire.confのみ存在していたので、/usr/share以下にあるファイルを変更します。

PipeWireのレイテンシ設定

設定変更前の状態を再掲しておきます。

PipeWireの状態(変更前)
S   ID  QUANT   RATE    WAIT    BUSY   W/Q   B/Q  ERR FORMAT           NAME
S   30      0      0    ---     ---   ---   ---     0                  Dummy-Driver
S   31      0      0    ---     ---   ---   ---     0                  Freewheel-Driver
S   47      0      0    ---     ---   ---   ---     0                  Midi-Bridge
R   51   2048  48000 594.1us 320.5us  0.01  0.01    1   S24_32 2 48000 alsa_output.platform-es8316-s
R   56   8192  48000 245.6us 253.7us  0.01  0.01    1    S32LE 2 48000  + mpv
S   52      0      0    ---     ---   ---   ---     0                  alsa_input.platform-es8316-so
S   53      0      0    ---     ---   ---   ---     0                  alsa_output.platform-hdmi0-so
S   54      0      0    ---     ---   ---   ---     0                  alsa_output.platform-hdmi1-so
S   55      0      0    ---     ---   ---   ---     0                  alsa_input.platform-hdmiin-so

RATEがサンプリング周波数で、QUANT(※)がバッファサイズのようです。ALSAデバイス側は2048サンプル、PulseAudioモジュール(mpvの話し相手)は8192サンプルです。mpvはpulse-buffer=500を指定したので、500msつまり24000サンプルになるはずですが、なぜか8192です。

ローカル再生なら2桁msのバッファサイズ量で問題ないし、むしろレイテンシがデカすぎなくらいですが、リモート再生だと音がブチブチ切れまくって聞くに堪えません。仕方ないのでバッファを増量する設定を追加します。context.propertiesにある「default.clock.*」系の設定が効くっぽいです。

pipewire.confの変更点

context.properties = {

#...

    default.clock.rate          = 48000
    default.clock.allowed-rates = [ 48000 ]
    default.clock.quantum       = 64
    default.clock.min-quantum   = 32
    default.clock.max-quantum   = 8192
    default.clock.quantum-limit = 48000

こんな感じにしてみました。大事なのはmax-quantum(デフォルト値2048)とquantum-limit(デフォルト値8192)でした。max-quantumはALSAデバイス側に使われるようで、quantum-limitはmpvから指定された値の上限になるみたいです。mpvから長いレイテンシを指定しても、quantum-limitまで切り詰められ意味がなかったのです。

PipeWireの状態(pipewire.conf変更後)
S   ID  QUANT   RATE    WAIT    BUSY   W/Q   B/Q  ERR FORMAT           NAME
S   30      0      0    ---     ---   ---   ---     0                  Dummy-Driver
S   31      0      0    ---     ---   ---   ---     0                  Freewheel-Driver
S   47      0      0    ---     ---   ---   ---     0                  Midi-Bridge
R   51   8192  48000 508.0us   1.2ms  0.00  0.01  160   S24_32 2 48000 alsa_output.platform-es8316-s
R   54  22080  48000 233.0us 181.7us  0.00  0.00    0    F32LE 2 48000  + mpv
S   52      0      0    ---     ---   ---   ---     0                  alsa_input.platform-es8316-so
S   53      0      0    ---     ---   ---   ---     0                  alsa_output.platform-hdmi0-so
S   55      0      0    ---     ---   ---   ---     0                  alsa_output.platform-hdmi1-so
S   56      0      0    ---     ---   ---   ---     0                  alsa_input.platform-hdmiin-so

設定変更後、ALSAデバイス側は8192サンプル、PulseAudioモジュール(mpvの話し相手)は22080サンプルになりました。mpvはpulse-buffer=500を指定しているので、24000になってほしいんですけど。なぜこんな微妙な値になるの……??

編集者:すずき(2025/10/19 16:52)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年10月19日

PipeWireの音切れ問題 - サーバー側pipewire-pulseの設定

目次: ALSA

未だにPipeWireの音切れがなくなりません。結論から書いておくと音切れは解決できませんでした。もう飽きました。今回はサーバー側PipeWire PulseAudioモジュールのレイテンシ調整をしました。

PipeWire PulseAudioモジュールの設定

以前の日記(2025年10月11日の日記参照)で紹介したとおり、pipewireそのものにはリモート音声再生する機能はなくて、PulseAudioのプロトコルを受け付けるpipewire-pulseモジュールを使います。

モジュール(pipewire-pulse)の動作をstraceで追うと、設定ファイルは下記の3つが有効みたいです。

  • ~/.config/pipewire/pipewire-pulse.conf
  • /etc/pipewire/pipewire-pulse.conf
  • /usr/share/pipewire/pipewire-pulse.conf

このうち/usr/share/pipewire/pipewire-pulse.confしか存在していなかったので、/usr/shareの設定ファイルを使います。

pipewire-pulse.conf

pulse.properties = {

#...

    pulse.min.req          = 64/48000
    pulse.default.req      = 128/48000
    pulse.min.frag         = 128/48000
    pulse.default.frag     = 96000/48000
    pulse.default.tlength  = 96000/48000
    pulse.min.quantum      = 64/48000

#...

}

とりあえずこんな感じに設定してみました。PipeWireの設定と何が違うんでしょうねえ。

PipeWireの状態(pipewire.conf変更後)

S   ID  QUANT   RATE    WAIT    BUSY   W/Q   B/Q  ERR FORMAT           NAME
S   30      0      0    ---     ---   ---   ---     0                  Dummy-Driver
S   31      0      0    ---     ---   ---   ---     0                  Freewheel-Driver
S   47      0      0    ---     ---   ---   ---     0                  Midi-Bridge
R   51   8192  48000   1.2ms   1.2ms  0.01  0.01    0   S24_32 2 48000 alsa_output.platform-es8316-s
R   74  23744  48000 252.6us 692.1us  0.00  0.00    0    F32LE 2 48000  + mpv
S   52      0      0    ---     ---   ---   ---     0                  alsa_input.platform-es8316-so
S   53      0      0    ---     ---   ---   ---     0                  alsa_output.platform-hdmi0-so
S   55      0      0    ---     ---   ---   ---     0                  alsa_output.platform-hdmi1-so
S   56      0      0    ---     ---   ---   ---     0                  alsa_input.platform-hdmiin-so

PulseAudioモジュール側のバッファサイズが24000に近づきましたが、微妙に値が違います。まだ何か設定が要るんです??

しかし困ったことに、PipeWireのドキュメントは本当に意味がわからないです。読んでも「そんなこと聞いてないんだけど?」状態になります。設定項目のブロック構造もわからんし、何の設定項目があって、どの範囲の値が指定できて、どの値で何が起きるのか、全然分かりません。もうイヤ。

飽きた!

これ以上PipeWireに関してやる気が起きません……。PipeWireで何かトラブったら、PipeWireを即削除してPulseAudioを入れ直しをオススメします。

編集者:すずき(2025/10/19 16:47)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 2025年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報