首頁 > 軟體

使用GNU/Linux播放電視節目

2020-06-16 17:51:53

目前,生活中很多事情都可以在電腦前完成,讀書、寫程式、聽音樂、看視訊等。如果也可以在電腦上收看有線電視節目的話,那就更好了。為此,我購買了圓剛視訊採集卡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上看到的電視節目截圖:

本文永久更新連結地址http://www.linuxidc.com/Linux/2015-10/123910.htm


IT145.com E-mail:sddin#qq.com