目前,生活中很多事情都可以在電腦前完成,讀書、寫程序、聽音樂、看視頻等。如果也可以在電腦上收看有線電視節目的話,那就更好了。為此,我購買了圓剛視頻采集卡AverMedia C725B。如下圖所示。
官方給出的此卡介紹為(詳見這裡):
C725標清采集卡是一張支持AV端子、S端子以及立體聲輸入的PCI-E撷取卡,可將PAL、NTSC和SECAM等模擬格式影像數字化,撷取並另存為 無壓縮的AVI格式檔案。C725標清采集卡隨附的軟件開發工具包(SDK)提供常用功能,能幫助開發者或系統整合商輕松且有效率地完成工作。此外,這套 SDK可兼容於Visual C++和 Visual Basic等主流程序語言,讓開發者可更輕易上手。
官方聲稱支持Linux,而且驅動程序需要向其索取,網站不提供下載。然而,AverMedia C725是否真的可以在自己的Debian Wheezy(3.2.0-4-amd64 #1 SMP Debian 3.2.51-1 x86_64 GNU/Linux)上工作就不好說了,只好冒點風險試一下。
在安裝好AverMedia PCI卡後,首先向官方聯系取得驅動程序。需要注意的是,一定要獲得與自己的Linux內核版本一致的驅動,否則編譯極有可能不通過。我自己就是在嘗試了官方默認提供的適用於老版本內核的驅動失敗後,才要求圓剛的技術人員重新為自己編譯了一個適用於Linux 3.2.51的版本。有了官方提供的驅動,再安裝linux-source包與linux-headers包,就可以開始編譯驅動了。首先,需要導出環境變量C_INCLUDE_PATH,其中包含了linux-source提供的dvb相關文件:
export C_INCLUDE_PATH=/usr/include/:/usr/src/linux-source-3.2/drivers/media/dvb/dvb-core/:/usr/src/linux-source-3.2/drivers/media/dvb/frontends/
之後的編譯過程就是通常的make三步曲,一切都很順利。
有了硬件基礎後,電視播放軟件我選擇使用mplayer,錄制視頻則是mencoder。由於命令行所需的參數很多,我寫了一個腳本程序rtv.sh,可以方便的播放或錄制節目。通過指定命令行參數watch、nowatch、onlywatch,該腳本可以在三種模式下運行:
該腳本用到了如下幾個程序:
腳本程序的執行過程是:首先檢測用戶的命令行參數輸入,然後查看是否已有mencoder、tee、mplayer進程運行。若存在,則表明已經在播放或錄制節目了,從而提示用戶後退出;否則,執行如下的流程:
腳本程序的源代碼為:
#!/bin/bash
script_name="rtv.sh"
script_usage=$(cat <<EOF
rtv MODE [FILE]
EOF
)
script_function=$(cat <<EOF
Record or watch TV. MODE can be 'watch', 'onlywatch', 'nowatch'. When 'watch' or 'nowatch' is specified, the file name must be provided.
EOF
)
script_doc=$(cat <<EOF
-h Display this help.
EOF
)
script_examples=$(cat <<EOF
rtv nowatch test.avi
rtv watch test.avi
rtv onlywatch
EOF
)
state_prefix="==="
warning_prefix="***"
error_prefix="!!!"
function display_help() {
if [ -n "$script_usage" ]; then
echo -e "Usage: $script_usage"
fi
if [ -n "$script_function" ]; then
echo -e "$script_function"
fi
if [ -n "$script_doc" ] ; then
echo -e "\n$script_doc"
fi
if [ -n "$script_examples" ]; then
echo -e "\nExamples"
echo -e "$script_examples"
fi
}
# Process command options
while getopts ":h" opt; do
case $opt in
h ) display_help
exit 0 ;;
\? ) display_help
exit 1 ;;
esac
done
shift $(($OPTIND - 1))
# Define a function for returning a process id
function get_pid_by_name()
{
local process_str
echo "Searching process $1..."
process_str=`ps aux | grep "$1" | tr --squeeze-repeats '[:blank:]+' '\t' | cut -f 2`
if [ -n "$process_str" ]; then
# The process for grep appears in the second field
process_str=`echo $process_str | cut -s -d ' ' -f 1`
if [ -n "$process_str" ]; then
temp_pid=$process_str
echo "The process id is $temp_pid!"
else
echo "The process $1 cannot be found, perfect!"
fi
else
echo "The process $1 cannot be found, perfect!"
fi
}
# Start execute the command
if [ $OSTYPE = 'linux-gnu' ] && [ `hostname` = "QuantumHome" ]; then
if [ -z "$1" ]; then
echo "$error_prefix Please specify the recording and watching mode: watch|nowatch|onlywatch"
exit 0
fi
if [ "$1" != "onlywatch" ] && [ -z "$2" ]; then
echo "$error_prefix Please provide the video file name to be saved!"
exit 0
fi
# Declare the pid as integers
declare -i temp_pid=-1 mplayer_pid=-1 mencoder_pid=-1 tee_pid=-1
get_pid_by_name mencoder
mencoder_pid=$temp_pid
temp_pid=-1
get_pid_by_name tee
tee_pid=$temp_pid
temp_pid=-1
get_pid_by_name mplayer
mplayer_pid=$temp_pid
temp_pid=-1
if [ $(($mencoder_pid!=-1 && $mplayer_pid!=-1 && $tee_pid!=-1)) = 1 ]; then
echo "$error_prefix A tv recording or watching activity is now working, please exit it first!"
exit 0
fi
# Create FIFO named pipe
if [ ! -e "/tmp/tv.fifo" ]; then
echo "$state_prefix FIFO does not exist, now being created..."
mkfifo /tmp/tv.fifo && echo "$state_prefix Creating FIFO successful!"
fi
# Start tee and mplayer
case "$1" in
watch ) echo "$state_prefix Start recording tv and watch it using mplayer..."
# Note: sudo must be used in order to make mplayer appear
cat /tmp/tv.fifo | tee -a "${2%.avi}.avi" | sudo -u orlando DISPLAY=:0.0 mplayer -cache 51200 -framedrop -ao sdl -vo xv - & ;;
nowatch ) echo "$state_prefix Start recording tv without watching it..."
cat /tmp/tv.fifo | tee -a "${2%.avi}.avi" & ;;
onlywatch ) echo "$state_prefix Start watching tv without recording it..."
# Note: "tee -a -" will not work here
cat /tmp/tv.fifo | tee | sudo -u orlando DISPLAY=:0.0 mplayer -cache 51200 -framedrop -ao sdl -vo xv - & ;;
* ) echo "$error_prefix Please specify the recording and watching mode: watch|nowatch|onlywatch"
exit 0;
esac
# Start mencoder to feed the video stream into FIFO
echo "$state_prefix Now start mencoder to capture tv..."
mencoder tv:// -tv driver=v4l2:device=/dev/video0:norm=PAL:alsa:adevice=hw.2,0:amode=1:audiorate=48000:forceaudio:volume=100:immediatemode=0:normid=8:input=1:buffersize=1024:width=768:height=576:outfmt=i420 -oac mp3lame -lameopts fast:preset=standard -ovc lavc -lavcopts vcodec=mpeg4:vhq:vbitrate=1800 -o /tmp/tv.fifo
else
echo "$warning_prefix Operating system or host name is not supported!"
fi
該腳本程序中,mencoder的參數較為復雜,這裡對其解釋如下:
需要說明的是,由rtv.sh錄制的avi文件沒有索引,所以直接用mplayer播放無法快進與快退。為此,可以用下面的腳本程序自動生成索引後再播放(其中,參數$1為avi視頻文件名):
#!/bin/bash mplayer -forceidx -saveidx "${1%avi}idx" "$1"
若想回放已經錄制過且生成了索引的視頻,則可以使用下面的腳本程序(其中,參數$1為avi視頻文件名):
#!/bin/bash mplayer -loadidx "${1%avi}idx" "$1"
到這裡,用於播放與錄制電視節目的腳本程序rtv.sh就介紹完了。下面再來介紹用於停止播放或錄制的腳本程序stop_rtv.sh。這個就比較簡單了,無非就是殺死相應的進程而已。源代碼如下:
#!/bin/bash # Define a function for returning a process id function get_pid_by_name() { local process_str echo "Searching process $1..." process_str=`ps aux | grep "$1" | tr --squeeze-repeats '[:blank:]+' '\t' | cut -f 2` if [ -n "$process_str" ]; then # The process for grep appears in the second field process_str=`echo $process_str | cut -s -d ' ' -f 1` if [ -n "$process_str" ]; then temp_pid=$process_str echo "The process id is $temp_pid!" else echo "The process $1 cannot be found, perfect!" fi else echo "The process $1 cannot be found, perfect!" fi } # Declare pid as integers declare -i temp_pid=-1 mplayer_pid=-1 mencoder_pid=-1 tee_pid=-1 # Kill mencoder process get_pid_by_name mencoder mencoder_pid=$temp_pid temp_pid=-1 if [ $(($mencoder_pid!=-1)) = 1 ]; then # The SIGINT has no effect on mencoder processes while SIGKILL will cause loss of /dev/video0 node kill -2 $mencoder_pid && echo "mencoder has been killed!" else echo "mencoder process does not exist!" fi # Kill tee process get_pid_by_name tee tee_pid=$temp_pid temp_pid=-1 if [ $(($tee_pid!=-1)) = 1 ]; then kill -2 $tee_pid && echo "tee has been killed!" else echo "tee process does not exist!" fi # Kill mplayer process if not in nowatch mode if [ "$1" != "nowatch" ]; then get_pid_by_name mplayer mplayer_pid=$temp_pid temp_pid=-1 if [ $(($mplayer_pid!=-1)) = 1 ]; then # Note: mplayer is started by using sudo, therefore when killing it, sudo should also be used sudo -u orlando kill -2 $mplayer_pid && echo "mplayer has been killed!" else echo "mplayer process does not exist!" fi fi echo "TV recording and playing have been stopped!"
有了rtv.sh與stop_rtv.sh兩個腳本,再將其與at命令結合,則可以實現定時錄制與播放節目了。例如:
$ at 20:00 today warning: commands will be executed using /bin/sh at> rtv watch 我愛發明 at> <EOT> # Input Ctrl+D job 21 at Wed Feb 5 20:00:00 2014 $ at 21:00 today warning: commands will be executed using /bin/sh at> stop_rtv watch at> <EOT> # Input Ctrl+D job 22 at Wed Feb 5 21:00:00 2014
由於晚上播出的電視節目大部分在第二天白天會重播,因此在自己上班的同時,這些節目便可以按計劃一個不落地錄下來。同時,原本需要晚上熬夜看的節目也可以保存起來等到第二天再看。
還有一個問題就是,電視卡是插在台式機上的,所以只能在這台電腦上觀看電視節目。那麼自己在廚房做飯的時候怎麼看電視呢?比如晚上7點的新聞聯播?由於自己有iPhone,實現這個功能就不難了。首先,在電腦上運行rtv.sh程序(rtv.sh nowatch filename.avi),將電視節目錄到文件中。然後,將文件所在的目錄共享到 Samba服務器上。最後,用iPhone上的視頻播放器OPlayer訪問該服務器並在線播放即可。看到的節目會稍微有一點延遲,不過也沒有什麼太大的關系。下圖就是iPhone上看到的電視節目截圖: