88520297 2015-10-08
目前,生活中很多事情都可以在电脑前完成,读书、写程序、听音乐、看视频等。如果也可以在电脑上收看有线电视节目的话,那就更好了。为此,我购买了圆刚视频采集卡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上看到的电视节目截图: