TOSHIBA RDシリーズからネットdeダビング経由で映像を取得する

test127.JPG
TOSHIBA RD-H1などのHDDレコーダーには「ネットdeダビング」という機能により機器間で録画した映像をやりとりできる。ここではこのネットdeダビングを利用してTOSHIBA RDシリーズからPCへ映像をダビング(コピー)する。

今回のソースコードにはFTPサーバー部しか実装されていない。そのため実際にTOSHIBA RDシリーズから映像データをダビングするには「NetBIOSの名前問い合わせに返答する 」を起動しておく。

■「ネットdeダビング」での処理の流れ

1.(RD上で操作)赤外線リモコンの「ネットdeダビング」ボタンを押す
2.(RD上で操作)ダビング先として「ネットワーク」を選択する
3.(RD→PC)NetBIOS(UDP/137)を利用した名前の問い合わせが送信される
4.(PC→RD)NetBIOS(UDP/137)による名前を応答する
5.(RD上で表示)「ネットワーク機器名」としてNetBIOSで応答のあった機器が列挙される
6.(RD上で操作)PCを示すダビング先を選択する
7.(RD⇔PC)PCがFTPサーバー、RDがFTPクライアントとして動作して映像がPCにアップロードされる

前回の「NetBIOSの名前問い合わせに返答する 」により1~6の処理が可能になる。今回は7の処理を実装した。
ただし実装はシングルスレッドで、かつ、ネットワーク遅延やパケット分割、各種エラー発生時の処理は省略している。そのため実際に流用する場合はそれらを考慮したものに変更する必要がある。


■「ネットdeダビング」でのFTPの流れ

1.(PC)(TCP/21で受信待機する)
2.(RD→PC)(FTPサーバー(PC)へTCP/21で接続する)
3.(RD←PC)220
4.(RD→PC)USER b2b404fc9e0344e64c40d549d0c21219
5.(RD←PC)331
6.(RD→PC)PASS b2b404fc9e0344e64c40d549d0c21219
7.(RD←PC)230
8.(RD→PC)TYPE I
9.(RD←PC)200

10.(RD→PC)PASV
11.(PC)(データ転送用に任意のポートを開ける)
12.(RD←PC)227 Entering Passive Mode (x,x,x,x,y,y). (xは自分のIPアドレス、yはデータ転送用のポート番号)
13.(PC)(データ転送用のポートで受信待機する)
14.(RD→PC)(データ転送用のポートにアクセスする)
15.(RD→PC)STOR $netdubbing$dubbinginfo.xml
16.(RD←PC)150
17.(RD→PC)(データ転送用のポートにXMLが送られる。内容は以下のようなもの)

<?xml version="1.0" encoding="shift_jis"?>
<dubbinginfo>
<titlesize>2246</titlesize>
<device>0</device>
</dubbinginfo>

18.(RD←PC)226

19.(RD→PC)PASV
20.(PC)(データ転送用に任意のポートを開ける)
21.(RD←PC)227 Entering Passive Mode (x,x,x,x,y,y). (xは自分のIPアドレス、yはデータ転送用のポート番号)
22.(PC)(データ転送用のポートで受信待機する)
23.(RD→PC)(データ転送用のポートにアクセスする)
24.(RD→PC)RETR $xml$status.xml
25.(RD←PC)150
26.(RD←PC)(データ転送用のポートにXMLを送る。内容は以下のようなもの)

<?xml version=\"1.0\" encoding=\"shift_jis\"?>
<status>
<dubbing>ready</dubbing>
<accept_dubbing>ready</accept_dubbing>
<device0_remain>167772160</device0_remain>
<device0_recorded>0</device0_recorded>
<device0_title_remain>350</device0_title_remain>
<device1_remain>167772160</device1_remain>
<device1_recorded>0</device1_recorded>
<device1_title_remain>350</device1_title_remain>
</status>

27.(RD←PC)226

