view recpt1/tssplitter_lite.c @ 98:3fd15032fd3a

tweak user interface for sid: - rename --split option as --sid - show all available service IDs along with specified SIDs.
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Wed, 10 Feb 2010 15:02:25 +0900
parents 52f8e081763d
children 3a3f15b063e1
line wrap: on
line source

/* tssplitter_lite.c -- split TS stream.

   Copyright 2009 querulous
   Copyright 2010 Naoya OYAMA <naoya.oyama@gmail.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include <fcntl.h>
#include <sys/stat.h>
#include "decoder.h"
#include "recpt1.h"
#include "tssplitter_lite.h"


/**
 * サービスID解析
 */
char** AnalyzeSid(
	char* sid)						// [in]		サービスID(カンマ区切りテキスト)
{
	int i = 0;
	char** sid_list = NULL;
	char* p;
	int CommaNum = 0;

	/* sid は次の形式の引数を許容する */
	/* 指定無し */
	/* SID[0] */
	/* SID[0],SID[1],...,SID[N-1],SID[N] */

	/*カンマの数を数える*/
	p = sid;
	while(*p != '\0')
	{
		if( *p == C_CHAR_COMMA ){
			CommaNum++;
		}
		p++;
	}

	/* sid_listの数はカンマの数+2(NULL止めするから) */
	sid_list = malloc(sizeof(char*)*(CommaNum+2));
	if ( sid_list == NULL )
	{
		fprintf(stderr, "AnalyzeSid() malloc error.\n");
		return NULL;
	}

	/* sidが空である場合 */
	p = sid;
	if ( strlen(p) == 0 )
	{
		sid_list[0] = NULL;
		return sid_list;
	}

	/* カンマ無し */
	if ( CommaNum == 0 )
	{
		sid_list[0] = sid;
		sid_list[1] = NULL;
		return sid_list;
	}

	/* カンマ区切りで複数指定時 */
	i=0;
	p = sid;
	/* 文字列端に到達するか、カンマ数が数えた数に達したら終了 */
	while((*p != '\0') || i < CommaNum)
	{
		/* 現在の処理位置をsid_list[i]にセット */
		/* このタイミングの p は
		 * ・sid先頭
		 * ・[,]の次の文字
		 * いずれかであるので p を sid_list[i] に代入してよい
		 */
		sid_list[i] = p;
		i++;

		/* 最初に現れる[,]をNULL文字で置換する */
		p = strchr(p, C_CHAR_COMMA);
		if ( p == NULL )
		{
			/* カンマが見つからない場合は最後の処理対象なので終了 */
			break;
		}
		*p = '\0';
		/* 処理位置をNULLで置換した文字の次の位置に設定する */
		p++;
	}

	/* 最後のsid_list[n]はNULLポインタで止める */
	sid_list[i] = NULL;

	i=0;
	while( sid_list[i] != NULL )
	{
		i++;
	}
#if 0
	for(i=0; sid_list[i] != NULL; i++)
	{
		printf("sid_list[%d]=[%s].\n",i, sid_list[i]);
	}
#endif
	return sid_list;
}

/**
 * 初期化処理
 */
splitter* split_startup(
	char *sid		// [in]		サービスID(引数で指定した文字列)
)
{
	splitter* sp;
	sp = malloc(sizeof(splitter));
	if ( sp == NULL )
	{
		fprintf(stderr, "split_startup malloc error.\n");
		return NULL;
	}
	memset(sp->pids, 0, sizeof(sp->pids));
	memset(sp->pmt_pids, 0, sizeof(sp->pmt_pids));

	sp->sid_list	= NULL;
	sp->pat			= NULL;
	sp->sid_list	= AnalyzeSid(sid);
	if ( sp->sid_list == NULL )
	{
		free(sp);
		return NULL;
	}
	sp->pat_count	= 0xFF;
	sp->pmt_drop = -1;
	sp->pmt_counter = 0;

	return sp;
}

/**
 * 落とすPIDを確定させる
 */
