この記事の最終更新日: 2026年6月30日

SIP.jsを使ってブラウザ通話を実装していると、接続自体は成功しているように見えるのに「音声が聞こえない」という問題に遭遇することがあります。
たとえば、次のような状態です。
- SIPサーバーへのREGISTERは成功している
- INVITEも送受信できている
- 通話状態は成立しているように見える
- しかし相手の声が聞こえない
- こちらの声だけ届かない
- 双方とも無音になる
- ローカル環境では動くが、本番環境では音が出ない
SIP.jsはSIPのシグナリングを扱うライブラリですが、実際の音声通信はWebRTCによって行われます。
そのため、音声が聞こえない原因はSIP.jsだけではなく、WebSocket、SIP認証、SDP、ICE、STUN/TURN、ブラウザの音声権限、HTMLのaudio要素など、複数の層に分かれます。
この記事では、SIP.jsで音声が聞こえないときに確認すべきポイントを整理します。
- SIP.jsで音声が聞こえない問題は大きく3種類ある
- SIP.jsは音声を直接運ぶわけではない
- 原因1:マイク権限が許可されていない
- 原因2:HTTPSではなくHTTPで動かしている
- 原因3:remote audio要素に音声ストリームを設定していない
- 原因4:ブラウザの自動再生制限に引っかかっている
- 原因5:audio要素がミュートされている
- 原因6:ICE接続に失敗している
- 原因7:STUN/TURNサーバーを設定していない
- 原因8:片方向音声になっている
- 原因9:SDPの内容が期待通りではない
- 原因10:音声コーデックが合っていない
- 原因11:SIPサーバー側がWebRTCに対応していない
- 原因12:WebSocket接続は成功しているが、メディア経路が別問題になっている
- 原因13:ファイアウォールやネットワークでUDPが遮断されている
- 原因14:ローカル音声とリモート音声を混同している
- 原因15:通話状態だけを見て音声成功と判断している
- Chromeのwebrtc-internalsで確認する
- よくある切り分け手順
- トラブル別の原因早見表
- 実装時に入れておきたいログ
- SIP.jsで音声が聞こえないときのチェックリスト
- まとめ
SIP.jsで音声が聞こえない問題は大きく3種類ある
まず、音声トラブルは大きく分けて次の3パターンに分類できます。
1. 相手の声が聞こえない
2. 自分の声が相手に届かない
3. 双方とも音声が聞こえない
この分類は非常に重要です。
なぜなら、原因の場所が変わるからです。
| 症状 | 主な原因 |
|---|---|
| 相手の声が聞こえない | remote audioの再生設定、ICE、受信トラック、ブラウザ制限 |
| 自分の声が届かない | マイク権限、送信トラック、SDP、ミュート設定 |
| 双方とも無音 | ICE失敗、TURN未設定、SDP不一致、NAT越え失敗 |
| 通話開始前に失敗 | SIP認証、WebSocket接続、INVITE失敗 |
| ローカルだけ動く | HTTPS、証明書、NAT、TURN、ファイアウォール |
「SIP.jsで音が出ない」と一括りに考えると原因を見失いやすいです。
まずは、SIPの接続が失敗しているのか、WebRTCのメディア通信が失敗しているのかを切り分ける必要があります。
SIP.jsは音声を直接運ぶわけではない
SIP.jsを使っていると、SIP.jsが音声通話そのものを処理しているように感じるかもしれません。
しかし、実際には役割が分かれています。
SIP.js
→ SIPのREGISTER、INVITE、BYEなどを扱う
WebSocket
→ SIPサーバーとの通信経路
WebRTC
→ 音声・映像などのメディア通信
ICE / STUN / TURN
→ NAT越え、通信経路の確立
audio要素
→ ブラウザ上で受信音声を再生
つまり、SIP.jsで通話状態が成立していても、WebRTC側のメディア通信が失敗していれば音声は聞こえません。
逆に、WebRTCの接続ができていても、ブラウザのaudio要素で正しく再生していなければ、ユーザーには無音に見えます。
原因1:マイク権限が許可されていない
自分の声が相手に届かない場合、最初に確認すべきはマイク権限です。
ブラウザでマイク使用が拒否されていると、音声トラックを取得できません。
典型的には、次のような問題が起きます。
- マイク許可ダイアログで拒否した
- ブラウザ設定でマイクがブロックされている
- OS側でブラウザのマイク利用が無効になっている
- HTTPSではない環境でgetUserMediaが使えない
- 別アプリがマイクを占有している
確認用に、まずは単純なgetUserMediaだけを試すとよいです。
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: false,
});
console.log(stream.getAudioTracks());
ここで音声トラックが取得できない場合、SIP.js以前の問題です。
SIP.jsの設定を見る前に、ブラウザ単体でマイクが使えるか確認しましょう。
原因2:HTTPSではなくHTTPで動かしている
WebRTCのマイク取得は、基本的にセキュアコンテキストで動かす必要があります。
つまり、本番環境ではHTTPSが必要です。
OK:
![]()
Example Domain
例外的にOK:
![]()
403 Error - Forbidden
NG:
![]()
Example Domain
ローカル環境では動いていたのに、サーバーにデプロイしたらマイクが使えない場合、HTTPS化されていないことが原因の可能性があります。
特に、開発時にlocalhostでは動作していたため、本番環境でも同じように動くと思い込むケースがあります。
SIP.jsの問題に見えて、実際にはブラウザのセキュリティ制約であることも多いです。
原因3:remote audio要素に音声ストリームを設定していない
相手の声が聞こえない場合、受信した音声ストリームをHTMLのaudio要素に設定できていない可能性があります。
WebRTCでは、相手の音声が自動的にスピーカーから再生されるわけではありません。
受信したMediaStreamをaudio要素に渡す必要があります。
例として、次のようなaudio要素を用意します。
<audio id="remoteAudio" autoplay></audio>
そして、PeerConnectionの受信トラックをMediaStreamにまとめて設定します。
const remoteAudio = document.getElementById("remoteAudio");
const remoteStream = new MediaStream();
peerConnection.getReceivers().forEach((receiver) => {
if (receiver.track) {
remoteStream.addTrack(receiver.track);
}
});
remoteAudio.srcObject = remoteStream;
remoteAudio.play();
SIP.jsでは、バージョンによってSessionDescriptionHandler周りの書き方が異なります。
そのため、単に「通話がEstablishedになった」だけでは不十分です。
実際にRTCPeerConnectionから音声トラックを取得し、audio要素に接続できているか確認しましょう。
原因4:ブラウザの自動再生制限に引っかかっている
近年のブラウザでは、ユーザー操作なしに音声を自動再生することが制限されています。
そのため、相手の音声ストリームをaudio要素に設定していても、ブラウザが再生をブロックする場合があります。
特に起きやすいのは次のケースです。
- 着信を自動応答している
- ユーザーがボタンを押す前にaudio.play()している
- autoplay属性だけに頼っている
- モバイルブラウザで再生しようとしている
対策としては、ユーザー操作のタイミングで再生処理を行うことです。
answerButton.addEventListener("click", async () => {
await session.accept();
const remoteAudio = document.getElementById("remoteAudio");
await remoteAudio.play();
});
ただし、実装によってはsession.accept()直後にまだremote trackが揃っていないこともあります。
その場合は、通話確立後にPeerConnectionの状態やreceiverを確認しながら、audio要素へ接続する必要があります。
原因5:audio要素がミュートされている
意外と多いのが、audio要素自体がミュートされているパターンです。
たとえば、次のような設定になっていると音声は出ません。
<audio id="remoteAudio" autoplay muted></audio>
ローカル音声の確認用にmutedを付けたまま、remote audioにも同じ設定を流用してしまうことがあります。
確認すべき項目は次の通りです。
console.log(remoteAudio.muted);
console.log(remoteAudio.volume);
console.log(remoteAudio.paused);
console.log(remoteAudio.srcObject);
特に確認すべき値は以下です。
| 項目 | 正常な目安 |
|---|---|
| muted | false |
| volume | 1 |
| paused | false |
| srcObject | MediaStreamが入っている |
| autoplay | trueまたはユーザー操作でplay済み |
音声が出ないときは、SIPやWebRTCの前に、まずaudio要素が本当に再生可能な状態か確認しましょう。
原因6:ICE接続に失敗している
SIP.jsで音声が聞こえない原因として非常に多いのが、ICE接続の失敗です。
ICEは、WebRTCで実際のメディア通信経路を確立するための仕組みです。
WebRTCでは、SIPのINVITEが成功していても、ICEで通信経路が確立できなければ音声は流れません。
確認すべき状態は、RTCPeerConnectionのICE connection stateです。
peerConnection.oniceconnectionstatechange = () => {
console.log("ICE state:", peerConnection.iceConnectionState);
};
主な状態は次の通りです。
| 状態 | 意味 |
|---|---|
| checking | 接続経路を探索中 |
| connected | 接続成功 |
| completed | 接続完了 |
| failed | 接続失敗 |
| disconnected | 一時的に切断 |
| closed | 接続終了 |
音声が聞こえない場合、failed や disconnected になっていないか確認しましょう。
原因7:STUN/TURNサーバーを設定していない
ローカルネットワーク内では通話できるのに、外部ネットワークや本番環境で音声が聞こえない場合、STUN/TURNの設定不足が疑われます。
WebRTCはP2P通信を行うため、NATやファイアウォールを越える必要があります。
このときに使われるのがSTUN/TURNです。
STUN:
自分のグローバルIPやポートを調べるための仕組み
TURN:
直接通信できない場合に、メディア通信を中継する仕組み
SIP.jsのWebRTC設定で、ICE serversを指定します。
const sessionDescriptionHandlerFactoryOptions = {
peerConnectionConfiguration: {
iceServers: [
{
urls: "stun:stun.l.google.com:19302",
},
{
urls: "turn:turn.example.com:3478",
username: "user",
credential: "password",
},
],
},
};
実務では、STUNだけでは不十分なことがあります。
特に次のような環境ではTURNが必要になる可能性が高いです。
- 企業ネットワーク
- モバイル回線
- 対称NAT環境
- 厳しいファイアウォール配下
- VPN利用時
- 双方が異なるNAT配下にいる場合
「SIPはつながるが音声が無音」という場合は、TURN未設定がかなり有力な原因になります。
原因8:片方向音声になっている
WebRTC通話では、片方向だけ音声が聞こえる問題もよくあります。
たとえば、次のような状態です。
Aさん → Bさんの声は聞こえる
Bさん → Aさんの声は聞こえない
これは「片方向音声」と呼ばれることがあります。
原因としては、次のようなものがあります。
- 片方のマイク権限がない
- 片方の音声トラックが送信されていない
- NAT越えが片方向だけ失敗している
- SDP内のIPアドレスやcandidateが不正
- 片方のaudio要素だけ再生できていない
- SIPサーバーやメディアサーバー側でRTP経路が崩れている
確認するときは、送信側と受信側の両方でログを見ます。
const senders = peerConnection.getSenders();
const receivers = peerConnection.getReceivers();
console.log("senders", senders.map(s => s.track));
console.log("receivers", receivers.map(r => r.track));
送信側にaudio trackがない場合、自分の声は相手に届きません。
受信側にaudio trackがない場合、相手の声は聞こえません。
原因9:SDPの内容が期待通りではない
SIP.jsの通話では、SIPメッセージ内でSDPが交換されます。
SDPには、音声コーデック、メディア種別、ICE candidate、通信先情報などが含まれます。
音声が聞こえない場合、SDPの中身を確認することも重要です。
特に見るべきポイントは次の通りです。
m=audio
a=sendrecv
a=sendonly
a=recvonly
a=inactive
a=candidate
a=rtpmap
c=IN IP4 ...
たとえば、SDP内で次のようになっていると、音声の送受信に影響します。
a=inactive
これはメディアを送受信しない状態を意味します。
また、次のような状態も問題になります。
a=sendonly
こちらは送信のみで、受信しない設定です。
通常の双方向通話では、基本的に次のような状態が期待されます。
a=sendrecv
SDPの中身を見ることで、SIP.js側の問題なのか、SIPサーバーやPBX側の設定なのかを切り分けやすくなります。
原因10:音声コーデックが合っていない
WebRTCとSIPサーバー・PBX・メディアゲートウェイの間で、利用可能な音声コーデックが一致していない場合も音声が出ません。
WebRTCではOpusがよく使われますが、SIP/PBX側ではG.711、PCMU、PCMAなどが使われることもあります。
代表的な音声コーデックは次の通りです。
| コーデック | 説明 |
|---|---|
| Opus | WebRTCでよく使われる高品質な音声コーデック |
| PCMU | G.711 μ-law |
| PCMA | G.711 A-law |
| G.729 | 一部のSIP環境で使われるが、WebRTCブラウザ側ではそのまま扱えないことが多い |
ブラウザ同士のWebRTC通話であれば問題になりにくいですが、SIP.jsからSIPサーバー、PBX、電話網、VoIPゲートウェイへ接続する場合は注意が必要です。
特に、SIPサーバー側でWebRTC用の設定が正しく入っていないと、SDPのネゴシエーションに失敗したり、通話は成立しているのに音声が流れないことがあります。
原因11:SIPサーバー側がWebRTCに対応していない
SIP.jsはブラウザでSIPを扱うためのライブラリですが、接続先のSIPサーバー側もWebRTC向けの設定に対応している必要があります。
確認すべき代表的な項目は次の通りです。
- WebSocketまたはSecure WebSocketに対応しているか
- WSSで接続できるか
- WebRTC用のtransport設定があるか
- DTLS-SRTPに対応しているか
- ICEに対応しているか
- NAT越えの設定が正しいか
- WebRTC向けのコーデック設定があるか
従来のSIPソフトフォンでは接続できるのに、SIP.jsでは音声が出ない場合、SIPサーバー側がブラウザWebRTC前提の設定になっていない可能性があります。
原因12:WebSocket接続は成功しているが、メディア経路が別問題になっている
SIP.jsでは、SIPサーバーとの通信にWebSocketを使います。
ここで注意したいのは、WebSocket接続と音声通信は別物だという点です。
WebSocket:
SIPメッセージを送受信する経路
RTP / SRTP:
音声メディアを送受信する経路
そのため、WebSocketが接続済みでも音声が聞こえるとは限りません。
開発者ツールでWebSocketが正常に見えていると、「通信できているから音声も流れるはず」と考えがちですが、音声はWebRTCのICE経路で流れます。
つまり、見るべきログが違います。
| 確認対象 | 見る場所 |
|---|---|
| SIP接続 | WebSocketログ、SIPメッセージ |
| 認証 | REGISTER、401、200 OK |
| 通話開始 | INVITE、180、183、200 OK、ACK |
| 音声経路 | ICE state、candidate、RTCPeerConnection |
| 音声再生 | audio要素、MediaStream、track |
WebSocketが成功しているのに音声が出ない場合は、WebRTC側の確認に進みましょう。
原因13:ファイアウォールやネットワークでUDPが遮断されている
WebRTCのメディア通信ではUDPが使われることが多いです。
しかし、企業ネットワークや一部のWi-Fi環境では、UDP通信が制限されている場合があります。
この場合、SIPの接続はできても音声が流れません。
よくある環境は次の通りです。
- 会社のネットワーク
- ホテルや公共Wi-Fi
- 学校や病院のネットワーク
- VPN接続中
- セキュリティ製品が入っているPC
- ルーターの設定が厳しい環境
このような環境では、TURN over TCPやTURN over TLSが必要になることがあります。
STUNだけで済ませるのではなく、TURNサーバーを用意して、制限されたネットワークでも中継できるようにする必要があります。
原因14:ローカル音声とリモート音声を混同している
SIP.jsやWebRTCの実装では、ローカル音声とリモート音声を別々に扱う必要があります。
local stream:
自分のマイク音声
remote stream:
相手から届いた音声
ローカル音声を確認するためにaudio要素へ設定している場合、それはあくまで自分のマイク音声です。
相手の声が聞こえるかどうかは、remote streamを再生できているかで判断します。
実装上は、次のように分けて考えるとよいです。
const localStream = await navigator.mediaDevices.getUserMedia({ audio: true });
const remoteStream = new MediaStream();
そして、remote audioにはremoteStreamを設定します。
remoteAudio.srcObject = remoteStream;
localStreamをremoteAudioに設定してしまうと、自分の音声確認にはなりますが、相手の声の確認にはなりません。
原因15:通話状態だけを見て音声成功と判断している
SIP.jsでは、セッション状態がEstablishedになったからといって、音声通信が完全に成功したとは限りません。
Establishedは、SIPセッションとして成立したことを意味します。
しかし、WebRTCのメディア通信が成功しているかどうかは別途確認する必要があります。
最低限、以下を確認しましょう。
console.log(peerConnection.connectionState);
console.log(peerConnection.iceConnectionState);
console.log(peerConnection.iceGatheringState);
console.log(peerConnection.signalingState);
見るべき状態の例です。
| 項目 | 正常な目安 |
|---|---|
| connectionState | connected |
| iceConnectionState | connected / completed |
| iceGatheringState | complete |
| signalingState | stable |
SIPの状態とWebRTCの状態を分けてログに出すだけでも、原因特定がかなり楽になります。
Chromeのwebrtc-internalsで確認する
WebRTCの音声トラブル調査では、Chromeの chrome://webrtc-internals が非常に役立ちます。
Chromeで通話を開始したあと、別タブで以下を開きます。
chrome://webrtc-internals
ここでは、RTCPeerConnectionの状態や統計情報を確認できます。
見るべきポイントは次の通りです。
- ICE connection state
- candidate pair
- local candidate
- remote candidate
- bytes sent
- bytes received
- packets sent
- packets received
- audio trackの有無
- codec情報
- inbound-rtp
- outbound-rtp
特に重要なのは、bytes sent / bytes receivedです。
bytes sent が増えている
→ 自分の音声を送れている可能性が高い
bytes received が増えている
→ 相手の音声データを受信できている可能性が高い
もしbytes receivedが増えているのに音が聞こえない場合は、audio要素やブラウザの再生制限が疑わしいです。
逆に、bytes receivedが増えていない場合は、ICE、TURN、NAT、SDP、相手側の送信設定を疑います。
よくある切り分け手順
SIP.jsで音声が聞こえない場合は、次の順番で確認すると効率的です。
1. SIP接続が成功しているか
まず、SIPのREGISTERやINVITEが成功しているか確認します。
REGISTER → 200 OK
INVITE → 200 OK
ACK送信済み
ここで失敗している場合、音声以前にSIP接続の問題です。
2. マイクが取得できているか
次に、自分のマイク音声が取得できているか確認します。
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
console.log(stream.getAudioTracks());
audio trackが存在し、enabledがtrueであることを確認します。
stream.getAudioTracks().forEach(track => {
console.log(track.enabled, track.readyState);
});
3. PeerConnectionがconnectedになっているか
ICE接続状態を確認します。
console.log(peerConnection.iceConnectionState);
console.log(peerConnection.connectionState);
failed や disconnected であれば、ネットワーク経路の問題を疑います。
4. remote trackを受信しているか
相手の音声トラックを受信しているか確認します。
peerConnection.getReceivers().forEach(receiver => {
console.log(receiver.track?.kind, receiver.track?.readyState);
});
audio のreceiverが存在しない場合、相手から音声が届いていない可能性があります。
5. audio要素で再生できているか
最後に、受信した音声をaudio要素で再生できているか確認します。
console.log(remoteAudio.srcObject);
console.log(remoteAudio.paused);
console.log(remoteAudio.muted);
console.log(remoteAudio.volume);
ここで srcObject が空、muted がtrue、paused がtrueなら、再生側の問題です。
トラブル別の原因早見表
| 症状 | 疑うべき原因 |
|---|---|
| 自分の声が相手に届かない | マイク権限、getUserMedia失敗、送信trackなし |
| 相手の声が聞こえない | remote audio未設定、autoplay制限、受信trackなし |
| 双方とも無音 | ICE失敗、TURN未設定、SDP不一致 |
| ローカルでは動くが本番で動かない | HTTPS、証明書、NAT、TURN設定 |
| 社内ネットワークだけ無音 | UDP遮断、TURN over TCP/TLS未設定 |
| 通話は成立するが数秒後に無音 | ICE disconnected、ネットワーク切断 |
| 特定の相手とだけ無音 | コーデック、PBX設定、相手側NAT |
| 着信時だけ音が出ない | autoplay制限、ユーザー操作前のplay |
| 片方向だけ聞こえない | 片方の送信track、NAT、SDP方向属性 |
| bytes receivedは増えるが音が出ない | audio要素、ミュート、再生ブロック |
実装時に入れておきたいログ
音声トラブルを調査しやすくするために、開発時点でログを入れておくと便利です。
function logPeerConnectionState(peerConnection) {
peerConnection.onconnectionstatechange = () => {
console.log("connectionState:", peerConnection.connectionState);
};
peerConnection.oniceconnectionstatechange = () => {
console.log("iceConnectionState:", peerConnection.iceConnectionState);
};
peerConnection.onicegatheringstatechange = () => {
console.log("iceGatheringState:", peerConnection.iceGatheringState);
};
peerConnection.onsignalingstatechange = () => {
console.log("signalingState:", peerConnection.signalingState);
};
peerConnection.ontrack = (event) => {
console.log("ontrack:", event.track.kind, event.track.readyState);
};
}
また、通話確立後にsendersとreceiversを確認するログも有効です。
function logTracks(peerConnection) {
console.log("senders");
peerConnection.getSenders().forEach(sender => {
console.log(sender.track?.kind, sender.track?.enabled, sender.track?.readyState);
});
console.log("receivers");
peerConnection.getReceivers().forEach(receiver => {
console.log(receiver.track?.kind, receiver.track?.enabled, receiver.track?.readyState);
});
}
このようなログを入れておくと、音声が「送れていない」のか「受け取れていない」のか「再生できていない」のかを切り分けやすくなります。
SIP.jsで音声が聞こえないときのチェックリスト
最後に、確認項目をチェックリストとしてまとめます。
□ SIPサーバーへのREGISTERは成功しているか
□ INVITE、200 OK、ACKの流れは正常か
□ ブラウザでマイク権限が許可されているか
□ getUserMediaでaudio trackを取得できているか
□ HTTPSまたはlocalhostで動かしているか
□ remote audio要素にMediaStreamを設定しているか
□ audio要素がmutedになっていないか
□ audio要素のvolumeが0になっていないか
□ autoplay制限に引っかかっていないか
□ ユーザー操作後にaudio.play()しているか
□ ICE connection stateがconnected/completedになっているか
□ STUN/TURNサーバーを設定しているか
□ TURN認証情報が正しいか
□ SDPにm=audioが含まれているか
□ SDPがa=sendrecvになっているか
□ 音声コーデックがサーバー側と一致しているか
□ chrome://webrtc-internalsでbytes sent/receivedが増えているか
□ 社内ネットワークやVPNでUDPが遮断されていないか
□ SIPサーバー側がWebRTC向けに設定されているか
まとめ
SIP.jsで音声が聞こえない場合、原因はSIP.jsのコードだけにあるとは限りません。
SIP.jsはSIPのシグナリングを扱いますが、実際の音声通信はWebRTCで行われます。
そのため、音声トラブルでは次のように層を分けて確認することが重要です。
SIP接続の問題
WebSocket接続の問題
マイク権限の問題
WebRTC / ICEの問題
STUN / TURNの問題
SDP / コーデックの問題
audio要素の再生問題
ブラウザの自動再生制限
ネットワークやファイアウォールの問題
特に多い原因は、マイク権限、remote audio要素の設定漏れ、autoplay制限、ICE接続失敗、TURN未設定です。
「通話状態は成立しているのに音が聞こえない」という場合は、SIPの成功だけで判断せず、RTCPeerConnection、ICE state、audio track、audio要素、webrtc-internalsを順番に確認しましょう。
SIP.jsの音声トラブルは原因箇所が多いため、やみくもに設定を変えるよりも、「SIPは成功しているか」「音声トラックはあるか」「メディア通信は成立しているか」「ブラウザで再生できているか」という順番で切り分けるのが現実的です。

大阪のエンジニアが書いているブログ。



コメント