Finish splitting controller into multiple files, universalised coding style across all files.

This commit is contained in:
Thulinma 2012-12-12 20:16:53 +01:00
parent 0db5f60b95
commit 0920b3259b
30 changed files with 1616 additions and 1246 deletions

View file

@ -11,7 +11,7 @@ SUBDIRS=converters analysers
bin_PROGRAMS=MistBuffer MistController MistConnRAW MistConnRTMP MistConnHTTP MistConnHTTPProgressive MistConnHTTPDynamic MistConnHTTPSmooth MistPlayer bin_PROGRAMS=MistBuffer MistController MistConnRAW MistConnRTMP MistConnHTTP MistConnHTTPProgressive MistConnHTTPDynamic MistConnHTTPSmooth MistPlayer
MistBuffer_SOURCES=buffer.cpp buffer_user.h buffer_user.cpp buffer_stream.h buffer_stream.cpp tinythread.cpp tinythread.h ../VERSION MistBuffer_SOURCES=buffer.cpp buffer_user.h buffer_user.cpp buffer_stream.h buffer_stream.cpp tinythread.cpp tinythread.h ../VERSION
MistBuffer_LDADD=$(MIST_LIBS) -lpthread MistBuffer_LDADD=$(MIST_LIBS) -lpthread
MistController_SOURCES=controller.cpp controller_connectors.h controller_connectors.cpp controller_storage.h controller_storage.cpp ../VERSION ./server.html.h MistController_SOURCES=controller.cpp controller_connectors.h controller_connectors.cpp controller_storage.h controller_storage.cpp controller_streams.h controller_streams.cpp controller_capabilities.h controller_capabilities.cpp ../VERSION ./server.html.h
MistConnRAW_SOURCES=conn_raw.cpp ../VERSION MistConnRAW_SOURCES=conn_raw.cpp ../VERSION
MistConnRTMP_SOURCES=conn_rtmp.cpp ../VERSION MistConnRTMP_SOURCES=conn_rtmp.cpp ../VERSION
MistConnHTTP_SOURCES=conn_http.cpp tinythread.cpp tinythread.h ../VERSION ./embed.js.h MistConnHTTP_SOURCES=conn_http.cpp tinythread.cpp tinythread.h ../VERSION ./embed.js.h

View file

@ -11,14 +11,16 @@
/// Debugging tool for AMF data. /// Debugging tool for AMF data.
/// Expects AMF data through stdin, outputs human-readable information to stderr. /// Expects AMF data through stdin, outputs human-readable information to stderr.
int main(int argc, char ** argv) { int main(int argc, char ** argv){
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION); Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
std::string temp; std::string temp;
while (std::cin.good()){temp += std::cin.get();}//read all of std::cin to temp while (std::cin.good()){
temp.erase(temp.size()-1, 1);//strip the invalid last character temp += std::cin.get();
AMF::Object amfdata = AMF::parse(temp);//parse temp into an AMF::Object } //read all of std::cin to temp
amfdata.Print();//pretty-print the object temp.erase(temp.size() - 1, 1); //strip the invalid last character
AMF::Object amfdata = AMF::parse(temp); //parse temp into an AMF::Object
amfdata.Print(); //pretty-print the object
return 0; return 0;
} }

View file

@ -36,15 +36,21 @@ int main(int argc, char ** argv){
long long unsigned int bps = 0; long long unsigned int bps = 0;
F.seekNext(); F.seekNext();
while (!F.getJSON().isNull()){ while ( !F.getJSON().isNull()){
std::cout << F.getJSON().toPrettyString() << std::endl; std::cout << F.getJSON().toPrettyString() << std::endl;
nowpack = F.getJSON()["time"].asInt(); nowpack = F.getJSON()["time"].asInt();
if (firstpack == 0){firstpack = nowpack;} if (firstpack == 0){
firstpack = nowpack;
}
if (F.getJSON()["datatype"].asString() == "audio"){ if (F.getJSON()["datatype"].asString() == "audio"){
if (lastaudio != 0 && (nowpack - lastaudio) != 0){ if (lastaudio != 0 && (nowpack - lastaudio) != 0){
bps = F.getJSON()["data"].asString().size() / (nowpack - lastaudio); bps = F.getJSON()["data"].asString().size() / (nowpack - lastaudio);
if (bps < aud_min){aud_min = bps;} if (bps < aud_min){
if (bps > aud_max){aud_max = bps;} aud_min = bps;
}
if (bps > aud_max){
aud_max = bps;
}
} }
totalaudio += F.getJSON()["data"].asString().size(); totalaudio += F.getJSON()["data"].asString().size();
lastaudio = nowpack; lastaudio = nowpack;
@ -52,22 +58,34 @@ int main(int argc, char ** argv){
if (F.getJSON()["datatype"].asString() == "video"){ if (F.getJSON()["datatype"].asString() == "video"){
if (lastvideo != 0 && (nowpack - lastvideo) != 0){ if (lastvideo != 0 && (nowpack - lastvideo) != 0){
bps = F.getJSON()["data"].asString().size() / (nowpack - lastvideo); bps = F.getJSON()["data"].asString().size() / (nowpack - lastvideo);
if (bps < vid_min){vid_min = bps;} if (bps < vid_min){
if (bps > vid_max){vid_max = bps;} vid_min = bps;
}
if (bps > vid_max){
vid_max = bps;
}
} }
if (F.getJSON()["keyframe"].asInt() != 0){ if (F.getJSON()["keyframe"].asInt() != 0){
if (lastkey != 0){ if (lastkey != 0){
bps = nowpack - lastkey; bps = nowpack - lastkey;
if (bps < key_min){key_min = bps;} if (bps < key_min){
if (bps > key_max){key_max = bps;} key_min = bps;
}
if (bps > key_max){
key_max = bps;
}
} }
keyframes++; keyframes++;
lastkey = nowpack; lastkey = nowpack;
} }
if (F.getJSON()["offset"].asInt() != 0){ if (F.getJSON()["offset"].asInt() != 0){
bps = F.getJSON()["offset"].asInt(); bps = F.getJSON()["offset"].asInt();
if (bps < bfrm_min){bfrm_min = bps;} if (bps < bfrm_min){
if (bps > bfrm_max){bfrm_max = bps;} bfrm_min = bps;
}
if (bps > bfrm_max){
bfrm_max = bps;
}
} }
totalvideo += F.getJSON()["data"].asString().size(); totalvideo += F.getJSON()["data"].asString().size();
lastvideo = nowpack; lastvideo = nowpack;
@ -76,7 +94,7 @@ int main(int argc, char ** argv){
} }
std::cout << std::endl << "Summary:" << std::endl; std::cout << std::endl << "Summary:" << std::endl;
meta["length"] = (long long int)((nowpack - firstpack)/1000); meta["length"] = (long long int)((nowpack - firstpack) / 1000);
if (meta.isMember("audio")){ if (meta.isMember("audio")){
meta["audio"]["bps"] = (long long int)(totalaudio / ((lastaudio - firstpack) / 1000)); meta["audio"]["bps"] = (long long int)(totalaudio / ((lastaudio - firstpack) / 1000));
std::cout << " Audio: " << meta["audio"]["codec"].asString() << std::endl; std::cout << " Audio: " << meta["audio"]["codec"].asString() << std::endl;
@ -96,4 +114,4 @@ int main(int argc, char ** argv){
std::cout << " B-frames: " << bfrm_min << " - " << bfrm_max << std::endl; std::cout << " B-frames: " << bfrm_min << " - " << bfrm_max << std::endl;
} }
return 0; return 0;
}//main } //main

View file

@ -15,20 +15,20 @@
#include <mist/config.h> #include <mist/config.h>
/// Reads FLV from stdin and outputs human-readable information to stderr. /// Reads FLV from stdin and outputs human-readable information to stderr.
int main(int argc, char ** argv) { int main(int argc, char ** argv){
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION); Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
FLV::Tag FLV_in; // Temporary storage for incoming FLV data. FLV::Tag FLV_in; // Temporary storage for incoming FLV data.
std::ofstream vData( "vData" ); std::ofstream vData("vData");
std::ofstream aData( "aData" ); std::ofstream aData("aData");
while (!feof(stdin)){ while ( !feof(stdin)){
if (FLV_in.FileLoader(stdin)){ if (FLV_in.FileLoader(stdin)){
std::cout << "Tag: " << FLV_in.tagType() << "\n\tTime: " << FLV_in.tagTime() << std::endl; std::cout << "Tag: " << FLV_in.tagType() << "\n\tTime: " << FLV_in.tagTime() << std::endl;
if( FLV_in.data[0] == 0x08 ) {//Audio if (FLV_in.data[0] == 0x08){ //Audio
aData.write( FLV_in.data + 13, FLV_in.len - 17 ); aData.write(FLV_in.data + 13, FLV_in.len - 17);
} }
if( FLV_in.data[0] == 0x09 ) {//Video if (FLV_in.data[0] == 0x09){ //Video
vData.write( FLV_in.data + 16, FLV_in.len - 20 ); vData.write(FLV_in.data + 16, FLV_in.len - 20);
} }
} }
} }

View file

@ -12,21 +12,23 @@
/// Debugging tool for MP4 data. /// Debugging tool for MP4 data.
/// Expects MP4 data through stdin, outputs human-readable information to stderr. /// Expects MP4 data through stdin, outputs human-readable information to stderr.
int main(int argc, char ** argv) { int main(int argc, char ** argv){
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION); Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
std::string temp; std::string temp;
while (std::cin.good()){temp += std::cin.get();}//read all of std::cin to temp while (std::cin.good()){
temp.erase(temp.size()-1, 1);//strip the invalid last character temp += std::cin.get();
} //read all of std::cin to temp
temp.erase(temp.size() - 1, 1); //strip the invalid last character
MP4::Box mp4data; MP4::Box mp4data;
while (mp4data.read(temp)){ while (mp4data.read(temp)){
std::cerr << mp4data.toPrettyString(0) << std::endl; std::cerr << mp4data.toPrettyString(0) << std::endl;
if( mp4data.isType( "mdat" ) ) { if (mp4data.isType("mdat")){
std::ofstream oFile; std::ofstream oFile;
oFile.open( "mdat" ); oFile.open("mdat");
oFile << std::string( mp4data.payload(), mp4data.payloadSize() ); oFile << std::string(mp4data.payload(), mp4data.payloadSize());
oFile.close(); oFile.close();
} }
} }

View file

@ -30,9 +30,11 @@ int Detail = 0;
/// Automatically skips 3073 bytes of handshake data. /// Automatically skips 3073 bytes of handshake data.
int main(int argc, char ** argv){ int main(int argc, char ** argv){
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION); Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
conf.addOption("detail", JSON::fromString("{\"arg_num\":1, \"arg\":\"integer\", \"default\":0, \"help\":\"Bitmask, 1 = Reconstruct, 2 = Explicit media info, 4 = Verbose chunks\"}")); conf.addOption("detail",
JSON::fromString(
"{\"arg_num\":1, \"arg\":\"integer\", \"default\":0, \"help\":\"Bitmask, 1 = Reconstruct, 2 = Explicit media info, 4 = Verbose chunks\"}"));
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
Detail = conf.getInteger("detail"); Detail = conf.getInteger("detail");
if (Detail > 0){ if (Detail > 0){
fprintf(stderr, "Detail level set:\n"); fprintf(stderr, "Detail level set:\n");
@ -49,36 +51,38 @@ int main(int argc, char ** argv){
} }
std::string inbuffer; std::string inbuffer;
while (std::cin.good()){inbuffer += std::cin.get();}//read all of std::cin to temp while (std::cin.good()){
inbuffer.erase(0, 3073);//strip the handshake part inbuffer += std::cin.get();
} //read all of std::cin to temp
inbuffer.erase(0, 3073); //strip the handshake part
RTMPStream::Chunk next; RTMPStream::Chunk next;
FLV::Tag F;//FLV holder FLV::Tag F; //FLV holder
AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER); AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER);
AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER); AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER);
while (next.Parse(inbuffer)){ while (next.Parse(inbuffer)){
if ((Detail & DETAIL_VERBOSE) == DETAIL_VERBOSE){ if ((Detail & DETAIL_VERBOSE) == DETAIL_VERBOSE){
fprintf(stderr, "Chunk info: [%#2X] CS ID %u, timestamp %u, len %u, type ID %u, Stream ID %u\n", next.headertype, next.cs_id, next.timestamp, next.len, next.msg_type_id, next.msg_stream_id); fprintf(stderr, "Chunk info: [%#2X] CS ID %u, timestamp %u, len %u, type ID %u, Stream ID %u\n", next.headertype, next.cs_id, next.timestamp,
next.len, next.msg_type_id, next.msg_stream_id);
} }
switch (next.msg_type_id){ switch (next.msg_type_id){
case 0://does not exist case 0: //does not exist
fprintf(stderr, "Error chunk - %i, %i, %i, %i, %i\n", next.cs_id, next.timestamp, next.real_len, next.len_left, next.msg_stream_id); fprintf(stderr, "Error chunk - %i, %i, %i, %i, %i\n", next.cs_id, next.timestamp, next.real_len, next.len_left, next.msg_stream_id);
//return 0; //return 0;
break;//happens when connection breaks unexpectedly break; //happens when connection breaks unexpectedly
case 1://set chunk size case 1: //set chunk size
RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str()); RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str());
fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max); fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max);
break; break;
case 2://abort message - we ignore this one case 2: //abort message - we ignore this one
fprintf(stderr, "CTRL: Abort message: %i\n", ntohl(*(int*)next.data.c_str())); fprintf(stderr, "CTRL: Abort message: %i\n", ntohl(*(int*)next.data.c_str()));
//4 bytes of stream id to drop //4 bytes of stream id to drop
break; break;
case 3://ack case 3: //ack
RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str()); RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str());
fprintf(stderr, "CTRL: Acknowledgement: %i\n", RTMPStream::snd_window_at); fprintf(stderr, "CTRL: Acknowledgement: %i\n", RTMPStream::snd_window_at);
break; break;
case 4:{ case 4: {
short int ucmtype = ntohs(*(short int*)next.data.c_str()); short int ucmtype = ntohs(*(short int*)next.data.c_str());
switch (ucmtype){ switch (ucmtype){
case 0: case 0:
@ -106,8 +110,9 @@ int main(int argc, char ** argv){
fprintf(stderr, "CTRL: User control message: UNKNOWN %hu - %u\n", ucmtype, ntohl(*(unsigned int*)(next.data.c_str()+2))); fprintf(stderr, "CTRL: User control message: UNKNOWN %hu - %u\n", ucmtype, ntohl(*(unsigned int*)(next.data.c_str()+2)));
break; break;
} }
} break; }
case 5://window size of other end break;
case 5: //window size of other end
RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str()); RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str());
RTMPStream::rec_window_at = RTMPStream::rec_cnt; RTMPStream::rec_window_at = RTMPStream::rec_cnt;
fprintf(stderr, "CTRL: Window size: %i\n", RTMPStream::rec_window_size); fprintf(stderr, "CTRL: Window size: %i\n", RTMPStream::rec_window_size);
@ -147,19 +152,20 @@ int main(int argc, char ** argv){
case 16: case 16:
fprintf(stderr, "Received AFM3 shared object\n"); fprintf(stderr, "Received AFM3 shared object\n");
break; break;
case 17:{ case 17: {
fprintf(stderr, "Received AFM3 command message:\n"); fprintf(stderr, "Received AFM3 command message:\n");
char soort = next.data[0]; char soort = next.data[0];
next.data = next.data.substr(1); next.data = next.data.substr(1);
if (soort == 0){ if (soort == 0){
amfdata = AMF::parse(next.data); amfdata = AMF::parse(next.data);
std::cerr << amfdata.Print() << std::endl; std::cerr << amfdata.Print() << std::endl;
}else{ }else{
amf3data = AMF::parse3(next.data); amf3data = AMF::parse3(next.data);
amf3data.Print(); amf3data.Print();
} }
} break; }
case 18:{ break;
case 18: {
fprintf(stderr, "Received AFM0 data message (metadata):\n"); fprintf(stderr, "Received AFM0 data message (metadata):\n");
amfdata = AMF::parse(next.data); amfdata = AMF::parse(next.data);
amfdata.Print(); amfdata.Print();
@ -167,15 +173,17 @@ int main(int argc, char ** argv){
F.ChunkLoader(next); F.ChunkLoader(next);
std::cout.write(F.data, F.len); std::cout.write(F.data, F.len);
} }
} break; }
break;
case 19: case 19:
fprintf(stderr, "Received AFM0 shared object\n"); fprintf(stderr, "Received AFM0 shared object\n");
break; break;
case 20:{//AMF0 command message case 20: { //AMF0 command message
fprintf(stderr, "Received AFM0 command message:\n"); fprintf(stderr, "Received AFM0 command message:\n");
amfdata = AMF::parse(next.data); amfdata = AMF::parse(next.data);
std::cerr << amfdata.Print() << std::endl; std::cerr << amfdata.Print() << std::endl;
} break; }
break;
case 22: case 22:
fprintf(stderr, "Received aggregate message\n"); fprintf(stderr, "Received aggregate message\n");
break; break;
@ -183,8 +191,8 @@ int main(int argc, char ** argv){
fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n"); fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n");
return 1; return 1;
break; break;
}//switch for type of chunk } //switch for type of chunk
}//while chunk parsed } //while chunk parsed
fprintf(stderr, "No more readable data\n"); fprintf(stderr, "No more readable data\n");
return 0; return 0;
}//main } //main

View file

