
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;
}
プロジェクトファイルをダウンロード