有一个特殊场景需要在一段音频里面嵌入字符串,类似声音领域的水印。
要求:
(一)尽量减少对原音频的影响;
(二)其他设备通过话筒、录音接收到音频后,能够从中提取出“水印”信息
我自己研究了一下,本来是打算在20000赫兹的地方嵌入信息,但是环境嘈杂会影响读取
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Audio Data Transmission</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
.container {
max-width: 600px;
margin: auto;
}
.section {
margin-bottom: 20px;
}
button {
padding: 10px 20px;
font-size: 16px;
}
#outputText {
white-space: pre-wrap;
border: 1px solid #ccc;
padding: 10px;
min-height: 100px;
}
canvas {
display: block;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1>Audio Data Transmission</h1>
<!-- Sender Section -->
<div class="section">
<h2>Send Data</h2>
<input type="text" id="inputText" placeholder="Enter text to send">
<button onclick="sendData()">Send</button>
<canvas id="senderSpectrum" width="600" height="100"></canvas>
</div>
<!-- Receiver Section -->
<div class="section">
<h2>Receive Data</h2>
<p id="outputText"></p>
<canvas id="receiverSpectrum" width="600" height="100"></canvas>
</div>
</div>
<script>
let isSending = false;
let receivedBits = '';
let receivedData = '';
function sendData() {
if (isSending) {
alert("Already sending data. Please wait.");
return;
}
isSending = true;
const text = document.getElementById('inputText').value;
const binaryData = stringToBinary(text) + '00000000'; // Add end of transmission marker
modulateAndPlay(binaryData);
}
function stringToBinary(str) {
return str.split('').map(char => char.charCodeAt(0).toString(2).padStart(8, '0')).join('');
}
function modulateAndPlay(binaryData) {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioCtx.createOscillator();
oscillator.type = 'sine';
oscillator.frequency.setValueAtTime(20000, audioCtx.currentTime);
const gainNode = audioCtx.createGain();
gainNode.gain.value = 0;
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
// Create an AnalyserNode to get frequency data
const analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;
gainNode.connect(analyser);
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
// Get the canvas context for drawing the spectrum
const canvas = document.getElementById('senderSpectrum');
const canvasCtx = canvas.getContext('2d');
let index = 0;
const interval = setInterval(() => {
if (index < binaryData.length) {
gainNode.gain.value = binaryData[index] === '1' ? 0.5 : 0;
index++;
} else {
clearInterval(interval);
oscillator.stop();
isSending = false;
}
}, 100); // Adjust the timing as needed
oscillator.start();
function drawSpectrum() {
analyser.getByteFrequencyData(dataArray);
canvasCtx.fillStyle = 'rgb(0, 0, 0)';
canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
const barWidth = canvas.width / bufferLength;
let barHeight;
for(let i = 0; i < bufferLength; i++) {
barHeight = dataArray[i];
canvasCtx.fillStyle = 'rgb(' + (barHeight + 100) + ',50,50)';
canvasCtx.fillRect(i * barWidth, canvas.height - barHeight, barWidth, barHeight);
}
requestAnimationFrame(drawSpectrum);
}
drawSpectrum();
}
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const source = audioCtx.createMediaStreamSource(stream);
const analyser = audioCtx.createAnalyser();
source.connect(analyser);
analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
// Get the canvas context for drawing the spectrum
const canvas = document.getElementById('receiverSpectrum');
const canvasCtx = canvas.getContext('2d');
function detectSignal() {
analyser.getByteFrequencyData(dataArray);
const targetIndex = Math.round(20000 * (bufferLength / audioCtx.sampleRate));
const amplitude = dataArray[targetIndex];
if (amplitude > 128) { // Threshold for detecting a '1'
receivedBits += '1';
} else {
receivedBits += '0';
}
if (receivedBits.length >= 8) {
const byte = receivedBits.slice(0, 8);
receivedBits = receivedBits.slice(8);
const charCode = parseInt(byte, 2);
if (charCode === 0) {
// End of transmission marker
const receivedText = binaryToString(receivedData);
document.getElementById('outputText').innerText = receivedText;
receivedData = '';
} else {
receivedData += String.fromCharCode(charCode);
}
}
// Draw the spectrum
canvasCtx.fillStyle = 'rgb(0, 0, 0)';
canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
const barWidth = canvas.width / bufferLength;
for(let i = 0; i < bufferLength; i++) {
barHeight = dataArray[i];
canvasCtx.fillStyle = 'rgb(' + (barHeight + 100) + ',50,50)';
canvasCtx.fillRect(i * barWidth, canvas.height - barHeight, barWidth, barHeight);
}
requestAnimationFrame(detectSignal);
}
detectSignal();
})
.catch(err => console.error("The following error occurred: " + err));
function binaryToString(binary) {
if (!binary || binary.length % 8 !== 0) {
console.warn("Invalid binary string length:", binary.length);
return '';
}
return binary.match(/.{8}/g).map(byte => String.fromCharCode(parseInt(byte, 2))).join('');
}
</script>
</body>
</html>
```