dinsdag 19 juli 2016

Linux shell script to stream audio to a Zavio IP Camera

While I was showing an apprentice how to reverse engineer closed HTTP interfaces, we figured out how the proprietary Zavio webbrowser plugin streams audio data to the Zavio F3510 IP Cameras and other models from around 2016.

Then I wrote a rudimentary bash shell script that enables you to play MP3, WAV, FLAC, MIDI or any other file formats that avconv can handle through the camera's poor little speaker.

Interestingly, you can also use this script to continuously stream live audio from a HTTP URL to the camera and that way you can enjoy music anywhere you've installed your camera's.

Currently, this script is compatible with bash on Linux but I don't see why it wouldn't work on bash for Windows or Mac OS if you're into that kind of thing. Just make sure you have the necessary dependencies (avconv, curl,...) in your search PATH.

Enjoy!

#!/bin/bash
# Simple shell script to stream audio to a Zavio F3510 or other Zavio camera's from around the year 2014, 2015 and 2016
# No configuration is required, just supply the parameters on the command line
# Supported audio formats: mp3, mp2, wav, ogg and many more, thanks to avconv and all related projects
# Uses curl, the Swiss Army Knife of networking
# Copyleft by Tom Van Braeckel, 19-07-2016 🔥

# How it works:
# =============
# The Zavio needs to receive pcm_mulaw encoded audio samples
# at a sample rate of 8000 samples/second (8kHz) mono at 16 bits per sample through a HTTP web interface.
# The data is sent through periodic HTTP requests in chunks of 1000 bytes audio data + HTTP protocol overhead.

# Dependencies:
# -------------
# - avconv
# - curl
# - base64
# - sleep (a modern one that accepts non-integer arguments, as in: sleep 0.06)
# - echo (a modern one that supports the -e "\r" construct)


# Theoretical note:
# -----------------
# The HTTP API that we call has no active two-way synchronisation protocol so there will always be timer drift between the rate at which we send samples and the rate at which they are consumed. When there is drift, the Zavio seems to insert a few dummy samples or drop samples to correct the drift. This could theoretically be heard as clicks or cracks when playing long audio clips but it seems to work fine for me.

# Calculation of the rate at which we need to send chunks of audio to the HTTP API:
# ---------------------------------------------------------------------------------
# 1000 bytes/chunk / 2 bytes/sample = 500 samples/chunk
# 8k samples/second / 500 samples/chunk = 16 chunks/second = 1/16 second/chunk = 0.0625 second/chunk

sleep_time_per_audio_chunk=0.06 # target sleep time (0.0625 seconds) minus around 4% = 0.0025s overhead (forking new sleep process)

ip="$1"
auth="$2"
file="$3"
boost="$4"
if [ -z "$ip" -o -z "$auth" -o -z "$file" ]; then
echo "Usage: $0 ip_address username:password filename/url [volumeboost in dB]"
echo
echo "Example: $0 192.168.4.20 tom:supercool world_domination.wav"
echo "Example: $0 192.168.1.203 admin:admin audiofile.mp3 10"
echo "Example: $0 192.168.1.203 admin:admin http://streamingserver.com/stream -5"
exit 1
fi

# Default to 0 boost
if [ -z "$boost" ]; then
boost=0
fi
echo "boost = $boost"


# Encode the username:password pair in base64 encoding
auth64=$(echo -n "$auth" | base64)

# Arbitrary string of text
string="--As from earth's Bos om, sprung to sight"

echo -n "Sending audio sample..."
# Send the encoded audio to stdout with avconv and amplify it a bit, read it in chunks of 1000 bytes, add the HTTP prefix and let curl do the HTTP 1.0 request
# We do this while curl is doing requests of size > 1000 bytes, until the grep no longer exits successfully, which ends the loop
avconv -i "$file" -f wav -acodec pcm_mulaw -ar 8000 -ac 1 -af "volume=$boost"dB - | while (echo -en "$string\r\nContent-Type: audio/wav\r\n" ; head -c 1000 ) | curl -v --http1.0 -H "Content-Type: multipart/form-data; boundary=$string" -H "Authorization: Basic $auth64" --data-binary @- http://$ip/cgi-bin/operator/transmit 2>&1 | grep -q "Content-Length: 1[0-9][0-9][0-9]"; do
sleep "$sleep_time_per_audio_chunk"
echo -n .
done
echo "done."

Geen opmerkingen:

Een reactie plaatsen