2011/02/20 Counter
EpgRecの録画予約アルゴリズム"Reservation.class.php"を改善する。
真の4ch同時録画機を目指して・・・・
epgrecをデフォルトのまま使用していると、連続した番組の予約を"行う"設定にしていても、PT2を2枚挿していて、地デジ4ch同時録画が可能なはずなのに、下図の様に@→A→B→Cの順に録画予約していくと、DやEが予約できません。予約する順番を@→B→D→E→A→Cの様にAとCを後から登録すれば予約できます。なぜこのようになるかというと、デフォルトのepgrecは、連続した番組の予約を"行う"設定のときでも、新規予約の直前と直後の予約との時間調整は行いますが、@とAや、BとCの様に直前直後でない重複予約との時間調整は行いません。これではせっかく搭載しているチューナー数を生かし切れておらず、不満を生んでいました。
そこで、搭載したチューナー数を最大限に活用するために録画アルゴリズム"Reservation.class.php"の改善に取り組みました。
結果、希望通りの動作をするものができましたので、公開することにしました。
改善したアルゴリズムでは、連続した番組の予約を"行う"設定にしていれば、重複関係にある全ての予約に対して、連続した予約の時間調整を行うので、搭載されたチューナー数を最大限生かし切ることができます。
(注意)epgrecはEPGデータ取得時もチューナー資源を消費するため、同時録画チャンネル数が減ることがあります。
改善した"Reservation.class.php"は、下記のように動作します。
@→A→B→Cの順に予約していった場合、Dを予約したとき、@とAもしくはBとCの間で、重複予約の時間調整が行われます。下記の例は@とAの間で時間調整が行われた場合です。@とAの間には"録画コマンドの切り替え時間"分の間隔が開けられ、録画時間の重複が解消されます。これにより、4台のチューナーで5番組の録画ができるようなります。
さらにEの予約を行うと、BとCの間隔を開けて、6番組の録画ができるようにします。
これによって、搭載したチューナー数を最大限に生かせるはずです。
尚、注意してほしいのは"録画コマンドの切り替え時間"の設定です。設定時間が短すぎると、後続の録画に失敗するので、動作確認を充分に行って設定値を決めてくさい。また、連続した番組の予約を"行わない"に設定した場合は、時間調整は行われませんので、上記の例では、録画できるのは@、A、B、Cの4番組になります。

また、私はepgrec20100322fix1版に20100406版の一部を追加して使っていますが、20100406版の"recorder.php"は使ってません。理由は、"recorder.php"を使うと録画開始に時間がかかり、リレー録画するために"録画コマンドの切り替え時間"を長くしなければならなくなるからです。"録画コマンドの切り替え時間"はできるだけ短くしておきたいので、録画実行部は20100322版のままにしています。

"Reservation.class.php"用のパッチファイルをここにおきます。
これにより、貴重なチューナー資源を余すところなく使い切れるはずです。
もちろん、PT2x1枚の2ch環境でも同様に動作するはずです。
たぶん、20100322版とそのfix1版、fix2版および20100406版に適用できるはずです。
おきまりですが、使用の結果いかなる損害が発生したとしても当方は一切の責任を負いません。
興味のある方は自己責任で試してみてください。

