2015年12月22日火曜日

動画配信サーバを作ってみた

Google、Apple、Amazon、IT関連の大企業はみんな動画配信サービスを行っています。
世の中に動画配信サーバの作り方みたいのがあまりないし、セキュアに配信しないといけないし、どうやってやっているのか勉強するために動画配信サーバを作ってみました。

まず、iPhoneのコンテンツ配信の仕様のドキュメント、Safari Web ContentGuideを見ます。

https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariWebContent/

pdf版もあるのでダウンロードするのが良いと思います。

読むのが大変なので要約すると、HTTPでVideoやAudioを配信するサーバはRangeヘッダーに対応しなくてはいけないと書かれています。


次に、サーバを立てます。
ApacheでもAnhttpdでもなんでもよいです。

次にサーバの設定でCGIを許可します。
許可の仕方はサーバごとに異なるので、サーバのドキュメントを見て設定してください。

ここからが重要です。
次に、nphスクリプトを作ります。
nphスクリプトというのはスクリプトのファイル名がnph-で始まるCGIスクリプトを指します。
nph-で始まると通常のCGIと何が違うのかというと、HTTPのレスポンスヘッダーをサーバ側で自動で送らないので、クライアント側で自由なレスポンスを返すことができます。

npHスクリプトをかくことで、Rangeヘッダーの時に必要な206 Partial Contentをサーバが返すことができるのです。


あとはスクリプトを作ってストリームを返せば動画配信サーバが完成

ということで最後にスクリプトと言っておきながらC言語でnphスクリプトを書いて見ました。

あとは環境変数のQUERY_STRINGでセッション値を取得するなどして、セキュアにストリームを配信すれば誰でも動画配信サービスをすることができます。

動画配信サーバってめちゃくちゃ簡単。
こんなにも簡単な技術であんなにお金を儲けられちゃうなんて。


npg-stream.c
----------------------------------------------
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#ifdef WIN32
#include<fcntl.h>
#include <io.h>
#endif

#ifdef _MSC_VER
#if _MSC_VER >= 1400
#pragma warning( disable : 4996 )
#pragma warning( disable : 4819 )
#endif
#endif


//
//  binarymode
//
static void setstdoutmode()
{
#ifdef WIN32
_setmode(_fileno(stdout), _O_BINARY);
#endif

}


//
// util
//

void mygetenv(char* buf,const char* moji, int sz)
{
char *p;
if (buf == NULL || moji==NULL || sz<1)return;
p = getenv(moji);
if (p == NULL)p = "";

buf[0] = 0;
strncpy(buf, p, sz);
buf[sz-1] = 0;

}
int mymin(int a, int b)
{
if (a < b)return a;
return b;
}

FILE* fzengo_open(const char* file, const char* mode, const char* path)
{
FILE* ret = NULL;
char buf[4096];
if (file == NULL || mode == NULL || path == NULL)return ret;

if (strlen(file) > 1000)return ret;
if (strlen(path) > 1000)return ret;

// 1
sprintf(buf, "%s/%s", path, file);
ret = fopen(buf, mode);
if (ret != NULL)return ret;

// 2
sprintf(buf, "../%s/%s", path,file);
ret = fopen(buf, mode);
if (ret != NULL)return ret;

// 3
sprintf(buf, "../../%s/%s", path, file);
ret = fopen(buf, mode);
if (ret != NULL)return ret;

// 4
ret = fopen(file, mode);
if (ret != NULL)return ret;

// 5
sprintf(buf, "../%s", file);
ret = fopen(buf, mode);
if (ret != NULL)return ret;

return ret;
}

FILE* fall_zengo_open(const char* file, const char* mode)
{
FILE* ret = NULL;

ret = fzengo_open(file, mode, "excel");
if (ret != NULL)return ret;
ret = fzengo_open(file, mode, "music");
if (ret != NULL)return ret;
ret = fzengo_open(file, mode, "video");
if (ret != NULL)return ret;
ret = fzengo_open(file, mode, "movie");
if (ret != NULL)return ret;

return ret;
}

