Hoi

Modify Bash Script

I found a good website tldp.org, It contains detailed information about the Shell.

Kill Streamer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
killer(){
echo "Am I running?"
if [ -z "$DEVICE" ]; then printf "Please specify a device.\n" && exit 1; fi
if [ ! -e "/var/tmp/camera/${DEVICE/"/dev/"/"dev-"}" ]; then printf "Device ${DEVICE/"/dev/"/"dev-"} not found.\n" && exit 1; fi # [1]
echo "Am I running?"
_int=0
vars="$GSTLAUNCH_PID $CAPTURE_STREAM ${DEFAULT_SOURCE} ${ECANCEL_ID} ${SINK_ID} $MODE $ADB $ADB_FLAGS $PORT"

for var in $(sed 's/;/\n/g' "/var/tmp/camera/${DEVICE/"/dev/"/"dev-"}") # [2]
do
echo "Am I running?"
echo $_int
vars[int]=var
((++_int))
done
echo "Am I running?"
rm "${DEVICE/"/dev/"/"dev-"}"
kill $GSTLAUNCH_PID > /dev/null 2>&1 || echo ""
if [ $CAPTURE_STREAM = a -o $CAPTURE_STREAM = av ]; then
pactl set-default-source ${DEFAULT_SOURCE}
pactl unload-module ${ECANCEL_ID}
pactl unload-module ${SINK_ID}
fi

# Remove the port forwarding, to avoid issues on the next run
if [ $MODE = adb ]; then "$ADB" $ADB_FLAGS forward --remove tcp:$PORT; fi

printf "Disconnected from IP Webcam. Have a nice day!\n"
}

Save Streamer Variable

1
2
3
if [ ! -d "/var/tmp/camera" ]; then mkdir -p "/var/tmp/camera"; fi

echo "$GSTLAUNCH_PID;$CAPTURE_STREAM;${DEFAULT_SOURCE};${ECANCEL_ID};${SINK_ID};$MODE;$ADB;$ADB_FLAGS;$PORT" > "/var/tmp/camera/${DEVICE/"/dev/"/"dev-"}" # [3]

[1] Mistake

Parameter Expansion can’t be used as variable,

  • $_DEVICE = ${DEVICE/"/dev/"/"dev-"}
  • $_DEVICE = "${DEVICE/"/dev/"/"dev-"}"
  • $_DEVICE = $(echo ${DEVICE/"/dev/"/"dev-"})

either not work.

They result in

1
/usr/bin/camera: line 531: /var/tmp/camera/: Is a directory

or

1
/usr/bin/camera: line 530: dev-video0: command not found

Because I named the variable in the wrong way.

And I can do this

_DEVICE=$(sed 's/\/dev\//dev-/' <<< "$DEVICE"), the same result but using sed.

[2] [3]

Depending on whether the audio sink was initialized, not every variable has its value.
In my situation:

1
2
❯ cat /var/tmp/camera/dev-video0
11877;v;;;;wifi;/usr/bin/adb;;8080

that is what [3] echo, when it read by [2], the empty value part (;;;;) will be substituted for four spaces, which means to bash is a single splitter, causing values after that are assigned to the wrong keys.

So I use source later.

Audio Streaming

PulseAudio creates a Source named.monitor for sinking devices.
But it won’t show up as an input on the KDE user interface.
By using module-remap-source we can create a virtual input device that remaps the null-sink to a source.

But I don’t set it as default because of its latency.

1
2
3
4
5
6
7
8
9
10
11
12
13
if [ -z $SINK_ID ] ; then
SINK_ID=$(pactl load-module module-null-sink \
sink_name="$SINK_NAME" \
$PA_AUDIO_CAPS \
sink_properties="device.description='IPWebCam.AudioSink'") # [1]

SOURCE_ID=$(pactl load-module module-remap-source \
source_name="${SINK_NAME}-Source" \
$PA_AUDIO_CAPS \
master="${SINK_NAME}.monitor" \
source_properties="device.description='IPWebCam.Microphone'") # [1]
pactl set-default-source ${SINK_NAME}.monitor
fi

[1] I want to write device.description with Space rather than ‘.’, but the situation doesn’t allow it.

result

[ ] and [[ ]]

1
2
[[ $KILL == 1 ]] && killer
[[ $FLIP_METHOD == "help" ]] && echo "$FLIP_HELP" && exit 0

I don’t need to declare KILL and FILP_METHOD and assign its value in advance if I use [[ ]]. If not, it will show unary operator expected.

Final

@felicia-wen
This commit includes:
9eb6d24

  • Stdout only
  • A new method to kill streaming sessions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
❯ camera --use-wifi 192.168.1.150 -d /dev/video1
IP Webcam audio is streaming through pulseaudio sink 'ipwebcam'.
IP Webcam video is streaming through v4l2loopback device /dev/video1.
You can now open your videochat app.
❯ camera --use-wifi 192.168.1.150 -d /dev/video2
IP Webcam audio is streaming through pulseaudio sink 'ipwebcam'.
IP Webcam video is streaming through v4l2loopback device /dev/video2.
You can now open your videochat app.
❯ camera --use-wifi 192.168.1.150 -d /dev/video3
IP Webcam audio is streaming through pulseaudio sink 'ipwebcam'.
IP Webcam video is streaming through v4l2loopback device /dev/video3.
You can now open your videochat app.
❯ camera --use-wifi 192.168.1.150 -d /dev/video1
IP Webcam audio is streaming through pulseaudio sink 'ipwebcam'.
IP Webcam video is streaming through v4l2loopback device /dev/video1.
You can now open your videochat app.
❯ camera --use-wifi 192.168.1.150 -d /dev/video1 -k
Cleaning session(s):
/var/tmp/camera/dev-video1 /var/tmp/camera/dev-video1_1
Disconnected from IP Webcam. Have a nice day!
❯ camera --use-wifi 192.168.1.150 -d /dev/video3 -k
Cleaning session(s):
/var/tmp/camera/dev-video3
Disconnected from IP Webcam. Have a nice day!
❯ camera --use-wifi 192.168.1.150 -d /dev/video2 -k
Cleaning session(s):
/var/tmp/camera/dev-video2
Disconnected from IP Webcam. Have a nice day!

