I’m back. I’ve spent some time working on this and think I have a straighforward ffmpeg solution. I’m not a broadcastify or ffmpeg expert. My solution has about a 20-40 second delay to broadcastify, so maybe someone can improve upon that.
Get Setup
First, you’ll need to install ffmpeg.
sudo apt update
sudo apt install ffmpeg
You can test an audio-less broadcast - run this, substituting your own feed credentials.
export BROADCASTIFY_SERVER="audio9.broadcastify.com"
export BROADCASTIFY_PORT="80"
export BROADCASTIFY_MOUNT="/yourmount"
export BROADCASTIFY_PASSWORD="hunter2"
export BROADCASTIFY_URL="icecast://source:${BROADCASTIFY_PASSWORD}@${BROADCASTIFY_SERVER}:${BROADCASTIFY_PORT}${BROADCASTIFY_MOUNT}"
ffmpeg -hide_banner -nostdin -loglevel info \
-f lavfi -i anullsrc=r=22050:cl=mono \
-c:a libmp3lame -b:a 32k -content_type audio/mpeg -f mp3 \
-legacy_icecast 1 -ice_user source \
"${BROADCASTIFY_URL}"
```
You should see output like this, and after 20-40 seconds your feed will turn green/online.
ffmpeg -hide_banner -nostdin -loglevel info -f lavfi -i anullsrc=r=22050:cl=mono -c:a libmp3lame -b:a 32k -content_type audio/mpeg -f mp3 -legacy_icecast 1 "${BROADCASTIFY_URL}"
Input #0, lavfi, from 'anullsrc=r=22050:cl=mono':
Duration: N/A, start: 0.000000, bitrate: 176 kb/s
Stream #0:0: Audio: pcm_u8, 22050 Hz, mono, u8, 176 kb/s
Stream mapping:
Stream #0:0 -> #0:0 (pcm_u8 (native) -> mp3 (libmp3lame))
Output #0, mp3, to 'icecast://source:hunter2@audio9.broadcastify.com:80/amountpoint':
Metadata:
TSSE : Lavf59.27.100
Stream #0:0: Audio: mp3, 22050 Hz, mono, s16p, 32 kb/s
Metadata:
encoder : Lavc59.37.100 libmp3lame
size= 1971kB time=00:08:24.45 bitrate= 32.0kbits/s speed=78.4x
Create The Script
This script is made to be re-usable - you won't have to edit it to put your credentials in, unless you are
doing dev testing.
# instead of editing and chmod, install creates the empty file and sets ownership permissions
sudo install -o asterisk -g asterisk -m 755 /dev/null /usr/local/bin/allstar-to-broadcastify.sh
sudo nano /usr/local/bin/allstar-to-broadcastify.sh
Content:
#!/usr/bin/env bash
#
# allstar-to-broadcastify.sh
# Reads RAW PCM (s16le/8k/mono) on stdin (from Asterisk outstreamcmd)
# and pushes MP3 to Broadcastify (Icecast).
#
# Uses an optional .env file (default: /etc/allstar-broadcastify.env)
# See sample .env at the end of this message.
set -euo pipefail
### ---------- Config / .env ---------- ###
ENV_FILE="${ENV_FILE:-/etc/allstar-broadcastify.env}"
# Auto-import variables from ENV_FILE if present
if [[ -f "$ENV_FILE" ]]; then
set -a
# shellcheck disable=SC1090
. "$ENV_FILE"
set +a
fi
# Required (or provided via .env)
BROADCASTIFY_SERVER="${BROADCASTIFY_SERVER:-}"
BROADCASTIFY_PORT="${BROADCASTIFY_PORT:-80}"
BROADCASTIFY_MOUNT="${BROADCASTIFY_MOUNT:-}"
BROADCASTIFY_PASSWORD="${BROADCASTIFY_PASSWORD:-}"
# If URL not provided, derive it. (FFmpeg expects a leading slash in the mount.)
if [[ -z "${BROADCASTIFY_URL:-}" ]]; then
case "$BROADCASTIFY_MOUNT" in
/*) mount_norm="$BROADCASTIFY_MOUNT" ;;
*) mount_norm="/$BROADCASTIFY_MOUNT" ;;
esac
BROADCASTIFY_URL="icecast://source:${BROADCASTIFY_PASSWORD}@${BROADCASTIFY_SERVER}:${BROADCASTIFY_PORT}${mount_norm}"
fi
# Audio settings
IN_AR="${IN_AR:-8000}" # Asterisk outstreamcmd default is s16le, 8 kHz, mono
OUT_AR="${OUT_AR:-22050}" # Broadcastify-friendly
BITRATE="${BITRATE:-32k}" # MP3 CBR bitrate
# Logging / runtime
LOG_FILE="${LOG_FILE:-/var/log/asterisk/allstar-bcast.log}"
STATS_FILE="${STATS_FILE:-/var/log/asterisk/allstar-bcast.stats}"
DEBUG="${DEBUG:-0}" # 1 = ffmpeg -stats/-progress to STATS_FILE
ROTATE_MAX_BYTES="${ROTATE_MAX_BYTES:-1048576}" # 1 MiB
ROTATE_KEEP="${ROTATE_KEEP:-3}"
BROADCASTIFY_LOCK_FILE="${BROADCASTIFY_LOCK_FILE:-/run/asterisk/allstar-bcast.lock}"
# Retry
RETRY_DELAY_SECS="${RETRY_DELAY_SECS:-3}"
RETRY_JITTER_MAX="${RETRY_JITTER_MAX:-2}"
# Self-test (generate silence instead of using stdin)
SELF_TEST="${SELF_TEST:-0}" # 1 = use anullsrc at OUT_AR
### ---------- Helpers ---------- ###
touch "$LOG_FILE"
rotate_logs() {
local f="$1" max_bytes="$2" keep="$3"
[[ -f "$f" ]] || return 0
local size; size=$(wc -c <"$f" 2>/dev/null || echo 0)
[[ "$size" -lt "$max_bytes" ]] && return 0
for (( i=keep-1; i>=1; i-- )); do
[[ -f "${f}.${i}" ]] && mv -f "${f}.${i}" "${f}.$((i+1))"
done
mv -f "$f" "${f}.1"
: > "$f"
}
ts() { date +"%Y-%m-%d %H:%M:%S%z"; }
log() { rotate_logs "$LOG_FILE" "$ROTATE_MAX_BYTES" "$ROTATE_KEEP"; printf "%s %s\n" "$(ts)" "$*" >>"$LOG_FILE"; }
mask_pw() { sed -E 's#(source:)[^@]+@#\1***@#'; }
# Validate required inputs
if [[ -z "$BROADCASTIFY_SERVER" || -z "$BROADCASTIFY_MOUNT" || -z "$BROADCASTIFY_PASSWORD" ]]; then
echo "Missing required BROADCASTIFY_* vars. Set them in $ENV_FILE or env. Aborting." >&2
exit 2
fi
# Acquire non-blocking lock so only one instance runs
exec 9>"$BROADCASTIFY_LOCK_FILE" || true
if ! flock -n 9; then
log "[WARN] Another instance appears to be running (lock: $BROADCASTIFY_LOCK_FILE). Exiting."
exit 1
fi
### ---------- Build ffmpeg args ---------- ###
FF_MISC=(-hide_banner -nostdin)
if [[ "$DEBUG" == "1" ]]; then
: > "$STATS_FILE" || true
FF_LOG=(-loglevel info -stats -stats_period 5 -progress "$STATS_FILE")
log "[INFO] Debug mode ON. Writing ffmpeg progress/stats to $STATS_FILE"
else
FF_LOG=(-loglevel warning -nostats)
fi
# Input: either stdin (from Asterisk) or test tone
if [[ "$SELF_TEST" == "1" ]]; then
FF_IN=(-f lavfi -re -i "anullsrc=r=${OUT_AR}:cl=mono")
else
FF_IN=(-f s16le -ar "$IN_AR" -ac 1 -i -)
fi
# Filters & encoding
FF_FILTER=(-af aresample=async=1:min_hard_comp=0.100:first_pts=0 -ar "$OUT_AR" -ac 1)
FF_ENC=(-c:a libmp3lame -b:a "$BITRATE" -content_type audio/mpeg -f mp3)
# Icecast / Broadcastify flags
FF_ICECAST=(-legacy_icecast 1)
URL="$BROADCASTIFY_URL"
log "[INFO] Running as user=$(id -un) uid=$(id -u) group=$(id -gn) gid=$(id -g)"
log "[INFO] Starting stream to $(echo "$URL" | mask_pw) (bitrate $BITRATE, out_ar $OUT_AR, debug=$DEBUG, self_test=$SELF_TEST)"
trap 'log "[INFO] Caught SIGINT"; exit 130' INT
trap 'log "[INFO] Caught SIGTERM"; exit 143' TERM
### ---------- Main loop ---------- ###
while :; do
rotate_logs "$LOG_FILE" "$ROTATE_MAX_BYTES" "$ROTATE_KEEP"
[[ "$DEBUG" == "1" ]] && rotate_logs "$STATS_FILE" "$ROTATE_MAX_BYTES" "$ROTATE_KEEP"
log "[INFO] Launching ffmpeg…"
ffmpeg "${FF_MISC[@]}" "${FF_LOG[@]}" \
"${FF_IN[@]}" "${FF_FILTER[@]}" "${FF_ENC[@]}" \
"${FF_ICECAST[@]}" \
"$URL" >>"$LOG_FILE" 2>&1 || true
ec=$?
log "[WARN] ffmpeg exited with code $ec"
# small randomized backoff before reconnecting
jitter=0
if command -v shuf >/dev/null 2>&1; then
jitter=$(shuf -i 0-"$RETRY_JITTER_MAX" -n 1)
fi
sleep_time=$(( RETRY_DELAY_SECS + jitter ))
log "[INFO] Retrying in ${sleep_time}s…"
sleep "$sleep_time"
done
Create the Config
sudo install -o asterisk -g asterisk -m 660 /dev/null /etc/allstar-broadcastify.env
sudo nano /etc/allstar-broadcastify.env
Content:
# Broadcastify creds (required)
BROADCASTIFY_SERVER="audio9.broadcastify.com" # your audio server
BROADCASTIFY_PORT="80"
BROADCASTIFY_MOUNT="/yourmountpoint" # leading slash required by ffmpeg
BROADCASTIFY_PASSWORD="yourpassword""
# Derived URL (optional; if omitted, script builds it from vars above)
# BROADCASTIFY_URL="icecast://source:${BROADCASTIFY_PASSWORD}@${BROADCASTIFY_SERVER}:${BROADCASTIFY_PORT}${BROADCASTIFY_MOUNT}"
# Audio / behavior (optional)
IN_AR="8000"
OUT_AR="22050"
BITRATE="32k"
# WARNING: enabling debug will log your credentials to the log file
DEBUG="0" # 1 = write ffmpeg -progress to stats file
SELF_TEST="0" # 1 = generate silence (no Asterisk needed)
# Logging / retry (optional)
LOG_FILE="/var/log/asterisk/allstar-bcast.log"
STATS_FILE="/var/log/asterisk/allstar-bcast.stats"
RETRY_DELAY_SECS="3"
RETRY_JITTER_MAX="2"
Add to Asterisk
Once you've setup your env file and the script, add the outstreamcmd to rpt.conf
outstreamcmd = /usr/local/bin/allstar-to-broadcastify.sh
Restart Asterisk and Monitor
sudo astres.sh
tail -f /var/log/asterisk/allstar-bcast.log
Without debug your log output looks like this. If the log file doesn’t get created or is empty, double check your script and
2025-09-13 17:30:10-0400 [INFO] Running as user=asterisk uid=106 group=asterisk gid=112
2025-09-13 17:30:10-0400 [INFO] Starting stream to icecast://source:***@audio9.broadcastify.com:80/yourmount (bitrate 32k, out_ar 22050, debug=0, tls=0, self_test=0)
2025-09-13 17:30:10-0400 [INFO] Launching ffmpeg…
Guessed Channel Layout for Input Stream #0.0 : mono
Then when you restart, if you enable debug, you should see something like this. Keep in mind that debug does outuput your creds as part of the URL. Ony use this when debugging issues, and plan to clean up your log files afterwards.
2025-09-13 17:28:41-0400 [INFO] Debug mode ON. Writing ffmpeg progress/stats to /var/log/asterisk/allstar-bcast.stats
2025-09-13 17:28:41-0400 [INFO] Running as user=asterisk uid=106 group=asterisk gid=112
2025-09-13 17:28:41-0400 [INFO] Starting stream to icecast://source:***@audio9.broadcastify.com:80/yourmount (bitrate 32k, out_ar 22050, debug=1, tls=0, self_test=0)
2025-09-13 17:28:41-0400 [INFO] Launching ffmpeg…
ffmpeg stats and -progress period set to 5.
Guessed Channel Layout for Input Stream #0.0 : mono
Input #0, s16le, from 'pipe:':
Duration: N/A, bitrate: 128 kb/s
Stream #0:0: Audio: pcm_s16le, 8000 Hz, mono, s16, 128 kb/s
Stream mapping:
Stream #0:0 -> #0:0 (pcm_s16le (native) -> mp3 (libmp3lame))
Output #0, mp3, to 'icecast://source:hunter2@audio9.broadcastify.com:80/yourmount':
Metadata:
TSSE : Lavf59.27.100
Stream #0:0: Audio: mp3, 22050 Hz, mono, s16p, 32 kb/s
Metadata:
encoder : Lavc59.37.100 libmp3lame
size= 27kB time=00:00:06.95 bitrate= 32.3kbits/s speed=1.39x
Troubleshooting
Check. your source file for syntax errors (extra quotes, unquoted lines, etc)
asl@r146:/ $ sudo source /etc/allstar-broadcastify.env
-bash: /etc/allstar-broadcastify.env: line 22: unexpected EOF while looking for matching `"'
Run it in test (no audio mode). You may have to shut down asterisk to have it clear the run/lock file.
# sudo astdn.sh
sudo DEBUG=1 SELF_TEST=1 /usr/local/bin/allstar-to-broadcastify.sh
Running this command won’t ouptut anything, but it should write to /var/log/asterisk/allstar-bcast.log