The REST API enables two-way audio communication between third-party web browsers and supported IP cameras. You can send audio streams or pre-recorded audio files to a camera's speaker, as well as receive real-time audio streams from a camera's microphone.
Prerequisites
Before setting up two-way audio, ensure that:
The target camera supports two-way audio and is fully compatible with the VMS.
To verify support, search for the camera on the IPVD list and check that the 2-way audio tag is enabled.Your user account has the required access rights to transmit and receive two-way audio on the target device.
Send audio to a camera
To transmit audio from a browser to a camera speaker, establish a WebSocket connection to the audio backchannel endpoint:
ws://<server_ip>:<server_port>/api/http_audio?camera_id=<id>&format=<audio_sample_format>&sample_rate=<audio_sample_rate>&channels=<audio_channels_count>Path parameters
Replace the placeholder values in your connection URL as follows:
<server_ip>— The IP address or hostname of your Nx Witness server.<server_port>— The active port number of your Nx Witness server (for example,7001).<id>— The unique Camera ID of the target device.
Audio formatting parameters
When transmitting raw audio formats (such as PCM), append the following parameters to your query:
format— The PCM sample encoding format. Supported values includeu8,s16be,s16le,s32be,s32le,f32be,f32le,f64be, andf64le.sample_rate— The audio sampling rate expressed as an integer (for example,44100).channels— The channel configuration. Use1for mono or2for stereo.
NOTE: Omit the format, sample_rate, and channels parameters if you are streaming wrapped media containers such as MP4, WAV, WebM, or AAC. The server automatically detects container formats upon connection. |
Request example
ws://localhost:7001/api/http_audio?camera_id=2a4717bb-1d3e-4878-a28b-af4eaedbfb89&format=f32le&sample_rate=44100&channels=1Receive audio from a camera
To stream real-time audio from a camera's microphone back to the browser, send an HTTP request to the WebM media endpoint:
http://<server_ip>:<server_port>/rest/v4/devices/<camera_id>/media.webm?audioOnly=trueCode example: HTML and JavaScript integration
The following sample demonstrates how to implement live microphone input, audio file uploading, and camera audio playback in a single web page.
Test the demo
Save the HTML sample code below as a local file (for example,
two-way-audio.html).Open the file in a modern web browser.
Enter your Server (host:port), User, Password, and Camera ID.
Select one of the actions:
Click Start mic to stream live audio from your computer to the camera. Allow browser microphone access when prompted.
Click Connect & upload after selecting a local audio file to play it through the camera speaker.
Click Load camera audio to listen to the live audio stream coming from the camera.
Sample Code
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Nx Two-Way Audio Demo</title>
</head>
<body>
Server (host:port): <input type="text" id="server" size="40" value="localhost:7001"><br>
User: <input type="text" id="user" size="40" value="admin"><br>
Password: <input type="text" id="pass" size="40"><br>
Camera ID: <input type="text" id="camera_id" size="40"><br>
<h2>Microphone</h2>
<button onclick="startMic()">Start Mic</button>
<button onclick="stopMic()">Stop Mic</button>
<h2>Send an audio file</h2>
<input type="file" id="filename">
<button onclick="sendFile()">Connect & upload</button>
<h2>Listen to camera</h2>
<button onclick="listen()">Load Camera Audio</button><br>
<audio id="cameraAudio" controls></audio>
<script>
let ws = null, audioContext = null, micStream = null, workletNode = null;
let sessionToken = null;
const WORKLET_CODE = `
class MicSender extends AudioWorkletProcessor {
constructor() { super(); this._buf = new Float32Array(4096); this._n = 0; }
process(inputs) {
const ch = inputs[0] && inputs[0][0];
if (ch) {
for (let i = 0; i < ch.length; i++) {
this._buf[this._n++] = ch[i];
if (this._n === this._buf.length) {
this.port.postMessage(this._buf.slice(0));
this._n = 0;
}
}
}
return true;
}
}
registerProcessor('mic-sender', MicSender);
`;
function base(scheme) {
// Token/bearer auth (login + _sessionToken) is only accepted over TLS,
// so this always uses wss/https — there is no plain-http mode.
const host = document.getElementById('server').value;
return scheme + "s://" + host;
}
function camId() { return encodeURIComponent(document.getElementById('camera_id').value); }
async function getToken() {
if (sessionToken) return sessionToken;
const resp = await fetch(base("http") + "/rest/v4/login/sessions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username: document.getElementById('user').value,
password: document.getElementById('pass').value,
durationS: 3600
})
});
if (!resp.ok) throw new Error("Login failed: HTTP " + resp.status);
const data = await resp.json();
sessionToken = data.token;
console.log("Logged in; Token expires in", data.expiresInS, "s");
return sessionToken;
}
function openSocket(url, onopen) {
console.log("connecting to", url.replace(/_sessionToken=[^&]+/, "_sessionToken=***"));
ws = new WebSocket(url);
ws.binaryType = "arraybuffer";
ws.onopen = () => { console.log("Connected"); if (onopen) onopen(); };
ws.onclose = (e) => console.log("Closed", e.code, e.reason);
ws.onerror = () => console.log("WebSocket error");
}
async function startMic() {
let tok;
try { tok = await getToken(); }
catch (e) { console.log("Authentication Failed:", e); return; }
let stream;
try { stream = await navigator.mediaDevices.getUserMedia({ audio: true }); }
catch (e) { console.log("Microphone Error:", e); return; }
micStream = stream;
audioContext = new AudioContext();
await audioContext.resume();
const rate = audioContext.sampleRate;
const moduleUrl = URL.createObjectURL(new Blob([WORKLET_CODE], { type: "application/javascript" }));
await audioContext.audioWorklet.addModule(moduleUrl);
URL.revokeObjectURL(moduleUrl);
const url = base("ws") + "/api/http_audio?camera_id=" + camId()
+ "&_sessionToken=" + encodeURIComponent(tok)
+ "&format=f32le&sample_rate=" + rate + "&channels=1";
openSocket(url, () => {
const source = audioContext.createMediaStreamSource(stream);
workletNode = new AudioWorkletNode(audioContext, "mic-sender");
workletNode.port.onmessage = (e) => {
if (ws && ws.readyState === WebSocket.OPEN) ws.send(e.data);
};
source.connect(workletNode);
const mute = audioContext.createGain();
mute.gain.value = 0;
workletNode.connect(mute).connect(audioContext.destination);
});
}
function stopMic() {
if (workletNode) { workletNode.disconnect(); workletNode = null; }
if (micStream) { micStream.getTracks().forEach(t => t.stop()); micStream = null; }
if (audioContext) { audioContext.close(); audioContext = null; }
if (ws) { ws.close(); ws = null; }
}
async function sendFile() {
const file = document.getElementById('filename').files[0];
if (!file) { console.log("Choose a file first"); return; }
let tok;
try { tok = await getToken(); }
catch (e) { console.log("Authentication Failed:", e); return; }
const url = base("ws") + "/api/http_audio?camera_id=" + camId()
+ "&_sessionToken=" + encodeURIComponent(tok);
openSocket(url, () => {
const reader = new FileReader();
reader.onload = (e) => {
ws.send(e.target.result);
// "Upload complete" = The browser has flushed every byte to the network
// (bufferedAmount drains to 0). Only then close, which is the end-of-file
// signal that makes the server play the file.
const timer = setInterval(() => {
if (ws.readyState !== WebSocket.OPEN) { clearInterval(timer); return; } // errored/closed early
if (ws.bufferedAmount === 0) {
clearInterval(timer);
console.log("upload complete:", e.target.result.byteLength, "bytes");
ws.close();
}
}, 50);
};
reader.readAsArrayBuffer(file);
});
}
async function listen() {
let tok;
try { tok = await getToken(); }
catch (e) { console.log("Authentication failed:", e); return; }
const url = base("http") + "/rest/v4/devices/" + camId()
+ "/media.webm?audioOnly=true&_sessionToken=" + encodeURIComponent(tok);
const audio = document.getElementById('cameraAudio');
audio.src = url;
audio.play().catch(e => console.log("Play() was blocked:", e));
}
</script>
</body>
</html>
Comments
0 comments
Article is closed for comments.