Hi351x-family cameras (Hi3510/Hi3516 and compatible SoCs, hfonvif/1.0 ONVIF stack — the kind that identify as IPCamera and stream via rtsp://<ip>:554/stream_0/stream_1) can serve a full-resolution JPEG still over plain HTTP — without decoding the RTSP stream yourself.
The snapshot URL
curl -o snapshot.jpg "http://10.91.1.207/action/cgi_images"This returns Content-Type: image/jpeg at the camera’s main-stream resolution (e.g. 1920×1080). Verified working on firmware 22.010.30.6_MAIN_V44.
If the endpoint returns a JSON error like {"code":208,...} on other paths, note that the correct path is /action/cgi_images — not /snap.jpg, /webcapture.jpg, or /tmpfs/auto.jpg (those all 404 on this family).
Discovering it via ONVIF (port 8080)
The same URI is what ONVIF GetSnapshotUri advertises. Useful for confirming the endpoint on a different firmware or when the path differs:
# 1) Find the Media service XAddr
curl -s -X POST -H "Content-Type: application/soap+xml" \
--data '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body><GetCapabilities xmlns="http://www.onvif.org/ver10/device/wsdl"><Category>Media</Category></GetCapabilities></s:Body></s:Envelope>' \
http://10.91.1.207:8080/onvif/device_service | grep -o '<tt:XAddr>[^<]*'
# 2) List profiles → take the profile token (e.g. profile0)
curl -s -X POST -H "Content-Type: application/soap+xml" \
--data '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body><GetProfiles xmlns="http://www.onvif.org/ver10/media/wsdl"/></s:Body></s:Envelope>' \
http://10.91.1.207:8080/onvif/Media | grep -o 'token="profile[0-9]*"'
# 3) GetSnapshotUri
curl -s -X POST -H "Content-Type: application/soap+xml" \
--data '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body><GetSnapshotUri xmlns="http://www.onvif.org/ver10/media/wsdl"><ProfileToken>profile0</ProfileToken></GetSnapshotUri></s:Body></s:Envelope>' \
http://10.91.1.207:8080/onvif/Media | grep -o '<tt:Uri>[^<]*'Expected output:
http://10.91.1.207:80/action/cgi_imagesPython example
import urllib.request
def take_snapshot(ip, out_path="snapshot.jpg", timeout=10):
url = f"http://{ip}/action/cgi_images"
data = urllib.request.urlopen(url, timeout=timeout).read()
if not data.startswith(b"\xff\xd8"):
raise ValueError(f"not a JPEG ({len(data)} bytes)")
with open(out_path, "wb") as f:
f.write(data)
return out_path
take_snapshot("10.91.1.207")Camera configuration
For configuring the camera’s streams, encoders, resolution, GOP etc., use
Hi351x-Camera-CLI — a CLI
tool that scans the network for these cameras, dumps their full configuration
and applies changes (setVencConf, etc.) across single devices or whole
subnets.