tunsock BLOG
Illusts of Life with Old cats
Goods of Life with Old cats
instagram of Life with Old cats
外出先からAndroidスマホでPCのカメラ画像を表示する方法
New

外出先からAndroidスマホでPCのカメラ画像を表示する方法

July 31, 2026

会社への通勤時や旅行中など、外出先からスマホで自宅にあるPCに接続のカメラ画像を見たいと思うことはありませんか? その場合、PCにリモートデスクトップでログインして確認する方法などがあります。 ただ、それだと自宅のルーターの設定を変えたり、カメラ画像を簡単に見れない、セキュリティに不安があるなどの課題があると思います。 そこで、P2Pによる簡単に且つ安全に行う方法をご紹介したいと思います。

icon_logo

アクセス環境の概要

アクセス環境は、Androidスマートフォンとカメラが接続されているWindows PCを使用します。 そして、AndroidとWindowsの間を繋ぐネットワーク環境として「トンネルアプリ」を使用します。 (当方がアプリ開発を行い無料で提供しているP2Pアプリです)

トンネルアプリは、2つの端末にインストールして使用し、一方のアプリでクライアントアプリからのアクセスを代理ポートで受け付け、一方のアプリでサーバーアプリへのアクセスを行います。 端末アプリ間の通信データはEnd-to-Endで暗号化しますので、その内容を第三者に見られることなく安全にアクセスすることができるようになっています。

image01

Windows PC側のセットアップ

Windows PCでは、まず「トンネルアプリ(Windows版)」をインスールして起動します。 すると、以下のコマンドプロンプトとブラウザ画面が表示されます。なお、この例では、トンネルアプリの端末の番号が「000001」となっています。

image02_ja

続いて、カメラ画像を取得する簡易のカメラサーバーアプリをセットアップします。

アプリの仕様は、指定のHTTPリクエストを送信するとカメラのスナップショットをJPEG画像で配信するシンプルなものとします。 Windowsで稼働させることもあり、C#で実装したサンプルコードを以下に記載します。

アプリは、ポート番号「8080」でHTTPリクエストを待ち受けし、そのアクセス元は「127.0.0.1(ローカルホスト)」のみに制限しています。 また、カメラ画像のファイル名は「camera.jpg」で指定します。※それ以外のリクエストはエラーで応答します。

using System;
using System.IO;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Windows.Media.Capture;
using Windows.Media.MediaProperties;
using Windows.Storage;

class Program
{
    static async Task Main(string[] args)
    {
        IPAddress ipAddress = IPAddress.Loopback;
        int port = 8080;
        TcpListener server = new TcpListener(ipAddress, port);
        server.Start();
        Console.WriteLine($"HTTP server host: {ipAddress} port: {port} accepting...");
        try
        {
            while (true)
            {
                TcpClient client = await server.AcceptTcpClientAsync();
                _ = Task.Run(() => HandleClientAsync(client));
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"ERROR: The server has stopped. {ex.Message}");
        }
        finally
        {
            server.Stop();
        }
    }

    private static async Task HandleClientAsync(TcpClient client)
    {
        using (client)
        using (NetworkStream stream = client.GetStream())
        {
            byte[] buffer = new byte[2048];
            try
            {
                int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
                if (bytesRead == 0) return;
                string rawRequest = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                var httpContext = ParseHttpGet(rawRequest);
                if (httpContext.IsValid)
                {
                    string dirPath = AppContext.BaseDirectory;
                    string fileName = "camera.jpg";
                    if (!httpContext.Path.Equals($"/{fileName}")){
                        await ReponseError(stream, "404", "Not Found");
                        Console.WriteLine($"ERROR: Path: {httpContext.Path}");
                        return;
                    }
                    await CaptureSnapshotJpeg(dirPath, fileName);
                    string filePath = $"{dirPath}\\{fileName}";
                    byte[] fileBytes = File.ReadAllBytes(filePath);
                    string httpHeader
                        = "HTTP/1.1 200 OK\r\n" 
                        + "Content-Type: image/jpeg;\r\n" 
                        + $"Content-Length: {fileBytes.Length}\r\n" 
                        + "Connection: close\r\n\r\n";
                    byte[] headerBytes = Encoding.UTF8.GetBytes(httpHeader);
                    await stream.WriteAsync(headerBytes, 0, headerBytes.Length);
                    await stream.WriteAsync(fileBytes, 0, fileBytes.Length);
                    Console.WriteLine($"SUCCESS: {httpContext.Method} {httpContext.Path} {fileBytes.Length}");
                }
                else
                {
                   await ReponseError(stream, "400", "Bad Request");
                   Console.WriteLine($"ERROR: Method: {httpContext.Method}");
                }
            }
            catch (Exception ex)
            {
                await ReponseError(stream, "500", "Internal Server Error");
                Console.WriteLine($"ERROR: {ex.Message}");
            }
        }
    }