const char* getmime(const char* name)
{
const char* ret = "application/octet-stream";

if (name == NULL)return ret;
if (strstr(name, ".mp3")) ret = "audio/mpeg";
if (strstr(name, ".m4v")) ret = "video/x-m4v";
return ret;
}

//
// print
//
static void print(char* p)
{
if (p == NULL)return;
printf("%s\r\n",p);
}

static void printresponse()
{
print("HTTP/1.1 200 OK");
print("Connection: close");
print("Pragma: no-cache");
print("Cache-Control: no-cache");
print("Accept-Ranges: bytes");
}


static void printresponse_range(int st, int ed, int fsz)
{
char buf[256];
//  causes proxy issue
print("HTTP/1.1 206 Partial Content");
print("Connection: close");
print("Pragma: no-cache");
print("Cache-Control: no-cache");
print("Accept-Ranges: bytes");
sprintf(buf, "Content-Range: bytes %d-%d/%d", st, ed, fsz);
print(buf);
}


static void printheader(const char* p)
{
char buf[256];
if (p == NULL)return;
sprintf(buf, "Content-Type: %s", p);
print(buf);
print("");
}

static void printheader_size(const char* p,int sz)
{
char buf[256];
if (p == NULL)return;
sprintf(buf, "Content-Type: %s", p);
print(buf);
sprintf(buf, "Content-Length: %d", sz);
print(buf);
print("");
}

static void printbody(char*p, int l)
{
if (p == NULL || l < 1)return;
fwrite(p, 1, l, stdout);
}


static void print_file(char *file)
{
FILE* fp;
char buf[1024 * 256];
int sz, fsz;
char* mime;

if (file == NULL)return;
fp = fall_zengo_open(file, "rb");
if (fp == NULL)return;

fseek(fp, 0, SEEK_END);
fsz = ftell(fp);
fseek(fp, 0, SEEK_SET);

printresponse(fsz);

mime = getmime(file);
printheader_size(mime, fsz);

do{
sz = fread(buf, 1, mymin(fsz, sizeof(buf)), fp);
if (sz < 1)break;
printbody(buf, sz);
fsz -= sz;
if (fsz < 1)break;
} while (1);
fclose(fp);

}

static void print_file_range(char *file,int st,int ed)
{
FILE* fp;
char buf[1024 * 256];
int sz, fsz,ssz;
char* mime;

if (file == NULL)return;
fp = fall_zengo_open(file, "rb");
if (fp == NULL)return;

fseek(fp, 0, SEEK_END);
fsz = ftell(fp);
fseek(fp, 0, SEEK_SET);

if (ed == -1)ed = fsz - 1;
ssz = ed - st + 1;

if (ed < 0 || st < 0)goto end;
if (st + ssz>fsz)goto end;


fseek(fp, st, SEEK_SET);
printresponse_range(st,ed,fsz);

mime = getmime(file);
printheader_size(mime, ssz);

do{
sz = fread(buf, 1, mymin(ssz, sizeof(buf)), fp);
if (sz < 1)break;
printbody(buf, sz);
ssz -= sz;
if (ssz < 1)break;
} while (1);

end:
fclose(fp);

}



//
// main
//
int main(int argc, char *argv[])
{
char env[1024];
char query[1024];
char buf[1024];
char* filename="hoge.m4v";
char* p;
int f = 0, st = -1, ed = -1;
int fno = 0;

setstdoutmode();

mygetenv(env,"HTTP_RANGE",sizeof(env));
mygetenv(query, "QUERY_STRING", sizeof(env));

p = strstr(query, "f=");
if (p)sscanf(p + 2, "%d", &fno);
if (fno < 1)fno = 1;


if (filename[0] == 0)strcpy(filename, "null");

p = strstr(env, "bytes=");
if (p){
p += 6;
sscanf(p, "%d-%d", &st, &ed);
}

if (st != -1)f = 1;

if (f){
print_file_range(filename, st, ed);
}
else{
print_file(filename);
}
return 0;
}

----------------------------------------------



0 件のコメント:

コメントを投稿