Initial commit: phonestream project
This commit is contained in:
Executable
+256
@@ -0,0 +1,256 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user