int split_select(
	splitter *sp,						// [in/out]		splitter構造体
	ARIB_STD_B25_BUFFER *sbuf			// [in]			入力TS
)
{
	int result;
	// TS解析
	result = ReadTs(&(sp->pat), sp->pids, sp->sid_list, sp->pmt_pids, sbuf, &(sp->pmt_drop), &(sp->pmt_counter));

	return result;
}

/**
 * 終了処理
 */
void split_shutdown(
	splitter* sp
)
{
	if ( sp != NULL ) {
		if ( sp->pat != NULL )
		{
			free(sp->pat);
			sp->pat = NULL;
		}
		if ( sp->sid_list != NULL )
		{
			free(sp->sid_list);
			sp->sid_list = NULL;
		}
		free(sp);
		sp = NULL;
	}
}

/**
 * TS 解析処理
 *
 * 対象のチャンネル番号のみの PAT の再構築と出力対象 PID の抽出を行う
 */
int ReadTs(
	unsigned char** pat,				// [out]	PAT 情報(再構築後)
	unsigned char* pids,				// [out]	出力対象 PID 情報
	char** sid_list,					// [in]		出力対象サービス ID のリスト
	unsigned char* pmt_pids,			// [in]		出力対象PIDのPMT PID
	ARIB_STD_B25_BUFFER *sbuf,			// [in]		pt1_drvの入力TS
	int* pmt_drop,						// [in]		PMTの落とすべき数
	int* pmt_counter					// [out]	PMTの落とした数
)
{
	int length = sbuf->size;
	int pid;
	int result = TSS_ERROR;
	int index;

	index = 0;
	while (((length - index - LENGTH_PACKET)) > 0)
	{
		pid = GetPid(((unsigned char*)sbuf->data)+index+1);
		// PAT
		if (0x0000 == pid)
		{
			result = AnalyzePat(((unsigned char*)sbuf->data)+index, pat, pids, sid_list, pmt_pids, pmt_drop);
			if (TSS_SUCCESS != result)
			{
				/* 下位の関数内部でmalloc error発生 */
				return result;
			}
		}

		// PMT
		/* 落とすpmt_pidである場合には、pmtに書かれている
		 * 落とすべきPCR/AUDIO/VIDEO PIDを取得する */
		if (pmt_pids[pid] == 1)
		{
			/* この中にはPMT毎に一度しか入らないようにしておく */
			AnalyzePmt(((unsigned char*)sbuf->data)+index, pids);
			pmt_pids[pid]++;
			*pmt_counter = *(pmt_counter)+1;
		}
		/* 全ての落とすPMTの中に書かれている、落とすPCR/AUDIO/VIDEOのPIDを得たら処理を抜ける */
		/* pmt_counter と pmt_drop が一致する場合に条件は満たされる */
		if ((*pmt_counter == *pmt_drop)) {
			result = TSS_SUCCESS;
			break;
		}
		else
		{
			result = TSS_ERROR;
		}
		index += LENGTH_PACKET;
	}

	return(result);
}

/**
 * TS 分離処理
 */
int split_ts(
	splitter *splitter,					// [in]		splitterパラメータ
	ARIB_STD_B25_BUFFER *sbuf,			// [in]		入力TS
	BUFSZ *dbuf							// [out]	出力TS
)
{
	int pid;
	int s_offset = 0;
	int d_offset = 0;

	/* 初期化 */
	dbuf->size = 0;
	if ( sbuf->size < 0 )
	{
		return TSS_ERROR;
	}

	while (sbuf->size > s_offset)
	{
		pid = GetPid(((unsigned char*)sbuf->data)+s_offset+1);

		// PAT
		if (0x0000 == pid)
		{
			// 巡回カウンタカウントアップ
			if (0xFF == splitter->pat_count)
			{
				splitter->pat_count = splitter->pat[3];
			} else
			{
				splitter->pat_count = (splitter->pat_count)+1;
				if (0 == splitter->pat_count % 0x10)
				{
					splitter->pat_count = splitter->pat_count - 0x10;
				}
			}
			splitter->pat[3] = splitter->pat_count;

			memcpy(((unsigned char*)dbuf->buffer)+d_offset, splitter->pat, LENGTH_PACKET);
			d_offset += LENGTH_PACKET;
			dbuf->size = dbuf->size + LENGTH_PACKET;
		}

		// その他 PID
		else
		{
			/* pids[pid] が 0 は落とさないパケットなので書き込む */
			if (0 == splitter->pids[pid])
			{
				memcpy(((unsigned char*)dbuf->buffer)+d_offset, ((unsigned char*)sbuf->data)+s_offset, LENGTH_PACKET);
				d_offset += LENGTH_PACKET;
				dbuf->size = dbuf->size + LENGTH_PACKET;
			}
		}
		s_offset = s_offset + LENGTH_PACKET;
	}

	return(TSS_SUCCESS);
}