28.(RD→PC)PASV
29.(PC)(データ転送用に任意のポートを開ける)
30.(RD←PC)227 Entering Passive Mode (x,x,x,x,y,y). (xは自分のIPアドレス、yはデータ転送用のポート番号)
31.(PC)(データ転送用のポートで受信待機する)
32.(RD→PC)(データ転送用のポートにアクセスする)
33.(RD→PC)STOR $netdubbing$dev0.dat
34.(RD←PC)150
35.(RD→PC)(データ転送用のポートにmpegファイルの内容が送られる。内容は以下のようなもの)
36.(RD←PC)226

37.(RD→PC)PASV
38.(PC)(データ転送用に任意のポートを開ける)
39.(RD←PC)227 Entering Passive Mode (x,x,x,x,y,y). (xは自分のIPアドレス、yはデータ転送用のポート番号)
40.(PC)(データ転送用のポートで受信待機する)
41.(RD→PC)(データ転送用のポートにアクセスする)
42.(RD→PC)STOR $netdubbing$dubbinginfo.xml
43.(RD←PC)150
44.(RD→PC)(データ転送用のポートにXMLが送られる。内容は以下のようなもの)

<?xml version="1.0" encoding="shift_jis"?>
<dubbinginfo>
<titlesize>0</titlesize>
<device>0</device>
</dubbinginfo>

45.(RD←PC)226

46.(RD→PC)PASV
47.(PC)(データ転送用に任意のポートを開ける)
48.(RD←PC)227 Entering Passive Mode (x,x,x,x,y,y). (xは自分のIPアドレス、yはデータ転送用のポート番号)
49.(PC)(データ転送用のポートで受信待機する)
50.(RD→PC)(データ転送用のポートにアクセスする)
51.(RD→PC)RETR $xml$status.xml
52.(RD←PC)150
53.(RD←PC)(データ転送用のポートにXMLを送る。内容は以下のようなもの)

<?xml version=\"1.0\" encoding=\"shift_jis\"?>
<status>
<dubbing>ready</dubbing>
<accept_dubbing>ready</accept_dubbing>
<device0_remain>167772160</device0_remain>
<device0_recorded>0</device0_recorded>
<device0_title_remain>350</device0_title_remain>
<device1_remain>167772160</device1_remain>
<device1_recorded>0</device1_recorded>
<device1_title_remain>350</device1_title_remain>
</status>

54.(RD←PC)226

55.(RD→PC)RETR QUIT

※PCからRDへの送信例は適当に作成しています。本来はきちんとHDD容量や転送デバイスを考慮した値を入れる必要があります。

依存環境:ATL
#include <winsock2.h>
#pragma	comment(lib,"Ws2_32.lib")

bool	Send(SOCKET sock,const char* pszMessage)
{
	int		nRet;

	nRet = ::send(sock,pszMessage,(int)::strlen(pszMessage),0);

	return (nRet == SOCKET_ERROR) ? false : true;
}


bool	SendResponseCode(SOCKET sock,int nCode,const char* pszMessage=NULL)
{
	char	pszData[128];

	if(pszMessage == NULL)
		::sprintf_s(pszData,128,"%d \r\n",nCode);		//TOSHIBA RDシリーズでは%d直後のスペースが必須!
	else
		::sprintf_s(pszData,128,"%d %s\r\n",nCode,pszMessage);

	return	Send(sock,pszData);
}