    private class HttpRequestContext
    {
        public bool IsValid { get; set; } = false;
        public string Method { get; set; } = string.Empty;
        public string Path { get; set; } = string.Empty;
        public Dictionary<string, string> QueryParameters { get; set; } = new();
    }

    private static HttpRequestContext ParseHttpGet(string rawRequest)
    {
        var context = new HttpRequestContext();
        if (string.IsNullOrWhiteSpace(rawRequest)) return context;
        string[] lines = rawRequest.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);
        if (lines.Length == 0) return context;
        string[] tokens = lines[0].Split(' ');
        if (tokens.Length < 3) return context;
        context.Method = tokens[0];
        string fullPath = tokens[1];
        if (context.Method != "GET") return context;
        context.IsValid = true;
        int queryIndex = fullPath.IndexOf('?');
        if (queryIndex >= 0)
        {
            context.Path = fullPath.Substring(0, queryIndex);
            string queryString = fullPath.Substring(queryIndex + 1);
            string[] pairs = queryString.Split('&');
            foreach (string pair in pairs)
            {
                string[] kv = pair.Split('=');
                if (kv.Length == 2)
                {
                    string key = Uri.UnescapeDataString(kv[0]);
                    string value = Uri.UnescapeDataString(kv[1]);
                    context.QueryParameters[key] = value;
                }
            }
        }
        else
        {
            context.Path = fullPath;
        }
        return context;
    }

    static async Task ReponseError(NetworkStream stream, string code, string message)
    {
        string badRequestResponse = $"HTTP/1.1 {code} {message}\r\nConnection: close\r\n\r\n";
        byte[] responseBytes = Encoding.UTF8.GetBytes(badRequestResponse);
        await stream.WriteAsync(responseBytes, 0, responseBytes.Length);
    }

    static async Task CaptureSnapshotJpeg(string dirPath, string fileName)
    {
        var mediaCapture = new MediaCapture();
        var settings = new MediaCaptureInitializationSettings
        {
            StreamingCaptureMode = StreamingCaptureMode.Video
        };
        await mediaCapture.InitializeAsync(settings);
        StorageFolder folder = await StorageFolder.GetFolderFromPathAsync(dirPath);
        StorageFile file = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
        var imgFormat = ImageEncodingProperties.CreateJpeg();
        await mediaCapture.CapturePhotoToStorageFileAsync(imgFormat, file);
        mediaCapture.Dispose();
    }
}

当方のPC環境でビルドした時のコマンドのサンプルも以下に記載します。
※C#のコードをビルドするためには、.NET SDK、MSBuild、Visual Studio Build Tools のインストールが必要です。

"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\Roslyn\csc.exe" /reference:"C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.28000.0\Windows.winmd" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\Facades\System.Runtime.dll" /reference:"C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Runtime.WindowsRuntime.dll" Program.cs

ビルドが成功すると、実行プログラムの「Program.exe」が作成されるので、PowerShellのコンソールから起動します。

PS C:\camera_server_app> .\Program.exe
HTTP server host: 127.0.0.1 port: 8080 accepting...

PCに接続されたカメラが有効になっていることを確認したうえで、同じPCのWebブラウザーから「http://127.0.0.1:8080/camera.jpg」でアクセスするとカメラ画像が表示されます。

SUCCESS: GET /camera.jpg 174301
image03

Androidスマホのセットアップ

Androidスマホ [2] にも「トンネルアプリ(Android版)」をインストールして起動します。 この例では、トンネルアプリの端末の番号が「000002」となっています。

image05_ja

先ほどインストールしたWindows PC側のトンネルアプリのQRコードを表示し、AndroidスマホのQRコードリーダーでそれを読み取ります。

image06_ja

すると、Windows PC側のトンネルアプリがサーバー側端末として登録されます。

image07_ja

続いて、トンネル通信設定を登録します。

「クライアント側端末」では、「端末内のアプリのみ」を選択して「127.0.0.1(ローカルホスト)」からのアクセスに制限し、 Webブラウザーからアクセスを受け付けるための代理ポートとして「20080」を設定します。

「サーバー側端末」には、Windows PC側のトンネルアプリ「000001」を選択し、そこからのアクセス先として「127.0.0.1」とポート「8080」を設定して保存します。

image08_ja
image09_ja

AndroidスマホのWebブラウザーから「http://127.0.0.1:20080/camera.jpg」でアクセスするとカメラ画像が表示されます!

image10

最後までお読みいただきありがとうございました!ご意見などございましたらメッセージをお寄せいただけると幸いです。

P2P · Android · IoT/M2M · Camera · Windows

Illusts of Life with Old cats
Goods of Life with Old cats