/**
 * PAT 解析処理
 *
 * PAT を解析し、出力対象チャンネルが含まれているかチェックを行い、PAT を再構築する
 */
int AnalyzePat(
	unsigned char* buf,					// [in]		読み込んだバッファ
	unsigned char** pat,				// [out]	PAT 情報(再構築後)
	unsigned char* pids,				// [out]	出力対象 PID 情報
	char** sid_list,					// [in]		出力対象サービス ID のリスト
	unsigned char* pmt_pids,			// [out]	サービス ID に対応する PMT の PID
	int* pmt_drop
)						// [out]	落とすPMTの数
{
	int pos[MAX_PID];
	int service_id;
	int i, j=0, k;
	int size = 0;
	int pid;
	int result = TSS_SUCCESS;
	char **p;
	int sid_found;
	int avail_sids[MAX_SERVICES];

	if (NULL == *pat)
	{
		/* 初期化 */
		*pmt_drop = 0;
		memset(pos, 0, sizeof(pos));
		size = buf[7];

		// 対象チャンネル判定
		/* size + 8 = パケット全長 */
		/* 最終 4 バイトはCRCなので飛ばす */
		for (i = 17; i < (size + 8) - 4; i = i + 4)
		{
			sid_found = 0;
			service_id = (buf[i] << 8) + buf[i + 1];
			avail_sids[j] = service_id;
			p = sid_list;
			while(*p != NULL)
			{
				if (service_id == atoi(*p))
				{
					/* 録画対象 = 落とす対象とはしないものなので、基本的に何もしない */
					/* 録画対象の pmt_pids は 0 とする */
					/* 録画対象の pmt の pids は 0 とする */
					pid = GetPid(&buf[i + 2]);
					*(pmt_pids+pid) = 0;
					*(pids+pid) = 0;
					pos[pid] = i;
					sid_found = 1;
					break;
				}
				else if (strstr(*p, "all") != NULL )
				{
					/* all指定時には全保存する */
					pid = GetPid(&buf[i + 2]);
					*(pmt_pids+pid) = 0;
					*(pids+pid) = 0;
					pos[pid] = i;
					sid_found = 1;
					break;
				}
				p++;
			}
			if ( ! sid_found )
			{
				/* sid_list を全部なめたが録画対象であると判定されないものを落とす */
				/* 削除対象の pmt_pids は 1 とする */
				/* 削除対象の pmt の pids は 1 とする */
				pid = GetPid(&buf[i + 2]);
				*(pmt_pids+pid) = 1;
				*(pids+pid) = 1;
				pos[pid] = i;
				*(pmt_drop) = *(pmt_drop)+1;
			}
			j++;
		}
		fprintf(stderr, "Available sid = ");
		for(k=0; k<j; k++)
			fprintf(stderr, "%d ", avail_sids[k]);
		fprintf(stderr, "\n");

		fprintf(stderr, "Chosen sid    = ");
		p = sid_list;
		while(*p != NULL) {
			fprintf(stderr, "%s ", *p);
			p++;
		}
		fprintf(stderr, "\n");

		// PAT 再構築
		result = RecreatePat(buf, pat, pids, pos);
	}

	return(result);
}

/**
 * PAT 再構築処理
 *
 * PMT から出力対象チャンネル以外のチャンネル情報を削除し、PAT を再構築する
 */