"Reservation.class.php"の変更した部分のソースは下記の通りです。
2010年03月22日版なら81行目あたりから178行目あたりまでに相当します。

                        // 既存予約数 = TUNER番号
                        $tuners = ($crec->type == "GR") ? (int)($settings->gr_tuners) : (int)($settings->bs_tuners);
                        $type_str = ($crec->type == "GR") ? "type = 'GR' " : "(type = 'BS' OR type = 'CS') ";
                        
                        // 影響する予約情報を集める
                        $trecs = DBRecord::createRecords(RESERVE_TBL, "WHERE complete = '0' ".
                                                                                        "AND ".$type_str.
                                                                                        "AND starttime < '".toDatetime($end_time)."' ".
                                                                                        "AND endtime > '".toDatetime($rec_start)."'" );
                        // 情報を配列に入れる
                        for( $i = 0; $i < count($trecs) ; $i++ ) {
                                $dim_start_time[$i] = toTimestamp($trecs[$i]->starttime);
                                $dim_end_time[$i] = toTimestamp($trecs[$i]->endtime);
                        }
                        // 新規予約の値も配列に追加
                        $dim_start_time[count($trecs)] = $rec_start;
                        $dim_end_time[count($trecs)] = $end_time;
                        
                        // 配列を使って重複を調べ、重複解消を検証する
                        $battings = 0;
                        $mi = 0;
                        for( $i = 0; $i <= count($trecs) ; $i++ ) {
                                $mem_battings = 0;
                                for( $j = 0; $j <= count($trecs) ; $j++ ) {
                                        if( ( $i <> $j ) && ( $dim_start_time[$j] < $dim_end_time[$i] ) && ( $dim_end_time[$j] >= $dim_end_time[$i] ) ) {
                                                $mem_battings++; // 重複をカウント
                                        }
                                }
                                if( $mem_battings > $tuners ) { // 重複が多すぎるので予約不可
                                        throw new Exception( " 重複予約があります" );
                                }
                                // チューナー数が足りないとき、連続予約="する"なら重複解消を試みる
                                if( ( $mem_battings >= $tuners ) && ( $settings->force_cont_rec == 1 ) ) {
                                        for( $j = 0; $j <= count($trecs) ; $j++ ) {
                                                // 連続予約があるか?
                                                if( ( $i <> $j ) && ( $dim_end_time[$i] > $dim_start_time[$j] - $settings->rec_switch_time )
                                                 && ( $dim_end_time[$i] <= $dim_start_time[$j] + $settings->extra_time + $settings->former_time ) ) {
                                                        // 録画が始まっていないか?
                                                        if( $dim_start_time[$i] > ( time() + PADDING_TIME + $settings->former_time + $settings->rec_switch_time ) + 1 ) {
                                                                $mem[$mi] = $i; // 変更すべき予約IDをメモ
                                                                $dim_end_time[$i] = $dim_start_time[$j] - $settings->rec_switch_time; // 先行予約の終了時刻を早める
                                                        }
                                                        else {
                                                                $mem[$mi] = $j; // 変更すべき予約IDをメモ
                                                                $dim_start_time[$j] = $dim_end_time[$i] + $settings->rec_switch_time; // 後続予約の開始時刻を遅くする
                                                        }
                                                        $mi++;
                                                        $mem_battings--;
                                                        break;
                                                }
                                        }
                                }
                                if( $mem_battings >= $tuners ) { // 重複解消できない
                                        for( $j = 0; $j < count($trecs) ; $j++ ) {
                                                if( ( $dim_start_time[$j] < $dim_end_time[$i] ) && ( $dim_end_time[$j] >= $dim_end_time[$i] ) ) {
                                                         $msg = $msg."\n  「".$trecs[$j]->title."」";
                                                }
                                        }
                                        throw new Exception( " 予約が重複しています".$msg );
                                }
                                if( $battings < $mem_battings ) {
                                        $battings = $mem_battings;
                                }
                        }
                        
                        // ここまでくれば予約可能
                        for( $i = 0; $i < $mi ; $i++ ) { // 重複解消が必要なら実行する
                                if( $mem[$i] == count($trecs) ) { // 変更すべきは新規予約
                                        $rec_start = $dim_start_time[$mem[$i]];
                                        $end_time = $dim_end_time[$mem[$i]];
                                        $duration = $end_time - $rec_start;     // durationを計算しなおす
                                }
                                else { // 変更すべきは既存予約
                                        // 予約修正に必要な情報を取り出す
                                        $prev_id           = $trecs[$mem[$i]]->id;
                                        $prev_program_id   = $trecs[$mem[$i]]->program_id;
                                        $prev_channel_id   = $trecs[$mem[$i]]->channel_id;
                                        $prev_title        = $trecs[$mem[$i]]->title;
                                        $prev_description  = $trecs[$mem[$i]]->description;
                                        $prev_category_id  = $trecs[$mem[$i]]->category_id;
                                        $prev_starttime    = $trecs[$mem[$i]]->starttime;
                                        $prev_endtime      = $trecs[$mem[$i]]->endtime;
                                        $prev_autorec      = $trecs[$mem[$i]]->autorec;
                                        $prev_mode         = $trecs[$mem[$i]]->mode;
                                        $prev_dirty        = $trecs[$mem[$i]]->dirty;
                                        $prev_start_time = toTimestamp($prev_starttime);
                                        // 開始時刻を再設定
                                        $prev_starttime = toDatetime( $dim_start_time[$mem[$i]] + $settings->former_time );
                                        // 終了時刻を再設定
                                        $prev_endtime   = toDatetime( $dim_end_time[$mem[$i]] );
                                        // tryのネスト
                                        try {
                                                self::cancel( $prev_id );       // いったん予約取り消し
                                                self::custom(                   // 再予約
                                                        $prev_starttime,        // 開始時間Datetime型
                                                        $prev_endtime,          // 終了時間Datetime型
                                                        $prev_channel_id,       // チャンネルID
                                                        $prev_title,            // タイトル
                                                        $prev_description,      // 概要
                                                        $prev_category_id,      // カテゴリID
                                                        $prev_program_id,       // 番組ID
                                                        $prev_autorec,          // 自動録画
                                                        $prev_mode,
                                                        $prev_dirty );
                                        }
                                        catch( Exception $e ) {
                                                throw new Exception( " 予約時刻変更(再予約)に失敗しました\n  「".$prev_title."」" );
                                        }
                                }
                        }
                        
                        // チューナー番号
                        $tuner = $battings;
 

CentOS5.5+PT2+epgrecで地デジ4ch同時録画サーバーを作る。
地デジ録画サーバーをメールで録画予約できる様にする。
ホームへ戻る