Fix stallDurations metadata buffer, add web=true, rebuild provider
- Increase camera metadata buffer from 2048 to 4096 bytes to prevent silent truncation of stallDurations (required by Android 16) - Add web=true to module.prop for KernelSU WebUI detection - Rebuilt virtual_camera_provider with buffer fix - Updated WebUI JS with improved status display - Bump version to 3.1
This commit is contained in:
+45
-23
@@ -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
|
||||
+4
-3
@@ -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
|
||||
|
||||
+1
-1
@@ -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"
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -29,6 +29,7 @@
|
||||
<div class="grid">
|
||||
<button id="btn-start" class="btn">Start Provider</button>
|
||||
<button id="btn-stop" class="btn danger">Stop</button>
|
||||
<button id="btn-restart" class="btn warning">Restart</button>
|
||||
<button id="btn-feed" class="btn">Start Feeder</button>
|
||||
<button id="btn-unfeed" class="btn danger">Stop Feeder</button>
|
||||
<button id="btn-refresh" class="btn">Refresh</button>
|
||||
|
||||
+18
-2
@@ -3,6 +3,18 @@ function log(m,t){const e=document.getElementById('console');const d=new Date();
|
||||
e.innerHTML+=`<div class="${t||'log'}">[${d.toLocaleTimeString()}] ${m}</div>`;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);
|
||||
});
|
||||
});
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user