almost live, but horrible hack for stream capture, and not live ffmpeg, just live save of images

This commit is contained in:
devdesk 2024-02-15 00:26:10 +02:00
parent 7d75ad7596
commit 17c7d0e555
6 changed files with 153 additions and 48 deletions

View File

@ -3,6 +3,29 @@
https://telavivmakers.org/tamiwiki/projects/thermalcam https://telavivmakers.org/tamiwiki/projects/thermalcam
### Starting the stream
#### Enable jumbo frames
```
sudo ip link set eth0 mtu 9000
```
#### Send start packet
You need to send a special packet.
Sending it via sudo because of raw sockets:
```bash
sudo ./venv/bin/python ./replay.py
```
To send it you need the capability to open sockets in raw mode, but that does not work well with scripts (see [1]
[1] setcap for executables, not helpful for python scripts:
```
setcap cap_net_raw,cap_net_admin=eip ./replay.py
```
### Rust lib usage ### Rust lib usage
# if you don't already have a virtualenv. Linux specific, adjust to your OS. # if you don't already have a virtualenv. Linux specific, adjust to your OS.

138
decode.py Executable file → Normal file
View File

@ -1,9 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
from pathlib import Path
import os import os
import subprocess import subprocess
from io import BytesIO
import numpy as np import numpy as np
from tqdm import tqdm from tqdm import tqdm
from datetime import datetime
import pandas as pd import pandas as pd
import pcapng import pcapng
from struct import unpack from struct import unpack
@ -12,6 +15,7 @@ from PIL import Image
# Create the parser # Create the parser
parser = argparse.ArgumentParser(description="Process a pcap file.") parser = argparse.ArgumentParser(description="Process a pcap file.")
parser.add_argument("--live", action="store_true", help="Process images live")
# Add an argument for the pcap file, with a default value # Add an argument for the pcap file, with a default value
parser.add_argument('input_file', nargs='?', default='in.pcap', help='The pcap file to process') parser.add_argument('input_file', nargs='?', default='in.pcap', help='The pcap file to process')
@ -19,31 +23,57 @@ parser.add_argument('input_file', nargs='?', default='in.pcap', help='The pcap f
# Parse the arguments # Parse the arguments
args = parser.parse_args() args = parser.parse_args()
# Now use args.input_file as the file to process
input_file = args.input_file # TODO - probably a better way to do this
basename = os.path.splitext(os.path.basename(input_file))[0] def live_capture():
while True:
# TODO: This is wrong:
# during scapy.all.sniff construction we lose about 27 packets.
# we work around it by capturing 2* packets(frame), so we just lose
# about half the frames.
cap = scapy.all.sniff(iface="enp1s0f0", count=32 * 2)
for pkt in cap:
l = len(bytes(pkt))
if l != 6972:
continue
data = bytes(pkt)
with open('udp.bytes', 'ab+') as fd:
fd.write(bytes(pkt.payload))
yield data
# Read packets from a pcap file if args.live:
scanner = pcapng.scanner.FileScanner(open(input_file, "rb")) print('live stream, import scapy')
blocks = tqdm(scanner) import scapy.all
print('open stream')
# Helper function to safely get an attribute from an object stream = live_capture()
def tryget(obj, att): start = datetime.now()
if hasattr(obj, att): timestamp = start.strftime('%Y%m%d_%H%M%S')
return getattr(obj, att) basename = f'live_{timestamp}'
return None blocks = tqdm(stream)
else:
# Now use args.input_file as the file to process
input_file = args.input_file
basename = os.path.splitext(os.path.basename(input_file))[0]
stream = open(input_file, 'rb')
# Read packets from a pcap file
scanner = pcapng.scanner.FileScanner(stream)
blocks = tqdm(scanner)
def rightsize(it): def rightsize(it):
for i, obj in enumerate(it): for i, obj in enumerate(it):
if not hasattr(obj, 'packet_len'): if isinstance(obj, bytes):
l = len(obj)
data = obj
else:
if not hasattr(obj, 'packet_len'):
continue
l = obj.packet_len
data = obj.packet_data
if l != 6972:
continue continue
len = obj.packet_len yield data
if len != 6972:
continue
yield obj.packet_data
def removestart(it): def removestart(it):
@ -52,7 +82,6 @@ def removestart(it):
yield x[0x2A:] yield x[0x2A:]
# Function to parse packet data # Function to parse packet data
def parse(data): def parse(data):
hdr = 4 + 2 * 7 # Header length hdr = 4 + 2 * 7 # Header length
@ -73,26 +102,38 @@ def parsed(it):
# Function to group data into frames # Function to group data into frames
def frames(it): def frames(it):
current = [] current = []
#otherdata = []
for obj in it: for obj in it:
if obj['part'] == 0: if obj['part'] == 0:
if len(current) > 0: if len(current) > 0:
yield b"".join(current) yield b"".join(current)
current = [] current = []
#otherdata = []
current.append(obj["data"]) current.append(obj["data"])
#otherdata.append(obj)
if len(current) > 0: if len(current) > 0:
yield b"".join(current) yield b"".join(current)
WIDTH = 384
HEIGHT = 288
def iterimages(it, width, height, pixelformat=">H"):
def skip_bad_frames(it, width=WIDTH, height=HEIGHT):
for frame in it: for frame in it:
if len(frame) != width * height * 2: # 16 bpp if len(frame) != width * height * 2: # 16 bpp
# Will be fixed when we stopped doing restarts
#print(f'{len(frame)} != {width} * {height} * 2')
continue continue
yield frame
def iterimages(it, width=WIDTH, height=HEIGHT, pixelformat=">H"):
for frame in it:
yield Image.fromarray(np.frombuffer(frame, dtype=pixelformat).reshape(width, height)) yield Image.fromarray(np.frombuffer(frame, dtype=pixelformat).reshape(width, height))
# Get frames and convert them to images # Get frames and convert them to images
frames = frames(parsed(removestart(rightsize(blocks)))) frames = skip_bad_frames(frames(parsed(removestart(rightsize(blocks)))))
images = iterimages(it=frames, width=384, height=288)
# Create the directory for frames if not exists # Create the directory for frames if not exists
frame_dir = f"frames/{basename}" frame_dir = f"frames/{basename}"
@ -100,26 +141,37 @@ if not os.path.exists(frame_dir):
os.makedirs(frame_dir) os.makedirs(frame_dir)
# Save each image as a PNG file # Save each image as a PNG file
for i, img in enumerate(images): if not args.live:
img.save(f'frames/{basename}/{basename}_{i:04}.png') images = iterimages(it=frames)
for i, img in enumerate(images):
img.save(f'frames/{basename}/{basename}_{i:04}.png')
ffmpeg_input = f"frames/{basename}/{basename}_%04d.png"
command = [
"ffmpeg",
"-y", # Overwrite output file without asking
"-hide_banner", # Hide banner
"-loglevel", "info", # Log level
"-f", "image2", # Input format
"-framerate", "25", # Framerate
"-i", ffmpeg_input, # Input file pattern
"-vf", "transpose=1", # Video filter for transposing
"-s", "384x288", # Size of one frame
"-vcodec", "libopenh264", # Video codec
"-pix_fmt", "yuv420p", # Pixel format: YUV 4:2:0
"thermal.mp4", # Output file in MP4 container
]
# Produce a video from the saved images subprocess.run(command)
ffmpeg_input = f"frames/{basename}/{basename}_%04d.png" print("to play: ffplay thermal.mp4")
command = [
"ffmpeg",
"-y", # Overwrite output file without asking
"-hide_banner", # Hide banner
"-loglevel", "info", # Log level
"-f", "image2", # Input format
"-framerate", "25", # Framerate
"-i", ffmpeg_input, # Input file pattern
"-vf", "transpose=1", # Video filter for transposing
"-s", "384x288", # Size of one frame
"-vcodec", "libx264", # Video codec
"-pix_fmt", "yuv420p", # Pixel format: YUV 4:2:0
"thermal.mp4", # Output file in MP4 container
]
subprocess.run(command) else:
output = 'to_ffmpeg'
print("to play: ffplay thermal.mp4") # live: write to named pipe
if not Path(output).exists():
print(f'making fifo at {output}')
os.mkfifo(output)
fd = open(output, 'wb')
for i, frame in enumerate(frames):
print(f'frame {i:3}')
Image.fromarray(np.frombuffer(frame, dtype='>H').reshape(WIDTH, HEIGHT)).save(f'test_{i:04}.png')
fd.write(frame)

9
listen.py Normal file
View File

@ -0,0 +1,9 @@
from socket import socket, AF_INET, SOCK_DGRAM
s = socket(AF_INET, SOCK_DGRAM)
s.bind(('', 8090))
while True:
d = s.recvfrom(1024)
print(d)

1
replay.py Normal file → Executable file
View File

@ -1,3 +1,4 @@
#!/usr/bin/env python3
#replay the "trigger" packet. #replay the "trigger" packet.
#this packets will start the source broadcasting its packets. #this packets will start the source broadcasting its packets.

View File

@ -2,11 +2,31 @@
from pathlib import Path from pathlib import Path
from thermaldecoder import decode from thermaldecoder import decode
import numpy as np import numpy as np
import subprocess
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
# Create a directory to store the frames if it doesn't exist
root = Path('frames') root = Path('frames')
root.mkdir(exist_ok=True) root.mkdir(exist_ok=True)
frames = list(decode('in.pcap'))
f = np.array(frames[0]) # Decode the frames from the pcap file
f.shape = (384, 288) frames = list(decode('indesk.pcapng'))
plt.imshow(f)
plt.show() # Iterate over the frames
for i, frame in enumerate(frames):
try:
# Convert the frame to an image file
img_path = root / f"frame_{i}.png"
f = np.array(frame)
f.shape = (384, 288)
plt.imshow(f)
plt.axis('off')
plt.savefig(img_path, bbox_inches='tight', pad_inches=0)
plt.close()
# Use ffmpeg to display the image
subprocess.run(['ffmpeg', '-i', str(img_path), '-vf', 'scale=800:600', '-framerate', '25', '-f', 'image2pipe', '-'], check=True)
except ValueError as e:
print(f"Error processing frame {i}: {e}")

Binary file not shown.