Compare commits
4 Commits
880fd39ea9
...
2fffb905e3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2fffb905e3 | ||
![]() |
0da0701d34 | ||
![]() |
0408433fc3 | ||
![]() |
1acf98ef54 |
81
blarf.js
81
blarf.js
@ -31,6 +31,7 @@
|
|||||||
var DebugSine = 0
|
var DebugSine = 0
|
||||||
|
|
||||||
AudCtx = new AudioContext({sampleRate: hz})
|
AudCtx = new AudioContext({sampleRate: hz})
|
||||||
|
AudCtx.suspend()
|
||||||
|
|
||||||
if(AudCtx.audioWorklet) {
|
if(AudCtx.audioWorklet) {
|
||||||
AudCtx.audioWorklet.addModule("rawpcmworklet.js").then(function() {
|
AudCtx.audioWorklet.addModule("rawpcmworklet.js").then(function() {
|
||||||
@ -45,14 +46,6 @@
|
|||||||
var outL = e.outputBuffer.getChannelData(0)
|
var outL = e.outputBuffer.getChannelData(0)
|
||||||
var outR = channels > 1 ? e.outputBuffer.getChannelData(1) : null
|
var outR = channels > 1 ? e.outputBuffer.getChannelData(1) : null
|
||||||
|
|
||||||
/*for(var i = 0; i < outL.length; i++) {
|
|
||||||
outL[i] = Math.sin(440 * 2 * 3.14159 * (DebugSine / AudHz))
|
|
||||||
DebugSine++
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioQueue = []
|
|
||||||
return*/
|
|
||||||
|
|
||||||
var leftToWrite = outL.length
|
var leftToWrite = outL.length
|
||||||
var offset = 0
|
var offset = 0
|
||||||
|
|
||||||
@ -110,6 +103,21 @@
|
|||||||
|
|
||||||
var RenderStartTime, VideoStartTime
|
var RenderStartTime, VideoStartTime
|
||||||
|
|
||||||
|
function crop_audio_queue(durationToRemove) {
|
||||||
|
while(AudioQueue.length && durationToRemove) {
|
||||||
|
var amount = Math.min(durationToRemove, AudioQueue[0].left.length)
|
||||||
|
|
||||||
|
AudioQueue[0].left = AudioQueue[0].left.subarray(amount)
|
||||||
|
AudioQueue[0].right = AudioQueue[0].right.subarray(amount)
|
||||||
|
|
||||||
|
if(AudioQueue[0].left.length == 0) {
|
||||||
|
AudioQueue.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
durationToRemove -= amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var Statistics = {}
|
var Statistics = {}
|
||||||
var TheWorker = new Worker("blarfwork.js")
|
var TheWorker = new Worker("blarfwork.js")
|
||||||
TheWorker.onmessage = function(e) {
|
TheWorker.onmessage = function(e) {
|
||||||
@ -122,25 +130,20 @@
|
|||||||
// Audio may be loaded but it might not play because of autoplay permissions
|
// Audio may be loaded but it might not play because of autoplay permissions
|
||||||
// In this case the audio queue will fill up and cause ever-increasing AV desync
|
// In this case the audio queue will fill up and cause ever-increasing AV desync
|
||||||
|
|
||||||
// To prevent this, manually crop the audio to the duration in the video queue
|
|
||||||
|
|
||||||
if(AudCtx && AudCtx.state != "running") {
|
if(AudCtx && AudCtx.state != "running") {
|
||||||
var durationInAudioQueue = AudioQueue.length ? AudioQueue.reduce((acc, el) => acc + el.left.length, 0) : 0
|
var durationInAudioQueue = AudioQueue.length ? AudioQueue.reduce((acc, el) => acc + el.left.length, 0) : 0
|
||||||
|
|
||||||
var durationToRemove = Math.max(durationInAudioQueue - (VideoQueue.length ? (VideoQueue[VideoQueue.length - 1].t - VideoQueue[0].t) : 0) * AudHz / 1000, 0)
|
//if(AudWorklet) {
|
||||||
|
// // With audio worklets, crop to 1024 samples max to prevent ring buffer overflow
|
||||||
while(AudioQueue.length && durationToRemove) {
|
//
|
||||||
var amount = Math.min(durationToRemove, AudioQueue[0].left.length)
|
// crop_audio_queue(Math.max(durationInAudioQueue - 1024, 0))
|
||||||
|
//} else {
|
||||||
AudioQueue[0].left = AudioQueue[0].left.subarray(amount)
|
// // Without audio worklets we use a FIFO and can crop to the duration in the video queue
|
||||||
AudioQueue[0].right = AudioQueue[0].left.subarray(amount)
|
//
|
||||||
|
var durationToRemove = Math.max(durationInAudioQueue - (VideoQueue.length ? (VideoQueue[VideoQueue.length - 1].t - VideoQueue[0].t) : 0) * AudHz / 1000, 0)
|
||||||
if(AudioQueue[0].left.length == 0) {
|
//
|
||||||
AudioQueue.shift()
|
crop_audio_queue(durationToRemove)
|
||||||
}
|
//}
|
||||||
|
|
||||||
durationToRemove -= amount
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +161,7 @@
|
|||||||
text = text + k + ":" + (Math.floor(100 * Statistics[k].sum / Statistics[k].count) / 100) + ","
|
text = text + k + ":" + (Math.floor(100 * Statistics[k].sum / Statistics[k].count) / 100) + ","
|
||||||
}
|
}
|
||||||
stats.innerText = text*/
|
stats.innerText = text*/
|
||||||
stats.innerHTML = (VideoQueue.length ? (VideoQueue[VideoQueue.length - 1].t - VideoQueue[0].t) : "0") + "v" + (AudioQueue.reduce(function(acc, obj) {return acc + obj.left.length * 1000 / AudHz}, 0)|0) + "a"
|
stats.innerText = (VideoQueue.length ? (VideoQueue[VideoQueue.length - 1].t - VideoQueue[0].t) : "0") + "v" + (AudioQueue.reduce(function(acc, obj) {return acc + obj.left.length * 1000 / AudHz}, 0)|0) + "a"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,6 +396,27 @@
|
|||||||
ebml.ondata = matr.ondata.bind(matr)
|
ebml.ondata = matr.ondata.bind(matr)
|
||||||
ebml.onexit = matr.onexit.bind(matr)
|
ebml.onexit = matr.onexit.bind(matr)
|
||||||
|
|
||||||
|
function merge_audio_queue() {
|
||||||
|
var s = 0
|
||||||
|
|
||||||
|
for(var i = 0; i < AudioQueue.length; i++) {
|
||||||
|
s += AudioQueue[i].left.length
|
||||||
|
}
|
||||||
|
|
||||||
|
var L = new Float32Array(s)
|
||||||
|
var R = new Float32Array(s)
|
||||||
|
|
||||||
|
s = 0
|
||||||
|
|
||||||
|
for(var i = 0; i < AudioQueue.length; i++) {
|
||||||
|
L.set(AudioQueue[i].left, s)
|
||||||
|
R.set(AudioQueue[i].right, s)
|
||||||
|
s += AudioQueue[i].left.length
|
||||||
|
}
|
||||||
|
|
||||||
|
return {msg: "data", left: L, right: R}
|
||||||
|
}
|
||||||
|
|
||||||
function reconnect_ws() {
|
function reconnect_ws() {
|
||||||
var ws = new WebSocket(BlarfEl.getAttribute("data-target"))
|
var ws = new WebSocket(BlarfEl.getAttribute("data-target"))
|
||||||
ws.binaryType = "arraybuffer"
|
ws.binaryType = "arraybuffer"
|
||||||
@ -402,9 +426,14 @@
|
|||||||
|
|
||||||
// It would make more sense for this to be in `render` but we need the guarantee that this will run when the tab is out of focus
|
// It would make more sense for this to be in `render` but we need the guarantee that this will run when the tab is out of focus
|
||||||
if(AudCtx.state == "running" && AudWorklet && AudioQueue.length) {
|
if(AudCtx.state == "running" && AudWorklet && AudioQueue.length) {
|
||||||
AudWorklet.port.postMessage({msg: "data", audio: AudioQueue})
|
AudWorklet.port.postMessage(merge_audio_queue())
|
||||||
AudioQueue.length = 0
|
AudioQueue.length = 0
|
||||||
}
|
}
|
||||||
|
if(VideoQueue.length) {
|
||||||
|
while(document.timeline.currentTime - VideoQueue[0].t > 5000) {
|
||||||
|
VideoQueue.shift()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ws.onclose = function(ev) {
|
ws.onclose = function(ev) {
|
||||||
setTimeout(reconnect_ws, 5000)
|
setTimeout(reconnect_ws, 5000)
|
||||||
|
13
main2.c
13
main2.c
@ -101,15 +101,18 @@ static void consume(Client *cli, size_t n) {
|
|||||||
cli->len -= n;
|
cli->len -= n;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void transmit(Client *cli, const char *buf, size_t sz) {
|
static int transmit(Client *cli, const char *buf, size_t sz) {
|
||||||
while(sz) {
|
while(sz) {
|
||||||
ssize_t s = send(cli->fd, buf, sz, 0);
|
ssize_t s = send(cli->fd, buf, sz, MSG_NOSIGNAL);
|
||||||
|
|
||||||
if(s >= 0) {
|
if(s >= 0) {
|
||||||
buf += s;
|
buf += s;
|
||||||
sz -= s;
|
sz -= s;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void transmit_all(const char *buf, size_t sz) {
|
static void transmit_all(const char *buf, size_t sz) {
|
||||||
@ -306,7 +309,9 @@ static int handle(Client *cli) {
|
|||||||
size_t rsize = cli->len;
|
size_t rsize = cli->len;
|
||||||
int pret = phr_decode_chunked(&cli->chudec, cli->buf, &rsize);
|
int pret = phr_decode_chunked(&cli->chudec, cli->buf, &rsize);
|
||||||
|
|
||||||
if(pret == -1) return 0;
|
if(pret == -1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
stream_step(cli->buf, rsize);
|
stream_step(cli->buf, rsize);
|
||||||
|
|
||||||
@ -367,6 +372,8 @@ static int handle(Client *cli) {
|
|||||||
cli->ws.incoming = realloc(cli->ws.incoming, cli->ws.incomingSz + payloadSz);
|
cli->ws.incoming = realloc(cli->ws.incoming, cli->ws.incomingSz + payloadSz);
|
||||||
memcpy(cli->ws.incoming + cli->ws.incomingSz, cli->buf + i + 4, payloadSz);
|
memcpy(cli->ws.incoming + cli->ws.incomingSz, cli->buf + i + 4, payloadSz);
|
||||||
|
|
||||||
|
consume(cli, i + 4 + payloadSz);
|
||||||
|
|
||||||
if(fin) {
|
if(fin) {
|
||||||
receive_ws(cli);
|
receive_ws(cli);
|
||||||
|
|
||||||
|
@ -1,28 +1,50 @@
|
|||||||
// To explain succinctly, the people who designed AudioWorklet and
|
// To explain succinctly, the people who designed AudioWorklet and
|
||||||
// deprecated ScriptProcessorNode are retarded and we need a worklet
|
// deprecated ScriptProcessorNode are retarded and we need a worklet
|
||||||
// that does basically nothing to get glitchless audio.
|
// that does basically nothing
|
||||||
|
|
||||||
|
// Must be careful to create as little garbage as possible otherwise
|
||||||
|
// even this will produce cracks/pops/clicks/blips.
|
||||||
|
// It was like this on Firefox; Chromium managed.
|
||||||
|
|
||||||
class RawPCMWorklet extends AudioWorkletProcessor {
|
class RawPCMWorklet extends AudioWorkletProcessor {
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.left = new Float32Array()
|
this.ringL = new Float32Array(65536)
|
||||||
this.right = new Float32Array()
|
this.ringR = new Float32Array(65536)
|
||||||
|
this.ringWrite = 0
|
||||||
|
this.ringRead = 0
|
||||||
|
|
||||||
|
for(var z = 0; z < 65536; z++) {
|
||||||
|
this.ringL[z] = Math.sin(z / 128 * 2 * Math.PI) * 0.3
|
||||||
|
}
|
||||||
|
|
||||||
this.port.onmessage = (event) => {
|
this.port.onmessage = (event) => {
|
||||||
var newaudioframes = event.data.audio
|
var newaudioframes = event.data
|
||||||
|
|
||||||
for(var i = 0; i < newaudioframes.length; i++) {
|
var newlen = newaudioframes.left.length
|
||||||
var newleft = new Float32Array(this.left.length + newaudioframes[i].left.length)
|
|
||||||
newleft.set(this.left, 0)
|
|
||||||
newleft.set(newaudioframes[i].left, this.left.length)
|
|
||||||
this.left = newleft
|
|
||||||
|
|
||||||
var newright = new Float32Array(this.right.length + newaudioframes[i].right.length)
|
if(newaudioframes.left.length > this.ringL.length) {
|
||||||
newright.set(this.right, 0)
|
newaudioframes.left = newaudioframes.left.slice(newaudioframes.left.length - this.ringL.length)
|
||||||
newright.set(newaudioframes[i].right, this.right.length)
|
newaudioframes.right = newaudioframes.right.slice(newaudioframes.right.length - this.ringL.length)
|
||||||
this.right = newright
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.ringWrite % this.ringL.length + newaudioframes.left.length <= this.ringL.length) {
|
||||||
|
this.ringL.set(newaudioframes.left, this.ringWrite % this.ringL.length)
|
||||||
|
this.ringR.set(newaudioframes.right, this.ringWrite % this.ringL.length)
|
||||||
|
} else {
|
||||||
|
var boundary = this.ringL.length - this.ringWrite % this.ringL.length
|
||||||
|
|
||||||
|
this.ringL.set(newaudioframes.left.slice(0, boundary), this.ringWrite % this.ringL.length)
|
||||||
|
this.ringL.set(newaudioframes.left.slice(boundary), 0)
|
||||||
|
|
||||||
|
this.ringR.set(newaudioframes.right.slice(0, boundary), this.ringWrite % this.ringL.length)
|
||||||
|
this.ringR.set(newaudioframes.right.slice(boundary), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ringWrite += newlen
|
||||||
|
|
||||||
|
console.log(this.ringWrite - this.ringRead)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,15 +54,35 @@ class RawPCMWorklet extends AudioWorkletProcessor {
|
|||||||
var left = output[0]
|
var left = output[0]
|
||||||
var right = output[1]
|
var right = output[1]
|
||||||
|
|
||||||
var available = Math.min(left.length, this.left.length)
|
/*if(this.ringWrite < 16384) {
|
||||||
|
return true
|
||||||
|
}*/
|
||||||
|
|
||||||
left.set(this.left.slice(0, available))
|
var available = Math.min(left.length, Math.max(0, this.ringWrite - this.ringRead))
|
||||||
right.set(this.right.slice(0, available))
|
|
||||||
|
|
||||||
this.left = this.left.slice(available)
|
if(this.ringRead % this.ringL.length + available <= this.ringL.length) {
|
||||||
this.right = this.right.slice(available)
|
left.set(this.ringL.slice(this.ringRead % this.ringL.length, this.ringRead % this.ringL.length + available))
|
||||||
|
right.set(this.ringR.slice(this.ringRead % this.ringL.length, this.ringRead % this.ringL.length + available))
|
||||||
|
} else {
|
||||||
|
left.set(this.ringL.slice(this.ringRead % this.ringL.length))
|
||||||
|
right.set(this.ringR.slice(this.ringRead % this.ringL.length))
|
||||||
|
|
||||||
return true;
|
var boundary = this.ringL.length - this.ringRead % this.ringL.length
|
||||||
|
|
||||||
|
left.set(this.ringL.slice(0, available - boundary), boundary)
|
||||||
|
right.set(this.ringR.slice(0, available - boundary), boundary)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ringRead += left.length
|
||||||
|
|
||||||
|
/*for(var s = 0; s < available; s++) {
|
||||||
|
var sw = Math.sin((this.debug + s) / 48000 * 440 * 2 * 3.1415926) * 0.3
|
||||||
|
left[s] = sw
|
||||||
|
right[s] = sw
|
||||||
|
}
|
||||||
|
this.debug += available*/
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user