int RecreatePat(
	unsigned char* buf,					// [in]		読み込んだバッファ
	unsigned char** pat,				// [out]	PAT 情報(再構築後)
	unsigned char* pids,				// [out]	出力対象 PID 情報
	int *pos)							// [in]		取得対象 PMT のバッファ中の位置
{
	unsigned char y[LENGTH_CRC_DATA];
	int crc;
	int i;
	int j;
	int pos_i;
	int pid_num = 0;

	// CRC 計算のためのデータ
	{
		// チャンネルによって変わらない部分
		for (i = 0; i < LENGTH_PAT_HEADER; i++)
		{
			y[i] = buf[i + 5];
		}
		// チャンネルによって変わる部分
		for (i = 0; i < MAX_PID; i++)
		{
			if(pos[i] != 0)
			{
				/* buf[pos_i] を y にコピー(抽出したPIDの数) */
				pos_i = pos[i];
				for (j = 0; j < 4; j++)
				{
					y[LENGTH_PAT_HEADER + ((4*pid_num) + j)] = buf[pos_i + j];
				}
				pid_num++;
			}
		}
	}
	/* パケットサイズ計算 */
	y[2] = pid_num * 4 + 0x0d;
	// CRC 計算
	crc = GetCrc32(y, LENGTH_PAT_HEADER + pid_num*4);

	// PAT 再構成
	*pat = (unsigned char*)malloc(LENGTH_PACKET);
	if ( *pat == NULL )
	{
		fprintf(stderr, "RecreatePat() malloc error.\n");
		return(TSS_NULL);
	}
	memset(*pat, 0xFF, LENGTH_PACKET);
	for (i = 0; i < 5; i++)
	{
		(*pat)[i] = buf[i];
	}
	for (i = 0; i < LENGTH_PAT_HEADER + pid_num*4; i++)
	{
		(*pat)[i + 5] = y[i];
	}
	(*pat)[5 + LENGTH_PAT_HEADER + pid_num*4] = (crc >> 24) & 0xFF;
	(*pat)[6 + LENGTH_PAT_HEADER + pid_num*4] = (crc >> 16) & 0xFF;
	(*pat)[7 + LENGTH_PAT_HEADER + pid_num*4] = (crc >>  8) & 0xFF;
	(*pat)[8 + LENGTH_PAT_HEADER + pid_num*4] = (crc      ) & 0xFF;
	return(TSS_SUCCESS);
}

/**
 * PMT 解析処理
 *
 * PMT を解析し、削除対象の PID を特定する
 */
int AnalyzePmt(
	unsigned char* buf,					// [in]		読み込んだバッファ
	unsigned char* pids)				// [out]	出力対象 PID 情報
{
	unsigned char Nall;
	unsigned char N;
	int pcr;
	int epid;

	Nall = ((buf[6] & 0x0F) << 4) + buf[7];

	// PCR
	pcr = GetPid(&buf[13]);
	pids[pcr] = 1;

	N = ((buf[15] & 0x0F) << 4) + buf[16] + 16 + 1;

	// ES PID
	while (N < Nall + 8 - 4)
	{
		epid = GetPid(&buf[N + 1]);

		pids[epid] = 1;
		N += 4 + (((buf[N + 3]) & 0x0F) << 4) + buf[N + 4] + 1;
	}

	return TSS_SUCCESS;
}

/**
 * CRC 計算
 */
int GetCrc32(
	unsigned char* data,				// [in]		CRC 計算対象データ
	int len)							// [in]		CRC 計算対象データ長
{
	int crc;
	int i, j;
	int c;
	int bit;

	crc = 0xFFFFFFFF;
	for (i = 0; i < len; i++)
	{
		char x;
		x = data[i];

		for (j = 0; j < 8; j++)
		{

			bit = (x >> (7 - j)) & 0x1;

			c = 0;
			if (crc & 0x80000000)
			{
				c = 1;
			}

			crc = crc << 1;

			if (c ^ bit)
			{
				crc ^= 0x04C11DB7;
			}

			crc &= 0xFFFFFFFF;
		}
	}

	return crc;
}

/**
 * PID 取得
 */
int GetPid(
	unsigned char* data)				// [in]		取得対象データのポインタ
{
	return ((data[0] & 0x1F) << 8) + data[1];
}