And I found one particular nice script, which give option to stream webcam as PIP, and it had on line inside:
What?echo "Please setup the Audio Output (something like 'pavucontrol')"
Then what is that script for, if I need to manually change sound preference in order to grab sound.
And then I've reached top of my frustration when I couldn't find any control to do that i my volume control program.
Until... I've found that I've used GNOME Volume Control where I should use pavucontrol.
After that I've set on quest to discover how to do it from script - and nowhere I could find it.
Everywhere, after making sinks and loopbacks there was a line
That's it... I forgot to eat and used whole day for it.go to pavucontrol and connects sinks and streams
What do you need.
Particularly whole PulseAudio package (execute as root or root privileged user):
Code: Select all
aptitude install pulseaudio pulseaudio-utils pavumeter pavucontrol paman paprefs pasystray
You also need MAWK, as it's lighter than GAWK and serves it's purpose, but you can use AWK everywhere I use MAWK - no problem whatsoever.
So,lets start.
What do we want to achieve?
Grab a sound from one program (be it game, or media player - I've tested it on MPD), plus, grab mic sound (if mic is not muted in volume control - don't do it with speakers on because of powerloop between them and mic, better to use headphones).
I've used FFMPEG for that.
First, we must run source of our sound, then grabbing tool, to create recording stream that we could manipulate with PulseAudio.
So I've started playback with MPD, and then run FFMPEG:
Code: Select all
ffmpeg -y -f alsa -i pulse -ss 1 -strict -2 -c:a aac -b:a 64k /tmp/ffgrabaudio.mp4
Make new null sinks:
Code: Select all
SINK_GRAB_ID=$(pactl load-module module-null-sink sink_name="grab" sink_properties=device.description="INgrabOUT")
SINK_DUPLEX_ID=$(pactl load-module module-null-sink sink_name="duplex_out" sink_properties=device.description="duplexOUT")
Also, don't forget a "sink_properties=device.description". It can't be exact as "sink_name", but if you ever want to look into pavucontrol and tell what is what, you'll need them, because without, every null sink will be shown in pavucontrol as... "null sink" - very descriptive and informing.
Now, we need to make some loopbacks, so output of some sink would go to input another one.
When made with no parameters, it reroute both your sound output and input to input together - that's simplest thing to grab whole system sound for screencasting.
No need for null sinks at all, just one loopback module to load and all works... you will record game sound, system sound, notifications... everything.
It's as simple as
Code: Select all
pactl load-module module-loopback
So, let's back on track.
First, make loopback that put grab sink sound back to default sound output, so you can hear it - you want to play a game without a sound?
Code: Select all
MODULE1=$(pactl load-module module-loopback source="grab.monitor")
They "play" what comes to input on output.
Input is simple sink_name, output is sink_name.monitor
So we made loopback from grab sink to default output sink (PulseAudio is routing it to default output sink, but you might chose your own).
Now, interesting part, make route from grab sink to duplex sink, so sound could be mixed with mic and recorded from duplex sink:
Code: Select all
MODULE2=$(pactl load-module module-loopback source="grab.monitor" sink="duplex_out")
And then, we need reroute mic to duplex sink, so it will not be heard at default output (speakers/headphones - it actually prevents powerloop that I wrote earlier).
For that, we need default source name as well:
Code: Select all
DEFAULT_SOURCE=`pacmd dump | mawk '/set-default-source/ {print $2}'`
MODULE3=$(pactl load-module module-loopback source=${DEFAULT_SOURCE} sink="duplex_out")
Easy peasy lemon squeezy. Or I thought at first.
So, lets connect FFMPEG to duplex_out sink as first (no particular reason for that).
We need, FFMPEG stream ID and duplex_out input, or a "source" as PulseAudio call them. And we need FFMPEG client ID for finding stream of it:
Code: Select all
FFMPEG_ID=$(pactl list short clients | mawk 'tolower($3) ~ "ffmpeg" {print $1}')
FFMPEG_STREAM_ID=$(pactl list short source-outputs | mawk -v check=${FFMPEG_ID} '$3 == check {print $1}')
SOURCE_DUPLEX_ID=$(pactl list short sources | mawk '$2 ~ "duplex_out" {print $1}')
Code: Select all
pactl move-source-output ${FFMPEG_STREAM_ID} ${SOURCE_DUPLEX_ID}
Again, we need client stream ID, for that we need client ID and finally we need grab sink ID:
Code: Select all
CLIENT_ID=$(pactl list short clients | mawk 'tolower($3) ~ "mpd" {print $1}')
CLIENT_STREAM_ID=$(pactl list short sink-inputs | mawk -v clientID=${CLIENT_ID} '$3 == clientID {print $1}')
OUTPUT_SINK_ID=$(pactl list short sinks | mawk '$2 ~ "duplex_out" {print $1}')
Code: Select all
pactl move-sink-input ${CLIENT_STREAM_ID} ${OUTPUT_SINK_ID}
MPD is routed to grab sink and default sink, mic is routed to duplex sink and grab sink is also routed to duplex sink.
Why not route MPD to FFMPEG then?
Well... only sinks can get more than one route... so routing MPD to FFMPEG would cut off MPD sound from speakers and no mic recording.
After recording ,we should clean.
Simplest way is to run
Code: Select all
pulseaudio -k
Code: Select all
pactl unload-module ${MODULE3}
pactl unload-module ${MODULE2}
pactl unload-module ${MODULE1}
pactl unload-module ${SINK_DUPLEX_ID}
pactl unload-module ${SINK_GRAB_ID}
Time to make this into a script.
This is my test script to play with, enjoy:
Code: Select all
#! /bin/bash
# FFMPEG starts first, since it's streams are needed for operations
function ffmpeg_grab(){
ffmpeg -y -f alsa -i pulse -ss 1 -t 30 -strict -2 -c:a aac -b:a 64k /tmp/ffgrabaudio.mp4 &>/dev/null
}
# Confiuguration of PulseAudio for simmultaneos recording of desired application + mic (if not muted)
function PAset(){
# set name of grab sink (I've cosen to be name of grabed application)
SINK_GRAB=$(echo $1 |mawk '{gsub(/ +/,"_"); print}')
# set name of duplex sink from which all recordings will happen
SINK_DUPLEX="duplex_out"
DEFAULT_SINK=`pacmd dump | mawk '/set-default-sink/ {print $2}'`
DEFAULT_SOURCE=`pacmd dump | mawk '/set-default-source/ {print $2}'`
# make "grab" sink - to this sink application will be redirected
SINK_GRAB_ID=$(pactl load-module module-null-sink sink_name=${SINK_GRAB} sink_properties=device.description="INgrabOUT")
# echo "Grab: ${SINK_GRAB}: ${SINK_GRAB_ID}"
# make "duplex" sink - from this sink FFMPEG will record
SINK_DUPLEX_ID=$(pactl load-module module-null-sink sink_name=${SINK_DUPLEX} sink_properties=device.description="duplexOUT")
# get ID of duplex source sink - need it as source for FFMPEG
SOURCE_DUPLEX_ID=$(pactl list short sources |mawk -v check=${SINK_DUPLEX} '$2 ~ check {print $1}')
# echo "${SINK_DUPLEX}: ${SINK_DUPLEX_ID}: ${SOURCE_DUPLEX_ID}"
# connect source of "grab" sink by loopback to default output - it will allow to hear recorded application
MODULE1=$(pactl load-module module-loopback source="${SINK_GRAB}.monitor")
# connect source of "grab" sink by loopback to "duplex" sink - this will allow to record from "grab"
MODULE2=$(pactl load-module module-loopback source="${SINK_GRAB}.monitor" sink=${SINK_DUPLEX})
# connect default sources by loopback to duplex - this will redirect mic to duplex
# echo "Default source"
MODULE3=$(pactl load-module module-loopback source=${DEFAULT_SOURCE} sink=${SINK_DUPLEX})
# echo "Moved to duplex"
# get client ID of application to record from
CLIENT_ID=$(pactl list short clients |mawk -v clientName=$1 'tolower($3) ~ clientName {print $1}')
# get stream ID of application to record from
CLIENT_STREAM_ID=$(pactl list short sink-inputs |mawk -v clientID=${CLIENT_ID} '$3 == clientID {print $1}')
# get output ID of "grab" sink
OUTPUT_SINK_ID=$(pactl list short sinks |mawk -v check=${SINK_GRAB} '$2 ~ check {print $1}')
# echo "$1: ${CLIENT_ID}: ${CLIENT_STREAM_ID}: ${OUTPUT_SINK_ID}"
# move output stream of client as input to sink of "grab" - application will send sound data to "grab" sink
pactl move-sink-input ${CLIENT_STREAM_ID} ${OUTPUT_SINK_ID}
# get FFMPEG client ID
FFMPEG_ID=$(pactl list short clients |mawk -v clientName="ffmpeg" 'tolower($3) ~ clientName {print $1}')
# get FFMPEG recording stream ID
FFMPEG_STREAM_ID=$(pactl list short source-outputs |mawk -v check=${FFMPEG_ID} '$3 == check {print $1}')
# echo "FFMPEG: ${FFMPEG_ID}: ${FFMPEG_STREAM_ID}"
# move output of "duplex" sink to FFMPEG stream recording
pactl move-source-output ${FFMPEG_STREAM_ID} ${SOURCE_DUPLEX_ID}
}
function PAsystem(){
SOURCE_ID=$(pactl list short sources |mawk '/combine/ {print $1}')
[ ${SOURCE_ID} ] || SOURCE_ID=$(pactl list short sources |mawk '/alsa_output/ {print $1}')
FFMPEG_ID=$(pactl list short clients |mawk -v clientName="ffmpeg" 'tolower($3) ~ clientName {print $1}')
FFMPEG_STREAM_ID=$(pactl list short source-outputs |mawk -v check=${FFMPEG_ID} '$3 == check {print $1}')
echo "FFMPEG: ${FFMPEG_ID}: ${FFMPEG_STREAM_ID}"
pactl move-source-output ${FFMPEG_STREAM_ID} ${SOURCE_ID}
}
case $1 in
-g) ffmpeg_grab &
[[ "$2" != "system" ]] && PAset $2
;;
-c) pulseaudio -k
#pactl unload-module ${MODULE3}
#pactl unload-module ${MODULE2}
#pactl unload-module ${MODULE1}
#pactl unload-module ${SINK_DUPLEX_ID}
#pactl unload-module ${SINK_GRAB_ID}
;;
-l) if [ "$2" ]; then pactl list short clients |mawk -v clientID=$2 'tolower($3) ~ clientID {print "ID:",$1,"\t\tName:",$3}'
else pactl list short clients |mawk '{print $3}'; fi
;;
*) echo "use 'ffgrabaudio.sh -g <clientname>' to record sound from specified client"
echo "use 'ffgrabaudio.sh -g system' to record everything"
echo "use 'ffgrabaudio.sh -l' to list available clients"
echo "use 'ffgrabaudio.sh -c' to clear mess in PulseAudio"
echo "use 'ffgrabaudio.sh -h' or 'ffgrabaudio.sh' for this help"
;;
esac
exit