bool	FTPServer()
{
	int		nRet;
	SOCKET	sock;
	SOCKET	sockDataConnection;
	sockaddr_in	addr;

	sock = ::socket(PF_INET,SOCK_STREAM,0);
	if(sock == SOCKET_ERROR)
		return	false;

	sockDataConnection = SOCKET_ERROR;


	addr.sin_family		= PF_INET;
	addr.sin_port		= ::htons(21);		//FTPポート
	addr.sin_addr.s_addr= INADDR_ANY;

	nRet = ::bind(sock,(sockaddr*)&addr,sizeof(sockaddr_in));
	if(nRet == SOCKET_ERROR)
	{
		::closesocket(sock);
		return	false;
	}

	nRet = ::listen(sock,SOMAXCONN);
	if(nRet == SOCKET_ERROR)
	{
		::closesocket(sock);
		return	false;
	}

	while(1)
	{
		int			nAddrLen;
		SOCKET		sockAccept;
		sockaddr_in	addrAccept;

		ATLTRACE("FTP Server Start...\n");
		nAddrLen = sizeof(sockaddr_in);
		sockAccept = ::accept(sock,(sockaddr*)&addrAccept,&nAddrLen);
		if(sockAccept == SOCKET_ERROR)
			break;

		//FTPサーバーへのコネクト開始メッセージ
		SendResponseCode(sockAccept,220);
		ATLTRACE("FTP Connected\n");

		while(1)
		{
			char	pBuffer[2048];

			nRet = ::recv(sockAccept,pBuffer,sizeof(pBuffer),0);
			if(nRet == SOCKET_ERROR)
				break;
			if(nRet == 0)
				continue;

			pBuffer[nRet] = NULL;
			ATLTRACE("%s\n",pBuffer);

			if(nRet < 4)
				break;

			if(::StrCmpNIA(pBuffer,"USER",4) == 0)
			{
				SendResponseCode(sockAccept,331);
				continue;
			}
			if(::StrCmpNIA(pBuffer,"PASS",4) == 0)
			{
				SendResponseCode(sockAccept,230);
				continue;
			}
			if(::StrCmpNIA(pBuffer,"TYPE",4) == 0)
			{
				SendResponseCode(sockAccept,200);
				continue;
			}
			if(::StrCmpNIA(pBuffer,"PASV",4) == 0)
			{
				//データ転送用ソケットを作って、そのポートをRDに知らせる

				int		i;
				int		nPort;
				char	pszMessage[1024];

				SOCKET		sockData;
				sockaddr_in	sDataAddr;

				sockData = ::socket(PF_INET,SOCK_STREAM,0);
				if(sockData == SOCKET_ERROR)
					break;

				nPort = 22222;
				for(i = 0; i < 100; i++)
				{
					::ZeroMemory(&sDataAddr,sizeof(sockaddr_in));
					sDataAddr.sin_family		= AF_INET;
					sDataAddr.sin_addr.s_addr	= INADDR_ANY;
					sDataAddr.sin_port			= ::htons(nPort);

					nRet = ::bind(sockData,(sockaddr*)&sDataAddr,sizeof(sockaddr_in));
					if(nRet != SOCKET_ERROR)
						break;

					//ポート番号を変えて再試行
					nPort++;
				}
				if(nRet == SOCKET_ERROR)
					break;
				::listen(sockData,1);

				int			nAddrLen;
				sockaddr	addr;

				nAddrLen = sizeof(sockaddr);
				::getsockname(sockAccept,&addr,&nAddrLen);

				::sprintf_s(pszMessage,1024,"Entering Passive Mode (%u,%u,%u,%u,%u,%u).\r\n"
					,addr.sa_data[2] & 0xFF
					,addr.sa_data[3] & 0xFF
					,addr.sa_data[4] & 0xFF
					,addr.sa_data[5] & 0xFF
					,(nPort >> 8) & 0x00FF
					,(nPort >> 0) & 0x00FF);

				//待機アドレス、ポートを知らせる
				SendResponseCode(sockAccept,227,pszMessage);

				//RDからの接続を待つ
				nAddrLen = sizeof(sockaddr_in);
				sockDataConnection = ::accept(sockData,(sockaddr*)&sDataAddr,&nAddrLen);
				::closesocket(sockData);
				if(sockDataConnection == SOCKET_ERROR)
					break;

				continue;
			}
			if(::StrCmpNIA(pBuffer,"STOR $netdubbing$dev0.dat",(int)::strlen("STOR $netdubbing$dev0.dat")) == 0)
			{
				//MPEGデータの受信

				char	pBuffer[2048];
				HANDLE	hFile;

				//ファイル決め打ちで保存
				hFile = ::CreateFile(_T("c:\\_Down\\test2.mpg"),GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
				if(hFile == INVALID_HANDLE_VALUE)
					break;

				ATLTRACE("Receiving mpeg file...\n");

				//転送開始
				SendResponseCode(sockAccept,150);

				while(1)
				{
					::Sleep(0);

					nRet = ::recv(sockDataConnection,pBuffer,sizeof(pBuffer),0);
					if(nRet == SOCKET_ERROR)
						break;
					if(nRet == 0)
						break;

					BOOL	ret;
					DWORD	dwWrittenSize;

					ret = ::WriteFile(hFile,pBuffer,nRet,&dwWrittenSize,NULL);
					if(ret == FALSE)
					{
						SendResponseCode(sockAccept,450);
						break;
					}
				}

				//転送終了
				SendResponseCode(sockAccept,226);

				::CloseHandle(hFile);
				ATLTRACE("Received\n");

				::closesocket(sockDataConnection);
				sockDataConnection = SOCKET_ERROR;

				continue;
			}
			if(::StrCmpNIA(pBuffer,"STOR",4) == 0)
			{
				//mpegファイル以外のデータ受信
				//dubbinginfoやコマンドなどを処理

				char	pBuffer[2048];

				//転送開始
				SendResponseCode(sockAccept,150);

				while(1)
				{
					::Sleep(0);

					nRet = ::recv(sockDataConnection,pBuffer,sizeof(pBuffer),0);
					if(nRet == SOCKET_ERROR)
						break;
					if(nRet == 0)
						break;
					pBuffer[nRet] = NULL;

					ATLTRACE("%s\n",pBuffer);
				}

				//転送終了
				SendResponseCode(sockAccept,226);

				::closesocket(sockDataConnection);
				sockDataConnection = SOCKET_ERROR;

				continue;
			}
			if(::StrCmpNIA(pBuffer,"RETR",4) == 0)
			{
				//ステータス情報の送信
				//本来ならdubbinginfoを見て返答すべきだが決め打ち

				char	pBuffer[4096];

				strcpy_s(pBuffer,4096,
					"<?xml version=\"1.0\" encoding=\"shift_jis\"?>\r\n"
					"<status>\r\n"
					"<dubbing>ready</dubbing>\r\n"
					"<accept_dubbing>ready</accept_dubbing>\r\n"
					"<device0_remain>167772160</device0_remain>\r\n"
					"<device0_recorded>0</device0_recorded>\r\n"
					"<device0_title_remain>350</device0_title_remain>\r\n"
					"<device1_remain>167772160</device1_remain>\r\n"
					"<device1_recorded>0</device1_recorded>\r\n"
					"<device1_title_remain>350</device1_title_remain>\r\n"
					"</status>\r\n");

				//転送開始
				SendResponseCode(sockAccept,150);

				Send(sockDataConnection,pBuffer);

				//転送終了
				SendResponseCode(sockAccept,226);

				::closesocket(sockDataConnection);
				sockDataConnection = SOCKET_ERROR;

				continue;
			}
			if(::StrCmpNIA(pBuffer,"QUIT",4) == 0)
				break;


			ATLTRACE("unknown cmd\n");
		}

		::shutdown(sockAccept,SD_BOTH);
		::closesocket(sockAccept);
	}


	if(sockDataConnection != SOCKET_ERROR)
	{
		::shutdown(sockDataConnection,SD_BOTH);
		::closesocket(sockDataConnection);
	}

	::shutdown(sock,SD_BOTH);
	::closesocket(sock);

	return	true;
}




bool	Test()
{
	WSAData wsaData;

	//Winsock2.2
	::WSAStartup(MAKEWORD(2,2),&wsaData);

	FTPServer();

	::WSACleanup();

	return	true;
}

プロジェクトファイルをダウンロード


カテゴリー「ネットワーク」 のエントリー