You've already forked fm-dx-webserver
mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-02-26 22:13:53 +01:00
Merge pull request #134 from AmateurAudioDude/fixes/v1.3.6.1-ffmpeg-audio-settings
Fixes/v1.3.6.1 ffmpeg audio settings
This commit is contained in:
@@ -46,7 +46,9 @@ let serverConfig = {
|
|||||||
audioBitrate: "128k",
|
audioBitrate: "128k",
|
||||||
audioBoost: false,
|
audioBoost: false,
|
||||||
softwareMode: false,
|
softwareMode: false,
|
||||||
startupVolume: "0.95"
|
startupVolume: "0.95",
|
||||||
|
ffmpeg: false,
|
||||||
|
samplerateOffset: "0"
|
||||||
},
|
},
|
||||||
identification: {
|
identification: {
|
||||||
token: null,
|
token: null,
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
const ffmpegStaticPath = require('ffmpeg-static');
|
const checkFFmpeg = require('./checkFFmpeg');
|
||||||
const {serverConfig} = require('../server_config')
|
const {serverConfig} = require('../server_config');
|
||||||
|
|
||||||
|
let ffmpegStaticPath;
|
||||||
|
|
||||||
|
function runStream() {
|
||||||
/*
|
/*
|
||||||
Stdin streamer is part of 3LAS (Low Latency Live Audio Streaming)
|
Stdin streamer is part of 3LAS (Low Latency Live Audio Streaming)
|
||||||
https://github.com/JoJoBond/3LAS
|
https://github.com/JoJoBond/3LAS
|
||||||
@@ -243,7 +246,7 @@ class FallbackProviderMp3 extends AFallbackProvider {
|
|||||||
return [
|
return [
|
||||||
"-fflags", "+nobuffer+flush_packets", "-flags", "low_delay", "-rtbufsize", "32", "-probesize", "32",
|
"-fflags", "+nobuffer+flush_packets", "-flags", "low_delay", "-rtbufsize", "32", "-probesize", "32",
|
||||||
"-f", "s16le",
|
"-f", "s16le",
|
||||||
"-ar", this.Server.SampleRate.toString(),
|
"-ar", Number(this.Server.SampleRate.toString()) + Number(serverConfig.audio.samplerateOffset),
|
||||||
"-ac", this.Server.Channels.toString(),
|
"-ac", this.Server.Channels.toString(),
|
||||||
"-i", "pipe:0",
|
"-i", "pipe:0",
|
||||||
"-c:a", "libmp3lame",
|
"-c:a", "libmp3lame",
|
||||||
@@ -274,7 +277,7 @@ class FallbackProviderWav extends AFallbackProvider {
|
|||||||
return [
|
return [
|
||||||
"-fflags", "+nobuffer+flush_packets", "-flags", "low_delay", "-rtbufsize", "32", "-probesize", "32",
|
"-fflags", "+nobuffer+flush_packets", "-flags", "low_delay", "-rtbufsize", "32", "-probesize", "32",
|
||||||
"-f", "s16le",
|
"-f", "s16le",
|
||||||
"-ar", this.Server.SampleRate.toString(),
|
"-ar", Number(this.Server.SampleRate.toString()) + Number(serverConfig.audio.samplerateOffset),
|
||||||
"-ac", this.Server.Channels.toString(),
|
"-ac", this.Server.Channels.toString(),
|
||||||
"-i", "pipe:0",
|
"-i", "pipe:0",
|
||||||
"-c:a", "pcm_s16le",
|
"-c:a", "pcm_s16le",
|
||||||
@@ -327,4 +330,10 @@ for (let i = 2; i < (process.argv.length - 1); i += 2) {
|
|||||||
}
|
}
|
||||||
const Server = StreamServer.Create(Options);
|
const Server = StreamServer.Create(Options);
|
||||||
Server.Run();
|
Server.Run();
|
||||||
//# sourceMappingURL=3las.server.js.map
|
//# sourceMappingURL=3las.server.js.map
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFFmpeg().then((ffmpegResult) => {
|
||||||
|
ffmpegStaticPath = ffmpegResult;
|
||||||
|
runStream();
|
||||||
|
});
|
||||||
23
server/stream/checkFFmpeg.js
Normal file
23
server/stream/checkFFmpeg.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const { spawn } = require('child_process');
|
||||||
|
|
||||||
|
function checkFFmpeg() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const checkFFmpegProcess = spawn('ffmpeg', ['-version'], {
|
||||||
|
stdio: ['ignore', 'ignore', 'ignore'],
|
||||||
|
});
|
||||||
|
|
||||||
|
checkFFmpegProcess.on('error', () => {
|
||||||
|
resolve(require('ffmpeg-static'));
|
||||||
|
});
|
||||||
|
|
||||||
|
checkFFmpegProcess.on('exit', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve('ffmpeg');
|
||||||
|
} else {
|
||||||
|
resolve(require('ffmpeg-static'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = checkFFmpeg;
|
||||||
@@ -1,59 +1,134 @@
|
|||||||
const { spawn } = require('child_process');
|
const { spawn, execSync } = require('child_process');
|
||||||
const ffmpeg = require('ffmpeg-static');
|
const { configName, serverConfig, configUpdate, configSave, configExists } = require('../server_config');
|
||||||
const { configName, serverConfig, configUpdate, configSave, configExists } = require('../server_config');
|
const { logDebug, logError, logInfo, logWarn, logFfmpeg } = require('../console');
|
||||||
const { logDebug, logError, logInfo, logWarn, logFfmpeg } = require('../console');
|
const checkFFmpeg = require('./checkFFmpeg');
|
||||||
|
|
||||||
function enableAudioStream() {
|
let ffmpeg, ffmpegCommand, ffmpegParams;
|
||||||
var ffmpegParams, ffmpegCommand;
|
|
||||||
serverConfig.webserver.webserverPort = Number(serverConfig.webserver.webserverPort);
|
function checkAudioUtilities() {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
const flags = `-fflags +nobuffer+flush_packets -flags low_delay -rtbufsize 6192 -probesize 32`;
|
try {
|
||||||
const codec = `-acodec pcm_s16le -ar 48000 -ac ${serverConfig.audio.audioChannels}`;
|
execSync('which rec');
|
||||||
const output = `${serverConfig.audio.audioBoost == true ? '-af "volume=3.5"' : ''} -f s16le -fflags +nobuffer+flush_packets -packetsize 384 -flush_packets 1 -bufsize 960`;
|
//console.log('[Audio Utility Check] SoX ("rec") found.');
|
||||||
|
} catch (error) {
|
||||||
if (process.platform === 'win32') {
|
logError('[Audio Utility Check] Error: SoX ("rec") not found. Please install SoX (e.g., using `brew install sox`).');
|
||||||
// Windows
|
process.exit(1); // Exit the process with an error code
|
||||||
ffmpegCommand = "\"" + ffmpeg.replace(/\\/g, '\\\\') + "\"";
|
}
|
||||||
ffmpegParams = `${flags} -f dshow -audio_buffer_size 200 -i audio="${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node server/stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
|
} else if (process.platform === 'linux') {
|
||||||
} else {
|
try {
|
||||||
// Linux
|
execSync('which arecord');
|
||||||
ffmpegCommand = 'ffmpeg';
|
//console.log('[Audio Utility Check] ALSA ("arecord") found.');
|
||||||
ffmpegParams = `${flags} -f alsa -i "${serverConfig.audio.softwareMode && serverConfig.audio.softwareMode == true ? 'plug' : ''}${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node server/stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
|
} catch (error) {
|
||||||
}
|
logError('[Audio Utility Check] Error: ALSA ("arecord") not found. Please ensure ALSA utilities are installed (e.g., using `sudo apt-get install alsa-utils` or `sudo yum install alsa-utils`).');
|
||||||
|
process.exit(1); // Exit the process with an error code
|
||||||
logInfo("Trying to start audio stream on device: \x1b[35m" + serverConfig.audio.audioDevice);
|
}
|
||||||
logInfo(`Using internal audio network port ${serverConfig.webserver.webserverPort + 10}.`);
|
} else {
|
||||||
|
//console.log(`[Audio Utility Check] Platform "${process.platform}" does not require explicit checks for rec or arecord.`);
|
||||||
// If an audio device is configured, start the stream
|
}
|
||||||
if(serverConfig.audio.audioDevice.length > 2) {
|
}
|
||||||
let startupSuccess = false;
|
|
||||||
const childProcess = spawn(ffmpegCommand, [ffmpegParams], { shell: true });
|
function buildCommand() {
|
||||||
|
// Common audio options for FFmpeg
|
||||||
childProcess.stdout.on('data', (data) => {
|
const baseOptions = {
|
||||||
logFfmpeg(`stdout: ${data}`);
|
flags: '-fflags +nobuffer+flush_packets -flags low_delay -rtbufsize 6192 -probesize 32',
|
||||||
});
|
codec: `-acodec pcm_s16le -ar 48000 -ac ${serverConfig.audio.audioChannels}`,
|
||||||
|
output: '-f s16le -fflags +nobuffer+flush_packets -packetsize 384 -flush_packets 1 -bufsize 960'
|
||||||
childProcess.stderr.on('data', (data) => {
|
};
|
||||||
logFfmpeg(`stderr: ${data}`);
|
|
||||||
if(data.includes('I/O error')) {
|
if (process.platform === 'win32') {
|
||||||
logError('Audio device \x1b[35m' + serverConfig.audio.audioDevice + '\x1b[0m failed to start. Start server with the command \x1b[33mnode . --ffmpegdebug \x1b[0mfor more info.');
|
// Windows: ffmpeg using dshow
|
||||||
}
|
logInfo('[Audio Stream] Platform: Windows (win32). Using "dshow" input.');
|
||||||
if(data.includes('size=') && startupSuccess === false) {
|
ffmpegCommand = `"${ffmpeg.replace(/\\/g, '\\\\')}"`;
|
||||||
logInfo('Audio stream started up successfully.');
|
return `${ffmpegCommand} ${baseOptions.flags} -f dshow -audio_buffer_size 200 -i audio="${serverConfig.audio.audioDevice}" ` +
|
||||||
startupSuccess = true;
|
`${baseOptions.codec} ${baseOptions.output} pipe:1 | node server/stream/3las.server.js -port ` +
|
||||||
}
|
`${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
|
||||||
});
|
} else if (process.platform === 'darwin') {
|
||||||
|
// macOS: using SoX's rec with coreaudio
|
||||||
childProcess.on('close', (code) => {
|
if (!serverConfig.audio.ffmpeg) {
|
||||||
logFfmpeg(`Child process exited with code ${code}`);
|
logInfo('[Audio Stream] Platform: macOS (darwin) using "coreaudio" with the default audio device.');
|
||||||
});
|
const recCommand = `rec -t coreaudio -b 32 -r 48000 -c ${serverConfig.audio.audioChannels} -t raw -b 16 -r 48000 -c ${serverConfig.audio.audioChannels} -`;
|
||||||
|
return `${recCommand} | node server/stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10}` +
|
||||||
childProcess.on('error', (err) => {
|
` -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
|
||||||
logFfmpeg(`Error starting child process: ${err}`);
|
} else {
|
||||||
});
|
ffmpegCommand = ffmpeg;
|
||||||
}
|
ffmpegParams = `${baseOptions.flags} -f alsa -i "${serverConfig.audio.softwareMode && serverConfig.audio.softwareMode == true ? 'plug' : ''}${serverConfig.audio.audioDevice}" ${baseOptions.codec}`;
|
||||||
}
|
ffmpegParams += ` ${baseOptions.output} -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 10 pipe:1 | node server/stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
|
||||||
|
return `${ffmpegCommand} ${ffmpegParams}`;
|
||||||
if(configExists()) {
|
}
|
||||||
enableAudioStream();
|
} else {
|
||||||
|
// Linux: use alsa with arecord
|
||||||
|
// If softwareMode is enabled, prefix the device with 'plug'
|
||||||
|
if (!serverConfig.audio.ffmpeg) {
|
||||||
|
const audioDevicePrefix = (serverConfig.audio.softwareMode && serverConfig.audio.softwareMode === true) ? 'plug' : '';
|
||||||
|
logInfo('[Audio Stream] Platform: Linux. Using "alsa" input.');
|
||||||
|
const recCommand = `while true; do arecord -D "${audioDevicePrefix}${serverConfig.audio.audioDevice}" -f S16_LE -r 48000 -c ${serverConfig.audio.audioChannels} -t raw -; done`;
|
||||||
|
return `${recCommand} | node server/stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10}` +
|
||||||
|
` -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
|
||||||
|
} else {
|
||||||
|
ffmpegCommand = ffmpeg;
|
||||||
|
ffmpegParams = `${baseOptions.flags} -f alsa -i "${serverConfig.audio.softwareMode && serverConfig.audio.softwareMode == true ? 'plug' : ''}${serverConfig.audio.audioDevice}" ${baseOptions.codec}`;
|
||||||
|
ffmpegParams += ` ${baseOptions.output} -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 10 pipe:1 | node server/stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
|
||||||
|
return `${ffmpegCommand} ${ffmpegParams}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableAudioStream() {
|
||||||
|
// Ensure the webserver port is a number.
|
||||||
|
serverConfig.webserver.webserverPort = Number(serverConfig.webserver.webserverPort);
|
||||||
|
let startupSuccess = false;
|
||||||
|
const command = buildCommand();
|
||||||
|
|
||||||
|
// Only log audio device details if the platform is not macOS.
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
logInfo(`Trying to start audio stream on device: \x1b[35m${serverConfig.audio.audioDevice}\x1b[0m`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// For macOS, log the default audio device.
|
||||||
|
logInfo(`Trying to start audio stream on default input device.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo(`Using internal audio network port: ${serverConfig.webserver.webserverPort + 10}`);
|
||||||
|
logInfo('Using', ffmpeg === 'ffmpeg' ? 'system-installed FFmpeg' : 'ffmpeg-static');
|
||||||
|
logDebug(`[Audio Stream] Full command:\n${command}`);
|
||||||
|
|
||||||
|
// Start the stream only if a valid audio device is configured.
|
||||||
|
if (serverConfig.audio.audioDevice && serverConfig.audio.audioDevice.length > 2) {
|
||||||
|
const childProcess = spawn(command, { shell: true });
|
||||||
|
|
||||||
|
childProcess.stdout.on('data', (data) => {
|
||||||
|
logFfmpeg(`[stream:stdout] ${data}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
childProcess.stderr.on('data', (data) => {
|
||||||
|
logFfmpeg(`[stream:stderr] ${data}`);
|
||||||
|
|
||||||
|
if (data.includes('I/O error')) {
|
||||||
|
logError(`[Audio Stream] Audio device "${serverConfig.audio.audioDevice}" failed to start.`);
|
||||||
|
logError('Please start the server with: node . --ffmpegdebug for more info.');
|
||||||
|
}
|
||||||
|
if (data.includes('size=') && !startupSuccess) {
|
||||||
|
logInfo('[Audio Stream] Audio stream started up successfully.');
|
||||||
|
startupSuccess = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
childProcess.on('close', (code) => {
|
||||||
|
logFfmpeg(`[Audio Stream] Child process exited with code: ${code}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
childProcess.on('error', (err) => {
|
||||||
|
logFfmpeg(`[Audio Stream] Error starting child process: ${err}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logWarn('[Audio Stream] No valid audio device configured. Skipping audio stream initialization.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(configExists()) {
|
||||||
|
checkFFmpeg().then((ffmpegResult) => {
|
||||||
|
ffmpeg = ffmpegResult;
|
||||||
|
if (!serverConfig.audio.ffmpeg) checkAudioUtilities();
|
||||||
|
enableAudioStream();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -217,6 +217,18 @@
|
|||||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'ALSA Software mode', id: 'audio-softwareMode'}) %>
|
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'ALSA Software mode', id: 'audio-softwareMode'}) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex-container">
|
||||||
|
<div class="panel-50 p-bottom-20">
|
||||||
|
<h3>FFmpeg</h3>
|
||||||
|
<p>Legacy option for Linux / macOS that could resolve audio issues, but will consume additional CPU and RAM usage.</p>
|
||||||
|
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Additional FFmpeg', id: 'audio-ffmpeg'}) %>
|
||||||
|
</div>
|
||||||
|
<div class="panel-50 p-bottom-20">
|
||||||
|
<h3>Samplerate Offset</h3>
|
||||||
|
<p>Using a negative value could eliminate audio buffering issues during long periods of listening. However, a value that’s too low might increase the buffer over time.</p>
|
||||||
|
<p><input class="panel-33 input-text w-100 auto" type="number" style="min-height: 40px; color: var(--color-text); padding: 10px; padding-left: 10px; box-sizing: border-box; border: 2px solid transparent; font-family: 'Titillium Web', sans-serif;" id="audio-samplerateOffset" min="-10" max="10" step="1" value="0" aria-label="Samplerate offset"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-full m-0 tab-content no-bg" id="webserver" role="tabpanel">
|
<div class="panel-full m-0 tab-content no-bg" id="webserver" role="tabpanel">
|
||||||
|
|||||||
@@ -146,8 +146,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-100 no-bg text-center">
|
<div class="panel-100 no-bg text-center">
|
||||||
<p>If you use an USB audio card on Linux, enabling this option might fix your audio issues.</p>
|
<div class="flex-container">
|
||||||
<%- include('_components', {component: 'checkbox', cssClass: 'panel-100 flex-container flex-center', label: 'ALSA Software mode', id: 'audio-softwareMode'}) %>
|
<div class="panel-50 no-bg">
|
||||||
|
<p>If you use an USB audio card on Linux, enabling this option might fix your audio issues.</p>
|
||||||
|
<%- include('_components', {component: 'checkbox', cssClass: 'panel-100 flex-container flex-center', label: 'ALSA Software mode', id: 'audio-softwareMode'}) %>
|
||||||
|
</div>
|
||||||
|
<div class="panel-50 no-bg">
|
||||||
|
<p>Legacy option for Linux / macOS that could resolve audio issues, but will consume additional CPU and RAM usage.</p>
|
||||||
|
<%- include('_components', {component: 'checkbox', cssClass: 'panel-100 flex-container flex-center', label: 'Additional FFmpeg', id: 'audio-ffmpeg'}) %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user