
使っていないPixel 8aを、Codexと一緒にペット見守りカメラにした話
きっかけ
家にある、使っていないGoogle Pixel 8aを何かに使えないかなと思ったのがきっかけでした。いろいろ考えて、ペットの見守りカメラとして再利用できないかと思いました。
今も市販の見守りカメラを複数置いているのですが、気になっていたのは、こちらから話しかけたときの音声です。声が割れてしまい、使いづらいなと思っていました。
そこで、外出先から様子を見られて、必要なときに声をかけられる。それを、自分の使いやすい形で作ってみることにしました。
開発の相手はCodex。要件を伝えて実装してもらい、実際のPixelやスマホで動かして、気づいたことを返す。そのやり取りを繰り返して、個人用のWebアプリ「おるすばん」を作っています。
最初に決めたのは、映像の安定性と音声
最初から機能を増やしすぎず、今回ほしいものを絞りました。
Pixelの背面カメラで撮影する
外出先のスマホやPCから映像と音声を確認する
ボタンを押している間だけ、こちらの声を届ける
ズーム、全画面、写真の保存ができる
許可したGoogleアカウントだけが使える
録画、AI分析、動体検知、課金などは今回は入れていません。まずは、自分が見守りに使えることを目標にしました。
構成はHTML・CSS・JavaScriptにFirebase、映像と音声の通信はWebRTCです。撮影側はAndroid版Chrome。専用のAndroidアプリを作らず、ブラウザで動かす形にしました。
開発は、基本の映像・音声、双方向音声、ズームなどの操作、最後に安定化、という順番で進めました。
「見られました」「きこえます」までにも、ひと山あった
最初はFirebaseへのログインやデプロイで何度かつまずきました。
Windowsでのコマンドの実行場所、CLIのエラー、クラウド側の権限設定。画面を開けても、撮影開始でエラーが出る場面もありました。
そこで、出てきたメッセージをそのままCodexに渡して、一つずつ確認。ブラウザから関数へ届くところの設定と、アプリ内で誰を許可するかという設定を分けて見直し、映像がつながるところまで進みました。
そして、実機で「見られました。つながっています」「きこえます」と確認できました。ただ、映像と音が届いた後にも、直したいところが次々に出てきました。
音声は「届く」だけでなく、実際に聞いて調整する
いちばん重視していたのが、Pixelから再生される話しかけ音声です。
押している間だけ声を送るPush-to-Talkを入れ、アプリ内の受信音量や、ノイズ抑制・自動ゲインの設定も用意しました。音声のピークを確認するテスト画面もあります。
話しかけている間は、Pixelのマイクを一時的に消音します。Pixelのスピーカーから出た声を再びマイクが拾うのを抑えるためです。指を離したときや画面を切り替えたときも、送話が残らないようにしています。
途中では「音が出ない」と思った場面もありましたが、小さいながら再生されていることを確認できました。アプリ内の音量と、本体のメディア音量を調整しながら確認しています。
ここは、コードのテストだけでは分からない部分でした。実際の声を、実際の端末のスピーカーで聞く作業が必要でした。
熱くなったので、見ていない間はカメラを休ませる
実際に動かしてみると、撮影用Pixelの本体がかなり温かく感じられました。
そこで、撮影側には映像プレビューを表示しないように変更。さらに、誰も閲覧していない間は、カメラとマイクを休止させる形にしました。

