Communitygithub.com

shiguredo/shiguredo-video-device

時雨堂のクロスプラットフォームカメラキャプチャライブラリ shiguredo_video_device の機能・API リファレンス。macOS (AVFoundation) / Linux (V4L2 / PipeWire) / Windows (Media Foundation) でのデバイス列挙とフレームキャプチャ、feature によるバックエンド選択、ピクセルフォーマットとフレームのメモリレイアウト、フレームコールバックの制約、バックエンドごとの挙動差に関する質問時に使用。

shiguredo-video-device とは?

`shiguredo_video_device` は、macOS、Linux、Windows 上でカメラデバイスの列挙とフレームキャプチャを、プラットフォーム共通の Rust API で提供するライブラリです。Cargo features によりバックエンド(AVFoundation、V4L2、PipeWire、Media Foundation)を選択でき、各プラットフォームで一貫した API を利用できます。フレームはコールバック駆動で取得し、NV12、YUY2、I420、MJPEG などのピクセルフォーマットに対応。macOS では CVPixelBuffer への参照をコピーなしで取得可能です。キャプチャ設定は解像度、フレームレート、ピクセルフォーマットを指定でき、実際の値はフレームごとに確認します。コールバック内での重い処理や `stop()` 呼び出しは禁止されており、チャネル経由でのフレーム受け渡しが推奨されます。エンコーダーなどに渡す際は、stride を除去した連続バッファへの変換コードも提供されています。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/shiguredo/video-device-rs/tree/HEAD/skills/shiguredo-video-device

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

shiguredo_video_device

macOS / Linux / Windows のカメラ列挙とフレームキャプチャを、プラットフォーム共通の Rust API で提供するライブラリ。

特徴

  • クロスプラットフォーム: macOS (AVFoundation)、Linux (V4L2 / PipeWire)、Windows (Media Foundation) を同じ API で扱う
  • feature によるバックエンド選択: 有効化するバックエンドと既定バックエンドを feature で選ぶ
  • コールバック駆動: キャプチャ中はバックエンドのスレッド上でフレームごとにコールバックが呼ばれる
  • ピクセルフォーマット: NV12 / YUY2 / I420、Linux (V4L2) では MJPEG パススルー (mjpeg feature)
  • 少ない依存: Rust の依存クレートは Windows (mf feature) の windows クレートのみ
  • CVPixelBuffer 参照 (macOS): フレームに CVPixelBuffer への参照 (PixelBuffer) が付き、コピーせずに VideoToolbox などへ渡せる

バージョン情報

  • crate 名: shiguredo_video_device
  • バージョン: 2026.4.0
  • Rust Edition: 2024
  • 最小 Rust バージョン: 1.95
  • 最小 macOS バージョン: 14.0 (AVCaptureDeviceTypeExternal を参照するため、macOS 13 以前では動作しない)
  • ライセンス: Apache-2.0
  • リポジトリ: https://github.com/shiguredo/video-device-rs

Error / PixelFormat に #[non_exhaustive] は付いていない。match で全バリアントを列挙できる。

導入

[dependencies]
shiguredo_video_device = "2026.4"

デフォルトでは macOS は AVFoundation、Linux は V4L2、Windows は Media Foundation を既定バックエンドにする。 ビルド対象 OS 以外の feature は無視されるため、OS ごとに依存を分ける必要はない。

feature

feature対象 OS説明
avfmacOSAVFoundation バックエンドを有効化する
v4l2LinuxV4L2 バックエンドを有効化する
pipewireLinuxPipeWire バックエンドを有効化する (libpipewire-0.3 に動的リンクする)
mfWindowsMedia Foundation バックエンドを有効化する (windows クレートに依存する)
mjpegLinuxV4L2 で MJPEG パススルーキャプチャを有効化する (v4l2 を含む)
default-avfmacOS既定バックエンドを AVFoundation にする (avf を含む)
default-v4l2Linux既定バックエンドを V4L2 にする (v4l2 を含む)
default-pipewireLinux既定バックエンドを PipeWire にする (pipewire を含む)
default-mfWindows既定バックエンドを Media Foundation にする (mf を含む)

