257 lines
7.3 KiB
Bash
Executable File
257 lines
7.3 KiB
Bash
Executable File
#!/bin/bash
|
|
# PhoneStream — Redmi Note 9 Pro → виртуальная веб-камера через USB
|
|
# Использование: ./phonestream.sh [start|stop|status|menu]
|
|
|
|
V4L2_DEV="/dev/video20"
|
|
MODULE="v4l2loopback"
|
|
PHONE_ID="c3c2c293"
|
|
|
|
# Профили: описание|разрешение|fps|битрейт
|
|
PROFILES=(
|
|
"Максимум (5MP 4:3)|2608x1960|30|25M"
|
|
"Широкий (4MP 16:9)|2688x1512|30|20M"
|
|
"2.5K (5MP 4:3)|2592x1944|30|20M"
|
|
"Full HD (1080p)|1920x1080|30|10M"
|
|
"Full HD 60fps|1920x1080|60|15M"
|
|
"HD 120fps (slow-mo)|1280x720|120|10M"
|
|
"HD 240fps (slow-mo)|1280x720|240|10M"
|
|
)
|
|
|
|
DEFAULT_PROFILE=0 # Максимум по умолчанию
|
|
|
|
# ─── Цвета ───
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}[✓]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
|
err() { echo -e "${RED}[✗]${NC} $1"; }
|
|
|
|
# ─── Проверка зависимостей ───
|
|
check_deps() {
|
|
local missing=()
|
|
for cmd in scrcpy adb v4l2-ctl; do
|
|
command -v "$cmd" &>/dev/null || missing+=("$cmd")
|
|
done
|
|
if [ ${#missing[@]} -gt 0 ]; then
|
|
err "Не найдены: ${missing[*]}"
|
|
echo " sudo apt install scrcpy v4l2loopback-dkms v4l2loopback-utils ffmpeg adb"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ─── Загрузка v4l2loopback ───
|
|
load_module() {
|
|
if lsmod | grep -q "$MODULE"; then
|
|
log "v4l2loopback уже загружен"
|
|
return
|
|
fi
|
|
log "Загружаю v4l2loopback..."
|
|
sudo modprobe "$MODULE" devices=1 video_nr=20 card_label="PhoneCam" exclusive_caps=1
|
|
sleep 1
|
|
if [ -e "$V4L2_DEV" ]; then
|
|
log "Виртуальная камера $V4L2_DEV готова"
|
|
else
|
|
err "Не удалось создать $V4L2_DEV"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ─── Выгрузка модуля ───
|
|
unload_module() {
|
|
if ! lsmod | grep -q "$MODULE"; then
|
|
log "v4l2loopback не загружен"
|
|
return
|
|
fi
|
|
# Сначала убить все scrcpy
|
|
pkill -f "scrcpy.*v4l2-sink" 2>/dev/null
|
|
sleep 1
|
|
sudo rmmod "$MODULE" 2>/dev/null
|
|
log "v4l2loopback выгружен"
|
|
}
|
|
|
|
# ─── Проверка телефона ───
|
|
check_phone() {
|
|
if ! adb devices 2>/dev/null | grep -q "$PHONE_ID"; then
|
|
err "Телефон не подключён по USB"
|
|
echo " 1. Подключи телефон по USB"
|
|
echo " 2. Включи отладку по USB (Настройки → Для разработчиков)"
|
|
echo " 3. Разреши отладку на телефоне"
|
|
exit 1
|
|
fi
|
|
log "Телефон подключён: $PHONE_ID"
|
|
}
|
|
|
|
# ─── Запуск стрима ───
|
|
start_stream() {
|
|
local idx=${1:-$DEFAULT_PROFILE}
|
|
local profile="${PROFILES[$idx]}"
|
|
local res=$(echo "$profile" | cut -d'|' -f2)
|
|
local fps=$(echo "$profile" | cut -d'|' -f3)
|
|
local bitrate=$(echo "$profile" | cut -d'|' -f4)
|
|
local name=$(echo "$profile" | cut -d'|' -f1)
|
|
|
|
# Проверяем не запущен ли уже
|
|
if pgrep -f "scrcpy.*v4l2-sink" >/dev/null 2>&1; then
|
|
warn "Стрим уже запущен. Останови сначала: $0 stop"
|
|
return
|
|
fi
|
|
|
|
check_deps
|
|
load_module
|
|
check_phone
|
|
|
|
# Закрываем камеру на телефоне
|
|
adb shell am force-stop com.android.camera 2>/dev/null
|
|
adb shell am force-stop com.android.camera2 2>/dev/null
|
|
adb shell am force-stop com.google.android.GoogleCamera 2>/dev/null
|
|
sleep 0.5
|
|
|
|
log "Запуск: $name (${res}@${fps}fps, ${bitrate}bps)"
|
|
|
|
local high_speed_flag=""
|
|
if [ "$fps" -gt 60 ]; then
|
|
high_speed_flag="--camera-high-speed"
|
|
fi
|
|
|
|
scrcpy \
|
|
--no-audio \
|
|
--no-playback \
|
|
--video-source=camera \
|
|
--camera-id=0 \
|
|
--camera-size="$res" \
|
|
--camera-fps="$fps" \
|
|
--video-bit-rate="$bitrate" \
|
|
--v4l2-sink="$V4L2_DEV" \
|
|
$high_speed_flag \
|
|
2>&1 &
|
|
|
|
local pid=$!
|
|
sleep 3
|
|
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
log "Стрим запущен (PID: $pid)"
|
|
log "Камера доступна: $V4L2_DEV"
|
|
echo ""
|
|
echo " Для остановки: $0 stop"
|
|
else
|
|
err "Не удалось запустить стрим"
|
|
wait "$pid" 2>/dev/null
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ─── Остановка ───
|
|
stop_stream() {
|
|
pkill -f "scrcpy.*v4l2-sink" 2>/dev/null
|
|
sleep 1
|
|
# Принудительно если не закрылся
|
|
pkill -9 -f "scrcpy.*v4l2-sink" 2>/dev/null
|
|
log "Стрим остановлен"
|
|
}
|
|
|
|
# ─── Статус ───
|
|
status() {
|
|
echo -e "${CYAN}═══ PhoneStream Status ═══${NC}"
|
|
echo ""
|
|
|
|
# v4l2loopback
|
|
if lsmod | grep -q "$MODULE"; then
|
|
echo -e " Модуль v4l2loopback: ${GREEN}загружен${NC}"
|
|
else
|
|
echo -e " Модуль v4l2loopback: ${RED}не загружен${NC}"
|
|
fi
|
|
|
|
# /dev/video20
|
|
if [ -e "$V4L2_DEV" ]; then
|
|
echo -e " Виртуальная камера: ${GREEN}$V4L2_DEV${NC}"
|
|
local info=$(v4l2-ctl -d "$V4L2_DEV" --all 2>/dev/null | grep "Width/Height" | head -1)
|
|
if [ -n "$info" ]; then
|
|
echo -e " Разрешение: ${GREEN}$info${NC}"
|
|
fi
|
|
else
|
|
echo -e " Виртуальная камера: ${RED}отсутствует${NC}"
|
|
fi
|
|
|
|
# Телефон
|
|
if adb devices 2>/dev/null | grep -q "$PHONE_ID"; then
|
|
echo -e " Телефон: ${GREEN}подключён${NC}"
|
|
else
|
|
echo -e " Телефон: ${RED}не подключён${NC}"
|
|
fi
|
|
|
|
# Процесс
|
|
local pid=$(pgrep -f "scrcpy.*v4l2-sink" 2>/dev/null | head -1)
|
|
if [ -n "$pid" ]; then
|
|
echo -e " Стрим: ${GREEN}активен (PID: $pid)${NC}"
|
|
else
|
|
echo -e " Стрим: ${RED}не активен${NC}"
|
|
fi
|
|
}
|
|
|
|
# ─── Меню выбора профиля ───
|
|
menu() {
|
|
echo -e "${CYAN}═══ PhoneStream — Выбери профиль ═══${NC}"
|
|
echo ""
|
|
for i in "${!PROFILES[@]}"; do
|
|
local name=$(echo "${PROFILES[$i]}" | cut -d'|' -f1)
|
|
local res=$(echo "${PROFILES[$i]}" | cut -d'|' -f2)
|
|
local fps=$(echo "${PROFILES[$i]}" | cut -d'|' -f3)
|
|
if [ "$i" -eq "$DEFAULT_PROFILE" ]; then
|
|
echo -e " ${GREEN}$i)${NC} $name ${YELLOW}[по умолчанию]${NC}"
|
|
else
|
|
echo -e " ${GREEN}$i)${NC} $name"
|
|
fi
|
|
done
|
|
echo ""
|
|
read -p " → Выбор [0]: " choice
|
|
choice=${choice:-$DEFAULT_PROFILE}
|
|
|
|
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -lt "${#PROFILES[@]}" ]; then
|
|
start_stream "$choice"
|
|
else
|
|
err "Неверный выбор"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ─── Точка входа ───
|
|
case "${1:-start}" in
|
|
start)
|
|
start_stream "$2"
|
|
;;
|
|
stop)
|
|
stop_stream
|
|
;;
|
|
restart)
|
|
stop_stream
|
|
sleep 1
|
|
start_stream "$2"
|
|
;;
|
|
status)
|
|
status
|
|
;;
|
|
menu)
|
|
menu
|
|
;;
|
|
*)
|
|
echo "Использование: $0 {start|stop|restart|status|menu} [номер_профиля]"
|
|
echo ""
|
|
echo " Профили:"
|
|
for i in "${!PROFILES[@]}"; do
|
|
local name=$(echo "${PROFILES[$i]}" | cut -d'|' -f1)
|
|
echo " $i — $name"
|
|
done
|
|
echo ""
|
|
echo " Примеры:"
|
|
echo " $0 # Запуск с максимальным профилем"
|
|
echo " $0 start 3 # Запуск Full HD"
|
|
echo " $0 stop # Остановка"
|
|
echo " $0 menu # Интерактивный выбор"
|
|
exit 1
|
|
;;
|
|
esac
|