開発中のデモ環境で撮影した待機画面です。撮影側では映像を表示せず、閲覧開始を待ちます。「撮影待機を開始」は、カメラをずっと回し続けるという意味ではありません。閲覧が始まったときに撮影を始め、閲覧が終われば休ませます。
発熱やバッテリーの使い方は、これから長時間の実機試験で確かめる部分です。この変更で何度下がった、といった測定はまだしていません。
スマホでは、話しかけながら映像を見たい
PCで使いやすくても、スマホで同じように使いやすいとは限りませんでした。
特に困ったのが、話しかけるボタンまでスクロールすると、映像が見えなくなること。声をかけたときのペットの反応を見たいので、ここは直したいところでした。
スマホでは、閲覧を始めると映像と主要な操作を画面内に固定する表示へ変更しました。マイクを準備した後は、送話ボタンが横幅いっぱいに広がります。
ズーム、音声のON/OFF、写真の保存も同じ画面に。詳しい設定や接続状況は、必要なときだけ開く形です。「ボタンがある」ことと、「使いたい場面ですぐ押せる」ことの違いを、実機で操作して感じました。
登録待ちやブラウザの違いも、使いながら修正
一度撮影待機を終了すると、再開時にエラーになることがありました。これは、古い停止状態がキャッシュから通知され、新しい待機を取り消してしまうケースを修正しました。
その後も、「撮影待機の登録が残っています。○秒待ってください」という表示が頻繁に出ました。同じ画面で停止・再開するとき、自分の前回の登録を引き継げるように変更。こちらは、実機でも改善を確認できています。
閲覧用スマホのAndroid Braveでは映像が出ず、Chromeに変えると見られた場面もありました。現時点ではChromeで使っています。Braveで映らなかった詳しい原因は、まだ確定していません。
もう一人が使うことも考え、別のGoogleアカウントを閲覧専用で許可する仕組みも追加しました。今は同時に一人までで、交代して見る形です。誰かが閲覧中なら、そのことを表示します。追加する人のUID登録と、二人での実機確認はこれからです。
いま確認しているのは、回線とバッテリー
画質にも、回線による違いがありました。Wi-Fiでは横幅1280程度で見られていたのが、モバイル回線では320になることもあり、1倍でも粗く感じました。
通信状況に合わせて映像の解像度が下がっている可能性があります。現時点では、解像度を無理に固定するよりも、映像が途切れにくいことと音声を優先して確認しています。
そして、充電ケーブルを外して持ち運び、置き場所を変えて使えないかという話にもなりました。
バッテリー残量78%から、充電なしで1時間つなぐ試験を始めたところです。ただ、途中で接続が切れていることに気づきました。閲覧用の現役スマホが時々Wi-Fiを拾わなくなることもあるため、今度はPCから見守って比較することにしています。
その後の途中経過では、残量は78%から73%に下がりました。5ポイントの減少です。ただ、正確な経過時間を記録しておらず、途中の切断もあるので、これを「1時間に5%減る」という結果にはできません。
実際の使い方は、常時映像を見続けるというより、必要なときだけ接続し、閲覧は1日30分程度を想定しています。そうなると気になるのは、長い待機時間の消費です。カメラとマイクが休止していても、画面表示や接続管理の通信は続きます。次は、閲覧しない待機だけの時間と、映像を受信している時間を分けて測りたいと思っています。
また、現在のWeb版は、Chromeの撮影画面を前面に表示して使う設計です。スリープ防止は入れていますが、画面を消したまま動く仕組みではありません。外部回線で直接接続できない場合に備えたTURN中継も設定可能な構成にしていますが、実サーバーの用意と中継試験はまだです。
作ってみて分かったこと
ここまでで、コードやアクセス制御などの自動試験は70件が成功しています。別途、合成映像・音声を使ったブラウザ試験も20項目を確認しました。
それでも、端末の熱さ、スピーカーの聞こえ方、スマホで指を動かしたときの使い勝手、実際のWi-Fiでの安定性は、手元の端末で試して初めて分かりました。
Codexに要望を伝え、動いたものを使ってみて、「ここが熱い」「この表示が出る」「話すときに映像が見えない」と返す。その具体的なやり取りが、アプリを自分の用途に近づけていく作業になりました。
使っていないスマホから始めた、小さな個人開発。まずは、家のペットを安心して見守れる道具にするために、もう少し使いながら確かめていこうと思います。
技術メモ:AIと一緒に開発する人向けに、今回の構成と修正を整理
ここからは、Codexなどに実装を頼みながら、自分でも設定や実機確認を進める人向けのメモです。今回、何がどの役割を持ち、どこでつまずいたのかをまとめます。
Firebaseは映像の配信サーバーとして使っていない
|部品|今回の役割|
|HTML・CSS・JavaScript|撮影/閲覧画面と操作。スマホでは映像と送話ボタンを画面内に配置|
|Firebase Hosting|WebアプリをHTTPSで公開|
|Firebase Authentication|Googleログインで利用者を識別|
|Firestore|許可設定と、接続準備に使う情報・予約・有効期限を管理|
|Cloud Functions|許可確認、撮影登録、閲覧予約、期限更新、終了処理|
|WebRTC|端末間の映像・双方向音声と操作メッセージの通信|
Firestoreでやり取りするのは、SDPとICE candidateという接続準備の情報です。動画をFirestoreへ次々にアップロードする作りではありません。接続準備が済むと、映像と音声はWebRTCで流れます。
STUNは直接接続の準備に使い、直接つながらない回線ではTURNが中継します。今回の実装はTURNの短期認証情報をサーバー側で発行できる構成ですが、中継サーバー自体の用意と試験はまだです。自宅Wi-Fiで映っただけで、すべての外部回線でも映るとは扱っていません。
認証は、画面・関数・データのそれぞれで確認する
Googleログインできた人を全員許可する設定にはしていません。管理者が登録したUIDだけを使えるようにし、FunctionsとFirestore Security Rulesの両方で制限します。画面のボタンを無効にするだけでは、データへのアクセス制御にはなりません。
追加する閲覧者には、管理者が撮影所有者のUIDを紐付けます。追加者は閲覧専用で、撮影登録や撮影待機の停止はできません。接続準備の書込も、撮影側のofferと閲覧側のanswer、それぞれのICE情報に分けています。同時閲覧はトランザクションで1件に限定し、2人目には閲覧中のメッセージを返します。
接続準備情報には有効期限を付けています。FirestoreのTTL削除が終わるのを待たず、Rulesとサーバー側の期限判定でも利用を制限します。
「撮影待機」と「実際の撮影」を別の状態にする
撮影待機では、最初の操作でカメラ・マイクの権限を確認した後、トラックを`stop()`して解放します。閲覧予約が入ったら新しく取得し、閲覧が終われば再び解放します。撮影側の映像プレビューも描画しません。
ここで、登録が有効であることを示す状態と、カメラが実際に動いている状態を混同しないことが大事でした。取得の途中で閲覧が終わった場合も、遅れて届いたカメラやマイクを解放する必要があります。
接続管理は25秒ごとに期限を更新し、予約の有効期間は90秒です。端末側では、更新に65秒失敗すると配信を止めます。突然ブラウザが閉じたり、終了通知が届かなかったりしても、予約が永久に残らないための仕組みです。待機中も、この通信と画面のスリープ防止は続きます。
実装でつまずいた点と、直した内容
|症状|今回確認した原因・対応|
|ログイン後も撮影開始が失敗する|Cloud RunのHTTP受付と、関数内のFirebase認証を分けて調査。受付の復旧後も、未認証の関数呼出が拒否されることを確認|
|Functionsのデプロイ対象が見つからない|独自のcodebase名を含む対象指定へ修正。任意のTURN関数は別codebaseへ分離|
|停止した後の再開が、すぐ取り消される|Firestoreの初回キャッシュ通知で古い停止状態を拾っていた。撮影登録はサーバー確認済みの通知で判定|
|登録が残っていて、再開まで待たされる|同じ画面の前回登録IDが一致する場合だけ引き継ぐ。新しい登録を古い終了通知が閉じないよう、操作を識別|
|閲覧側からの声が届かない|受け取ったofferに対応する音声の送信経路へマイクを接続。受信音声の再生経路も確認|
|スマホで映像が出ない|手動の再生操作と受信統計を追加。実機ではBraveからChromeへの変更で視聴できたが、ブラウザ差の詳しい原因は未確定|
AIに修正を頼むときも、エラー全文、どちらの端末で起きたか、直前に何を押したかを渡すと、調べる場所を絞れました。「音が出ない」でも、撮影側からの音が聞こえないのか、話しかけた声がPixelで鳴らないのかで、確認する経路が違います。
音声はOpus優先、受信音量と本体音量は別
音声コーデックはOpusを優先し、エコーキャンセルを要求します。ノイズ抑制と自動ゲインは切り替え可能にしました。ただし、ブラウザが実際に適用した設定は別途確認します。
通常の受信音はWeb Audioで音量調整と音割れ抑制を行います。アプリ内のゲインは0〜1の減衰用で、本体スピーカーの音量を変更する機能ではありません。標準再生へ切り替える予備経路もありますが、その場合は通常のゲイン・リミッターを通りません。入力時点で割れた音や、物理スピーカーの歪みまで直せるわけではありません。
Push-to-Talkでは、押下中だけ送話マイクを有効にします。指を離す、画面を隠す、接続が切れる、といった場合に消音し、20秒の上限も設けています。送話状態はWebRTCのDataChannelで通知し、その間はPixelのマイクと閲覧側の受信音を一時消音する半二重方式です。
撮影設定と、実際に届く画質を分けて見る
カメラには初期値として1280×720・15fpsを要求しますが、これは受信側へ必ずその品質で届く保証ではありません。撮影トラックの実設定に加え、受信側の解像度・fps・kbpsを確認できるようにしました。モバイル回線で解像度が下がった件は、通信状況の影響が候補で、原因の確定や画質の改善試験はこれからです。
ズームはgetCapabilitiesでカメラ側の対応を調べ、対応していればapplyConstraintsを使います。非対応、または足りない倍率の分は閲覧画面でデジタル拡大します。方式を画面に表示し、写真保存でも表示中の拡大・切抜きに合わせます。
自動テスト、実機試験、運用試験を分ける
今回の自動試験70件は、Node.jsの49件とFirestore Emulatorの21件です。登録・終了の競合、権限違反、期限、送話解除などを確認しています。別途、実ブラウザのWebRTCで、合成映像・音声を使った20項目も確認しました。
これで、Pixelの発熱、物理スピーカーの音質、家庭のWi-Fiでの長時間接続まで確認できたことにはなりません。いまは、接続が切れる件を閲覧用スマホとPCで比較しています。自動ICE restartや無人での自動再接続は実装しておらず、失敗後の再接続は手動です。1回の閲覧セッションには2時間の期限もあります。
今後の電池試験は、同じ明るさ・Wi-Fi条件で「閲覧なしの待機」と「30分の閲覧」を分けて測る予定です。78%から73%という途中結果だけで、1日や1週間の持続時間を計算しないようにしています。
最後に、更新する場所も確認が必要でした。画面だけの変更ならHosting、認証や共有の変更ならFunctionsとRulesも更新します。今回の追加アカウント対応は、Hostingだけを更新しても完了しません。AIに変更を頼むときは、ソースの修正に加えて「どこへ反映する必要があるか」「何で確認したか」「まだ何を試していないか」まで、一緒に整理しておくと進めやすかったです。
2026年10月25日時点の開発記録です。画像は記事用のデモ画面です。長時間運用・接続切断の切り分け・追加アカウントでの利用は検証を続けています。クラウドや中継サーバーの運用費用も、別途確認が必要です。