support single-frame capture
This commit is contained in:
@@ -27,6 +27,7 @@ Connect to the device's access point (e.g., FTYC811847AGFDZ) and run `node dist/
|
|||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
|
### HTTP Server
|
||||||
To execute the HTTP server, run `node dist/bin.cjs http_server`; you can access the JPEG stream at http://localhost:5000/.
|
To execute the HTTP server, run `node dist/bin.cjs http_server`; you can access the JPEG stream at http://localhost:5000/.
|
||||||
|
|
||||||
The roundtrip delay when using MJPEG is [~350ms](pics/delay.jpg?raw=true).
|
The roundtrip delay when using MJPEG is [~350ms](pics/delay.jpg?raw=true).
|
||||||
@@ -38,9 +39,9 @@ There's a basic UI which can display multiple cameras:
|
|||||||
Clicking on the image will take you to a page that has audio streaming. Click the button below the image to mute/unmute the audio.
|
Clicking on the image will take you to a page that has audio streaming. Click the button below the image to mute/unmute the audio.
|
||||||
|
|
||||||
|
|
||||||
## Settings
|
#### Settings
|
||||||
|
|
||||||
You can provide a config file in `yml` format, check `config.example.yml` for an updated example:
|
You can provide a config file in `yml` format, then pass it as an argument: `node bin.cjs http_server --config_file <your_config.yml>`
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
http_server:
|
http_server:
|
||||||
@@ -82,6 +83,12 @@ All keys are optional
|
|||||||
|
|
||||||
You must restart the HTTP server for changes to the settings file to take effect.
|
You must restart the HTTP server for changes to the settings file to take effect.
|
||||||
|
|
||||||
|
### Single capture mode
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node bin.cjs frame --discovery_ip 192.168.40.104 --out out.jpg
|
||||||
|
```
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
## Protocol
|
## Protocol
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import { DevSerial } from "./impl.js";
|
||||||
|
import { RemoteInfo } from "dgram";
|
||||||
|
import { logger } from "./logger.js";
|
||||||
|
import { startVideoStream } from "./session.js";
|
||||||
|
import { discoverDevices } from "./discovery.js";
|
||||||
|
import { Session } from "./session.js";
|
||||||
|
import { Handlers, makeSession } from "./session.js";
|
||||||
|
import { config } from "./settings.js";
|
||||||
|
|
||||||
|
const sessions: Record<string, Session> = {};
|
||||||
|
export const captureSingle = ({ discovery_ip, out_file }: { discovery_ip: string; out_file: string }) => {
|
||||||
|
let devEv = discoverDevices([discovery_ip]);
|
||||||
|
|
||||||
|
const startSession = (s: Session) => {
|
||||||
|
startVideoStream(s);
|
||||||
|
logger.info(`Camera ${s.devName} is now ready to stream`);
|
||||||
|
};
|
||||||
|
|
||||||
|
devEv.on("discover", (rinfo: RemoteInfo, dev: DevSerial) => {
|
||||||
|
if (dev.devId in sessions) {
|
||||||
|
logger.info(`Camera ${dev.devId} at ${rinfo.address} already discovered, ignoring`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Discovered camera ${dev.devId} at ${rinfo.address}`);
|
||||||
|
const s = makeSession(Handlers, dev, rinfo, startSession, 5000);
|
||||||
|
sessions[dev.devId] = s;
|
||||||
|
config.cameras[dev.devId] = { fix_packet_loss: false };
|
||||||
|
|
||||||
|
s.eventEmitter.on("frame", () => {
|
||||||
|
const assembled = Buffer.concat(s.curImage);
|
||||||
|
fs.writeFileSync(out_file, assembled);
|
||||||
|
logger.info(`Got frame. Exiting`);
|
||||||
|
devEv.emit("close");
|
||||||
|
s.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
+20
@@ -2,6 +2,7 @@ import process from "node:process";
|
|||||||
import { hideBin } from "yargs/helpers";
|
import { hideBin } from "yargs/helpers";
|
||||||
import yargs from "yargs/yargs";
|
import yargs from "yargs/yargs";
|
||||||
|
|
||||||
|
import { captureSingle } from "../capture_single.js";
|
||||||
import { serveHttp } from "../http_server.js";
|
import { serveHttp } from "../http_server.js";
|
||||||
import { pair } from "../pair.js";
|
import { pair } from "../pair.js";
|
||||||
import { loadConfig, config } from "../settings.js";
|
import { loadConfig, config } from "../settings.js";
|
||||||
@@ -70,5 +71,24 @@ yargs(hideBin(process.argv))
|
|||||||
pair({ ssid: argv.ssid, password: argv.password });
|
pair({ ssid: argv.ssid, password: argv.password });
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
.command(
|
||||||
|
"frame",
|
||||||
|
"capture a single frame from the first discovered camera",
|
||||||
|
(yargs) => {
|
||||||
|
return yargs
|
||||||
|
.option("log_level", { describe: "Set log level", default: "info" })
|
||||||
|
.option("discovery_ip", { describe: "Camera discovery IP address", default: "192.168.1.255" })
|
||||||
|
.option("out", { describe: "Path for output file" })
|
||||||
|
.demandOption(["out"])
|
||||||
|
.string(["out", "discovery_ip"]);
|
||||||
|
},
|
||||||
|
(argv) => {
|
||||||
|
buildLogger(argv.log_level, undefined);
|
||||||
|
if (majorVersion < 16) {
|
||||||
|
logger.error(`Node version ${majorVersion} is not supported, may malfunction`);
|
||||||
|
}
|
||||||
|
captureSingle({ discovery_ip: argv.discovery_ip, out_file: argv.out });
|
||||||
|
},
|
||||||
|
)
|
||||||
.demandCommand()
|
.demandCommand()
|
||||||
.parseSync();
|
.parseSync();
|
||||||
|
|||||||
+1
-1
@@ -249,7 +249,7 @@ const deal_with_data = (session: Session, dv: DataView) => {
|
|||||||
}
|
}
|
||||||
// this should always be enabled but currently it seems to cause more
|
// this should always be enabled but currently it seems to cause more
|
||||||
// visual distortion than just missing some frames
|
// visual distortion than just missing some frames
|
||||||
if (!config.cameras[session.devName].fix_packet_loss) {
|
if (!config.cameras[session.devName]?.fix_packet_loss) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user