diff --git a/control.sh b/control.sh index fff2ecc..49c2727 100755 --- a/control.sh +++ b/control.sh @@ -1,62 +1,84 @@ #!/system/bin/sh CONFIG_FILE="/data/local/tmp/virtualcam_config.json" MODDIR=$(dirname "$0") +LIB_DST="/data/local/tmp/virtualcam_libs" +FFMPEG_BIN="/data/local/tmp/ffmpeg" load_config() { - [ -f "$CONFIG_FILE" ] && . /data/adb/modules/alias/system/bin/shflags 2>/dev/null || true + [ -f "$CONFIG_FILE" ] || return SOURCE_TYPE=$(grep -o '"source_type":"[^"]*"' "$CONFIG_FILE" 2>/dev/null | cut -d'"' -f4) RTSP_URL=$(grep -o '"rtsp_url":"[^"]*"' "$CONFIG_FILE" 2>/dev/null | cut -d'"' -f4) MP4_PATH=$(grep -o '"mp4_path":"[^"]*"' "$CONFIG_FILE" 2>/dev/null | cut -d'"' -f4) RES_W=$(grep -o '"res_w":[0-9]*' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2) RES_H=$(grep -o '"res_h":[0-9]*' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2) FPS=$(grep -o '"fps":[0-9]*' "$CONFIG_FILE" 2>/dev/null | cut -d: -f2) - : ${SOURCE_TYPE:=rtsp} ${RTSP_URL:=rtsp://192.168.1.100:8554/stream} - : ${MP4_PATH:=/sdcard/Download/video.mp4} ${RES_W:=1920} ${RES_H:=1080} ${FPS:=30} + : ${SOURCE_TYPE:=mp4} ${RTSP_URL:=rtsp://192.168.1.100:8554/stream} + : ${MP4_PATH:=/sdcard/Movies/2.mp4} ${RES_W:=1920} ${RES_H:=1080} ${FPS:=30} } case "${1:-help}" in status) - PROVIDER_PID=$(pgrep -f virtual_camera_provider | head -1) - FEEDER_PID=$(pgrep -f "ffmpeg.*video0\|gst-launch.*video0" | head -1) + PROVIDER_PID=$(pgrep -f "virtual_camera_provider$" | head -1) + FEEDER_PID=$(pgrep -f "ffmpeg.*video0" | head -1) V4L2=$(lsmod 2>/dev/null | grep -q v4l2loopback && echo 1 || echo 0) SVC=$(service list 2>/dev/null | grep -q "virtual/0" && echo 1 || echo 0) VIDEO=$(cat /sys/class/video4linux/video0/name 2>/dev/null || echo "not found") echo "{\"provider_pid\":${PROVIDER_PID:-0},\"feeder_pid\":${FEEDER_PID:-0},\"v4l2_loaded\":$V4L2,\"service_registered\":$SVC,\"video0\":\"$VIDEO\"}" ;; start) - insmod "$MODDIR/v4l2loopback.ko" devices=1 video_nr=0 card_label="VirtualCam" exclusive_caps=1 2>/dev/null || true - pkill -9 -f virtual_camera_provider 2>/dev/null || true - "$MODDIR/vendor/bin/virtual_camera_provider" -d /dev/video0 -w 1920 -h 1080 -i virtual & + rmmod v4l2loopback 2>/dev/null; sleep 1 + insmod "$MODDIR/v4l2loopback.ko" devices=1 video_nr=0 card_label="VirtualCam" 2>/dev/null || true + pkill -9 -f "virtual_camera_provider$" 2>/dev/null || true + mkdir -p "$LIB_DST" 2>/dev/null + cp "$MODDIR/vendor/lib64"/*.so "$LIB_DST/" 2>/dev/null + nohup env LD_LIBRARY_PATH="$LIB_DST" "$MODDIR/vendor/bin/virtual_camera_provider" \ + -d /dev/video0 -w 1920 -h 1080 -i virtual > /dev/null 2>&1 & echo '{"status":"started"}' ;; stop) - pkill -9 -f virtual_camera_provider 2>/dev/null || true + pkill -9 -f "virtual_camera_provider$" 2>/dev/null || true + pkill -9 cameraserver 2>/dev/null || true echo '{"status":"stopped"}' ;; + restart) + pkill -9 -f "virtual_camera_provider$" 2>/dev/null || true + pkill -9 cameraserver 2>/dev/null || true + sleep 2 + mkdir -p "$LIB_DST" 2>/dev/null + cp "$MODDIR/vendor/lib64"/*.so "$LIB_DST/" 2>/dev/null + nohup env LD_LIBRARY_PATH="$LIB_DST" "$MODDIR/vendor/bin/virtual_camera_provider" \ + -d /dev/video0 -w 1920 -h 1080 -i virtual > /dev/null 2>&1 & + echo '{"status":"restarted"}' + ;; start_feeder) load_config - pkill -9 -f "ffmpeg.*video0\|gst-launch.*video0" 2>/dev/null || true + pkill -9 -f "ffmpeg.*video0" 2>/dev/null || true + if command -v "$FFMPEG_BIN" >/dev/null 2>&1; then + FF="$FFMPEG_BIN" + elif command -v ffmpeg >/dev/null 2>&1; then + FF="ffmpeg" + else + echo '{"status":"error","message":"ffmpeg not found"}' + exit 1 + fi if [ "$SOURCE_TYPE" = "rtsp" ]; then - ffmpeg -rtsp_transport tcp -i "$RTSP_URL" -f v4l2 -input_format nv21 \ - -video_size "${RES_W}x${RES_H}" -framerate "$FPS" -pix_fmt nv21 /dev/video0 & - elif [ "$SOURCE_TYPE" = "mp4" -a -f "$MP4_PATH" ]; then - ffmpeg -stream_loop -1 -re -i "$MP4_PATH" -f v4l2 -input_format nv21 \ - -video_size "${RES_W}x${RES_H}" -framerate "$FPS" -pix_fmt nv21 /dev/video0 & + nohup "$FF" -rtsp_transport tcp -i "$RTSP_URL" \ + -f v4l2 -pix_fmt yuv420p /dev/video0 > /dev/null 2>&1 & + elif [ "$SOURCE_TYPE" = "mp4" ] && [ -f "$MP4_PATH" ]; then + nohup "$FF" -stream_loop -1 -re -i "$MP4_PATH" \ + -f v4l2 -pix_fmt yuv420p /dev/video0 > /dev/null 2>&1 & fi echo '{"status":"feeder_started"}' ;; stop_feeder) - pkill -9 -f "ffmpeg.*video0\|gst-launch.*video0" 2>/dev/null || true + pkill -9 -f "ffmpeg.*video0" 2>/dev/null || true echo '{"status":"feeder_stopped"}' ;; get_config) load_config - echo "{\"source_type\":\"$SOURCE_TYPE\",\"rtsp_url\":\"$RTSP_URL\",\"mp4_path\":\"$MP4_PATH\",\"res_w\":$RES_W,\"res_h\":$RES_H,\"fps\":$FPS}" + echo "{\"source_type\":\"$SOURCE_TYPE\",\"rtsp_url\":\"$RTSP_URL\",\"mp4_path\":\"$MP4_PATH\",\"res_w\":${RES_W:-1920},\"res_h\":${RES_H:-1080},\"fps\":${FPS:-30}}" ;; - set_config|save) - cat > "$CONFIG_FILE" << DATA -{"source_type":"$2","rtsp_url":"$3","mp4_path":"$4","res_w":$5,"res_h":$6,"fps":$7} -DATA - echo '{"status":"saved"}' + *) + echo "Usage: $0 {status|start|stop|restart|start_feeder|stop_feeder|get_config}" ;; -esac +esac \ No newline at end of file diff --git a/module.prop b/module.prop index f9b86b9..fd76d18 100644 --- a/module.prop +++ b/module.prop @@ -1,6 +1,7 @@ id=virtualcam name=Virtual Camera (RTSP/MP4) -version=3.0 -versionCode=3 +version=3.1 +versionCode=4 author=sisyphus -description=Replaces Pixel 9A cameras with virtual cameras backed by v4l2loopback. Feeds RTSP stream or MP4 video. FULL hardware level with 25+ metadata entries, FPS ranges, AE/AWB/AF modes, stabilization. WebUI for configuration. AOSP-built AIDL HAL3 provider. +description=Virtual Camera HAL3 - replaces Pixel 9A cameras with v4l2loopback backed virtual cameras. FULL hardware level, complete metadata. RTSP/MP4 feed. WebUI. +web=true diff --git a/post-fs-data.sh b/post-fs-data.sh index b2a74f0..090a716 100755 --- a/post-fs-data.sh +++ b/post-fs-data.sh @@ -11,7 +11,7 @@ kill -9 $(pgrep -f camera-provider-2-7-google) 2>/dev/null || true # Reload v4l2loopback (rmmod first to clear stale params like exclusive_caps) rmmod v4l2loopback 2>/dev/null || true sleep 1 -insmod "$MODDIR/v4l2loopback.ko" devices=1 video_nr=0 card_label="VirtualCam" 2>/dev/null || true +insmod "$MODDIR/v4l2loopback.ko" devices=2 video_nr=0,1 card_label="VirtualCam" 2>/dev/null || true # Copy provider binary and AIDL libs PROVIDER_DST="/data/local/tmp/virtual_camera_provider" diff --git a/vendor/bin/virtual_camera_provider b/vendor/bin/virtual_camera_provider index 054041a..8632d9b 100755 Binary files a/vendor/bin/virtual_camera_provider and b/vendor/bin/virtual_camera_provider differ diff --git a/webui/index.html b/webui/index.html index 297ecd6..541a28c 100644 --- a/webui/index.html +++ b/webui/index.html @@ -29,6 +29,7 @@
+ diff --git a/webui/script.js b/webui/script.js index 3c475ec..447f72e 100644 --- a/webui/script.js +++ b/webui/script.js @@ -3,6 +3,18 @@ function log(m,t){const e=document.getElementById('console');const d=new Date(); e.innerHTML+=`
[${d.toLocaleTimeString()}] ${m}
`;e.scrollTop=e.scrollHeight} async function exec(c){try{return await KSU.exec(c)}catch(e){log('exec error','err');return null}} +async function loadConfig(){ + const r=await exec(CMD+' get_config');if(!r)return; + try{const c=JSON.parse(r); + document.querySelector(`.src-btn[data-src="${c.source_type}"]`).click(); + document.getElementById('rtsp-url').value=c.rtsp_url||''; + document.getElementById('mp4-path').value=c.mp4_path||''; + document.getElementById('res-w').value=c.res_w||1920; + document.getElementById('res-h').value=c.res_h||1080; + document.getElementById('fps').value=c.fps||30; + }catch(e){} +} + async function status(){ const r=await exec(CMD+' status');if(!r)return; try{ @@ -18,6 +30,7 @@ async function status(){ } document.addEventListener('DOMContentLoaded',()=>{ + loadConfig(); status(); document.querySelectorAll('.src-btn').forEach(b=>{ b.addEventListener('click',()=>{ @@ -38,11 +51,14 @@ document.addEventListener('DOMContentLoaded',()=>{ log('Config saved','ok'); }); document.getElementById('btn-start').addEventListener('click',async()=>{ - log('Starting provider...','info');await exec(CMD+' start');log('Provider started','ok');setTimeout(status,2000); + log('Starting provider...','info');await exec(CMD+' start');log('Provider started','ok');setTimeout(status,3000); }); document.getElementById('btn-stop').addEventListener('click',async()=>{ log('Stopping...','info');await exec(CMD+' stop');log('Stopped','ok');status(); }); + document.getElementById('btn-restart').addEventListener('click',async()=>{ + log('Restarting provider...','info');await exec(CMD+' restart');log('Provider restarted','ok');setTimeout(status,3000); + }); document.getElementById('btn-feed').addEventListener('click',async()=>{ log('Starting feeder...','info');await exec(CMD+' start_feeder');log('Feeder started','ok');setTimeout(status,2000); }); @@ -50,4 +66,4 @@ document.addEventListener('DOMContentLoaded',()=>{ log('Stopping feeder...','info');await exec(CMD+' stop_feeder');log('Feeder stopped','ok');status(); }); document.getElementById('btn-refresh').addEventListener('click',status); -}); +}); \ No newline at end of file diff --git a/webui/style.css b/webui/style.css index 3ad6bdd..481da30 100644 --- a/webui/style.css +++ b/webui/style.css @@ -23,5 +23,6 @@ input{width:100%;padding:10px;border:1px solid #30363d;border-radius:6px;backgro .grid{display:grid;grid-template-columns:1fr 1fr;gap:8px} .btn{padding:12px;border:1px solid #30363d;border-radius:6px;background:#21262d;color:#c9d1d9;cursor:pointer;text-align:center} .btn.danger{border-color:#f8514944;color:#f85149} +.btn.warning{border-color:#d2992244;color:#d29922} .console{background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:10px;height:150px;overflow-y:auto;font-family:monospace;font-size:12px;color:#8b949e} .console .ok{color:#3fb950}.console .err{color:#f85149}.console .info{color:#58a6ff}