デフォルト feature は default-avf / default-v4l2 / default-mf。

  • 同一 OS で default-* を 2 つ以上有効にすると、ビルドスクリプトが panic してビルドできない
  • Linux で既定を PipeWire にするには default-features = false が必要。このとき macOS / Windows の既定も外れるため、クロスプラットフォームのアプリでは default-avf / default-mf を指定し直す
  • default-* が無い OS では VideoDeviceList::enumerate() / VideoCapture::new() が存在しない。enumerate_avf() / new_v4l2() などの明示 API を使う
  • バックエンドが 1 つも有効でない OS では VideoCapture / VideoDevice / VideoDeviceList が公開されない (Error / PixelFormat などの型だけが残る)
  • Linux では v4l2 と pipewire を同時に有効化できる
  • クレート内部の enable_* cfg はビルドスクリプトがこのクレートにだけ設定するもので、利用側からは参照できない。利用側は #[cfg(target_os = "linux")] と自身の feature で分岐する

Linux の既定を PipeWire にし、macOS / Windows は既定のまま使う場合:

[dependencies.shiguredo_video_device]
version = "2026.4"
default-features = false
features = ["default-avf", "default-mf", "default-pipewire"]

既定は V4L2 のまま、PipeWire と MJPEG も使えるようにする場合:

[dependencies]
shiguredo_video_device = { version = "2026.4", features = ["pipewire", "mjpeg"] }

ビルド要件

OS要件
macOSXcode Command Line Tools (bindgen 用の libclang を含む)
Linux (V4L2)libclang (Ubuntu では libclang-dev)
Linux (PipeWire)libclang と libpipewire-0.3-dev、pkg-config
Windows追加の依存パッケージは不要

コア API

公開型

型説明主要 API
VideoDeviceListデバイス列挙結果enumerate(), enumerate_avf(), enumerate_v4l2(), enumerate_pipewire(), enumerate_mf(), as_slice(), len(), is_empty(), &VideoDeviceList: IntoIterator
VideoDevice1 台のデバイスname() -> Result<String>, unique_id() -> Result<String>, formats() -> Vec<VideoFormat>, format_count() -> usize
VideoFormatデバイスが報告するフォーマットwidth: i32, height: i32, min_fps: f32, max_fps: f32, pixel_format: PixelFormat
VideoCaptureキャプチャセッションnew(), new_avf(), new_v4l2(), new_pipewire(), new_mf(), start(), stop(), config()
VideoCaptureConfigキャプチャ設定 (Default 実装あり)device_id: Option<String>, width: i32, height: i32, fps: i32, pixel_format: Option<PixelFormat>
VideoFrame<'a>コールバックに渡る借用フレームdata, uv_data, width, height, stride, stride_uv, pixel_format, timestamp_us, pixel_buffer, to_owned()
VideoFrameOwned所有フレームVideoFrame と同じフィールド (Vec<u8> で保持), as_frame()
PixelFormatピクセルフォーマットNv12, Yuy2, I420, Mjpeg, Unknown(u32), name(), to_raw(), Display
PixelBuffermacOS の CVPixelBuffer 参照as_ptr() -> *mut c_void (Clone で CFRetain、Drop で CFRelease)
Error / Result<T>エラー型と結果型「エラー型」の節を参照

enumerate() / new() は既定バックエンド (default-*) を使う。enumerate_*() / new_*() は、ビルド対象 OS が一致し、対応する feature が有効なときだけ存在する。

VideoDeviceList / VideoDevice

  • VideoDevice は VideoDeviceList からの借用 (&VideoDevice) でしか取り出せない。リストを捨てた後も情報を使う場合は unique_id() などの値をコピーしておく
  • unique_id() の値は、そのまま VideoCaptureConfig::device_id に渡せる (同じバックエンドに限る)
  • name() / unique_id() は FFI バックエンド (macOS / Linux) で C 側が NULL や非 UTF-8 を返しうるため Result を返す。Windows では失敗しない
  • format_count() は C 側が報告するエントリ数で、取得できなかったエントリを除いた formats().len() より大きいことがある
  • VideoFormat の min_fps / max_fps はバックエンド依存の概算値である (「バックエンドごとの挙動差」の節を参照)

VideoCapture のライフサイクル

  • new*() はセッションを作るだけで、キャプチャは始まらない (macOS は権限が未決定ならここで OS のダイアログを出し、応答まで戻らない)
  • start() は冪等で、実行中なら Ok(()) を返す。stop() 後の再 start() も許容する
  • stop() はブロッキングで、キャプチャスレッドとコールバックの完了を待ってから戻る。実行中でなければ何もしない
  • drop すると、実行中なら stop() と同じブロッキング停止を行ってからセッションを破棄する
  • config() は new*() に渡した要求値を返す。ネゴシエーション後の実際の値ではないため、解像度・フォーマット・ストライドは各フレームのフィールドを参照する

VideoCaptureConfig

フィールドDefault の値説明
device_idNoneVideoDevice::unique_id() の値。None は既定デバイス
width640要求する幅
height480要求する高さ
fps30要求するフレームレート
pixel_formatNone要求するピクセルフォーマット。None はバックエンドが選ぶ
  • Windows では width / height / fps が 0 以下だと Error::InvalidCaptureConfig を返す。他のバックエンドは Rust 側で検証しない
  • pixel_format: Some(PixelFormat::Unknown(_)) は全バックエンドで Error::UnsupportedPixelFormat を返す
  • pixel_format: Some(PixelFormat::Mjpeg) は Linux V4L2 + mjpeg feature でのみ使える。それ以外は Error::UnsupportedPixelFormat(PixelFormat::Mjpeg) を返す
  • 要求値はあくまで希望であり、実際の値はバックエンドとデバイスが決める

PixelFormat

バリアント内容to_raw() (FourCC)
Nv12YUV 4:2:0 セミプラナー (Y プレーン + UV インターリーブプレーン)0x3231564E (NV12)
Yuy2YUV 4:2:2 パック (Y0 U Y1 V)0x32595559 (YUY2)
I420YUV 4:2:0 プラナー (Y / U / V の 3 プレーン)0x30323449 (I420)
MjpegMotion JPEG。デコードは利用者の責務0x47504A4D (MJPG)
Unknown(u32)未知の FourCC保持している値
  • name() は "NV12" / "YUY2" / "I420" / "MJPEG" / "Unknown" を返す。Display は Unknown のとき Unknown(0x????????) 形式で値を含める
  • 現状のバックエンドは Unknown を生成しない。未知のフォーマットは formats() から除外され、そのフレームはコールバックに渡らない

フレームのメモリレイアウト

フォーマットdatauv_datastridestride_uv
NV12Y プレーンSome: UV インターリーブプレーン (ceil(height / 2) 行)Y の 1 行のバイト数UV の 1 行のバイト数
I420Y プレーンSome: U プレーンの直後に V プレーンが続く連結領域 (各 stride_uv * ceil(height / 2) バイト)Y の 1 行のバイト数U / V それぞれの 1 行のバイト数
YUY2パックデータNone1 行のバイト数0
MJPEGJPEG ペイロード (長さは data.len())None00
  • stride は行末のパディングを含みうる。幅ちょうどの連続バッファが必要なら行ごとに詰め直す (「コード例」の pack_frame を参照)
  • NV12 / I420 / YUY2 の data.len() は、FFI バックエンド (macOS / Linux) では stride * height、Windows では width * height (YUY2 は width * 2 * height)
  • timestamp_us はマイクロ秒だが起点はバックエンドごとに異なる。フレーム間の相対時間にだけ使い、壁時計の時刻としては使わない
  • pixel_buffer は macOS では Some、それ以外では None

フレームコールバックの制約

コールバックの型は F: Fn(VideoFrame<'_>) + Send + 'static である。

  • VideoFrame のスライスはコールバックの実行中にのみ有効。コールバック後も使うなら frame.to_owned() で VideoFrameOwned にコピーする
  • コールバックはバックエンドのキャプチャスレッドで実行される。重い処理をするとフレームが落ちるため、sync_channel + try_send などで別スレッドに渡す
  • FnMut ではないため、状態の更新には Cell / RefCell / Mutex / アトミック型などの内部可変性を使う。Sync を要求しないため、コールバックが並行して呼ばれることはない
  • コールバックは panic してはならない。macOS / Linux ではプロセスが abort しうる。Windows ではキャプチャスレッドが終了し、stop() の後の start() が Error::CaptureFaulted を返す (stop() を挟まない start() は実行中とみなして Ok(()) を返す)。ライブラリは catch_unwind で吸収しない
  • コールバック内から stop() を呼んだり VideoCapture を drop したりしてはならない。Linux (V4L2 / PipeWire) では use-after-free、macOS ではデッドロックになりうる。Windows の VideoCapture は !Send のため、safe Rust ではコールバックに渡せない。停止はチャネルやフラグで別スレッドに通知し、そのスレッドで stop() を呼ぶ

スレッド安全性

型SendSync備考
VideoDeviceList / VideoDeviceYesYes
VideoCapture (macOS / Linux)YesNo
VideoCapture (Windows)NoNoCOM 初期化ガードを保持するため、作成したスレッドで使い、そのスレッドで drop する
VideoFrameOwned / PixelBufferYesNoArc<VideoFrameOwned> はスレッド間で共有できない。複数の受信側に配るなら clone するか Mutex で包む
ErrorYesYesCopy

コード例

デバイス列挙

use shiguredo_video_device::VideoDeviceList;

fn main() -> shiguredo_video_device::Result<()> {
    // 既定バックエンドでデバイスを列挙する
    let devices = VideoDeviceList::enumerate()?;
    for device in &devices {
        // name() / unique_id() は FFI バックエンドで失敗しうるため Result を返す
        println!("{} ({})", device.name()?, device.unique_id()?);
        for format in device.formats() {
            println!(
                "  {}x{} {:.0}-{:.0}fps {}",
                format.width, format.height, format.min_fps, format.max_fps, format.pixel_format
            );
        }
    }
    Ok(())
}

チャネル経由でフレームを受け取る (推奨パターン)

use std::sync::mpsc::sync_channel;
use std::time::Duration;

use shiguredo_video_device::{VideoCapture, VideoCaptureConfig, VideoFrame, VideoFrameOwned};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 上限付きチャネルで背圧をかける。無制限チャネルは受信側が遅いとメモリが増え続ける
    let (tx, rx) = sync_channel::<VideoFrameOwned>(4);

    let config = VideoCaptureConfig {
        width: 1280,
        height: 720,
        fps: 30,
        ..VideoCaptureConfig::default()
    };

    let mut capture = VideoCapture::new(config, move |frame: VideoFrame<'_>| {
        // スライスはコールバック中のみ有効なので to_owned() でコピーしてから送る
        // 受信側が詰まっているときはブロックせずにフレームを捨てる
        let _ = tx.try_send(frame.to_owned());
    })?;

    capture.start()?;

    for _ in 0..30 {
        let frame = rx.recv_timeout(Duration::from_secs(5))?;
        // 実際の解像度とフォーマットはフレーム側を見る (config() は要求値)
        println!(
            "{}x{} {} ts={}us",
            frame.width, frame.height, frame.pixel_format, frame.timestamp_us
        );
    }

    // stop() はコールバックの完了を待ってから戻る。コールバック内からは呼ばない
    capture.stop();
    Ok(())
}

デバイスとフォーマットを指定する

use shiguredo_video_device::{
    Error, PixelFormat, VideoCapture, VideoCaptureConfig, VideoDeviceList,
};

fn open_first_device() -> Result<VideoCapture, Box<dyn std::error::Error>> {
    let devices = VideoDeviceList::enumerate()?;
    let Some(device) = devices.as_slice().first() else {
        return Err("no video device found".into());
    };

    // デバイスが 1280x720 の NV12 を報告していればそれを要求し、無ければバックエンドに任せる
    let pixel_format = device
        .formats()
        .into_iter()
        .find(|f| f.width == 1280 && f.height == 720 && f.pixel_format == PixelFormat::Nv12)
        .map(|f| f.pixel_format);

    let config = VideoCaptureConfig {
        // unique_id() の値はそのまま device_id に渡せる
        device_id: Some(device.unique_id()?),

関連スキル