Skip to content

Commit

Permalink
Added command line options to drive behavior, some basic error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Joonas Trussmann committed May 11, 2021
1 parent e47afb2 commit eef4a70
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 58 deletions.
43 changes: 33 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,35 @@

# tl;dr;

Install the dependencies
Install **nodejs** and the Javascript dependencies

npm install

Connect your DJI FPV goggles to your PC, power on your quad, make sure the stream is connected and run:

node index.js
If all goes well voc-poc will find your DJI Goggles, obtain the USB interface, send the magic `0x524d5654` packet to the USB Bulk Transfer Out Endpoint and start dumping data from the USB Bulk Transfer Out Endpoint to **out.bin**.
node index.js -f outfile.bin

If all goes well voc-poc will find your DJI Goggles, obtain the USB interface and write the raw video stream to outfile.bin.

If you want to preview the stream live install ffplay (part of the **ffmpeg** package) and use the -o option.

node index.js -o | ffplay -i - -analyzeduration 1 -probesize 32 -sync ext

If you want to produce a nice outfile.mp4, pipe the output through ffmpeg:

node index.js -o | ffmpeg -vcodec copy outfile.mp4 -i -

If needed -o and -f may be combined.

See `node index.js --help` for more options.

## Troubleshooting
If you get USB errors on Windows make sure the Goggles Bulk Transfer Endpoint is using WinUSB drivers using [Zadiq](https://zadig.akeo.ie/).

## How does this work

It sends the magic `0x524d5654` packet to the USB Bulk Transfer Out Endpoint on the Goggles and starts dumping data from the USB Bulk Transfer Out Endpoint to file or stdout.

## The Protocol
The Goggles have a Bulk Transfer Endpoint. To initiate the video stream one has to simply send `0x524d5654` to the Out Bulk Endpoint and then Listen on the In Bulk Endpoint for data.

Expand All @@ -23,20 +40,22 @@ The data format is currently largely unknown:
- `0x00010910` seems to indicate the beginning of some sort of h264 frame according to [this](https://github.com/district-michael/fpv_live/blob/4c7bb40e5cc5daec67b39cc093235afb959a4bfe/src/main/java/com/dji/video/framing/internal/parser/VideoFrameParser.java#L47).
- Followed by a 6 byte (frame?) counter
- Which is always followed by `0xc867ff`?
- ???
- Profit

In any case, ffmpeg and other an make sense of it.

## Todo
### Essential

- ~~Get data out over USB~~
- Figure out the packet structure
- Mux together an mp4 stream for output
- ~~Figure out the packet structure~~
- It sort of just works
- ~~Mux together an mp4 stream for output~~
- ffmpeg is good enough for the time being

### Nice to have

- Set the output file path via cli
- Graceful termination
- ~~Set the output file path via cli~~
- More error handling
- Wait for Goggles to appear if not connected
- Reconnect (and re send magic packet) when connection lost
- Support multiple Goggles
Expand All @@ -46,6 +65,10 @@ The data format is currently largely unknown:

Why not?

## Support the effort

If you'd like, you can send some ETH to `0xbAB1fec80922328F27De6E2F1CDBC2F322397637` or BTC to `3L7dE5EHtyd2b1tXBwdnWC2MADkV2VTbrq` or [buy me a coffe](https://www.buymeacoffee.com/fpv.wtf).

## Credits

Shout out to Jack from [D3VL](https://d3vl.com/) for the debugging help, the og-s for [dji-firmware-tools](https://github.com/o-gs/dji-firmware-tools) and everyone else doing great work in the scene.
133 changes: 87 additions & 46 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,99 @@
var usb = require('usb')
const usb = require('usb')
const yargs = require('yargs')
const fs = require('fs')


const argv = yargs
.option('f', {
alias: 'file',
describe: 'output video feed to file',
type: 'string'
})
.option('o', {
alias: 'stdout',
describe: 'send video feed to stdout for playback. eg: node index.js -o | ffplay -',
type: 'boolean'
})
.option('s', {
alias: 'readsize',
describe: 'size in bytes to queue for usb bulk interface reads',
default: 512,
type: 'integer'
})
.option('q', {
alias: 'queuesize',
describe: 'number of polling usb bulk read requests to keep in flight',
default: 3,
type: 'integer'
})
.option('v', {
alias: 'verbose',
describe: 'be noisy - doesn not play well with -o',
type: 'boolean'
})
.help()
.alias('help', 'h')
.argv;

var goggles = usb.findByIds("0x2ca3", "0x1f")
console.log(goggles)

if(!goggles) {
console.error("Goggles USB device not found. Please connect your goggles and restart the script.")
process.exit(1)
}
goggles.open()
console.log(goggles.interfaces)
if(!goggles.interfaces) {
console.error("Couldn't open Goggles USB device")
process.exit(1)
}
var interface = goggles.interface(3)
console.log(interface)
interface.claim()
if(!interface.endpoints) {
console.error("Couldn't claim bulk interface")
process.exit(1)
}

if(!argv.f && !argv.o) {
console.log("warning: no outputs specified")
argv.v = true
}

const fs = require('fs');
var fd

// specify the path to the file, and create a buffer with characters we want to write
let path = 'out.bin';
var inpoint = interface.endpoints[1]
inpoint.timeout = 100
//outpoint.timeout = 200

// open the file in writing mode, adding a callback function where we do the actual writing
fs.open(path, 'w', function(err, fd) {
if (err) {
throw 'could not open file: ' + err;
}
var inpoint = interface.endpoints[1]
inpoint.timeout = 100


var outpoint = interface.endpoints[0]
//var magic = Buffer.from("54564d52", "hex")
var magic = Buffer.from("524d5654", "hex")
console.log(magic)

outpoint.transfer(magic, function() {
console.log("magic written")
/*inpoint.transfer(20000, function(error, data){
console.log("error", error)
console.log("data", data)
setTimeout(getData)
})*/

})
inpoint.addListener("data", function(data) {
console.log(data)
fs.writeSync(fd, data)
})
inpoint.addListener("error", function(error) {
console.log(error)
})
inpoint.startPoll(3, 20000)

// write the contents of the buffer, from position 0 to the end, to the file descriptor returned in opening our file
var outpoint = interface.endpoints[0]
var magic = Buffer.from("524d5654", "hex")

outpoint.transfer(magic, function(error) {
if(error) {
console.error(error)
}
console.debug("send magic bytes")

});

})
inpoint.addListener("data", function(data) {
if(argv.o) {
process.stdout.write(data)
}
if(argv.v) {
console.debug("received "+data.length+" bytes")
}
if(argv.f) {
if(!fd) {
fd = fs.openSync(argv.f, "w")
if(!fd) {
console.error("couldn't open file "+ argv.f + ": "+err)
process.exit(1)
}
}
fs.writeSync(fd, data)
}
})
inpoint.addListener("error", function(error) {
console.error(error)
})
inpoint.startPoll(argv.q, argv.s)

/*endpoint.transfer(512, function(error, data){
console.log(error)
console.log(data)
})*/
48 changes: 47 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"main": "index.js",
"dependencies": {
"int-to-binary": "^1.0.2",
"usb": "^1.7.1"
"usb": "^1.7.1",
"yargs": "^17.0.1"
},
"devDependencies": {},
"scripts": {
Expand Down

0 comments on commit eef4a70

Please sign in to comment.