歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> 使用GNU/Linux播放電視節目

使用GNU/Linux播放電視節目

日期:2017/2/28 13:57:20   编辑:Linux教程

目前,生活中很多事情都可以在電腦前完成,讀書、寫程序、聽音樂、看視頻等。如果也可以在電腦上收看有線電視節目的話,那就更好了。為此,我購買了圓剛視頻采集卡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,該腳本可以在三種模式下運行:

  • watch:在播放電視的同時,將視頻存為avi文件;
  • nowatch:只錄制節目存為avi文件,不實時播放;
  • onlywatch:只用mplayer看節目而不錄制。

該腳本用到了如下幾個程序:

  • mencoder:從視頻采集卡以指定的格式獲取視頻流並將其輸出到一個名稱FIFO管道(named FIFO pipe);
  • tee:從標准輸入獲得數據流再將其轉向至標准輸出。利用管道行,可以將名稱FIFO管道的內容作為tee命令的標准輸入,而tee命令的標准輸出作為mplayer程序的標准輸入予以播放。同時,tee命令可以使用命令行參數-a filename。這樣,在將數據流送至標准輸出乃至mplayer的同時,也會將其保存至指定的文件。利用這個機制,就可以實現同時播放並錄制電視節目了;
  • mplayer:播放由tee轉來的視頻流。

腳本程序的執行過程是:首先檢測用戶的命令行參數輸入,然後查看是否已有mencoder、tee、mplayer進程運行。若存在,則表明已經在播放或錄制節目了,從而提示用戶後退出;否則,執行如下的流程:

  1. 首先,使用mkfifo創建名稱FIFO管道/tmp/tv.fifo;
  2. 其次,用cat命令輸出該管道的內容至標准輸出,利用管道行將該輸出作為tee命令的標准輸入。在錄制與播放的模式下(watch),將其分為兩路,一路送至avi文件,一路送至mplayer用於播放;在只錄制不播放的模式下(nowatch),只輸出至avi文件;在純播放模式下(onlywatch),只送至mplayer。
  3. 最後,用mencoder從AverMedia C725B采集卡在系統中的設備節點/dev/video0截取視頻流,以指定的格式輸出到之前創建的名稱管道。

腳本程序的源代碼為:

#!/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的參數較為復雜,這裡對其解釋如下:

  • driver=v4l2:使用video for linux驅動;
  • device=/dev/video0:視頻卡設備節點。只有當驅動安裝正確後,才會出現該節點;
  • norm=PAL:指定模擬電視制式,中國為PAL;
  • alsa:從ALSA捕獲音頻;
  • adevice=hw.2,0:指定音頻設備。當使用OSS音頻系統時,adevice的值為/dev/xxx,當使用ALSA音頻系統時,則為形如hw:2,0的硬件ID。需注意的是,由於命令行不能出現冒號,應將hw:2,0中的冒號改為逗號;
  • amode=1:指定聲音模式。0為單聲道,1為立體聲;
  • audiorate=48000:輸入聲音采樣率;
  • forceaudio:強制采集音頻,即便v4l庫未發現有音頻源;
  • volume=100:指定視頻采集卡上混頻器的音量;
  • immediatemode=0:0表示同時采集與緩存音頻與視頻流(mencoder的默認值),1表示只采集視頻流,而音頻流則會通過回環線路由電視卡送至聲卡(mplayer的默認值)。由於我使用的是mencoder,所以設為0;
  • normid=8:采集卡的電視標准編號;
  • buffersize=1024:采集緩沖區的大小,單位為MB
  • width=768:height=576:輸出視頻像素寬度與高度;
  • outfmt=i420:輸出視頻數據格式;
  • -oac mp3lame:指定音頻編碼器為mp3lame;
  • -lameopts fast:preset=standard:指定音頻編碼器的選項;
  • -ovc lavc:指定視頻編碼器為libavcodec;
  • -lavcopts vcodec=mpeg4:vhq:vbitrate=1800:指定視頻編碼器選項。

需要說明的是,由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上看到的電視節目截圖:

Copyright © Linux教程網 All Rights Reserved