USB Cameraの画像をMJPEGでストリーミング

September 4, 2025 – 3:06 pm

我が家のLinuxServerに接続したUSB-Cameraの映像をMotion JPEGでストリーミング配信をGoogle AI Studio(Gmini2.5 Pro)の助けを借りてやってみたので備忘録という感じでメモしておいた。

ブラウザ上で描画した画像のスナップショット

動画配信用の Nodejs(JavaScript) ソース:

const http = require('http');
const express = require('express');
const { spawn } = require('child_process');

const app = express();
const server = http.createServer(app);

// HTMLページを配信するエンドポイント
app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html');
});

// MJPEGストリームを配信するエンドポイント
app.get('/stream', (req, res) => {

    // ストリーミングに必要なHTTPヘッダーを設定
    res.writeHead(200, {
        'Content-Type': 'multipart/x-mixed-replace; boundary=--myboundary',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'Pragma': 'no-cache'
    });

    // ffmpegコマンドを子プロセスとして起動
    const ffmpeg = spawn('ffmpeg', [
        '-f', 'v4l2',             // 入力フォーマット (video4linux2)
        '-framerate', '15',       // フレームレート
        '-video_size', '640x480', // 映像サイズ
        '-i', '/dev/video0',      // 入力デバイス
        '-c:v', 'mjpeg',          // ビデオコーデックをmjpegに
        '-q:v', '5',              // 品質 (1-31, 低いほど高品質)
        '-f', 'mjpeg',            // 出力フォーマットをmjpegストリームに
        '-'                       // 出力先を標準出力に
    ]);

    let buffer = Buffer.alloc(0);
    const EOI = Buffer.from([0xff, 0xd9]); // JPEGの終端マーカー

    ffmpeg.stdout.on('data', (data) => {
        // 受け取ったデータをバッファの末尾に結合
        buffer = Buffer.concat([buffer, data]);

        let eoi_pos = 0;
        // バッファ内に終端マーカーがなくなるまでループ
        while ((eoi_pos = buffer.indexOf(EOI)) !== -1) {
            const frame_end = eoi_pos + 2;
            // 最初のバイトから終端マーカーまでを1フレームとして切り出す
            const frame = buffer.slice(0, frame_end);
            // 切り出したフレームをバッファから削除
            buffer = buffer.slice(frame_end);

            // 完成した1フレームをブラウザに送信
            res.write(`--myboundary\r\n`);
            res.write('Content-Type: image/jpeg\r\n');
            res.write(`Content-Length: ${frame.length}\r\n`);
            res.write('\r\n');
            res.write(frame, 'binary');
            res.write('\r\n');
        }
    });

    // ffmpegプロセスでエラーが発生した場合の処理
    ffmpeg.stderr.on('data', (data) => {
        console.error(`ffmpeg stderr: ${data}`);
    });

    // ffmpegプロセスが終了した場合の処理
  ffmpeg.on('close', (code) => {
       console.log(`ffmpeg process exited with code ${code}`);
        res.end(); // レスポンスを終了する
    });

    // クライアントが接続を切断した場合にffmpegプロセスを終了させる
    req.on('close', () => {
        console.log('Client disconnected.');
        ffmpeg.kill(); // ffmpegプロセスを強制終了
    });
});

const PORT = 3000;
server.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

画像描画用のHTMLファイル(index.html)

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>MJPEG Stream Viewer</title>
    <style>
        body { font-family: sans-serif; text-align: center; }
        img { border: 1px solid black; max-width: 90%; }
    </style>
</head>
<body>
    <h1>Web Camera MJPEG Stream</h1>
    <!-- このimgタグがストリーミング映像を表示します -->
    <img src="/stream" alt="Web Camera Stream">
</body>
</html>

Post a Comment