~/Project/co-work/ipcam-gst/ipwebcam-gst master ⇡1 !1 ❯
  • Flip help
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
❯ camera -f help

Usage: ipcam --filp [flip method]

[flip method] is none by default. Here are some values you can try
out (from gst/videofilter/gstvideoflip.c):

- clockwise: clockwise 90 degrees
- rotate-180: 180 degrees
- counterclockwise: counter-clockwise 90 degrees
- horizontal-flip: flip horizontally
- vertical-flip: flip vertically
- upper-left-diagonal: flip across upper-left/lower-right diagonal
- upper-right-diagonal: flip across upper-right/lower-left diagonal

However, some of these flip methods do not seem to work. In
particular, those which change the picture size, such as clockwise
or counterclockwise. *-flip and rotate-180 do work, though.

~/Project/co-work/ipcam-gst/ipwebcam-gst master ⇡1 !1 ❯
  • log saving
1
2
❯ ls /var/tmp/camera/feed*
/var/tmp/camera/feed-1658672663.log /var/tmp/camera/feed-1658672713.log
  • Audio streaming check
  • etc.

Add system.d Unit

Wants

a Wants b, but it can live without b.

Requires

a Requires b, no b it will fail.

After, Before

Let a After b, b Before a.
So who Wants will be happy, who Requires can live.
Please let After, Before always be with Requires, Wants.

Also, there’s a WantedBy, which basically is the reversed Wants, if b is WantedBy a, and b is enabled, then a will let b start After or Before a.

Example: Execute After hibernation.

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=After Hibernate Scripts
After=hibernate.target

[Service]
Type=oneshot
ExecStart=systemctl --user restart microphone

[Install]
WantedBy=hibernate.target

Type

oneshot, simple

Execute the process in ExecStart, Execute the process in ExecStop once the main process is exited with 0.

I faced the service stop right after the start because of this.

Add RemainAfterExit=yes to prevent ExecStop from being executed after ExecStart exit, or use forking.

oneshot can have many Exec but simple don’t.
oneshots were executed one by one, simples are parallel.

They both treat commands as the main process.

forking

Same as simple, but treats command as sub-process, ExecStop is only executed if every sub-process in ExecStart is exited with 0.

Howdy Integration

Because always turning on the camera generates a lot of heat, which led my phone to stop charging and drain the battery until it powers off, so I edit the pam.py to start streaming only when Howdy starts, I made this simple editing hard at first because I didn’t notice that pam.py is running on Python 2.

It seems like polkit and sddm don’t allow messages to print directly but via pam conversation, otherwise, it will fail with like Unknown Messages error.[1]

Start and Stop the Camera

pam.py

1
2
3
4
5
6
7
8
9
10
11
def cameraSwitch(pamh, arg):
try:
if arg == "on":
pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, "Camera is Switching on."))
pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, subprocess.check_output("/usr/bin/camera -v -d /dev/video3 --use-wifi 192.168.1.150", shell=True, stderr=subprocess.STDOUT)))
if arg == "off":
pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, "Camera is Switching off."))
pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, subprocess.check_output("camera -k -d /dev/video3", shell=True, stderr=subprocess.STDOUT)))
except subprocess.CalledProcessError:
pamh.conversation(pamh.Message(pamh.PAM_ERROR_MSG, "Failure, camera switch {} fail.".format(arg)))
return pamh.PAM_SYSTEM_ERR

Add Retries

compare.py

Add retries, because the camera won’t work in the meantime it connects.

1
2
3
4
5
6
7
8
9
10
ret, frame = self.internal.read()
i = 0
while not ret and i<=5:
if i == 5:
#print(_("Failed to read camera specified in the 'device_path' config option, aborting")) # [1]
sys.exit(1)
sleep(i*.5)
ret, frame = self.internal.read()

i+=1

pam.py

1
2
3
4
5
6
7
8
9
# Run compare as python3 subprocess to circumvent python version and import issues
status = subprocess.call(["/usr/bin/python3", os.path.dirname(os.path.abspath(__file__)) + "/compare.py", pamh.get_user()])

i=0
while i<=5 and status != 0:
#pamh.conversation(pamh.Message(pamh.PAM_TEXT_INFO, "Retrying..."))
status = subprocess.call(["/usr/bin/python3", os.path.dirname(os.path.abspath(__file__)) + "/compare.py", pamh.get_user()])
i+=1
if i == 5 and status != 0: cameraSwitch(pamh, "off")

Hoi

I wrapped up the configurations and upload them here. Hoi.

I may make a PKGBUILD for AUR if I still have interests later.


Hoi
https://lcia.eu.org/2022/07/24/howdy的安装经历/
Author
mingww64
Posted on
July 24, 2022
Licensed under