#!/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