@ -17,7 +17,7 @@
#include <mist/stream.h> #include <mist/stream.h>
/// Holds all code unique to the Buffer. /// Holds all code unique to the Buffer.
namespace Buffer{ namespace Buffer {
volatile bool buffer_running = true; ///< Set to false when shutting down. volatile bool buffer_running = true; ///< Set to false when shutting down.
Stream * thisStream = 0; Stream * thisStream = 0;
@ -26,19 +26,20 @@ namespace Buffer{
/// Gets the current system time in milliseconds. /// Gets the current system time in milliseconds.
long long int getNowMS(){ long long int getNowMS(){
timeval t; timeval t;
gettimeofday(&t, 0); gettimeofday( &t, 0);
return t.tv_sec * 1000 + t.tv_usec/1000; return t.tv_sec * 1000 + t.tv_usec / 1000;
}//getNowMS } //getNowMS
void handleStats(void * empty){ void handleStats(void * empty){
if (empty != 0){return;} if (empty != 0){
return;
}
std::string double_newline = "\n\n"; std::string double_newline = "\n\n";
Socket::Connection StatsSocket = Socket::Connection("/tmp/mist/statistics", true); Socket::Connection StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
while (buffer_running){ while (buffer_running){
usleep(1000000); //sleep one second usleep(1000000); //sleep one second
Stream::get()->cleanUsers(); Stream::get()->cleanUsers();
if (!StatsSocket.connected()){ if ( !StatsSocket.connected()){
StatsSocket = Socket::Connection("/tmp/mist/statistics", true); StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
} }
if (StatsSocket.connected()){ if (StatsSocket.connected()){
@ -52,9 +53,9 @@ namespace Buffer{
void handleUser(void * v_usr){ void handleUser(void * v_usr){
user * usr = (user*)v_usr; user * usr = (user*)v_usr;
#if DEBUG >= 4 #if DEBUG >= 4
std::cerr << "Thread launched for user " << usr->MyStr << ", socket number " << usr->S.getSocket() << std::endl; std::cerr << "Thread launched for user " << usr->MyStr << ", socket number " << usr->S.getSocket() << std::endl;
#endif #endif
usr->myRing = thisStream->getRing(); usr->myRing = thisStream->getRing();
if (thisStream->getHeader().size() > 0){ if (thisStream->getHeader().size() > 0){
@ -66,19 +67,19 @@ namespace Buffer{
usr->Send(); usr->Send();
if (usr->S.spool() && usr->S.Received().size()){ if (usr->S.spool() && usr->S.Received().size()){
//delete anything that doesn't end with a newline //delete anything that doesn't end with a newline
if (!usr->S.Received().get().empty() && *(usr->S.Received().get().rbegin()) != '\n'){ if ( !usr->S.Received().get().empty() && *(usr->S.Received().get().rbegin()) != '\n'){
usr->S.Received().get().clear(); usr->S.Received().get().clear();
continue; continue;
} }
usr->S.Received().get().resize(usr->S.Received().get().size() - 1); usr->S.Received().get().resize(usr->S.Received().get().size() - 1);
if (!usr->S.Received().get().empty()){ if ( !usr->S.Received().get().empty()){
switch (usr->S.Received().get()[0]){ switch (usr->S.Received().get()[0]){
case 'P':{ //Push case 'P': { //Push
std::cout << "Push attempt from IP " << usr->S.Received().get().substr(2) << std::endl; std::cout << "Push attempt from IP " << usr->S.Received().get().substr(2) << std::endl;
if (thisStream->checkWaitingIP(usr->S.Received().get().substr(2))){ if (thisStream->checkWaitingIP(usr->S.Received().get().substr(2))){
if (thisStream->setInput(usr->S)){ if (thisStream->setInput(usr->S)){
std::cout << "Push accepted!" << std::endl; std::cout << "Push accepted!" << std::endl;
usr->S = Socket::Connection(-1); usr->S = Socket::Connection( -1);
return; return;
}else{ }else{
usr->Disconnect("Push denied - push already in progress!"); usr->Disconnect("Push denied - push already in progress!");
@ -86,31 +87,40 @@ namespace Buffer{
}else{ }else{
usr->Disconnect("Push denied - invalid IP address!"); usr->Disconnect("Push denied - invalid IP address!");
} }
} break; }
case 'S':{ //Stats break;
case 'S': { //Stats
usr->tmpStats = Stats(usr->S.Received().get().substr(2)); usr->tmpStats = Stats(usr->S.Received().get().substr(2));
unsigned int secs = usr->tmpStats.conntime - usr->lastStats.conntime; unsigned int secs = usr->tmpStats.conntime - usr->lastStats.conntime;
if (secs < 1){secs = 1;} if (secs < 1){
secs = 1;
}
usr->curr_up = (usr->tmpStats.up - usr->lastStats.up) / secs; usr->curr_up = (usr->tmpStats.up - usr->lastStats.up) / secs;
usr->curr_down = (usr->tmpStats.down - usr->lastStats.down) / secs; usr->curr_down = (usr->tmpStats.down - usr->lastStats.down) / secs;
usr->lastStats = usr->tmpStats; usr->lastStats = usr->tmpStats;
thisStream->saveStats(usr->MyStr, usr->tmpStats); thisStream->saveStats(usr->MyStr, usr->tmpStats);
} break; }
case 's':{ //second-seek break;
case 's': { //second-seek
//ignored for now //ignored for now
} break; }
case 'f':{ //frame-seek break;
case 'f': { //frame-seek
//ignored for now //ignored for now
} break; }
case 'p':{ //play break;
case 'p': { //play
//ignored for now //ignored for now
} break; }
case 'o':{ //once-play break;
case 'o': { //once-play
//ignored for now //ignored for now
} break; }
case 'q':{ //quit-playing break;
case 'q': { //quit-playing
//ignored for now //ignored for now
} break; }
break;
} }
} }
} }
@ -120,11 +130,13 @@ namespace Buffer{
/// Loop reading DTSC data from stdin and processing it at the correct speed. /// Loop reading DTSC data from stdin and processing it at the correct speed.
void handleStdin(void * empty){ void handleStdin(void * empty){
if (empty != 0){return;} if (empty != 0){
long long int timeDiff = 0;//difference between local time and stream time return;
unsigned int lastPacket = 0;//last parsed packet timestamp }
long long int timeDiff = 0; //difference between local time and stream time
unsigned int lastPacket = 0; //last parsed packet timestamp
std::string inBuffer; std::string inBuffer;
char charBuffer[1024*10]; char charBuffer[1024 * 10];
unsigned int charCount; unsigned int charCount;
long long int now; long long int now;
@ -142,7 +154,7 @@ namespace Buffer{
thisStream->dropWriteLock(true); thisStream->dropWriteLock(true);
}else{ }else{
thisStream->dropWriteLock(false); thisStream->dropWriteLock(false);
std::cin.read(charBuffer, 1024*10); std::cin.read(charBuffer, 1024 * 10);
charCount = std::cin.gcount(); charCount = std::cin.gcount();
inBuffer.append(charBuffer, charCount); inBuffer.append(charBuffer, charCount);
} }
@ -157,7 +169,9 @@ namespace Buffer{
/// Loop reading DTSC data from an IP push address. /// Loop reading DTSC data from an IP push address.
/// No changes to the speed are made. /// No changes to the speed are made.
void handlePushin(void * empty){ void handlePushin(void * empty){
if (empty != 0){return;} if (empty != 0){
return;
}
while (buffer_running){ while (buffer_running){
if (thisStream->getIPInput().connected()){ if (thisStream->getIPInput().connected()){
if (thisStream->getIPInput().spool()){ if (thisStream->getIPInput().spool()){
@ -167,30 +181,34 @@ namespace Buffer{
thisStream->dropWriteLock(true); thisStream->dropWriteLock(true);
}else{ }else{
thisStream->dropWriteLock(false); thisStream->dropWriteLock(false);
usleep(1000);//1ms wait usleep(1000); //1ms wait
} }
}else{ }else{
usleep(1000);//1ms wait usleep(1000); //1ms wait
} }
}else{ }else{
usleep(1000000);//1s wait usleep(1000000); //1s wait
} }
} }
SS.close(); SS.close();
} }
/// Starts a loop, waiting for connections to send data to. /// Starts a loop, waiting for connections to send data to.
int Start(int argc, char ** argv) { int Start(int argc, char ** argv){
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION); Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
conf.addOption("stream_name", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Name of the stream this buffer will be providing.\"}")); conf.addOption("stream_name",
conf.addOption("awaiting_ip", JSON::fromString("{\"arg_num\":2, \"arg\":\"string\", \"default\":\"\", \"help\":\"IP address to expect incoming data from. This will completely disable reading from standard input if used.\"}")); JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Name of the stream this buffer will be providing.\"}"));
conf.addOption("reportstats", JSON::fromString("{\"default\":0, \"help\":\"Report stats to a controller process.\", \"short\":\"s\", \"long\":\"reportstats\"}")); conf.addOption("awaiting_ip",
JSON::fromString(
"{\"arg_num\":2, \"arg\":\"string\", \"default\":\"\", \"help\":\"IP address to expect incoming data from. This will completely disable reading from standard input if used.\"}"));
conf.addOption("reportstats",
JSON::fromString("{\"default\":0, \"help\":\"Report stats to a controller process.\", \"short\":\"s\", \"long\":\"reportstats\"}"));
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
std::string name = conf.getString("stream_name"); std::string name = conf.getString("stream_name");
SS = Util::Stream::makeLive(name); SS = Util::Stream::makeLive(name);
if (!SS.connected()) { if ( !SS.connected()){
perror("Could not create stream socket"); perror("Could not create stream socket");
return 1; return 1;
} }
@ -201,7 +219,9 @@ namespace Buffer{
Socket::Connection std_input(fileno(stdin)); Socket::Connection std_input(fileno(stdin));
tthread::thread * StatsThread = 0; tthread::thread * StatsThread = 0;
if (conf.getBool("reportstats")){StatsThread = new tthread::thread(handleStats, 0);} if (conf.getBool("reportstats")){
StatsThread = new tthread::thread(handleStats, 0);
}
tthread::thread * StdinThread = 0; tthread::thread * StdinThread = 0;
std::string await_ip = conf.getString("awaiting_ip"); std::string await_ip = conf.getString("awaiting_ip");
if (await_ip == ""){ if (await_ip == ""){
@ -220,7 +240,7 @@ namespace Buffer{
thisStream->addUser(usr_ptr); thisStream->addUser(usr_ptr);
usr_ptr->Thread = new tthread::thread(handleUser, (void *)usr_ptr); usr_ptr->Thread = new tthread::thread(handleUser, (void *)usr_ptr);
} }
}//main loop } //main loop
// disconnect listener // disconnect listener
buffer_running = false; buffer_running = false;
@ -230,16 +250,20 @@ namespace Buffer{
StatsThread->join(); StatsThread->join();
delete StatsThread; delete StatsThread;
} }
if (thisStream->getIPInput().connected()){thisStream->getIPInput().close();} if (thisStream->getIPInput().connected()){
thisStream->getIPInput().close();
}
StdinThread->join(); StdinThread->join();
delete StdinThread; delete StdinThread;
delete thisStream; delete thisStream;
return 0; return 0;
} }
};//Buffer namespace }
;
//Buffer namespace
/// Entry point for Buffer, simply calls Buffer::Start(). /// Entry point for Buffer, simply calls Buffer::Start().
int main(int argc, char ** argv){ int main(int argc, char ** argv){
return Buffer::Start(argc, argv); return Buffer::Start(argc, argv);
}//main } //main

View file

@ -13,7 +13,9 @@ Buffer::Stream * Buffer::Stream::get(){
if (ref == 0){ if (ref == 0){
//prevent creating two at the same time //prevent creating two at the same time
creator.lock(); creator.lock();
if (ref == 0){ref = new Stream();} if (ref == 0){
ref = new Stream();
}
creator.unlock(); creator.unlock();
} }
return ref; return ref;
@ -31,9 +33,9 @@ Buffer::Stream::~Stream(){
while (users.size() > 0){ while (users.size() > 0){
stats_mutex.lock(); stats_mutex.lock();
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
if ((**usersIt).S.connected()){ if (( * *usersIt).S.connected()){
(**usersIt).S.close(); ( * *usersIt).S.close();
printf("Closing user %s\n", (**usersIt).MyStr.c_str()); printf("Closing user %s\n", ( * *usersIt).MyStr.c_str());
} }
} }
stats_mutex.unlock(); stats_mutex.unlock();
@ -51,8 +53,8 @@ std::string & Buffer::Stream::getStats(){
stats_mutex.lock(); stats_mutex.lock();
if (users.size() > 0){ if (users.size() > 0){
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
tot_down += (**usersIt).curr_down; tot_down += ( * *usersIt).curr_down;
tot_up += (**usersIt).curr_up; tot_up += ( * *usersIt).curr_up;
tot_count++; tot_count++;
} }
} }
@ -62,8 +64,12 @@ std::string & Buffer::Stream::getStats(){
Storage["totals"]["now"] = now; Storage["totals"]["now"] = now;
Storage["buffer"] = name; Storage["buffer"] = name;
Storage["meta"] = Strm->metadata; Storage["meta"] = Strm->metadata;
if (Storage["meta"].isMember("audio")){Storage["meta"]["audio"].removeMember("init");} if (Storage["meta"].isMember("audio")){
if (Storage["meta"].isMember("video")){Storage["meta"]["video"].removeMember("init");} Storage["meta"]["audio"].removeMember("init");
}
if (Storage["meta"].isMember("video")){
Storage["meta"]["video"].removeMember("init");
}
ret = Storage.toString(); ret = Storage.toString();
Storage["log"].null(); Storage["log"].null();
stats_mutex.unlock(); stats_mutex.unlock();
@ -92,7 +98,7 @@ void Buffer::Stream::setWaitingIP(std::string ip){
/// Check if this is the IP address to accept push data from. /// Check if this is the IP address to accept push data from.
bool Buffer::Stream::checkWaitingIP(std::string ip){ bool Buffer::Stream::checkWaitingIP(std::string ip){
if (ip == waiting_ip || ip == "::ffff:"+waiting_ip){ if (ip == waiting_ip || ip == "::ffff:" + waiting_ip){
return true; return true;
}else{ }else{
std::cout << ip << " != " << waiting_ip << std::endl; std::cout << ip << " != " << waiting_ip << std::endl;
@ -115,7 +121,6 @@ Socket::Connection & Buffer::Stream::getIPInput(){
return ip_input; return ip_input;
} }
/// Stores intermediate statistics. /// Stores intermediate statistics.
void Buffer::Stream::saveStats(std::string username, Stats & stats){ void Buffer::Stream::saveStats(std::string username, Stats & stats){
stats_mutex.lock(); stats_mutex.lock();
@ -133,9 +138,10 @@ void Buffer::Stream::clearStats(std::string username, Stats & stats, std::string
stats_mutex.lock(); stats_mutex.lock();
if (Storage["curr"].isMember(username)){ if (Storage["curr"].isMember(username)){
Storage["curr"].removeMember(username); Storage["curr"].removeMember(username);
#if DEBUG >= 4 #if DEBUG >= 4
std::cout << "Disconnected user " << username << ": " << reason << ". " << stats.connector << " transferred " << stats.up << " up and " << stats.down << " down in " << stats.conntime << " seconds to " << stats.host << std::endl; std::cout << "Disconnected user " << username << ": " << reason << ". " << stats.connector << " transferred " << stats.up << " up and "
#endif << stats.down << " down in " << stats.conntime << " seconds to " << stats.host << std::endl;
#endif
} }
Storage["log"][username]["connector"] = stats.connector; Storage["log"][username]["connector"] = stats.connector;
Storage["log"][username]["up"] = stats.up; Storage["log"][username]["up"] = stats.up;
@ -154,23 +160,23 @@ void Buffer::Stream::cleanUsers(){
repeat = false; repeat = false;
if (users.size() > 0){ if (users.size() > 0){
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
if ((**usersIt).Thread == 0 && !(**usersIt).S.connected()){ if (( * *usersIt).Thread == 0 && !( * *usersIt).S.connected()){
delete *usersIt; delete *usersIt;
users.erase(usersIt); users.erase(usersIt);
repeat = true; repeat = true;
break; break;
}else{ }else{
if (!(**usersIt).S.connected()){ if ( !( * *usersIt).S.connected()){
if ((**usersIt).Thread->joinable()){ if (( * *usersIt).Thread->joinable()){
(**usersIt).Thread->join(); ( * *usersIt).Thread->join();
delete (**usersIt).Thread; delete ( * *usersIt).Thread;
(**usersIt).Thread = 0; ( * *usersIt).Thread = 0;
} }
} }
} }
} }
} }
}while(repeat); }while (repeat);
stats_mutex.unlock(); stats_mutex.unlock();
} }
@ -190,7 +196,9 @@ void Buffer::Stream::dropWriteLock(bool newpackets_available){
writers--; writers--;
rw_mutex.unlock(); rw_mutex.unlock();
rw_change.notify_all(); rw_change.notify_all();
if (newpackets_available){moreData.notify_all();} if (newpackets_available){
moreData.notify_all();
}
} }
/// Blocks until reading is safe. /// Blocks until reading is safe.

View file

@ -3,11 +3,13 @@
#pragma once #pragma once
#include <string> #include <string>
#include <mist/dtsc.h>
#include <mist/json.h> #include <mist/json.h>
#include <mist/socket.h>
#include "tinythread.h" #include "tinythread.h"
#include "buffer_user.h" #include "buffer_user.h"
namespace Buffer{ namespace Buffer {
/// Keeps track of a single streams inputs and outputs, taking care of thread safety and all other related issues. /// Keeps track of a single streams inputs and outputs, taking care of thread safety and all other related issues.
class Stream{ class Stream{
public: public:
@ -53,9 +55,9 @@ namespace Buffer{
void waitForData(); void waitForData();
/// Cleanup function /// Cleanup function
~Stream(); ~Stream();
private: private:
volatile int readers;///< Current count of active readers; volatile int readers; ///< Current count of active readers;
volatile int writers;///< Current count of waiting/active writers. volatile int writers; ///< Current count of waiting/active writers.
tthread::mutex rw_mutex; ///< Mutex for read/write locking. tthread::mutex rw_mutex; ///< Mutex for read/write locking.
tthread::condition_variable rw_change; ///< Triggered when reader/writer count changes. tthread::condition_variable rw_change; ///< Triggered when reader/writer count changes.
static Stream * ref; static Stream * ref;
@ -70,4 +72,5 @@ namespace Buffer{
std::string name; ///< Name for this buffer. std::string name; ///< Name for this buffer.
tthread::condition_variable moreData; ///< Triggered when more data becomes available. tthread::condition_variable moreData; ///< Triggered when more data becomes available.
}; };
}; }
;

View file

@ -5,7 +5,6 @@
#include "buffer_stream.h" #include "buffer_stream.h"
#include <sstream> #include <sstream>
#include <stdlib.h> //for atoi and friends #include <stdlib.h> //for atoi and friends
int Buffer::user::UserCount = 0; int Buffer::user::UserCount = 0;
/// Creates a new user from a newly connected socket. /// Creates a new user from a newly connected socket.
@ -21,42 +20,54 @@ Buffer::user::user(Socket::Connection fd){
currsend = 0; currsend = 0;
myRing = 0; myRing = 0;
Thread = 0; Thread = 0;
}//constructor gotproperaudio = false;
lastpointer = 0;
} //constructor
/// Drops held DTSC::Ring class, if one is held. /// Drops held DTSC::Ring class, if one is held.
Buffer::user::~user(){ Buffer::user::~user(){
Stream::get()->dropRing(myRing); Stream::get()->dropRing(myRing);
}//destructor } //destructor
/// Disconnects the current user. Doesn't do anything if already disconnected. /// Disconnects the current user. Doesn't do anything if already disconnected.
/// Prints "Disconnected user" to stdout if disconnect took place. /// Prints "Disconnected user" to stdout if disconnect took place.
void Buffer::user::Disconnect(std::string reason) { void Buffer::user::Disconnect(std::string reason){
if (S.connected()){S.close();} if (S.connected()){
S.close();
}
Stream::get()->clearStats(MyStr, lastStats, reason); Stream::get()->clearStats(MyStr, lastStats, reason);
}//Disconnect } //Disconnect
/// Tries to send the current buffer, returns true if success, false otherwise. /// Tries to send the current buffer, returns true if success, false otherwise.
/// Has a side effect of dropping the connection if send will never complete. /// Has a side effect of dropping the connection if send will never complete.
bool Buffer::user::doSend(const char * ptr, int len){ bool Buffer::user::doSend(const char * ptr, int len){
if (!len){return false;}//do not do empty sends if ( !len){
int r = S.iwrite(ptr+currsend, len-currsend); return false;
} //do not do empty sends
int r = S.iwrite(ptr + currsend, len - currsend);
if (r <= 0){ if (r <= 0){
if (errno == EWOULDBLOCK){return false;} if (errno == EWOULDBLOCK){
return false;
}
Disconnect(S.getError()); Disconnect(S.getError());
return false; return false;
} }
currsend += r; currsend += r;
return (currsend == len); return (currsend == len);
}//doSend } //doSend
/// Try to send data to this user. Disconnects if any problems occur. /// Try to send data to this user. Disconnects if any problems occur.
void Buffer::user::Send(){ void Buffer::user::Send(){
if (!myRing){return;}//no ring! if ( !myRing){
if (!S.connected()){return;}//cancel if not connected return;
} //no ring!
if ( !S.connected()){
return;
} //cancel if not connected
if (myRing->waiting){ if (myRing->waiting){
Stream::get()->waitForData(); Stream::get()->waitForData();
return; return;
}//still waiting for next buffer? } //still waiting for next buffer?
if (myRing->starved){ if (myRing->starved){
//if corrupt data, warn and get new DTSC::Ring //if corrupt data, warn and get new DTSC::Ring
std::cout << "Warning: User " << MyNum << " was send corrupt video data and send to the next keyframe!" << std::endl; std::cout << "Warning: User " << MyNum << " was send corrupt video data and send to the next keyframe!" << std::endl;
@ -69,11 +80,14 @@ void Buffer::user::Send(){
if (doSend(Stream::get()->getStream()->outPacket(myRing->b).c_str(), Stream::get()->getStream()->outPacket(myRing->b).length())){ if (doSend(Stream::get()->getStream()->outPacket(myRing->b).c_str(), Stream::get()->getStream()->outPacket(myRing->b).length())){
//switch to next buffer //switch to next buffer
currsend = 0; currsend = 0;
if (myRing->b <= 0){myRing->waiting = true; return;}//no next buffer? go in waiting mode. if (myRing->b <= 0){
myRing->waiting = true;
return;
} //no next buffer? go in waiting mode.
myRing->b--; myRing->b--;
}//completed a send } //completed a send
Stream::get()->dropReadLock(); Stream::get()->dropReadLock();
}//send } //send
/// Default constructor - should not be in use. /// Default constructor - should not be in use.
Buffer::Stats::Stats(){ Buffer::Stats::Stats(){
@ -87,22 +101,22 @@ Buffer::Stats::Stats(std::string s){
size_t f = s.find(' '); size_t f = s.find(' ');
if (f != std::string::npos){ if (f != std::string::npos){
host = s.substr(0, f); host = s.substr(0, f);
s.erase(0, f+1); s.erase(0, f + 1);
} }
f = s.find(' '); f = s.find(' ');
if (f != std::string::npos){ if (f != std::string::npos){
connector = s.substr(0, f); connector = s.substr(0, f);
s.erase(0, f+1); s.erase(0, f + 1);
} }
f = s.find(' '); f = s.find(' ');
if (f != std::string::npos){ if (f != std::string::npos){
conntime = atoi(s.substr(0, f).c_str()); conntime = atoi(s.substr(0, f).c_str());
s.erase(0, f+1); s.erase(0, f + 1);
} }
f = s.find(' '); f = s.find(' ');
if (f != std::string::npos){ if (f != std::string::npos){
up = atoi(s.substr(0, f).c_str()); up = atoi(s.substr(0, f).c_str());
s.erase(0, f+1); s.erase(0, f + 1);
down = atoi(s.c_str()); down = atoi(s.c_str());
} }
} }

View file

@ -7,17 +7,17 @@
#include <mist/socket.h> #include <mist/socket.h>
#include "tinythread.h" #include "tinythread.h"
namespace Buffer{ namespace Buffer {
/// Converts a stats line to up, down, host, connector and conntime values. /// Converts a stats line to up, down, host, connector and conntime values.
class Stats{ class Stats{
public: public:
unsigned int up; unsigned int up;
unsigned int down; unsigned int down;
std::string host; std::string host;
std::string connector; std::string connector;
unsigned int conntime; unsigned int conntime;
Stats(); Stats();
Stats(std::string s); Stats(std::string s);
}; };
/// Holds connected users. /// Holds connected users.

View file

@ -14,7 +14,6 @@
#include <mist/socket.h> #include <mist/socket.h>
#include <mist/http_parser.h> #include <mist/http_parser.h>
#include <mist/config.h> #include <mist/config.h>
#include <mist/procs.h>
#include <mist/stream.h> #include <mist/stream.h>
#include <mist/timing.h> #include <mist/timing.h>
#include <mist/auth.h> #include <mist/auth.h>
@ -22,7 +21,7 @@
#include "embed.js.h" #include "embed.js.h"
/// Holds everything unique to HTTP Connector. /// Holds everything unique to HTTP Connector.
namespace Connector_HTTP{ namespace Connector_HTTP {
/// Class for keeping track of connections to connectors. /// Class for keeping track of connections to connectors.
class ConnConn{ class ConnConn{
@ -34,12 +33,14 @@ namespace Connector_HTTP{
ConnConn(){ ConnConn(){
conn = 0; conn = 0;
lastuse = 0; lastuse = 0;
}; }
;
/// Constructor that sets lastuse to 0, but socket to s. /// Constructor that sets lastuse to 0, but socket to s.
ConnConn(Socket::Connection * s){ ConnConn(Socket::Connection * s){
conn = s; conn = s;
lastuse = 0; lastuse = 0;
}; }
;
/// Destructor that deletes the socket if non-null. /// Destructor that deletes the socket if non-null.
~ConnConn(){ ~ConnConn(){
if (conn){ if (conn){
@ -47,7 +48,8 @@ namespace Connector_HTTP{
delete conn; delete conn;
} }
conn = 0; conn = 0;
}; }
;
}; };
std::map<std::string, ConnConn *> connconn; ///< Connections to connectors std::map<std::string, ConnConn *> connconn; ///< Connections to connectors
@ -59,7 +61,7 @@ namespace Connector_HTTP{
tthread::thread * timeouter = 0; ///< Thread that times out connections to connectors. tthread::thread * timeouter = 0; ///< Thread that times out connections to connectors.
void Timeout_Thread(void * n){ void Timeout_Thread(void * n){
n = 0;//prevent unused variable warning n = 0; //prevent unused variable warning
tthread::lock_guard<tthread::mutex> guard(timeout_mutex); tthread::lock_guard<tthread::mutex> guard(timeout_mutex);
while (true){ while (true){
{ {
@ -69,19 +71,21 @@ namespace Connector_HTTP{
} }
std::map<std::string, ConnConn*>::iterator it; std::map<std::string, ConnConn*>::iterator it;
for (it = connconn.begin(); it != connconn.end(); it++){ for (it = connconn.begin(); it != connconn.end(); it++){
if (!it->second->conn->connected() || it->second->lastuse++ > 15){ if ( !it->second->conn->connected() || it->second->lastuse++ > 15){
if (it->second->in_use.try_lock()){ if (it->second->in_use.try_lock()){
it->second->in_use.unlock(); it->second->in_use.unlock();
delete it->second; delete it->second;
connconn.erase(it); connconn.erase(it);
it = connconn.begin();//get a valid iterator it = connconn.begin(); //get a valid iterator
if (it == connconn.end()){return;} if (it == connconn.end()){
return;
}
} }
} }
} }
conn_mutex.unlock(); conn_mutex.unlock();
} }
usleep(1000000);//sleep 1 second and re-check usleep(1000000); //sleep 1 second and re-check
} }
} }
@ -89,14 +93,16 @@ namespace Connector_HTTP{
void Handle_None(HTTP::Parser & H, Socket::Connection * conn){ void Handle_None(HTTP::Parser & H, Socket::Connection * conn){
H.Clean(); H.Clean();
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver); H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
H.SetBody("<!DOCTYPE html><html><head><title>Unsupported Media Type</title></head><body><h1>Unsupported Media Type</h1>The server isn't quite sure what you wanted to receive from it.</body></html>"); H.SetBody(
"<!DOCTYPE html><html><head><title>Unsupported Media Type</title></head><body><h1>Unsupported Media Type</h1>The server isn't quite sure what you wanted to receive from it.</body></html>");
conn->SendNow(H.BuildResponse("415", "Unsupported Media Type")); conn->SendNow(H.BuildResponse("415", "Unsupported Media Type"));
} }
void Handle_Timeout(HTTP::Parser & H, Socket::Connection * conn){ void Handle_Timeout(HTTP::Parser & H, Socket::Connection * conn){
H.Clean(); H.Clean();
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver); H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
H.SetBody("<!DOCTYPE html><html><head><title>Gateway timeout</title></head><body><h1>Gateway timeout</h1>Though the server understood your request and attempted to handle it, somehow handling it took longer than it should. Your request has been cancelled - please try again later.</body></html>"); H.SetBody(
"<!DOCTYPE html><html><head><title>Gateway timeout</title></head><body><h1>Gateway timeout</h1>Though the server understood your request and attempted to handle it, somehow handling it took longer than it should. Your request has been cancelled - please try again later.</body></html>");
conn->SendNow(H.BuildResponse("504", "Gateway Timeout")); conn->SendNow(H.BuildResponse("504", "Gateway Timeout"));
} }
@ -109,21 +115,24 @@ namespace Connector_HTTP{
H.Clean(); H.Clean();
H.SetHeader("Content-Type", "text/xml"); H.SetHeader("Content-Type", "text/xml");
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver); H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
H.SetBody("<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy SYSTEM \"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\"><cross-domain-policy><allow-access-from domain=\"*\" /><site-control permitted-cross-domain-policies=\"all\"/></cross-domain-policy>"); H.SetBody(
"<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy SYSTEM \"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\"><cross-domain-policy><allow-access-from domain=\"*\" /><site-control permitted-cross-domain-policies=\"all\"/></cross-domain-policy>");
conn->SendNow(H.BuildResponse("200", "OK")); conn->SendNow(H.BuildResponse("200", "OK"));
return; return;
}//crossdomain.xml } //crossdomain.xml
if (url == "/clientaccesspolicy.xml"){ if (url == "/clientaccesspolicy.xml"){
H.Clean(); H.Clean();
H.SetHeader("Content-Type", "text/xml"); H.SetHeader("Content-Type", "text/xml");
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver); H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
H.SetBody("<?xml version=\"1.0\" encoding=\"utf-8\"?><access-policy><cross-domain-access><policy><allow-from http-methods=\"*\" http-request-headers=\"*\"><domain uri=\"*\"/></allow-from><grant-to><resource path=\"/\" include-subpaths=\"true\"/></grant-to></policy></cross-domain-access></access-policy>"); H.SetBody(
"<?xml version=\"1.0\" encoding=\"utf-8\"?><access-policy><cross-domain-access><policy><allow-from http-methods=\"*\" http-request-headers=\"*\"><domain uri=\"*\"/></allow-from><grant-to><resource path=\"/\" include-subpaths=\"true\"/></grant-to></policy></cross-domain-access></access-policy>");
conn->SendNow(H.BuildResponse("200", "OK")); conn->SendNow(H.BuildResponse("200", "OK"));
return; return;
}//clientaccesspolicy.xml } //clientaccesspolicy.xml
if ((url.length() > 9 && url.substr(0, 6) == "/info_" && url.substr(url.length() - 3, 3) == ".js") || (url.length() > 10 && url.substr(0, 7) == "/embed_" && url.substr(url.length() - 3, 3) == ".js")){ if ((url.length() > 9 && url.substr(0, 6) == "/info_" && url.substr(url.length() - 3, 3) == ".js")
|| (url.length() > 10 && url.substr(0, 7) == "/embed_" && url.substr(url.length() - 3, 3) == ".js")){
std::string streamname; std::string streamname;
if (url.substr(0, 6) == "/info_"){ if (url.substr(0, 6) == "/info_"){
streamname = url.substr(6, url.length() - 9); streamname = url.substr(6, url.length() - 9);
@ -134,7 +143,9 @@ namespace Connector_HTTP{
JSON::Value ServConf = JSON::fromFile("/tmp/mist/streamlist"); JSON::Value ServConf = JSON::fromFile("/tmp/mist/streamlist");
std::string response; std::string response;
std::string host = H.GetHeader("Host"); std::string host = H.GetHeader("Host");
if (host.find(':')){host.resize(host.find(':'));} if (host.find(':')){
host.resize(host.find(':'));
}
H.Clean(); H.Clean();
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver); H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
H.SetHeader("Content-Type", "application/javascript"); H.SetHeader("Content-Type", "application/javascript");
@ -145,72 +156,74 @@ namespace Connector_HTTP{
json_resp["height"] = ServConf["streams"][streamname]["meta"]["video"]["height"].asInt(); json_resp["height"] = ServConf["streams"][streamname]["meta"]["video"]["height"].asInt();
//first, see if we have RTMP working and output all the RTMP. //first, see if we have RTMP working and output all the RTMP.
for (JSON::ArrIter it = ServConf["config"]["protocols"].ArrBegin(); it != ServConf["config"]["protocols"].ArrEnd(); it++){ for (JSON::ArrIter it = ServConf["config"]["protocols"].ArrBegin(); it != ServConf["config"]["protocols"].ArrEnd(); it++){
if ((*it)["connector"].asString() == "RTMP"){ if (( *it)["connector"].asString() == "RTMP"){
JSON::Value tmp; JSON::Value tmp;
tmp["type"] = "rtmp"; tmp["type"] = "rtmp";
tmp["url"] = "rtmp://" + host + ":" + (*it)["port"].asString() + "/play/" + streamname; tmp["url"] = "rtmp://" + host + ":" + ( *it)["port"].asString() + "/play/" + streamname;
json_resp["source"].append(tmp); json_resp["source"].append(tmp);
} }
} }
//then, see if we have HTTP working and output all the dynamic. //then, see if we have HTTP working and output all the dynamic.
for (JSON::ArrIter it = ServConf["config"]["protocols"].ArrBegin(); it != ServConf["config"]["protocols"].ArrEnd(); it++){ for (JSON::ArrIter it = ServConf["config"]["protocols"].ArrBegin(); it != ServConf["config"]["protocols"].ArrEnd(); it++){
if ((*it)["connector"].asString() == "HTTP"){ if (( *it)["connector"].asString() == "HTTP"){
JSON::Value tmp; JSON::Value tmp;
tmp["type"] = "f4v"; tmp["type"] = "f4v";
tmp["url"] = "http://" + host + ":" + (*it)["port"].asString() + "/"+streamname+"/manifest.f4m"; tmp["url"] = "http://" + host + ":" + ( *it)["port"].asString() + "/" + streamname + "/manifest.f4m";
json_resp["source"].append(tmp); json_resp["source"].append(tmp);
} }
} }
//and all the progressive. //and all the progressive.
for (JSON::ArrIter it = ServConf["config"]["protocols"].ArrBegin(); it != ServConf["config"]["protocols"].ArrEnd(); it++){ for (JSON::ArrIter it = ServConf["config"]["protocols"].ArrBegin(); it != ServConf["config"]["protocols"].ArrEnd(); it++){
if ((*it)["connector"].asString() == "HTTP"){ if (( *it)["connector"].asString() == "HTTP"){
JSON::Value tmp; JSON::Value tmp;
tmp["type"] = "flv"; tmp["type"] = "flv";
tmp["url"] = "http://" + host + ":" + (*it)["port"].asString() + "/"+streamname+".flv"; tmp["url"] = "http://" + host + ":" + ( *it)["port"].asString() + "/" + streamname + ".flv";
json_resp["source"].append(tmp); json_resp["source"].append(tmp);
} }
} }
}else{ }else{
json_resp["error"] = "The specified stream is not available on this server."; json_resp["error"] = "The specified stream is not available on this server.";
json_resp["bbq"] = "sauce";//for legacy purposes ^_^ json_resp["bbq"] = "sauce"; //for legacy purposes ^_^
} }
response += "mistvideo['" + streamname + "'] = "+json_resp.toString()+";\n"; response += "mistvideo['" + streamname + "'] = " + json_resp.toString() + ";\n";
if (url.substr(0, 6) != "/info_" && !json_resp.isMember("error")){ if (url.substr(0, 6) != "/info_" && !json_resp.isMember("error")){
response.append("\n("); response.append("\n(");
response.append((char*)embed_js, (size_t)embed_js_len-2);//remove trailing ";\n" from xxd conversion response.append((char*)embed_js, (size_t)embed_js_len - 2); //remove trailing ";\n" from xxd conversion
response.append("(\"" + streamname + "\"));\n"); response.append("(\"" + streamname + "\"));\n");
} }
H.SetBody(response); H.SetBody(response);
conn->SendNow(H.BuildResponse("200", "OK")); conn->SendNow(H.BuildResponse("200", "OK"));
return; return;
}//embed code generator } //embed code generator
Handle_None(H, conn);//anything else doesn't get handled Handle_None(H, conn); //anything else doesn't get handled
} }
/// Handles requests without associated handler, displaying a nice friendly error message. /// Handles requests without associated handler, displaying a nice friendly error message.
void Handle_Through_Connector(HTTP::Parser & H, Socket::Connection * conn, std::string & connector){ void Handle_Through_Connector(HTTP::Parser & H, Socket::Connection * conn, std::string & connector){
//create a unique ID based on a hash of the user agent and host, followed by the stream name and connector //create a unique ID based on a hash of the user agent and host, followed by the stream name and connector
std::string uid = Secure::md5(H.GetHeader("User-Agent")+conn->getHost())+"_"+H.GetVar("stream")+"_"+connector; std::string uid = Secure::md5(H.GetHeader("User-Agent") + conn->getHost()) + "_" + H.GetVar("stream") + "_" + connector;
H.SetHeader("X-UID", uid);//add the UID to the headers before copying H.SetHeader("X-UID", uid); //add the UID to the headers before copying
H.SetHeader("X-Origin", conn->getHost());//add the UID to the headers before copying H.SetHeader("X-Origin", conn->getHost()); //add the UID to the headers before copying
std::string request = H.BuildRequest();//copy the request for later forwarding to the connector std::string request = H.BuildRequest(); //copy the request for later forwarding to the connector
std::string orig_url = H.getUrl(); std::string orig_url = H.getUrl();
H.Clean(); H.Clean();
//check if a connection exists, and if not create one //check if a connection exists, and if not create one
conn_mutex.lock(); conn_mutex.lock();
if (!connconn.count(uid) || !connconn[uid]->conn->connected()){ if ( !connconn.count(uid) || !connconn[uid]->conn->connected()){
if (connconn.count(uid)){connconn.erase(uid);} if (connconn.count(uid)){
connconn[uid] = new ConnConn(new Socket::Connection("/tmp/mist/http_"+connector)); connconn.erase(uid);
connconn[uid]->conn->setBlocking(false);//do not block on spool() with no data }
#if DEBUG >= 4 connconn[uid] = new ConnConn(new Socket::Connection("/tmp/mist/http_" + connector));
connconn[uid]->conn->setBlocking(false); //do not block on spool() with no data
#if DEBUG >= 4
std::cout << "Created new connection " << uid << std::endl; std::cout << "Created new connection " << uid << std::endl;
#endif #endif
}else{ }else{
#if DEBUG >= 4 #if DEBUG >= 4
std::cout << "Re-using connection " << uid << std::endl; std::cout << "Re-using connection " << uid << std::endl;
#endif #endif
} }
//start a new timeout thread, if neccesary //start a new timeout thread, if neccesary
if (timeout_mutex.try_lock()){ if (timeout_mutex.try_lock()){
@ -226,7 +239,7 @@ namespace Connector_HTTP{
//lock the mutex for this connection, and handle the request //lock the mutex for this connection, and handle the request
tthread::lock_guard<tthread::mutex> guard(connconn[uid]->in_use); tthread::lock_guard<tthread::mutex> guard(connconn[uid]->in_use);
//if the server connection is dead, handle as timeout. //if the server connection is dead, handle as timeout.
if (!connconn.count(uid) || !connconn[uid]->conn->connected()){ if ( !connconn.count(uid) || !connconn[uid]->conn->connected()){
Handle_Timeout(H, conn); Handle_Timeout(H, conn);
return; return;
} }
@ -239,7 +252,7 @@ namespace Connector_HTTP{
conn->spool(); conn->spool();
if (connconn[uid]->conn->Received().size() || connconn[uid]->conn->spool()){ if (connconn[uid]->conn->Received().size() || connconn[uid]->conn->spool()){
//make sure we end in a \n //make sure we end in a \n
if (*(connconn[uid]->conn->Received().get().rbegin()) != '\n'){ if ( *(connconn[uid]->conn->Received().get().rbegin()) != '\n'){
std::string tmp = connconn[uid]->conn->Received().get(); std::string tmp = connconn[uid]->conn->Received().get();
connconn[uid]->conn->Received().get().clear(); connconn[uid]->conn->Received().get().clear();
if (connconn[uid]->conn->Received().size()){ if (connconn[uid]->conn->Received().size()){
@ -250,7 +263,7 @@ namespace Connector_HTTP{
} }
//check if the whole response was received //check if the whole response was received
if (H.Read(connconn[uid]->conn->Received().get())){ if (H.Read(connconn[uid]->conn->Received().get())){
break;//continue down below this while loop break; //continue down below this while loop
} }
}else{ }else{
//keep trying unless the timeout triggers //keep trying unless the timeout triggers
@ -263,7 +276,7 @@ namespace Connector_HTTP{
} }
} }
} }
if (!connconn.count(uid) || !connconn[uid]->conn->connected() || !conn->connected()){ if ( !connconn.count(uid) || !connconn[uid]->conn->connected() || !conn->connected()){
//failure, disconnect and sent error to user //failure, disconnect and sent error to user
Handle_Timeout(H, conn); Handle_Timeout(H, conn);
return; return;
@ -310,13 +323,13 @@ namespace Connector_HTTP{
std::string getHTTPType(HTTP::Parser & H){ std::string getHTTPType(HTTP::Parser & H){
std::string url = H.getUrl(); std::string url = H.getUrl();
if ((url.find("f4m") != std::string::npos) || ((url.find("Seg") != std::string::npos) && (url.find("Frag") != std::string::npos))){ if ((url.find("f4m") != std::string::npos) || ((url.find("Seg") != std::string::npos) && (url.find("Frag") != std::string::npos))){
std::string streamname = url.substr(1,url.find("/",1)-1); std::string streamname = url.substr(1, url.find("/", 1) - 1);
Util::Stream::sanitizeName(streamname); Util::Stream::sanitizeName(streamname);
H.SetVar("stream", streamname); H.SetVar("stream", streamname);
return "dynamic"; return "dynamic";
} }
if (url.find("/smooth/") != std::string::npos && url.find(".ism") != std::string::npos ) { if (url.find("/smooth/") != std::string::npos && url.find(".ism") != std::string::npos){
std::string streamname = url.substr(8,url.find("/",8)-12); std::string streamname = url.substr(8, url.find("/", 8) - 12);
Util::Stream::sanitizeName(streamname); Util::Stream::sanitizeName(streamname);
H.SetVar("stream", streamname); H.SetVar("stream", streamname);
return "smooth"; return "smooth";
@ -324,28 +337,36 @@ namespace Connector_HTTP{
if (url.length() > 4){ if (url.length() > 4){
std::string ext = url.substr(url.length() - 4, 4); std::string ext = url.substr(url.length() - 4, 4);
if (ext == ".flv" || ext == ".mp3"){ if (ext == ".flv" || ext == ".mp3"){
std::string streamname = url.substr(1,url.length() - 5); std::string streamname = url.substr(1, url.length() - 5);
Util::Stream::sanitizeName(streamname); Util::Stream::sanitizeName(streamname);
H.SetVar("stream", streamname); H.SetVar("stream", streamname);
return "progressive"; return "progressive";
} }
} }
if (url == "/crossdomain.xml"){return "internal";} if (url == "/crossdomain.xml"){
if (url == "/clientaccesspolicy.xml"){return "internal";} return "internal";
if (url.length() > 10 && url.substr(0, 7) == "/embed_" && url.substr(url.length() - 3, 3) == ".js"){return "internal";} }
if (url.length() > 9 && url.substr(0, 6) == "/info_" && url.substr(url.length() - 3, 3) == ".js"){return "internal";} if (url == "/clientaccesspolicy.xml"){
return "internal";
}
if (url.length() > 10 && url.substr(0, 7) == "/embed_" && url.substr(url.length() - 3, 3) == ".js"){
return "internal";
}
if (url.length() > 9 && url.substr(0, 6) == "/info_" && url.substr(url.length() - 3, 3) == ".js"){
return "internal";
}
return "none"; return "none";
} }
/// Thread for handling a single HTTP connection /// Thread for handling a single HTTP connection
void Handle_HTTP_Connection(void * pointer){ void Handle_HTTP_Connection(void * pointer){
Socket::Connection * conn = (Socket::Connection *)pointer; Socket::Connection * conn = (Socket::Connection *)pointer;
conn->setBlocking(false);//do not block on conn.spool() when no data is available conn->setBlocking(false); //do not block on conn.spool() when no data is available
HTTP::Parser Client; HTTP::Parser Client;
while (conn->connected()){ while (conn->connected()){
if (conn->spool() || conn->Received().size()){ if (conn->spool() || conn->Received().size()){
//make sure it ends in a \n //make sure it ends in a \n
if (*(conn->Received().get().rbegin()) != '\n'){ if ( *(conn->Received().get().rbegin()) != '\n'){
std::string tmp = conn->Received().get(); std::string tmp = conn->Received().get();
conn->Received().get().clear(); conn->Received().get().clear();
if (conn->Received().size()){ if (conn->Received().size()){
@ -357,9 +378,10 @@ namespace Connector_HTTP{
if (Client.Read(conn->Received().get())){ if (Client.Read(conn->Received().get())){
std::string handler = getHTTPType(Client); std::string handler = getHTTPType(Client);
long long int startms = Util::getMS(); long long int startms = Util::getMS();
#if DEBUG >= 4 #if DEBUG >= 4
std::cout << "Received request: " << Client.getUrl() << " (" << conn->getSocket() << ") => " << handler << " (" << Client.GetVar("stream") << ")" << std::endl; std::cout << "Received request: " << Client.getUrl() << " (" << conn->getSocket() << ") => " << handler << " (" << Client.GetVar("stream")
#endif << ")" << std::endl;
#endif
if (handler == "none" || handler == "internal"){ if (handler == "none" || handler == "internal"){
if (handler == "internal"){ if (handler == "internal"){
Handle_Internal(Client, conn); Handle_Internal(Client, conn);
@ -369,13 +391,13 @@ namespace Connector_HTTP{
}else{ }else{
Handle_Through_Connector(Client, conn, handler); Handle_Through_Connector(Client, conn, handler);
} }
#if DEBUG >= 4 #if DEBUG >= 4
std::cout << "Completed request (" << conn->getSocket() << ") " << handler << " in " << (Util::getMS() - startms) << " ms" << std::endl; std::cout << "Completed request (" << conn->getSocket() << ") " << handler << " in " << (Util::getMS() - startms) << " ms" << std::endl;
#endif #endif
Client.Clean(); //clean for any possible next requests Client.Clean(); //clean for any possible next requests
} }
}else{ }else{
usleep(10000);//sleep 10ms usleep(10000); //sleep 10ms
} }
} }
//close and remove the connection //close and remove the connection
@ -384,8 +406,8 @@ namespace Connector_HTTP{
//remove this thread from active_threads and add it to done_threads. //remove this thread from active_threads and add it to done_threads.
thread_mutex.lock(); thread_mutex.lock();
for (std::set<tthread::thread *>::iterator it = active_threads.begin(); it != active_threads.end(); it++){ for (std::set<tthread::thread *>::iterator it = active_threads.begin(); it != active_threads.end(); it++){
if ((*it)->get_id() == tthread::this_thread::get_id()){ if (( *it)->get_id() == tthread::this_thread::get_id()){
tthread::thread * T = (*it); tthread::thread * T = ( *it);
active_threads.erase(T); active_threads.erase(T);
done_threads.insert(T); done_threads.insert(T);
break; break;
@ -394,26 +416,27 @@ namespace Connector_HTTP{
thread_mutex.unlock(); thread_mutex.unlock();
} }
};//Connector_HTTP namespace } //Connector_HTTP namespace
int main(int argc, char ** argv){ int main(int argc, char ** argv){
Util::Config conf(argv[0], PACKAGE_VERSION); Util::Config conf(argv[0], PACKAGE_VERSION);
conf.addConnectorOptions(8080); conf.addConnectorOptions(8080);
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
Socket::Server server_socket = Socket::Server(conf.getInteger("listen_port"), conf.getString("listen_interface")); Socket::Server server_socket = Socket::Server(conf.getInteger("listen_port"), conf.getString("listen_interface"));
if (!server_socket.connected()){return 1;} if ( !server_socket.connected()){
return 1;
}
conf.activate(); conf.activate();
while (server_socket.connected() && conf.is_active){ while (server_socket.connected() && conf.is_active){
Socket::Connection S = server_socket.accept(); Socket::Connection S = server_socket.accept();
if (S.connected()){//check if the new connection is valid if (S.connected()){ //check if the new connection is valid
//lock the thread mutex and spawn a new thread for this connection //lock the thread mutex and spawn a new thread for this connection
Connector_HTTP::thread_mutex.lock(); Connector_HTTP::thread_mutex.lock();
tthread::thread * T = new tthread::thread(Connector_HTTP::Handle_HTTP_Connection, (void *)(new Socket::Connection(S))); tthread::thread * T = new tthread::thread(Connector_HTTP::Handle_HTTP_Connection, (void *)(new Socket::Connection(S)));
Connector_HTTP::active_threads.insert(T); Connector_HTTP::active_threads.insert(T);
//clean up any threads that may have finished //clean up any threads that may have finished
while (!Connector_HTTP::done_threads.empty()){ while ( !Connector_HTTP::done_threads.empty()){
T = *Connector_HTTP::done_threads.begin(); T = *Connector_HTTP::done_threads.begin();
T->join(); T->join();
Connector_HTTP::done_threads.erase(T); Connector_HTTP::done_threads.erase(T);
@ -421,10 +444,28 @@ int main(int argc, char ** argv){
} }
Connector_HTTP::thread_mutex.unlock(); Connector_HTTP::thread_mutex.unlock();
}else{ }else{
usleep(100000);//sleep 100ms usleep(100000); //sleep 100ms
} }
}//while connected and not requested to stop } //while connected and not requested to stop
server_socket.close(); server_socket.close();
Util::Procs::StopAll();
//wait for existing connections to drop
bool repeat = true;
while (repeat){
Connector_HTTP::thread_mutex.lock();
repeat = !Connector_HTTP::active_threads.empty();
if (repeat){
usleep(100000); //sleep 100ms
}
//clean up any threads that may have finished
while ( !Connector_HTTP::done_threads.empty()){
tthread::thread * T = *Connector_HTTP::done_threads.begin();
T->join();
Connector_HTTP::done_threads.erase(T);
delete T;
}
Connector_HTTP::thread_mutex.unlock();
}
return 0; return 0;
}//main } //main

View file

@ -24,7 +24,7 @@
#include <mist/timing.h> #include <mist/timing.h>
/// Holds everything unique to HTTP Dynamic Connector. /// Holds everything unique to HTTP Dynamic Connector.
namespace Connector_HTTP{ namespace Connector_HTTP {
std::string GenerateBootstrap(std::string & MovieId, JSON::Value & metadata, int fragnum, int starttime, int endtime){ std::string GenerateBootstrap(std::string & MovieId, JSON::Value & metadata, int fragnum, int starttime, int endtime){
std::string empty; std::string empty;
@ -37,13 +37,12 @@ namespace Connector_HTTP{
} }
asrt.setVersion(1); asrt.setVersion(1);
asrt.setQualityEntry(empty, 0); asrt.setQualityEntry(empty, 0);
if (!metadata.isMember("keytime") || metadata["keytime"].size() == 0){ if ( !metadata.isMember("keytime") || metadata["keytime"].size() == 0){
asrt.setSegmentRun(1, 20000, 0); asrt.setSegmentRun(1, 20000, 0);
}else{ }else{
asrt.setSegmentRun(1, metadata["keytime"].size(), 0); asrt.setSegmentRun(1, metadata["keytime"].size(), 0);
} }
MP4::AFRT afrt; MP4::AFRT afrt;
if (starttime == 0){ if (starttime == 0){
afrt.setUpdate(false); afrt.setUpdate(false);
@ -54,10 +53,10 @@ namespace Connector_HTTP{
afrt.setTimeScale(1000); afrt.setTimeScale(1000);
afrt.setQualityEntry(empty, 0); afrt.setQualityEntry(empty, 0);
MP4::afrt_runtable afrtrun; MP4::afrt_runtable afrtrun;
if (!metadata.isMember("keytime") || metadata["keytime"].size() == 0){ if ( !metadata.isMember("keytime") || metadata["keytime"].size() == 0){
afrtrun.firstFragment = 1; afrtrun.firstFragment = 1;
afrtrun.firstTimestamp = 0; afrtrun.firstTimestamp = 0;
if (!metadata.isMember("video") || !metadata["video"].isMember("keyms") || metadata["video"]["keyms"].asInt() == 0){ if ( !metadata.isMember("video") || !metadata["video"].isMember("keyms") || metadata["video"]["keyms"].asInt() == 0){
afrtrun.duration = 2000; afrtrun.duration = 2000;
}else{ }else{
afrtrun.duration = metadata["video"]["keyms"].asInt(); afrtrun.duration = metadata["video"]["keyms"].asInt();
@ -65,21 +64,21 @@ namespace Connector_HTTP{
afrt.setFragmentRun(afrtrun, 0); afrt.setFragmentRun(afrtrun, 0);
}else{ }else{
for (int i = 0; i < metadata["keytime"].size(); i++){ for (int i = 0; i < metadata["keytime"].size(); i++){
afrtrun.firstFragment = i+1; afrtrun.firstFragment = i + 1;
afrtrun.firstTimestamp = metadata["keytime"][i].asInt(); afrtrun.firstTimestamp = metadata["keytime"][i].asInt();
if (i+1 < metadata["keytime"].size()){ if (i + 1 < metadata["keytime"].size()){
afrtrun.duration = metadata["keytime"][i+1].asInt() - metadata["keytime"][i].asInt(); afrtrun.duration = metadata["keytime"][i + 1].asInt() - metadata["keytime"][i].asInt();
}else{ }else{
if (metadata["lastms"].asInt()){ if (metadata["lastms"].asInt()){
afrtrun.duration = metadata["lastms"].asInt() - metadata["keytime"][i].asInt(); afrtrun.duration = metadata["lastms"].asInt() - metadata["keytime"][i].asInt();
}else{ }else{
afrtrun.duration = 3000;//guess 3 seconds if unknown afrtrun.duration = 3000; //guess 3 seconds if unknown
} }
} }
afrt.setFragmentRun(afrtrun, i); afrt.setFragmentRun(afrtrun, i);
} }
} }
MP4::ABST abst; MP4::ABST abst;
abst.setVersion(1); abst.setVersion(1);
abst.setBootstrapinfoVersion(1); abst.setBootstrapinfoVersion(1);
@ -95,7 +94,7 @@ namespace Connector_HTTP{
if (metadata["lastms"].asInt()){ if (metadata["lastms"].asInt()){
abst.setCurrentMediaTime(metadata["lastms"].asInt()); abst.setCurrentMediaTime(metadata["lastms"].asInt());
}else{ }else{
abst.setCurrentMediaTime(1000*metadata["length"].asInt()); abst.setCurrentMediaTime(1000 * metadata["length"].asInt());
} }
}else{ }else{
abst.setLive(true); abst.setLive(true);
@ -110,63 +109,65 @@ namespace Connector_HTTP{
abst.setSegmentRunTable(asrt, 0); abst.setSegmentRunTable(asrt, 0);
abst.setFragmentRunTable(afrt, 0); abst.setFragmentRunTable(afrt, 0);
#if DEBUG >= 8 #if DEBUG >= 8
std::cout << "Sending bootstrap:" << std::endl << abst.toPrettyString(0) << std::endl; std::cout << "Sending bootstrap:" << std::endl << abst.toPrettyString(0) << std::endl;
#endif #endif
return std::string((char*)abst.asBox(), (int)abst.boxedSize()); return std::string((char*)abst.asBox(), (int)abst.boxedSize());
} }
/// Returns a F4M-format manifest file /// Returns a F4M-format manifest file
std::string BuildManifest(std::string & MovieId, JSON::Value & metadata){ std::string BuildManifest(std::string & MovieId, JSON::Value & metadata){
std::string Result; std::string Result;
if (metadata.isMember("length") && metadata["length"].asInt() > 0){ if (metadata.isMember("length") && metadata["length"].asInt() > 0){
Result="<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" Result =
"<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n" "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<id>" + MovieId + "</id>\n" "<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n"
"<width>" + metadata["video"]["width"].asString() + "</width>\n" "<id>" + MovieId + "</id>\n"
"<height>" + metadata["video"]["height"].asString() + "</height>\n" "<width>" + metadata["video"]["width"].asString() + "</width>\n"
"<duration>" + metadata["length"].asString() + ".000</duration>\n" "<height>" + metadata["video"]["height"].asString() + "</height>\n"
"<mimeType>video/mp4</mimeType>\n" "<duration>" + metadata["length"].asString() + ".000</duration>\n"
"<streamType>recorded</streamType>\n" "<mimeType>video/mp4</mimeType>\n"
"<deliveryType>streaming</deliveryType>\n" "<streamType>recorded</streamType>\n"
"<bootstrapInfo profile=\"named\" id=\"bootstrap1\">" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0, 0)) + "</bootstrapInfo>\n" "<deliveryType>streaming</deliveryType>\n"
"<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"" + MovieId + "/\">\n" "<bootstrapInfo profile=\"named\" id=\"bootstrap1\">" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0, 0))
"<metadata>AgAKb25NZXRhRGF0YQgAAAAAAAl0cmFja2luZm8KAAAAAgMACXRpbWVzY2FsZQBA+GoAAAAAAAAGbGVuZ3RoAEGMcHoQAAAAAAhsYW5ndWFnZQIAA2VuZwARc2FtcGxlZGVzY3JpcHRpb24KAAAAAQMACnNhbXBsZXR5cGUCAARhdmMxAAAJAAAJAwAJdGltZXNjYWxlAEDncAAAAAAAAAZsZW5ndGgAQXtNvTAAAAAACGxhbmd1YWdlAgADZW5nABFzYW1wbGVkZXNjcmlwdGlvbgoAAAABAwAKc2FtcGxldHlwZQIABG1wNGEAAAkAAAkADWF1ZGlvY2hhbm5lbHMAQAAAAAAAAAAAD2F1ZGlvc2FtcGxlcmF0ZQBA53AAAAAAAAAOdmlkZW9mcmFtZXJhdGUAQDf/gi5SciUABmFhY2FvdABAAAAAAAAAAAAIYXZjbGV2ZWwAQD8AAAAAAAAACmF2Y3Byb2ZpbGUAQFNAAAAAAAAADGF1ZGlvY29kZWNpZAIABG1wNGEADHZpZGVvY29kZWNpZAIABGF2YzEABXdpZHRoAECQ4AAAAAAAAAZoZWlnaHQAQIMAAAAAAAAACmZyYW1lV2lkdGgAQJDgAAAAAAAAC2ZyYW1lSGVpZ2h0AECDAAAAAAAAAAxkaXNwbGF5V2lkdGgAQJDgAAAAAAAADWRpc3BsYXlIZWlnaHQAQIMAAAAAAAAADG1vb3Zwb3NpdGlvbgBBmxq2uAAAAAAIZHVyYXRpb24AQIKjqW3oyhIAAAk=</metadata>\n" + "</bootstrapInfo>\n"
"</media>\n" "<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"" + MovieId
"</manifest>\n"; + "/\">\n"
"<metadata>AgAKb25NZXRhRGF0YQgAAAAAAAl0cmFja2luZm8KAAAAAgMACXRpbWVzY2FsZQBA+GoAAAAAAAAGbGVuZ3RoAEGMcHoQAAAAAAhsYW5ndWFnZQIAA2VuZwARc2FtcGxlZGVzY3JpcHRpb24KAAAAAQMACnNhbXBsZXR5cGUCAARhdmMxAAAJAAAJAwAJdGltZXNjYWxlAEDncAAAAAAAAAZsZW5ndGgAQXtNvTAAAAAACGxhbmd1YWdlAgADZW5nABFzYW1wbGVkZXNjcmlwdGlvbgoAAAABAwAKc2FtcGxldHlwZQIABG1wNGEAAAkAAAkADWF1ZGlvY2hhbm5lbHMAQAAAAAAAAAAAD2F1ZGlvc2FtcGxlcmF0ZQBA53AAAAAAAAAOdmlkZW9mcmFtZXJhdGUAQDf/gi5SciUABmFhY2FvdABAAAAAAAAAAAAIYXZjbGV2ZWwAQD8AAAAAAAAACmF2Y3Byb2ZpbGUAQFNAAAAAAAAADGF1ZGlvY29kZWNpZAIABG1wNGEADHZpZGVvY29kZWNpZAIABGF2YzEABXdpZHRoAECQ4AAAAAAAAAZoZWlnaHQAQIMAAAAAAAAACmZyYW1lV2lkdGgAQJDgAAAAAAAAC2ZyYW1lSGVpZ2h0AECDAAAAAAAAAAxkaXNwbGF5V2lkdGgAQJDgAAAAAAAADWRpc3BsYXlIZWlnaHQAQIMAAAAAAAAADG1vb3Zwb3NpdGlvbgBBmxq2uAAAAAAIZHVyYXRpb24AQIKjqW3oyhIAAAk=</metadata>\n"
"</media>\n"
"</manifest>\n";
}else{ }else{
Result="<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" Result = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n" "<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n"
"<id>" + MovieId + "</id>\n" "<id>" + MovieId + "</id>\n"
"<mimeType>video/mp4</mimeType>\n" "<mimeType>video/mp4</mimeType>\n"
"<streamType>live</streamType>\n" "<streamType>live</streamType>\n"
"<deliveryType>streaming</deliveryType>\n" "<deliveryType>streaming</deliveryType>\n"
"<bootstrapInfo profile=\"named\" id=\"bootstrap1\">" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0, 0)) + "</bootstrapInfo>\n" "<bootstrapInfo profile=\"named\" id=\"bootstrap1\">" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0, 0)) + "</bootstrapInfo>\n"
"<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"" + MovieId + "/\"></media>\n" "<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"" + MovieId + "/\"></media>\n"
"</manifest>\n"; "</manifest>\n";
} }
#if DEBUG >= 8 #if DEBUG >= 8
std::cerr << "Sending this manifest:" << std::endl << Result << std::endl; std::cerr << "Sending this manifest:" << std::endl << Result << std::endl;
#endif #endif
return Result; return Result;
}//BuildManifest } //BuildManifest
/// Main function for Connector_HTTP_Dynamic /// Main function for Connector_HTTP_Dynamic
int Connector_HTTP_Dynamic(Socket::Connection conn){ int Connector_HTTP_Dynamic(Socket::Connection conn){
std::deque<std::string> FlashBuf; std::deque<std::string> FlashBuf;
int FlashBufSize = 0; int FlashBufSize = 0;
long long int FlashBufTime = 0; long long int FlashBufTime = 0;
FLV::Tag tmp;//temporary tag FLV::Tag tmp; //temporary tag
DTSC::Stream Strm;//Incoming stream buffer. DTSC::Stream Strm; //Incoming stream buffer.
HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender. HTTP::Parser HTTP_R, HTTP_S; //HTTP Receiver en HTTP Sender.
bool ready4data = false;//Set to true when streaming is to begin. bool ready4data = false; //Set to true when streaming is to begin.
bool pending_manifest = false; bool pending_manifest = false;
bool receive_marks = false;//when set to true, this stream will ignore keyframes and instead use pause marks bool receive_marks = false; //when set to true, this stream will ignore keyframes and instead use pause marks
bool inited = false; bool inited = false;
Socket::Connection ss(-1); Socket::Connection ss( -1);
std::string streamname; std::string streamname;
std::string recBuffer = ""; std::string recBuffer = "";
@ -176,12 +177,12 @@ namespace Connector_HTTP{
int temp; int temp;
int Flash_RequestPending = 0; int Flash_RequestPending = 0;
unsigned int lastStats = 0; unsigned int lastStats = 0;
conn.setBlocking(false);//do not block on conn.spool() when no data is available conn.setBlocking(false); //do not block on conn.spool() when no data is available
while (conn.connected()){ while (conn.connected()){
if (conn.spool() || conn.Received().size()){ if (conn.spool() || conn.Received().size()){
//make sure it ends in a \n //make sure it ends in a \n
if (*(conn.Received().get().rbegin()) != '\n'){ if ( *(conn.Received().get().rbegin()) != '\n'){
std::string tmp = conn.Received().get(); std::string tmp = conn.Received().get();
conn.Received().get().clear(); conn.Received().get().clear();
if (conn.Received().size()){ if (conn.Received().size()){
@ -191,18 +192,18 @@ namespace Connector_HTTP{
} }
} }
if (HTTP_R.Read(conn.Received().get())){ if (HTTP_R.Read(conn.Received().get())){
#if DEBUG >= 4 #if DEBUG >= 4
std::cout << "Received request: " << HTTP_R.getUrl() << std::endl; std::cout << "Received request: " << HTTP_R.getUrl() << std::endl;
#endif #endif
conn.setHost(HTTP_R.GetHeader("X-Origin")); conn.setHost(HTTP_R.GetHeader("X-Origin"));
if (HTTP_R.url.find("f4m") == std::string::npos){ if (HTTP_R.url.find("f4m") == std::string::npos){
streamname = HTTP_R.url.substr(1,HTTP_R.url.find("/",1)-1); streamname = HTTP_R.url.substr(1, HTTP_R.url.find("/", 1) - 1);
if (!ss){ if ( !ss){
ss = Util::Stream::getStream(streamname); ss = Util::Stream::getStream(streamname);
if (!ss.connected()){ if ( !ss.connected()){
#if DEBUG >= 1 #if DEBUG >= 1
fprintf(stderr, "Could not connect to server!\n"); fprintf(stderr, "Could not connect to server!\n");
#endif #endif
ss.close(); ss.close();
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetBody("No such stream is available on the system. Please try again.\n"); HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
@ -213,32 +214,34 @@ namespace Connector_HTTP{
ss.setBlocking(false); ss.setBlocking(false);
inited = true; inited = true;
} }
Quality = HTTP_R.url.substr( HTTP_R.url.find("/",1)+1 ); Quality = HTTP_R.url.substr(HTTP_R.url.find("/", 1) + 1);
Quality = Quality.substr(0, Quality.find("Seg")); Quality = Quality.substr(0, Quality.find("Seg"));
temp = HTTP_R.url.find("Seg") + 3; temp = HTTP_R.url.find("Seg") + 3;
Segment = atoi( HTTP_R.url.substr(temp,HTTP_R.url.find("-",temp)-temp).c_str()); Segment = atoi(HTTP_R.url.substr(temp, HTTP_R.url.find("-", temp) - temp).c_str());
temp = HTTP_R.url.find("Frag") + 4; temp = HTTP_R.url.find("Frag") + 4;
ReqFragment = atoi( HTTP_R.url.substr(temp).c_str() ); ReqFragment = atoi(HTTP_R.url.substr(temp).c_str());
#if DEBUG >= 4 #if DEBUG >= 4
printf( "Quality: %s, Seg %d Frag %d\n", Quality.c_str(), Segment, ReqFragment); printf("Quality: %s, Seg %d Frag %d\n", Quality.c_str(), Segment, ReqFragment);
#endif #endif
std::stringstream sstream; std::stringstream sstream;
sstream << "f " << ReqFragment << "\no \n"; sstream << "f " << ReqFragment << "\no \n";
ss.SendNow(sstream.str().c_str()); ss.SendNow(sstream.str().c_str());
Flash_RequestPending++; Flash_RequestPending++;
}else{ }else{
streamname = HTTP_R.url.substr(1,HTTP_R.url.find("/",1)-1); streamname = HTTP_R.url.substr(1, HTTP_R.url.find("/", 1) - 1);
if (!Strm.metadata.isNull()){ if ( !Strm.metadata.isNull()){
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type","text/xml"); HTTP_S.SetHeader("Content-Type", "text/xml");
HTTP_S.SetHeader("Cache-Control","no-cache"); HTTP_S.SetHeader("Cache-Control", "no-cache");
if (Strm.metadata.isMember("length")){receive_marks = true;} if (Strm.metadata.isMember("length")){
receive_marks = true;
}
std::string manifest = BuildManifest(streamname, Strm.metadata); std::string manifest = BuildManifest(streamname, Strm.metadata);
HTTP_S.SetBody(manifest); HTTP_S.SetBody(manifest);
conn.SendNow(HTTP_S.BuildResponse("200", "OK")); conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
#if DEBUG >= 3 #if DEBUG >= 3
printf("Sent manifest\n"); printf("Sent manifest\n");
#endif #endif
pending_manifest = false; pending_manifest = false;
}else{ }else{
pending_manifest = true; pending_manifest = true;
@ -249,19 +252,19 @@ namespace Connector_HTTP{
} }
}else{ }else{
if (Flash_RequestPending){ if (Flash_RequestPending){
usleep(1000);//sleep 1ms usleep(1000); //sleep 1ms
}else{ }else{
usleep(10000);//sleep 10ms usleep(10000); //sleep 10ms
} }
} }
if (ready4data){ if (ready4data){
if (!inited){ if ( !inited){
//we are ready, connect the socket! //we are ready, connect the socket!
ss = Util::Stream::getStream(streamname); ss = Util::Stream::getStream(streamname);
if (!ss.connected()){ if ( !ss.connected()){
#if DEBUG >= 1 #if DEBUG >= 1
fprintf(stderr, "Could not connect to server!\n"); fprintf(stderr, "Could not connect to server!\n");
#endif #endif
ss.close(); ss.close();
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetBody("No such stream is available on the system. Please try again.\n"); HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
@ -270,9 +273,9 @@ namespace Connector_HTTP{
continue; continue;
} }
ss.setBlocking(false); ss.setBlocking(false);
#if DEBUG >= 3 #if DEBUG >= 3
fprintf(stderr, "Everything connected, starting to send video data...\n"); fprintf(stderr, "Everything connected, starting to send video data...\n");
#endif #endif
inited = true; inited = true;
} }
unsigned int now = Util::epoch(); unsigned int now = Util::epoch();
@ -283,10 +286,10 @@ namespace Connector_HTTP{
if (ss.spool()){ if (ss.spool()){
while (Strm.parsePacket(ss.Received())){ while (Strm.parsePacket(ss.Received())){
if (Strm.getPacket(0).isMember("time")){ if (Strm.getPacket(0).isMember("time")){
if (!Strm.metadata.isMember("firsttime")){ if ( !Strm.metadata.isMember("firsttime")){
Strm.metadata["firsttime"] = Strm.getPacket(0)["time"]; Strm.metadata["firsttime"] = Strm.getPacket(0)["time"];
}else{ }else{
if (!Strm.metadata.isMember("length") || Strm.metadata["length"].asInt() == 0){ if ( !Strm.metadata.isMember("length") || Strm.metadata["length"].asInt() == 0){
Strm.getPacket(0)["time"] = Strm.getPacket(0)["time"].asInt() - Strm.metadata["firsttime"].asInt(); Strm.getPacket(0)["time"] = Strm.getPacket(0)["time"].asInt() - Strm.metadata["firsttime"].asInt();
} }
} }
@ -294,32 +297,36 @@ namespace Connector_HTTP{
} }
if (pending_manifest){ if (pending_manifest){
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type","text/xml"); HTTP_S.SetHeader("Content-Type", "text/xml");
HTTP_S.SetHeader("Cache-Control","no-cache"); HTTP_S.SetHeader("Cache-Control", "no-cache");
if (Strm.metadata.isMember("length")){receive_marks = true;} if (Strm.metadata.isMember("length")){
receive_marks = true;
}
std::string manifest = BuildManifest(streamname, Strm.metadata); std::string manifest = BuildManifest(streamname, Strm.metadata);
HTTP_S.SetBody(manifest); HTTP_S.SetBody(manifest);
conn.SendNow(HTTP_S.BuildResponse("200", "OK")); conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
#if DEBUG >= 3 #if DEBUG >= 3
printf("Sent manifest\n"); printf("Sent manifest\n");
#endif #endif
pending_manifest = false; pending_manifest = false;
} }
if (!receive_marks && Strm.metadata.isMember("length")){receive_marks = true;} if ( !receive_marks && Strm.metadata.isMember("length")){
receive_marks = true;
}
if ((Strm.getPacket(0).isMember("keyframe") && !receive_marks) || Strm.lastType() == DTSC::PAUSEMARK){ if ((Strm.getPacket(0).isMember("keyframe") && !receive_marks) || Strm.lastType() == DTSC::PAUSEMARK){
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Received a %s fragment of %i bytes.\n", Strm.getPacket(0)["datatype"].asString().c_str(), FlashBufSize); fprintf(stderr, "Received a %s fragment of %i bytes.\n", Strm.getPacket(0)["datatype"].asString().c_str(), FlashBufSize);
#endif #endif
if (Flash_RequestPending > 0 && FlashBufSize){ if (Flash_RequestPending > 0 && FlashBufSize){
#if DEBUG >= 3 #if DEBUG >= 3
fprintf(stderr, "Sending a fragment..."); fprintf(stderr, "Sending a fragment...");
#endif #endif
//static std::string btstrp; //static std::string btstrp;
//btstrp = GenerateBootstrap(streamname, Strm.metadata, ReqFragment, FlashBufTime, Strm.getPacket(0)["time"]); //btstrp = GenerateBootstrap(streamname, Strm.metadata, ReqFragment, FlashBufTime, Strm.getPacket(0)["time"]);
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type", "video/mp4"); HTTP_S.SetHeader("Content-Type", "video/mp4");
HTTP_S.SetBody(""); HTTP_S.SetBody("");
HTTP_S.SetHeader("Content-Length", FlashBufSize+8);//32+33+btstrp.size()); HTTP_S.SetHeader("Content-Length", FlashBufSize + 8); //32+33+btstrp.size());
conn.SendNow(HTTP_S.BuildResponse("200", "OK")); conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
//conn.SendNow("\x00\x00\x00\x21" "afra\x00\x00\x00\x00\x00\x00\x00\x03\xE8\x00\x00\x00\x01", 21); //conn.SendNow("\x00\x00\x00\x21" "afra\x00\x00\x00\x00\x00\x00\x00\x03\xE8\x00\x00\x00\x01", 21);
//unsigned long tmptime = htonl(FlashBufTime << 32); //unsigned long tmptime = htonl(FlashBufTime << 32);
@ -335,16 +342,16 @@ namespace Connector_HTTP{
//unsigned long fragno = htonl(ReqFragment); //unsigned long fragno = htonl(ReqFragment);
//conn.SendNow((char*)&fragno, 4); //conn.SendNow((char*)&fragno, 4);
unsigned long size = htonl(FlashBufSize+8); unsigned long size = htonl(FlashBufSize+8);
conn.SendNow((char*)&size, 4); conn.SendNow((char*) &size, 4);
conn.SendNow("mdat", 4); conn.SendNow("mdat", 4);
while (FlashBuf.size() > 0){ while (FlashBuf.size() > 0){
conn.SendNow(FlashBuf.front()); conn.SendNow(FlashBuf.front());
FlashBuf.pop_front(); FlashBuf.pop_front();
} }
Flash_RequestPending--; Flash_RequestPending--;
#if DEBUG >= 3 #if DEBUG >= 3
fprintf(stderr, "Done\n"); fprintf(stderr, "Done\n");
#endif #endif
} }
FlashBuf.clear(); FlashBuf.clear();
FlashBufSize = 0; FlashBufSize = 0;
@ -373,26 +380,32 @@ namespace Connector_HTTP{
} }
if (pending_manifest && !Strm.metadata.isNull()){ if (pending_manifest && !Strm.metadata.isNull()){
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type","text/xml"); HTTP_S.SetHeader("Content-Type", "text/xml");
HTTP_S.SetHeader("Cache-Control","no-cache"); HTTP_S.SetHeader("Cache-Control", "no-cache");
if (Strm.metadata.isMember("length")){receive_marks = true;} if (Strm.metadata.isMember("length")){
receive_marks = true;
}
std::string manifest = BuildManifest(streamname, Strm.metadata); std::string manifest = BuildManifest(streamname, Strm.metadata);
HTTP_S.SetBody(manifest); HTTP_S.SetBody(manifest);
conn.SendNow(HTTP_S.BuildResponse("200", "OK")); conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
#if DEBUG >= 3 #if DEBUG >= 3
printf("Sent manifest\n"); printf("Sent manifest\n");
#endif #endif
pending_manifest = false; pending_manifest = false;
} }
} }
if (!ss.connected()){break;} if ( !ss.connected()){
break;
}
} }
} }
conn.close(); conn.close();
ss.SendNow(conn.getStats("HTTP_Dynamic").c_str()); ss.SendNow(conn.getStats("HTTP_Dynamic").c_str());
ss.close(); ss.close();
#if DEBUG >= 1 #if DEBUG >= 1
if (FLV::Parse_Error){fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());} if (FLV::Parse_Error){
fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());
}
fprintf(stderr, "User %i disconnected.\n", conn.getSocket()); fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
if (inited){ if (inited){
fprintf(stderr, "Status was: inited\n"); fprintf(stderr, "Status was: inited\n");
@ -403,33 +416,35 @@ namespace Connector_HTTP{
fprintf(stderr, "Status was: connected\n"); fprintf(stderr, "Status was: connected\n");
} }
} }
#endif #endif
return 0; return 0;
}//Connector_HTTP_Dynamic main function } //Connector_HTTP_Dynamic main function
};//Connector_HTTP_Dynamic namespace } //Connector_HTTP_Dynamic namespace
int main(int argc, char ** argv){ int main(int argc, char ** argv){
Util::Config conf(argv[0], PACKAGE_VERSION); Util::Config conf(argv[0], PACKAGE_VERSION);
conf.addConnectorOptions(1935); conf.addConnectorOptions(1935);
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
Socket::Server server_socket = Socket::Server("/tmp/mist/http_dynamic"); Socket::Server server_socket = Socket::Server("/tmp/mist/http_dynamic");
if (!server_socket.connected()){return 1;} if ( !server_socket.connected()){
return 1;
}
conf.activate(); conf.activate();
while (server_socket.connected() && conf.is_active){ while (server_socket.connected() && conf.is_active){
Socket::Connection S = server_socket.accept(); Socket::Connection S = server_socket.accept();
if (S.connected()){//check if the new connection is valid if (S.connected()){ //check if the new connection is valid
pid_t myid = fork(); pid_t myid = fork();
if (myid == 0){//if new child, start MAINHANDLER if (myid == 0){ //if new child, start MAINHANDLER
return Connector_HTTP::Connector_HTTP_Dynamic(S); return Connector_HTTP::Connector_HTTP_Dynamic(S);
}else{//otherwise, do nothing or output debugging text }else{ //otherwise, do nothing or output debugging text
#if DEBUG >= 3 #if DEBUG >= 3
fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket()); fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket());
#endif #endif
} }
} }
}//while connected } //while connected
server_socket.close(); server_socket.close();
return 0; return 0;
}//main } //main

View file

@ -21,29 +21,29 @@
#include <mist/timing.h> #include <mist/timing.h>
/// Holds everything unique to HTTP Progressive Connector. /// Holds everything unique to HTTP Progressive Connector.
namespace Connector_HTTP{ namespace Connector_HTTP {
/// Main function for Connector_HTTP_Progressive /// Main function for Connector_HTTP_Progressive
int Connector_HTTP_Progressive(Socket::Connection conn){ int Connector_HTTP_Progressive(Socket::Connection conn){
bool progressive_has_sent_header = false; bool progressive_has_sent_header = false;
bool ready4data = false;///< Set to true when streaming is to begin. bool ready4data = false; ///< Set to true when streaming is to begin.
DTSC::Stream Strm;///< Incoming stream buffer. DTSC::Stream Strm; ///< Incoming stream buffer.
HTTP::Parser HTTP_R, HTTP_S;///<HTTP Receiver en HTTP Sender. HTTP::Parser HTTP_R, HTTP_S; ///<HTTP Receiver en HTTP Sender.
bool inited = false; bool inited = false;
Socket::Connection ss(-1); Socket::Connection ss( -1);
std::string streamname; std::string streamname;
FLV::Tag tag;///< Temporary tag buffer. FLV::Tag tag; ///< Temporary tag buffer.
unsigned int lastStats = 0; unsigned int lastStats = 0;
unsigned int seek_sec = 0;//seek position in ms unsigned int seek_sec = 0; //seek position in ms
unsigned int seek_byte = 0;//seek position in bytes unsigned int seek_byte = 0; //seek position in bytes
while (conn.connected()){ while (conn.connected()){
//only parse input if available or not yet init'ed //only parse input if available or not yet init'ed
if (!inited){ if ( !inited){
if (conn.Received().size() || conn.spool()){ if (conn.Received().size() || conn.spool()){
//make sure it ends in a \n //make sure it ends in a \n
if (*(conn.Received().get().rbegin()) != '\n'){ if ( *(conn.Received().get().rbegin()) != '\n'){
std::string tmp = conn.Received().get(); std::string tmp = conn.Received().get();
conn.Received().get().clear(); conn.Received().get().clear();
if (conn.Received().size()){ if (conn.Received().size()){
@ -53,35 +53,37 @@ namespace Connector_HTTP{
} }
} }
if (HTTP_R.Read(conn.Received().get())){ if (HTTP_R.Read(conn.Received().get())){
#if DEBUG >= 4 #if DEBUG >= 4
std::cout << "Received request: " << HTTP_R.getUrl() << std::endl; std::cout << "Received request: " << HTTP_R.getUrl() << std::endl;
#endif #endif
conn.setHost(HTTP_R.GetHeader("X-Origin")); conn.setHost(HTTP_R.GetHeader("X-Origin"));
//we assume the URL is the stream name with a 3 letter extension //we assume the URL is the stream name with a 3 letter extension
streamname = HTTP_R.getUrl().substr(1); streamname = HTTP_R.getUrl().substr(1);
size_t extDot = streamname.rfind('.'); size_t extDot = streamname.rfind('.');
if (extDot != std::string::npos){streamname.resize(extDot);};//strip the extension if (extDot != std::string::npos){
streamname.resize(extDot);
}; //strip the extension
int start = 0; int start = 0;
if (!HTTP_R.GetVar("start").empty()){ if ( !HTTP_R.GetVar("start").empty()){
start = atoi(HTTP_R.GetVar("start").c_str()); start = atoi(HTTP_R.GetVar("start").c_str());
} }
if (!HTTP_R.GetVar("starttime").empty()){ if ( !HTTP_R.GetVar("starttime").empty()){
start = atoi(HTTP_R.GetVar("starttime").c_str()); start = atoi(HTTP_R.GetVar("starttime").c_str());
} }
if (!HTTP_R.GetVar("apstart").empty()){ if ( !HTTP_R.GetVar("apstart").empty()){
start = atoi(HTTP_R.GetVar("apstart").c_str()); start = atoi(HTTP_R.GetVar("apstart").c_str());
} }
if (!HTTP_R.GetVar("ec_seek").empty()){ if ( !HTTP_R.GetVar("ec_seek").empty()){
start = atoi(HTTP_R.GetVar("ec_seek").c_str()); start = atoi(HTTP_R.GetVar("ec_seek").c_str());
} }
if (!HTTP_R.GetVar("fs").empty()){ if ( !HTTP_R.GetVar("fs").empty()){
start = atoi(HTTP_R.GetVar("fs").c_str()); start = atoi(HTTP_R.GetVar("fs").c_str());
} }
//under 3 hours we assume seconds, otherwise byte position //under 3 hours we assume seconds, otherwise byte position
if (start < 10800){ if (start < 10800){
seek_sec = start*1000;//ms, not s seek_sec = start * 1000; //ms, not s
}else{ }else{
seek_byte = start;//divide by 1mbit, then *1000 for ms. seek_byte = start; //divide by 1mbit, then *1000 for ms.
} }
ready4data = true; ready4data = true;
HTTP_R.Clean(); //clean for any possible next requests HTTP_R.Clean(); //clean for any possible next requests
@ -89,13 +91,13 @@ namespace Connector_HTTP{
} }
} }
if (ready4data){ if (ready4data){
if (!inited){ if ( !inited){
//we are ready, connect the socket! //we are ready, connect the socket!
ss = Util::Stream::getStream(streamname); ss = Util::Stream::getStream(streamname);
if (!ss.connected()){ if ( !ss.connected()){
#if DEBUG >= 1 #if DEBUG >= 1
fprintf(stderr, "Could not connect to server for %s!\n", streamname.c_str()); fprintf(stderr, "Could not connect to server for %s!\n", streamname.c_str());
#endif #endif
ss.close(); ss.close();
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetBody("No such stream is available on the system. Please try again.\n"); HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
@ -105,9 +107,9 @@ namespace Connector_HTTP{
} }
if (seek_byte){ if (seek_byte){
//wait until we have a header //wait until we have a header
while (!Strm.metadata){ while ( !Strm.metadata){
if (ss.spool()){ if (ss.spool()){
Strm.parsePacket(ss.Received());//read the metadata Strm.parsePacket(ss.Received()); //read the metadata
}else{ }else{
Util::sleep(5); Util::sleep(5);
} }
@ -126,9 +128,9 @@ namespace Connector_HTTP{
cmd << "s " << seek_sec << "\n"; cmd << "s " << seek_sec << "\n";
ss.SendNow(cmd.str().c_str()); ss.SendNow(cmd.str().c_str());
} }
#if DEBUG >= 3 #if DEBUG >= 3
fprintf(stderr, "Everything connected, starting to send video data...\n"); fprintf(stderr, "Everything connected, starting to send video data...\n");
#endif #endif
ss.SendNow("p\n"); ss.SendNow("p\n");
inited = true; inited = true;
} }
@ -139,13 +141,13 @@ namespace Connector_HTTP{
} }
if (ss.spool()){ if (ss.spool()){
while (Strm.parsePacket(ss.Received())){ while (Strm.parsePacket(ss.Received())){
if (!progressive_has_sent_header){ if ( !progressive_has_sent_header){
HTTP_S.Clean();//make sure no parts of old requests are left in any buffers HTTP_S.Clean(); //make sure no parts of old requests are left in any buffers
HTTP_S.SetHeader("Content-Type", "video/x-flv");//Send the correct content-type for FLV files HTTP_S.SetHeader("Content-Type", "video/x-flv"); //Send the correct content-type for FLV files
//HTTP_S.SetHeader("Transfer-Encoding", "chunked"); //HTTP_S.SetHeader("Transfer-Encoding", "chunked");
HTTP_S.protocol = "HTTP/1.0"; HTTP_S.protocol = "HTTP/1.0";
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));//no SetBody = unknown length - this is intentional, we will stream the entire file conn.SendNow(HTTP_S.BuildResponse("200", "OK")); //no SetBody = unknown length - this is intentional, we will stream the entire file
conn.SendNow(FLV::Header, 13);//write FLV header conn.SendNow(FLV::Header, 13); //write FLV header
//write metadata //write metadata
tag.DTSCMetaInit(Strm); tag.DTSCMetaInit(Strm);
conn.SendNow(tag.data, tag.len); conn.SendNow(tag.data, tag.len);
@ -160,24 +162,28 @@ namespace Connector_HTTP{
conn.SendNow(tag.data, tag.len); conn.SendNow(tag.data, tag.len);
} }
progressive_has_sent_header = true; progressive_has_sent_header = true;
#if DEBUG >= 1 #if DEBUG >= 1
fprintf(stderr, "Sent progressive FLV header\n"); fprintf(stderr, "Sent progressive FLV header\n");
#endif #endif
} }
tag.DTSCLoader(Strm); tag.DTSCLoader(Strm);
conn.SendNow(tag.data, tag.len);//write the tag contents conn.SendNow(tag.data, tag.len); //write the tag contents
} }
}else{ }else{
Util::sleep(1); Util::sleep(1);
} }
if (!ss.connected()){break;} if ( !ss.connected()){
break;
}
} }
} }
conn.close(); conn.close();
ss.SendNow(conn.getStats("HTTP_Dynamic").c_str()); ss.SendNow(conn.getStats("HTTP_Dynamic").c_str());
ss.close(); ss.close();
#if DEBUG >= 1 #if DEBUG >= 1
if (FLV::Parse_Error){fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());} if (FLV::Parse_Error){
fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());
}
fprintf(stderr, "User %i disconnected.\n", conn.getSocket()); fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
if (inited){ if (inited){
fprintf(stderr, "Status was: inited\n"); fprintf(stderr, "Status was: inited\n");
@ -188,33 +194,35 @@ namespace Connector_HTTP{
fprintf(stderr, "Status was: connected\n"); fprintf(stderr, "Status was: connected\n");
} }
} }
#endif #endif
return 0; return 0;
}//Connector_HTTP main function } //Connector_HTTP main function
};//Connector_HTTP namespace } //Connector_HTTP namespace
int main(int argc, char ** argv){ int main(int argc, char ** argv){
Util::Config conf(argv[0], PACKAGE_VERSION); Util::Config conf(argv[0], PACKAGE_VERSION);
conf.addConnectorOptions(1935); conf.addConnectorOptions(1935);
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
Socket::Server server_socket = Socket::Server("/tmp/mist/http_progressive"); Socket::Server server_socket = Socket::Server("/tmp/mist/http_progressive");
if (!server_socket.connected()){return 1;} if ( !server_socket.connected()){
return 1;
}
conf.activate(); conf.activate();
while (server_socket.connected() && conf.is_active){ while (server_socket.connected() && conf.is_active){
Socket::Connection S = server_socket.accept(); Socket::Connection S = server_socket.accept();
if (S.connected()){//check if the new connection is valid if (S.connected()){ //check if the new connection is valid
pid_t myid = fork(); pid_t myid = fork();
if (myid == 0){//if new child, start MAINHANDLER if (myid == 0){ //if new child, start MAINHANDLER
return Connector_HTTP::Connector_HTTP_Progressive(S); return Connector_HTTP::Connector_HTTP_Progressive(S);
}else{//otherwise, do nothing or output debugging text }else{ //otherwise, do nothing or output debugging text
#if DEBUG >= 3 #if DEBUG >= 3
fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket()); fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket());
#endif #endif
} }
} }
}//while connected } //while connected
server_socket.close(); server_socket.close();
return 0; return 0;
}//main } //main

View file

@ -15,7 +15,6 @@
#include <mist/http_parser.h> #include <mist/http_parser.h>
#include <mist/json.h> #include <mist/json.h>
#include <mist/dtsc.h> #include <mist/dtsc.h>
#include <mist/flv_tag.h>
#include <mist/base64.h> #include <mist/base64.h>
#include <mist/amf.h> #include <mist/amf.h>
#include <mist/mp4.h> #include <mist/mp4.h>
@ -25,56 +24,67 @@
#include <mist/timing.h> #include <mist/timing.h>
/// Holds everything unique to HTTP Dynamic Connector. /// Holds everything unique to HTTP Dynamic Connector.
namespace Connector_HTTP{ namespace Connector_HTTP {
/// Returns a Smooth-format manifest file /// Returns a Smooth-format manifest file
std::string BuildManifest(std::string & MovieId, JSON::Value & metadata){ std::string BuildManifest(std::string & MovieId, JSON::Value & metadata){
std::stringstream Result; std::stringstream Result;
Result << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; Result << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
Result << "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" TimeScale=\"10000000\" Duration=\"" << metadata["lastms"].asInt() << "\">\n"; Result << "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" TimeScale=\"10000000\" Duration=\"" << metadata["lastms"].asInt()
if( metadata.isMember( "audio" ) ) { << "\">\n";
Result << " <StreamIndex Type=\"audio\" QualityLevels=\"1\" Name=\"audio\" Chunks=\"" << metadata["keytime"].size() << "\" Url=\"Q({bitrate})/A({start time})\">\n"; if (metadata.isMember("audio")){
Result << " <QualityLevel Index=\"0\" Bitrate=\"" << metadata["audio"]["bps"].asInt()*8 << "\" CodecPrivateData=\""; Result << " <StreamIndex Type=\"audio\" QualityLevels=\"1\" Name=\"audio\" Chunks=\"" << metadata["keytime"].size()
<< "\" Url=\"Q({bitrate})/A({start time})\">\n";
Result << " <QualityLevel Index=\"0\" Bitrate=\"" << metadata["audio"]["bps"].asInt() * 8 << "\" CodecPrivateData=\"";
Result << std::hex; Result << std::hex;
for( int i = 0; i < metadata["audio"]["init"].asString().size(); i++ ) { for (int i = 0; i < metadata["audio"]["init"].asString().size(); i++){
Result << std::setfill('0') << std::setw(2) << std::right << (int)metadata["audio"]["init"].asString()[i]; Result << std::setfill('0') << std::setw(2) << std::right << (int)metadata["audio"]["init"].asString()[i];
} }
Result << std::dec; Result << std::dec;
Result << "\" SamplingRate=\"" << metadata["audio"]["rate"].asInt() << "\" Channels=\"2\" BitsPerSample=\"16\" PacketSize=\"4\" AudioTag=\"255\" FourCC=\"AACL\" />\n"; Result << "\" SamplingRate=\"" << metadata["audio"]["rate"].asInt()
for( int i = 0; i < metadata["keytime"].size()-1; i++ ) { << "\" Channels=\"2\" BitsPerSample=\"16\" PacketSize=\"4\" AudioTag=\"255\" FourCC=\"AACL\" />\n";
for (int i = 0; i < metadata["keytime"].size() - 1; i++){
Result << " <c "; Result << " <c ";
if( i == 0 ) { Result << "t=\"0\" "; } if (i == 0){
Result << "d=\"" << 10000 * ( metadata["keytime"][i+1].asInt() - metadata["keytime"][i].asInt() ) << "\" />\n"; Result << "t=\"0\" ";
}
Result << "d=\"" << 10000 * (metadata["keytime"][i + 1].asInt() - metadata["keytime"][i].asInt()) << "\" />\n";
} }
Result << " <c d=\"" << 10000 * ( metadata["lastms"].asInt() - metadata["keytime"][metadata["keytime"].size()-1].asInt() ) << "\" />\n"; Result << " <c d=\"" << 10000 * (metadata["lastms"].asInt() - metadata["keytime"][metadata["keytime"].size() - 1].asInt()) << "\" />\n";
Result << " </StreamIndex>\n"; Result << " </StreamIndex>\n";
} }
if( metadata.isMember( "video" ) ) { if (metadata.isMember("video")){
Result << " <StreamIndex Type=\"video\" QualityLevels=\"1\" Name=\"video\" Chunks=\"" << metadata["keytime"].size() << "\" Url=\"Q({bitrate})/V({start time})\" MaxWidth=\"" << metadata["video"]["width"].asInt() << "\" MaxHeight=\"" << metadata["video"]["height"].asInt() << "\" DisplayWidth=\"" << metadata["video"]["width"].asInt() << "\" DisplayHeight=\"" << metadata["video"]["height"].asInt() << "\">\n"; Result << " <StreamIndex Type=\"video\" QualityLevels=\"1\" Name=\"video\" Chunks=\"" << metadata["keytime"].size()
Result << " <QualityLevel Index=\"0\" Bitrate=\"" << metadata["video"]["bps"].asInt()*8 << "\" CodecPrivateData=\""; << "\" Url=\"Q({bitrate})/V({start time})\" MaxWidth=\"" << metadata["video"]["width"].asInt() << "\" MaxHeight=\""
<< metadata["video"]["height"].asInt() << "\" DisplayWidth=\"" << metadata["video"]["width"].asInt() << "\" DisplayHeight=\""
<< metadata["video"]["height"].asInt() << "\">\n";
Result << " <QualityLevel Index=\"0\" Bitrate=\"" << metadata["video"]["bps"].asInt() * 8 << "\" CodecPrivateData=\"";
MP4::AVCC avccbox; MP4::AVCC avccbox;
avccbox.setPayload( metadata["video"]["init"].asString() ); avccbox.setPayload(metadata["video"]["init"].asString());
std::string tmpString = avccbox.asAnnexB( ); std::string tmpString = avccbox.asAnnexB();
Result << std::hex; Result << std::hex;
for( int i = 0; i < tmpString.size(); i++ ) { for (int i = 0; i < tmpString.size(); i++){
Result << std::setfill('0') << std::setw(2) << std::right << (int)tmpString[i]; Result << std::setfill('0') << std::setw(2) << std::right << (int)tmpString[i];
} }
Result << std::dec; Result << std::dec;
Result << "\" MaxWidth=\"" << metadata["video"]["width"].asInt() << "\" MaxHeight=\"" << metadata["video"]["height"].asInt() << "\" FourCC=\"AVC1\" />\n"; Result << "\" MaxWidth=\"" << metadata["video"]["width"].asInt() << "\" MaxHeight=\"" << metadata["video"]["height"].asInt()
for( int i = 0; i < metadata["keytime"].size()-1; i++ ) { << "\" FourCC=\"AVC1\" />\n";
for (int i = 0; i < metadata["keytime"].size() - 1; i++){
Result << " <c "; Result << " <c ";
if( i == 0 ) { Result << "t=\"0\" "; } if (i == 0){
Result << "d=\"" << 10000 * ( metadata["keytime"][i+1].asInt() - metadata["keytime"][i].asInt() ) << "\" />\n"; Result << "t=\"0\" ";
}
Result << "d=\"" << 10000 * (metadata["keytime"][i + 1].asInt() - metadata["keytime"][i].asInt()) << "\" />\n";
} }
Result << " <c d=\"" << 10000 * ( metadata["lastms"].asInt() - metadata["keytime"][metadata["keytime"].size()-1].asInt() ) << "\" />\n"; Result << " <c d=\"" << 10000 * (metadata["lastms"].asInt() - metadata["keytime"][metadata["keytime"].size() - 1].asInt()) << "\" />\n";
Result << " </StreamIndex>\n"; Result << " </StreamIndex>\n";
} }
Result << "</SmoothStreamingMedia>\n"; Result << "</SmoothStreamingMedia>\n";
#if DEBUG >= 8 #if DEBUG >= 8
std::cerr << "Sending this manifest:" << std::endl << Result << std::endl; std::cerr << "Sending this manifest:" << std::endl << Result << std::endl;
#endif #endif
return Result.str(); return Result.str();
}//BuildManifest } //BuildManifest
/// Main function for Connector_HTTP_Dynamic /// Main function for Connector_HTTP_Dynamic
int Connector_HTTP_Dynamic(Socket::Connection conn){ int Connector_HTTP_Dynamic(Socket::Connection conn){
@ -82,22 +92,21 @@ namespace Connector_HTTP{
std::vector<int> Timestamps; std::vector<int> Timestamps;
int FlashBufSize = 0; int FlashBufSize = 0;
long long int FlashBufTime = 0; long long int FlashBufTime = 0;
FLV::Tag tmp;//temporary tag
DTSC::Stream Strm;//Incoming stream buffer. DTSC::Stream Strm; //Incoming stream buffer.
HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender. HTTP::Parser HTTP_R, HTTP_S; //HTTP Receiver en HTTP Sender.
bool ready4data = false;//Set to true when streaming is to begin. bool ready4data = false; //Set to true when streaming is to begin.
bool pending_manifest = false; bool pending_manifest = false;
bool receive_marks = false;//when set to true, this stream will ignore keyframes and instead use pause marks bool receive_marks = false; //when set to true, this stream will ignore keyframes and instead use pause marks
bool inited = false; bool inited = false;
Socket::Connection ss(-1); Socket::Connection ss( -1);
std::string streamname; std::string streamname;
std::string recBuffer = ""; std::string recBuffer = "";
bool wantsVideo = false; bool wantsVideo = false;
bool wantsAudio = false; bool wantsAudio = false;
std::string Quality; std::string Quality;
int Segment = -1; int Segment = -1;
long long int ReqFragment = -1; long long int ReqFragment = -1;
@ -105,12 +114,12 @@ namespace Connector_HTTP{
std::string tempStr; std::string tempStr;
int Flash_RequestPending = 0; int Flash_RequestPending = 0;
unsigned int lastStats = 0; unsigned int lastStats = 0;
conn.setBlocking(false);//do not block on conn.spool() when no data is available conn.setBlocking(false); //do not block on conn.spool() when no data is available
while (conn.connected()){ while (conn.connected()){
if (conn.spool() || conn.Received().size()){ if (conn.spool() || conn.Received().size()){
//make sure it ends in a \n //make sure it ends in a \n
if (*(conn.Received().get().rbegin()) != '\n'){ if ( *(conn.Received().get().rbegin()) != '\n'){
std::string tmp = conn.Received().get(); std::string tmp = conn.Received().get();
conn.Received().get().clear(); conn.Received().get().clear();
if (conn.Received().size()){ if (conn.Received().size()){
@ -120,18 +129,18 @@ namespace Connector_HTTP{
} }
} }
if (HTTP_R.Read(conn.Received().get())){ if (HTTP_R.Read(conn.Received().get())){
#if DEBUG >= 4 #if DEBUG >= 4
std::cout << "Received request: " << HTTP_R.getUrl() << std::endl; std::cout << "Received request: " << HTTP_R.getUrl() << std::endl;
#endif #endif
conn.setHost(HTTP_R.GetHeader("X-Origin")); conn.setHost(HTTP_R.GetHeader("X-Origin"));
if (HTTP_R.url.find("Manifest") == std::string::npos){ if (HTTP_R.url.find("Manifest") == std::string::npos){
streamname = HTTP_R.url.substr(8,HTTP_R.url.find("/",8)-12); streamname = HTTP_R.url.substr(8, HTTP_R.url.find("/", 8) - 12);
if (!ss){ if ( !ss){
ss = Util::Stream::getStream(streamname); ss = Util::Stream::getStream(streamname);
if (!ss.connected()){ if ( !ss.connected()){
#if DEBUG >= 1 #if DEBUG >= 1
fprintf(stderr, "Could not connect to server!\n"); fprintf(stderr, "Could not connect to server!\n");
#endif #endif
ss.close(); ss.close();
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetBody("No such stream " + streamname + " is available on the system. Please try again.\n"); HTTP_S.SetBody("No such stream " + streamname + " is available on the system. Please try again.\n");
@ -142,35 +151,41 @@ namespace Connector_HTTP{
ss.setBlocking(false); ss.setBlocking(false);
inited = true; inited = true;
} }
Quality = HTTP_R.url.substr( HTTP_R.url.find("/Q(",8)+3 ); Quality = HTTP_R.url.substr(HTTP_R.url.find("/Q(", 8) + 3);
Quality = Quality.substr(0, Quality.find(")")); Quality = Quality.substr(0, Quality.find(")"));
tempStr = HTTP_R.url.substr( HTTP_R.url.find(")/") + 2 ); tempStr = HTTP_R.url.substr(HTTP_R.url.find(")/") + 2);
wantsAudio = false; wantsAudio = false;
wantsVideo = false; wantsVideo = false;
if( tempStr[0] == 'A' ) { wantsAudio = true; } if (tempStr[0] == 'A'){
if( tempStr[0] == 'V' ) { wantsVideo = true; } wantsAudio = true;
tempStr = tempStr.substr( tempStr.find("(") + 1 ); }
ReqFragment = atoll( tempStr.substr(0,tempStr.find(")")).c_str() ); if (tempStr[0] == 'V'){
#if DEBUG >= 4 wantsVideo = true;
printf( "Quality: %s, Frag %d\n", Quality.c_str(), ( ReqFragment / 10000 ) ); }
#endif tempStr = tempStr.substr(tempStr.find("(") + 1);
ReqFragment = atoll(tempStr.substr(0, tempStr.find(")")).c_str());
#if DEBUG >= 4
printf("Quality: %s, Frag %d\n", Quality.c_str(), (ReqFragment / 10000));
#endif
std::stringstream sstream; std::stringstream sstream;
sstream << "s " << ( ReqFragment / 10000 ) << "\no \n"; sstream << "s " << (ReqFragment / 10000) << "\no \n";
ss.SendNow(sstream.str().c_str()); ss.SendNow(sstream.str().c_str());
Flash_RequestPending++; Flash_RequestPending++;
}else{ }else{
streamname = HTTP_R.url.substr(8,HTTP_R.url.find("/",8)-12); streamname = HTTP_R.url.substr(8, HTTP_R.url.find("/", 8) - 12);
if (!Strm.metadata.isNull()){ if ( !Strm.metadata.isNull()){
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type","text/xml"); HTTP_S.SetHeader("Content-Type", "text/xml");
HTTP_S.SetHeader("Cache-Control","no-cache"); HTTP_S.SetHeader("Cache-Control", "no-cache");
if (Strm.metadata.isMember("length")){receive_marks = true;} if (Strm.metadata.isMember("length")){
receive_marks = true;
}
std::string manifest = BuildManifest(streamname, Strm.metadata); std::string manifest = BuildManifest(streamname, Strm.metadata);
HTTP_S.SetBody(manifest); HTTP_S.SetBody(manifest);
conn.SendNow(HTTP_S.BuildResponse("200", "OK")); conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
#if DEBUG >= 3 #if DEBUG >= 3
printf("Sent manifest\n"); printf("Sent manifest\n");
#endif #endif
pending_manifest = false; pending_manifest = false;
}else{ }else{
pending_manifest = true; pending_manifest = true;
@ -181,19 +196,19 @@ namespace Connector_HTTP{
} }
}else{ }else{
if (Flash_RequestPending){ if (Flash_RequestPending){
usleep(1000);//sleep 1ms usleep(1000); //sleep 1ms
}else{ }else{
usleep(10000);//sleep 10ms usleep(10000); //sleep 10ms
} }
} }
if (ready4data){ if (ready4data){
if (!inited){ if ( !inited){
//we are ready, connect the socket! //we are ready, connect the socket!
ss = Util::Stream::getStream(streamname); ss = Util::Stream::getStream(streamname);
if (!ss.connected()){ if ( !ss.connected()){
#if DEBUG >= 1 #if DEBUG >= 1
fprintf(stderr, "Could not connect to server!\n"); fprintf(stderr, "Could not connect to server!\n");
#endif #endif
ss.close(); ss.close();
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetBody("No such stream " + streamname + " is available on the system. Please try again.\n"); HTTP_S.SetBody("No such stream " + streamname + " is available on the system. Please try again.\n");
@ -202,9 +217,9 @@ namespace Connector_HTTP{
continue; continue;
} }
ss.setBlocking(false); ss.setBlocking(false);
#if DEBUG >= 3 #if DEBUG >= 3
fprintf(stderr, "Everything connected, starting to send video data...\n"); fprintf(stderr, "Everything connected, starting to send video data...\n");
#endif #endif
inited = true; inited = true;
} }
unsigned int now = Util::epoch(); unsigned int now = Util::epoch();
@ -215,10 +230,10 @@ namespace Connector_HTTP{
if (ss.spool()){ if (ss.spool()){
while (Strm.parsePacket(ss.Received())){ while (Strm.parsePacket(ss.Received())){
if (Strm.getPacket(0).isMember("time")){ if (Strm.getPacket(0).isMember("time")){
if (!Strm.metadata.isMember("firsttime")){ if ( !Strm.metadata.isMember("firsttime")){
Strm.metadata["firsttime"] = Strm.getPacket(0)["time"]; Strm.metadata["firsttime"] = Strm.getPacket(0)["time"];
}else{ }else{
if (!Strm.metadata.isMember("length") || Strm.metadata["length"].asInt() == 0){ if ( !Strm.metadata.isMember("length") || Strm.metadata["length"].asInt() == 0){
Strm.getPacket(0)["time"] = Strm.getPacket(0)["time"].asInt() - Strm.metadata["firsttime"].asInt(); Strm.getPacket(0)["time"] = Strm.getPacket(0)["time"].asInt() - Strm.metadata["firsttime"].asInt();
} }
} }
@ -226,144 +241,149 @@ namespace Connector_HTTP{
} }
if (pending_manifest){ if (pending_manifest){
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type","text/xml"); HTTP_S.SetHeader("Content-Type", "text/xml");
HTTP_S.SetHeader("Cache-Control","no-cache"); HTTP_S.SetHeader("Cache-Control", "no-cache");
if (Strm.metadata.isMember("length")){receive_marks = true;} if (Strm.metadata.isMember("length")){
receive_marks = true;
}
std::string manifest = BuildManifest(streamname, Strm.metadata); std::string manifest = BuildManifest(streamname, Strm.metadata);
HTTP_S.SetBody(manifest); HTTP_S.SetBody(manifest);
conn.SendNow(HTTP_S.BuildResponse("200", "OK")); conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
#if DEBUG >= 3 #if DEBUG >= 3
printf("Sent manifest\n"); printf("Sent manifest\n");
#endif #endif
pending_manifest = false; pending_manifest = false;
} }
if (!receive_marks && Strm.metadata.isMember("length")){receive_marks = true;} if ( !receive_marks && Strm.metadata.isMember("length")){
if ( Strm.lastType() == DTSC::PAUSEMARK ) { receive_marks = true;
Timestamps.push_back( Strm.getPacket(0)["time"].asInt() ); }
if (Strm.lastType() == DTSC::PAUSEMARK){
Timestamps.push_back(Strm.getPacket(0)["time"].asInt());
} }
if ((Strm.getPacket(0).isMember("keyframe") && !receive_marks) || Strm.lastType() == DTSC::PAUSEMARK){ if ((Strm.getPacket(0).isMember("keyframe") && !receive_marks) || Strm.lastType() == DTSC::PAUSEMARK){
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Received a %s fragment of %i bytes.\n", Strm.getPacket(0)["datatype"].asString().c_str(), FlashBufSize); fprintf(stderr, "Received a %s fragment of %i bytes.\n", Strm.getPacket(0)["datatype"].asString().c_str(), FlashBufSize);
#endif #endif
if (Flash_RequestPending > 0 && FlashBufSize){ if (Flash_RequestPending > 0 && FlashBufSize){
#if DEBUG >= 3 #if DEBUG >= 3
fprintf(stderr, "Sending a fragment..."); fprintf(stderr, "Sending a fragment...");
#endif #endif
//static std::string btstrp; //static std::string btstrp;
//btstrp = GenerateBootstrap(streamname, Strm.metadata, ReqFragment, FlashBufTime, Strm.getPacket(0)["time"]); //btstrp = GenerateBootstrap(streamname, Strm.metadata, ReqFragment, FlashBufTime, Strm.getPacket(0)["time"]);
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type", "video/mp4"); HTTP_S.SetHeader("Content-Type", "video/mp4");
HTTP_S.SetBody(""); HTTP_S.SetBody("");
int myDuration; int myDuration;
MP4::MFHD mfhd_box; MP4::MFHD mfhd_box;
for( int i = 0; i < Strm.metadata["keytime"].size(); i++ ) { for (int i = 0; i < Strm.metadata["keytime"].size(); i++){
if( Strm.metadata["keytime"][i].asInt() >= ( ReqFragment / 10000 ) ) { if (Strm.metadata["keytime"][i].asInt() >= (ReqFragment / 10000)){
std::cerr << "Sequence Number: " << i+1 << std::endl; std::cerr << "Sequence Number: " << i + 1 << std::endl;
mfhd_box.setSequenceNumber( i+1 ); mfhd_box.setSequenceNumber(i + 1);
if( i != Strm.metadata["keytime"].size() ) { if (i != Strm.metadata["keytime"].size()){
myDuration = Strm.metadata["keytime"][i+1].asInt() - Strm.metadata["keytime"][i].asInt(); myDuration = Strm.metadata["keytime"][i + 1].asInt() - Strm.metadata["keytime"][i].asInt();
} else { }else{
myDuration = Strm.metadata["lastms"].asInt() - Strm.metadata["keytime"][i].asInt(); myDuration = Strm.metadata["lastms"].asInt() - Strm.metadata["keytime"][i].asInt();
} }
myDuration = myDuration * 10000; myDuration = myDuration * 10000;
break; break;
} }
} }
MP4::TFHD tfhd_box; MP4::TFHD tfhd_box;
tfhd_box.setFlags( MP4::tfhdSampleFlag ); tfhd_box.setFlags(MP4::tfhdSampleFlag);
tfhd_box.setTrackID( 1 ); tfhd_box.setTrackID(1);
tfhd_box.setDefaultSampleFlags( 0x000000C0 | MP4::noIPicture | MP4::noDisposable | MP4::noKeySample ); tfhd_box.setDefaultSampleFlags(0x000000C0 | MP4::noIPicture | MP4::noDisposable | MP4::noKeySample);
MP4::TRUN trun_box; MP4::TRUN trun_box;
//maybe reinsert dataOffset //maybe reinsert dataOffset
std::cerr << "Setting Flags: " << (MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleDuration | MP4::trunsampleSize) << std::endl; std::cerr << "Setting Flags: " << (MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleDuration | MP4::trunsampleSize)
trun_box.setFlags( MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleDuration | MP4::trunsampleSize ); << std::endl;
trun_box.setDataOffset( 42 ); trun_box.setFlags(MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleDuration | MP4::trunsampleSize);
trun_box.setFirstSampleFlags( 0x00000040 | MP4::isIPicture | MP4::noDisposable | MP4::isKeySample ); trun_box.setDataOffset(42);
for( int i = 0; i < FlashBuf.size(); i++ ) { trun_box.setFirstSampleFlags(0x00000040 | MP4::isIPicture | MP4::noDisposable | MP4::isKeySample);
for (int i = 0; i < FlashBuf.size(); i++){
MP4::trunSampleInformation trunSample; MP4::trunSampleInformation trunSample;
trunSample.sampleSize = FlashBuf[i].size(); trunSample.sampleSize = FlashBuf[i].size();
//trunSample.sampleDuration = (Timestamps[i+1]-Timestamps[i]) * 10000; //trunSample.sampleDuration = (Timestamps[i+1]-Timestamps[i]) * 10000;
trunSample.sampleDuration = (((double)myDuration / FlashBuf.size()) * i) - (((double)myDuration / FlashBuf.size()) * (i-1)); trunSample.sampleDuration = (((double)myDuration / FlashBuf.size()) * i) - (((double)myDuration / FlashBuf.size()) * (i - 1));
trun_box.setSampleInformation( trunSample, i ); trun_box.setSampleInformation(trunSample, i);
} }
MP4::SDTP sdtp_box; MP4::SDTP sdtp_box;
sdtp_box.setVersion( 0 ); sdtp_box.setVersion(0);
sdtp_box.setValue( 0x24, 4 ); sdtp_box.setValue(0x24, 4);
for( int i = 1; i < FlashBuf.size(); i++ ) { for (int i = 1; i < FlashBuf.size(); i++){
sdtp_box.setValue( 0x14, 4+i ); sdtp_box.setValue(0x14, 4 + i);
} }
MP4::TRAF traf_box; MP4::TRAF traf_box;
traf_box.setContent( tfhd_box, 0 ); traf_box.setContent(tfhd_box, 0);
traf_box.setContent( trun_box, 1 ); traf_box.setContent(trun_box, 1);
traf_box.setContent( sdtp_box, 2 ); traf_box.setContent(sdtp_box, 2);
MP4::MOOF moof_box; MP4::MOOF moof_box;
moof_box.setContent( mfhd_box, 0 ); moof_box.setContent(mfhd_box, 0);
moof_box.setContent( traf_box, 1 ); moof_box.setContent(traf_box, 1);
//setting tha offsets! //setting tha offsets!
trun_box.setDataOffset(moof_box.boxedSize() + 8); trun_box.setDataOffset(moof_box.boxedSize() + 8);
traf_box.setContent( trun_box, 1 ); traf_box.setContent(trun_box, 1);
moof_box.setContent( traf_box, 1 ); moof_box.setContent(traf_box, 1);
//std::cerr << "\t[encoded] = " << ((MP4::TRUN&)(((MP4::TRAF&)(moof_box.getContent(1))).getContent(1))).getDataOffset() << std::endl;
//std::cerr << "\t[encoded] = " << ((MP4::TRUN&)(((MP4::TRAF&)(moof_box.getContent(1))).getContent(1))).getDataOffset() << std::endl;
HTTP_S.SetHeader("Content-Length", FlashBufSize + 8 + moof_box.boxedSize()); //32+33+btstrp.size());
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
HTTP_S.SetHeader("Content-Length", FlashBufSize+8+moof_box.boxedSize());//32+33+btstrp.size());
conn.SendNow(HTTP_S.BuildResponse("200", "OK")); conn.SendNow(moof_box.asBox(), moof_box.boxedSize());
conn.SendNow( moof_box.asBox(), moof_box.boxedSize() );
unsigned long size = htonl(FlashBufSize+8); unsigned long size = htonl(FlashBufSize+8);
conn.SendNow((char*)&size, 4); conn.SendNow((char*) &size, 4);
conn.SendNow("mdat", 4); conn.SendNow("mdat", 4);
while (FlashBuf.size() > 0){ while (FlashBuf.size() > 0){
conn.SendNow(FlashBuf.front()); conn.SendNow(FlashBuf.front());
FlashBuf.pop_front(); FlashBuf.pop_front();
} }
Flash_RequestPending--; Flash_RequestPending--;
#if DEBUG >= 3 #if DEBUG >= 3
fprintf(stderr, "Done\n"); fprintf(stderr, "Done\n");
#endif #endif
} }
FlashBuf.clear(); FlashBuf.clear();
FlashBufSize = 0; FlashBufSize = 0;
} }
if ( ( wantsAudio && Strm.lastType() == DTSC::AUDIO ) || ( wantsVideo && Strm.lastType() == DTSC::VIDEO ) ) { if ((wantsAudio && Strm.lastType() == DTSC::AUDIO) || (wantsVideo && Strm.lastType() == DTSC::VIDEO)){
FlashBuf.push_back( Strm.lastData() ); FlashBuf.push_back(Strm.lastData());
FlashBufSize += Strm.lastData().size(); FlashBufSize += Strm.lastData().size();
Timestamps.push_back( Strm.getPacket(0)["time"].asInt() ); Timestamps.push_back(Strm.getPacket(0)["time"].asInt());
} }
} }
if (pending_manifest && !Strm.metadata.isNull()){ if (pending_manifest && !Strm.metadata.isNull()){
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type","text/xml"); HTTP_S.SetHeader("Content-Type", "text/xml");
HTTP_S.SetHeader("Cache-Control","no-cache"); HTTP_S.SetHeader("Cache-Control", "no-cache");
if (Strm.metadata.isMember("length")){receive_marks = true;} if (Strm.metadata.isMember("length")){
receive_marks = true;
}
std::string manifest = BuildManifest(streamname, Strm.metadata); std::string manifest = BuildManifest(streamname, Strm.metadata);
HTTP_S.SetBody(manifest); HTTP_S.SetBody(manifest);
conn.SendNow(HTTP_S.BuildResponse("200", "OK")); conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
#if DEBUG >= 3 #if DEBUG >= 3
printf("Sent manifest\n"); printf("Sent manifest\n");
#endif #endif
pending_manifest = false; pending_manifest = false;
} }
} }
if (!ss.connected()){break;} if ( !ss.connected()){
break;
}
} }
} }
conn.close(); conn.close();
ss.SendNow(conn.getStats("HTTP_Smooth").c_str()); ss.SendNow(conn.getStats("HTTP_Smooth").c_str());
ss.close(); ss.close();
#if DEBUG >= 1 #if DEBUG >= 1
if (FLV::Parse_Error){fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());}
fprintf(stderr, "User %i disconnected.\n", conn.getSocket()); fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
if (inited){ if (inited){
fprintf(stderr, "Status was: inited\n"); fprintf(stderr, "Status was: inited\n");
@ -374,33 +394,35 @@ namespace Connector_HTTP{
fprintf(stderr, "Status was: connected\n"); fprintf(stderr, "Status was: connected\n");
} }
} }
#endif #endif
return 0; return 0;
}//Connector_HTTP_Smooth main function } //Connector_HTTP_Smooth main function
};//Connector_HTTP_Smooth namespace } //Connector_HTTP_Smooth namespace
int main(int argc, char ** argv){ int main(int argc, char ** argv){
Util::Config conf(argv[0], PACKAGE_VERSION); Util::Config conf(argv[0], PACKAGE_VERSION);
conf.addConnectorOptions(1935); conf.addConnectorOptions(1935);
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
Socket::Server server_socket = Socket::Server("/tmp/mist/http_smooth"); Socket::Server server_socket = Socket::Server("/tmp/mist/http_smooth");
if (!server_socket.connected()){return 1;} if ( !server_socket.connected()){
return 1;
}
conf.activate(); conf.activate();
while (server_socket.connected() && conf.is_active){ while (server_socket.connected() && conf.is_active){
Socket::Connection S = server_socket.accept(); Socket::Connection S = server_socket.accept();
if (S.connected()){//check if the new connection is valid if (S.connected()){ //check if the new connection is valid
pid_t myid = fork(); pid_t myid = fork();
if (myid == 0){//if new child, start MAINHANDLER if (myid == 0){ //if new child, start MAINHANDLER
return Connector_HTTP::Connector_HTTP_Dynamic(S); return Connector_HTTP::Connector_HTTP_Dynamic(S);
}else{//otherwise, do nothing or output debugging text }else{ //otherwise, do nothing or output debugging text
#if DEBUG >= 3 #if DEBUG >= 3
fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket()); fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket());
#endif #endif
} }
} }
}//while connected } //while connected
server_socket.close(); server_socket.close();
return 0; return 0;
}//main } //main

View file

@ -11,7 +11,7 @@
/// Contains the main code for the RAW connector. /// Contains the main code for the RAW connector.
/// Expects a single commandline argument telling it which stream to connect to, /// Expects a single commandline argument telling it which stream to connect to,
/// then outputs the raw stream to stdout. /// then outputs the raw stream to stdout.
int main(int argc, char ** argv) { int main(int argc, char ** argv){
Util::Config conf(argv[0], PACKAGE_VERSION); Util::Config conf(argv[0], PACKAGE_VERSION);
conf.addOption("stream_name", JSON::fromString("{\"arg_num\":1, \"help\":\"Name of the stream to write to stdout.\"}")); conf.addOption("stream_name", JSON::fromString("{\"arg_num\":1, \"help\":\"Name of the stream to write to stdout.\"}"));
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
@ -19,20 +19,20 @@ int main(int argc, char ** argv) {
//connect to the proper stream //connect to the proper stream
Socket::Connection S = Util::Stream::getStream(conf.getString("stream_name")); Socket::Connection S = Util::Stream::getStream(conf.getString("stream_name"));
S.setBlocking(false); S.setBlocking(false);
if (!S.connected()){ if ( !S.connected()){
std::cout << "Could not open stream " << conf.getString("stream_name") << std::endl; std::cout << "Could not open stream " << conf.getString("stream_name") << std::endl;
return 1; return 1;
} }
long long int lastStats = 0; long long int lastStats = 0;
long long int started = Util::epoch(); long long int started = Util::epoch();
while(std::cout.good()){ while (std::cout.good()){
if (S.spool()){ if (S.spool()){
while (S.Received().size()){ while (S.Received().size()){
std::cout.write(S.Received().get().c_str(),S.Received().get().size()); std::cout.write(S.Received().get().c_str(), S.Received().get().size());
S.Received().get().clear(); S.Received().get().clear();
} }
}else{ }else{
Util::sleep(10);//sleep 10ms if no data Util::sleep(10); //sleep 10ms if no data
} }
unsigned int now = Util::epoch(); unsigned int now = Util::epoch();
if (now != lastStats){ if (now != lastStats){

View file

@ -20,7 +20,7 @@
#include <mist/timing.h> #include <mist/timing.h>
/// Holds all functions and data unique to the RTMP Connector /// Holds all functions and data unique to the RTMP Connector
namespace Connector_RTMP{ namespace Connector_RTMP {
//for connection to server //for connection to server
bool ready4data = false; ///< Set to true when streaming starts. bool ready4data = false; ///< Set to true when streaming starts.
@ -34,17 +34,16 @@ namespace Connector_RTMP{
int play_msgtype = -1; int play_msgtype = -1;
//generic state keeping //generic state keeping
bool stream_inited = false;///true if init data for audio/video was sent bool stream_inited = false; ///true if init data for audio/video was sent
Socket::Connection Socket; ///< Socket connected to user Socket::Connection Socket; ///< Socket connected to user
Socket::Connection SS; ///< Socket connected to server Socket::Connection SS; ///< Socket connected to server
std::string streamname; ///< Stream that will be opened std::string streamname; ///< Stream that will be opened
void parseChunk(Socket::Buffer & buffer);///< Parses a single RTMP chunk. void parseChunk(Socket::Buffer & buffer); ///< Parses a single RTMP chunk.
void sendCommand(AMF::Object & amfreply, int messagetype, int stream_id);///< Sends a RTMP command either in AMF or AMF3 mode. void sendCommand(AMF::Object & amfreply, int messagetype, int stream_id); ///< Sends a RTMP command either in AMF or AMF3 mode.
void parseAMFCommand(AMF::Object & amfdata, int messagetype, int stream_id);///< Parses a single AMF command message. void parseAMFCommand(AMF::Object & amfdata, int messagetype, int stream_id); ///< Parses a single AMF command message.
int Connector_RTMP(Socket::Connection conn); int Connector_RTMP(Socket::Connection conn);
};//Connector_RTMP namespace; } //Connector_RTMP namespace;
/// Main Connector_RTMP function /// Main Connector_RTMP function
int Connector_RTMP::Connector_RTMP(Socket::Connection conn){ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
@ -53,22 +52,28 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
FLV::Tag tag, init_tag; FLV::Tag tag, init_tag;
DTSC::Stream Strm; DTSC::Stream Strm;
while (!Socket.Received().available(1537) && Socket.connected()){Socket.spool(); Util::sleep(5);} while ( !Socket.Received().available(1537) && Socket.connected()){
Socket.spool();
Util::sleep(5);
}
RTMPStream::handshake_in = Socket.Received().remove(1537); RTMPStream::handshake_in = Socket.Received().remove(1537);
RTMPStream::rec_cnt += 1537; RTMPStream::rec_cnt += 1537;
if (RTMPStream::doHandshake()){ if (RTMPStream::doHandshake()){
Socket.SendNow(RTMPStream::handshake_out); Socket.SendNow(RTMPStream::handshake_out);
while (!Socket.Received().available(1536) && Socket.connected()){Socket.spool(); Util::sleep(5);} while ( !Socket.Received().available(1536) && Socket.connected()){
Socket.spool();
Util::sleep(5);
}
Socket.Received().remove(1536); Socket.Received().remove(1536);
RTMPStream::rec_cnt += 1536; RTMPStream::rec_cnt += 1536;
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Handshake succcess!\n"); fprintf(stderr, "Handshake succcess!\n");
#endif #endif
}else{ }else{
#if DEBUG >= 1 #if DEBUG >= 1
fprintf(stderr, "Handshake fail!\n"); fprintf(stderr, "Handshake fail!\n");
#endif #endif
return 0; return 0;
} }
@ -80,23 +85,23 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
parseChunk(Socket.Received()); parseChunk(Socket.Received());
firsttime = false; firsttime = false;
}else{ }else{
Util::sleep(1);//sleep 1ms to prevent high CPU usage Util::sleep(1); //sleep 1ms to prevent high CPU usage
} }
if (ready4data){ if (ready4data){
if (!inited){ if ( !inited){
//we are ready, connect the socket! //we are ready, connect the socket!
SS = Util::Stream::getStream(streamname); SS = Util::Stream::getStream(streamname);
if (!SS.connected()){ if ( !SS.connected()){
#if DEBUG >= 1 #if DEBUG >= 1
fprintf(stderr, "Could not connect to server!\n"); fprintf(stderr, "Could not connect to server!\n");
#endif #endif
Socket.close();//disconnect user Socket.close(); //disconnect user
break; break;
} }
SS.setBlocking(false); SS.setBlocking(false);
#if DEBUG >= 3 #if DEBUG >= 3
fprintf(stderr, "Everything connected, starting to send video data...\n"); fprintf(stderr, "Everything connected, starting to send video data...\n");
#endif #endif
SS.SendNow("p\n"); SS.SendNow("p\n");
inited = true; inited = true;
} }
@ -112,10 +117,10 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
if (play_trans != -1){ if (play_trans != -1){
//send a status reply //send a status reply
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "onStatus"));//status reply amfreply.addContent(AMF::Object("", "onStatus")); //status reply
amfreply.addContent(AMF::Object("", (double)play_trans));//same transaction ID amfreply.addContent(AMF::Object("", (double)play_trans)); //same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
amfreply.addContent(AMF::Object(""));//info amfreply.addContent(AMF::Object("")); //info
amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Reset")); amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Reset"));
amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing and resetting...")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing and resetting..."));
@ -124,31 +129,31 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
sendCommand(amfreply, play_msgtype, play_streamid); sendCommand(amfreply, play_msgtype, play_streamid);
//send streamisrecorded if stream, well, is recorded. //send streamisrecorded if stream, well, is recorded.
if (Strm.metadata.isMember("length") && Strm.metadata["length"].asInt() > 0){ if (Strm.metadata.isMember("length") && Strm.metadata["length"].asInt() > 0){
Socket.Send(RTMPStream::SendUSR(4, 1));//send UCM StreamIsRecorded (4), stream 1 Socket.Send(RTMPStream::SendUSR(4, 1)); //send UCM StreamIsRecorded (4), stream 1
} }
//send streambegin //send streambegin
Socket.Send(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1 Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
//and more reply //and more reply
amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER); amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "onStatus"));//status reply amfreply.addContent(AMF::Object("", "onStatus")); //status reply
amfreply.addContent(AMF::Object("", (double)play_trans));//same transaction ID amfreply.addContent(AMF::Object("", (double)play_trans)); //same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
amfreply.addContent(AMF::Object(""));//info amfreply.addContent(AMF::Object("")); //info
amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Start")); amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Start"));
amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing!")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing!"));
amfreply.getContentP(3)->addContent(AMF::Object("details", "DDV")); amfreply.getContentP(3)->addContent(AMF::Object("details", "DDV"));
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
sendCommand(amfreply, play_msgtype, play_streamid); sendCommand(amfreply, play_msgtype, play_streamid);
RTMPStream::chunk_snd_max = 102400;//100KiB RTMPStream::chunk_snd_max = 102400; //100KiB
Socket.Send(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max));//send chunk size max (msg 1) Socket.Send(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max)); //send chunk size max (msg 1)
//send dunno? //send dunno?
Socket.Send(RTMPStream::SendUSR(32, 1));//send UCM no clue?, stream 1 Socket.Send(RTMPStream::SendUSR(32, 1)); //send UCM no clue?, stream 1
play_trans = -1; play_trans = -1;
} }
//sent init data if needed //sent init data if needed
if (!stream_inited){ if ( !stream_inited){
init_tag.DTSCMetaInit(Strm); init_tag.DTSCMetaInit(Strm);
Socket.SendNow(RTMPStream::SendMedia(init_tag)); Socket.SendNow(RTMPStream::SendMedia(init_tag));
if (Strm.metadata.isMember("audio") && Strm.metadata["audio"].isMember("init")){ if (Strm.metadata.isMember("audio") && Strm.metadata["audio"].isMember("init")){
@ -164,9 +169,9 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
//sent a tag //sent a tag
tag.DTSCLoader(Strm); tag.DTSCLoader(Strm);
Socket.SendNow(RTMPStream::SendMedia(tag)); Socket.SendNow(RTMPStream::SendMedia(tag));
#if DEBUG >= 8 #if DEBUG >= 8
fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), tag.tagTime(), tag.tagType().c_str()); fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), tag.tagTime(), tag.tagType().c_str());
#endif #endif
} }
} }
} }
@ -174,8 +179,10 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
Socket.close(); Socket.close();
SS.SendNow(Socket.getStats("RTMP").c_str()); SS.SendNow(Socket.getStats("RTMP").c_str());
SS.close(); SS.close();
#if DEBUG >= 1 #if DEBUG >= 1
if (FLV::Parse_Error){fprintf(stderr, "FLV Parse Error: %s\n", FLV::Error_Str.c_str());} if (FLV::Parse_Error){
fprintf(stderr, "FLV Parse Error: %s\n", FLV::Error_Str.c_str());
}
fprintf(stderr, "User %i disconnected.\n", conn.getSocket()); fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
if (inited){ if (inited){
fprintf(stderr, "Status was: inited\n"); fprintf(stderr, "Status was: inited\n");
@ -186,9 +193,9 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
fprintf(stderr, "Status was: connected\n"); fprintf(stderr, "Status was: connected\n");
} }
} }
#endif #endif
return 0; return 0;
}//Connector_RTMP } //Connector_RTMP
/// Tries to get and parse one RTMP chunk at a time. /// Tries to get and parse one RTMP chunk at a time.
void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){ void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
@ -210,35 +217,35 @@ void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
//send ACK if we received a whole window //send ACK if we received a whole window
if ((RTMPStream::rec_cnt - RTMPStream::rec_window_at > RTMPStream::rec_window_size)){ if ((RTMPStream::rec_cnt - RTMPStream::rec_window_at > RTMPStream::rec_window_size)){
RTMPStream::rec_window_at = RTMPStream::rec_cnt; RTMPStream::rec_window_at = RTMPStream::rec_cnt;
Socket.Send(RTMPStream::SendCTL(3, RTMPStream::rec_cnt));//send ack (msg 3) Socket.Send(RTMPStream::SendCTL(3, RTMPStream::rec_cnt)); //send ack (msg 3)
} }
switch (next.msg_type_id){ switch (next.msg_type_id){
case 0://does not exist case 0: //does not exist
#if DEBUG >= 2 #if DEBUG >= 2
fprintf(stderr, "UNKN: Received a zero-type message. This is an error.\n"); fprintf(stderr, "UNKN: Received a zero-type message. This is an error.\n");
#endif #endif
break;//happens when connection breaks unexpectedly break; //happens when connection breaks unexpectedly
case 1://set chunk size case 1: //set chunk size
RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str()); RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str());
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max); fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max);
#endif #endif
break; break;
case 2://abort message - we ignore this one case 2: //abort message - we ignore this one
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "CTRL: Abort message\n"); fprintf(stderr, "CTRL: Abort message\n");
#endif #endif
//4 bytes of stream id to drop //4 bytes of stream id to drop
break; break;
case 3://ack case 3: //ack
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "CTRL: Acknowledgement\n"); fprintf(stderr, "CTRL: Acknowledgement\n");
#endif #endif
RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str()); RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str());
RTMPStream::snd_window_at = RTMPStream::snd_cnt; RTMPStream::snd_window_at = RTMPStream::snd_cnt;
break; break;
case 4:{ case 4: {
//2 bytes event type, rest = event data //2 bytes event type, rest = event data
//types: //types:
//0 = stream begin, 4 bytes ID //0 = stream begin, 4 bytes ID
@ -249,50 +256,67 @@ void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
//6 = pingrequest, 4 bytes data //6 = pingrequest, 4 bytes data
//7 = pingresponse, 4 bytes data //7 = pingresponse, 4 bytes data
//we don't need to process this //we don't need to process this
#if DEBUG >= 4 #if DEBUG >= 4
short int ucmtype = ntohs(*(short int*)next.data.c_str()); short int ucmtype = ntohs(*(short int*)next.data.c_str());
switch (ucmtype){ switch (ucmtype){
case 0: fprintf(stderr, "CTRL: UCM StreamBegin %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break; case 0:
case 1: fprintf(stderr, "CTRL: UCM StreamEOF %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break; fprintf(stderr, "CTRL: UCM StreamBegin %i\n", ntohl(*((int*)(next.data.c_str()+2))));
case 2: fprintf(stderr, "CTRL: UCM StreamDry %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break; break;
case 3: fprintf(stderr, "CTRL: UCM SetBufferLength %i %i\n", ntohl(*((int*)(next.data.c_str()+2))), ntohl(*((int*)(next.data.c_str()+6)))); break; case 1:
case 4: fprintf(stderr, "CTRL: UCM StreamIsRecorded %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break; fprintf(stderr, "CTRL: UCM StreamEOF %i\n", ntohl(*((int*)(next.data.c_str()+2))));
case 6: fprintf(stderr, "CTRL: UCM PingRequest %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break; break;
case 7: fprintf(stderr, "CTRL: UCM PingResponse %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break; case 2:
default: fprintf(stderr, "CTRL: UCM Unknown (%hi)\n", ucmtype); break; fprintf(stderr, "CTRL: UCM StreamDry %i\n", ntohl(*((int*)(next.data.c_str()+2))));
break;
case 3:
fprintf(stderr, "CTRL: UCM SetBufferLength %i %i\n", ntohl(*((int*)(next.data.c_str()+2))), ntohl(*((int*)(next.data.c_str()+6))));
break;
case 4:
fprintf(stderr, "CTRL: UCM StreamIsRecorded %i\n", ntohl(*((int*)(next.data.c_str()+2))));
break;
case 6:
fprintf(stderr, "CTRL: UCM PingRequest %i\n", ntohl(*((int*)(next.data.c_str()+2))));
break;
case 7:
fprintf(stderr, "CTRL: UCM PingResponse %i\n", ntohl(*((int*)(next.data.c_str()+2))));
break;
default:
fprintf(stderr, "CTRL: UCM Unknown (%hi)\n", ucmtype);
break;
} }
#endif #endif
} break; }
case 5://window size of other end break;
#if DEBUG >= 4 case 5: //window size of other end
#if DEBUG >= 4
fprintf(stderr, "CTRL: Window size\n"); fprintf(stderr, "CTRL: Window size\n");
#endif #endif
RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str()); RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str());
RTMPStream::rec_window_at = RTMPStream::rec_cnt; RTMPStream::rec_window_at = RTMPStream::rec_cnt;
Socket.Send(RTMPStream::SendCTL(3, RTMPStream::rec_cnt));//send ack (msg 3) Socket.Send(RTMPStream::SendCTL(3, RTMPStream::rec_cnt)); //send ack (msg 3)
break; break;
case 6: case 6:
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "CTRL: Set peer bandwidth\n"); fprintf(stderr, "CTRL: Set peer bandwidth\n");
#endif #endif
//4 bytes window size, 1 byte limit type (ignored) //4 bytes window size, 1 byte limit type (ignored)
RTMPStream::snd_window_size = ntohl(*(int*)next.data.c_str()); RTMPStream::snd_window_size = ntohl(*(int*)next.data.c_str());
Socket.Send(RTMPStream::SendCTL(5, RTMPStream::snd_window_size));//send window acknowledgement size (msg 5) Socket.Send(RTMPStream::SendCTL(5, RTMPStream::snd_window_size)); //send window acknowledgement size (msg 5)
break; break;
case 8://audio data case 8: //audio data
case 9://video data case 9: //video data
case 18://meta data case 18: //meta data
if (SS.connected()){ if (SS.connected()){
F.ChunkLoader(next); F.ChunkLoader(next);
JSON::Value pack_out = F.toJSON(meta_out); JSON::Value pack_out = F.toJSON(meta_out);
if (!pack_out.isNull()){ if ( !pack_out.isNull()){
if (!sending){ if ( !sending){
counter++; counter++;
if (counter > 8){ if (counter > 8){
sending = true; sending = true;
SS.SendNow(meta_out.toNetPacked()); SS.SendNow(meta_out.toNetPacked());
SS.SendNow(prebuffer.str().c_str(), prebuffer.str().size());//write buffer SS.SendNow(prebuffer.str().c_str(), prebuffer.str().size()); //write buffer
prebuffer.str("");//clear buffer prebuffer.str(""); //clear buffer
SS.SendNow(pack_out.toNetPacked()); SS.SendNow(pack_out.toNetPacked());
}else{ }else{
prebuffer << pack_out.toNetPacked(); prebuffer << pack_out.toNetPacked();
@ -302,115 +326,125 @@ void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
} }
} }
}else{ }else{
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Received useless media data\n"); fprintf(stderr, "Received useless media data\n");
#endif #endif
Socket.close(); Socket.close();
} }
break; break;
case 15: case 15:
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Received AFM3 data message\n"); fprintf(stderr, "Received AFM3 data message\n");
#endif #endif
break; break;
case 16: case 16:
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Received AFM3 shared object\n"); fprintf(stderr, "Received AFM3 shared object\n");
#endif #endif
break; break;
case 17:{ case 17: {
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Received AFM3 command message\n"); fprintf(stderr, "Received AFM3 command message\n");
#endif #endif
if (next.data[0] != 0){ if (next.data[0] != 0){
next.data = next.data.substr(1); next.data = next.data.substr(1);
amf3data = AMF::parse3(next.data); amf3data = AMF::parse3(next.data);
#if DEBUG >= 4 #if DEBUG >= 4
amf3data.Print(); amf3data.Print();
#endif #endif
}else{ }else{
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Received AFM3-0 command message\n"); fprintf(stderr, "Received AFM3-0 command message\n");
#endif #endif
next.data = next.data.substr(1); next.data = next.data.substr(1);
amfdata = AMF::parse(next.data); amfdata = AMF::parse(next.data);
parseAMFCommand(amfdata, 17, next.msg_stream_id); parseAMFCommand(amfdata, 17, next.msg_stream_id);
}//parsing AMF0-style } //parsing AMF0-style
} break; }
case 19:
#if DEBUG >= 4
fprintf(stderr, "Received AFM0 shared object\n");
#endif
break; break;
case 20:{//AMF0 command message case 19:
#if DEBUG >= 4
fprintf(stderr, "Received AFM0 shared object\n");
#endif
break;
case 20: { //AMF0 command message
amfdata = AMF::parse(next.data); amfdata = AMF::parse(next.data);
parseAMFCommand(amfdata, 20, next.msg_stream_id); parseAMFCommand(amfdata, 20, next.msg_stream_id);
} break; }
break;
case 22: case 22:
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Received aggregate message\n"); fprintf(stderr, "Received aggregate message\n");
#endif #endif
break; break;
default: default:
#if DEBUG >= 1 #if DEBUG >= 1
fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n"); fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n");
#endif #endif
Connector_RTMP::stopparsing = true; Connector_RTMP::stopparsing = true;
break; break;
} }
} }
}//parseChunk } //parseChunk
void Connector_RTMP::sendCommand(AMF::Object & amfreply, int messagetype, int stream_id){ void Connector_RTMP::sendCommand(AMF::Object & amfreply, int messagetype, int stream_id){
#if DEBUG >= 4 #if DEBUG >= 4
std::cerr << amfreply.Print() << std::endl; std::cerr << amfreply.Print() << std::endl;
#endif #endif
if (messagetype == 17){ if (messagetype == 17){
Socket.SendNow(RTMPStream::SendChunk(3, messagetype, stream_id, (char)0+amfreply.Pack())); Socket.SendNow(RTMPStream::SendChunk(3, messagetype, stream_id, (char)0 + amfreply.Pack()));
}else{ }else{
Socket.SendNow(RTMPStream::SendChunk(3, messagetype, stream_id, amfreply.Pack())); Socket.SendNow(RTMPStream::SendChunk(3, messagetype, stream_id, amfreply.Pack()));
} }
}//sendCommand } //sendCommand
void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int stream_id){ void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int stream_id){
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Received command: %s\n", amfdata.Print().c_str()); fprintf(stderr, "Received command: %s\n", amfdata.Print().c_str());
#endif #endif
#if DEBUG >= 3 #if DEBUG >= 3
fprintf(stderr, "AMF0 command: %s\n", amfdata.getContentP(0)->StrValue().c_str()); fprintf(stderr, "AMF0 command: %s\n", amfdata.getContentP(0)->StrValue().c_str());
#endif #endif
if (amfdata.getContentP(0)->StrValue() == "connect"){ if (amfdata.getContentP(0)->StrValue() == "connect"){
double objencoding = 0; double objencoding = 0;
if (amfdata.getContentP(2)->getContentP("objectEncoding")){ if (amfdata.getContentP(2)->getContentP("objectEncoding")){
objencoding = amfdata.getContentP(2)->getContentP("objectEncoding")->NumValue(); objencoding = amfdata.getContentP(2)->getContentP("objectEncoding")->NumValue();
} }
#if DEBUG >= 4 #if DEBUG >= 4
int tmpint; int tmpint;
if (amfdata.getContentP(2)->getContentP("videoCodecs")){ if (amfdata.getContentP(2)->getContentP("videoCodecs")){
tmpint = (int)amfdata.getContentP(2)->getContentP("videoCodecs")->NumValue(); tmpint = (int)amfdata.getContentP(2)->getContentP("videoCodecs")->NumValue();
if (tmpint & 0x04){fprintf(stderr, "Sorensen video support detected\n");} if (tmpint & 0x04){
if (tmpint & 0x80){fprintf(stderr, "H264 video support detected\n");} fprintf(stderr, "Sorensen video support detected\n");
}
if (tmpint & 0x80){
fprintf(stderr, "H264 video support detected\n");
}
} }
if (amfdata.getContentP(2)->getContentP("audioCodecs")){ if (amfdata.getContentP(2)->getContentP("audioCodecs")){
tmpint = (int)amfdata.getContentP(2)->getContentP("audioCodecs")->NumValue(); tmpint = (int)amfdata.getContentP(2)->getContentP("audioCodecs")->NumValue();
if (tmpint & 0x04){fprintf(stderr, "MP3 audio support detected\n");} if (tmpint & 0x04){
if (tmpint & 0x400){fprintf(stderr, "AAC audio support detected\n");} fprintf(stderr, "MP3 audio support detected\n");
}
if (tmpint & 0x400){
fprintf(stderr, "AAC audio support detected\n");
}
} }
#endif #endif
RTMPStream::chunk_snd_max = 4096; RTMPStream::chunk_snd_max = 4096;
Socket.Send(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max));//send chunk size max (msg 1) Socket.Send(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max)); //send chunk size max (msg 1)
Socket.Send(RTMPStream::SendCTL(5, RTMPStream::snd_window_size));//send window acknowledgement size (msg 5) Socket.Send(RTMPStream::SendCTL(5, RTMPStream::snd_window_size)); //send window acknowledgement size (msg 5)
Socket.Send(RTMPStream::SendCTL(6, RTMPStream::rec_window_size));//send rec window acknowledgement size (msg 6) Socket.Send(RTMPStream::SendCTL(6, RTMPStream::rec_window_size)); //send rec window acknowledgement size (msg 6)
Socket.Send(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1 Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
//send a _result reply //send a _result reply
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "_result"));//result success amfreply.addContent(AMF::Object("", "_result")); //result success
amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(amfdata.getContent(1)); //same transaction ID
amfreply.addContent(AMF::Object(""));//server properties amfreply.addContent(AMF::Object("")); //server properties
amfreply.getContentP(2)->addContent(AMF::Object("fmsVer", "FMS/3,5,5,2004")); amfreply.getContentP(2)->addContent(AMF::Object("fmsVer", "FMS/3,5,5,2004"));
amfreply.getContentP(2)->addContent(AMF::Object("capabilities", (double)31)); amfreply.getContentP(2)->addContent(AMF::Object("capabilities", (double)31));
amfreply.getContentP(2)->addContent(AMF::Object("mode", (double)1)); amfreply.getContentP(2)->addContent(AMF::Object("mode", (double)1));
amfreply.addContent(AMF::Object(""));//info amfreply.addContent(AMF::Object("")); //info
amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetConnection.Connect.Success")); amfreply.getContentP(3)->addContent(AMF::Object("code", "NetConnection.Connect.Success"));
amfreply.getContentP(3)->addContent(AMF::Object("description", "Connection succeeded.")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Connection succeeded."));
@ -426,104 +460,106 @@ void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int
//amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null //amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null
//sendCommand(amfreply, messagetype, stream_id); //sendCommand(amfreply, messagetype, stream_id);
return; return;
}//connect } //connect
if (amfdata.getContentP(0)->StrValue() == "createStream"){ if (amfdata.getContentP(0)->StrValue() == "createStream"){
//send a _result reply //send a _result reply
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "_result"));//result success amfreply.addContent(AMF::Object("", "_result")); //result success
amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(amfdata.getContent(1)); //same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
amfreply.addContent(AMF::Object("", (double)1));//stream ID - we use 1 amfreply.addContent(AMF::Object("", (double)1)); //stream ID - we use 1
sendCommand(amfreply, messagetype, stream_id); sendCommand(amfreply, messagetype, stream_id);
Socket.Send(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1 Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
return; return;
}//createStream } //createStream
if ((amfdata.getContentP(0)->StrValue() == "closeStream") || (amfdata.getContentP(0)->StrValue() == "deleteStream")){ if ((amfdata.getContentP(0)->StrValue() == "closeStream") || (amfdata.getContentP(0)->StrValue() == "deleteStream")){
if (SS.connected()){SS.close();} if (SS.connected()){
SS.close();
}
return; return;
} }
if ((amfdata.getContentP(0)->StrValue() == "getStreamLength") || (amfdata.getContentP(0)->StrValue() == "getMovLen")){ if ((amfdata.getContentP(0)->StrValue() == "getStreamLength") || (amfdata.getContentP(0)->StrValue() == "getMovLen")){
//send a _result reply //send a _result reply
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "_result"));//result success amfreply.addContent(AMF::Object("", "_result")); //result success
amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(amfdata.getContent(1)); //same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
amfreply.addContent(AMF::Object("", (double)0));//zero length amfreply.addContent(AMF::Object("", (double)0)); //zero length
sendCommand(amfreply, messagetype, stream_id); sendCommand(amfreply, messagetype, stream_id);
return; return;
}//getStreamLength } //getStreamLength
if ((amfdata.getContentP(0)->StrValue() == "publish")){ if ((amfdata.getContentP(0)->StrValue() == "publish")){
if (amfdata.getContentP(3)){ if (amfdata.getContentP(3)){
streamname = amfdata.getContentP(3)->StrValue(); streamname = amfdata.getContentP(3)->StrValue();
/// \todo implement push for MistPlayer or restrict and change to getLive /// \todo implement push for MistPlayer or restrict and change to getLive
SS = Util::Stream::getStream(streamname); SS = Util::Stream::getStream(streamname);
if (!SS.connected()){ if ( !SS.connected()){
#if DEBUG >= 1 #if DEBUG >= 1
fprintf(stderr, "Could not connect to server!\n"); fprintf(stderr, "Could not connect to server!\n");
#endif #endif
Socket.close();//disconnect user Socket.close(); //disconnect user
return; return;
} }
SS.Send("P "); SS.Send("P ");
SS.Send(Socket.getHost().c_str()); SS.Send(Socket.getHost().c_str());
SS.Send("\n"); SS.Send("\n");
nostats = true; nostats = true;
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Connected to buffer, starting to send data...\n"); fprintf(stderr, "Connected to buffer, starting to send data...\n");
#endif #endif
} }
//send a _result reply //send a _result reply
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "_result"));//result success amfreply.addContent(AMF::Object("", "_result")); //result success
amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(amfdata.getContent(1)); //same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
amfreply.addContent(AMF::Object("", 1, AMF::AMF0_BOOL));//publish success? amfreply.addContent(AMF::Object("", 1, AMF::AMF0_BOOL)); //publish success?
sendCommand(amfreply, messagetype, stream_id); sendCommand(amfreply, messagetype, stream_id);
Socket.Send(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1 Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
//send a status reply //send a status reply
amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER); amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "onStatus"));//status reply amfreply.addContent(AMF::Object("", "onStatus")); //status reply
amfreply.addContent(AMF::Object("", 0, AMF::AMF0_NUMBER));//same transaction ID amfreply.addContent(AMF::Object("", 0, AMF::AMF0_NUMBER)); //same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
amfreply.addContent(AMF::Object(""));//info amfreply.addContent(AMF::Object("")); //info
amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Publish.Start")); amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Publish.Start"));
amfreply.getContentP(3)->addContent(AMF::Object("description", "Stream is now published!")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Stream is now published!"));
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
sendCommand(amfreply, messagetype, stream_id); sendCommand(amfreply, messagetype, stream_id);
return; return;
}//getStreamLength } //getStreamLength
if (amfdata.getContentP(0)->StrValue() == "checkBandwidth"){ if (amfdata.getContentP(0)->StrValue() == "checkBandwidth"){
//send a _result reply //send a _result reply
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "_result"));//result success amfreply.addContent(AMF::Object("", "_result")); //result success
amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(amfdata.getContent(1)); //same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
sendCommand(amfreply, messagetype, stream_id); sendCommand(amfreply, messagetype, stream_id);
return; return;
}//checkBandwidth } //checkBandwidth
if ((amfdata.getContentP(0)->StrValue() == "play") || (amfdata.getContentP(0)->StrValue() == "play2")){ if ((amfdata.getContentP(0)->StrValue() == "play") || (amfdata.getContentP(0)->StrValue() == "play2")){
//set reply number and stream name, actual reply is sent up in the SS.spool() handler //set reply number and stream name, actual reply is sent up in the SS.spool() handler
play_trans = amfdata.getContentP(1)->NumValue(); play_trans = amfdata.getContentP(1)->NumValue();
play_msgtype = messagetype; play_msgtype = messagetype;
play_streamid = stream_id; play_streamid = stream_id;
streamname = amfdata.getContentP(3)->StrValue(); streamname = amfdata.getContentP(3)->StrValue();
Connector_RTMP::ready4data = true;//start sending video data! Connector_RTMP::ready4data = true; //start sending video data!
return; return;
}//play } //play
if ((amfdata.getContentP(0)->StrValue() == "seek")){ if ((amfdata.getContentP(0)->StrValue() == "seek")){
//set reply number and stream name, actual reply is sent up in the SS.spool() handler //set reply number and stream name, actual reply is sent up in the SS.spool() handler
play_trans = amfdata.getContentP(1)->NumValue(); play_trans = amfdata.getContentP(1)->NumValue();
play_msgtype = messagetype; play_msgtype = messagetype;
play_streamid = stream_id; play_streamid = stream_id;
stream_inited = false; stream_inited = false;
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "onStatus"));//status reply amfreply.addContent(AMF::Object("", "onStatus")); //status reply
amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(amfdata.getContent(1)); //same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
amfreply.addContent(AMF::Object(""));//info amfreply.addContent(AMF::Object("")); //info
amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Seek.Notify")); amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Seek.Notify"));
amfreply.getContentP(3)->addContent(AMF::Object("description", "Seeking to the specified time")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Seeking to the specified time"));
@ -534,16 +570,16 @@ void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int
SS.Send(JSON::Value((long long int)amfdata.getContentP(3)->NumValue()).asString().c_str()); SS.Send(JSON::Value((long long int)amfdata.getContentP(3)->NumValue()).asString().c_str());
SS.Send("\n"); SS.Send("\n");
return; return;
}//seek } //seek
if ((amfdata.getContentP(0)->StrValue() == "pauseRaw") || (amfdata.getContentP(0)->StrValue() == "pause")){ if ((amfdata.getContentP(0)->StrValue() == "pauseRaw") || (amfdata.getContentP(0)->StrValue() == "pause")){
if (amfdata.getContentP(3)->NumValue()){ if (amfdata.getContentP(3)->NumValue()){
SS.Send("q\n");//quit playing SS.Send("q\n"); //quit playing
//send a status reply //send a status reply
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "onStatus"));//status reply amfreply.addContent(AMF::Object("", "onStatus")); //status reply
amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(amfdata.getContent(1)); //same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
amfreply.addContent(AMF::Object(""));//info amfreply.addContent(AMF::Object("")); //info
amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Pause.Notify")); amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Pause.Notify"));
amfreply.getContentP(3)->addContent(AMF::Object("description", "Pausing playback")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Pausing playback"));
@ -551,13 +587,13 @@ void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
sendCommand(amfreply, play_msgtype, play_streamid); sendCommand(amfreply, play_msgtype, play_streamid);
}else{ }else{
SS.Send("p\n");//start playing SS.Send("p\n"); //start playing
//send a status reply //send a status reply
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
amfreply.addContent(AMF::Object("", "onStatus"));//status reply amfreply.addContent(AMF::Object("", "onStatus")); //status reply
amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(amfdata.getContent(1)); //same transaction ID
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
amfreply.addContent(AMF::Object(""));//info amfreply.addContent(AMF::Object("")); //info
amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Unpause.Notify")); amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Unpause.Notify"));
amfreply.getContentP(3)->addContent(AMF::Object("description", "Resuming playback")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Resuming playback"));
@ -566,34 +602,36 @@ void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int
sendCommand(amfreply, play_msgtype, play_streamid); sendCommand(amfreply, play_msgtype, play_streamid);
} }
return; return;
}//seek } //seek
#if DEBUG >= 2 #if DEBUG >= 2
fprintf(stderr, "AMF0 command not processed! :(\n"); fprintf(stderr, "AMF0 command not processed! :(\n");
#endif #endif
}//parseAMFCommand } //parseAMFCommand
int main(int argc, char ** argv){ int main(int argc, char ** argv){
Util::Config conf(argv[0], PACKAGE_VERSION); Util::Config conf(argv[0], PACKAGE_VERSION);
conf.addConnectorOptions(1935); conf.addConnectorOptions(1935);
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
Socket::Server server_socket = Socket::Server(conf.getInteger("listen_port"), conf.getString("listen_interface")); Socket::Server server_socket = Socket::Server(conf.getInteger("listen_port"), conf.getString("listen_interface"));
if (!server_socket.connected()){return 1;} if ( !server_socket.connected()){
return 1;
}
conf.activate(); conf.activate();
while (server_socket.connected() && conf.is_active){ while (server_socket.connected() && conf.is_active){
Socket::Connection S = server_socket.accept(); Socket::Connection S = server_socket.accept();
if (S.connected()){//check if the new connection is valid if (S.connected()){ //check if the new connection is valid
pid_t myid = fork(); pid_t myid = fork();
if (myid == 0){//if new child, start MAINHANDLER if (myid == 0){ //if new child, start MAINHANDLER
return Connector_RTMP::Connector_RTMP(S); return Connector_RTMP::Connector_RTMP(S);
}else{//otherwise, do nothing or output debugging text }else{ //otherwise, do nothing or output debugging text
#if DEBUG >= 3 #if DEBUG >= 3
fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket()); fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket());
#endif #endif
} }
} }
}//while connected } //while connected
server_socket.close(); server_socket.close();
return 0; return 0;
}//main } //main

View file

@ -2,26 +2,8 @@
/// Contains all code for the controller executable. /// Contains all code for the controller executable.
#include <iostream> #include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector> #include <vector>
#include <map>
#include <set>
#include <cstdlib>
#include <queue>
#include <cmath>
#include <cstdio>
#include <climits>
#include <cstring>
#include <unistd.h>
#include <getopt.h>
#include <set>
#include <sys/wait.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <sstream>
#include <mist/config.h> #include <mist/config.h>
#include <mist/socket.h> #include <mist/socket.h>
#include <mist/http_parser.h> #include <mist/http_parser.h>
@ -30,343 +12,142 @@
#include <mist/timing.h> #include <mist/timing.h>
#include "controller_storage.h" #include "controller_storage.h"
#include "controller_connectors.h" #include "controller_connectors.h"
#include "controller_streams.h"
#include "controller_capabilities.h"
#include "server.html.h" #include "server.html.h"
#define UPLINK_INTERVAL 30 #define UPLINK_INTERVAL 30
#define COMPILED_USERNAME "" #define COMPILED_USERNAME ""
#define COMPILED_PASSWORD "" #define COMPILED_PASSWORD ""
namespace Controller{ namespace Controller {
std::map<std::string, int> lastBuffer; ///< Last moment of contact with all buffers. Secure::Auth keychecker; ///< Checks key authorization.
Secure::Auth keychecker; ///< Checks key authorization.
class ConnectedUser{
void WriteFile( std::string Filename, std::string contents ) { public:
std::ofstream File; Socket::Connection C;
File.open( Filename.c_str( ) ); HTTP::Parser H;
File << contents << std::endl; bool Authorized;
File.close( ); bool clientMode;
} int logins;
std::string Username;
class ConnectedUser{ ConnectedUser(Socket::Connection c){
public: C = c;
Socket::Connection C; H.Clean();
HTTP::Parser H; logins = 0;
bool Authorized; Authorized = false;
bool clientMode; clientMode = false;
int logins;
std::string Username;
ConnectedUser(Socket::Connection c){
C = c;
H.Clean();
logins = 0;
Authorized = false;
clientMode = false;
}
};
void Authorize( JSON::Value & Request, JSON::Value & Response, ConnectedUser & conn ) {
time_t Time = time(0);
tm * TimeInfo = localtime(&Time);
std::stringstream Date;
std::string retval;
Date << TimeInfo->tm_mday << "-" << TimeInfo->tm_mon << "-" << TimeInfo->tm_year + 1900;
std::string Challenge = Secure::md5( Date.str().c_str() + conn.C.getHost() );
if( Request.isMember( "authorize" ) ) {
std::string UserID = Request["authorize"]["username"];
if (Storage["account"].isMember(UserID)){
if (Secure::md5(Storage["account"][UserID]["password"].asString() + Challenge) == Request["authorize"]["password"].asString()){
Response["authorize"]["status"] = "OK";
conn.Username = UserID;
conn.Authorized = true;
return;
} }
} };
if (UserID != ""){
if (Request["authorize"]["password"].asString() != "" && Secure::md5(Storage["account"][UserID]["password"].asString()) != Request["authorize"]["password"].asString()){
Log("AUTH", "Failed login attempt "+UserID+" @ "+conn.C.getHost());
}
}
conn.logins++;
}
conn.Username = "";
conn.Authorized = false;
Response["authorize"]["status"] = "CHALL";
Response["authorize"]["challenge"] = Challenge;
return;
}
void CheckConfig(JSON::Value & in, JSON::Value & out){ void Authorize(JSON::Value & Request, JSON::Value & Response, ConnectedUser & conn){
for (JSON::ObjIter jit = in.ObjBegin(); jit != in.ObjEnd(); jit++){ time_t Time = time(0);
if (out.isMember(jit->first)){ tm * TimeInfo = localtime( &Time);
if (jit->second != out[jit->first]){ std::stringstream Date;
if (jit->first != "time"){ std::string retval;
Log("CONF", std::string("Updated configuration value ")+jit->first); Date << TimeInfo->tm_mday << "-" << TimeInfo->tm_mon << "-" << TimeInfo->tm_year + 1900;
std::string Challenge = Secure::md5(Date.str().c_str() + conn.C.getHost());
if (Request.isMember("authorize")){
std::string UserID = Request["authorize"]["username"];
if (Storage["account"].isMember(UserID)){
if (Secure::md5(Storage["account"][UserID]["password"].asString() + Challenge) == Request["authorize"]["password"].asString()){
Response["authorize"]["status"] = "OK";
conn.Username = UserID;
conn.Authorized = true;
return;
} }
} }
}else{ if (UserID != ""){
Log("CONF", std::string("New configuration value ")+jit->first); if (Request["authorize"]["password"].asString() != ""
} && Secure::md5(Storage["account"][UserID]["password"].asString()) != Request["authorize"]["password"].asString()){
} Log("AUTH", "Failed login attempt " + UserID + " @ " + conn.C.getHost());
for (JSON::ObjIter jit = out.ObjBegin(); jit != out.ObjEnd(); jit++){ }
if (!in.isMember(jit->first)){
Log("CONF", std::string("Deleted configuration value ")+jit->first);
}
}
out = in;
}
bool streamsEqual(JSON::Value & one, JSON::Value & two){
if (one["channel"]["URL"] != two["channel"]["URL"]){return false;}
if (one["preset"]["cmd"] != two["preset"]["cmd"]){return false;}
return true;
}
void startStream(std::string name, JSON::Value & data){
std::string URL = data["channel"]["URL"];
std::string preset = data["preset"]["cmd"];
std::string cmd1, cmd2, cmd3;
if (URL.substr(0, 4) == "push"){
std::string pusher = URL.substr(7);
cmd2 = "MistBuffer -s "+name+" "+pusher;
Util::Procs::Start(name, Util::getMyPath() + cmd2);
Log("BUFF", "(re)starting stream buffer "+name+" for push data from "+pusher);
}else{
if (URL.substr(0, 1) == "/"){
struct stat fileinfo;
if (stat(URL.c_str(), &fileinfo) != 0 || S_ISDIR(fileinfo.st_mode)){
Log("BUFF", "Warning for VoD stream "+name+"! File not found: "+URL);
data["error"] = "Not found: "+URL;
return;
} }
cmd1 = "cat "+URL; conn.logins++;
data["error"] = "Available";
return; //MistPlayer handles VoD
}else{
cmd1 = "ffmpeg -re -async 2 -i "+URL+" "+preset+" -f flv -";
cmd2 = "MistFLV2DTSC";
}
cmd3 = "MistBuffer -s "+name;
if (cmd2 != ""){
Util::Procs::Start(name, cmd1, Util::getMyPath() + cmd2, Util::getMyPath() + cmd3);
Log("BUFF", "(re)starting stream buffer "+name+" for ffmpeg data: "+cmd1);
}else{
Util::Procs::Start(name, cmd1, Util::getMyPath() + cmd3);
Log("BUFF", "(re)starting stream buffer "+name+" using input file "+URL);
} }
conn.Username = "";
conn.Authorized = false;
Response["authorize"]["status"] = "CHALL";
Response["authorize"]["challenge"] = Challenge;
return;
} }
}
void CheckStats(JSON::Value & stats){ void CheckConfig(JSON::Value & in, JSON::Value & out){
long long int currTime = Util::epoch(); for (JSON::ObjIter jit = in.ObjBegin(); jit != in.ObjEnd(); jit++){
for (JSON::ObjIter jit = stats.ObjBegin(); jit != stats.ObjEnd(); jit++){ if (out.isMember(jit->first)){
if (currTime - lastBuffer[jit->first] > 120){ if (jit->second != out[jit->first]){
stats.removeMember(jit->first); if (jit->first != "time"){
return; Log("CONF", std::string("Updated configuration value ") + jit->first);
}else{ }
if (jit->second.isMember("curr") && jit->second["curr"].size() > 0){ }
for (JSON::ObjIter u_it = jit->second["curr"].ObjBegin(); u_it != jit->second["curr"].ObjEnd(); ++u_it){ }else{
if (u_it->second.isMember("now") && u_it->second["now"].asInt() < currTime - 3){ Log("CONF", std::string("New configuration value ") + jit->first);
jit->second["log"].append(u_it->second); }
jit->second["curr"].removeMember(u_it->first); }
if (!jit->second["curr"].size()){break;} for (JSON::ObjIter jit = out.ObjBegin(); jit != out.ObjEnd(); jit++){
u_it = jit->second["curr"].ObjBegin(); if ( !in.isMember(jit->first)){
Log("CONF", std::string("Deleted configuration value ") + jit->first);
}
}
out = in;
}
void CheckStats(JSON::Value & stats){
long long int currTime = Util::epoch();
for (JSON::ObjIter jit = stats.ObjBegin(); jit != stats.ObjEnd(); jit++){
if (currTime - lastBuffer[jit->first] > 120){
stats.removeMember(jit->first);
return;
}else{
if (jit->second.isMember("curr") && jit->second["curr"].size() > 0){
for (JSON::ObjIter u_it = jit->second["curr"].ObjBegin(); u_it != jit->second["curr"].ObjEnd(); ++u_it){
if (u_it->second.isMember("now") && u_it->second["now"].asInt() < currTime - 3){
jit->second["log"].append(u_it->second);
jit->second["curr"].removeMember(u_it->first);
if ( !jit->second["curr"].size()){
break;
}
u_it = jit->second["curr"].ObjBegin();
}
} }
} }
} }
} }
} }
}
class cpudata { } //Controller namespace
public:
std::string model;
int cores;
int threads;
int mhz;
int id;
cpudata(){
model = "Unknown";
cores = 1;
threads = 1;
mhz = 0;
id = 0;
};
void fill(char * data){
int i;
i = 0;
if (sscanf(data, "model name : %n", &i) != EOF && i > 0){model = (data+i);}
if (sscanf(data, "cpu cores : %d", &i) == 1){cores = i;}
if (sscanf(data, "siblings : %d", &i) == 1){threads = i;}
if (sscanf(data, "physical id : %d", &i) == 1){id = i;}
if (sscanf(data, "cpu MHz : %d", &i) == 1){mhz = i;}
};
};
void checkCapable(JSON::Value & capa){
capa.null();
std::ifstream cpuinfo("/proc/cpuinfo");
if (cpuinfo){
std::map<int, cpudata> cpus;
char line[300];
int proccount = -1;
while (cpuinfo.good()){
cpuinfo.getline(line, 300);
if (cpuinfo.fail()){
//empty lines? ignore them, clear flags, continue
if (!cpuinfo.eof()){
cpuinfo.ignore();
cpuinfo.clear();
}
continue;
}
if (memcmp(line, "processor", 9) == 0){proccount++;}
cpus[proccount].fill(line);
}
//fix wrong core counts
std::map<int,int> corecounts;
for (int i = 0; i <= proccount; ++i){
corecounts[cpus[i].id]++;
}
//remove double physical IDs - we only want real CPUs.
std::set<int> used_physids;
int total_speed = 0;
int total_threads = 0;
for (int i = 0; i <= proccount; ++i){
if (!used_physids.count(cpus[i].id)){
used_physids.insert(cpus[i].id);
JSON::Value thiscpu;
thiscpu["model"] = cpus[i].model;
thiscpu["cores"] = cpus[i].cores;
if (cpus[i].cores < 2 && corecounts[cpus[i].id] > cpus[i].cores){
thiscpu["cores"] = corecounts[cpus[i].id];
}
thiscpu["threads"] = cpus[i].threads;
if (thiscpu["cores"].asInt() > thiscpu["threads"].asInt()){
thiscpu["threads"] = thiscpu["cores"];
}
thiscpu["mhz"] = cpus[i].mhz;
capa["cpu"].append(thiscpu);
total_speed += cpus[i].cores * cpus[i].mhz;
total_threads += cpus[i].threads;
}
}
capa["speed"] = total_speed;
capa["threads"] = total_threads;
}
std::ifstream meminfo("/proc/meminfo");
if (meminfo){
char line[300];
int bufcache = 0;
while (meminfo.good()){
meminfo.getline(line, 300);
if (meminfo.fail()){
//empty lines? ignore them, clear flags, continue
if (!meminfo.eof()){
meminfo.ignore();
meminfo.clear();
}
continue;
}
long long int i;
if (sscanf(line, "MemTotal : %Li kB", &i) == 1){capa["mem"]["total"] = i/1024;}
if (sscanf(line, "MemFree : %Li kB", &i) == 1){capa["mem"]["free"] = i/1024;}
if (sscanf(line, "SwapTotal : %Li kB", &i) == 1){capa["mem"]["swaptotal"] = i/1024;}
if (sscanf(line, "SwapFree : %Li kB", &i) == 1){capa["mem"]["swapfree"] = i/1024;}
if (sscanf(line, "Buffers : %Li kB", &i) == 1){bufcache += i/1024;}
if (sscanf(line, "Cached : %Li kB", &i) == 1){bufcache += i/1024;}
}
capa["mem"]["used"] = capa["mem"]["total"].asInt() - capa["mem"]["free"].asInt() - bufcache;
capa["mem"]["cached"] = bufcache;
capa["load"]["memory"] = ((capa["mem"]["used"].asInt() + (capa["mem"]["swaptotal"].asInt() - capa["mem"]["swapfree"].asInt())) * 100) / capa["mem"]["total"].asInt();
}
std::ifstream loadavg("/proc/loadavg");
if (loadavg){
char line[300];
int bufcache = 0;
loadavg.getline(line, 300);
//parse lines here
float onemin, fivemin, fifteenmin;
if (sscanf(line, "%f %f %f", &onemin, &fivemin, &fifteenmin) == 3){
capa["load"]["one"] = (long long int)(onemin * 100);
capa["load"]["five"] = (long long int)(onemin * 100);
capa["load"]["fifteen"] = (long long int)(onemin * 100);
}
}
}
void CheckAllStreams(JSON::Value & data){
long long int currTime = Util::epoch();
for (JSON::ObjIter jit = data.ObjBegin(); jit != data.ObjEnd(); jit++){
if (!Util::Procs::isActive(jit->first)){
startStream(jit->first, jit->second);
}
if (currTime - lastBuffer[jit->first] > 5){
if (jit->second.isMember("error") && jit->second["error"].asString() != ""){
jit->second["online"] = jit->second["error"];
}else{
jit->second["online"] = 0;
}
}else{
jit->second["online"] = 1;
}
}
static JSON::Value strlist;
bool changed = false;
if (strlist["config"] != Storage["config"]){
strlist["config"] = Storage["config"];
changed = true;
}
if (strlist["streams"] != Storage["streams"]){
strlist["streams"] = Storage["streams"];
changed = true;
}
if (changed){WriteFile("/tmp/mist/streamlist", strlist.toString());}
}
void CheckStreams(JSON::Value & in, JSON::Value & out){
bool changed = false;
for (JSON::ObjIter jit = in.ObjBegin(); jit != in.ObjEnd(); jit++){
if (out.isMember(jit->first)){
if (!streamsEqual(jit->second, out[jit->first])){
Log("STRM", std::string("Updated stream ")+jit->first);
Util::Procs::Stop(jit->first);
startStream(jit->first, jit->second);
}
}else{
Log("STRM", std::string("New stream ")+jit->first);
startStream(jit->first, jit->second);
}
}
for (JSON::ObjIter jit = out.ObjBegin(); jit != out.ObjEnd(); jit++){
if (!in.isMember(jit->first)){
Log("STRM", std::string("Deleted stream ")+jit->first);
Util::Procs::Stop(jit->first);
}
}
out = in;
}
}; //Connector namespace
int main(int argc, char ** argv){ int main(int argc, char ** argv){
Controller::Storage = JSON::fromFile("config.json"); Controller::Storage = JSON::fromFile("config.json");
JSON::Value stored_port = JSON::fromString("{\"long\":\"port\", \"short\":\"p\", \"arg\":\"integer\", \"help\":\"TCP port to listen on.\"}"); JSON::Value stored_port = JSON::fromString("{\"long\":\"port\", \"short\":\"p\", \"arg\":\"integer\", \"help\":\"TCP port to listen on.\"}");
stored_port["default"] = Controller::Storage["config"]["controller"]["port"]; stored_port["default"] = Controller::Storage["config"]["controller"]["port"];
if (!stored_port["default"]){stored_port["default"] = 4242;} if ( !stored_port["default"]){
JSON::Value stored_interface = JSON::fromString("{\"long\":\"interface\", \"short\":\"i\", \"arg\":\"string\", \"help\":\"Interface address to listen on, or 0.0.0.0 for all available interfaces.\"}"); stored_port["default"] = 4242;
}
JSON::Value stored_interface =
JSON::fromString(
"{\"long\":\"interface\", \"short\":\"i\", \"arg\":\"string\", \"help\":\"Interface address to listen on, or 0.0.0.0 for all available interfaces.\"}");
stored_interface["default"] = Controller::Storage["config"]["controller"]["interface"]; stored_interface["default"] = Controller::Storage["config"]["controller"]["interface"];
if (!stored_interface["default"]){stored_interface["default"] = "0.0.0.0";} if ( !stored_interface["default"]){
JSON::Value stored_user = JSON::fromString("{\"long\":\"username\", \"short\":\"u\", \"arg\":\"string\", \"help\":\"Username to drop privileges to, or root to not drop provileges.\"}"); stored_interface["default"] = "0.0.0.0";
}
JSON::Value stored_user = JSON::fromString(
"{\"long\":\"username\", \"short\":\"u\", \"arg\":\"string\", \"help\":\"Username to drop privileges to, or root to not drop provileges.\"}");
stored_user["default"] = Controller::Storage["config"]["controller"]["username"]; stored_user["default"] = Controller::Storage["config"]["controller"]["username"];
if (!stored_user["default"]){stored_user["default"] = "root";} if ( !stored_user["default"]){
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION); stored_user["default"] = "root";
}
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION " / " RELEASE);
conf.addOption("listen_port", stored_port); conf.addOption("listen_port", stored_port);
conf.addOption("listen_interface", stored_interface); conf.addOption("listen_interface", stored_interface);
conf.addOption("username", stored_user); conf.addOption("username", stored_user);
conf.addOption("daemonize", JSON::fromString("{\"long\":\"daemon\", \"short\":\"d\", \"default\":1, \"long_off\":\"nodaemon\", \"short_off\":\"n\", \"help\":\"Whether or not to daemonize the process after starting.\"}")); conf.addOption("daemonize",
conf.addOption("account", JSON::fromString("{\"long\":\"account\", \"short\":\"a\", \"arg\":\"string\" \"default\":\"\", \"help\":\"A username:password string to create a new account with.\"}")); JSON::fromString(
"{\"long\":\"daemon\", \"short\":\"d\", \"default\":1, \"long_off\":\"nodaemon\", \"short_off\":\"n\", \"help\":\"Whether or not to daemonize the process after starting.\"}"));
conf.addOption("account",
JSON::fromString(
"{\"long\":\"account\", \"short\":\"a\", \"arg\":\"string\" \"default\":\"\", \"help\":\"A username:password string to create a new account with.\"}"));
conf.addOption("uplink", JSON::fromString("{\"default\":0, \"help\":\"Enable MistSteward uplink.\", \"short\":\"U\", \"long\":\"uplink\"}")); conf.addOption("uplink", JSON::fromString("{\"default\":0, \"help\":\"Enable MistSteward uplink.\", \"short\":\"U\", \"long\":\"uplink\"}"));
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
@ -376,18 +157,18 @@ int main(int argc, char ** argv){
if (colon != std::string::npos && colon != 0 && colon != account.size()){ if (colon != std::string::npos && colon != 0 && colon != account.size()){
std::string uname = account.substr(0, colon); std::string uname = account.substr(0, colon);
std::string pword = account.substr(colon + 1, std::string::npos); std::string pword = account.substr(colon + 1, std::string::npos);
Controller::Log("CONF", "Created account "+uname+" through commandline option"); Controller::Log("CONF", "Created account " + uname + " through commandline option");
Controller::Storage["account"][uname]["password"] = Secure::md5(pword); Controller::Storage["account"][uname]["password"] = Secure::md5(pword);
} }
} }
time_t lastuplink = 0; time_t lastuplink = 0;
time_t processchecker = 0; time_t processchecker = 0;
Socket::Server API_Socket = Socket::Server(conf.getInteger("listen_port"), conf.getString("listen_interface"), true); Socket::Server API_Socket = Socket::Server(conf.getInteger("listen_port"), conf.getString("listen_interface"), true);
mkdir("/tmp/mist", S_IRWXU | S_IRWXG | S_IRWXO);//attempt to create /tmp/mist/ - ignore failures mkdir("/tmp/mist", S_IRWXU | S_IRWXG | S_IRWXO); //attempt to create /tmp/mist/ - ignore failures
Socket::Server Stats_Socket = Socket::Server("/tmp/mist/statistics", true); Socket::Server Stats_Socket = Socket::Server("/tmp/mist/statistics", true);
conf.activate(); conf.activate();
Socket::Connection Incoming; Socket::Connection Incoming;
std::vector< Controller::ConnectedUser > users; std::vector<Controller::ConnectedUser> users;
std::vector<Socket::Connection> buffers; std::vector<Socket::Connection> buffers;
JSON::Value Request; JSON::Value Request;
JSON::Value Response; JSON::Value Response;
@ -408,19 +189,22 @@ int main(int argc, char ** argv){
lastuplink = Util::epoch(); lastuplink = Util::epoch();
bool gotUplink = false; bool gotUplink = false;
if (users.size() > 0){ if (users.size() > 0){
for( std::vector< Controller::ConnectedUser >::iterator it = users.end() - 1; it >= users.begin(); it--) { for (std::vector<Controller::ConnectedUser>::iterator it = users.end() - 1; it >= users.begin(); it--){
if (!it->C.connected()){ if ( !it->C.connected()){
it->C.close(); it->C.close();
users.erase(it); users.erase(it);
break; break;
} }
if (it->clientMode){uplink = &*it; gotUplink = true;} if (it->clientMode){
uplink = & *it;
gotUplink = true;
}
} }
} }
if (!gotUplink){ if ( !gotUplink){
Incoming = Socket::Connection("gearbox.ddvtech.com", 4242, true); Incoming = Socket::Connection("gearbox.ddvtech.com", 4242, true);
if (Incoming.connected()){ if (Incoming.connected()){
users.push_back(Incoming); users.push_back((Controller::ConnectedUser)Incoming);
users.back().clientMode = true; users.back().clientMode = true;
uplink = &users.back(); uplink = &users.back();
gotUplink = true; gotUplink = true;
@ -434,7 +218,7 @@ int main(int argc, char ** argv){
Response["statistics"] = Controller::Storage["statistics"]; Response["statistics"] = Controller::Storage["statistics"];
Response["now"] = (unsigned int)lastuplink; Response["now"] = (unsigned int)lastuplink;
uplink->H.Clean(); uplink->H.Clean();
uplink->H.SetBody("command="+HTTP::Parser::urlencode(Response.toString())); uplink->H.SetBody("command=" + HTTP::Parser::urlencode(Response.toString()));
uplink->H.BuildRequest(); uplink->H.BuildRequest();
uplink->C.Send(uplink->H.BuildResponse("200", "OK")); uplink->C.Send(uplink->H.BuildResponse("200", "OK"));
uplink->H.Clean(); uplink->H.Clean();
@ -445,12 +229,16 @@ int main(int argc, char ** argv){
} }
Incoming = API_Socket.accept(true); Incoming = API_Socket.accept(true);
if (Incoming.connected()){users.push_back(Incoming);} if (Incoming.connected()){
users.push_back((Controller::ConnectedUser)Incoming);
}
Incoming = Stats_Socket.accept(true); Incoming = Stats_Socket.accept(true);
if (Incoming.connected()){buffers.push_back(Incoming);} if (Incoming.connected()){
buffers.push_back(Incoming);
}
if (buffers.size() > 0){ if (buffers.size() > 0){
for( std::vector< Socket::Connection >::iterator it = buffers.begin(); it != buffers.end(); it++) { for (std::vector<Socket::Connection>::iterator it = buffers.begin(); it != buffers.end(); it++){
if (!it->connected()){ if ( !it->connected()){
it->close(); it->close();
buffers.erase(it); buffers.erase(it);
break; break;
@ -472,10 +260,10 @@ int main(int argc, char ** argv){
std::string nowstr = Request["totals"]["now"].asString(); std::string nowstr = Request["totals"]["now"].asString();
Controller::Storage["statistics"][thisbuffer]["totals"][nowstr] = Request["totals"]; Controller::Storage["statistics"][thisbuffer]["totals"][nowstr] = Request["totals"];
Controller::Storage["statistics"][thisbuffer]["totals"][nowstr].removeMember("now"); Controller::Storage["statistics"][thisbuffer]["totals"][nowstr].removeMember("now");
Controller::Storage["statistics"][thisbuffer]["totals"].shrink(600);//limit to 10 minutes of data Controller::Storage["statistics"][thisbuffer]["totals"].shrink(600); //limit to 10 minutes of data
for (JSON::ObjIter jit = Request["log"].ObjBegin(); jit != Request["log"].ObjEnd(); jit++){ for (JSON::ObjIter jit = Request["log"].ObjBegin(); jit != Request["log"].ObjEnd(); jit++){
Controller::Storage["statistics"][thisbuffer]["log"].append(jit->second); Controller::Storage["statistics"][thisbuffer]["log"].append(jit->second);
Controller::Storage["statistics"][thisbuffer]["log"].shrink(1000);//limit to 1000 users per buffer Controller::Storage["statistics"][thisbuffer]["log"].shrink(1000); //limit to 1000 users per buffer
} }
} }
} }
@ -492,7 +280,8 @@ int main(int argc, char ** argv){
Controller::Storage["statistics"][oit->first]["curr"][sockit.asString()] = Request["vod"]; Controller::Storage["statistics"][oit->first]["curr"][sockit.asString()] = Request["vod"];
Controller::Storage["statistics"][oit->first]["curr"][sockit.asString()].removeMember("meta"); Controller::Storage["statistics"][oit->first]["curr"][sockit.asString()].removeMember("meta");
JSON::Value nowtotal; JSON::Value nowtotal;
for (JSON::ObjIter u_it = Controller::Storage["statistics"][oit->first]["curr"].ObjBegin(); u_it != Controller::Storage["statistics"][oit->first]["curr"].ObjEnd(); ++u_it){ for (JSON::ObjIter u_it = Controller::Storage["statistics"][oit->first]["curr"].ObjBegin();
u_it != Controller::Storage["statistics"][oit->first]["curr"].ObjEnd(); ++u_it){
nowtotal["up"] = nowtotal["up"].asInt() + u_it->second["up"].asInt(); nowtotal["up"] = nowtotal["up"].asInt() + u_it->second["up"].asInt();
nowtotal["down"] = nowtotal["down"].asInt() + u_it->second["down"].asInt(); nowtotal["down"] = nowtotal["down"].asInt() + u_it->second["down"].asInt();
nowtotal["count"] = nowtotal["count"].asInt() + 1; nowtotal["count"] = nowtotal["count"].asInt() + 1;
@ -507,14 +296,14 @@ int main(int argc, char ** argv){
} }
} }
if (users.size() > 0){ if (users.size() > 0){
for( std::vector< Controller::ConnectedUser >::iterator it = users.begin(); it != users.end(); it++) { for (std::vector<Controller::ConnectedUser>::iterator it = users.begin(); it != users.end(); it++){
if (!it->C.connected() || it->logins > 3){ if ( !it->C.connected() || it->logins > 3){
it->C.close(); it->C.close();
users.erase(it); users.erase(it);
break; break;
} }
if (it->C.spool() || it->C.Received().size()){ if (it->C.spool() || it->C.Received().size()){
if (*(it->C.Received().get().rbegin()) != '\n'){ if ( *(it->C.Received().get().rbegin()) != '\n'){
std::string tmp = it->C.Received().get(); std::string tmp = it->C.Received().get();
it->C.Received().get().clear(); it->C.Received().get().clear();
if (it->C.Received().size()){ if (it->C.Received().size()){
@ -547,7 +336,7 @@ int main(int argc, char ** argv){
Controller::Log("UPLK", "Responding to login challenge: " + Request["authorize"]["challenge"].asString()); Controller::Log("UPLK", "Responding to login challenge: " + Request["authorize"]["challenge"].asString());
Response["authorize"]["password"] = Secure::md5(COMPILED_PASSWORD + Request["authorize"]["challenge"].asString()); Response["authorize"]["password"] = Secure::md5(COMPILED_PASSWORD + Request["authorize"]["challenge"].asString());
it->H.Clean(); it->H.Clean();
it->H.SetBody("command="+HTTP::Parser::urlencode(Response.toString())); it->H.SetBody("command=" + HTTP::Parser::urlencode(Response.toString()));
it->H.BuildRequest(); it->H.BuildRequest();
it->C.Send(it->H.BuildResponse("200", "OK")); it->C.Send(it->H.BuildResponse("200", "OK"));
it->H.Clean(); it->H.Clean();
@ -555,8 +344,12 @@ int main(int argc, char ** argv){
} }
} }
}else{ }else{
if (Request.isMember("config")){Controller::CheckConfig(Request["config"], Controller::Storage["config"]);} if (Request.isMember("config")){
if (Request.isMember("streams")){Controller::CheckStreams(Request["streams"], Controller::Storage["streams"]);} Controller::CheckConfig(Request["config"], Controller::Storage["config"]);
}
if (Request.isMember("streams")){
Controller::CheckStreams(Request["streams"], Controller::Storage["streams"]);
}
if (Request.isMember("clearstatlogs")){ if (Request.isMember("clearstatlogs")){
Controller::Storage["log"].null(); Controller::Storage["log"].null();
Controller::Storage["statistics"].null(); Controller::Storage["statistics"].null();
@ -564,7 +357,7 @@ int main(int argc, char ** argv){
} }
}else{ }else{
Request = JSON::fromString(it->H.GetVar("command")); Request = JSON::fromString(it->H.GetVar("command"));
if (!Request.isObject() && it->H.url != "/api"){ if ( !Request.isObject() && it->H.url != "/api"){
it->H.Clean(); it->H.Clean();
it->H.SetHeader("Content-Type", "text/html"); it->H.SetHeader("Content-Type", "text/html");
it->H.SetHeader("X-Info", "To force an API response, request the file /api"); it->H.SetHeader("X-Info", "To force an API response, request the file /api");
@ -573,11 +366,15 @@ int main(int argc, char ** argv){
it->C.Send(it->H.BuildResponse("200", "OK")); it->C.Send(it->H.BuildResponse("200", "OK"));
it->H.Clean(); it->H.Clean();
}else{ }else{
Authorize(Request, Response, (*it)); Authorize(Request, Response, ( *it));
if (it->Authorized){ if (it->Authorized){
//Parse config and streams from the request. //Parse config and streams from the request.
if (Request.isMember("config")){Controller::CheckConfig(Request["config"], Controller::Storage["config"]);} if (Request.isMember("config")){
if (Request.isMember("streams")){Controller::CheckStreams(Request["streams"], Controller::Storage["streams"]);} Controller::CheckConfig(Request["config"], Controller::Storage["config"]);
}
if (Request.isMember("streams")){
Controller::CheckStreams(Request["streams"], Controller::Storage["streams"]);
}
if (Request.isMember("save")){ if (Request.isMember("save")){
Controller::WriteFile("config.json", Controller::Storage.toString()); Controller::WriteFile("config.json", Controller::Storage.toString());
Controller::Log("CONF", "Config written to file on request through API"); Controller::Log("CONF", "Config written to file on request through API");
@ -589,7 +386,9 @@ int main(int argc, char ** argv){
Response["streams"] = Controller::Storage["streams"]; Response["streams"] = Controller::Storage["streams"];
//add required data to the current unix time to the config, for syncing reasons //add required data to the current unix time to the config, for syncing reasons
Response["config"]["time"] = Util::epoch(); Response["config"]["time"] = Util::epoch();
if (!Response["config"].isMember("serverid")){Response["config"]["serverid"] = "";} if ( !Response["config"].isMember("serverid")){
Response["config"]["serverid"] = "";
}
//sent any available logs and statistics //sent any available logs and statistics
Response["log"] = Controller::Storage["log"]; Response["log"] = Controller::Storage["log"];
Response["statistics"] = Controller::Storage["statistics"]; Response["statistics"] = Controller::Storage["statistics"];
@ -600,14 +399,18 @@ int main(int argc, char ** argv){
} }
} }
jsonp = ""; jsonp = "";
if (it->H.GetVar("callback") != ""){jsonp = it->H.GetVar("callback");} if (it->H.GetVar("callback") != ""){
if (it->H.GetVar("jsonp") != ""){jsonp = it->H.GetVar("jsonp");} jsonp = it->H.GetVar("callback");
}
if (it->H.GetVar("jsonp") != ""){
jsonp = it->H.GetVar("jsonp");
}
it->H.Clean(); it->H.Clean();
it->H.SetHeader("Content-Type", "text/javascript"); it->H.SetHeader("Content-Type", "text/javascript");
if (jsonp == ""){ if (jsonp == ""){
it->H.SetBody(Response.toString()+"\n\n"); it->H.SetBody(Response.toString() + "\n\n");
}else{ }else{
it->H.SetBody(jsonp+"("+Response.toString()+");\n\n"); it->H.SetBody(jsonp + "(" + Response.toString() + ");\n\n");
} }
it->C.Send(it->H.BuildResponse("200", "OK")); it->C.Send(it->H.BuildResponse("200", "OK"));
it->H.Clean(); it->H.Clean();

View file

@ -0,0 +1,154 @@
#include <stdio.h>
#include <string.h>
#include <fstream>
#include <set>
#include "controller_capabilities.h"
namespace Controller {
class cpudata{
public:
std::string model;
int cores;
int threads;
int mhz;
int id;
cpudata(){
model = "Unknown";
cores = 1;
threads = 1;
mhz = 0;
id = 0;
}
;
void fill(char * data){
int i;
i = 0;
if (sscanf(data, "model name : %n", &i) != EOF && i > 0){
model = (data + i);
}
if (sscanf(data, "cpu cores : %d", &i) == 1){
cores = i;
}
if (sscanf(data, "siblings : %d", &i) == 1){
threads = i;
}
if (sscanf(data, "physical id : %d", &i) == 1){
id = i;
}
if (sscanf(data, "cpu MHz : %d", &i) == 1){
mhz = i;
}
}
;
};
void checkCapable(JSON::Value & capa){
capa.null();
std::ifstream cpuinfo("/proc/cpuinfo");
if (cpuinfo){
std::map<int, cpudata> cpus;
char line[300];
int proccount = -1;
while (cpuinfo.good()){
cpuinfo.getline(line, 300);
if (cpuinfo.fail()){
//empty lines? ignore them, clear flags, continue
if ( !cpuinfo.eof()){
cpuinfo.ignore();
cpuinfo.clear();
}
continue;
}
if (memcmp(line, "processor", 9) == 0){
proccount++;
}
cpus[proccount].fill(line);
}
//fix wrong core counts
std::map<int, int> corecounts;
for (int i = 0; i <= proccount; ++i){
corecounts[cpus[i].id]++;
}
//remove double physical IDs - we only want real CPUs.
std::set<int> used_physids;
int total_speed = 0;
int total_threads = 0;
for (int i = 0; i <= proccount; ++i){
if ( !used_physids.count(cpus[i].id)){
used_physids.insert(cpus[i].id);
JSON::Value thiscpu;
thiscpu["model"] = cpus[i].model;
thiscpu["cores"] = cpus[i].cores;
if (cpus[i].cores < 2 && corecounts[cpus[i].id] > cpus[i].cores){
thiscpu["cores"] = corecounts[cpus[i].id];
}
thiscpu["threads"] = cpus[i].threads;
if (thiscpu["cores"].asInt() > thiscpu["threads"].asInt()){
thiscpu["threads"] = thiscpu["cores"];
}
thiscpu["mhz"] = cpus[i].mhz;
capa["cpu"].append(thiscpu);
total_speed += cpus[i].cores * cpus[i].mhz;
total_threads += cpus[i].threads;
}
}
capa["speed"] = total_speed;
capa["threads"] = total_threads;
}
std::ifstream meminfo("/proc/meminfo");
if (meminfo){
char line[300];
int bufcache = 0;
while (meminfo.good()){
meminfo.getline(line, 300);
if (meminfo.fail()){
//empty lines? ignore them, clear flags, continue
if ( !meminfo.eof()){
meminfo.ignore();
meminfo.clear();
}
continue;
}
long long int i;
if (sscanf(line, "MemTotal : %Li kB", &i) == 1){
capa["mem"]["total"] = i / 1024;
}
if (sscanf(line, "MemFree : %Li kB", &i) == 1){
capa["mem"]["free"] = i / 1024;
}
if (sscanf(line, "SwapTotal : %Li kB", &i) == 1){
capa["mem"]["swaptotal"] = i / 1024;
}
if (sscanf(line, "SwapFree : %Li kB", &i) == 1){
capa["mem"]["swapfree"] = i / 1024;
}
if (sscanf(line, "Buffers : %Li kB", &i) == 1){
bufcache += i / 1024;
}
if (sscanf(line, "Cached : %Li kB", &i) == 1){
bufcache += i / 1024;
}
}
capa["mem"]["used"] = capa["mem"]["total"].asInt() - capa["mem"]["free"].asInt() - bufcache;
capa["mem"]["cached"] = bufcache;
capa["load"]["memory"] = ((capa["mem"]["used"].asInt() + (capa["mem"]["swaptotal"].asInt() - capa["mem"]["swapfree"].asInt())) * 100)
/ capa["mem"]["total"].asInt();
}
std::ifstream loadavg("/proc/loadavg");
if (loadavg){
char line[300];
int bufcache = 0;
loadavg.getline(line, 300);
//parse lines here
float onemin, fivemin, fifteenmin;
if (sscanf(line, "%f %f %f", &onemin, &fivemin, &fifteenmin) == 3){
capa["load"]["one"] = (long long int)(onemin * 100);
capa["load"]["five"] = (long long int)(onemin * 100);
capa["load"]["fifteen"] = (long long int)(onemin * 100);
}
}
}
}

View file

@ -0,0 +1,5 @@
#include <mist/json.h>
namespace Controller {
void checkCapable(JSON::Value & capa);
}

View file

@ -2,11 +2,35 @@
#include <mist/config.h> #include <mist/config.h>
#include <mist/procs.h> #include <mist/procs.h>
#include "controller_storage.h" #include "controller_storage.h"
#include "controller_connectors.h"
namespace Controller{ namespace Controller {
static std::map<std::string, std::string> current_connectors;
/// Checks if the binary mentioned in the protocol argument is currently active, if so, restarts it.
void UpdateProtocol(std::string protocol){
std::map<std::string, std::string>::iterator iter;
for (iter = current_connectors.begin(); iter != current_connectors.end(); iter++){
if (iter->second.substr(0, protocol.size()) == protocol){
Log("CONF", "Restarting connector for update: " + iter->second);
Util::Procs::Stop(iter->first);
int i = 0;
while (Util::Procs::isActive(iter->first) && i < 30){
Util::sleep(100);
}
if (i >= 30){
Log("WARN", "Connector still active 3 seconds after shutdown - delaying restart.");
}else{
Util::Procs::Start(iter->first, Util::getMyPath() + iter->second);
}
return;
}
}
}
/// Checks current protocol configuration, updates state of enabled connectors if neccesary.
void CheckProtocols(JSON::Value & p){ void CheckProtocols(JSON::Value & p){
static std::map<std::string, std::string> current_connectors;
std::map<std::string, std::string> new_connectors; std::map<std::string, std::string> new_connectors;
std::map<std::string, std::string>::iterator iter; std::map<std::string, std::string>::iterator iter;
bool haveHTTPgeneric = false; bool haveHTTPgeneric = false;
@ -16,35 +40,40 @@ namespace Controller{
JSON::Value counter = (long long int)0; JSON::Value counter = (long long int)0;
for (JSON::ArrIter ait = p.ArrBegin(); ait != p.ArrEnd(); ait++){ for (JSON::ArrIter ait = p.ArrBegin(); ait != p.ArrEnd(); ait++){
if (!(*ait).isMember("connector") || (*ait)["connector"].asString() == ""){continue;} if ( !( *ait).isMember("connector") || ( *ait)["connector"].asString() == ""){
continue;
tmp = std::string("MistConn") + (*ait)["connector"].asString() + std::string(" -n");
if ((*ait)["connector"].asString() == "HTTP"){haveHTTPgeneric = true;}
if ((*ait)["connector"].asString() != "HTTP" && (*ait)["connector"].asString().substr(0, 4) == "HTTP"){haveHTTPspecific = true;}
if ((*ait).isMember("port") && (*ait)["port"].asInt() != 0){
tmp += std::string(" -p ") + (*ait)["port"].asString();
}
if ((*ait).isMember("interface") && (*ait)["interface"].asString() != "" && (*ait)["interface"].asString() != "0.0.0.0"){
tmp += std::string(" -i ") + (*ait)["interface"].asString();
} }
if ((*ait).isMember("username") && (*ait)["username"].asString() != "" && (*ait)["username"].asString() != "root"){ tmp = std::string("MistConn") + ( *ait)["connector"].asString() + std::string(" -n");
tmp += std::string(" -u ") + (*ait)["username"].asString(); if (( *ait)["connector"].asString() == "HTTP"){
haveHTTPgeneric = true;
}
if (( *ait)["connector"].asString() != "HTTP" && ( *ait)["connector"].asString().substr(0, 4) == "HTTP"){
haveHTTPspecific = true;
} }
if ((*ait).isMember("args") && (*ait)["args"].asString() != ""){ if (( *ait).isMember("port") && ( *ait)["port"].asInt() != 0){
tmp += std::string(" ") + (*ait)["args"].asString(); tmp += std::string(" -p ") + ( *ait)["port"].asString();
} }
if (( *ait).isMember("interface") && ( *ait)["interface"].asString() != "" && ( *ait)["interface"].asString() != "0.0.0.0"){
tmp += std::string(" -i ") + ( *ait)["interface"].asString();
}
if (( *ait).isMember("username") && ( *ait)["username"].asString() != "" && ( *ait)["username"].asString() != "root"){
tmp += std::string(" -u ") + ( *ait)["username"].asString();
}
if (( *ait).isMember("args") && ( *ait)["args"].asString() != ""){
tmp += std::string(" ") + ( *ait)["args"].asString();
}
counter = counter.asInt() + 1; counter = counter.asInt() + 1;
new_connectors[std::string("Conn")+counter.asString()] = tmp; new_connectors[std::string("Conn") + counter.asString()] = tmp;
if (Util::Procs::isActive(std::string("Conn")+counter.asString())){ if (Util::Procs::isActive(std::string("Conn") + counter.asString())){
(*ait)["online"] = 1; ( *ait)["online"] = 1;
}else{ }else{
(*ait)["online"] = 0; ( *ait)["online"] = 0;
} }
} }
@ -67,7 +96,7 @@ namespace Controller{
if (haveHTTPgeneric && !haveHTTPspecific){ if (haveHTTPgeneric && !haveHTTPspecific){
Log("WARN", "HTTP Connector is enabled but no HTTP-based protocols are active!"); Log("WARN", "HTTP Connector is enabled but no HTTP-based protocols are active!");
} }
if (!haveHTTPgeneric && haveHTTPspecific){ if ( !haveHTTPgeneric && haveHTTPspecific){
Log("WARN", "HTTP-based protocols will not work without the generic HTTP connector!"); Log("WARN", "HTTP-based protocols will not work without the generic HTTP connector!");
} }
@ -75,5 +104,4 @@ namespace Controller{
current_connectors = new_connectors; current_connectors = new_connectors;
} }
} }

View file

@ -1,5 +1,11 @@
#include <mist/json.h>
namespace Controller {
namespace Controller{ /// Checks if the binary mentioned in the protocol argument is currently active, if so, restarts it.
void UpdateProtocol(std::string protocol);
/// Checks current protocol configuration, updates state of enabled connectors if neccesary.
void CheckProtocols(JSON::Value & p); void CheckProtocols(JSON::Value & p);
} }

View file

@ -1,9 +1,10 @@
#include <iostream> #include <iostream>
#include <fstream>
#include <mist/timing.h> #include <mist/timing.h>
#include "controller_storage.h" #include "controller_storage.h"
namespace Controller{ namespace Controller {
JSON::Value Storage; ///< Global storage of data. JSON::Value Storage; ///< Global storage of data.
/// Store and print a log message. /// Store and print a log message.
@ -11,15 +12,25 @@ namespace Controller{
//if last log message equals this one, do not log. //if last log message equals this one, do not log.
if (Storage["log"].size() > 0){ if (Storage["log"].size() > 0){
JSON::ArrIter it = Storage["log"].ArrEnd() - 1; JSON::ArrIter it = Storage["log"].ArrEnd() - 1;
if ((*it)[2] == message){return;} if (( *it)[2] == message){
return;
}
} }
JSON::Value m; JSON::Value m;
m.append(Util::epoch()); m.append(Util::epoch());
m.append(kind); m.append(kind);
m.append(message); m.append(message);
Storage["log"].append(m); Storage["log"].append(m);
Storage["log"].shrink(100);//limit to 100 log messages Storage["log"].shrink(100); //limit to 100 log messages
std::cout << "[" << kind << "] " << message << std::endl; std::cout << "[" << kind << "] " << message << std::endl;
} }
/// Write contents to Filename
void WriteFile(std::string Filename, std::string contents){
std::ofstream File;
File.open(Filename.c_str());
File << contents << std::endl;
File.close();
}
} }

View file

@ -1,11 +1,14 @@
#include <string> #include <string>
#include <mist/json.h> #include <mist/json.h>
namespace Controller{ namespace Controller {
extern JSON::Value Storage; ///< Global storage of data. extern JSON::Value Storage; ///< Global storage of data.
/// Store and print a log message. /// Store and print a log message.
void Log(std::string kind, std::string message); void Log(std::string kind, std::string message);
/// Write contents to Filename.
void WriteFile(std::string Filename, std::string contents);
} }

111
src/controller_streams.cpp Normal file
View file

@ -0,0 +1,111 @@
#include <mist/procs.h>
#include <mist/config.h>
#include <mist/timing.h>
#include "controller_streams.h"
#include "controller_storage.h"
#include <sys/stat.h>
namespace Controller {
std::map<std::string, int> lastBuffer; ///< Last moment of contact with all buffers.
bool streamsEqual(JSON::Value & one, JSON::Value & two){
if (one["channel"]["URL"] != two["channel"]["URL"]){
return false;
}
if (one["preset"]["cmd"] != two["preset"]["cmd"]){
return false;
}
return true;
}
void startStream(std::string name, JSON::Value & data){
std::string URL = data["channel"]["URL"];
std::string preset = data["preset"]["cmd"];
std::string cmd1, cmd2, cmd3;
if (URL.substr(0, 4) == "push"){
std::string pusher = URL.substr(7);
cmd2 = "MistBuffer -s " + name + " " + pusher;
Util::Procs::Start(name, Util::getMyPath() + cmd2);
Log("BUFF", "(re)starting stream buffer " + name + " for push data from " + pusher);
}else{
if (URL.substr(0, 1) == "/"){
struct stat fileinfo;
if (stat(URL.c_str(), &fileinfo) != 0 || S_ISDIR(fileinfo.st_mode)){
Log("BUFF", "Warning for VoD stream " + name + "! File not found: " + URL);
data["error"] = "Not found: " + URL;
return;
}
cmd1 = "cat " + URL;
data["error"] = "Available";
return; //MistPlayer handles VoD
}else{
cmd1 = "ffmpeg -re -async 2 -i " + URL + " " + preset + " -f flv -";
cmd2 = "MistFLV2DTSC";
}
cmd3 = "MistBuffer -s " + name;
if (cmd2 != ""){
Util::Procs::Start(name, cmd1, Util::getMyPath() + cmd2, Util::getMyPath() + cmd3);
Log("BUFF", "(re)starting stream buffer " + name + " for ffmpeg data: " + cmd1);
}else{
Util::Procs::Start(name, cmd1, Util::getMyPath() + cmd3);
Log("BUFF", "(re)starting stream buffer " + name + " using input file " + URL);
}
}
}
void CheckAllStreams(JSON::Value & data){
long long int currTime = Util::epoch();
for (JSON::ObjIter jit = data.ObjBegin(); jit != data.ObjEnd(); jit++){
if ( !Util::Procs::isActive(jit->first)){
startStream(jit->first, jit->second);
}
if (currTime - lastBuffer[jit->first] > 5){
if (jit->second.isMember("error") && jit->second["error"].asString() != ""){
jit->second["online"] = jit->second["error"];
}else{
jit->second["online"] = 0;
}
}else{
jit->second["online"] = 1;
}
}
static JSON::Value strlist;
bool changed = false;
if (strlist["config"] != Storage["config"]){
strlist["config"] = Storage["config"];
changed = true;
}
if (strlist["streams"] != Storage["streams"]){
strlist["streams"] = Storage["streams"];
changed = true;
}
if (changed){
WriteFile("/tmp/mist/streamlist", strlist.toString());
}
}
void CheckStreams(JSON::Value & in, JSON::Value & out){
bool changed = false;
for (JSON::ObjIter jit = in.ObjBegin(); jit != in.ObjEnd(); jit++){
if (out.isMember(jit->first)){
if ( !streamsEqual(jit->second, out[jit->first])){
Log("STRM", std::string("Updated stream ") + jit->first);
Util::Procs::Stop(jit->first);
startStream(jit->first, jit->second);
}
}else{
Log("STRM", std::string("New stream ") + jit->first);
startStream(jit->first, jit->second);
}
}
for (JSON::ObjIter jit = out.ObjBegin(); jit != out.ObjEnd(); jit++){
if ( !in.isMember(jit->first)){
Log("STRM", std::string("Deleted stream ") + jit->first);
Util::Procs::Stop(jit->first);
}
}
out = in;
}
} //Controller namespace

10
src/controller_streams.h Normal file
View file

@ -0,0 +1,10 @@
#include <mist/json.h>
namespace Controller {
extern std::map<std::string, int> lastBuffer; ///< Last moment of contact with all buffers.
bool streamsEqual(JSON::Value & one, JSON::Value & two);
void startStream(std::string name, JSON::Value & data);
void CheckAllStreams(JSON::Value & data);
void CheckStreams(JSON::Value & in, JSON::Value & out);
} //Controller namespace

View file

@ -16,20 +16,20 @@
#include <mist/config.h> #include <mist/config.h>
/// Holds all code that converts filetypes to/from DTSC. /// Holds all code that converts filetypes to/from DTSC.
namespace Converters{ namespace Converters {
/// Reads DTSC from STDIN, outputs FLV to STDOUT. /// Reads DTSC from STDIN, outputs FLV to STDOUT.
int DTSC2FLV() { int DTSC2FLV(){
FLV::Tag FLV_out; // Temporary storage for outgoing FLV data. FLV::Tag FLV_out; // Temporary storage for outgoing FLV data.
DTSC::Stream Strm; DTSC::Stream Strm;
std::string inBuffer; std::string inBuffer;
char charBuffer[1024*10]; char charBuffer[1024 * 10];
unsigned int charCount; unsigned int charCount;
bool doneheader = false; bool doneheader = false;
while (std::cin.good()){ while (std::cin.good()){
if (Strm.parsePacket(inBuffer)){ if (Strm.parsePacket(inBuffer)){
if (!doneheader){ if ( !doneheader){
doneheader = true; doneheader = true;
std::cout.write(FLV::Header, 13); std::cout.write(FLV::Header, 13);
FLV_out.DTSCMetaInit(Strm); FLV_out.DTSCMetaInit(Strm);
@ -47,22 +47,22 @@ namespace Converters{
std::cout.write(FLV_out.data, FLV_out.len); std::cout.write(FLV_out.data, FLV_out.len);
} }
}else{ }else{
std::cin.read(charBuffer, 1024*10); std::cin.read(charBuffer, 1024 * 10);
charCount = std::cin.gcount(); charCount = std::cin.gcount();
inBuffer.append(charBuffer, charCount); inBuffer.append(charBuffer, charCount);
} }
} }
std::cerr << "Done!" << std::endl; std::cerr << "Done!" << std::endl;
return 0;
}//FLV2DTSC
};//Converter namespace return 0;
} //FLV2DTSC
} //Converter namespace
/// Entry point for DTSC2FLV, simply calls Converters::DTSC2FLV(). /// Entry point for DTSC2FLV, simply calls Converters::DTSC2FLV().
int main(int argc, char ** argv){ int main(int argc, char ** argv){
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION); Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
return Converters::DTSC2FLV(); return Converters::DTSC2FLV();
}//main } //main

View file

@ -7,16 +7,16 @@
#include <mist/config.h> #include <mist/config.h>
/// Holds all code that converts filetypes to/from to DTSC. /// Holds all code that converts filetypes to/from to DTSC.
namespace Converters{ namespace Converters {
/// Reads an DTSC file and attempts to fix the metadata in it. /// Reads an DTSC file and attempts to fix the metadata in it.
int DTSCFix(Util::Config & conf) { int DTSCFix(Util::Config & conf){
DTSC::File F(conf.getString("filename")); DTSC::File F(conf.getString("filename"));
JSON::Value oriheader = F.getMeta(); JSON::Value oriheader = F.getMeta();
JSON::Value meta = oriheader; JSON::Value meta = oriheader;
JSON::Value pack; JSON::Value pack;
if (!oriheader.isMember("moreheader")){ if ( !oriheader.isMember("moreheader")){
std::cerr << "This file is not DTSCFix'able. Please re-convert and try again." << std::endl; std::cerr << "This file is not DTSCFix'able. Please re-convert and try again." << std::endl;
return 1; return 1;
} }
@ -46,14 +46,20 @@ namespace Converters{
long long unsigned int bps = 0; long long unsigned int bps = 0;
F.seekNext(); F.seekNext();
while (!F.getJSON().isNull()){ while ( !F.getJSON().isNull()){
nowpack = F.getJSON()["time"].asInt(); nowpack = F.getJSON()["time"].asInt();
if (firstpack == 0){firstpack = nowpack;} if (firstpack == 0){
firstpack = nowpack;
}
if (F.getJSON()["datatype"].asString() == "audio"){ if (F.getJSON()["datatype"].asString() == "audio"){
if (lastaudio != 0 && (nowpack - lastaudio) != 0){ if (lastaudio != 0 && (nowpack - lastaudio) != 0){
bps = F.getJSON()["data"].asString().size() / (nowpack - lastaudio); bps = F.getJSON()["data"].asString().size() / (nowpack - lastaudio);
if (bps < aud_min){aud_min = bps;} if (bps < aud_min){
if (bps > aud_max){aud_max = bps;} aud_min = bps;
}
if (bps > aud_max){
aud_max = bps;
}
} }
totalaudio += F.getJSON()["data"].asString().size(); totalaudio += F.getJSON()["data"].asString().size();
lastaudio = nowpack; lastaudio = nowpack;
@ -61,24 +67,36 @@ namespace Converters{
if (F.getJSON()["datatype"].asString() == "video"){ if (F.getJSON()["datatype"].asString() == "video"){
if (lastvideo != 0 && (nowpack - lastvideo) != 0){ if (lastvideo != 0 && (nowpack - lastvideo) != 0){
bps = F.getJSON()["data"].asString().size() / (nowpack - lastvideo); bps = F.getJSON()["data"].asString().size() / (nowpack - lastvideo);
if (bps < vid_min){vid_min = bps;} if (bps < vid_min){
if (bps > vid_max){vid_max = bps;} vid_min = bps;
}
if (bps > vid_max){
vid_max = bps;
}
} }
if (F.getJSON()["keyframe"].asInt() != 0){ if (F.getJSON()["keyframe"].asInt() != 0){
meta["keytime"].append(F.getJSON()["time"]); meta["keytime"].append(F.getJSON()["time"]);
meta["keybpos"].append(F.getLastReadPos()); meta["keybpos"].append(F.getLastReadPos());
if (lastkey != 0){ if (lastkey != 0){
bps = nowpack - lastkey; bps = nowpack - lastkey;
if (bps < key_min){key_min = bps;} if (bps < key_min){
if (bps > key_max){key_max = bps;} key_min = bps;
}
if (bps > key_max){
key_max = bps;
}
} }
keyframes++; keyframes++;
lastkey = nowpack; lastkey = nowpack;
} }
if (F.getJSON()["offset"].asInt() != 0){ if (F.getJSON()["offset"].asInt() != 0){
bps = F.getJSON()["offset"].asInt(); bps = F.getJSON()["offset"].asInt();
if (bps < bfrm_min){bfrm_min = bps;} if (bps < bfrm_min){
if (bps > bfrm_max){bfrm_max = bps;} bfrm_min = bps;
}
if (bps > bfrm_max){
bfrm_max = bps;
}
} }
totalvideo += F.getJSON()["data"].asString().size(); totalvideo += F.getJSON()["data"].asString().size();
lastvideo = nowpack; lastvideo = nowpack;
@ -86,7 +104,7 @@ namespace Converters{
F.seekNext(); F.seekNext();
} }
meta["length"] = (long long int)((nowpack - firstpack)/1000); meta["length"] = (long long int)((nowpack - firstpack) / 1000);
meta["lastms"] = (long long int)nowpack; meta["lastms"] = (long long int)nowpack;
if (meta.isMember("audio")){ if (meta.isMember("audio")){
meta["audio"]["bps"] = (long long int)(totalaudio / ((lastaudio - firstpack) / 1000)); meta["audio"]["bps"] = (long long int)(totalaudio / ((lastaudio - firstpack) / 1000));
@ -104,7 +122,7 @@ namespace Converters{
std::cerr << "Appending new header..." << std::endl; std::cerr << "Appending new header..." << std::endl;
std::string loader = meta.toPacked(); std::string loader = meta.toPacked();
long long int newHPos = F.addHeader(loader); long long int newHPos = F.addHeader(loader);
if (!newHPos){ if ( !newHPos){
std::cerr << "Failure appending new header. Cancelling." << std::endl; std::cerr << "Failure appending new header. Cancelling." << std::endl;
return 1; return 1;
} }
@ -117,9 +135,9 @@ namespace Converters{
}else{ }else{
return -1; return -1;
} }
}//DTSCFix } //DTSCFix
}; }
/// Entry point for FLV2DTSC, simply calls Converters::FLV2DTSC(). /// Entry point for FLV2DTSC, simply calls Converters::FLV2DTSC().
int main(int argc, char ** argv){ int main(int argc, char ** argv){
@ -127,4 +145,4 @@ int main(int argc, char ** argv){
conf.addOption("filename", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Filename of the file to attempt to fix.\"}")); conf.addOption("filename", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Filename of the file to attempt to fix.\"}"));
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
return Converters::DTSCFix(conf); return Converters::DTSCFix(conf);
}//main } //main

View file

@ -17,65 +17,67 @@
#include <mist/config.h> #include <mist/config.h>
/// Holds all code that converts filetypes to/from to DTSC. /// Holds all code that converts filetypes to/from to DTSC.
namespace Converters{ namespace Converters {
/// Reads FLV from STDIN, outputs DTSC to STDOUT. /// Reads FLV from STDIN, outputs DTSC to STDOUT.
int FLV2DTSC() { int FLV2DTSC(){
FLV::Tag FLV_in; // Temporary storage for incoming FLV data. FLV::Tag FLV_in; // Temporary storage for incoming FLV data.
JSON::Value meta_out; // Storage for outgoing header data. JSON::Value meta_out; // Storage for outgoing header data.
JSON::Value pack_out; // Storage for outgoing data. JSON::Value pack_out; // Storage for outgoing data.
std::stringstream prebuffer; // Temporary buffer before sending real data std::stringstream prebuffer; // Temporary buffer before sending real data
bool sending = false; bool sending = false;
unsigned int counter = 0; unsigned int counter = 0;
while (!feof(stdin)){ while ( !feof(stdin)){
if (FLV_in.FileLoader(stdin)){ if (FLV_in.FileLoader(stdin)){
pack_out = FLV_in.toJSON(meta_out); pack_out = FLV_in.toJSON(meta_out);
if (pack_out.isNull()){continue;} if (pack_out.isNull()){
if (!sending){ continue;
}
if ( !sending){
counter++; counter++;
if (counter > 8){ if (counter > 8){
sending = true; sending = true;
meta_out["moreheader"] = 0LL; meta_out["moreheader"] = 0LL;
std::string packed_header = meta_out.toPacked(); std::string packed_header = meta_out.toPacked();
unsigned int size = htonl(packed_header.size()); unsigned int size = htonl(packed_header.size());
std::cout << std::string(DTSC::Magic_Header, 4) << std::string((char*)&size, 4) << packed_header; std::cout << std::string(DTSC::Magic_Header, 4) << std::string((char*) &size, 4) << packed_header;
std::cout << prebuffer.rdbuf(); std::cout << prebuffer.rdbuf();
prebuffer.str(""); prebuffer.str("");
std::cerr << "Buffer done, starting real-time output..." << std::endl; std::cerr << "Buffer done, starting real-time output..." << std::endl;
}else{ }else{
std::string packed_out = pack_out.toPacked(); std::string packed_out = pack_out.toPacked();
unsigned int size = htonl(packed_out.size()); unsigned int size = htonl(packed_out.size());
prebuffer << std::string(DTSC::Magic_Packet, 4) << std::string((char*)&size, 4) << packed_out; prebuffer << std::string(DTSC::Magic_Packet, 4) << std::string((char*) &size, 4) << packed_out;
continue;//don't also write continue; //don't also write
} }
} }
//simply write //simply write
std::string packed_out = pack_out.toPacked(); std::string packed_out = pack_out.toPacked();
unsigned int size = htonl(packed_out.size()); unsigned int size = htonl(packed_out.size());
std::cout << std::string(DTSC::Magic_Packet, 4) << std::string((char*)&size, 4) << packed_out; std::cout << std::string(DTSC::Magic_Packet, 4) << std::string((char*) &size, 4) << packed_out;
} }
} }
// if the FLV input is very short, do output it correctly... // if the FLV input is very short, do output it correctly...
if (!sending){ if ( !sending){
std::cerr << "EOF - outputting buffer..." << std::endl; std::cerr << "EOF - outputting buffer..." << std::endl;
meta_out["moreheader"] = 0LL; meta_out["moreheader"] = 0LL;
std::string packed_header = meta_out.toPacked(); std::string packed_header = meta_out.toPacked();
unsigned int size = htonl(packed_header.size()); unsigned int size = htonl(packed_header.size());
std::cout << std::string(DTSC::Magic_Header, 4) << std::string((char*)&size, 4) << packed_header; std::cout << std::string(DTSC::Magic_Header, 4) << std::string((char*) &size, 4) << packed_header;
std::cout << prebuffer.rdbuf(); std::cout << prebuffer.rdbuf();
} }
std::cerr << "Done! If you output this data to a file, don't forget to run MistDTSCFix next." << std::endl; std::cerr << "Done! If you output this data to a file, don't forget to run MistDTSCFix next." << std::endl;
return 0;
}//FLV2DTSC
};//Buffer namespace return 0;
} //FLV2DTSC
}
/// Entry point for FLV2DTSC, simply calls Converters::FLV2DTSC(). /// Entry point for FLV2DTSC, simply calls Converters::FLV2DTSC().
int main(int argc, char ** argv){ int main(int argc, char ** argv){
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION); Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
conf.parseArgs(argc, argv); conf.parseArgs(argc, argv);
return Converters::FLV2DTSC(); return Converters::FLV2DTSC();
}//main } //main

View file

@ -32,32 +32,35 @@ class Stats{
std::string connector; std::string connector;
unsigned int conntime; unsigned int conntime;
Stats(){ Stats(){
up = 0; down = 0; conntime = 0; up = 0;
}; down = 0;
conntime = 0;
}
;
/// Reads a stats string and parses it to the internal representation. /// Reads a stats string and parses it to the internal representation.
Stats(std::string s){ Stats(std::string s){
size_t f = s.find(' '); size_t f = s.find(' ');
if (f != std::string::npos){ if (f != std::string::npos){
host = s.substr(0, f); host = s.substr(0, f);
s.erase(0, f+1); s.erase(0, f + 1);
} }
f = s.find(' '); f = s.find(' ');
if (f != std::string::npos){ if (f != std::string::npos){
connector = s.substr(0, f); connector = s.substr(0, f);
s.erase(0, f+1); s.erase(0, f + 1);
} }
f = s.find(' '); f = s.find(' ');
if (f != std::string::npos){ if (f != std::string::npos){
conntime = atoi(s.substr(0, f).c_str()); conntime = atoi(s.substr(0, f).c_str());
s.erase(0, f+1); s.erase(0, f + 1);
} }
f = s.find(' '); f = s.find(' ');
if (f != std::string::npos){ if (f != std::string::npos){
up = atoi(s.substr(0, f).c_str()); up = atoi(s.substr(0, f).c_str());
s.erase(0, f+1); s.erase(0, f + 1);
down = atoi(s.c_str()); down = atoi(s.c_str());
} }
}; }
}; };
int main(int argc, char** argv){ int main(int argc, char** argv){
@ -75,7 +78,7 @@ int main(int argc, char** argv){
pausemark["time"] = (long long int)0; pausemark["time"] = (long long int)0;
Socket::Connection StatsSocket = Socket::Connection("/tmp/mist/statistics", true); Socket::Connection StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
int lasttime = Util::epoch();//time last packet was sent int lasttime = Util::epoch(); //time last packet was sent
//send the header //send the header
std::string meta_str = meta.toNetPacked(); std::string meta_str = meta.toNetPacked();
@ -87,8 +90,8 @@ int main(int argc, char** argv){
JSON::Value last_pack; JSON::Value last_pack;
bool meta_sent = false; bool meta_sent = false;
long long now, lastTime = 0;//for timing of sending packets long long now, lastTime = 0; //for timing of sending packets
long long bench = 0;//for benchmarking long long bench = 0; //for benchmarking
Stats sts; Stats sts;
CYG_DEFI CYG_DEFI
@ -97,21 +100,22 @@ int main(int argc, char** argv){
if (CYG_LOOP in_out.spool()){ if (CYG_LOOP in_out.spool()){
while (in_out.Received().size()){ while (in_out.Received().size()){
//delete anything that doesn't end with a newline //delete anything that doesn't end with a newline
if (*(in_out.Received().get().rbegin()) != '\n'){ if ( *(in_out.Received().get().rbegin()) != '\n'){
in_out.Received().get().clear(); in_out.Received().get().clear();
continue; continue;
} }
in_out.Received().get().resize(in_out.Received().get().size() - 1); in_out.Received().get().resize(in_out.Received().get().size() - 1);
if (!in_out.Received().get().empty()){ if ( !in_out.Received().get().empty()){
switch (in_out.Received().get()[0]){ switch (in_out.Received().get()[0]){
case 'P':{ //Push case 'P': { //Push
#if DEBUG >= 4 #if DEBUG >= 4
std::cerr << "Received push - ignoring (" << in_out.Received().get() << ")" << std::endl; std::cerr << "Received push - ignoring (" << in_out.Received().get() << ")" << std::endl;
#endif #endif
in_out.close();//pushing to VoD makes no sense in_out.close(); //pushing to VoD makes no sense
} break; }
case 'S':{ //Stats break;
if (!StatsSocket.connected()){ case 'S': { //Stats
if ( !StatsSocket.connected()){
StatsSocket = Socket::Connection("/tmp/mist/statistics", true); StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
} }
if (StatsSocket.connected()){ if (StatsSocket.connected()){
@ -125,7 +129,7 @@ int main(int argc, char** argv){
json_sts["vod"]["filename"] = conf.getString("filename"); json_sts["vod"]["filename"] = conf.getString("filename");
json_sts["vod"]["now"] = Util::epoch(); json_sts["vod"]["now"] = Util::epoch();
json_sts["vod"]["start"] = Util::epoch() - sts.conntime; json_sts["vod"]["start"] = Util::epoch() - sts.conntime;
if (!meta_sent){ if ( !meta_sent){
json_sts["vod"]["meta"] = meta; json_sts["vod"]["meta"] = meta;
json_sts["vod"]["meta"]["audio"].removeMember("init"); json_sts["vod"]["meta"]["audio"].removeMember("init");
json_sts["vod"]["meta"]["video"].removeMember("init"); json_sts["vod"]["meta"]["video"].removeMember("init");
@ -137,34 +141,42 @@ int main(int argc, char** argv){
StatsSocket.Send("\n\n"); StatsSocket.Send("\n\n");
StatsSocket.flush(); StatsSocket.flush();
} }
} break; }
case 's':{ //second-seek break;
case 's': { //second-seek
int ms = JSON::Value(in_out.Received().get().substr(2)).asInt(); int ms = JSON::Value(in_out.Received().get().substr(2)).asInt();
bool ret = source.seek_time(ms); bool ret = source.seek_time(ms);
lastTime = 0; lastTime = 0;
} break; }
case 'f':{ //frame-seek break;
case 'f': { //frame-seek
bool ret = source.seek_frame(JSON::Value(in_out.Received().get().substr(2)).asInt()); bool ret = source.seek_frame(JSON::Value(in_out.Received().get().substr(2)).asInt());
lastTime = 0; lastTime = 0;
} break; }
case 'p':{ //play break;
case 'p': { //play
playing = -1; playing = -1;
lastTime = 0; lastTime = 0;
in_out.setBlocking(false); in_out.setBlocking(false);
} break; }
case 'o':{ //once-play break;
if (playing <= 0){playing = 1;} case 'o': { //once-play
if (playing <= 0){
playing = 1;
}
++playing; ++playing;
in_out.setBlocking(false); in_out.setBlocking(false);
#if DEBUG >= 4 #if DEBUG >= 4
std::cerr << "Playing one keyframe" << std::endl; std::cerr << "Playing one keyframe" << std::endl;
#endif #endif
bench = Util::getMS(); bench = Util::getMS();
} break; }
case 'q':{ //quit-playing break;
case 'q': { //quit-playing
playing = 0; playing = 0;
in_out.setBlocking(true); in_out.setBlocking(true);
} break; }
break;
} }
in_out.Received().get().clear(); in_out.Received().get().clear();
} }
@ -172,44 +184,48 @@ int main(int argc, char** argv){
} }
if (playing != 0){ if (playing != 0){
now = Util::getMS(); now = Util::getMS();
source.seekNext(); source.seekNext();
if (!source.getJSON()){playing = 0;} if ( !source.getJSON()){
if (source.getJSON().isMember("keyframe")){ playing = 0;
if (playing == -1 && meta["video"]["keyms"].asInt() > now-lastTime) { }
Util::sleep(meta["video"]["keyms"].asInt()-(now-lastTime)); if (source.getJSON().isMember("keyframe")){
} if (playing == -1 && meta["video"]["keyms"].asInt() > now - lastTime){
lastTime = now; Util::sleep(meta["video"]["keyms"].asInt() - (now - lastTime));
if (playing > 0){--playing;}
} }
if (playing == 0){ lastTime = now;
#if DEBUG >= 4 if (playing > 0){
std::cerr << "Sending pause_marker (" << (Util::getMS() - bench) << "ms)" << std::endl; --playing;
#endif
pausemark["time"] = source.getJSON()["time"];
pausemark.toPacked();
in_out.SendNow(pausemark.toNetPacked());
in_out.setBlocking(true);
}else{
lasttime = Util::epoch();
//insert proper header for this type of data
in_out.Send("DTPD");
//insert the packet length
unsigned int size = htonl(source.getPacket().size());
in_out.Send((char*)&size, 4);
in_out.SendNow(source.getPacket());
} }
}
if (playing == 0){
#if DEBUG >= 4
std::cerr << "Sending pause_marker (" << (Util::getMS() - bench) << "ms)" << std::endl;
#endif
pausemark["time"] = source.getJSON()["time"];
pausemark.toPacked();
in_out.SendNow(pausemark.toNetPacked());
in_out.setBlocking(true);
}else{
lasttime = Util::epoch();
//insert proper header for this type of data
in_out.Send("DTPD");
//insert the packet length
unsigned int size = htonl(source.getPacket().size());
in_out.Send((char*) &size, 4);
in_out.SendNow(source.getPacket());
}
}else{ }else{
Util::sleep(10); Util::sleep(10);
} }
} }
StatsSocket.close(); StatsSocket.close();
in_out.close(); in_out.close();
#if DEBUG >= 4 #if DEBUG >= 4
if (Util::epoch() - lasttime < 60){ if (Util::epoch() - lasttime < 60){
std::cerr << "Player exited (disconnect)." << std::endl; std::cerr << "Player exited (disconnect)." << std::endl;
}else{ }else{
std::cerr << "Player exited (timeout)." << std::endl; std::cerr << "Player exited (timeout)." << std::endl;
} }
#endif #endif
return 0; return 0;
} }