LTS Commits
This commit is contained in:
parent
f24d97b510
commit
4bdbd82f66
72 changed files with 8245 additions and 105 deletions
|
|
@ -96,6 +96,7 @@ namespace Info {
|
|||
fileSpecs["tracks"][trackIt->first].removeMember("keys");
|
||||
fileSpecs["tracks"][trackIt->first].removeMember("keysizes");
|
||||
fileSpecs["tracks"][trackIt->first].removeMember("parts");
|
||||
fileSpecs["tracks"][trackIt->first].removeMember("ivecs");/*LTS*/
|
||||
}
|
||||
}
|
||||
printf( "%s", fileSpecs.toString().c_str() );
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include <string.h>
|
||||
#include <mist/mp4.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
///\brief Holds everything unique to the analysers.
|
||||
namespace Analysers {
|
||||
|
|
@ -25,9 +26,15 @@ namespace Analysers {
|
|||
mp4Buffer.erase(mp4Buffer.size() - 1, 1);
|
||||
|
||||
MP4::Box mp4Data;
|
||||
int dataSize = mp4Buffer.size();
|
||||
int curPos = 0;
|
||||
while (mp4Data.read(mp4Buffer)){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos);
|
||||
std::cerr << mp4Data.toPrettyString(0) << std::endl;
|
||||
curPos += dataSize - mp4Buffer.size();
|
||||
dataSize = mp4Buffer.size();
|
||||
}
|
||||
DEBUG_MSG(DLVL_DEVEL, "Stopped parsing at position %d", curPos);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
209
src/analysers/rtp_analyser.cpp
Normal file
209
src/analysers/rtp_analyser.cpp
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/rtp.h>
|
||||
#include <mist/http_parser.h>
|
||||
|
||||
//rtsp://krabs:1935/vod/gear1.mp4
|
||||
|
||||
namespace Analysers {
|
||||
int analyseRTP(){
|
||||
Socket::Connection conn("localhost", 554, true);
|
||||
//Socket::Connection conn("krabs", 1935, true);
|
||||
HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender.
|
||||
int step = 0;
|
||||
/*1 = sent describe
|
||||
2 = recd describe
|
||||
3 = sent setup
|
||||
4 = received setup
|
||||
5 = sent play"*/
|
||||
std::vector<std::string> tracks;
|
||||
std::vector<Socket::UDPConnection> connections;
|
||||
unsigned int trackIt = 0;
|
||||
while (conn.connected()){
|
||||
// std::cerr << "loopy" << std::endl;
|
||||
if(step == 0){
|
||||
HTTP_S.protocol = "RTSP/1.0";
|
||||
HTTP_S.method = "DESCRIBE";
|
||||
//rtsp://krabs:1935/vod/gear1.mp4
|
||||
//rtsp://localhost/g1
|
||||
HTTP_S.url = "rtsp://localhost/steers";
|
||||
//HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4";
|
||||
HTTP_S.SetHeader("CSeq",1);
|
||||
HTTP_S.SendRequest(conn);
|
||||
step++;
|
||||
|
||||
}else if(step == 2){
|
||||
std::cerr <<"setup " << tracks[trackIt] << std::endl;
|
||||
HTTP_S.method = "SETUP";
|
||||
HTTP_S.url = "rtsp://localhost/steers/" + tracks[trackIt];
|
||||
//HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4/" + tracks[trackIt];
|
||||
HTTP_S.SetHeader("CSeq",2+trackIt);
|
||||
std::stringstream ss;
|
||||
ss << "RTP/steersVP;unicast;client_port="<< 20000 + 2*trackIt<<"-"<< 20001 + 2*trackIt;
|
||||
HTTP_S.SetHeader("Transport",ss.str());//make client ports, 4200 + 2*offset
|
||||
trackIt++;
|
||||
step++;
|
||||
HTTP_S.SendRequest(conn);
|
||||
std::cerr << "step " << step << "/\\"<< ss.str()<<std::endl;
|
||||
}else if(step == 4){
|
||||
std::cerr << "Play!!!1" << std::endl;
|
||||
HTTP_S.method = "PLAY";
|
||||
HTTP_S.url = "rtsp://localhost/steers";
|
||||
//HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4";
|
||||
HTTP_S.SetHeader("Range","npt=0.000-");
|
||||
HTTP_S.SendRequest(conn);
|
||||
step++;
|
||||
std::cerr << "step for play.." << step << std::endl;
|
||||
}
|
||||
|
||||
if (conn.Received().size() || conn.spool()){
|
||||
if (HTTP_R.Read(conn)){
|
||||
if(step == 1){
|
||||
std::cerr << "recvd desc" << std::endl;
|
||||
for(size_t ml = HTTP_R.body.find("a=control:",HTTP_R.body.find("m=")); ml != std::string::npos; ml = HTTP_R.body.find("a=control:",ml+1)){
|
||||
std::cerr << "found trekk" << std::endl;
|
||||
tracks.push_back(HTTP_R.body.substr(ml+10,HTTP_R.body.find_first_of("\r\n",ml)-(ml+10)));
|
||||
connections.push_back(Socket::UDPConnection());
|
||||
}
|
||||
for(unsigned int x = 0; x < connections.size();x++){
|
||||
connections[x].SetDestination("127.0.0.1",666);
|
||||
connections[x].bind(20000+2*x);
|
||||
connections[x].setBlocking(true);
|
||||
}
|
||||
step++;
|
||||
}else if(step == 3){
|
||||
std::cerr << "recvd setup" << std::endl;
|
||||
std::cerr << "trackIt: " << trackIt << " size " << tracks.size() << std::endl;
|
||||
if(trackIt < tracks.size())
|
||||
step--;
|
||||
else
|
||||
step++;
|
||||
std::cerr << HTTP_R.GetHeader("Transport");
|
||||
}
|
||||
HTTP_R.Clean();
|
||||
}
|
||||
}//!
|
||||
if(step == 5){
|
||||
|
||||
for(unsigned int cx = 0; cx < connections.size(); cx++){
|
||||
// std::cerr <<"PLAY MF" << std::endl;
|
||||
if(connections[cx].Receive()){
|
||||
RTP::Packet* pakketje = new RTP::Packet(connections[cx].data, connections[cx].data_len);
|
||||
/*std::cout << "Version = " << pakketje->getVersion() << std::endl;
|
||||
std::cout << "Padding = " << pakketje->getPadding() << std::endl;
|
||||
std::cout << "Extension = " << pakketje->getExtension() << std::endl;
|
||||
std::cout << "Contributing sources = " << pakketje->getContribCount() << std::endl;
|
||||
std::cout << "Marker = " << pakketje->getMarker() << std::endl;
|
||||
std::cout << "Payload Type = " << pakketje->getPayloadType() << std::endl;
|
||||
std::cout << "Sequence = " << pakketje->getSequence() << std::endl;
|
||||
std::cout << "Timestamp = " << pakketje->getTimeStamp() << std::endl;
|
||||
std::cout << "SSRC = " << pakketje->getSSRC() << std::endl;
|
||||
std::cout << "datalen: " << connections[cx].data_len << std::endl;
|
||||
std::cout << "payload:" << std::endl;*/
|
||||
|
||||
if(pakketje->getPayloadType() == 97){
|
||||
int h264type = (int)(connections[cx].data[12] & 0x1f);
|
||||
std::cout << h264type << " - ";
|
||||
if(h264type == 0){
|
||||
std::cout << "unspecified - ";
|
||||
}else if(h264type == 1){
|
||||
std::cout << "Coded slice of a non-IDR picture - ";
|
||||
}else if(h264type == 2){
|
||||
std::cout << "Coded slice data partition A - ";
|
||||
}else if(h264type == 3){
|
||||
std::cout << "Coded slice data partition B - ";
|
||||
}else if(h264type == 4){
|
||||
std::cout << "Coded slice data partition C - ";
|
||||
}else if(h264type == 5){
|
||||
std::cout << "Coded slice of an IDR picture - ";
|
||||
}else if(h264type == 6){
|
||||
std::cout << "Supplemental enhancement information (SEI) - ";
|
||||
}else if(h264type == 7){
|
||||
std::cout << "Sequence parameter set - ";
|
||||
}else if(h264type == 8){
|
||||
std::cout << "Picture parameter set - ";
|
||||
}else if(h264type == 9){
|
||||
std::cout << "Access unit delimiter - ";
|
||||
}else if(h264type == 10){
|
||||
std::cout << "End of sequence - ";
|
||||
}else if(h264type == 11){
|
||||
std::cout << "End of stream - ";
|
||||
}else if(h264type == 12){
|
||||
std::cout << "Filler data - ";
|
||||
}else if(h264type == 13){
|
||||
std::cout << "Sequence parameter set extension - ";
|
||||
}else if(h264type == 14){
|
||||
std::cout << "Prefix NAL unit - ";
|
||||
}else if(h264type == 15){
|
||||
std::cout << "Subset sequence parameter set - ";
|
||||
}else if(h264type == 16){
|
||||
std::cout << "Reserved - ";
|
||||
}else if(h264type == 17){
|
||||
std::cout << "Reserved - ";
|
||||
}else if(h264type == 18){
|
||||
std::cout << "Reserved - ";
|
||||
}else if(h264type == 19){
|
||||
std::cout << "Coded slice of an auxiliary coded picture without partitioning - ";
|
||||
}else if(h264type == 20){
|
||||
std::cout << "Coded slice extension - ";
|
||||
}else if(h264type == 21){
|
||||
std::cout << "Reserved - ";
|
||||
}else if(h264type == 22){
|
||||
std::cout << "Reserved - ";
|
||||
}else if(h264type == 23){
|
||||
std::cout << "Reserved - ";
|
||||
}else if(h264type == 24){
|
||||
std::cout << "stap a - ";
|
||||
}else if(h264type == 25){
|
||||
std::cout << "stap b - ";
|
||||
}else if(h264type == 26){
|
||||
std::cout << "mtap16 - ";
|
||||
}else if(h264type == 27){
|
||||
std::cout << "mtap24 - ";
|
||||
}else if(h264type == 28){
|
||||
std::cout << "fu a - ";
|
||||
}else if(h264type == 29){
|
||||
std::cout << "fu b - ";
|
||||
}else if(h264type == 30){
|
||||
std::cout << "Unspecified - ";
|
||||
}else if(h264type == 31){
|
||||
std::cout << "Unspecified - ";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
for(unsigned int i = 13 ; i < connections[cx].data_len;i++){
|
||||
std::cout << std::hex <<std::setw(2) << std::setfill('0') << (int)connections[cx].data[i]<< std::dec;
|
||||
}
|
||||
std::cout << std::endl<<std::endl;
|
||||
}
|
||||
delete pakketje;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 666;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
||||
conf.parseArgs(argc, argv);
|
||||
return Analysers::analyseRTP();
|
||||
}
|
||||
197
src/analysers/rtsp_rtp_analyser.cpp
Normal file
197
src/analysers/rtsp_rtp_analyser.cpp
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#include <mist/config.h>
|
||||
#include <mist/rtp.h>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace RtspRtp{
|
||||
|
||||
int analyseRtspRtp(std::string rtspUrl){
|
||||
/*//parse hostname
|
||||
std::string hostname = rtspUrl.substr(7);
|
||||
hostname = hostname.substr(0,hostname.find('/'));
|
||||
std::cout << hostname << std::endl;
|
||||
HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender.
|
||||
Socket::Connection conn(hostname,554,false);//setting rtsp connection
|
||||
|
||||
bool optionsSent = false;
|
||||
bool optionsRecvd = false;
|
||||
bool descSent = false;
|
||||
bool descRecvd = false;
|
||||
bool setupComplete = false;
|
||||
bool playSent = false;
|
||||
int CSeq = 1;
|
||||
while(conn.connected()){
|
||||
if(!optionsSent){
|
||||
HTTP_R.protocol="RTSP/1.0";
|
||||
HTTP_R.method = "OPTIONS";
|
||||
HTTP_R.url = rtspUrl;
|
||||
HTTP_R.SetHeader("CSeq",CSeq);
|
||||
CSeq++;
|
||||
HTTP_R.SetHeader("User-Agent","mistANALyser");
|
||||
HTTP_R.SendRequest(conn);
|
||||
optionsSent = true;
|
||||
}
|
||||
|
||||
if (optionsSent&& !optionsRecvd && (conn.Received().size() || conn.spool() )){
|
||||
if(HTTP_S.Read(conn)){
|
||||
std::cout << "recv opts" << std::endl;
|
||||
|
||||
std::cout << HTTP_S.BuildResponse(HTTP_S.method,HTTP_S.url);
|
||||
optionsRecvd = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(optionsRecvd && !descSent){
|
||||
HTTP_S.Clean();
|
||||
HTTP_R.protocol="RTSP/1.0";
|
||||
HTTP_R.method = "DESCRIBE";
|
||||
HTTP_R.url = rtspUrl;
|
||||
HTTP_R.SetHeader("CSeq",CSeq);
|
||||
CSeq++;
|
||||
HTTP_R.SetHeader("User-Agent","mistANALyser");
|
||||
HTTP_R.SendRequest(conn);
|
||||
descSent = true;
|
||||
|
||||
}
|
||||
|
||||
std::vector<std::string> trackIds;
|
||||
|
||||
if (descSent&&!descRecvd && (conn.Received().size() || conn.spool() )){
|
||||
|
||||
if(HTTP_S.Read(conn)){
|
||||
std::cout << "recv desc2" << std::endl;
|
||||
std::cout << HTTP_S.BuildResponse(HTTP_S.method,HTTP_S.url);
|
||||
size_t pos = HTTP_S.body.find("m=");
|
||||
do{
|
||||
//finding all track IDs
|
||||
pos = HTTP_S.body.find("a=control:",pos);
|
||||
if(pos !=std::string::npos){
|
||||
trackIds.push_back(HTTP_S.body.substr(pos+10,HTTP_S.body.find("\r\n",pos)-pos-10 ) );//setting track IDs;
|
||||
pos++;
|
||||
}
|
||||
}while(pos != std::string::npos);
|
||||
//we have all the tracks
|
||||
|
||||
descRecvd = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unsigned int setupsSent = 0;
|
||||
unsigned int setupsRecvd = 0;
|
||||
Socket::UDPConnection connectors[trackIds.size()];
|
||||
unsigned int setports[trackIds.size()];
|
||||
uint32_t bport = 10000;
|
||||
std::string sessionID = "";
|
||||
|
||||
std::stringstream setup;
|
||||
|
||||
if(descRecvd && !setupComplete){
|
||||
//time to setup.
|
||||
for(std::vector<std::string>::iterator it = trackIds.begin();it!=trackIds.end();it++){
|
||||
std::cout << "setup " << setupsSent<< std::endl;
|
||||
while(!connectors[setupsSent].SetConnection( bport,false) ){
|
||||
bport +=2;//finding an available port
|
||||
}
|
||||
std::cout << "setup" << bport<< std::endl;
|
||||
setports[setupsSent] = bport;
|
||||
bport +=2;
|
||||
if(setupsSent == setupsRecvd){
|
||||
//send only one setup
|
||||
HTTP_S.Clean();
|
||||
HTTP_R.protocol="RTSP/1.0";
|
||||
HTTP_R.method = "SETUP";
|
||||
HTTP_R.url = rtspUrl+ '/' + *(it);
|
||||
setup << "RTP/AVP/UDP;unicast;client_port="<< setports[setupsSent] <<"-" <<setports[setupsSent]+1 ;
|
||||
HTTP_R.SetHeader("Transport",setup.str() );
|
||||
std:: cout << setup.str()<<std::endl;
|
||||
setup.str(std::string());
|
||||
setup.clear();
|
||||
HTTP_R.SetHeader("CSeq",CSeq);
|
||||
CSeq++;
|
||||
if(sessionID != ""){
|
||||
HTTP_R.SetHeader("Session",sessionID);
|
||||
}
|
||||
HTTP_R.SetHeader("User-Agent","mistANALyser");
|
||||
HTTP_R.SendRequest(conn);
|
||||
setupsSent ++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
while(setupsSent == setupsRecvd+1){
|
||||
//lets Assume we assume we always receive a response
|
||||
if ( (conn.Received().size() || conn.spool() )){
|
||||
if(HTTP_S.Read(conn)){
|
||||
std::cout << "recv setup" << std::endl;
|
||||
std::cout << HTTP_S.BuildResponse(HTTP_S.method,HTTP_S.url);
|
||||
optionsRecvd = true;
|
||||
sessionID = HTTP_S.GetHeader("Session");
|
||||
setupsRecvd++;
|
||||
}
|
||||
}
|
||||
}
|
||||
//set up all parameters, and then after the for loop we have to listen to setups and all. sent if both are equal, and recv if one is sent
|
||||
|
||||
}
|
||||
setupComplete = true;
|
||||
}
|
||||
|
||||
if(setupComplete && !playSent){
|
||||
//time to play
|
||||
HTTP_S.Clean();
|
||||
HTTP_R.protocol="RTSP/1.0";
|
||||
HTTP_R.method = "PLAY";
|
||||
HTTP_R.url = rtspUrl;
|
||||
|
||||
HTTP_R.SetHeader("CSeq",CSeq);
|
||||
CSeq++;
|
||||
HTTP_R.SetHeader("User-Agent","mistANALyser");
|
||||
HTTP_R.SetHeader("Session",sessionID);
|
||||
HTTP_R.SendRequest(conn);
|
||||
playSent = true;
|
||||
std::cout << "sent play" << std::endl;
|
||||
char buffer[2000];
|
||||
while(!connectors[0].iread((void*)buffer,2000)) {
|
||||
std::cout << "buffer";
|
||||
}
|
||||
std::cout <<"buffer is not empty" << std::endl;
|
||||
|
||||
}
|
||||
|
||||
//streams set up
|
||||
//time to read some packets
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if(descRecvd){
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
conn.close();*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
||||
conf.addOption("url",JSON::fromString("{\"arg\":\"string\",\"short\":\"u\",\"long\":\"url\",\"help\":\"URL To get.\", \"default\":\"rtsp://localhost/s1k\"}"));
|
||||
conf.parseArgs(argc, argv);
|
||||
return RtspRtp::analyseRtspRtp(conf.getString("url"));
|
||||
}
|
||||
84
src/analysers/stats_analyser.cpp
Normal file
84
src/analysers/stats_analyser.cpp
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/// \file stats_analyser.cpp
|
||||
/// Will emulate a given amount of clients in the statistics.
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/shared_memory.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
/// Will emulate a given amount of clients in the statistics.
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
||||
conf.addOption("clients", JSON::fromString("{\"arg\":\"num\", \"short\":\"c\", \"long\":\"clients\", \"default\":1000, \"help\":\"Amount of clients to emulate.\"}"));
|
||||
conf.addOption("stream", JSON::fromString("{\"arg\":\"string\", \"short\":\"s\", \"long\":\"stream\", \"default\":\"test\", \"help\":\"Streamname to pretend to request.\"}"));
|
||||
conf.addOption("up", JSON::fromString("{\"arg\":\"string\", \"short\":\"u\", \"long\":\"up\", \"default\":131072, \"help\":\"Bytes per second upstream.\"}"));
|
||||
conf.addOption("down", JSON::fromString("{\"arg\":\"string\", \"short\":\"d\", \"long\":\"down\", \"default\":13000, \"help\":\"Bytes per second downstream.\"}"));
|
||||
conf.addOption("sine", JSON::fromString("{\"arg\":\"string\", \"short\":\"S\", \"long\":\"sine\", \"default\":0, \"help\":\"Bytes per second variance in a sine pattern.\"}"));
|
||||
conf.addOption("userscale", JSON::fromString("{\"arg\":\"string\", \"short\":\"U\", \"long\":\"userscale\", \"default\":0, \"help\":\"If != 0, scales users from 0% to 100% bandwidth.\"}"));
|
||||
conf.parseArgs(argc, argv);
|
||||
|
||||
std::string streamName = conf.getString("stream");
|
||||
long long clientCount = conf.getInteger("clients");
|
||||
long long up = conf.getInteger("up");
|
||||
long long down = conf.getInteger("down");
|
||||
long long sine = conf.getInteger("sine");
|
||||
long long scale = conf.getInteger("userscale");
|
||||
long long currsine = sine;
|
||||
long long goingUp = 0;
|
||||
|
||||
IPC::sharedClient ** clients = (IPC::sharedClient **)malloc(sizeof(IPC::sharedClient *)*clientCount);
|
||||
for (long long i = 0; i < clientCount; i++){
|
||||
clients[i] = new IPC::sharedClient("statistics", STAT_EX_SIZE, true);
|
||||
}
|
||||
|
||||
unsigned long long int counter = 0;
|
||||
conf.activate();
|
||||
|
||||
while (conf.is_active){
|
||||
unsigned long long int now = Util::epoch();
|
||||
counter++;
|
||||
if (sine){
|
||||
currsine += goingUp;
|
||||
if (currsine < -down || currsine < -up){
|
||||
currsine = std::max(-down, -up);
|
||||
}
|
||||
if (currsine > 0){
|
||||
goingUp -= sine/100 + 1;
|
||||
}else{
|
||||
goingUp += sine/100 + 1;
|
||||
}
|
||||
}
|
||||
for (long long i = 0; i < clientCount; i++){
|
||||
if (clients[i]->getData()){
|
||||
IPC::statExchange tmpEx(clients[i]->getData());
|
||||
tmpEx.now(now);
|
||||
tmpEx.host("::42");
|
||||
tmpEx.crc(i);
|
||||
tmpEx.streamName(streamName);
|
||||
tmpEx.connector("TEST");
|
||||
if (scale){
|
||||
tmpEx.up(tmpEx.up() + (up+currsine)*i/clientCount);
|
||||
tmpEx.down(tmpEx.down() + (down+currsine)*i/clientCount);
|
||||
}else{
|
||||
tmpEx.up(tmpEx.up()+up+currsine);
|
||||
tmpEx.down(tmpEx.down()+down+currsine);
|
||||
}
|
||||
tmpEx.time(counter);
|
||||
tmpEx.lastSecond(counter * 1000);
|
||||
clients[i]->keepAlive();
|
||||
}
|
||||
}
|
||||
Util::wait(1000);
|
||||
}
|
||||
|
||||
for (long long i = 0; i < clientCount; i++){
|
||||
clients[i]->finish();
|
||||
delete clients[i];
|
||||
}
|
||||
|
||||
free(clients);
|
||||
return 0;
|
||||
}
|
||||
189
src/analysers/ts_analyser.cpp
Executable file
189
src/analysers/ts_analyser.cpp
Executable file
|
|
@ -0,0 +1,189 @@
|
|||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string.h>
|
||||
#include <fstream>
|
||||
#include <unistd.h>
|
||||
#include <sstream>
|
||||
#include <signal.h>
|
||||
#include <mist/ts_packet.h>
|
||||
#include <mist/config.h>
|
||||
|
||||
|
||||
namespace Analysers {
|
||||
std::string printPES(const std::string & d, unsigned long PID, int detailLevel){
|
||||
unsigned int headSize = 0;
|
||||
std::stringstream res;
|
||||
bool known = false;
|
||||
res << "[PES " << PID << "]";
|
||||
if ((d[3] & 0xF0) == 0xE0){
|
||||
res << " [Video " << (int)(d[3] & 0xF) << "]";
|
||||
known = true;
|
||||
}
|
||||
if (!known && (d[3] & 0xE0) == 0xC0){
|
||||
res << " [Audio " << (int)(d[3] & 0x1F) << "]";
|
||||
known = true;
|
||||
}
|
||||
if (!known){
|
||||
res << " [Unknown stream ID]";
|
||||
}
|
||||
if (d[0] != 0 || d[1] != 0 || d[2] != 1){
|
||||
res << " [!INVALID START CODE!]";
|
||||
}
|
||||
if (known){
|
||||
if ((d[6] & 0xC0) != 0x80){
|
||||
res << " [!INVALID FIRST BITS!]";
|
||||
}
|
||||
if (d[6] & 0x30){
|
||||
res << " [SCRAMBLED]";
|
||||
}
|
||||
if (d[6] & 0x08){
|
||||
res << " [Priority]";
|
||||
}
|
||||
if (d[6] & 0x04){
|
||||
res << " [Aligned]";
|
||||
}
|
||||
if (d[6] & 0x02){
|
||||
res << " [Copyrighted]";
|
||||
}
|
||||
if (d[6] & 0x01){
|
||||
res << " [Original]";
|
||||
}else{
|
||||
res << " [Copy]";
|
||||
}
|
||||
|
||||
if (d[7] & 0x20){
|
||||
res << " [ESCR present, not decoded!]";
|
||||
headSize += 6;
|
||||
}
|
||||
if (d[7] & 0x10){
|
||||
res << " [ESR present, not decoded!]";
|
||||
headSize += 3;
|
||||
}
|
||||
if (d[7] & 0x08){
|
||||
res << " [Trick mode present, not decoded!]";
|
||||
headSize += 1;
|
||||
}
|
||||
if (d[7] & 0x04){
|
||||
res << " [Add. copy present, not decoded!]";
|
||||
headSize += 1;
|
||||
}
|
||||
if (d[7] & 0x02){
|
||||
res << " [CRC present, not decoded!]";
|
||||
headSize += 2;
|
||||
}
|
||||
if (d[7] & 0x01){
|
||||
res << " [Extension present, not decoded!]";
|
||||
headSize += 0; /// \todo Implement this. Complicated field, bah.
|
||||
}
|
||||
int timeFlags = ((d[7] & 0xC0) >> 6);
|
||||
if (timeFlags == 2){
|
||||
headSize += 5;
|
||||
}
|
||||
if (timeFlags == 3){
|
||||
headSize += 10;
|
||||
}
|
||||
if (d[8] != headSize){
|
||||
res << " [Padding: " << ((int)d[8] - headSize) << "b]";
|
||||
}
|
||||
if (timeFlags & 0x02){
|
||||
long long unsigned int time = (((unsigned int)d[9] & 0xE) >> 1);
|
||||
time <<= 15;
|
||||
time |= ((unsigned int)d[10] << 7) | (((unsigned int)d[11] >> 1) & 0x7F);
|
||||
time <<= 15;
|
||||
time |= ((unsigned int)d[12] << 7) | (((unsigned int)d[13] >> 1) & 0x7F);
|
||||
res << " [PTS " << ((double)time / 90000) << "s]";
|
||||
}
|
||||
if (timeFlags & 0x01){
|
||||
long long unsigned int time = ((d[14] >> 1) & 0x07);
|
||||
time <<= 15;
|
||||
time |= ((int)d[15] << 7) | (d[16] >> 1);
|
||||
time <<= 15;
|
||||
time |= ((int)d[17] << 7) | (d[18] >> 1);
|
||||
res << " [DTS " << ((double)time/90000) << "s]";
|
||||
}
|
||||
}
|
||||
if ((((int)d[4]) << 8 | d[5]) != (d.size() - 6)){
|
||||
res << " [Size " << (((int)d[4]) << 8 | d[5]) << " => " << (d.size() - 6) << "]";
|
||||
}
|
||||
res << std::endl;
|
||||
|
||||
if(detailLevel==1){
|
||||
unsigned int counter = 0;
|
||||
for (unsigned int i = 9+headSize; i<d.size(); ++i){
|
||||
if ((i < d.size() - 4) && d[i] == 0 && d[i+1] == 0 && d[i+2] == 0 && d[i+3] == 1){res << std::endl; counter = 0;}
|
||||
res << std::hex << std::setw(2) << std::setfill('0') << (int)(d[i]&0xff) << " ";
|
||||
counter++;
|
||||
if ((counter) % 32 == 31){res << std::endl;}
|
||||
}
|
||||
res << std::endl;
|
||||
}
|
||||
return res.str();
|
||||
}
|
||||
|
||||
/// Debugging tool for TS data.
|
||||
/// Expects TS data through stdin, outputs human-readable information to stderr.
|
||||
/// \return The return code of the analyser.
|
||||
int analyseTS(bool validate, bool analyse, int detailLevel){
|
||||
std::map<unsigned long long, std::string> payloads;
|
||||
TS::Packet packet;
|
||||
long long int upTime = Util::bootSecs();
|
||||
int64_t pcr = 0;
|
||||
unsigned int bytes = 0;
|
||||
char packetPtr[188];
|
||||
while (std::cin.good()){
|
||||
std::cin.read(packetPtr,188);
|
||||
if(std::cin.gcount() != 188){break;}
|
||||
bytes += 188;
|
||||
if(packet.FromPointer(packetPtr)){
|
||||
if(analyse){
|
||||
if (packet.getUnitStart() && payloads[packet.getPID()] != ""){
|
||||
std::cout << printPES(payloads[packet.getPID()], packet.getPID(), detailLevel);
|
||||
payloads.erase(packet.getPID());
|
||||
}
|
||||
if (detailLevel < 2){
|
||||
std::stringstream nul;
|
||||
nul << packet.toPrettyString(0, detailLevel);
|
||||
}else{
|
||||
std::cout << packet.toPrettyString(0, detailLevel);
|
||||
}
|
||||
if (packet.getPID() && !packet.isPMT()){
|
||||
payloads[packet.getPID()].append(packet.getPayload(), packet.getPayloadLength());
|
||||
}
|
||||
}
|
||||
if(packet && packet.getAdaptationField() > 1 && packet.hasPCR()){pcr = packet.getPCR();}
|
||||
}
|
||||
if(bytes > 1024){
|
||||
long long int tTime = Util::bootSecs();
|
||||
if(validate && tTime - upTime > 5 && tTime - upTime > pcr/27000000){
|
||||
std::cerr << "data received too slowly" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
bytes = 0;
|
||||
}
|
||||
}
|
||||
for (std::map<unsigned long long, std::string>::iterator it = payloads.begin(); it != payloads.end(); it++){
|
||||
if (!it->first || it->first == 4096){ continue; }
|
||||
std::cout << printPES(it->second, it->first, detailLevel);
|
||||
}
|
||||
long long int finTime = Util::bootSecs();
|
||||
if(validate){
|
||||
fprintf(stdout,"time since boot,time at completion,real time duration of data receival,video duration\n");
|
||||
fprintf(stdout, "%lli000,%lli000,%lli000,%li \n",upTime,finTime,finTime-upTime,pcr/27000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
||||
conf.addOption("analyse", JSON::fromString("{\"long\":\"analyse\", \"short\":\"a\", \"default\":1, \"long_off\":\"notanalyse\", \"short_off\":\"b\", \"help\":\"Analyse a file's contents (-a), or don't (-b) returning false on error. Default is analyse.\"}"));
|
||||
conf.addOption("validate", JSON::fromString("{\"long\":\"validate\", \"short\":\"V\", \"default\":0, \"long_off\":\"notvalidate\", \"short_off\":\"X\", \"help\":\"Validate (-V) the file contents or don't validate (-X) its integrity, returning false on error. Default is don't validate.\"}"));
|
||||
conf.addOption("detail", JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":3, \"help\":\"Detail level of analysis.\"}"));
|
||||
conf.parseArgs(argc, argv);
|
||||
return Analysers::analyseTS(conf.getBool("validate"),conf.getBool("analyse"),conf.getInteger("detail"));
|
||||
}
|
||||
|
|
@ -46,6 +46,11 @@
|
|||
#include "controller_capabilities.h"
|
||||
#include "controller_connectors.h"
|
||||
#include "controller_statistics.h"
|
||||
/*LTS-START*/
|
||||
#include "controller_updater.h"
|
||||
#include "controller_limits.h"
|
||||
#include "controller_uplink.h"
|
||||
/*LTS-END*/
|
||||
#include "controller_api.h"
|
||||
|
||||
#ifndef COMPILED_USERNAME
|
||||
|
|
@ -88,7 +93,19 @@ void createAccount (std::string account){
|
|||
/// Status monitoring thread.
|
||||
/// Will check outputs, inputs and converters every five seconds
|
||||
void statusMonitor(void * np){
|
||||
#ifdef UPDATER
|
||||
unsigned long updatechecker = Util::epoch(); /*LTS*/
|
||||
#endif
|
||||
while (Controller::conf.is_active){
|
||||
/*LTS-START*/
|
||||
#ifdef UPDATER
|
||||
if (Util::epoch() - updatechecker > 3600){
|
||||
updatechecker = Util::epoch();
|
||||
Controller::CheckUpdateInfo();
|
||||
}
|
||||
#endif
|
||||
/*LTS-END*/
|
||||
|
||||
//this scope prevents the configMutex from being locked constantly
|
||||
{
|
||||
tthread::lock_guard<tthread::mutex> guard(Controller::configMutex);
|
||||
|
|
@ -127,6 +144,12 @@ int main(int argc, char ** argv){
|
|||
Controller::conf.addOption("account", JSON::fromString("{\"long\":\"account\", \"short\":\"a\", \"arg\":\"string\" \"default\":\"\", \"help\":\"A username:password string to create a new account with.\"}"));
|
||||
Controller::conf.addOption("logfile", JSON::fromString("{\"long\":\"logfile\", \"short\":\"L\", \"arg\":\"string\" \"default\":\"\",\"help\":\"Redirect all standard output to a log file, provided with an argument\"}"));
|
||||
Controller::conf.addOption("configFile", JSON::fromString("{\"long\":\"config\", \"short\":\"c\", \"arg\":\"string\" \"default\":\"config.json\", \"help\":\"Specify a config file other than default.\"}"));
|
||||
#ifdef UPDATER
|
||||
Controller::conf.addOption("update", JSON::fromString("{\"default\":0, \"help\":\"Check for and install updates before starting.\", \"short\":\"D\", \"long\":\"update\"}")); /*LTS*/
|
||||
#endif
|
||||
Controller::conf.addOption("uplink", JSON::fromString("{\"default\":\"\", \"arg\":\"string\", \"help\":\"MistSteward uplink host and port.\", \"short\":\"U\", \"long\":\"uplink\"}")); /*LTS*/
|
||||
Controller::conf.addOption("uplink-name", JSON::fromString("{\"default\":\"" COMPILED_USERNAME "\", \"arg\":\"string\", \"help\":\"MistSteward uplink username.\", \"short\":\"N\", \"long\":\"uplink-name\"}")); /*LTS*/
|
||||
Controller::conf.addOption("uplink-pass", JSON::fromString("{\"default\":\"" COMPILED_PASSWORD "\", \"arg\":\"string\", \"help\":\"MistSteward uplink password.\", \"short\":\"P\", \"long\":\"uplink-pass\"}")); /*LTS*/
|
||||
Controller::conf.parseArgs(argc, argv);
|
||||
if(Controller::conf.getString("logfile")!= ""){
|
||||
//open logfile, dup stdout to logfile
|
||||
|
|
@ -246,14 +269,29 @@ int main(int argc, char ** argv){
|
|||
Controller::Log("CONF", "Controller started");
|
||||
Controller::conf.activate();//activate early, so threads aren't killed.
|
||||
|
||||
/*LTS-START*/
|
||||
#ifdef UPDATER
|
||||
if (Controller::conf.getBool("update")){
|
||||
Controller::CheckUpdates();
|
||||
}
|
||||
#endif
|
||||
/*LTS-END*/
|
||||
|
||||
//start stats thread
|
||||
tthread::thread statsThread(Controller::SharedMemStats, &Controller::conf);
|
||||
//start monitoring thread
|
||||
tthread::thread monitorThread(statusMonitor, 0);
|
||||
//start monitoring thread /*LTS*/
|
||||
tthread::thread uplinkThread(Controller::uplinkConnection, 0);/*LTS*/
|
||||
|
||||
//start main loop
|
||||
Controller::conf.serveThreadedSocket(Controller::handleAPIConnection);
|
||||
//print shutdown reason
|
||||
/*LTS-START*/
|
||||
if (Controller::restarting){
|
||||
Controller::Log("CONF", "Controller restarting for update");
|
||||
}
|
||||
/*LTS-END*/
|
||||
if (!Controller::conf.is_active){
|
||||
Controller::Log("CONF", "Controller shutting down because of user request (received shutdown signal)");
|
||||
}else{
|
||||
|
|
@ -263,6 +301,7 @@ int main(int argc, char ** argv){
|
|||
//join all joinable threads
|
||||
statsThread.join();
|
||||
monitorThread.join();
|
||||
uplinkThread.join();/*LTS*/
|
||||
//give everything some time to print messages
|
||||
Util::wait(100);
|
||||
//close stderr to make the stderr reading thread exit
|
||||
|
|
@ -282,5 +321,12 @@ int main(int argc, char ** argv){
|
|||
//stop all child processes
|
||||
Util::Procs::StopAll();
|
||||
std::cout << "Killed all processes, wrote config to disk. Exiting." << std::endl;
|
||||
/*LTS-START*/
|
||||
if (Controller::restarting){
|
||||
std::string myFile = Util::getMyPath() + "MistController";
|
||||
execvp(myFile.c_str(), argv);
|
||||
std::cout << "Error restarting: " << strerror(errno) << std::endl;
|
||||
}
|
||||
/*LTS-END*/
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@
|
|||
#include "controller_connectors.h"
|
||||
#include "controller_capabilities.h"
|
||||
#include "controller_statistics.h"
|
||||
/*LTS-START*/
|
||||
#include "controller_updater.h"
|
||||
#include "controller_limits.h"
|
||||
/*LTS-END*/
|
||||
|
||||
///\brief Check the submitted configuration and handle things accordingly.
|
||||
///\param in The new configuration.
|
||||
|
|
@ -114,6 +118,10 @@ void Controller::checkConfig(JSON::Value & in, JSON::Value & out){
|
|||
/// Please note that this is NOT secure. At all. Never use this mechanism over a public network!
|
||||
/// A status of `"ACC_MADE"` indicates the account was created successfully and can now be used to login as normal.
|
||||
bool Controller::authorize(JSON::Value & Request, JSON::Value & Response, Socket::Connection & conn){
|
||||
#ifdef NOAUTH
|
||||
Response["authorize"]["status"] = "OK";
|
||||
return true;
|
||||
#endif
|
||||
time_t Time = time(0);
|
||||
tm * TimeInfo = localtime( &Time);
|
||||
std::stringstream Date;
|
||||
|
|
@ -191,6 +199,148 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
if (Request.isMember("streams")){
|
||||
Controller::CheckStreams(Request["streams"], Controller::Storage["streams"]);
|
||||
}
|
||||
/*LTS-START*/
|
||||
///
|
||||
/// \api
|
||||
/// `"addstream"` requests (LTS-only) take the form of:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// {
|
||||
/// "streamname": {
|
||||
/// //Stream configuration - see the "streams" call for details on this format.
|
||||
/// }
|
||||
/// /// Optionally, repeat for more streams.
|
||||
/// }
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// These requests will add new streams or update existing streams with the same names, without touching other streams. In other words, this call can be used for incremental updates to the stream list instead of complete updates, like the "streams" call.
|
||||
///
|
||||
if (Request.isMember("addstream")){
|
||||
Controller::AddStreams(Request["addstream"], Controller::Storage["streams"]);
|
||||
}
|
||||
///
|
||||
/// \api
|
||||
/// `"deletestream"` requests (LTS-only) take the form of:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// {
|
||||
/// "streamname": {} //any contents in this object are ignored
|
||||
/// /// Optionally, repeat for more streams.
|
||||
/// }
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// OR
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// [
|
||||
/// "streamname",
|
||||
/// /// Optionally, repeat for more streams.
|
||||
/// ]
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// OR
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// "streamname"
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// These requests will remove the named stream(s), without touching other streams. In other words, this call can be used for incremental updates to the stream list instead of complete updates, like the "streams" call.
|
||||
///
|
||||
if (Request.isMember("deletestream")){
|
||||
//if array, delete all elements
|
||||
//if object, delete all entries
|
||||
//if string, delete just the one
|
||||
if (Request["deletestream"].isString()){
|
||||
Controller::Storage["streams"].removeMember(Request["deletestream"].asStringRef());
|
||||
}
|
||||
if (Request["deletestream"].isArray()){
|
||||
for (JSON::ArrIter it = Request["deletestream"].ArrBegin(); it != Request["deletestream"].ArrEnd(); ++it){
|
||||
Controller::Storage["streams"].removeMember(it->asString());
|
||||
}
|
||||
}
|
||||
if (Request["deletestream"].isObject()){
|
||||
for (JSON::ObjIter it = Request["deletestream"].ObjBegin(); it != Request["deletestream"].ObjEnd(); ++it){
|
||||
Controller::Storage["streams"].removeMember(it->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
///
|
||||
/// \api
|
||||
/// `"addprotocol"` requests (LTS-only) take the form of:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// {
|
||||
/// "connector": "HTTP" //Name of the connector to enable
|
||||
/// //any required and/or optional settings may be given here as "name": "value" pairs inside this object.
|
||||
/// }
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// OR
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// [
|
||||
/// {
|
||||
/// "connector": "HTTP" //Name of the connector to enable
|
||||
/// //any required and/or optional settings may be given here as "name": "value" pairs inside this object.
|
||||
/// }
|
||||
/// /// Optionally, repeat for more protocols.
|
||||
/// ]
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// These requests will add the given protocol configurations, without touching existing configurations. In other words, this call can be used for incremental updates to the protocols list instead of complete updates, like the "config" call.
|
||||
/// There is no response to this call.
|
||||
///
|
||||
if (Request.isMember("addprotocol")){
|
||||
if (Request["addprotocol"].isArray()){
|
||||
for (JSON::ArrIter it = Request["addprotocol"].ArrBegin(); it != Request["addprotocol"].ArrEnd(); ++it){
|
||||
Controller::Storage["config"]["protocols"].append(*it);
|
||||
}
|
||||
}
|
||||
if (Request["addprotocol"].isObject()){
|
||||
Controller::Storage["config"]["protocols"].append(Request["addprotocol"]);
|
||||
}
|
||||
Controller::CheckProtocols(Controller::Storage["config"]["protocols"], capabilities);
|
||||
}
|
||||
///
|
||||
/// \api
|
||||
/// `"deleteprotocol"` requests (LTS-only) take the form of:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// {
|
||||
/// "connector": "HTTP" //Name of the connector to enable
|
||||
/// //any required and/or optional settings may be given here as "name": "value" pairs inside this object.
|
||||
/// }
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// OR
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// [
|
||||
/// {
|
||||
/// "connector": "HTTP" //Name of the connector to enable
|
||||
/// //any required and/or optional settings may be given here as "name": "value" pairs inside this object.
|
||||
/// }
|
||||
/// /// Optionally, repeat for more protocols.
|
||||
/// ]
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// These requests will remove the given protocol configurations (exact matches only), without touching other configurations. In other words, this call can be used for incremental updates to the protocols list instead of complete updates, like the "config" call.
|
||||
/// There is no response to this call.
|
||||
///
|
||||
if (Request.isMember("deleteprotocol")){
|
||||
if (Request["deleteprotocol"].isArray() && Request["deleteprotocol"].size()){
|
||||
JSON::Value newProtocols;
|
||||
for (JSON::ArrIter it = Controller::Storage["config"]["protocols"].ArrBegin(); it != Controller::Storage["config"]["protocols"].ArrEnd(); ++it){
|
||||
bool add = true;
|
||||
for (JSON::ArrIter pit = Request["deleteprotocol"].ArrBegin(); pit != Request["deleteprotocol"].ArrEnd(); ++pit){
|
||||
if (*it == *pit){
|
||||
add = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (add){
|
||||
newProtocols.append(*it);
|
||||
}
|
||||
}
|
||||
Controller::Storage["config"]["protocols"] = newProtocols;
|
||||
Controller::CheckProtocols(Controller::Storage["config"]["protocols"], capabilities);
|
||||
}
|
||||
if (Request["deleteprotocol"].isObject()){
|
||||
JSON::Value newProtocols;
|
||||
for (JSON::ArrIter it = Controller::Storage["config"]["protocols"].ArrBegin(); it != Controller::Storage["config"]["protocols"].ArrEnd(); ++it){
|
||||
if (*it != Request["deleteprotocol"]){
|
||||
newProtocols.append(*it);
|
||||
}
|
||||
}
|
||||
Controller::Storage["config"]["protocols"] = newProtocols;
|
||||
Controller::CheckProtocols(Controller::Storage["config"]["protocols"], capabilities);
|
||||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
if (Request.isMember("capabilities")){
|
||||
Controller::checkCapable(capabilities);
|
||||
Response["capabilities"] = capabilities;
|
||||
|
|
@ -248,6 +398,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
/// ]
|
||||
/// ]
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
///
|
||||
if(Request.isMember("browse")){
|
||||
if(Request["browse"] == ""){
|
||||
Request["browse"] = ".";
|
||||
|
|
@ -312,6 +463,28 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
}
|
||||
Response["ui_settings"] = Storage["ui_settings"];
|
||||
}
|
||||
/*LTS-START*/
|
||||
///
|
||||
/// \api
|
||||
/// LTS builds will always include an `"LTS"` response, set to 1.
|
||||
///
|
||||
Response["LTS"] = 1;
|
||||
///
|
||||
/// \api
|
||||
/// `"autoupdate"` requests (LTS-only) will cause MistServer to apply a rolling update to itself, and are not responded to.
|
||||
///
|
||||
#ifdef UPDATER
|
||||
if (Request.isMember("autoupdate")){
|
||||
Controller::CheckUpdates();
|
||||
}
|
||||
if (Request.isMember("checkupdate")){
|
||||
Controller::updates = Controller::CheckUpdateInfo();
|
||||
}
|
||||
if (Request.isMember("update") || Request.isMember("checkupdate")){
|
||||
Response["update"] = Controller::updates;
|
||||
}
|
||||
#endif
|
||||
/*LTS-END*/
|
||||
//sent current configuration, no matter if it was changed or not
|
||||
Response["config"] = Controller::Storage["config"];
|
||||
Response["config"]["version"] = PACKAGE_VERSION "/" + Util::Config::libver + "/" RELEASE;
|
||||
|
|
@ -363,6 +536,9 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
Controller::fillTotals(Request["totals"], Response["totals"]);
|
||||
}
|
||||
}
|
||||
if (Request.isMember("active_streams")){
|
||||
Controller::fillActive(Request["active_streams"], Response["active_streams"]);
|
||||
}
|
||||
|
||||
Controller::writeConfig();
|
||||
|
||||
|
|
@ -370,6 +546,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
Util::sleep(1000);//sleep a second to prevent bruteforcing
|
||||
logins++;
|
||||
}
|
||||
Controller::checkServerLimits(); /*LTS*/
|
||||
}//config mutex lock
|
||||
//send the response, either normally or through JSONP callback.
|
||||
std::string jsonp = "";
|
||||
|
|
|
|||
403
src/controller/controller_limits.cpp
Normal file
403
src/controller/controller_limits.cpp
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
#include "controller_limits.h"
|
||||
#include "controller_statistics.h"
|
||||
#include "controller_storage.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
namespace Controller{
|
||||
void checkStreamLimits(std::string streamName, long long currentKbps, long long connectedUsers){
|
||||
if( !Storage["streams"].isMember(streamName)){
|
||||
return;
|
||||
}
|
||||
if( !Storage["streams"][streamName].isMember("limits")){
|
||||
return;
|
||||
}
|
||||
if( !Storage["streams"][streamName]["limits"]){
|
||||
return;
|
||||
}
|
||||
|
||||
Storage["streams"][streamName].removeMember("hardlimit_active");
|
||||
if (Storage["streams"][streamName]["online"].asInt() != 1){
|
||||
for (JSON::ArrIter limitIt = Storage["streams"][streamName]["limits"].ArrBegin(); limitIt != Storage["streams"][streamName]["limits"].ArrEnd(); limitIt++){
|
||||
if ((*limitIt).isMember("triggered")){
|
||||
if ((*limitIt)["type"].asString() == "soft"){
|
||||
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset - stream unavailable.");
|
||||
}else{
|
||||
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset - stream unavailable.");
|
||||
}
|
||||
(*limitIt).removeMember("triggered");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//run over all limits.
|
||||
for (JSON::ArrIter limitIt = Storage["streams"][streamName]["limits"].ArrBegin(); limitIt != Storage["streams"][streamName]["limits"].ArrEnd(); limitIt++){
|
||||
bool triggerLimit = false;
|
||||
if ((*limitIt)["name"].asString() == "users" && connectedUsers >= (*limitIt)["value"].asInt()){
|
||||
triggerLimit = true;
|
||||
}
|
||||
if ((*limitIt)["name"].asString() == "kbps_max" && currentKbps >= (*limitIt)["value"].asInt()){
|
||||
triggerLimit = true;
|
||||
}
|
||||
if (triggerLimit){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Storage["streams"][streamName]["hardlimit_active"] = true;
|
||||
}
|
||||
if ((*limitIt).isMember("triggered")){
|
||||
continue;
|
||||
}
|
||||
if ((*limitIt)["type"].asString() == "soft"){
|
||||
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " triggered.");
|
||||
}else{
|
||||
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " triggered.");
|
||||
}
|
||||
(*limitIt)["triggered"] = true;
|
||||
}else{
|
||||
if ( !(*limitIt).isMember("triggered")){
|
||||
continue;
|
||||
}
|
||||
if ((*limitIt)["type"].asString() == "soft"){
|
||||
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
|
||||
}else{
|
||||
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
|
||||
}
|
||||
(*limitIt).removeMember("triggered");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkServerLimits(){
|
||||
|
||||
int currentKbps = 0;
|
||||
int connectedUsers = 0;
|
||||
std::map<std::string, long long> strmUsers;
|
||||
std::map<std::string, long long> strmBandw;
|
||||
|
||||
/*
|
||||
if (curConns.size()){
|
||||
for (std::map<unsigned long, statStorage>::iterator it = curConns.begin(); it != curConns.end(); it++){
|
||||
if (it->second.log.size() < 2){continue;}
|
||||
std::map<unsigned long long, statLog>::reverse_iterator statRef = it->second.log.rbegin();
|
||||
std::map<unsigned long long, statLog>::reverse_iterator prevRef = --(it->second.log.rbegin());
|
||||
unsigned int diff = statRef->first - prevRef->first;
|
||||
strmUsers[it->second.streamName]++;
|
||||
connectedUsers++;
|
||||
strmBandw[it->second.streamName] += (((statRef->second.down - prevRef->second.down) + (statRef->second.up - prevRef->second.up)) / diff);
|
||||
currentKbps += (((statRef->second.down - prevRef->second.down) + (statRef->second.up - prevRef->second.up)) / diff);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
//check stream limits
|
||||
if (Storage["streams"].size()){
|
||||
for (JSON::ObjIter strmIt = Storage["streams"].ObjBegin(); strmIt != Storage["streams"].ObjEnd(); strmIt++){
|
||||
checkStreamLimits(strmIt->first, strmBandw[strmIt->first], strmUsers[strmIt->first]);
|
||||
}
|
||||
}
|
||||
|
||||
Storage["config"].removeMember("hardlimit_active");
|
||||
if ( !Storage["config"]["limits"].size()){
|
||||
return;
|
||||
}
|
||||
if ( !Storage["streams"].size()){
|
||||
return;
|
||||
}
|
||||
|
||||
for (JSON::ArrIter limitIt = Storage["config"]["limits"].ArrBegin(); limitIt != Storage["config"]["limits"].ArrEnd(); limitIt++){
|
||||
bool triggerLimit = false;
|
||||
if ((*limitIt)["name"].asString() == "users" && connectedUsers >= (*limitIt)["value"].asInt()){
|
||||
triggerLimit = true;
|
||||
}
|
||||
if ((*limitIt)["name"].asString() == "kbps_max" && currentKbps >= (*limitIt)["value"].asInt()){
|
||||
triggerLimit = true;
|
||||
}
|
||||
if (triggerLimit){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Storage["config"]["hardlimit_active"] = true;
|
||||
}
|
||||
if ((*limitIt).isMember("triggered")){
|
||||
continue;
|
||||
}
|
||||
if ((*limitIt)["type"].asString() == "soft"){
|
||||
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " triggered.");
|
||||
}else{
|
||||
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " triggered.");
|
||||
}
|
||||
(*limitIt)["triggered"] = true;
|
||||
}else{
|
||||
if ( !(*limitIt).isMember("triggered")){
|
||||
continue;
|
||||
}
|
||||
if ((*limitIt)["type"].asString() == "soft"){
|
||||
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " reset.");
|
||||
}else{
|
||||
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " reset.");
|
||||
}
|
||||
(*limitIt).removeMember("triggered");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool onList(std::string ip, std::string list){
|
||||
if (list == ""){
|
||||
return false;
|
||||
}
|
||||
std::string entry;
|
||||
std::string lowerIpv6;//lower-case
|
||||
std::string upperIpv6;//full-caps
|
||||
do{
|
||||
entry = list.substr(0,list.find(" "));//make sure we have a single entry
|
||||
lowerIpv6 = "::ffff:" + entry;
|
||||
upperIpv6 = "::FFFF:" + entry;
|
||||
if (entry == ip || lowerIpv6 == ip || upperIpv6 == ip){
|
||||
return true;
|
||||
}
|
||||
long long unsigned int starPos = entry.find("*");
|
||||
if (starPos == std::string::npos){
|
||||
if (ip == entry){
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
if (starPos == 0){//beginning of the filter
|
||||
if (ip.substr(ip.length() - entry.size() - 1) == entry.substr(1)){
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
if (starPos == entry.size() - 1){//end of the filter
|
||||
if (ip.find(entry.substr(0, entry.size() - 1)) == 0 ){
|
||||
return true;
|
||||
}
|
||||
if (ip.find(entry.substr(0, lowerIpv6.size() - 1)) == 0 ){
|
||||
return true;
|
||||
}
|
||||
if (ip.find(entry.substr(0, upperIpv6.size() - 1)) == 0 ){
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
Log("CONF","Invalid list entry detected: " + entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
list.erase(0, entry.size() + 1);
|
||||
}while (list != "");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string hostLookup(std::string ip){
|
||||
struct sockaddr_in6 sa;
|
||||
char hostName[1024];
|
||||
char service[20];
|
||||
if (inet_pton(AF_INET6, ip.c_str(), &(sa.sin6_addr)) != 1){
|
||||
return "\n";
|
||||
}
|
||||
sa.sin6_family = AF_INET6;
|
||||
sa.sin6_port = 0;
|
||||
sa.sin6_flowinfo = 0;
|
||||
sa.sin6_scope_id = 0;
|
||||
int tmpRet = getnameinfo((struct sockaddr*)&sa, sizeof sa, hostName, sizeof hostName, service, sizeof service, NI_NAMEREQD );
|
||||
if ( tmpRet == 0){
|
||||
return hostName;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool isBlacklisted(std::string host, std::string streamName, int timeConnected){
|
||||
std::string myHostName = hostLookup(host);
|
||||
if (myHostName == "\n"){
|
||||
return false;
|
||||
}
|
||||
std::string myCountryName = getCountry(host);
|
||||
JSON::ArrIter limitIt;
|
||||
bool hasWhitelist = false;
|
||||
bool hostOnWhitelist = false;
|
||||
if (Storage["streams"].isMember(streamName)){
|
||||
if (Storage["streams"][streamName].isMember("limits") && Storage["streams"][streamName]["limits"].size()){
|
||||
for (limitIt = Storage["streams"][streamName]["limits"].ArrBegin(); limitIt != Storage["streams"][streamName]["limits"].ArrEnd(); limitIt++){
|
||||
if ((*limitIt)["name"].asString() == "host"){
|
||||
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||
if (!onList(host, (*limitIt)["value"].asString().substr(1))){
|
||||
if (myHostName == ""){
|
||||
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
if ( !onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if ((*limitIt)["value"].asString()[0] == '-'){
|
||||
if (onList(host, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
if (myHostName != "" && onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + myHostName + " blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + myHostName + " blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((*limitIt)["name"].asString() == "geo"){
|
||||
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||
if (myCountryName == ""){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
if (!onList(myCountryName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if ((*limitIt)["val"].asString()[0] == '-'){
|
||||
if (onList(myCountryName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Storage["config"]["limits"].size()){
|
||||
for (limitIt = Storage["config"]["limits"].ArrBegin(); limitIt != Storage["config"]["limits"].ArrEnd(); limitIt++){
|
||||
if ((*limitIt)["name"].asString() == "host"){
|
||||
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||
if (!onList(host, (*limitIt)["value"].asString().substr(1))){
|
||||
if (myHostName == ""){
|
||||
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
if ( !onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if ((*limitIt)["value"].asString()[0] == '-'){
|
||||
if (onList(host, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
if (myHostName != "" && onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + myHostName + " blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + myHostName + " blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((*limitIt)["name"].asString() == "geo"){
|
||||
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||
if (myCountryName == ""){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
if (!onList(myCountryName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if ((*limitIt)["value"].asString()[0] == '-'){
|
||||
if (onList(myCountryName, (*limitIt)["val"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasWhitelist){
|
||||
if (hostOnWhitelist || myHostName == ""){
|
||||
return false;
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string getCountry(std::string ip){
|
||||
char * code = NULL;
|
||||
#ifdef GEOIP
|
||||
GeoIP * geoIP;
|
||||
geoIP = GeoIP_open(GEOIPV4, GEOIP_STANDARD | GEOIP_CHECK_CACHE);
|
||||
if (!geoIP){
|
||||
std::cerr << "An error occured loading the IPv4 database" << std::endl;
|
||||
}else{
|
||||
code = (char*)GeoIP_country_code_by_addr(geoIP, ip.c_str());
|
||||
GeoIP_delete(geoIP);
|
||||
}
|
||||
if (!code){
|
||||
geoIP = GeoIP_open(GEOIPV6, GEOIP_STANDARD | GEOIP_CHECK_CACHE);
|
||||
if (!geoIP){
|
||||
std::cerr << "An error occured loading the IPv6 database" << std::endl;
|
||||
}else{
|
||||
code = (char*)GeoIP_country_code_by_addr_v6(geoIP, ip.c_str());
|
||||
GeoIP_delete(geoIP);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!code){
|
||||
return "";
|
||||
}
|
||||
return code;
|
||||
}
|
||||
}
|
||||
21
src/controller/controller_limits.h
Normal file
21
src/controller/controller_limits.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
#include <mist/json.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
/*LTS-START*/
|
||||
#ifdef GEOIP
|
||||
#include <GeoIP.h>
|
||||
#define GEOIPV4 "/usr/share/GeoIP/GeoIP.dat"
|
||||
#define GEOIPV6 "/usr/share/GeoIP/GeoIPv6.dat"
|
||||
#endif
|
||||
/*LTS-END*/
|
||||
|
||||
namespace Controller{
|
||||
void checkStreamLimits(std::string streamName, long long currentKbps, long long connectedUsers);
|
||||
void checkServerLimits();
|
||||
bool isBlacklisted(std::string host, std::string streamName, int timeConnected);
|
||||
std::string hostLookup(std::string ip);
|
||||
bool onList(std::string ip, std::string list);
|
||||
std::string getCountry(std::string ip);
|
||||
}
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
#include <cstdio>
|
||||
#include <mist/config.h>
|
||||
#include "controller_statistics.h"
|
||||
#include "controller_limits.h"
|
||||
|
||||
#ifndef KILL_ON_EXIT
|
||||
#define KILL_ON_EXIT false
|
||||
#endif
|
||||
|
||||
// These are used to store "clients" field requests in a bitfield for speedup.
|
||||
#define STAT_CLI_HOST 1
|
||||
|
|
@ -23,6 +28,7 @@
|
|||
|
||||
std::map<Controller::sessIndex, Controller::statSession> Controller::sessions; ///< list of sessions that have statistics data available
|
||||
std::map<unsigned long, Controller::sessIndex> Controller::connToSession; ///< Map of socket IDs to session info.
|
||||
bool Controller::killOnExit = KILL_ON_EXIT;
|
||||
tthread::mutex Controller::statsMutex;
|
||||
|
||||
Controller::sessIndex::sessIndex(std::string dhost, unsigned int dcrc, std::string dstreamName, std::string dconnector){
|
||||
|
|
@ -79,6 +85,10 @@ bool Controller::sessIndex::operator>= (const Controller::sessIndex &b) const{
|
|||
return !(*this < b);
|
||||
}
|
||||
|
||||
/// Forces a disconnect to all users.
|
||||
void Controller::killStatistics(char * data, size_t len, unsigned int id){
|
||||
(*(data - 1)) = 128;//Send disconnect message;
|
||||
}
|
||||
|
||||
/// This function runs as a thread and roughly once per second retrieves
|
||||
/// statistics from all connected clients, as well as wipes
|
||||
|
|
@ -98,10 +108,19 @@ void Controller::SharedMemStats(void * config){
|
|||
it->second.wipeOld(cutOffPoint);
|
||||
}
|
||||
}
|
||||
Controller::checkServerLimits(); /*LTS*/
|
||||
}
|
||||
Util::sleep(1000);
|
||||
}
|
||||
DEBUG_MSG(DLVL_HIGH, "Stopping stats thread");
|
||||
if (Controller::killOnExit){
|
||||
DEBUG_MSG(DLVL_WARN, "Killing all connected clients to force full shutdown");
|
||||
unsigned int c = 0;//to prevent eternal loops
|
||||
do{
|
||||
statServer.parseEach(killStatistics);
|
||||
Util::wait(250);
|
||||
}while(statServer.amount && c++ < 10);
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the given active connection with new stats data.
|
||||
|
|
@ -416,6 +435,11 @@ void Controller::parseStatistics(char * data, size_t len, unsigned int id){
|
|||
sessions[idx].finish(id);
|
||||
connToSession.erase(id);
|
||||
}
|
||||
/*LTS-START*/
|
||||
//if (counter < 125 && Controller::isBlacklisted(tmpEx.host(), ID, tmpEx.time())){
|
||||
// (*(data - 1)) = 128;//Send disconnect message;
|
||||
//}
|
||||
/*LTS-END*/
|
||||
}
|
||||
|
||||
/// Returns true if this stream has at least one connected client.
|
||||
|
|
@ -554,6 +578,36 @@ void Controller::fillClients(JSON::Value & req, JSON::Value & rep){
|
|||
//all done! return is by reference, so no need to return anything here.
|
||||
}
|
||||
|
||||
/// This takes a "active_streams" request, and fills in the response data.
|
||||
///
|
||||
/// \api
|
||||
/// `"active_streams"` requests are always empty (passed data is ignored), and are responded to as:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// [
|
||||
/// //Array of stream names
|
||||
/// "streamA",
|
||||
/// "streamB",
|
||||
/// "streamC"
|
||||
/// ]
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// All streams that any statistics data is available for are listed, and only those streams.
|
||||
void Controller::fillActive(JSON::Value & req, JSON::Value & rep){
|
||||
//collect the data first
|
||||
std::set<std::string> streams;
|
||||
//check all sessions
|
||||
if (sessions.size()){
|
||||
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
|
||||
streams.insert(it->first.streamName);
|
||||
}
|
||||
}
|
||||
//Good, now output what we found...
|
||||
rep.null();
|
||||
for (std::set<std::string>::iterator it = streams.begin(); it != streams.end(); it++){
|
||||
rep.append(*it);
|
||||
}
|
||||
//all done! return is by reference, so no need to return anything here.
|
||||
}
|
||||
|
||||
class totalsData {
|
||||
public:
|
||||
totalsData(){
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
|
||||
|
||||
namespace Controller {
|
||||
|
||||
extern bool killOnExit;
|
||||
|
||||
struct statLog {
|
||||
long time;
|
||||
long lastSecond;
|
||||
|
|
@ -80,7 +83,9 @@ namespace Controller {
|
|||
extern std::map<unsigned long, sessIndex> connToSession;
|
||||
extern tthread::mutex statsMutex;
|
||||
void parseStatistics(char * data, size_t len, unsigned int id);
|
||||
void killStatistics(char * data, size_t len, unsigned int id);
|
||||
void fillClients(JSON::Value & req, JSON::Value & rep);
|
||||
void fillActive(JSON::Value & req, JSON::Value & rep);
|
||||
void fillTotals(JSON::Value & req, JSON::Value & rep);
|
||||
void SharedMemStats(void * config);
|
||||
bool hasViewers(std::string streamName);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "controller_capabilities.h"
|
||||
#include "controller_storage.h"
|
||||
#include "controller_statistics.h"
|
||||
#include "controller_limits.h" /*LTS*/
|
||||
#include <sys/stat.h>
|
||||
#include <map>
|
||||
|
||||
|
|
@ -73,6 +74,7 @@ namespace Controller {
|
|||
trackIt->second.removeMember("keys");
|
||||
trackIt->second.removeMember("keysizes");
|
||||
trackIt->second.removeMember("parts");
|
||||
trackIt->second.removeMember("ivecs");/*LTS*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +84,7 @@ namespace Controller {
|
|||
//vod-style stream
|
||||
data.removeMember("error");
|
||||
struct stat fileinfo;
|
||||
if (stat(URL.c_str(), &fileinfo) != 0 || S_ISDIR(fileinfo.st_mode)){
|
||||
if (stat(URL.c_str(), &fileinfo) != 0){
|
||||
data["error"] = "Stream offline: Not found: " + URL;
|
||||
if (data["error"].asStringRef() != prevState){
|
||||
Log("BUFF", "Warning for VoD stream " + name + "! File not found: " + URL);
|
||||
|
|
@ -121,6 +123,9 @@ namespace Controller {
|
|||
DEBUG_MSG(DLVL_INSANE, "Invalid metadata (no tracks object) for stream %s - triggering reload", name.c_str());
|
||||
getMeta = true;
|
||||
}
|
||||
if (*(URL.rbegin()) == '/'){
|
||||
getMeta = false;
|
||||
}
|
||||
if (getMeta){
|
||||
// if the file isn't dtsc and there's no dtsh file, run getStream on it
|
||||
// this guarantees that if the stream is playable, it now has a valid header.
|
||||
|
|
@ -198,6 +203,7 @@ namespace Controller {
|
|||
}else{
|
||||
data["online"] = 1;
|
||||
}
|
||||
checkServerLimits(); /*LTS*/
|
||||
return;
|
||||
}
|
||||
/// \todo Implement ffmpeg pulling again?
|
||||
|
|
@ -231,6 +237,7 @@ namespace Controller {
|
|||
}
|
||||
jit->second["online"] = 0;
|
||||
}
|
||||
checkServerLimits(); /*LTS*/
|
||||
}else{
|
||||
// assume all is fine
|
||||
jit->second.removeMember("error");
|
||||
|
|
@ -242,7 +249,7 @@ namespace Controller {
|
|||
jit->second["error"] = "No (valid) source connected ";
|
||||
}else{
|
||||
// for live streams, keep track of activity
|
||||
if (jit->second["meta"].isMember("live")){
|
||||
if (jit->second.isMember("meta") && jit->second["meta"].isMember("live")){
|
||||
static std::map<std::string, liveCheck> checker;
|
||||
//check H264 tracks for optimality
|
||||
if (jit->second.isMember("meta") && jit->second["meta"].isMember("tracks")){
|
||||
|
|
@ -251,7 +258,11 @@ namespace Controller {
|
|||
checker[jit->first].lastms = trIt->second["lastms"].asInt();
|
||||
checker[jit->first].last_active = currTime;
|
||||
}
|
||||
|
||||
/*LTS-START*/
|
||||
if (trIt->second["firstms"].asInt() > Storage["streams"][jit->first]["cut"].asInt()){
|
||||
Storage["streams"][jit->first].removeMember("cut");
|
||||
}
|
||||
/*LTS-END*/
|
||||
}
|
||||
}
|
||||
// mark stream as offline if no activity for 5 seconds
|
||||
|
|
@ -289,14 +300,8 @@ namespace Controller {
|
|||
Log("STRM", std::string("Updated stream ") + jit->first);
|
||||
}
|
||||
}else{
|
||||
out[jit->first] = jit->second;
|
||||
out[jit->first]["name"] = jit->first;
|
||||
out[jit->first]["source"] = jit->second["source"];
|
||||
if (jit->second.isMember("DVR")){
|
||||
out[jit->first]["DVR"] = jit->second["DVR"].asInt();
|
||||
}
|
||||
if (jit->second.isMember("cut")){
|
||||
out[jit->first]["cut"] = jit->second["cut"].asInt();
|
||||
}
|
||||
Log("STRM", std::string("New stream ") + jit->first);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
253
src/controller/controller_updater.cpp
Normal file
253
src/controller/controller_updater.cpp
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
/// \file controller_updater.cpp
|
||||
/// Contains all code for the controller updater.
|
||||
|
||||
#include <fstream> //for files
|
||||
#include <iostream> //for stdio
|
||||
#include <unistd.h> //for unlink
|
||||
#include <sys/stat.h> //for chmod
|
||||
#include <stdlib.h> //for srand, rand
|
||||
#include <time.h> //for time
|
||||
#include <signal.h> //for raise
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/auth.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/config.h>
|
||||
#include "controller_storage.h"
|
||||
#include "controller_connectors.h"
|
||||
#include "controller_updater.h"
|
||||
|
||||
namespace Controller {
|
||||
bool restarting = false;
|
||||
JSON::Value updates;
|
||||
std::string uniqId;
|
||||
|
||||
std::string readFile(std::string filename){
|
||||
std::ifstream file(filename.c_str());
|
||||
if ( !file.good()){
|
||||
return "";
|
||||
}
|
||||
file.seekg(0, std::ios::end);
|
||||
unsigned int len = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
std::string out;
|
||||
out.reserve(len);
|
||||
unsigned int i = 0;
|
||||
while (file.good() && i++ < len){
|
||||
out += file.get();
|
||||
}
|
||||
file.close();
|
||||
return out;
|
||||
} //readFile
|
||||
|
||||
bool writeFile(std::string filename, std::string & contents){
|
||||
unlink(filename.c_str());
|
||||
std::ofstream file(filename.c_str(), std::ios_base::trunc | std::ios_base::out);
|
||||
if ( !file.is_open()){
|
||||
return false;
|
||||
}
|
||||
file << contents;
|
||||
file.close();
|
||||
chmod(filename.c_str(), S_IRWXU | S_IRWXG);
|
||||
return true;
|
||||
} //writeFile
|
||||
|
||||
/// \api
|
||||
/// `"update"` and `"checkupdate"` requests (LTS-only) are responded to as:
|
||||
/// ~~~~~~~~~~~~~~~{.js}
|
||||
/// {
|
||||
/// "error": "Something went wrong", // 'Optional'
|
||||
/// "release": "LTS64_99",
|
||||
/// "version": "1.2 / 6.0.0",
|
||||
/// "date": "January 5th, 2014",
|
||||
/// "uptodate": 0,
|
||||
/// "needs_update": ["MistBuffer", "MistController"], //Controller is guaranteed to be last
|
||||
/// "MistController": "abcdef1234567890", //md5 sum of latest version
|
||||
/// //... all other MD5 sums follow
|
||||
/// }
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// Note that `"update"` will only list known information, while `"checkupdate"` triggers an information refresh from the update server.
|
||||
JSON::Value CheckUpdateInfo(){
|
||||
JSON::Value ret;
|
||||
|
||||
if (uniqId == ""){
|
||||
srand(time(NULL));
|
||||
do{
|
||||
char meh = 64 + rand() % 62;
|
||||
uniqId += meh;
|
||||
}while(uniqId.size() < 16);
|
||||
}
|
||||
|
||||
//initialize connection
|
||||
HTTP::Parser http;
|
||||
JSON::Value updrInfo;
|
||||
Socket::Connection updrConn("releases.mistserver.org", 80, true);
|
||||
if ( !updrConn){
|
||||
Log("UPDR", "Could not connect to releases.mistserver.org to get update information.");
|
||||
ret["error"] = "Could not connect to releases.mistserver.org to get update information.";
|
||||
return ret;
|
||||
}
|
||||
|
||||
//retrieve update information
|
||||
http.url = "/getsums.php?verinfo=1&rel=" RELEASE "&pass=" SHARED_SECRET "&uniqId=" + uniqId;
|
||||
http.method = "GET";
|
||||
http.SetHeader("Host", "releases.mistserver.org");
|
||||
http.SetHeader("X-Version", PACKAGE_VERSION "/" + Util::Config::libver + "/" RELEASE);
|
||||
updrConn.SendNow(http.BuildRequest());
|
||||
http.Clean();
|
||||
unsigned int startTime = Util::epoch();
|
||||
while ((Util::epoch() - startTime < 10) && (updrConn || updrConn.Received().size())){
|
||||
if (updrConn.spool() || updrConn.Received().size()){
|
||||
if ( *(updrConn.Received().get().rbegin()) != '\n'){
|
||||
std::string tmp = updrConn.Received().get();
|
||||
updrConn.Received().get().clear();
|
||||
if (updrConn.Received().size()){
|
||||
updrConn.Received().get().insert(0, tmp);
|
||||
}else{
|
||||
updrConn.Received().append(tmp);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (http.Read(updrConn.Received().get())){
|
||||
updrInfo = JSON::fromString(http.body);
|
||||
break; //break out of while loop
|
||||
}
|
||||
}
|
||||
}
|
||||
updrConn.close();
|
||||
|
||||
if (updrInfo){
|
||||
if (updrInfo.isMember("error")){
|
||||
Log("UPDR", updrInfo["error"].asStringRef());
|
||||
ret["error"] = updrInfo["error"];
|
||||
ret["uptodate"] = 1;
|
||||
return ret;
|
||||
}
|
||||
ret["release"] = RELEASE;
|
||||
if (updrInfo.isMember("version")){
|
||||
ret["version"] = updrInfo["version"];
|
||||
}
|
||||
if (updrInfo.isMember("date")){
|
||||
ret["date"] = updrInfo["date"];
|
||||
}
|
||||
ret["uptodate"] = 1;
|
||||
ret["needs_update"].null();
|
||||
|
||||
// check if everything is up to date or not
|
||||
for (JSON::ObjIter it = updrInfo.ObjBegin(); it != updrInfo.ObjEnd(); it++){
|
||||
if (it->first.substr(0, 4) != "Mist"){
|
||||
continue;
|
||||
}
|
||||
ret[it->first] = it->second;
|
||||
if (it->second.asString() != Secure::md5(readFile(Util::getMyPath() + it->first))){
|
||||
ret["uptodate"] = 0;
|
||||
if (it->first.substr(0, 14) == "MistController"){
|
||||
ret["needs_update"].append(it->first);
|
||||
}else{
|
||||
ret["needs_update"].prepend(it->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
Log("UPDR", "Could not retrieve update information from releases server.");
|
||||
ret["error"] = "Could not retrieve update information from releases server.";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Calls CheckUpdateInfo(), uses the resulting JSON::Value to download any needed updates.
|
||||
/// Will shut down the server if the JSON::Value contained a "shutdown" member.
|
||||
void CheckUpdates(){
|
||||
JSON::Value updrInfo = CheckUpdateInfo();
|
||||
if (updrInfo.isMember("error")){
|
||||
Log("UPDR", "Error retrieving update information: " + updrInfo["error"].asString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (updrInfo.isMember("shutdown")){
|
||||
Log("DDVT", "Shutting down: " + updrInfo["shutdown"].asString());
|
||||
restarting = false;
|
||||
raise(SIGINT); //trigger shutdown
|
||||
return;
|
||||
}
|
||||
|
||||
if (updrInfo["uptodate"]){
|
||||
//nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
//initialize connection
|
||||
Socket::Connection updrConn("releases.mistserver.org", 80, true);
|
||||
if ( !updrConn){
|
||||
Log("UPDR", "Could not connect to releases.mistserver.org.");
|
||||
return;
|
||||
}
|
||||
|
||||
//loop through the available components, update them
|
||||
for (JSON::ArrIter it = updrInfo["needs_update"].ArrBegin(); it != updrInfo["needs_update"].ArrEnd(); it++){
|
||||
updateComponent(it->asStringRef(), updrInfo[it->asStringRef()].asStringRef(), updrConn);
|
||||
}
|
||||
updrConn.close();
|
||||
} //CheckUpdates
|
||||
|
||||
/// Attempts to download an update for the listed component.
|
||||
/// \param component Filename of the component being checked.
|
||||
/// \param md5sum The MD5 sum of the latest version of this file.
|
||||
/// \param updrConn An connection to releases.mistserver.org to (re)use. Will be (re)opened if closed.
|
||||
void updateComponent(const std::string & component, const std::string & md5sum, Socket::Connection & updrConn){
|
||||
Log("UPDR", "Downloading update for " + component);
|
||||
std::string new_file;
|
||||
HTTP::Parser http;
|
||||
http.url = "/getfile.php?rel=" RELEASE "&pass=" SHARED_SECRET "&file=" + component;
|
||||
http.method = "GET";
|
||||
http.SetHeader("Host", "releases.mistserver.org");
|
||||
if ( !updrConn){
|
||||
updrConn = Socket::Connection("releases.mistserver.org", 80, true);
|
||||
if ( !updrConn){
|
||||
Log("UPDR", "Could not connect to releases.mistserver.org for file download.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
http.SendRequest(updrConn);
|
||||
http.Clean();
|
||||
unsigned int startTime = Util::epoch();
|
||||
while ((Util::epoch() - startTime < 90) && (updrConn || updrConn.Received().size())){
|
||||
if (updrConn.spool() || updrConn.Received().size()){
|
||||
if ( *(updrConn.Received().get().rbegin()) != '\n'){
|
||||
std::string tmp = updrConn.Received().get();
|
||||
updrConn.Received().get().clear();
|
||||
if (updrConn.Received().size()){
|
||||
updrConn.Received().get().insert(0, tmp);
|
||||
}else{
|
||||
updrConn.Received().append(tmp);
|
||||
}
|
||||
}
|
||||
if (http.Read(updrConn.Received().get())){
|
||||
new_file = http.body;
|
||||
break; //break out of while loop
|
||||
}
|
||||
}
|
||||
}
|
||||
http.Clean();
|
||||
if (new_file == ""){
|
||||
Log("UPDR", "Could not retrieve new version of " + component + " - retrying next time.");
|
||||
return;
|
||||
}
|
||||
if (Secure::md5(new_file) != md5sum){
|
||||
Log("UPDR", "Checksum "+Secure::md5(new_file)+" of " + component + " does not match "+md5sum+" - retrying next time.");
|
||||
return;
|
||||
}
|
||||
if (writeFile(Util::getMyPath() + component, new_file)){
|
||||
Controller::UpdateProtocol(component);
|
||||
if (component == "MistController"){
|
||||
restarting = true;
|
||||
raise(SIGINT); //trigger restart
|
||||
}
|
||||
Log("UPDR", "New version of " + component + " installed.");
|
||||
}else{
|
||||
Log("UPDR", component + " could not be updated! (No write access to file?)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} //Controller namespace
|
||||
21
src/controller/controller_updater.h
Normal file
21
src/controller/controller_updater.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/// \file controller_updater.cpp
|
||||
/// Contains all code for the controller updater.
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifndef SHARED_SECRET
|
||||
#define SHARED_SECRET "empty"
|
||||
#endif
|
||||
|
||||
namespace Controller {
|
||||
extern bool restarting;///< Signals if the controller is shutting down (false) or restarting (true).
|
||||
extern JSON::Value updates;
|
||||
extern std::string uniqId;
|
||||
|
||||
std::string readFile(std::string filename);
|
||||
bool writeFile(std::string filename, std::string & contents);
|
||||
JSON::Value CheckUpdateInfo();
|
||||
void CheckUpdates();
|
||||
void updateComponent(const std::string & component, const std::string & md5sum, Socket::Connection & updrConn);
|
||||
|
||||
} //Controller namespace
|
||||
138
src/controller/controller_uplink.cpp
Normal file
138
src/controller/controller_uplink.cpp
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
#include <stdlib.h>
|
||||
#include <mist/auth.h>
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/timing.h>
|
||||
#include "controller_uplink.h"
|
||||
#include "controller_storage.h"
|
||||
#include "controller_streams.h"
|
||||
#include "controller_connectors.h"
|
||||
#include "controller_capabilities.h"
|
||||
#include "controller_statistics.h"
|
||||
#include "controller_updater.h"
|
||||
#include "controller_limits.h"
|
||||
#include "controller_api.h"
|
||||
|
||||
void Controller::uplinkConnection(void * np) {
|
||||
std::string uplink_name = Controller::conf.getString("uplink-name");
|
||||
std::string uplink_pass = Controller::conf.getString("uplink-pass");
|
||||
std::string uplink_addr = Controller::conf.getString("uplink");
|
||||
std::string uplink_host = "";
|
||||
std::string uplink_chal = "";
|
||||
int uplink_port = 0;
|
||||
if (uplink_addr.size() > 0) {
|
||||
size_t colon = uplink_addr.find(':');
|
||||
if (colon != std::string::npos && colon != 0 && colon != uplink_addr.size()) {
|
||||
uplink_host = uplink_addr.substr(0, colon);
|
||||
uplink_port = atoi(uplink_addr.substr(colon + 1, std::string::npos).c_str());
|
||||
Controller::Log("CONF", "Connection to uplink enabled on host " + uplink_host + " and port " + uplink_addr.substr(colon + 1, std::string::npos));
|
||||
}
|
||||
}
|
||||
//cancel the whole thread if no uplink is set
|
||||
if (!uplink_port) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (uniqId == ""){
|
||||
srand(time(NULL));
|
||||
do{
|
||||
char meh = 64 + rand() % 62;
|
||||
uniqId += meh;
|
||||
}while(uniqId.size() < 16);
|
||||
}
|
||||
|
||||
unsigned long long lastSend = Util::epoch() - 5;
|
||||
Socket::Connection uplink;
|
||||
while (Controller::conf.is_active) {
|
||||
if (!uplink) {
|
||||
INFO_MSG("Connecting to uplink at %s:%u", uplink_host.c_str(), uplink_port);
|
||||
uplink = Socket::Connection(uplink_host, uplink_port, true);
|
||||
}
|
||||
if (uplink) {
|
||||
if (uplink.spool()) {
|
||||
if (uplink.Received().available(9)) {
|
||||
std::string data = uplink.Received().copy(8);
|
||||
if (data.substr(0, 4) != "DTSC") {
|
||||
uplink.Received().clear();
|
||||
continue;
|
||||
}
|
||||
unsigned int size = ntohl(*(const unsigned int *)(data.data() + 4));
|
||||
if (uplink.Received().available(8 + size)) {
|
||||
std::string packet = uplink.Received().remove(8 + size);
|
||||
DTSC::Scan inScan = DTSC::Packet(packet.data(), packet.size()).getScan();
|
||||
if (!inScan){continue;}
|
||||
JSON::Value curVal;
|
||||
//Parse config and streams from the request.
|
||||
if (inScan.hasMember("authorize") && inScan.getMember("authorize").hasMember("challenge")){
|
||||
uplink_chal = inScan.getMember("authorize").getMember("challenge").asString();
|
||||
}
|
||||
if (inScan.hasMember("config")) {
|
||||
curVal = inScan.getMember("config").asJSON();
|
||||
Controller::checkConfig(curVal, Controller::Storage["config"]);
|
||||
Controller::CheckProtocols(Controller::Storage["config"]["protocols"], capabilities);
|
||||
}
|
||||
if (inScan.hasMember("streams")) {
|
||||
curVal = inScan.getMember("streams").asJSON();
|
||||
Controller::CheckStreams(curVal, Controller::Storage["streams"]);
|
||||
}
|
||||
if (inScan.hasMember("addstream")) {
|
||||
curVal = inScan.getMember("addstream").asJSON();
|
||||
Controller::AddStreams(curVal, Controller::Storage["streams"]);
|
||||
Controller::CheckAllStreams(Controller::Storage["streams"]);
|
||||
}
|
||||
if (inScan.hasMember("deletestream")) {
|
||||
curVal = inScan.getMember("deletestream").asJSON();
|
||||
//if array, delete all elements
|
||||
//if object, delete all entries
|
||||
//if string, delete just the one
|
||||
if (curVal.isString()) {
|
||||
Controller::Storage["streams"].removeMember(curVal.asStringRef());
|
||||
}
|
||||
if (curVal.isArray()) {
|
||||
for (JSON::ArrIter it = curVal.ArrBegin(); it != curVal.ArrEnd(); ++it) {
|
||||
Controller::Storage["streams"].removeMember(it->asString());
|
||||
}
|
||||
}
|
||||
if (curVal.isObject()) {
|
||||
for (JSON::ObjIter it = curVal.ObjBegin(); it != curVal.ObjEnd(); ++it) {
|
||||
Controller::Storage["streams"].removeMember(it->first);
|
||||
}
|
||||
}
|
||||
Controller::CheckAllStreams(Controller::Storage["streams"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Util::epoch() - lastSend >= 2) {
|
||||
JSON::Value data;
|
||||
data["tracks"].null();//make sure the data is encoded as DTSC
|
||||
if (uplink_chal.size()){
|
||||
data["authorize"]["username"] = uplink_name;
|
||||
data["authorize"]["password"] = Secure::md5( Secure::md5(uplink_pass) + uplink_chal);
|
||||
}
|
||||
JSON::Value totalsRequest;
|
||||
Controller::fillClients(totalsRequest, data["clients"]);
|
||||
totalsRequest["start"] = (long long)lastSend;
|
||||
Controller::fillTotals(totalsRequest, data["totals"]);
|
||||
data["streams"] = Controller::Storage["streams"];
|
||||
for (JSON::ObjIter it = data["streams"].ObjBegin(); it != data["streams"].ObjEnd(); it++){
|
||||
it->second.removeMember("meta");
|
||||
it->second.removeMember("l_meta");
|
||||
it->second.removeMember("name");
|
||||
}
|
||||
data["config"] = Controller::Storage["config"];
|
||||
data["config"]["uniq"] = uniqId;
|
||||
data["config"]["version"] = PACKAGE_VERSION "/" + Util::Config::libver + "/" RELEASE;
|
||||
Controller::checkCapable(capabilities);
|
||||
data["capabilities"] = capabilities;
|
||||
data["capabilities"].removeMember("connectors");
|
||||
data.sendTo(uplink);
|
||||
lastSend = Util::epoch();
|
||||
}
|
||||
} else {
|
||||
Controller::Log("UPLK", "Could not connect to uplink.");
|
||||
}
|
||||
Util::wait(2000);//wait for 2.5 seconds
|
||||
}
|
||||
}
|
||||
3
src/controller/controller_uplink.h
Normal file
3
src/controller/controller_uplink.h
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
namespace Controller {
|
||||
void uplinkConnection(void * np);
|
||||
}
|
||||
281
src/input/input_av.cpp
Normal file
281
src/input/input_av.cpp
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
#include "input_av.h"
|
||||
|
||||
namespace Mist {
|
||||
inputAV::inputAV(Util::Config * cfg) : Input(cfg) {
|
||||
pFormatCtx = 0;
|
||||
capa["name"] = "AV";
|
||||
capa["decs"] = "Enables generic avformat/avcodec based input";
|
||||
capa["source_match"] = "/*";
|
||||
capa["priority"] = 1ll;
|
||||
capa["codecs"][0u][0u].null();
|
||||
capa["codecs"][0u][1u].null();
|
||||
capa["codecs"][0u][2u].null();
|
||||
av_register_all();
|
||||
AVCodec * cInfo = 0;
|
||||
while ((cInfo = av_codec_next(cInfo)) != 0){
|
||||
if (cInfo->type == AVMEDIA_TYPE_VIDEO){
|
||||
capa["codecs"][0u][0u].append(cInfo->name);
|
||||
}
|
||||
if (cInfo->type == AVMEDIA_TYPE_AUDIO){
|
||||
capa["codecs"][0u][1u].append(cInfo->name);
|
||||
}
|
||||
if (cInfo->type == AVMEDIA_TYPE_SUBTITLE){
|
||||
capa["codecs"][0u][3u].append(cInfo->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputAV::~inputAV(){
|
||||
if (pFormatCtx){
|
||||
avformat_close_input(&pFormatCtx);
|
||||
}
|
||||
}
|
||||
|
||||
bool inputAV::setup() {
|
||||
if (config->getString("input") == "-") {
|
||||
std::cerr << "Input from stdin not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (!config->getString("streamname").size()){
|
||||
if (config->getString("output") == "-") {
|
||||
std::cerr << "Output to stdout not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
if (config->getString("output") != "-") {
|
||||
std::cerr << "File output in player mode not supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//make sure all av inputs are registered properly, just in case
|
||||
//setup() already does this, but under windows it doesn't remember that it has.
|
||||
//Very sad, that. We may need to get windows some medication for it.
|
||||
av_register_all();
|
||||
|
||||
//close any already open files
|
||||
if (pFormatCtx){
|
||||
avformat_close_input(&pFormatCtx);
|
||||
pFormatCtx = 0;
|
||||
}
|
||||
|
||||
//Open video file
|
||||
int ret = avformat_open_input(&pFormatCtx, config->getString("input").c_str(), NULL, NULL);
|
||||
if(ret != 0){
|
||||
char errstr[300];
|
||||
av_strerror(ret, errstr, 300);
|
||||
DEBUG_MSG(DLVL_FAIL, "Could not open file: %s", errstr);
|
||||
return false; // Couldn't open file
|
||||
}
|
||||
|
||||
//Retrieve stream information
|
||||
ret = avformat_find_stream_info(pFormatCtx, NULL);
|
||||
if(ret < 0){
|
||||
char errstr[300];
|
||||
av_strerror(ret, errstr, 300);
|
||||
DEBUG_MSG(DLVL_FAIL, "Could not find stream info: %s", errstr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool inputAV::readHeader() {
|
||||
//See whether a separate header file exists.
|
||||
DTSC::File tmp(config->getString("input") + ".dtsh");
|
||||
if (tmp){
|
||||
myMeta = tmp.getMeta();
|
||||
return true;
|
||||
}
|
||||
|
||||
myMeta.tracks.clear();
|
||||
myMeta.live = false;
|
||||
myMeta.vod = true;
|
||||
for(unsigned int i=0; i < pFormatCtx->nb_streams; ){
|
||||
AVStream * strm = pFormatCtx->streams[i++];
|
||||
myMeta.tracks[i].trackID = i;
|
||||
switch (strm->codec->codec_id){
|
||||
case AV_CODEC_ID_HEVC:
|
||||
myMeta.tracks[i].codec = "HEVC";
|
||||
break;
|
||||
case AV_CODEC_ID_H264:
|
||||
myMeta.tracks[i].codec = "H264";
|
||||
break;
|
||||
case AV_CODEC_ID_THEORA:
|
||||
myMeta.tracks[i].codec = "theora";
|
||||
break;
|
||||
case AV_CODEC_ID_VORBIS:
|
||||
myMeta.tracks[i].codec = "vorbis";
|
||||
break;
|
||||
case AV_CODEC_ID_OPUS:
|
||||
myMeta.tracks[i].codec = "opus";
|
||||
break;
|
||||
case AV_CODEC_ID_AAC:
|
||||
myMeta.tracks[i].codec = "AAC";
|
||||
break;
|
||||
case AV_CODEC_ID_MP3:
|
||||
myMeta.tracks[i].codec = "MP3";
|
||||
break;
|
||||
case AV_CODEC_ID_AC3:
|
||||
case AV_CODEC_ID_EAC3:
|
||||
myMeta.tracks[i].codec = "AC3";
|
||||
break;
|
||||
default:
|
||||
const AVCodecDescriptor *desc = av_codec_get_codec_descriptor(strm->codec);
|
||||
if (desc && desc->name){
|
||||
myMeta.tracks[i].codec = desc->name;
|
||||
}else{
|
||||
myMeta.tracks[i].codec = "?";
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (strm->codec->extradata_size){
|
||||
myMeta.tracks[i].init = std::string((char*)strm->codec->extradata, strm->codec->extradata_size);
|
||||
}
|
||||
if(strm->codec->codec_type == AVMEDIA_TYPE_VIDEO){
|
||||
myMeta.tracks[i].type = "video";
|
||||
if (strm->avg_frame_rate.den && strm->avg_frame_rate.num){
|
||||
myMeta.tracks[i].fpks = (strm->avg_frame_rate.num * 1000) / strm->avg_frame_rate.den;
|
||||
}else{
|
||||
myMeta.tracks[i].fpks = 0;
|
||||
}
|
||||
myMeta.tracks[i].width = strm->codec->width;
|
||||
myMeta.tracks[i].height = strm->codec->height;
|
||||
}
|
||||
if(strm->codec->codec_type == AVMEDIA_TYPE_AUDIO){
|
||||
myMeta.tracks[i].type = "audio";
|
||||
myMeta.tracks[i].rate = strm->codec->sample_rate;
|
||||
switch (strm->codec->sample_fmt){
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
case AV_SAMPLE_FMT_U8P:
|
||||
myMeta.tracks[i].size = 8;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
case AV_SAMPLE_FMT_S16P:
|
||||
myMeta.tracks[i].size = 16;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
case AV_SAMPLE_FMT_S32P:
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
case AV_SAMPLE_FMT_FLTP:
|
||||
myMeta.tracks[i].size = 32;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_DBL:
|
||||
case AV_SAMPLE_FMT_DBLP:
|
||||
myMeta.tracks[i].size = 64;
|
||||
break;
|
||||
default:
|
||||
myMeta.tracks[i].size = 0;
|
||||
break;
|
||||
}
|
||||
myMeta.tracks[i].channels = strm->codec->channels;
|
||||
}
|
||||
}
|
||||
|
||||
AVPacket packet;
|
||||
while(av_read_frame(pFormatCtx, &packet)>=0){
|
||||
AVStream * strm = pFormatCtx->streams[packet.stream_index];
|
||||
JSON::Value pkt;
|
||||
pkt["trackid"] = (long long)packet.stream_index + 1;
|
||||
pkt["data"] = std::string((char*)packet.data, packet.size);
|
||||
pkt["time"] = (long long)(packet.dts * 1000 * strm->time_base.num / strm->time_base.den);
|
||||
if (pkt["time"].asInt() < 0){
|
||||
pkt["time"] = 0ll;
|
||||
}
|
||||
if (packet.flags & AV_PKT_FLAG_KEY && myMeta.tracks[(long long)packet.stream_index + 1].type != "audio"){
|
||||
pkt["keyframe"] = 1ll;
|
||||
pkt["bpos"] = (long long)packet.pos;
|
||||
}
|
||||
if (packet.pts != AV_NOPTS_VALUE && packet.pts != packet.dts){
|
||||
pkt["offset"] = (long long)((packet.pts - packet.dts) * 1000 * strm->time_base.num / strm->time_base.den);
|
||||
}
|
||||
myMeta.update(pkt);
|
||||
av_free_packet(&packet);
|
||||
}
|
||||
myMeta.live = false;
|
||||
myMeta.vod = true;
|
||||
|
||||
//store dtsc-style header file for faster processing, later
|
||||
std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str());
|
||||
oFile << myMeta.toJSON().toNetPacked();
|
||||
oFile.close();
|
||||
|
||||
seek(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputAV::getNext(bool smart) {
|
||||
AVPacket packet;
|
||||
while (av_read_frame(pFormatCtx, &packet)>=0){
|
||||
//filter tracks we don't care about
|
||||
if (!selectedTracks.count(packet.stream_index + 1)){
|
||||
DEBUG_MSG(DLVL_HIGH, "Track %u not selected", packet.stream_index + 1);
|
||||
continue;
|
||||
}
|
||||
AVStream * strm = pFormatCtx->streams[packet.stream_index];
|
||||
JSON::Value pkt;
|
||||
pkt["trackid"] = (long long)packet.stream_index + 1;
|
||||
pkt["data"] = std::string((char*)packet.data, packet.size);
|
||||
pkt["time"] = (long long)(packet.dts * 1000 * strm->time_base.num / strm->time_base.den);
|
||||
if (pkt["time"].asInt() < 0){
|
||||
pkt["time"] = 0ll;
|
||||
}
|
||||
if (packet.flags & AV_PKT_FLAG_KEY && myMeta.tracks[(long long)packet.stream_index + 1].type != "audio"){
|
||||
pkt["keyframe"] = 1ll;
|
||||
pkt["bpos"] = (long long)packet.pos;
|
||||
}
|
||||
if (packet.pts != AV_NOPTS_VALUE && packet.pts != packet.dts){
|
||||
pkt["offset"] = (long long)((packet.pts - packet.dts) * 1000 * strm->time_base.num / strm->time_base.den);
|
||||
}
|
||||
pkt.netPrepare();
|
||||
lastPack.reInit(pkt.toNetPacked().data(), pkt.toNetPacked().size());
|
||||
av_free_packet(&packet);
|
||||
return;//success!
|
||||
}
|
||||
lastPack.null();
|
||||
setup();
|
||||
//failure :-(
|
||||
DEBUG_MSG(DLVL_FAIL, "getNext failed");
|
||||
}
|
||||
|
||||
void inputAV::seek(int seekTime) {
|
||||
int stream_index = av_find_default_stream_index(pFormatCtx);
|
||||
//Convert ts to frame
|
||||
unsigned long long reseekTime = av_rescale(seekTime, pFormatCtx->streams[stream_index]->time_base.den, pFormatCtx->streams[stream_index]->time_base.num);
|
||||
reseekTime /= 1000;
|
||||
unsigned long long seekStreamDuration = pFormatCtx->streams[stream_index]->duration;
|
||||
int flags = AVSEEK_FLAG_BACKWARD;
|
||||
if (reseekTime > 0 && reseekTime < seekStreamDuration){
|
||||
flags |= AVSEEK_FLAG_ANY; // H.264 I frames don't always register as "key frames" in FFmpeg
|
||||
}
|
||||
int ret = av_seek_frame(pFormatCtx, stream_index, reseekTime, flags);
|
||||
if (ret < 0){
|
||||
ret = av_seek_frame(pFormatCtx, stream_index, reseekTime, AVSEEK_FLAG_ANY);
|
||||
}
|
||||
}
|
||||
|
||||
void inputAV::trackSelect(std::string trackSpec) {
|
||||
selectedTracks.clear();
|
||||
long long unsigned int index;
|
||||
while (trackSpec != "") {
|
||||
index = trackSpec.find(' ');
|
||||
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
|
||||
if (index != std::string::npos) {
|
||||
trackSpec.erase(0, index + 1);
|
||||
} else {
|
||||
trackSpec = "";
|
||||
}
|
||||
}
|
||||
//inFile.selectTracks(selectedTracks);
|
||||
}
|
||||
}
|
||||
|
||||
32
src/input/input_av.h
Normal file
32
src/input/input_av.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef INT64_MIN
|
||||
#define INT64_MIN (-(9223372036854775807 ## LL)-1)
|
||||
#endif
|
||||
|
||||
#ifndef INT64_MAX
|
||||
#define INT64_MAX ((9223372036854775807 ## LL))
|
||||
#endif
|
||||
|
||||
#include "input.h"
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
namespace Mist {
|
||||
class inputAV : public Input {
|
||||
public:
|
||||
inputAV(Util::Config * cfg);
|
||||
~inputAV();
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
private:
|
||||
AVFormatContext *pFormatCtx;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputAV mistIn;
|
||||
|
|
@ -31,6 +31,44 @@ namespace Mist {
|
|||
capa["optional"]["DVR"]["option"] = "--buffer";
|
||||
capa["optional"]["DVR"]["type"] = "uint";
|
||||
capa["optional"]["DVR"]["default"] = 50000LL;
|
||||
/*LTS-start*/
|
||||
option.null();
|
||||
option["arg"] = "string";
|
||||
option["long"] = "record";
|
||||
option["short"] = "r";
|
||||
option["help"] = "Record the stream to a file";
|
||||
option["value"].append("");
|
||||
config->addOption("record", option);
|
||||
capa["optional"]["record"]["name"] = "Record to file";
|
||||
capa["optional"]["record"]["help"] = "Filename to record the stream to.";
|
||||
capa["optional"]["record"]["option"] = "--record";
|
||||
capa["optional"]["record"]["type"] = "str";
|
||||
capa["optional"]["record"]["default"] = "";
|
||||
option.null();
|
||||
option["arg"] = "integer";
|
||||
option["long"] = "cut";
|
||||
option["short"] = "c";
|
||||
option["help"] = "Any timestamps before this will be cut from the live buffer";
|
||||
option["value"].append(0LL);
|
||||
config->addOption("cut", option);
|
||||
capa["optional"]["cut"]["name"] = "Cut time (ms)";
|
||||
capa["optional"]["cut"]["help"] = "Any timestamps before this will be cut from the live buffer.";
|
||||
capa["optional"]["cut"]["option"] = "--cut";
|
||||
capa["optional"]["cut"]["type"] = "uint";
|
||||
capa["optional"]["cut"]["default"] = 0LL;
|
||||
option.null();
|
||||
option["arg"] = "integer";
|
||||
option["long"] = "segment-size";
|
||||
option["short"] = "S";
|
||||
option["help"] = "Target time duration in milliseconds for segments";
|
||||
option["value"].append(5000LL);
|
||||
config->addOption("segmentsize", option);
|
||||
capa["optional"]["segmentsize"]["name"] = "Segment size (ms)";
|
||||
capa["optional"]["segmentsize"]["help"] = "Target time duration in milliseconds for segments.";
|
||||
capa["optional"]["segmentsize"]["option"] = "--segment-size";
|
||||
capa["optional"]["segmentsize"]["type"] = "uint";
|
||||
capa["optional"]["segmentsize"]["default"] = 5000LL;
|
||||
/*LTS-end*/
|
||||
capa["source_match"] = "push://*";
|
||||
capa["priority"] = 9ll;
|
||||
capa["desc"] = "Provides buffered live input";
|
||||
|
|
@ -41,6 +79,7 @@ namespace Mist {
|
|||
singleton = this;
|
||||
bufferTime = 0;
|
||||
cutTime = 0;
|
||||
segmentSize = 0;
|
||||
}
|
||||
|
||||
inputBuffer::~inputBuffer() {
|
||||
|
|
@ -48,7 +87,29 @@ namespace Mist {
|
|||
if (myMeta.tracks.size()) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Cleaning up, removing last keyframes");
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
while (removeKey(it->first)) {}
|
||||
std::map<unsigned long, DTSCPageData> & locations = bufferLocations[it->first];
|
||||
|
||||
//First detect all entries on metaPage
|
||||
for (int i = 0; i < 8192; i += 8) {
|
||||
int * tmpOffset = (int *)(metaPages[it->first].mapped + i);
|
||||
if (tmpOffset[0] == 0 && tmpOffset[1] == 0) {
|
||||
continue;
|
||||
}
|
||||
unsigned long keyNum = ntohl(tmpOffset[0]);
|
||||
|
||||
//Add an entry into bufferLocations[tNum] for the pages we haven't handled yet.
|
||||
if (!locations.count(keyNum)) {
|
||||
locations[keyNum].curOffset = 0;
|
||||
}
|
||||
locations[keyNum].pageNum = keyNum;
|
||||
locations[keyNum].keyNum = ntohl(tmpOffset[1]);
|
||||
}
|
||||
for (std::map<unsigned long, DTSCPageData>::iterator it2 = locations.begin(); it2 != locations.end(); it2++){
|
||||
char thisPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), it->first, it2->first);
|
||||
IPC::sharedPage erasePage(thisPageName, 20971520);
|
||||
erasePage.master = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,8 +153,59 @@ namespace Mist {
|
|||
DEBUG_MSG(DLVL_HIGH, "Erasing key %d:%lu", tid, myMeta.tracks[tid].keys[0].getNumber());
|
||||
//remove all parts of this key
|
||||
for (int i = 0; i < myMeta.tracks[tid].keys[0].getParts(); i++) {
|
||||
/*LTS-START*/
|
||||
if (recFile.is_open()) {
|
||||
if (!recMeta.tracks.count(tid)) {
|
||||
recMeta.tracks[tid] = myMeta.tracks[tid];
|
||||
recMeta.tracks[tid].reset();
|
||||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
myMeta.tracks[tid].parts.pop_front();
|
||||
}
|
||||
///\todo Fix recording
|
||||
/*LTS-START
|
||||
///\todo Maybe optimize this by keeping track of the byte positions
|
||||
if (recFile.good()){
|
||||
long long unsigned int firstms = myMeta.tracks[tid].keys[0].getTime();
|
||||
long long unsigned int lastms = myMeta.tracks[tid].lastms;
|
||||
if (myMeta.tracks[tid].keys.size() > 1){
|
||||
lastms = myMeta.tracks[tid].keys[1].getTime();
|
||||
}
|
||||
DEBUG_MSG(DLVL_DEVEL, "Recording track %d from %llums to %llums", tid, firstms, lastms);
|
||||
long long unsigned int bpos = 0;
|
||||
DTSC::Packet recPack;
|
||||
int pageLen = dataPages[tid][bufferLocations[tid].begin()->first].len;
|
||||
char * pageMapped = dataPages[tid][bufferLocations[tid].begin()->first].mapped;
|
||||
while( bpos < (unsigned long long)pageLen) {
|
||||
int tmpSize = ((int)pageMapped[bpos + 4] << 24) | ((int)pageMapped[bpos + 5] << 16) | ((int)pageMapped[bpos + 6] << 8) | (int)pageMapped[bpos + 7];
|
||||
tmpSize += 8;
|
||||
recPack.reInit(pageMapped + bpos, tmpSize, true);
|
||||
if (tmpSize != recPack.getDataLen()){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Something went wrong while trying to record a packet @ %llu, %d != %d", bpos, tmpSize, recPack.getDataLen());
|
||||
break;
|
||||
}
|
||||
if (recPack.getTime() >= lastms){/// \todo getTime never reaches >= lastms, so probably the recording bug has something to do with this
|
||||
DEBUG_MSG(DLVL_HIGH, "Stopping record, %llu >= %llu", recPack.getTime(), lastms);
|
||||
break;
|
||||
}
|
||||
if (recPack.getTime() >= firstms){
|
||||
//Actually record to file here
|
||||
JSON::Value recJSON = recPack.toJSON();
|
||||
recJSON["bpos"] = recBpos;
|
||||
recFile << recJSON.toNetPacked();
|
||||
recFile.flush();
|
||||
recBpos = recFile.tellp();
|
||||
recMeta.update(recJSON);
|
||||
}
|
||||
bpos += recPack.getDataLen();
|
||||
}
|
||||
recFile.flush();
|
||||
std::ofstream tmp(std::string(recName + ".dtsh").c_str());
|
||||
tmp << recMeta.toJSON().toNetPacked();
|
||||
tmp.close();
|
||||
}
|
||||
LTS-END*/
|
||||
//remove the key itself
|
||||
myMeta.tracks[tid].keys.pop_front();
|
||||
myMeta.tracks[tid].keySizes.pop_front();
|
||||
|
|
@ -242,6 +354,12 @@ namespace Mist {
|
|||
}
|
||||
|
||||
void inputBuffer::userCallback(char * data, size_t len, unsigned int id) {
|
||||
/*LTS-START*/
|
||||
//Reload the configuration to make sure we stay up to date with changes through the api
|
||||
if (Util::epoch() - lastReTime > 4) {
|
||||
setup();
|
||||
}
|
||||
/*LTS-END*/
|
||||
//Static variable keeping track of the next temporary mapping to use for a track.
|
||||
static int nextTempId = 1001;
|
||||
//Get the counter of this user
|
||||
|
|
@ -331,6 +449,18 @@ namespace Mist {
|
|||
|
||||
std::string trackIdentifier = trackMeta.tracks.find(value)->second.getIdentifier();
|
||||
DEBUG_MSG(DLVL_HIGH, "Attempting colision detection for track %s", trackIdentifier.c_str());
|
||||
/*LTS-START*/
|
||||
//Get the identifier for the track, and attempt colission detection.
|
||||
int collidesWith = -1;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
//If the identifier of an existing track and the current track match, assume the are the same track and reject the negotiated one.
|
||||
///\todo Maybe switch to a new form of detecting collisions, especially with regards to multiple audio languages and camera angles.
|
||||
if (it->second.getIdentifier() == trackIdentifier) {
|
||||
collidesWith = it->first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
|
||||
//Remove the "negotiate" status in either case
|
||||
negotiatingTracks.erase(value);
|
||||
|
|
@ -338,15 +468,49 @@ namespace Mist {
|
|||
metaPages[value].master = true;
|
||||
metaPages.erase(value);
|
||||
|
||||
int finalMap = (trackMeta.tracks.find(value)->second.type == "video" ? 1 : 2);
|
||||
//Check if the track collides, and whether the track it collides with is active.
|
||||
if (collidesWith != -1 && activeTracks.count(collidesWith)) {/*LTS*/
|
||||
//Print a warning message and set the state of the track to rejected.
|
||||
WARN_MSG("Collision of temporary track %lu with existing track %d detected. Handling as a new valid track.", value, collidesWith);
|
||||
collidesWith = -1;
|
||||
}
|
||||
/*LTS-START*/
|
||||
unsigned long finalMap = collidesWith;
|
||||
if (finalMap == -1) {
|
||||
//No collision has been detected, assign a new final number
|
||||
finalMap = (myMeta.tracks.size() ? myMeta.tracks.rbegin()->first : 0) + 1;
|
||||
DEBUG_MSG(DLVL_DEVEL, "No colision detected for temporary track %lu from user %u, assigning final track number %lu", value, id, finalMap);
|
||||
}
|
||||
/*LTS-END*/
|
||||
//Resume either if we have more than 1 keyframe on the replacement track (assume it was already pushing before the track "dissapeared")
|
||||
//or if the firstms of the replacement track is later than the lastms on the existing track
|
||||
if (!myMeta.tracks.count(finalMap) || trackMeta.tracks.find(value)->second.keys.size() > 1 || trackMeta.tracks.find(value)->second.firstms >= myMeta.tracks[finalMap].lastms) {
|
||||
if (myMeta.tracks.count(finalMap) && myMeta.tracks[finalMap].lastms > 0) {
|
||||
INFO_MSG("Resume of track %d detected, coming from temporary track %lu of user %u", finalMap, value, id);
|
||||
INFO_MSG("Resume of track %lu detected, coming from temporary track %lu of user %u", finalMap, value, id);
|
||||
} else {
|
||||
INFO_MSG("New track detected, assigned track id %d, coming from temporary track %lu of user %u", finalMap, value, id);
|
||||
INFO_MSG("New track detected, assigned track id %lu, coming from temporary track %lu of user %u", finalMap, value, id);
|
||||
}
|
||||
} else {
|
||||
//Otherwise replace existing track
|
||||
INFO_MSG("Replacement of track %lu detected, coming from temporary track %lu of user %u", finalMap, value, id);
|
||||
myMeta.tracks.erase(finalMap);
|
||||
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
||||
metaPages[finalMap].master = true;
|
||||
metaPages.erase(finalMap);
|
||||
bufferLocations.erase(finalMap);
|
||||
}
|
||||
|
||||
//Register the new track as an active track.
|
||||
activeTracks.insert(finalMap);
|
||||
//Register the time of registration as initial value for the lastUpdated field.
|
||||
lastUpdated[finalMap] = Util::bootSecs();
|
||||
//Register the user thats is pushing this element
|
||||
pushLocation[finalMap] = thisData;
|
||||
//Initialize the metadata for this track if it was not in place yet.
|
||||
if (!myMeta.tracks.count(finalMap)) {
|
||||
DEBUG_MSG(DLVL_HIGH, "Inserting metadata for track number %lu", finalMap);
|
||||
myMeta.tracks[finalMap] = trackMeta.tracks.begin()->second;
|
||||
myMeta.tracks[finalMap].trackID = finalMap;
|
||||
}
|
||||
|
||||
//Register the new track as an active track.
|
||||
|
|
@ -456,7 +620,8 @@ namespace Mist {
|
|||
lastUpdated[tNum] = Util::bootSecs();
|
||||
while (tmpPack) {
|
||||
//Update the metadata with this packet
|
||||
myMeta.update(tmpPack);
|
||||
///\todo Why is there an LTS tag here?
|
||||
myMeta.update(tmpPack, segmentSize);/*LTS*/
|
||||
//Set the first time when appropriate
|
||||
if (pageData.firstTime == 0) {
|
||||
pageData.firstTime = tmpPack.getTime();
|
||||
|
|
@ -469,6 +634,7 @@ namespace Mist {
|
|||
}
|
||||
|
||||
bool inputBuffer::setup() {
|
||||
lastReTime = Util::epoch(); /*LTS*/
|
||||
std::string strName = config->getString("streamname");
|
||||
Util::sanitizeName(strName);
|
||||
strName = strName.substr(0, (strName.find_first_of("+ ")));
|
||||
|
|
@ -496,6 +662,74 @@ namespace Mist {
|
|||
bufferTime = tmpNum;
|
||||
}
|
||||
|
||||
/*LTS-START*/
|
||||
//if stream is configured and setting is present, use it, always
|
||||
if (streamCfg && streamCfg.getMember("cut")) {
|
||||
tmpNum = streamCfg.getMember("cut").asInt();
|
||||
} else {
|
||||
if (streamCfg) {
|
||||
//otherwise, if stream is configured use the default
|
||||
tmpNum = config->getOption("cut", true)[0u].asInt();
|
||||
} else {
|
||||
//if not, use the commandline argument
|
||||
tmpNum = config->getOption("cut").asInt();
|
||||
}
|
||||
}
|
||||
//if the new value is different, print a message and apply it
|
||||
if (cutTime != tmpNum) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Setting cutTime from %u to new value of %lli", cutTime, tmpNum);
|
||||
cutTime = tmpNum;
|
||||
}
|
||||
|
||||
|
||||
//if stream is configured and setting is present, use it, always
|
||||
if (streamCfg && streamCfg.getMember("segmentsize")) {
|
||||
tmpNum = streamCfg.getMember("segmentsize").asInt();
|
||||
} else {
|
||||
if (streamCfg) {
|
||||
//otherwise, if stream is configured use the default
|
||||
tmpNum = config->getOption("segmentsize", true)[0u].asInt();
|
||||
} else {
|
||||
//if not, use the commandline argument
|
||||
tmpNum = config->getOption("segmentsize").asInt();
|
||||
}
|
||||
}
|
||||
//if the new value is different, print a message and apply it
|
||||
if (segmentSize != tmpNum) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Setting segmentSize from %u to new value of %lli", segmentSize, tmpNum);
|
||||
segmentSize = tmpNum;
|
||||
}
|
||||
|
||||
//if stream is configured and setting is present, use it, always
|
||||
std::string rec;
|
||||
if (streamCfg && streamCfg.getMember("record")) {
|
||||
rec = streamCfg.getMember("record").asInt();
|
||||
} else {
|
||||
if (streamCfg) {
|
||||
//otherwise, if stream is configured use the default
|
||||
rec = config->getOption("record", true)[0u].asString();
|
||||
} else {
|
||||
//if not, use the commandline argument
|
||||
rec = config->getOption("record").asString();
|
||||
}
|
||||
}
|
||||
//if the new value is different, print a message and apply it
|
||||
if (recName != rec) {
|
||||
//close currently recording file, for we should open a new one
|
||||
DEBUG_MSG(DLVL_DEVEL, "Stopping recording of %s to %s", config->getString("streamname").c_str(), recName.c_str());
|
||||
recFile.close();
|
||||
recMeta.tracks.clear();
|
||||
recName = rec;
|
||||
}
|
||||
if (recName != "" && !recFile.is_open()) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Starting recording of %s to %s", config->getString("streamname").c_str(), recName.c_str());
|
||||
recFile.open(recName.c_str());
|
||||
if (recFile.fail()) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Error occured during record opening: %s", strerror(errno));
|
||||
}
|
||||
recBpos = 0;
|
||||
}
|
||||
/*LTS-END*/
|
||||
configLock.post();
|
||||
configLock.close();
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#include <fstream>
|
||||
|
||||
#include "input.h"
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/shared_memory.h>
|
||||
|
|
@ -10,6 +12,8 @@ namespace Mist {
|
|||
private:
|
||||
unsigned int bufferTime;
|
||||
unsigned int cutTime;
|
||||
unsigned int segmentSize; /*LTS*/
|
||||
unsigned int lastReTime; /*LTS*/
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
|
|
@ -31,6 +35,11 @@ namespace Mist {
|
|||
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > bufferLocations;
|
||||
std::map<unsigned long, char *> pushLocation;
|
||||
inputBuffer * singleton;
|
||||
|
||||
std::string recName;/*LTS*/
|
||||
DTSC::Meta recMeta;/*LTS*/
|
||||
std::ofstream recFile;/*LTS*/
|
||||
long long int recBpos;/*LTS*/
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
19
src/input/input_folder.cpp
Normal file
19
src/input/input_folder.cpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
#include "input_folder.h"
|
||||
|
||||
namespace Mist {
|
||||
inputFolder::inputFolder(Util::Config * cfg) : Input(cfg) {
|
||||
capa["name"] = "Folder";
|
||||
capa["decs"] = "Folder input, re-starts itself as the appropiate input.";
|
||||
capa["source_match"] = "/*/";
|
||||
capa["priority"] = 9ll;
|
||||
}
|
||||
}
|
||||
14
src/input/input_folder.h
Normal file
14
src/input/input_folder.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#include "input.h"
|
||||
#include <mist/dtsc.h>
|
||||
|
||||
namespace Mist {
|
||||
class inputFolder : public Input {
|
||||
public:
|
||||
inputFolder(Util::Config * cfg);
|
||||
protected:
|
||||
bool setup(){return false;};
|
||||
bool readHeader(){return false;};
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputFolder mistIn;
|
||||
407
src/input/input_ismv.cpp
Normal file
407
src/input/input_ismv.cpp
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
#include "input_ismv.h"
|
||||
|
||||
namespace Mist {
|
||||
inputISMV::inputISMV(Util::Config * cfg) : Input(cfg) {
|
||||
capa["name"] = "ISMV";
|
||||
capa["decs"] = "Enables ISMV Input";
|
||||
capa["source_match"] = "/*.ismv";
|
||||
capa["priority"] = 9ll;
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
|
||||
inFile = 0;
|
||||
}
|
||||
|
||||
bool inputISMV::setup() {
|
||||
if (config->getString("input") == "-") {
|
||||
std::cerr << "Input from stdin not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (!config->getString("streamname").size()){
|
||||
if (config->getString("output") == "-") {
|
||||
std::cerr << "Output to stdout not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
if (config->getString("output") != "-") {
|
||||
std::cerr << "File output in player mode not supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//open File
|
||||
inFile = fopen(config->getString("input").c_str(), "r");
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool inputISMV::readHeader() {
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
//See whether a separate header file exists.
|
||||
DTSC::File tmp(config->getString("input") + ".dtsh");
|
||||
if (tmp) {
|
||||
myMeta = tmp.getMeta();
|
||||
return true;
|
||||
}
|
||||
//parse ismv header
|
||||
fseek(inFile, 0, SEEK_SET);
|
||||
std::string ftyp;
|
||||
readBox("ftyp", ftyp);
|
||||
if (ftyp == ""){
|
||||
return false;
|
||||
}
|
||||
std::string boxRes;
|
||||
readBox("moov", boxRes);
|
||||
if (boxRes == ""){
|
||||
return false;
|
||||
}
|
||||
MP4::MOOV hdrBox;
|
||||
hdrBox.read(boxRes);
|
||||
parseMoov(hdrBox);
|
||||
int tId;
|
||||
std::vector<MP4::trunSampleInformation> trunSamples;
|
||||
std::vector<std::string> initVecs;
|
||||
std::string mdat;
|
||||
unsigned int currOffset;
|
||||
JSON::Value lastPack;
|
||||
unsigned int lastBytePos = 0;
|
||||
std::map<int, unsigned int> currentDuration;
|
||||
unsigned int curBytePos = ftell(inFile);
|
||||
//parse fragments form here
|
||||
while (parseFrag(tId, trunSamples, initVecs, mdat)) {
|
||||
if (!currentDuration.count(tId)) {
|
||||
currentDuration[tId] = 0;
|
||||
}
|
||||
currOffset = 8;
|
||||
int i = 0;
|
||||
while (currOffset < mdat.size()) {
|
||||
lastPack.null();
|
||||
lastPack["time"] = currentDuration[tId] / 10000;
|
||||
lastPack["trackid"] = tId;
|
||||
lastPack["data"] = mdat.substr(currOffset, trunSamples[i].sampleSize);
|
||||
if (initVecs.size() == trunSamples.size()) {
|
||||
lastPack["ivec"] = initVecs[i];
|
||||
}
|
||||
lastPack["duration"] = trunSamples[i].sampleDuration;
|
||||
if (myMeta.tracks[tId].type == "video") {
|
||||
if (i) {
|
||||
lastPack["interframe"] = 1LL;
|
||||
lastBytePos ++;
|
||||
} else {
|
||||
lastPack["keyframe"] = 1LL;
|
||||
lastBytePos = curBytePos;
|
||||
}
|
||||
lastPack["bpos"] = lastBytePos;
|
||||
lastPack["nalu"] = 1LL;
|
||||
unsigned int offsetConv = trunSamples[i].sampleOffset / 10000;
|
||||
lastPack["offset"] = *(int*)&offsetConv;
|
||||
} else {
|
||||
if (i == 0) {
|
||||
lastPack["keyframe"] = 1LL;
|
||||
lastPack["bpos"] = curBytePos;
|
||||
}
|
||||
}
|
||||
myMeta.update(lastPack);
|
||||
currentDuration[tId] += trunSamples[i].sampleDuration;
|
||||
currOffset += trunSamples[i].sampleSize;
|
||||
i ++;
|
||||
}
|
||||
curBytePos = ftell(inFile);
|
||||
}
|
||||
std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str());
|
||||
oFile << myMeta.toJSON().toNetPacked();
|
||||
oFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputISMV::getNext(bool smart) {
|
||||
static JSON::Value thisPack;
|
||||
thisPack.null();
|
||||
if (!buffered.size()){
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
int tId = buffered.begin()->trackId;
|
||||
thisPack["time"] = (long long int)(buffered.begin()->time / 10000);
|
||||
thisPack["trackid"] = tId;
|
||||
fseek(inFile, buffered.begin()->position, SEEK_SET);
|
||||
char * tmpData = (char*)malloc(buffered.begin()->size * sizeof(char));
|
||||
fread(tmpData, buffered.begin()->size, 1, inFile);
|
||||
thisPack["data"] = std::string(tmpData, buffered.begin()->size);
|
||||
free(tmpData);
|
||||
if (buffered.begin()->iVec != "") {
|
||||
thisPack["ivec"] = buffered.begin()->iVec;
|
||||
}
|
||||
if (myMeta.tracks[tId].type == "video") {
|
||||
if (buffered.begin()->isKeyFrame) {
|
||||
thisPack["keyframe"] = 1LL;
|
||||
} else {
|
||||
thisPack["interframe"] = 1LL;
|
||||
}
|
||||
thisPack["nalu"] = 1LL;
|
||||
thisPack["offset"] = buffered.begin()->offset / 10000;
|
||||
} else {
|
||||
if (buffered.begin()->isKeyFrame) {
|
||||
thisPack["keyframe"] = 1LL;
|
||||
}
|
||||
}
|
||||
thisPack["bpos"] = buffered.begin()->position;
|
||||
buffered.erase(buffered.begin());
|
||||
if (buffered.size() < 2 * selectedTracks.size()){
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
parseFragHeader(*it, lastKeyNum[*it]);
|
||||
lastKeyNum[*it]++;
|
||||
}
|
||||
}
|
||||
std::string tmpStr = thisPack.toNetPacked();
|
||||
thisPacket.reInit(tmpStr.data(), tmpStr.size());
|
||||
}
|
||||
|
||||
///\brief Overloads Input::atKeyFrame, for ISMV always sets the keyframe number
|
||||
bool inputISMV::atKeyFrame(){
|
||||
return thisPacket.getFlag("keyframe");
|
||||
}
|
||||
|
||||
void inputISMV::seek(int seekTime) {
|
||||
buffered.clear();
|
||||
//Seek to corresponding keyframes on EACH track
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
unsigned int i;
|
||||
for (i = 0; i < myMeta.tracks[*it].keys.size(); i++){
|
||||
if (myMeta.tracks[*it].keys[i].getTime() > seekTime && i > 0){//Ehh, whut?
|
||||
break;
|
||||
}
|
||||
}
|
||||
i --;
|
||||
DEBUG_MSG(DLVL_DEVEL, "ISMV seek frag %d:%d", *it, i);
|
||||
parseFragHeader(*it, i);
|
||||
lastKeyNum[*it] = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void inputISMV::trackSelect(std::string trackSpec) {
|
||||
selectedTracks.clear();
|
||||
size_t index;
|
||||
while (trackSpec != "") {
|
||||
index = trackSpec.find(' ');
|
||||
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
|
||||
if (index != std::string::npos) {
|
||||
trackSpec.erase(0, index + 1);
|
||||
} else {
|
||||
trackSpec = "";
|
||||
}
|
||||
}
|
||||
seek(0);
|
||||
}
|
||||
|
||||
void inputISMV::parseMoov(MP4::MOOV & moovBox) {
|
||||
for (unsigned int i = 0; i < moovBox.getContentCount(); i++) {
|
||||
if (moovBox.getContent(i).isType("mvhd")) {
|
||||
MP4::MVHD content = (MP4::MVHD &)moovBox.getContent(i);
|
||||
}
|
||||
if (moovBox.getContent(i).isType("trak")) {
|
||||
MP4::TRAK content = (MP4::TRAK &)moovBox.getContent(i);
|
||||
int trackId;
|
||||
for (unsigned int j = 0; j < content.getContentCount(); j++) {
|
||||
if (content.getContent(j).isType("tkhd")) {
|
||||
MP4::TKHD subContent = (MP4::TKHD &)content.getContent(j);
|
||||
trackId = subContent.getTrackID();
|
||||
myMeta.tracks[trackId].trackID = trackId;
|
||||
}
|
||||
if (content.getContent(j).isType("mdia")) {
|
||||
MP4::MDIA subContent = (MP4::MDIA &)content.getContent(j);
|
||||
for (unsigned int k = 0; k < subContent.getContentCount(); k++) {
|
||||
if (subContent.getContent(k).isType("hdlr")) {
|
||||
MP4::HDLR subsubContent = (MP4::HDLR &)subContent.getContent(k);
|
||||
if (subsubContent.getHandlerType() == "soun") {
|
||||
myMeta.tracks[trackId].type = "audio";
|
||||
}
|
||||
if (subsubContent.getHandlerType() == "vide") {
|
||||
myMeta.tracks[trackId].type = "video";
|
||||
}
|
||||
}
|
||||
if (subContent.getContent(k).isType("minf")) {
|
||||
MP4::MINF subsubContent = (MP4::MINF &)subContent.getContent(k);
|
||||
for (unsigned int l = 0; l < subsubContent.getContentCount(); l++) {
|
||||
if (subsubContent.getContent(l).isType("stbl")) {
|
||||
MP4::STBL stblBox = (MP4::STBL &)subsubContent.getContent(l);
|
||||
for (unsigned int m = 0; m < stblBox.getContentCount(); m++) {
|
||||
if (stblBox.getContent(m).isType("stsd")) {
|
||||
MP4::STSD stsdBox = (MP4::STSD &)stblBox.getContent(m);
|
||||
for (unsigned int n = 0; n < stsdBox.getEntryCount(); n++) {
|
||||
if (stsdBox.getEntry(n).isType("mp4a") || stsdBox.getEntry(n).isType("enca")) {
|
||||
MP4::MP4A mp4aBox = (MP4::MP4A &)stsdBox.getEntry(n);
|
||||
myMeta.tracks[trackId].codec = "AAC";
|
||||
std::string tmpStr;
|
||||
tmpStr += (char)((mp4aBox.toAACInit() & 0xFF00) >> 8);
|
||||
tmpStr += (char)(mp4aBox.toAACInit() & 0x00FF);
|
||||
myMeta.tracks[trackId].init = tmpStr;
|
||||
myMeta.tracks[trackId].channels = mp4aBox.getChannelCount();
|
||||
myMeta.tracks[trackId].size = mp4aBox.getSampleSize();
|
||||
myMeta.tracks[trackId].rate = mp4aBox.getSampleRate();
|
||||
}
|
||||
if (stsdBox.getEntry(n).isType("avc1") || stsdBox.getEntry(n).isType("encv")) {
|
||||
MP4::AVC1 avc1Box = (MP4::AVC1 &)stsdBox.getEntry(n);
|
||||
myMeta.tracks[trackId].height = avc1Box.getHeight();
|
||||
myMeta.tracks[trackId].width = avc1Box.getWidth();
|
||||
myMeta.tracks[trackId].init = std::string(avc1Box.getCLAP().payload(), avc1Box.getCLAP().payloadSize());
|
||||
myMeta.tracks[trackId].codec = "H264";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool inputISMV::parseFrag(int & tId, std::vector<MP4::trunSampleInformation> & trunSamples, std::vector<std::string> & initVecs, std::string & mdat) {
|
||||
tId = -1;
|
||||
trunSamples.clear();
|
||||
initVecs.clear();
|
||||
mdat.clear();
|
||||
std::string boxRes;
|
||||
readBox("moof", boxRes);
|
||||
if (boxRes == ""){
|
||||
return false;
|
||||
}
|
||||
MP4::MOOF moof;
|
||||
moof.read(boxRes);
|
||||
for (unsigned int i = 0; i < moof.getContentCount(); i++) {
|
||||
if (moof.getContent(i).isType("traf")) {
|
||||
MP4::TRAF trafBox = (MP4::TRAF &)moof.getContent(i);
|
||||
for (unsigned int j = 0; j < trafBox.getContentCount(); j++) {
|
||||
if (trafBox.getContent(j).isType("trun")) {
|
||||
MP4::TRUN trunBox = (MP4::TRUN &)trafBox.getContent(j);
|
||||
for (unsigned int i = 0; i < trunBox.getSampleInformationCount(); i++) {
|
||||
trunSamples.push_back(trunBox.getSampleInformation(i));
|
||||
}
|
||||
}
|
||||
if (trafBox.getContent(j).isType("tfhd")) {
|
||||
tId = ((MP4::TFHD &)trafBox.getContent(j)).getTrackID();
|
||||
}
|
||||
/*LTS-START*/
|
||||
if (trafBox.getContent(j).isType("uuid")) {
|
||||
if (((MP4::UUID &)trafBox.getContent(j)).getUUID() == "a2394f52-5a9b-4f14-a244-6c427c648df4") {
|
||||
MP4::UUID_SampleEncryption uuidBox = (MP4::UUID_SampleEncryption &)trafBox.getContent(j);
|
||||
for (unsigned int i = 0; i < uuidBox.getSampleCount(); i++) {
|
||||
initVecs.push_back(uuidBox.getSample(i).InitializationVector);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
}
|
||||
}
|
||||
}
|
||||
readBox("mdat", mdat);
|
||||
if (mdat ==""){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputISMV::parseFragHeader(const unsigned int & trackId, const unsigned int & keyNum) {
|
||||
if (!myMeta.tracks.count(trackId) || (myMeta.tracks[trackId].keys.size() <= keyNum)) {
|
||||
return;
|
||||
}
|
||||
long long int lastPos = myMeta.tracks[trackId].keys[keyNum].getBpos();
|
||||
long long int lastTime = myMeta.tracks[trackId].keys[keyNum].getTime() * 10000;
|
||||
fseek(inFile, lastPos, SEEK_SET);
|
||||
std::string boxRes;
|
||||
readBox("moof", boxRes);
|
||||
if (boxRes == ""){
|
||||
return;
|
||||
}
|
||||
MP4::MOOF moof;
|
||||
moof.read(boxRes);
|
||||
MP4::TRUN trunBox;
|
||||
MP4::UUID_SampleEncryption uuidBox; /*LTS*/
|
||||
for (unsigned int i = 0; i < moof.getContentCount(); i++) {
|
||||
if (moof.getContent(i).isType("traf")) {
|
||||
MP4::TRAF trafBox = (MP4::TRAF &)moof.getContent(i);
|
||||
for (unsigned int j = 0; j < trafBox.getContentCount(); j++) {
|
||||
if (trafBox.getContent(j).isType("trun")) {
|
||||
trunBox = (MP4::TRUN &)trafBox.getContent(j);
|
||||
}
|
||||
if (trafBox.getContent(j).isType("tfhd")) {
|
||||
if (trackId != ((MP4::TFHD &)trafBox.getContent(j)).getTrackID()){
|
||||
DEBUG_MSG(DLVL_FAIL,"Trackids do not match");
|
||||
return;
|
||||
}
|
||||
}
|
||||
/*LTS-START*/
|
||||
if (trafBox.getContent(j).isType("uuid")) {
|
||||
if (((MP4::UUID &)trafBox.getContent(j)).getUUID() == "a2394f52-5a9b-4f14-a244-6c427c648df4") {
|
||||
uuidBox = (MP4::UUID_SampleEncryption &)trafBox.getContent(j);
|
||||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
}
|
||||
}
|
||||
}
|
||||
lastPos = ftell(inFile) + 8;
|
||||
for (unsigned int i = 0; i < trunBox.getSampleInformationCount(); i++){
|
||||
seekPos myPos;
|
||||
myPos.position = lastPos;
|
||||
myPos.trackId = trackId;
|
||||
myPos.time = lastTime;
|
||||
myPos.duration = trunBox.getSampleInformation(i).sampleDuration;
|
||||
myPos.size = trunBox.getSampleInformation(i).sampleSize;
|
||||
if( trunBox.getFlags() & MP4::trunsampleOffsets){
|
||||
unsigned int offsetConv = trunBox.getSampleInformation(i).sampleOffset;
|
||||
myPos.offset = *(int*)&offsetConv;
|
||||
}else{
|
||||
myPos.offset = 0;
|
||||
}
|
||||
myPos.isKeyFrame = (i == 0);
|
||||
/*LTS-START*/
|
||||
if (i <= uuidBox.getSampleCount()){
|
||||
myPos.iVec = uuidBox.getSample(i).InitializationVector;
|
||||
}
|
||||
/*LTS-END*/
|
||||
lastTime += trunBox.getSampleInformation(i).sampleDuration;
|
||||
lastPos += trunBox.getSampleInformation(i).sampleSize;
|
||||
buffered.insert(myPos);
|
||||
}
|
||||
}
|
||||
|
||||
void inputISMV::readBox(const char * type, std::string & result) {
|
||||
int pos = ftell(inFile);
|
||||
char mp4Head[8];
|
||||
fread(mp4Head, 8, 1, inFile);
|
||||
fseek(inFile, pos, SEEK_SET);
|
||||
if (memcmp(mp4Head + 4, type, 4)) {
|
||||
DEBUG_MSG(DLVL_FAIL, "No %.4s box found at position %d", type, pos);
|
||||
result = "";
|
||||
return;
|
||||
}
|
||||
unsigned int boxSize = (mp4Head[0] << 24) + (mp4Head[1] << 16) + (mp4Head[2] << 8) + mp4Head[3];
|
||||
char * tmpBox = (char *)malloc(boxSize * sizeof(char));
|
||||
fread(tmpBox, boxSize, 1, inFile);
|
||||
result = std::string(tmpBox, boxSize);
|
||||
free(tmpBox);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
51
src/input/input_ismv.h
Normal file
51
src/input/input_ismv.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#include "input.h"
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/mp4.h>
|
||||
#include <mist/mp4_generic.h>
|
||||
#include <mist/mp4_encryption.h>
|
||||
#include <set>
|
||||
|
||||
namespace Mist {
|
||||
struct seekPos {
|
||||
bool operator < (const seekPos & rhs) const {
|
||||
if (time < rhs.time){
|
||||
return true;
|
||||
}
|
||||
return (time == rhs.time && trackId < rhs.trackId);
|
||||
}
|
||||
long long int position;
|
||||
int trackId;
|
||||
long long int time;
|
||||
long long int duration;
|
||||
int size;
|
||||
long long int offset;
|
||||
bool isKeyFrame;
|
||||
std::string iVec;
|
||||
};
|
||||
|
||||
|
||||
class inputISMV : public Input {
|
||||
public:
|
||||
inputISMV(Util::Config * cfg);
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
bool atKeyFrame();
|
||||
|
||||
FILE * inFile;
|
||||
|
||||
void parseMoov(MP4::MOOV & moovBox);
|
||||
bool parseFrag(int & tId, std::vector<MP4::trunSampleInformation> & trunSamples, std::vector<std::string> & initVecs, std::string & mdat);
|
||||
void parseFragHeader(const unsigned int & trackId, const unsigned int & keyNum);
|
||||
void readBox(const char * type, std::string & result);
|
||||
std::set<seekPos> buffered;
|
||||
std::map<int, int> lastKeyNum;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputISMV mistIn;
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ namespace Mist {
|
|||
}
|
||||
}
|
||||
if (!offset){
|
||||
DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %llu", filePos);
|
||||
DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %lu", filePos);
|
||||
return;
|
||||
}
|
||||
filePos += offset;
|
||||
|
|
|
|||
632
src/input/input_mp4.cpp
Normal file
632
src/input/input_mp4.cpp
Normal file
|
|
@ -0,0 +1,632 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/flv_tag.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
#include "input_mp4.h"
|
||||
|
||||
namespace Mist {
|
||||
|
||||
mp4TrackHeader::mp4TrackHeader(){
|
||||
initialised = false;
|
||||
stscStart = 0;
|
||||
sampleIndex = 0;
|
||||
deltaIndex = 0;
|
||||
deltaPos = 0;
|
||||
deltaTotal = 0;
|
||||
offsetIndex = 0;
|
||||
offsetPos = 0;
|
||||
sttsBox.clear();
|
||||
cttsBox.clear();
|
||||
stszBox.clear();
|
||||
stcoBox.clear();
|
||||
}
|
||||
|
||||
long unsigned int mp4TrackHeader::size(){
|
||||
if (!stszBox.asBox()){
|
||||
return 0;
|
||||
}else{
|
||||
return stszBox.getSampleCount();
|
||||
}
|
||||
}
|
||||
|
||||
void mp4TrackHeader::read(MP4::TRAK & trakBox){
|
||||
initialised = false;
|
||||
std::string tmp;//temporary string for copying box data
|
||||
MP4::Box trakLoopPeek;
|
||||
timeScale = 1;
|
||||
//for all in trak
|
||||
for (uint32_t j = 0; j < trakBox.getContentCount(); j++){
|
||||
trakLoopPeek = MP4::Box(trakBox.getContent(j).asBox(),false);
|
||||
std::string trakBoxType = trakLoopPeek.getType();
|
||||
if (trakBoxType == "mdia"){//fi tkhd & if mdia
|
||||
MP4::Box mdiaLoopPeek;
|
||||
//for all in mdia
|
||||
for (uint32_t k = 0; k < ((MP4::MDIA&)trakLoopPeek).getContentCount(); k++){
|
||||
mdiaLoopPeek = MP4::Box(((MP4::MDIA&)trakLoopPeek).getContent(k).asBox(),false);
|
||||
std::string mdiaBoxType = mdiaLoopPeek.getType();
|
||||
if (mdiaBoxType == "mdhd"){
|
||||
timeScale = ((MP4::MDHD&)mdiaLoopPeek).getTimeScale();
|
||||
}else if (mdiaBoxType == "minf"){//fi hdlr
|
||||
//for all in minf
|
||||
//get all boxes: stco stsz,stss
|
||||
MP4::Box minfLoopPeek;
|
||||
for (uint32_t l = 0; l < ((MP4::MINF&)mdiaLoopPeek).getContentCount(); l++){
|
||||
minfLoopPeek = MP4::Box(((MP4::MINF&)mdiaLoopPeek).getContent(l).asBox(),false);
|
||||
std::string minfBoxType = minfLoopPeek.getType();
|
||||
///\todo more stuff here
|
||||
if (minfBoxType == "stbl"){
|
||||
MP4::Box stblLoopPeek;
|
||||
for (uint32_t m = 0; m < ((MP4::STBL&)minfLoopPeek).getContentCount(); m++){
|
||||
stblLoopPeek = MP4::Box(((MP4::STBL&)minfLoopPeek).getContent(m).asBox(),false);
|
||||
std::string stblBoxType = stblLoopPeek.getType();
|
||||
if (stblBoxType == "stts"){
|
||||
tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize());
|
||||
sttsBox.clear();
|
||||
sttsBox.read(tmp);
|
||||
}else if (stblBoxType == "ctts"){
|
||||
tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize());
|
||||
cttsBox.clear();
|
||||
cttsBox.read(tmp);
|
||||
}else if (stblBoxType == "stsz"){
|
||||
tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize());
|
||||
stszBox.clear();
|
||||
stszBox.read(tmp);
|
||||
}else if (stblBoxType == "stco" || stblBoxType == "co64"){
|
||||
tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize());
|
||||
stcoBox.clear();
|
||||
stcoBox.read(tmp);
|
||||
}else if (stblBoxType == "stsc"){
|
||||
tmp = std::string(stblLoopPeek.asBox() ,stblLoopPeek.boxedSize());
|
||||
stscBox.clear();
|
||||
stscBox.read(tmp);
|
||||
}
|
||||
}//rof stbl
|
||||
}//fi stbl
|
||||
}//rof minf
|
||||
}//fi minf
|
||||
}//rof mdia
|
||||
}//fi mdia
|
||||
}//rof trak
|
||||
}
|
||||
|
||||
void mp4TrackHeader::getPart(long unsigned int index, long long unsigned int & offset,unsigned int& size, long long unsigned int & timestamp, long long unsigned int & timeOffset){
|
||||
|
||||
|
||||
if (index < sampleIndex){
|
||||
sampleIndex = 0;
|
||||
stscStart = 0;
|
||||
}
|
||||
|
||||
while (stscStart < stscBox.getEntryCount()){
|
||||
//check where the next index starts
|
||||
unsigned long long nextSampleIndex;
|
||||
if (stscStart + 1 < stscBox.getEntryCount()){
|
||||
nextSampleIndex = sampleIndex + (stscBox.getSTSCEntry(stscStart+1).firstChunk - stscBox.getSTSCEntry(stscStart).firstChunk) * stscBox.getSTSCEntry(stscStart).samplesPerChunk;
|
||||
}else{
|
||||
nextSampleIndex = stszBox.getSampleCount();
|
||||
}
|
||||
//if the next one is too far, we're in the right spot
|
||||
if (nextSampleIndex > index){
|
||||
break;
|
||||
}
|
||||
sampleIndex = nextSampleIndex;
|
||||
++stscStart;
|
||||
}
|
||||
|
||||
if (sampleIndex > index){
|
||||
FAIL_MSG("Could not complete seek - not in file (%llu > %lu)", sampleIndex, index);
|
||||
}
|
||||
|
||||
long long unsigned stcoPlace = (stscBox.getSTSCEntry(stscStart).firstChunk - 1 ) + ((index - sampleIndex) / stscBox.getSTSCEntry(stscStart).samplesPerChunk);
|
||||
long long unsigned stszStart = sampleIndex + (stcoPlace - (stscBox.getSTSCEntry(stscStart).firstChunk - 1)) * stscBox.getSTSCEntry(stscStart).samplesPerChunk;
|
||||
|
||||
//set the offset to the correct value
|
||||
if (stcoBox.getType() == "co64"){
|
||||
offset = ((MP4::CO64*)&stcoBox)->getChunkOffset(stcoPlace);
|
||||
}else{
|
||||
offset = stcoBox.getChunkOffset(stcoPlace);
|
||||
}
|
||||
|
||||
for (int j = stszStart; j < index; j++){
|
||||
offset += stszBox.getEntrySize(j);
|
||||
}
|
||||
|
||||
if (index < deltaPos){
|
||||
deltaIndex = 0;
|
||||
deltaPos = 0;
|
||||
deltaTotal = 0;
|
||||
}
|
||||
|
||||
MP4::STTSEntry tmpSTTS;
|
||||
while (deltaIndex < sttsBox.getEntryCount()){
|
||||
tmpSTTS = sttsBox.getSTTSEntry(deltaIndex);
|
||||
if ((index - deltaPos) < tmpSTTS.sampleCount){
|
||||
break;
|
||||
}else{
|
||||
deltaTotal += tmpSTTS.sampleCount * tmpSTTS.sampleDelta;
|
||||
deltaPos += tmpSTTS.sampleCount;
|
||||
}
|
||||
++deltaIndex;
|
||||
}
|
||||
timestamp = ((deltaTotal + ((index-deltaPos) * tmpSTTS.sampleDelta))*1000) / timeScale;
|
||||
initialised = true;
|
||||
|
||||
if (index < offsetPos){
|
||||
offsetIndex = 0;
|
||||
offsetPos = 0;
|
||||
}
|
||||
MP4::CTTSEntry tmpCTTS;
|
||||
while (offsetIndex < cttsBox.getEntryCount()){
|
||||
tmpCTTS = cttsBox.getCTTSEntry(offsetIndex);
|
||||
if ((index - offsetPos) < tmpCTTS.sampleCount){
|
||||
timeOffset = (tmpCTTS.sampleOffset*1000)/timeScale;
|
||||
break;
|
||||
}
|
||||
offsetPos += tmpCTTS.sampleCount;
|
||||
++offsetIndex;
|
||||
}
|
||||
|
||||
//next lines are common for next-getting and seeking
|
||||
size = stszBox.getEntrySize(index);
|
||||
|
||||
}
|
||||
|
||||
inputMP4::inputMP4(Util::Config * cfg) : Input(cfg) {
|
||||
malSize = 4;//initialise data read buffer to 0;
|
||||
data = (char*)malloc(malSize);
|
||||
capa["name"] = "MP4";
|
||||
capa["decs"] = "Enables MP4 Input";
|
||||
capa["source_match"] = "/*.mp4";
|
||||
capa["priority"] = 9ll;
|
||||
capa["codecs"][0u][0u].append("HEVC");
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][0u].append("H263");
|
||||
capa["codecs"][0u][0u].append("VP6");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
}
|
||||
|
||||
inputMP4::~inputMP4(){
|
||||
free(data);
|
||||
}
|
||||
|
||||
bool inputMP4::setup() {
|
||||
if (config->getString("input") == "-") {
|
||||
std::cerr << "Input from stdin not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (!config->getString("streamname").size()){
|
||||
if (config->getString("output") == "-") {
|
||||
std::cerr << "Output to stdout not yet supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
if (config->getString("output") != "-") {
|
||||
std::cerr << "File output in player mode not supported" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//open File
|
||||
inFile = fopen(config->getString("input").c_str(), "r");
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool inputMP4::readHeader() {
|
||||
if (!inFile) {
|
||||
INFO_MSG("inFile failed!");
|
||||
return false;
|
||||
}
|
||||
//make trackmap here from inFile
|
||||
long long unsigned int trackNo = 0;
|
||||
|
||||
std::string tmp;//temp string for reading boxes.
|
||||
|
||||
|
||||
//first we get the necessary header parts
|
||||
while(!feof(inFile)){
|
||||
std::string boxType = MP4::readBoxType(inFile);
|
||||
if (boxType=="moov"){
|
||||
MP4::MOOV moovBox;
|
||||
moovBox.read(inFile);
|
||||
//for all box in moov
|
||||
|
||||
MP4::Box moovLoopPeek;
|
||||
for (uint32_t i = 0; i < moovBox.getContentCount(); i++){
|
||||
tmp = std::string(moovBox.getContent(i).asBox() ,moovBox.getContent(i).boxedSize());
|
||||
moovLoopPeek.read(tmp);
|
||||
//if trak
|
||||
if (moovLoopPeek.getType() == "trak"){
|
||||
//create new track entry here
|
||||
trackNo ++;
|
||||
|
||||
headerData[trackNo].read((MP4::TRAK&)moovLoopPeek);
|
||||
}
|
||||
}
|
||||
}else if (boxType == "erro"){
|
||||
break;
|
||||
}else{
|
||||
if (!MP4::skipBox(inFile)){//moving on to next box
|
||||
DEBUG_MSG(DLVL_FAIL,"Error in skipping box, exiting");
|
||||
return false;
|
||||
}
|
||||
}//fi moov
|
||||
}//when at the end of the file
|
||||
//seek file to 0;
|
||||
fseeko(inFile,0,SEEK_SET);
|
||||
|
||||
//See whether a separate header file exists.
|
||||
DTSC::File tmpdtsh(config->getString("input") + ".dtsh");
|
||||
if (tmpdtsh){
|
||||
myMeta = tmpdtsh.getMeta();
|
||||
return true;
|
||||
}
|
||||
trackNo = 0;
|
||||
std::set<mp4PartBpos> BPosSet;
|
||||
//Create header file from MP4 data
|
||||
while(!feof(inFile)){
|
||||
std::string boxType = MP4::readBoxType(inFile);
|
||||
if (boxType=="moov"){
|
||||
MP4::MOOV moovBox;
|
||||
moovBox.read(inFile);
|
||||
//for all box in moov
|
||||
MP4::Box moovLoopPeek;
|
||||
for (uint32_t i = 0; i < moovBox.getContentCount(); i++){
|
||||
tmp = std::string(moovBox.getContent(i).asBox(), moovBox.getContent(i).boxedSize());
|
||||
moovLoopPeek.read(tmp);
|
||||
//if trak
|
||||
if (moovLoopPeek.getType() == "trak"){
|
||||
//create new track entry here
|
||||
long long unsigned int trackNo = myMeta.tracks.size()+1;
|
||||
myMeta.tracks[trackNo].trackID = trackNo;
|
||||
MP4::Box trakLoopPeek;
|
||||
unsigned long int timeScale = 1;
|
||||
//for all in trak
|
||||
for (uint32_t j = 0; j < ((MP4::TRAK&)moovLoopPeek).getContentCount(); j++){
|
||||
tmp = std::string(((MP4::MOOV&)moovLoopPeek).getContent(j).asBox(),((MP4::MOOV&)moovLoopPeek).getContent(j).boxedSize());
|
||||
trakLoopPeek.read(tmp);
|
||||
std::string trakBoxType = trakLoopPeek.getType();
|
||||
//note: per track: trackID codec, type (vid/aud), init
|
||||
//if tkhd
|
||||
if (trakBoxType == "tkhd"){
|
||||
MP4::TKHD tkhdBox(0,0,0,0);///\todo: this can be done with casting
|
||||
tmp = std::string(trakLoopPeek.asBox(), trakLoopPeek.boxedSize());
|
||||
tkhdBox.read(tmp);
|
||||
//remember stuff for decoding stuff later
|
||||
if (tkhdBox.getWidth() > 0){
|
||||
myMeta.tracks[trackNo].width = tkhdBox.getWidth();
|
||||
myMeta.tracks[trackNo].height = tkhdBox.getHeight();
|
||||
}
|
||||
}else if (trakBoxType == "mdia"){//fi tkhd & if mdia
|
||||
MP4::Box mdiaLoopPeek;
|
||||
//for all in mdia
|
||||
for (uint32_t k = 0; k < ((MP4::MDIA&)trakLoopPeek).getContentCount(); k++){
|
||||
tmp = std::string(((MP4::MDIA&)trakLoopPeek).getContent(k).asBox(),((MP4::MDIA&)trakLoopPeek).getContent(k).boxedSize());
|
||||
mdiaLoopPeek.read(tmp);
|
||||
std::string mdiaBoxType = mdiaLoopPeek.getType();
|
||||
if (mdiaBoxType == "mdhd"){
|
||||
timeScale = ((MP4::MDHD&)mdiaLoopPeek).getTimeScale();
|
||||
}else if (mdiaBoxType == "hdlr"){//fi mdhd
|
||||
std::string handlerType = ((MP4::HDLR&)mdiaLoopPeek).getHandlerType();
|
||||
if (handlerType != "vide" && handlerType !="soun"){
|
||||
myMeta.tracks.erase(trackNo);
|
||||
//skip meta boxes for now
|
||||
break;
|
||||
}
|
||||
}else if (mdiaBoxType == "minf"){//fi hdlr
|
||||
//for all in minf
|
||||
//get all boxes: stco stsz,stss
|
||||
MP4::Box minfLoopPeek;
|
||||
for (uint32_t l = 0; l < ((MP4::MINF&)mdiaLoopPeek).getContentCount(); l++){
|
||||
tmp = std::string(((MP4::MINF&)mdiaLoopPeek).getContent(l).asBox(),((MP4::MINF&)mdiaLoopPeek).getContent(l).boxedSize());
|
||||
minfLoopPeek.read(tmp);
|
||||
std::string minfBoxType = minfLoopPeek.getType();
|
||||
///\todo more stuff here
|
||||
if (minfBoxType == "stbl"){
|
||||
MP4::Box stblLoopPeek;
|
||||
MP4::STSS stssBox;
|
||||
MP4::STTS sttsBox;
|
||||
MP4::STSZ stszBox;
|
||||
MP4::STCO stcoBox;
|
||||
MP4::STSC stscBox;
|
||||
for (uint32_t m = 0; m < ((MP4::STBL&)minfLoopPeek).getContentCount(); m++){
|
||||
tmp = std::string(((MP4::STBL&)minfLoopPeek).getContent(m).asBox(),((MP4::STBL&)minfLoopPeek).getContent(m).boxedSize());
|
||||
std::string stboxRead = tmp;
|
||||
stblLoopPeek.read(tmp);
|
||||
std::string stblBoxType = stblLoopPeek.getType();
|
||||
if (stblBoxType == "stss"){
|
||||
stssBox.read(stboxRead);
|
||||
}else if (stblBoxType == "stts"){
|
||||
sttsBox.read(stboxRead);
|
||||
}else if (stblBoxType == "stsz"){
|
||||
stszBox.read(stboxRead);
|
||||
}else if (stblBoxType == "stco" || stblBoxType == "co64"){
|
||||
stcoBox.read(stboxRead);
|
||||
}else if (stblBoxType == "stsc"){
|
||||
stscBox.read(stboxRead);
|
||||
}else if (stblBoxType == "stsd"){
|
||||
//check for codec in here
|
||||
MP4::Box & tmpBox = ((MP4::STSD&)stblLoopPeek).getEntry(0);
|
||||
std::string tmpType = tmpBox.getType();
|
||||
INFO_MSG("Found track of type %s", tmpType.c_str());
|
||||
if (tmpType == "avc1" || tmpType == "h264" || tmpType == "mp4v"){
|
||||
myMeta.tracks[trackNo].type = "video";
|
||||
myMeta.tracks[trackNo].codec = "H264";
|
||||
myMeta.tracks[trackNo].width = ((MP4::VisualSampleEntry&)tmpBox).getWidth();
|
||||
myMeta.tracks[trackNo].height = ((MP4::VisualSampleEntry&)tmpBox).getHeight();
|
||||
MP4::Box tmpBox2 = tmpBox;
|
||||
MP4::Box tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getCLAP();
|
||||
if (tmpContent.getType() == "avcC"){
|
||||
myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize());
|
||||
}
|
||||
tmpContent = ((MP4::VisualSampleEntry&)tmpBox2).getPASP();
|
||||
if (tmpContent.getType() == "avcC"){
|
||||
myMeta.tracks[trackNo].init = std::string(tmpContent.payload(),tmpContent.payloadSize());
|
||||
}
|
||||
}else if (tmpType == "hev1"){
|
||||
myMeta.tracks[trackNo].type = "video";
|
||||
myMeta.tracks[trackNo].codec = "HEVC";
|
||||
myMeta.tracks[trackNo].width = ((MP4::VisualSampleEntry&)tmpBox).getWidth();
|
||||
myMeta.tracks[trackNo].height = ((MP4::VisualSampleEntry&)tmpBox).getHeight();
|
||||
MP4::Box tmpBox2 = ((MP4::VisualSampleEntry&)tmpBox).getCLAP();
|
||||
myMeta.tracks[trackNo].init = std::string(tmpBox2.payload(),tmpBox2.payloadSize());
|
||||
}else if (tmpType == "mp4a" || tmpType == "aac " || tmpType == "ac-3"){
|
||||
myMeta.tracks[trackNo].type = "audio";
|
||||
myMeta.tracks[trackNo].channels = ((MP4::AudioSampleEntry&)tmpBox).getChannelCount();
|
||||
myMeta.tracks[trackNo].rate = (long long int)(((MP4::AudioSampleEntry&)tmpBox).getSampleRate());
|
||||
if (tmpType == "ac-3"){
|
||||
myMeta.tracks[trackNo].codec = "AC3";
|
||||
}else{
|
||||
MP4::Box esds = ((MP4::AudioSampleEntry&)tmpBox).getCodecBox();
|
||||
if (((MP4::ESDS&)esds).isAAC()){
|
||||
myMeta.tracks[trackNo].codec = "AAC";
|
||||
myMeta.tracks[trackNo].init = ((MP4::ESDS&)esds).getInitData();
|
||||
}else{
|
||||
myMeta.tracks[trackNo].codec = "MP3";
|
||||
}
|
||||
}
|
||||
myMeta.tracks[trackNo].size = 16;///\todo this might be nice to calculate from mp4 file;
|
||||
//get Visual sample entry -> esds -> startcodes
|
||||
}else{
|
||||
myMeta.tracks.erase(trackNo);
|
||||
}
|
||||
}
|
||||
}//rof stbl
|
||||
uint64_t totaldur = 0;///\todo note: set this to begin time
|
||||
mp4PartBpos BsetPart;
|
||||
long long unsigned int entryNo = 0;
|
||||
long long unsigned int sampleNo = 0;
|
||||
MP4::STTSEntry tempSTTS;
|
||||
tempSTTS = sttsBox.getSTTSEntry(entryNo);
|
||||
long long unsigned int curSTSS = 0;
|
||||
bool vidTrack = (myMeta.tracks[trackNo].type == "video");
|
||||
//change to for over all samples
|
||||
unsigned int stcoIndex = 0;
|
||||
unsigned int stscIndex = 0;
|
||||
unsigned int fromSTCOinSTSC = 0;
|
||||
long long unsigned int tempOffset;
|
||||
bool stcoIs64 = (stcoBox.getType() == "co64");
|
||||
if (stcoIs64){
|
||||
tempOffset = ((MP4::CO64*)&stcoBox)->getChunkOffset(0);
|
||||
}else{
|
||||
tempOffset = stcoBox.getChunkOffset(0);
|
||||
}
|
||||
long long unsigned int nextFirstChunk;
|
||||
if (stscBox.getEntryCount() > 1){
|
||||
nextFirstChunk = stscBox.getSTSCEntry(1).firstChunk - 1;
|
||||
}else{
|
||||
if (stcoIs64){
|
||||
nextFirstChunk = ((MP4::CO64*)&stcoBox)->getEntryCount();
|
||||
}else{
|
||||
nextFirstChunk = stcoBox.getEntryCount();
|
||||
}
|
||||
}
|
||||
for(long long unsigned int sampleIndex = 0; sampleIndex < stszBox.getSampleCount(); sampleIndex ++){
|
||||
if (stcoIndex >= nextFirstChunk){//note
|
||||
stscIndex ++;
|
||||
if (stscIndex + 1 < stscBox.getEntryCount()){
|
||||
nextFirstChunk = stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1;
|
||||
}else{
|
||||
if (stcoIs64){
|
||||
nextFirstChunk = ((MP4::CO64*)&stcoBox)->getEntryCount();
|
||||
}else{
|
||||
nextFirstChunk = stcoBox.getEntryCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (vidTrack && curSTSS < stssBox.getEntryCount() && sampleIndex + 1 == stssBox.getSampleNumber(curSTSS)){
|
||||
BsetPart.keyframe = true;
|
||||
curSTSS ++;
|
||||
}else{
|
||||
BsetPart.keyframe = false;
|
||||
}
|
||||
//in bpos set
|
||||
BsetPart.stcoNr=stcoIndex;
|
||||
//bpos = chunkoffset[samplenr] in stco
|
||||
BsetPart.bpos = tempOffset;
|
||||
fromSTCOinSTSC ++;
|
||||
if (fromSTCOinSTSC < stscBox.getSTSCEntry(stscIndex).samplesPerChunk){//as long as we are still in this chunk
|
||||
tempOffset += stszBox.getEntrySize(sampleIndex);
|
||||
}else{
|
||||
stcoIndex ++;
|
||||
fromSTCOinSTSC = 0;
|
||||
if (stcoIs64){
|
||||
tempOffset = ((MP4::CO64*)&stcoBox)->getChunkOffset(stcoIndex);
|
||||
}else{
|
||||
tempOffset = stcoBox.getChunkOffset(stcoIndex);
|
||||
}
|
||||
}
|
||||
//time = totaldur + stts[entry][sample]
|
||||
BsetPart.time = (totaldur*1000)/timeScale;
|
||||
totaldur += tempSTTS.sampleDelta;
|
||||
sampleNo++;
|
||||
if (sampleNo >= tempSTTS.sampleCount){
|
||||
entryNo++;
|
||||
sampleNo = 0;
|
||||
if (entryNo < sttsBox.getEntryCount()){
|
||||
tempSTTS = sttsBox.getSTTSEntry(entryNo);
|
||||
}
|
||||
}
|
||||
//set size, that's easy
|
||||
BsetPart.size = stszBox.getEntrySize(sampleIndex);
|
||||
//trackid
|
||||
BsetPart.trackID=trackNo;
|
||||
BPosSet.insert(BsetPart);
|
||||
}//while over stsc
|
||||
if (vidTrack){
|
||||
//something wrong with the time formula, but the answer is right for some reason
|
||||
/// \todo Fix this. This makes no sense whatsoever.
|
||||
if (stcoIs64){
|
||||
myMeta.tracks[trackNo].fpks = (((double)(((MP4::CO64*)&stcoBox)->getEntryCount()*1000))/((totaldur*1000)/timeScale))*1000;
|
||||
}else{
|
||||
myMeta.tracks[trackNo].fpks = (((double)(stcoBox.getEntryCount()*1000))/((totaldur*1000)/timeScale))*1000;
|
||||
}
|
||||
}
|
||||
}//fi stbl
|
||||
}//rof minf
|
||||
}//fi minf
|
||||
}//rof mdia
|
||||
}//fi mdia
|
||||
}//rof trak
|
||||
}//endif trak
|
||||
}//rof moov
|
||||
}else if (boxType == "erro"){
|
||||
break;
|
||||
}else{
|
||||
if (!MP4::skipBox(inFile)){//moving on to next box
|
||||
DEBUG_MSG(DLVL_FAIL,"Error in Skipping box, exiting");
|
||||
return false;
|
||||
}
|
||||
}// if moov
|
||||
}// end while file read
|
||||
//for all in bpos set, find its data
|
||||
clearerr(inFile);
|
||||
|
||||
for (std::set<mp4PartBpos>::iterator it = BPosSet.begin(); it != BPosSet.end(); it++){
|
||||
if (!fseeko(inFile,it->bpos,SEEK_SET)){
|
||||
if (it->size > malSize){
|
||||
data = (char*)realloc(data, it->size);
|
||||
malSize = it->size;
|
||||
}
|
||||
int tmp = fread(data, it->size, 1, inFile);
|
||||
if (tmp == 1){
|
||||
//add data
|
||||
myMeta.update(it->time, 1, it->trackID, it->size, it->bpos, it->keyframe);
|
||||
}else{
|
||||
INFO_MSG("fread did not return 1, bpos: %llu size: %llu keyframe: %d error: %s", it->bpos, it->size, it->keyframe, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
INFO_MSG("fseek failed!");
|
||||
return false;
|
||||
}
|
||||
}//rof bpos set
|
||||
//outputting dtsh file
|
||||
std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str());
|
||||
oFile << myMeta.toJSON().toNetPacked();
|
||||
oFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputMP4::getNext(bool smart) {//get next part from track in stream
|
||||
if (curPositions.empty()){
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
//pop uit set
|
||||
mp4PartTime curPart = *curPositions.begin();
|
||||
curPositions.erase(curPositions.begin());
|
||||
|
||||
bool isKeyframe = false;
|
||||
if(nextKeyframe[curPart.trackID] < myMeta.tracks[curPart.trackID].keys.size()){
|
||||
//checking if this is a keyframe
|
||||
if (myMeta.tracks[curPart.trackID].type == "video" && (long long int) curPart.time == myMeta.tracks[curPart.trackID].keys[(nextKeyframe[curPart.trackID])].getTime()){
|
||||
isKeyframe = true;
|
||||
}
|
||||
//if a keyframe has passed, we find the next keyframe
|
||||
if (myMeta.tracks[curPart.trackID].keys[(nextKeyframe[curPart.trackID])].getTime() < (long long int)curPart.time){
|
||||
nextKeyframe[curPart.trackID] ++;
|
||||
}
|
||||
}
|
||||
if (fseeko(inFile,curPart.bpos,SEEK_SET)){
|
||||
DEBUG_MSG(DLVL_FAIL,"seek unsuccessful; bpos: %llu error: %s",curPart.bpos, strerror(errno));
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
if (curPart.size > malSize){
|
||||
data = (char*)realloc(data, curPart.size);
|
||||
malSize = curPart.size;
|
||||
}
|
||||
if (fread(data, curPart.size, 1, inFile)!=1){
|
||||
DEBUG_MSG(DLVL_FAIL,"read unsuccessful at %ld", ftell(inFile));
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data, curPart.size, 0/*Note: no bpos*/, isKeyframe);
|
||||
|
||||
//get the next part for this track
|
||||
curPart.index ++;
|
||||
if (curPart.index < headerData[curPart.trackID].size()){
|
||||
headerData[curPart.trackID].getPart(curPart.index, curPart.bpos, curPart.size, curPart.time, curPart.offset);
|
||||
curPositions.insert(curPart);
|
||||
}
|
||||
}
|
||||
|
||||
void inputMP4::seek(int seekTime) {//seek to a point
|
||||
nextKeyframe.clear();
|
||||
//for all tracks
|
||||
curPositions.clear();
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
nextKeyframe[*it] = 0;
|
||||
mp4PartTime addPart;
|
||||
addPart.bpos = 0;
|
||||
addPart.size = 0;
|
||||
addPart.time = 0;
|
||||
addPart.trackID = *it;
|
||||
//for all indexes in those tracks
|
||||
for (unsigned int i = 0; i < headerData[*it].size(); i++){
|
||||
//if time > seekTime
|
||||
headerData[*it].getPart(i, addPart.bpos, addPart.size, addPart.time, addPart.offset);
|
||||
//check for keyframe time in myMeta and update nextKeyframe
|
||||
//
|
||||
if (myMeta.tracks[*it].keys[(nextKeyframe[*it])].getTime() < addPart.time){
|
||||
nextKeyframe[*it] ++;
|
||||
}
|
||||
if (addPart.time >= seekTime){
|
||||
addPart.index = i;
|
||||
//use addPart thingy in time set and break
|
||||
curPositions.insert(addPart);
|
||||
break;
|
||||
}//end if time > seektime
|
||||
}//end for all indexes
|
||||
}//rof all tracks
|
||||
}
|
||||
|
||||
void inputMP4::trackSelect(std::string trackSpec) {
|
||||
selectedTracks.clear();
|
||||
long long int index;
|
||||
while (trackSpec != "") {
|
||||
index = trackSpec.find(' ');
|
||||
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
|
||||
VERYHIGH_MSG("Added track %d, index = %lld, (index == npos) = %d", atoi(trackSpec.substr(0, index).c_str()), index, index == std::string::npos);
|
||||
if (index != std::string::npos) {
|
||||
trackSpec.erase(0, index + 1);
|
||||
} else {
|
||||
trackSpec = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
102
src/input/input_mp4.h
Normal file
102
src/input/input_mp4.h
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#include "input.h"
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/mp4.h>
|
||||
#include <mist/mp4_generic.h>
|
||||
namespace Mist {
|
||||
class mp4PartTime{
|
||||
public:
|
||||
mp4PartTime() : time(0), offset(0), trackID(0), bpos(0), size(0), index(0) {}
|
||||
bool operator < (const mp4PartTime & rhs) const {
|
||||
if (time < rhs.time){
|
||||
return true;
|
||||
}else{
|
||||
if (time == rhs.time){
|
||||
if (trackID < rhs.trackID){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
long long unsigned int time;
|
||||
long long unsigned int offset;
|
||||
unsigned int trackID;
|
||||
long long unsigned int bpos;
|
||||
unsigned int size;
|
||||
long unsigned int index;
|
||||
};
|
||||
|
||||
struct mp4PartBpos{
|
||||
bool operator < (const mp4PartBpos & rhs) const {
|
||||
if (time < rhs.time){
|
||||
return true;
|
||||
}else{
|
||||
if (time == rhs.time){
|
||||
if (trackID < rhs.trackID){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
long long unsigned int time;
|
||||
long long unsigned int trackID;
|
||||
long long unsigned int bpos;
|
||||
long long unsigned int size;
|
||||
long long unsigned int stcoNr;
|
||||
bool keyframe;
|
||||
};
|
||||
|
||||
class mp4TrackHeader{
|
||||
public:
|
||||
mp4TrackHeader();
|
||||
void read(MP4::TRAK & trakBox);
|
||||
MP4::STCO stcoBox;
|
||||
MP4::STSZ stszBox;
|
||||
MP4::STTS sttsBox;
|
||||
MP4::CTTS cttsBox;
|
||||
MP4::STSC stscBox;
|
||||
long unsigned int timeScale;
|
||||
void getPart(long unsigned int index, long long unsigned int & offset,unsigned int& size, long long unsigned int & timestamp, long long unsigned int & timeOffset);
|
||||
long unsigned int size();
|
||||
private:
|
||||
bool initialised;
|
||||
//next variables are needed for the stsc/stco loop
|
||||
long long unsigned int stscStart;
|
||||
long long unsigned int sampleIndex;
|
||||
//next variables are needed for the stts loop
|
||||
long long unsigned deltaIndex;///< Index in STTS box
|
||||
long long unsigned deltaPos;///< Sample counter for STTS box
|
||||
long long unsigned deltaTotal;///< Total timestamp for STTS box
|
||||
//for CTTS box loop
|
||||
long long unsigned offsetIndex;///< Index in CTTS box
|
||||
long long unsigned offsetPos;///< Sample counter for CTTS box
|
||||
};
|
||||
|
||||
class inputMP4 : public Input {
|
||||
public:
|
||||
inputMP4(Util::Config * cfg);
|
||||
~inputMP4();
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
|
||||
FILE * inFile;
|
||||
|
||||
std::map<unsigned int, mp4TrackHeader> headerData;
|
||||
std::set<mp4PartTime> curPositions;
|
||||
|
||||
//remember last seeked keyframe;
|
||||
std::map <unsigned int, unsigned int> nextKeyframe;
|
||||
|
||||
//these next two variables keep a buffer for reading from filepointer inFile;
|
||||
unsigned long long int malSize;
|
||||
char* data;///\todo rename this variable to a more sensible name, it is a temporary piece of memory to read from files
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputMP4 mistIn;
|
||||
516
src/input/input_ts.cpp
Executable file
516
src/input/input_ts.cpp
Executable file
|
|
@ -0,0 +1,516 @@
|
|||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/flv_tag.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/ts_packet.h>
|
||||
#include <mist/mp4_generic.h>
|
||||
#include "input_ts.h"
|
||||
|
||||
namespace Mist {
|
||||
|
||||
|
||||
/// Constructor of TS Input
|
||||
/// \arg cfg Util::Config that contains all current configurations.
|
||||
inputTS::inputTS(Util::Config * cfg) : Input(cfg) {
|
||||
capa["name"] = "TS";
|
||||
capa["decs"] = "Enables TS Input";
|
||||
capa["source_match"] = "/*.ts";
|
||||
capa["priority"] = 9ll;
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
}
|
||||
|
||||
///Setup of TS Input
|
||||
bool inputTS::setup() {
|
||||
if (config->getString("input") == "-") {
|
||||
inFile = stdin;
|
||||
}else{
|
||||
inFile = fopen(config->getString("input").c_str(), "r");
|
||||
}
|
||||
|
||||
if (config->getString("output") != "-") {
|
||||
std::cerr << "Output to non-stdout not yet supported" << std::endl;
|
||||
}
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
///Track selector of TS Input
|
||||
///\arg trackSpec specifies which tracks are to be selected
|
||||
///\todo test whether selecting a subset of tracks work
|
||||
void inputTS::trackSelect(std::string trackSpec) {
|
||||
selectedTracks.clear();
|
||||
long long int index;
|
||||
while (trackSpec != "") {
|
||||
index = trackSpec.find(' ');
|
||||
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
|
||||
if (index != std::string::npos) {
|
||||
trackSpec.erase(0, index + 1);
|
||||
} else {
|
||||
trackSpec = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void inputTS::parsePESHeader(int tid, pesBuffer & buf){
|
||||
if (buf.data.size() < 9){
|
||||
return;
|
||||
}
|
||||
if (buf.data.size() < 9 + buf.data[8]){
|
||||
return;
|
||||
}
|
||||
if( (((int)buf.data[0] << 16) | ((int)buf.data[1] << 8) | buf.data[2]) != 0x000001){
|
||||
DEBUG_MSG(DLVL_WARN, "Parsing PES for track %d failed due to incorrect header (%0.6X), throwing away", tid, (((int)buf.data[0] << 16) | ((int)buf.data[1] << 8) | buf.data[2]) );
|
||||
buf.data = "";
|
||||
return;
|
||||
}
|
||||
buf.len = (((int)buf.data[4] << 8) | buf.data[5]) - (3 + buf.data[8]);
|
||||
if ((buf.data[7] >> 6) & 0x02){//Check for PTS presence
|
||||
buf.time = ((buf.data[9] >> 1) & 0x07);
|
||||
buf.time <<= 15;
|
||||
buf.time |= ((int)buf.data[10] << 7) | ((buf.data[11] >> 1) & 0x7F);
|
||||
buf.time <<= 15;
|
||||
buf.time |= ((int)buf.data[12] << 7) | ((buf.data[13] >> 1) & 0x7F);
|
||||
buf.time /= 90;
|
||||
if (((buf.data[7] & 0xC0) >> 6) & 0x01){//Check for DTS presence (yes, only if PTS present)
|
||||
buf.offset = buf.time;
|
||||
buf.time = ((buf.data[14] >> 1) & 0x07);
|
||||
buf.time <<= 15;
|
||||
buf.time |= ((int)buf.data[15] << 7) | ((buf.data[16] >> 1) & 0x7F);
|
||||
buf.time <<= 15;
|
||||
buf.time |= ((int)buf.data[17] << 7) | ((buf.data[18] >> 1) & 0x7F);
|
||||
buf.time /= 90;
|
||||
buf.offset -= buf.time;
|
||||
}
|
||||
}
|
||||
if (!firstTimes.count(tid)){
|
||||
firstTimes[tid] = buf.time;
|
||||
}
|
||||
buf.time -= firstTimes[tid];
|
||||
buf.data.erase(0, 9 + buf.data[8]);
|
||||
}
|
||||
|
||||
void inputTS::parsePESPayload(int tid, pesBuffer & buf){
|
||||
if (myMeta.tracks[tid].codec == "H264"){
|
||||
parseH264PES(tid, buf);
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "AAC"){
|
||||
parseAACPES(tid, buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void inputTS::parseAACPES(int tid, pesBuffer & buf){
|
||||
if (!buf.data.size()){
|
||||
buf.len = 0;
|
||||
return;
|
||||
}
|
||||
if (myMeta.tracks[tid].init == ""){
|
||||
char audioInit[2];//5 bits object type, 4 bits frequency index, 4 bits channel index
|
||||
char AACProfile = ((buf.data[2] >> 6) & 0x03) + 1;
|
||||
char frequencyIndex = ((buf.data[2] >> 2) & 0x0F);
|
||||
char channelConfig = ((buf.data[2] & 0x01) << 2) | ((buf.data[3] >> 6) & 0x03);
|
||||
switch(frequencyIndex){
|
||||
case 0: myMeta.tracks[tid].rate = 96000; break;
|
||||
case 1: myMeta.tracks[tid].rate = 88200; break;
|
||||
case 2: myMeta.tracks[tid].rate = 64000; break;
|
||||
case 3: myMeta.tracks[tid].rate = 48000; break;
|
||||
case 4: myMeta.tracks[tid].rate = 44100; break;
|
||||
case 5: myMeta.tracks[tid].rate = 32000; break;
|
||||
case 6: myMeta.tracks[tid].rate = 24000; break;
|
||||
case 7: myMeta.tracks[tid].rate = 22050; break;
|
||||
case 8: myMeta.tracks[tid].rate = 16000; break;
|
||||
case 9: myMeta.tracks[tid].rate = 12000; break;
|
||||
case 10: myMeta.tracks[tid].rate = 11025; break;
|
||||
case 11: myMeta.tracks[tid].rate = 8000; break;
|
||||
case 12: myMeta.tracks[tid].rate = 7350; break;
|
||||
default: myMeta.tracks[tid].rate = 0; break;
|
||||
}
|
||||
myMeta.tracks[tid].channels = channelConfig;
|
||||
if (channelConfig == 7){
|
||||
myMeta.tracks[tid].channels = 8;
|
||||
}
|
||||
audioInit[0] = ((AACProfile & 0x1F) << 3) | ((frequencyIndex & 0x0E) >> 1);
|
||||
audioInit[1] = ((frequencyIndex & 0x01) << 7) | ((channelConfig & 0x0F) << 3);
|
||||
myMeta.tracks[tid].init = std::string(audioInit, 2);
|
||||
//\todo This value is right now hardcoded, maybe fix this when we know how
|
||||
myMeta.tracks[tid].size = 16;
|
||||
}
|
||||
buf.len = (((buf.data[3] & 0x03) << 11) | (buf.data[4] << 3) | ((buf.data[5] >> 5) & 0x07)) - (buf.data[1] & 0x01 ? 7 :9);
|
||||
buf.curSampleCount += 1024 * ((buf.data[6] & 0x3) + 1);//Number of frames * samples per frame(1024);
|
||||
buf.data.erase(0, (buf.data[1] & 0x01 ? 7 : 9));//Substract header
|
||||
}
|
||||
|
||||
void inputTS::parseH264PES(int tid, pesBuffer & buf){
|
||||
static char annexB[] = {0x00,0x00,0x01};
|
||||
static char nalLen[4];
|
||||
|
||||
int nalLength = 0;
|
||||
std::string newData;
|
||||
int pos = 0;
|
||||
int nxtPos = buf.data.find(annexB, pos, 3);
|
||||
//Rewrite buf.data from annexB to size-prefixed h.264
|
||||
while (nxtPos != std::string::npos){
|
||||
//Detect next packet (if any) and deduce current packet length
|
||||
pos = nxtPos + 3;
|
||||
nxtPos = buf.data.find(annexB, pos, 3);
|
||||
if (nxtPos == std::string::npos){
|
||||
nalLength = buf.data.size() - pos;
|
||||
}else{
|
||||
nalLength = nxtPos - pos;
|
||||
if (buf.data[nxtPos - 1] == 0x00){//4-byte annexB header
|
||||
nalLength--;
|
||||
}
|
||||
}
|
||||
//Do nal type specific stuff
|
||||
switch (buf.data[pos] & 0x1F){
|
||||
case 0x05: buf.isKey = true; break;
|
||||
case 0x07: buf.sps = buf.data.substr(pos, nalLength); break;
|
||||
case 0x08: buf.pps = buf.data.substr(pos, nalLength); break;
|
||||
default: break;
|
||||
}
|
||||
if ((buf.data[pos] & 0x1F) != 0x07 && (buf.data[pos] & 0x1F) != 0x08 && (buf.data[pos] & 0x1F) != 0x09){
|
||||
//Append length + payload
|
||||
nalLen[0] = (nalLength >> 24) & 0xFF;
|
||||
nalLen[1] = (nalLength >> 16) & 0xFF;
|
||||
nalLen[2] = (nalLength >> 8) & 0xFF;
|
||||
nalLen[3] = nalLength & 0xFF;
|
||||
newData.append(nalLen, 4);
|
||||
newData += buf.data.substr(pos, nalLength);
|
||||
}
|
||||
}
|
||||
buf.data = newData;
|
||||
buf.len = newData.size();
|
||||
//If this packet had both a Sequence Parameter Set (sps) and a Picture Parameter Set (pps), calculate the metadata for the stream
|
||||
if (buf.sps != "" && buf.pps != ""){
|
||||
MP4::AVCC avccBox;
|
||||
avccBox.setVersion(1);
|
||||
avccBox.setProfile(buf.sps[1]);
|
||||
avccBox.setCompatibleProfiles(buf.sps[2]);
|
||||
avccBox.setLevel(buf.sps[3]);
|
||||
avccBox.setSPSNumber(1);
|
||||
avccBox.setSPS(buf.sps);
|
||||
avccBox.setPPSNumber(1);
|
||||
avccBox.setPPS(buf.pps);
|
||||
myMeta.tracks[tid].init = std::string(avccBox.payload(), avccBox.payloadSize());
|
||||
h264::SPS tmpNal(buf.sps, true);
|
||||
h264::SPSMeta tmpMeta = tmpNal.getCharacteristics();
|
||||
myMeta.tracks[tid].width = tmpMeta.width;
|
||||
myMeta.tracks[tid].height = tmpMeta.height;
|
||||
myMeta.tracks[tid].fpks = tmpMeta.fps * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
///Reads headers from a TS stream, and saves them into metadata
|
||||
///It works by going through the entire TS stream, and every time
|
||||
///It encounters a new PES start, it writes the currently found PES data
|
||||
///for a specific track to metadata. After the entire stream has been read,
|
||||
///it writes the remaining metadata.
|
||||
///\todo Find errors, perhaps parts can be made more modular
|
||||
bool inputTS::readHeader(){
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
DTSC::File tmp(config->getString("input") + ".dtsh");
|
||||
if (tmp){
|
||||
myMeta = tmp.getMeta();
|
||||
return true;
|
||||
}
|
||||
|
||||
TS::Packet packet;//to analyse and extract data
|
||||
fseek(inFile, 0, SEEK_SET);//seek to beginning
|
||||
JSON::Value thisPacket;
|
||||
|
||||
std::set<int> PATIds;
|
||||
std::map<int, int> pidToType;
|
||||
std::map<int, pesBuffer> lastBuffer;
|
||||
|
||||
//h264::SPSmMta spsdata;//to analyse sps data, and extract resolution etc...
|
||||
long long int lastBpos = 0;
|
||||
while (packet.FromFile(inFile)){
|
||||
//Handle special packets (PAT/PMT)
|
||||
if(packet.getPID() == 0x00){
|
||||
PATIds.clear();
|
||||
for (int i = 0; i < ((TS::ProgramAssociationTable&)packet).getProgramCount(); i++){
|
||||
PATIds.insert(((TS::ProgramAssociationTable&)packet).getProgramPID(i));
|
||||
}
|
||||
}
|
||||
if(PATIds.count(packet.getPID())){
|
||||
TS::ProgramMappingEntry entry = ((TS::ProgramMappingTable&)packet).getEntry(0);
|
||||
while(entry){
|
||||
unsigned int pid = entry.getElementaryPid();
|
||||
pidToType[pid] = entry.getStreamType();
|
||||
//Check if the track exists in metadata
|
||||
if (!myMeta.tracks.count(pid)){
|
||||
switch (entry.getStreamType()){
|
||||
case 0x1B:
|
||||
myMeta.tracks[pid].codec = "H264";
|
||||
myMeta.tracks[pid].type = "video";
|
||||
myMeta.tracks[pid].trackID = pid;
|
||||
break;
|
||||
case 0x0F:
|
||||
myMeta.tracks[pid].codec = "AAC";
|
||||
myMeta.tracks[pid].type = "audio";
|
||||
myMeta.tracks[pid].trackID = pid;
|
||||
break;
|
||||
case 0x81:
|
||||
myMeta.tracks[pid].codec = "AC3";
|
||||
myMeta.tracks[pid].type = "audio";
|
||||
myMeta.tracks[pid].trackID = pid;
|
||||
break;
|
||||
default:
|
||||
DEBUG_MSG(DLVL_WARN, "Ignoring unsupported track type %0.2X, on pid %d", entry.getStreamType(), pid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
entry.advance();
|
||||
}
|
||||
}
|
||||
if(pidToType.count(packet.getPID())){
|
||||
//analyzing audio/video
|
||||
//we have audio/video payload
|
||||
//get trackID of this packet
|
||||
int tid = packet.getPID();
|
||||
if (packet.getUnitStart() && lastBuffer.count(tid) && lastBuffer[tid].len){
|
||||
parsePESPayload(tid, lastBuffer[tid]);
|
||||
thisPacket.null();
|
||||
thisPacket["data"] = lastBuffer[tid].data;
|
||||
thisPacket["trackid"] = tid;//last trackid
|
||||
thisPacket["bpos"] = lastBuffer[tid].bpos;
|
||||
thisPacket["time"] = lastBuffer[tid].time ;
|
||||
if (lastBuffer[tid].offset){
|
||||
thisPacket["offset"] = lastBuffer[tid].offset;
|
||||
}
|
||||
if (lastBuffer[tid].isKey){
|
||||
thisPacket["keyframe"] = 1LL;
|
||||
}
|
||||
myMeta.update(thisPacket);//metadata was read in
|
||||
lastBuffer.erase(tid);
|
||||
}
|
||||
if (!lastBuffer.count(tid)){
|
||||
lastBuffer[tid] = pesBuffer();
|
||||
lastBuffer[tid].bpos = lastBpos;
|
||||
}
|
||||
lastBuffer[tid].data.append(packet.getPayload(), packet.getPayloadLength());
|
||||
if (!lastBuffer[tid].len){
|
||||
parsePESHeader(tid, lastBuffer[tid]);
|
||||
}
|
||||
if (lastBuffer[tid].data.size() == lastBuffer[tid].len) {
|
||||
parsePESPayload(tid, lastBuffer[tid]);
|
||||
if (myMeta.tracks[tid].codec == "AAC"){
|
||||
while(lastBuffer[tid].data.size()){
|
||||
thisPacket.null();
|
||||
thisPacket["data"] = lastBuffer[tid].data.substr(0, lastBuffer[tid].len);
|
||||
thisPacket["trackid"] = tid;//last trackid
|
||||
thisPacket["bpos"] = lastBuffer[tid].bpos;
|
||||
thisPacket["time"] = lastBuffer[tid].time + (long long int)((double)((lastBuffer[tid].curSampleCount - 1024) * 1000)/ myMeta.tracks[tid].rate) ;
|
||||
myMeta.update(thisPacket);//metadata was read in
|
||||
lastBuffer[tid].data.erase(0, lastBuffer[tid].len);
|
||||
parsePESPayload(tid, lastBuffer[tid]);
|
||||
}
|
||||
}else{
|
||||
thisPacket.null();
|
||||
thisPacket["data"] = lastBuffer[tid].data;
|
||||
thisPacket["trackid"] = tid;//last trackid
|
||||
thisPacket["bpos"] = lastBuffer[tid].bpos;
|
||||
thisPacket["time"] = lastBuffer[tid].time ;
|
||||
if (myMeta.tracks[tid].type == "video"){
|
||||
if (lastBuffer[tid].offset){
|
||||
thisPacket["offset"] = lastBuffer[tid].offset;
|
||||
}
|
||||
if (lastBuffer[tid].isKey){
|
||||
thisPacket["keyframe"] = 1LL;
|
||||
}
|
||||
}
|
||||
myMeta.update(thisPacket);//metadata was read in
|
||||
}
|
||||
lastBuffer.erase(tid);
|
||||
}
|
||||
}
|
||||
lastBpos = ftell(inFile);
|
||||
}
|
||||
|
||||
std::ofstream oFile(std::string(config->getString("input") + ".dtsh").c_str());
|
||||
oFile << myMeta.toJSON().toNetPacked();
|
||||
oFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
///Reads a full PES packet, starting at the current byteposition
|
||||
///Assumes that you want a full PES for the first PID encountered
|
||||
///\todo Update to search for a specific PID
|
||||
pesBuffer inputTS::readFullPES(int tid){
|
||||
pesBuffer pesBuf;
|
||||
pesBuf.tid = tid;
|
||||
if (feof(inFile)){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Trying to read a PES past the end of the file, returning");
|
||||
return pesBuf;
|
||||
}
|
||||
unsigned int lastPos = ftell(inFile);
|
||||
TS::Packet tsBuf;
|
||||
tsBuf.FromFile(inFile);
|
||||
//Find first PES start on the selected track
|
||||
while (tsBuf.getPID() != tid || !tsBuf.getUnitStart()){
|
||||
lastPos = ftell(inFile);
|
||||
tsBuf.FromFile(inFile);
|
||||
if (feof(inFile)){
|
||||
return pesBuf;
|
||||
}
|
||||
}
|
||||
pesBuf.bpos = lastPos;
|
||||
pesBuf.data.append(tsBuf.getPayload(), tsBuf.getPayloadLength());
|
||||
parsePESHeader(tid, pesBuf);
|
||||
bool unbound = false;
|
||||
while (pesBuf.data.size() != pesBuf.len){
|
||||
//ReadNextPage
|
||||
tsBuf.FromFile(inFile);
|
||||
if (tsBuf.getPID() == tid && tsBuf.getUnitStart()){
|
||||
unbound = true;
|
||||
break;
|
||||
}
|
||||
if (feof(inFile)){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Reached EOF at an unexpected point... what happened?");
|
||||
return pesBuf;
|
||||
}
|
||||
if (tsBuf.getPID() == tid){
|
||||
pesBuf.data.append(tsBuf.getPayload(), tsBuf.getPayloadLength());
|
||||
pesBuf.lastPos = ftell(inFile);
|
||||
}
|
||||
if (pesBuf.len == 0){
|
||||
parsePESHeader(tid, pesBuf);
|
||||
}
|
||||
}
|
||||
pesBuf.lastPos = ftell(inFile);
|
||||
if (unbound){
|
||||
pesBuf.lastPos -= 188;
|
||||
}
|
||||
parsePESPayload(tid, pesBuf);
|
||||
return pesBuf;
|
||||
}
|
||||
|
||||
///Gets the next packet that is to be sent
|
||||
///At the moment, the logic of sending the last packet that was finished has been implemented,
|
||||
///but the seeking and finding data is not yet ready.
|
||||
///\todo Finish the implementation
|
||||
void inputTS::getNext(bool smart){
|
||||
static JSON::Value thisPack;
|
||||
if ( !playbackBuf.size()){
|
||||
DEBUG_MSG(DLVL_WARN, "No seek positions set - returning empty packet.");
|
||||
thisPacket.null();
|
||||
return;
|
||||
}
|
||||
|
||||
//Store current buffer
|
||||
pesBuffer thisBuf = *playbackBuf.begin();
|
||||
playbackBuf.erase(playbackBuf.begin());
|
||||
|
||||
//Seek follow up
|
||||
fseek(inFile, thisBuf.lastPos, SEEK_SET);
|
||||
pesBuffer nxtBuf;
|
||||
if (myMeta.tracks[thisBuf.tid].codec != "AAC" || playbackBuf.size() < 2){
|
||||
nxtBuf = readFullPES(thisBuf.tid);
|
||||
}
|
||||
if (nxtBuf.len){
|
||||
if (myMeta.tracks[nxtBuf.tid].codec == "AAC"){//only in case of aac we have more packets, for now
|
||||
while (nxtBuf.len){
|
||||
pesBuffer pesBuf;
|
||||
pesBuf.tid = nxtBuf.tid;
|
||||
pesBuf.time = nxtBuf.time + ((double)((nxtBuf.curSampleCount - 1024) * 1000)/ myMeta.tracks[nxtBuf.tid].rate) ;
|
||||
pesBuf.offset = nxtBuf.offset;
|
||||
pesBuf.len = nxtBuf.len;
|
||||
pesBuf.lastPos = nxtBuf.lastPos;
|
||||
pesBuf.isKey = false;
|
||||
pesBuf.data = nxtBuf.data.substr(0, nxtBuf.len);
|
||||
playbackBuf.insert(pesBuf);
|
||||
|
||||
nxtBuf.data.erase(0, nxtBuf.len);
|
||||
parsePESPayload(thisBuf.tid, nxtBuf);
|
||||
}
|
||||
}else{
|
||||
nxtBuf.data = nxtBuf.data.substr(0, nxtBuf.len);
|
||||
playbackBuf.insert(nxtBuf);
|
||||
}
|
||||
}
|
||||
|
||||
thisPack.null();
|
||||
thisPack["data"] = thisBuf.data;
|
||||
thisPack["trackid"] = thisBuf.tid;
|
||||
thisPack["bpos"] = thisBuf.bpos;
|
||||
thisPack["time"] = thisBuf.time;
|
||||
if (thisBuf.offset){
|
||||
thisPack["offset"] = thisBuf.offset;
|
||||
}
|
||||
if (thisBuf.isKey){
|
||||
thisPack["keyframe"] = 1LL;
|
||||
}
|
||||
std::string tmpStr = thisPack.toNetPacked();
|
||||
thisPacket.reInit(tmpStr.data(), tmpStr.size());
|
||||
}
|
||||
|
||||
///Seeks to a specific time
|
||||
void inputTS::seek(int seekTime){
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
if (feof(inFile)){
|
||||
clearerr(inFile);
|
||||
fseek(inFile, 0, SEEK_SET);
|
||||
}
|
||||
pesBuffer tmpBuf;
|
||||
tmpBuf.tid = *it;
|
||||
for (unsigned int i = 0; i < myMeta.tracks[*it].keys.size(); i++){
|
||||
if (myMeta.tracks[*it].keys[i].getTime() > seekTime){
|
||||
break;
|
||||
}
|
||||
if (myMeta.tracks[*it].keys[i].getTime() > tmpBuf.time){
|
||||
tmpBuf.time = myMeta.tracks[*it].keys[i].getTime();
|
||||
tmpBuf.bpos = myMeta.tracks[*it].keys[i].getBpos();
|
||||
}
|
||||
}
|
||||
|
||||
bool foundPacket = false;
|
||||
unsigned long long lastPos;
|
||||
pesBuffer nxtBuf;
|
||||
while ( !foundPacket){
|
||||
lastPos = ftell(inFile);
|
||||
if (feof(inFile)){
|
||||
DEBUG_MSG(DLVL_WARN, "Reached EOF during seek to %u in track %d - aborting @ %lld", seekTime, *it, lastPos);
|
||||
return;
|
||||
}
|
||||
fseek(inFile, tmpBuf.bpos, SEEK_SET);
|
||||
nxtBuf = readFullPES(*it);
|
||||
if (nxtBuf.time >= seekTime){
|
||||
foundPacket = true;
|
||||
}else{
|
||||
tmpBuf.bpos = nxtBuf.lastPos;
|
||||
}
|
||||
}
|
||||
if (myMeta.tracks[nxtBuf.tid].codec == "AAC"){//only in case of aac we have more packets, for now
|
||||
while (nxtBuf.len){
|
||||
pesBuffer pesBuf;
|
||||
pesBuf.tid = nxtBuf.tid;
|
||||
pesBuf.time = nxtBuf.time + ((double)((nxtBuf.curSampleCount - 1024) * 1000)/ myMeta.tracks[nxtBuf.tid].rate);
|
||||
pesBuf.offset = nxtBuf.offset;
|
||||
pesBuf.len = nxtBuf.len;
|
||||
pesBuf.lastPos = nxtBuf.lastPos;
|
||||
pesBuf.isKey = false;
|
||||
pesBuf.data = nxtBuf.data.substr(0, nxtBuf.len);
|
||||
playbackBuf.insert(pesBuf);
|
||||
|
||||
nxtBuf.data.erase(0, nxtBuf.len);
|
||||
parsePESPayload(nxtBuf.tid, nxtBuf);
|
||||
}
|
||||
}else{
|
||||
playbackBuf.insert(nxtBuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
81
src/input/input_ts.h
Executable file
81
src/input/input_ts.h
Executable file
|
|
@ -0,0 +1,81 @@
|
|||
#include "input.h"
|
||||
#include <mist/nal.h>
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/ts_packet.h>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
|
||||
namespace Mist {
|
||||
class pesBuffer {
|
||||
public:
|
||||
pesBuffer() : lastPos(0), len(0), time(0), offset(0), bpos(0), curSampleCount(0), isKey(false) {}
|
||||
///\brief Less-than comparison for seekPos structures.
|
||||
///\param rhs The seekPos to compare with.
|
||||
///\return Whether this object is smaller than rhs.
|
||||
bool operator < (const pesBuffer & rhs) const {
|
||||
if (time < rhs.time) {
|
||||
return true;
|
||||
} else {
|
||||
if (time == rhs.time){
|
||||
if (tid < rhs.tid){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
int tid;//When used for buffering, not for header generation
|
||||
long long int lastPos;//set by readFullPES, stores the byteposition directly after the last read ts packet
|
||||
long long int len;
|
||||
std::string data;
|
||||
long long int time;
|
||||
long long int offset;
|
||||
long long int bpos;
|
||||
long long int curSampleCount;
|
||||
bool isKey;
|
||||
std::string sps;
|
||||
std::string pps;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
/// This struct stores all metadata of a track, and sends them once a whole PES has been analyzed and sent
|
||||
struct trackInfo{
|
||||
//saves all data that needs to be sent.
|
||||
//as packets can be interleaved, the data needs to be temporarily stored
|
||||
long long int lastPos;//last byte position of trackSelect
|
||||
long long int pesTime;//the pes time of the current pes packet
|
||||
bool keyframe;//whether the current pes packet of the track has a keyframe or not
|
||||
std::string curPayload;//payload to be sent to user
|
||||
unsigned int packetCount;//number of TS packets read between between and end (to set bpos correctly)
|
||||
};
|
||||
|
||||
*/
|
||||
|
||||
/// This class contains all functions needed to implement TS Input
|
||||
class inputTS : public Input {
|
||||
public:
|
||||
inputTS(Util::Config * cfg);
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
void seek(int seekTime);
|
||||
void trackSelect(std::string trackSpec);
|
||||
void parsePESHeader(int tid, pesBuffer & buf);
|
||||
void parsePESPayload(int tid, pesBuffer & buf);
|
||||
void parseH264PES(int tid, pesBuffer & buf);
|
||||
void parseAACPES(int tid, pesBuffer & buf);
|
||||
pesBuffer readFullPES(int tid);
|
||||
FILE * inFile;///<The input file with ts data
|
||||
h264::NAL nal;///<Used to analyze raw h264 data
|
||||
long long int lastPos;///<last position played in file
|
||||
std::set<pesBuffer> playbackBuf;///Used for buffering playback items
|
||||
std::map<int, int> firstTimes;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::inputTS mistIn;
|
||||
|
||||
53
src/input/mist_in_folder.cpp
Normal file
53
src/input/mist_in_folder.cpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <semaphore.h>
|
||||
|
||||
#include INPUTTYPE
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/stream.h>
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
Util::Config conf(argv[0], PACKAGE_VERSION);
|
||||
mistIn conv(&conf);
|
||||
if (conf.parseArgs(argc, argv)) {
|
||||
if (conf.getBool("json")) {
|
||||
conv.run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string strm = conf.getString("streamname");
|
||||
if (strm.find_first_of("+ ") == std::string::npos){
|
||||
FAIL_MSG("Folder input requires a + or space in the stream name.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string folder = conf.getString("input");
|
||||
if (folder[folder.size() - 1] != '/'){
|
||||
FAIL_MSG("Input path must end in a forward slash.");
|
||||
return 1;
|
||||
}
|
||||
std::string folder_noslash = folder.substr(0, folder.size() - 1);
|
||||
struct stat fileCheck;
|
||||
if (stat(folder_noslash.c_str(), &fileCheck) != 0 || !S_ISDIR(fileCheck.st_mode)){
|
||||
FAIL_MSG("Folder input requires a folder as input.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string path = folder + strm.substr(strm.find_first_of("+ ")+1);
|
||||
if (stat(path.c_str(), &fileCheck) != 0 || S_ISDIR(fileCheck.st_mode)){
|
||||
FAIL_MSG("File not found: %s", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
Util::startInput(strm, path, false);
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
#include OUTPUTTYPE
|
||||
#include <mist/config.h>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
/*LTS-START*/
|
||||
#ifdef GEOIP
|
||||
#define GEOIPV4 "GeoIP.dat"
|
||||
#define GEOIPV6 "GeoIPv6.dat"
|
||||
#endif
|
||||
/*LTS-END*/
|
||||
|
||||
int spawnForked(Socket::Connection & S){
|
||||
mistOut tmp(S);
|
||||
|
|
@ -10,6 +18,21 @@ int spawnForked(Socket::Connection & S){
|
|||
int main(int argc, char * argv[]) {
|
||||
Util::Config conf(argv[0], PACKAGE_VERSION);
|
||||
mistOut::init(&conf);
|
||||
/*LTS-START*/
|
||||
#ifdef GEOIP
|
||||
mistOut::geoIP4 = GeoIP_open("/usr/share/GeoIP/" GEOIPV4, GEOIP_STANDARD | GEOIP_CHECK_CACHE);
|
||||
if (!mistOut::geoIP4){
|
||||
mistOut::geoIP4 = GeoIP_open(GEOIPV4, GEOIP_STANDARD | GEOIP_CHECK_CACHE);
|
||||
}
|
||||
mistOut::geoIP6 = GeoIP_open("/usr/share/GeoIP/" GEOIPV6, GEOIP_STANDARD | GEOIP_CHECK_CACHE);
|
||||
if (!mistOut::geoIP6){
|
||||
mistOut::geoIP6 = GeoIP_open(GEOIPV6, GEOIP_STANDARD | GEOIP_CHECK_CACHE);
|
||||
}
|
||||
if (!mistOut::geoIP4 || !mistOut::geoIP6){
|
||||
DEBUG_MSG(DLVL_FAIL, "Could not load all GeoIP databases. %s: %s, %s: %s", GEOIPV4, mistOut::geoIP4?"success":"fail", GEOIPV6, mistOut::geoIP6?"success":"fail");
|
||||
}
|
||||
#endif
|
||||
/*LTS-END*/
|
||||
if (conf.parseArgs(argc, argv)) {
|
||||
if (conf.getBool("json")) {
|
||||
std::cout << mistOut::capa.toString() << std::endl;
|
||||
|
|
@ -23,5 +46,11 @@ int main(int argc, char * argv[]) {
|
|||
return tmp.run();
|
||||
}
|
||||
}
|
||||
/*LTS-START*/
|
||||
#ifdef GEOIP
|
||||
GeoIP_delete(mistOut::geoIP4);
|
||||
GeoIP_delete(mistOut::geoIP6);
|
||||
#endif
|
||||
/*LTS-END*/
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@
|
|||
#include <mist/timing.h>
|
||||
#include "output.h"
|
||||
|
||||
/*LTS-START*/
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
/*LTS-END*/
|
||||
|
||||
namespace Mist {
|
||||
JSON::Value Output::capa = JSON::Value();
|
||||
|
||||
|
|
@ -29,6 +35,11 @@ namespace Mist {
|
|||
capa["optional"]["debug"]["help"] = "The debug level at which messages need to be printed.";
|
||||
capa["optional"]["debug"]["option"] = "--debug";
|
||||
capa["optional"]["debug"]["type"] = "debug";
|
||||
capa["optional"]["startpos"]["name"] = "Starting position in live buffer";
|
||||
capa["optional"]["startpos"]["help"] = "For live, where in the buffer the stream starts playback by default. 0 = beginning, 1000 = end";
|
||||
capa["optional"]["startpos"]["option"] = "--startPos";
|
||||
capa["optional"]["startpos"]["type"] = "uint";
|
||||
cfg->addOption("startpos", JSON::fromString("{\"arg\":\"uint\",\"default\":500,\"short\":\"P\",\"long\":\"startPos\",\"help\":\"For live, where in the buffer the stream starts playback by default. 0 = beginning, 1000 = end\"}"));
|
||||
}
|
||||
|
||||
Output::Output(Socket::Connection & conn) : myConn(conn) {
|
||||
|
|
@ -377,6 +388,273 @@ namespace Mist {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*LTS-START*/
|
||||
bool Output::onList(std::string ip, std::string list){
|
||||
if (list == ""){
|
||||
return false;
|
||||
}
|
||||
std::string entry;
|
||||
std::string lowerIpv6;//lower-case
|
||||
std::string upperIpv6;//full-caps
|
||||
do{
|
||||
entry = list.substr(0,list.find(" "));//make sure we have a single entry
|
||||
lowerIpv6 = "::ffff:" + entry;
|
||||
upperIpv6 = "::FFFF:" + entry;
|
||||
if (entry == ip || lowerIpv6 == ip || upperIpv6 == ip){
|
||||
return true;
|
||||
}
|
||||
long long unsigned int starPos = entry.find("*");
|
||||
if (starPos == std::string::npos){
|
||||
if (ip == entry){
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
if (starPos == 0){//beginning of the filter
|
||||
if (ip.substr(ip.length() - entry.size() - 1) == entry.substr(1)){
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
if (starPos == entry.size() - 1){//end of the filter
|
||||
if (ip.find(entry.substr(0, entry.size() - 1)) == 0 ){
|
||||
return true;
|
||||
}
|
||||
if (ip.find(entry.substr(0, lowerIpv6.size() - 1)) == 0 ){
|
||||
return true;
|
||||
}
|
||||
if (ip.find(entry.substr(0, upperIpv6.size() - 1)) == 0 ){
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
Log("CONF","Invalid list entry detected: " + entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
list.erase(0, entry.size() + 1);
|
||||
}while (list != "");
|
||||
return false;
|
||||
}
|
||||
|
||||
void Output::Log(std::string type, std::string message){
|
||||
/// \todo These logs need to show up in the controller.
|
||||
/// \todo Additionally, the triggering and untriggering of limits should be recorded in the controller as well.
|
||||
if (type == "HLIM"){
|
||||
DEBUG_MSG(DLVL_HIGH, "HardLimit Triggered: %s", message.c_str());
|
||||
}
|
||||
if (type == "SLIM"){
|
||||
DEBUG_MSG(DLVL_HIGH, "SoftLimit Triggered: %s", message.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::string Output::hostLookup(std::string ip){
|
||||
struct sockaddr_in6 sa;
|
||||
char hostName[1024];
|
||||
char service[20];
|
||||
if (inet_pton(AF_INET6, ip.c_str(), &(sa.sin6_addr)) != 1){
|
||||
return "\n";
|
||||
}
|
||||
sa.sin6_family = AF_INET6;
|
||||
sa.sin6_port = 0;
|
||||
sa.sin6_flowinfo = 0;
|
||||
sa.sin6_scope_id = 0;
|
||||
int tmpRet = getnameinfo((struct sockaddr*)&sa, sizeof sa, hostName, sizeof hostName, service, sizeof service, NI_NAMEREQD );
|
||||
if ( tmpRet == 0){
|
||||
return hostName;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool Output::isBlacklisted(std::string host, std::string streamName, int timeConnected){
|
||||
return false;//blacklisting temporarily disabled for performance reasons
|
||||
JSON::Value Storage = JSON::fromFile(Util::getTmpFolder() + "streamlist");
|
||||
std::string myHostName = hostLookup(host);
|
||||
if (myHostName == "\n"){
|
||||
return false;
|
||||
}
|
||||
std::string myCountryName = getCountry(host);
|
||||
JSON::ArrIter limitIt;
|
||||
bool hasWhitelist = false;
|
||||
bool hostOnWhitelist = false;
|
||||
if (Storage["streams"].isMember(streamName)){
|
||||
if (Storage["streams"][streamName].isMember("limits") && Storage["streams"][streamName]["limits"].size()){
|
||||
for (limitIt = Storage["streams"][streamName]["limits"].ArrBegin(); limitIt != Storage["streams"][streamName]["limits"].ArrEnd(); limitIt++){
|
||||
if ((*limitIt)["name"].asString() == "host"){
|
||||
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||
if (!onList(host, (*limitIt)["value"].asString().substr(1))){
|
||||
if (myHostName == ""){
|
||||
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
if ( !onList(myHostName, (*limitIt)["value"].asStringRef().substr(1))){
|
||||
if ((*limitIt)["type"].asStringRef() == "hard"){
|
||||
Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if ((*limitIt)["value"].asStringRef().size() > 1 && (*limitIt)["value"].asStringRef()[0] == '-'){
|
||||
if (onList(host, (*limitIt)["value"].asStringRef().substr(1))){
|
||||
if ((*limitIt)["type"].asStringRef() == "hard"){
|
||||
Log("HLIM", "Host " + host + " blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
if (myHostName != "" && onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asStringRef() == "hard"){
|
||||
Log("HLIM", "Host " + myHostName + " blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + myHostName + " blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((*limitIt)["name"].asString() == "geo"){
|
||||
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||
if (myCountryName == ""){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
if (!onList(myCountryName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if ((*limitIt)["value"].asString()[0] == '-'){
|
||||
if (onList(myCountryName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Storage["config"]["limits"].size()){
|
||||
for (limitIt = Storage["config"]["limits"].ArrBegin(); limitIt != Storage["config"]["limits"].ArrEnd(); limitIt++){
|
||||
if ((*limitIt)["name"].asString() == "host"){
|
||||
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||
if (!onList(host, (*limitIt)["value"].asString().substr(1))){
|
||||
if (myHostName == ""){
|
||||
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
if ( !onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " not whitelisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if ((*limitIt)["value"].asString()[0] == '-'){
|
||||
if (onList(host, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
if (myHostName != "" && onList(myHostName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + myHostName + " blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + myHostName + " blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((*limitIt)["name"].asString() == "geo"){
|
||||
if ((*limitIt)["value"].asString()[0] == '+'){
|
||||
if (myCountryName == ""){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " with unknown location blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
if (!onList(myCountryName, (*limitIt)["value"].asString().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " with location " + myCountryName + " not whitelisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if ((*limitIt)["value"].asStringRef().size() > 1 && (*limitIt)["value"].asStringRef()[0] == '-'){
|
||||
if (onList(myCountryName, (*limitIt)["value"].asStringRef().substr(1))){
|
||||
if ((*limitIt)["type"].asString() == "hard"){
|
||||
Log("HLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName);
|
||||
return true;
|
||||
}else{
|
||||
Log("SLIM", "Host " + host + " with location " + myCountryName + " blacklisted for stream " + streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasWhitelist){
|
||||
if (hostOnWhitelist || myHostName == ""){
|
||||
return false;
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef GEOIP
|
||||
GeoIP * Output::geoIP4 = 0;
|
||||
GeoIP * Output::geoIP6 = 0;
|
||||
#endif
|
||||
std::string Output::getCountry(std::string ip){
|
||||
char * code = NULL;
|
||||
#ifdef GEOIP
|
||||
if (geoIP4){
|
||||
code = (char*)GeoIP_country_code_by_addr(geoIP4, ip.c_str());
|
||||
}
|
||||
if (!code && geoIP6){
|
||||
code = (char*)GeoIP_country_code_by_addr_v6(geoIP6, ip.c_str());
|
||||
}
|
||||
#endif
|
||||
if (!code){
|
||||
return "";
|
||||
}
|
||||
return code;
|
||||
}
|
||||
/*LTS-END*/
|
||||
|
||||
void Output::requestHandler(){
|
||||
static bool firstData = true;//only the first time, we call onRequest if there's data buffered already.
|
||||
|
|
@ -586,6 +864,12 @@ namespace Mist {
|
|||
if (statsPage.getData()){
|
||||
unsigned long long int now = Util::epoch();
|
||||
if (now != lastStats){
|
||||
/*LTS-START*/
|
||||
if (statsPage.getData()[-1] > 127){
|
||||
myConn.close();
|
||||
return;
|
||||
}
|
||||
/*LTS-END*/
|
||||
lastStats = now;
|
||||
IPC::statExchange tmpEx(statsPage.getData());
|
||||
tmpEx.now(now);
|
||||
|
|
|
|||
|
|
@ -9,15 +9,20 @@
|
|||
#include <mist/dtsc.h>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/shared_memory.h>
|
||||
/*LTS-START*/
|
||||
#ifdef GEOIP
|
||||
#include <GeoIP.h>
|
||||
#endif
|
||||
/*LTS-END*/
|
||||
#include "../io.h"
|
||||
|
||||
namespace Mist {
|
||||
|
||||
|
||||
/// This struct keeps packet information sorted in playback order, so the
|
||||
/// Mist::Output class knows when to buffer which packet.
|
||||
struct sortedPageInfo{
|
||||
struct sortedPageInfo {
|
||||
bool operator < (const sortedPageInfo & rhs) const {
|
||||
if (time < rhs.time){
|
||||
if (time < rhs.time) {
|
||||
return true;
|
||||
}
|
||||
return (time == rhs.time && tid < rhs.tid);
|
||||
|
|
@ -41,6 +46,12 @@ namespace Mist {
|
|||
//static members for initialization and capabilities
|
||||
static void init(Util::Config * cfg);
|
||||
static JSON::Value capa;
|
||||
/*LTS-START*/
|
||||
#ifdef GEOIP
|
||||
static GeoIP * geoIP4;
|
||||
static GeoIP * geoIP6;
|
||||
#endif
|
||||
/*LTS-END*/
|
||||
//non-virtual generic functions
|
||||
int run();
|
||||
void stats();
|
||||
|
|
@ -64,6 +75,14 @@ namespace Mist {
|
|||
virtual void onFail();
|
||||
virtual void requestHandler();
|
||||
private://these *should* not be messed with in child classes.
|
||||
/*LTS-START*/
|
||||
void Log(std::string type, std::string message);
|
||||
bool checkLimits();
|
||||
bool isBlacklisted(std::string host, std::string streamName, int timeConnected);
|
||||
std::string hostLookup(std::string ip);
|
||||
bool onList(std::string ip, std::string list);
|
||||
std::string getCountry(std::string ip);
|
||||
/*LTS-END*/
|
||||
std::map<unsigned long, unsigned int> currKeyOpen;
|
||||
void loadPageForKey(long unsigned int trackId, long long int keyNum);
|
||||
int pageNumForKey(long unsigned int trackId, long long int keyNum);
|
||||
|
|
@ -82,7 +101,7 @@ namespace Mist {
|
|||
unsigned int maxSkipAhead;///< Maximum ms that we will go ahead of the intended timestamps.
|
||||
unsigned int minSkipAhead;///< Minimum ms that we will go ahead of the intended timestamps.
|
||||
unsigned int realTime;///< Playback speed times 1000 (1000 == 1.0X). Zero is infinite.
|
||||
|
||||
|
||||
//Read/write status variables
|
||||
Socket::Connection & myConn;///< Connection to the client.
|
||||
|
||||
|
|
@ -97,3 +116,4 @@ namespace Mist {
|
|||
};
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
669
src/output/output_dash_mp4.cpp
Normal file
669
src/output/output_dash_mp4.cpp
Normal file
|
|
@ -0,0 +1,669 @@
|
|||
#include "output_dash_mp4.h"
|
||||
#include <mist/defines.h>
|
||||
#include <mist/mp4.h>
|
||||
#include <mist/mp4_generic.h>
|
||||
#include <mist/mp4_dash.h>
|
||||
#include <mist/checksum.h>
|
||||
|
||||
namespace Mist {
|
||||
OutDashMP4::OutDashMP4(Socket::Connection & conn) : HTTPOutput(conn){realTime = 0;}
|
||||
OutDashMP4::~OutDashMP4(){}
|
||||
|
||||
std::string OutDashMP4::makeTime(long long unsigned int time){
|
||||
std::stringstream r;
|
||||
r << "PT" << (((time / 1000) / 60) /60) << "H" << ((time / 1000) / 60) % 60 << "M" << (time / 1000) % 60 << "." << time % 1000 / 10 << "S";
|
||||
return r.str();
|
||||
}
|
||||
|
||||
void OutDashMP4::buildFtyp(unsigned int tid){
|
||||
H.Chunkify("\000\000\000", 3, myConn);
|
||||
H.Chunkify("\040", 1, myConn);
|
||||
H.Chunkify("ftypisom\000\000\000\000isom", 16, myConn);
|
||||
if (myMeta.tracks[tid].type == "video"){
|
||||
H.Chunkify("avc1", 4, myConn);
|
||||
}else{
|
||||
H.Chunkify("M4A ", 4, myConn);
|
||||
}
|
||||
H.Chunkify("mp42dash", 8, myConn);
|
||||
}
|
||||
|
||||
void OutDashMP4::buildStyp(unsigned int tid){
|
||||
H.Chunkify("\000\000\000\030stypmsdh\000\000\000\000msdhmsix", 24, myConn);
|
||||
}
|
||||
|
||||
std::string OutDashMP4::buildMoov(unsigned int tid){
|
||||
std::string trackType = myMeta.tracks[tid].type;
|
||||
MP4::MOOV moovBox;
|
||||
|
||||
MP4::MVHD mvhdBox(0);
|
||||
mvhdBox.setTrackID(2);
|
||||
mvhdBox.setDuration(0xFFFFFFFF);
|
||||
moovBox.setContent(mvhdBox, 0);
|
||||
|
||||
MP4::IODS iodsBox;
|
||||
if (trackType == "video"){
|
||||
iodsBox.setODVideoLevel(0xFE);
|
||||
}else{
|
||||
iodsBox.setODAudioLevel(0xFE);
|
||||
}
|
||||
moovBox.setContent(iodsBox, 1);
|
||||
|
||||
|
||||
MP4::MVEX mvexBox;
|
||||
MP4::MEHD mehdBox;
|
||||
mehdBox.setFragmentDuration(0xFFFFFFFF);
|
||||
mvexBox.setContent(mehdBox, 0);
|
||||
MP4::TREX trexBox;
|
||||
trexBox.setTrackID(1);
|
||||
mvexBox.setContent(trexBox, 1);
|
||||
moovBox.setContent(mvexBox, 2);
|
||||
|
||||
MP4::TRAK trakBox;
|
||||
MP4::TKHD tkhdBox(1, 0, myMeta.tracks[tid].width, myMeta.tracks[tid].height);
|
||||
tkhdBox.setFlags(3);
|
||||
if (trackType == "audio"){
|
||||
tkhdBox.setVolume(256);
|
||||
tkhdBox.setWidth(0);
|
||||
tkhdBox.setHeight(0);
|
||||
}
|
||||
tkhdBox.setDuration(0xFFFFFFFF);
|
||||
trakBox.setContent(tkhdBox, 0);
|
||||
|
||||
MP4::MDIA mdiaBox;
|
||||
MP4::MDHD mdhdBox(0);
|
||||
mdhdBox.setLanguage(0x44);
|
||||
mdhdBox.setDuration(myMeta.tracks[tid].lastms);
|
||||
mdiaBox.setContent(mdhdBox, 0);
|
||||
|
||||
if (trackType == "video"){
|
||||
MP4::HDLR hdlrBox(myMeta.tracks[tid].type,"VideoHandler");
|
||||
mdiaBox.setContent(hdlrBox, 1);
|
||||
}else{
|
||||
MP4::HDLR hdlrBox(myMeta.tracks[tid].type,"SoundHandler");
|
||||
mdiaBox.setContent(hdlrBox, 1);
|
||||
}
|
||||
|
||||
MP4::MINF minfBox;
|
||||
MP4::DINF dinfBox;
|
||||
MP4::DREF drefBox;
|
||||
dinfBox.setContent(drefBox, 0);
|
||||
minfBox.setContent(dinfBox, 0);
|
||||
|
||||
MP4::STBL stblBox;
|
||||
MP4::STSD stsdBox;
|
||||
stsdBox.setVersion(0);
|
||||
|
||||
if (myMeta.tracks[tid].codec == "H264"){
|
||||
MP4::AVC1 avc1Box;
|
||||
avc1Box.setWidth(myMeta.tracks[tid].width);
|
||||
avc1Box.setHeight(myMeta.tracks[tid].height);
|
||||
|
||||
MP4::AVCC avccBox;
|
||||
avccBox.setPayload(myMeta.tracks[tid].init);
|
||||
avc1Box.setCLAP(avccBox);
|
||||
stsdBox.setEntry(avc1Box, 0);
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "HEVC"){
|
||||
MP4::HEV1 hev1Box;
|
||||
hev1Box.setWidth(myMeta.tracks[tid].width);
|
||||
hev1Box.setHeight(myMeta.tracks[tid].height);
|
||||
|
||||
MP4::HVCC hvccBox;
|
||||
hvccBox.setPayload(myMeta.tracks[tid].init);
|
||||
hev1Box.setCLAP(hvccBox);
|
||||
stsdBox.setEntry(hev1Box, 0);
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "AAC"){
|
||||
MP4::AudioSampleEntry ase;
|
||||
ase.setCodec("mp4a");
|
||||
ase.setDataReferenceIndex(1);
|
||||
ase.setSampleRate(myMeta.tracks[tid].rate);
|
||||
ase.setChannelCount(myMeta.tracks[tid].channels);
|
||||
ase.setSampleSize(myMeta.tracks[tid].size);
|
||||
MP4::ESDS esdsBox(myMeta.tracks[tid].init);
|
||||
ase.setCodecBox(esdsBox);
|
||||
stsdBox.setEntry(ase,0);
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "AC3"){
|
||||
///\todo Note: this code is copied, note for muxing seperation
|
||||
MP4::AudioSampleEntry ase;
|
||||
ase.setCodec("ac-3");
|
||||
ase.setDataReferenceIndex(1);
|
||||
ase.setSampleRate(myMeta.tracks[tid].rate);
|
||||
ase.setChannelCount(myMeta.tracks[tid].channels);
|
||||
ase.setSampleSize(myMeta.tracks[tid].size);
|
||||
MP4::DAC3 dac3Box;
|
||||
switch (myMeta.tracks[tid].rate){
|
||||
case 48000:
|
||||
dac3Box.setSampleRateCode(0);
|
||||
break;
|
||||
case 44100:
|
||||
dac3Box.setSampleRateCode(1);
|
||||
break;
|
||||
case 32000:
|
||||
dac3Box.setSampleRateCode(2);
|
||||
break;
|
||||
default:
|
||||
dac3Box.setSampleRateCode(3);
|
||||
break;
|
||||
}
|
||||
/// \todo the next settings are set to generic values, we might want to make these flexible
|
||||
dac3Box.setBitStreamIdentification(8);//check the docs, this is a weird property
|
||||
dac3Box.setBitStreamMode(0);//set to main, mixed audio
|
||||
dac3Box.setAudioConfigMode(2);///\todo find out if ACMode should be different
|
||||
if (myMeta.tracks[tid].channels > 4){
|
||||
dac3Box.setLowFrequencyEffectsChannelOn(1);
|
||||
}else{
|
||||
dac3Box.setLowFrequencyEffectsChannelOn(0);
|
||||
}
|
||||
dac3Box.setFrameSizeCode(20);//should be OK, but test this.
|
||||
ase.setCodecBox(dac3Box);
|
||||
}
|
||||
|
||||
stblBox.setContent(stsdBox, 0);
|
||||
|
||||
MP4::STTS sttsBox;
|
||||
sttsBox.setVersion(0);
|
||||
stblBox.setContent(sttsBox, 1);
|
||||
|
||||
MP4::STSC stscBox;
|
||||
stscBox.setVersion(0);
|
||||
stblBox.setContent(stscBox, 2);
|
||||
|
||||
MP4::STCO stcoBox;
|
||||
stcoBox.setVersion(0);
|
||||
stblBox.setContent(stcoBox, 3);
|
||||
|
||||
MP4::STSZ stszBox;
|
||||
stszBox.setVersion(0);
|
||||
stblBox.setContent(stszBox, 4);
|
||||
|
||||
minfBox.setContent(stblBox, 1);
|
||||
|
||||
if (trackType == "video"){
|
||||
MP4::VMHD vmhdBox;
|
||||
vmhdBox.setFlags(1);
|
||||
minfBox.setContent(vmhdBox, 2);
|
||||
}else{
|
||||
MP4::SMHD smhdBox;
|
||||
minfBox.setContent(smhdBox, 2);
|
||||
}
|
||||
|
||||
mdiaBox.setContent(minfBox, 2);
|
||||
|
||||
trakBox.setContent(mdiaBox, 1);
|
||||
|
||||
moovBox.setContent(trakBox, 3);
|
||||
|
||||
return std::string(moovBox.asBox(),moovBox.boxedSize());
|
||||
}
|
||||
|
||||
std::string OutDashMP4::buildSidx(unsigned int tid){
|
||||
MP4::AVCC avccBox;
|
||||
MP4::HVCC hvccBox;
|
||||
if (myMeta.tracks[tid].codec == "H264"){
|
||||
avccBox.setPayload(myMeta.tracks[tid].init);
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "HEVC"){
|
||||
hvccBox.setPayload(myMeta.tracks[tid].init);
|
||||
}
|
||||
int curPart = 0;
|
||||
MP4::SIDX sidxBox;
|
||||
sidxBox.setReferenceID(1);
|
||||
sidxBox.setTimescale(1000);
|
||||
sidxBox.setEarliestPresentationTime(myMeta.tracks[tid].firstms);
|
||||
sidxBox.setFirstOffset(0);
|
||||
int j = 0;
|
||||
for (std::deque<DTSC::Key>::iterator it = myMeta.tracks[tid].keys.begin(); it != myMeta.tracks[tid].keys.end(); it++){
|
||||
MP4::sidxReference refItem;
|
||||
refItem.referenceType = false;
|
||||
refItem.referencedSize = 0;
|
||||
for (int i = 0; i < it->getParts(); i++){
|
||||
refItem.referencedSize += myMeta.tracks[tid].parts[curPart++].getSize();
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "H264"){
|
||||
refItem.referencedSize += 14 + avccBox.getSPSLen() + avccBox.getPPSLen();
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "HEVC"){
|
||||
std::deque<MP4::HVCCArrayEntry> content = hvccBox.getArrays();
|
||||
for (std::deque<MP4::HVCCArrayEntry>::iterator it = content.begin(); it != content.end(); it++){
|
||||
for (std::deque<std::string>::iterator it2 = it->nalUnits.begin(); it2 != it->nalUnits.end(); it2++){
|
||||
refItem.referencedSize += 4 + (*it2).size();
|
||||
}
|
||||
}
|
||||
}
|
||||
fragmentSizes[tid][j] = refItem.referencedSize;
|
||||
if (it->getLength()){
|
||||
refItem.subSegmentDuration = it->getLength();
|
||||
}else{
|
||||
refItem.subSegmentDuration = myMeta.tracks[tid].lastms - it->getTime();
|
||||
}
|
||||
refItem.sapStart = false;
|
||||
refItem.sapType = 0;
|
||||
refItem.sapDeltaTime = 0;
|
||||
sidxBox.setReference(refItem, j++);
|
||||
}
|
||||
return std::string(sidxBox.asBox(),sidxBox.boxedSize());
|
||||
}
|
||||
|
||||
std::string OutDashMP4::buildSidx(unsigned int tid, unsigned int keyNum){
|
||||
MP4::AVCC avccBox;
|
||||
avccBox.setPayload(myMeta.tracks[tid].init);
|
||||
int curPart = 0;
|
||||
MP4::SIDX sidxBox;
|
||||
sidxBox.setReferenceID(1);
|
||||
sidxBox.setTimescale(1000);
|
||||
sidxBox.setEarliestPresentationTime(myMeta.tracks[tid].keys[keyNum].getTime());
|
||||
sidxBox.setFirstOffset(0);
|
||||
for (int i = 0; i < keyNum; i++){
|
||||
curPart += myMeta.tracks[tid].keys[i].getParts();
|
||||
}
|
||||
MP4::sidxReference refItem;
|
||||
refItem.referenceType = false;
|
||||
if (myMeta.tracks[tid].keys[keyNum].getLength()){
|
||||
refItem.subSegmentDuration = myMeta.tracks[tid].keys[keyNum].getLength();
|
||||
}else{
|
||||
refItem.subSegmentDuration = myMeta.tracks[tid].lastms - myMeta.tracks[tid].keys[keyNum].getTime();
|
||||
}
|
||||
refItem.sapStart = false;
|
||||
refItem.sapType = 0;
|
||||
refItem.sapDeltaTime = 0;
|
||||
sidxBox.setReference(refItem, 0);
|
||||
return std::string(sidxBox.asBox(),sidxBox.boxedSize());
|
||||
}
|
||||
|
||||
std::string OutDashMP4::buildMoof(unsigned int tid, unsigned int keyNum){
|
||||
MP4::MOOF moofBox;
|
||||
|
||||
MP4::MFHD mfhdBox;
|
||||
mfhdBox.setSequenceNumber(keyNum + 1);
|
||||
moofBox.setContent(mfhdBox, 0);
|
||||
|
||||
MP4::TRAF trafBox;
|
||||
MP4::TFHD tfhdBox;
|
||||
if (myMeta.tracks[tid].codec == "H264" || myMeta.tracks[tid].codec == "HEVC"){
|
||||
tfhdBox.setTrackID(1);
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "AAC"){
|
||||
tfhdBox.setFlags(MP4::tfhdSampleFlag);
|
||||
tfhdBox.setTrackID(1);
|
||||
tfhdBox.setDefaultSampleFlags(MP4::isKeySample);
|
||||
}
|
||||
trafBox.setContent(tfhdBox, 0);
|
||||
|
||||
MP4::TFDT tfdtBox;
|
||||
///\todo Determine index for live
|
||||
tfdtBox.setBaseMediaDecodeTime(myMeta.tracks[tid].keys[keyNum].getTime());
|
||||
trafBox.setContent(tfdtBox, 1);
|
||||
|
||||
int i = 0;
|
||||
|
||||
for (int j = 0; j < keyNum; j++){
|
||||
i += myMeta.tracks[tid].keys[j].getParts();
|
||||
}
|
||||
|
||||
MP4::TRUN trunBox;
|
||||
if (myMeta.tracks[tid].codec == "H264"){
|
||||
trunBox.setFlags(MP4::trundataOffset | MP4::trunsampleSize | MP4::trunsampleDuration | MP4::trunfirstSampleFlags | MP4::trunsampleOffsets);
|
||||
trunBox.setFirstSampleFlags(MP4::isKeySample);
|
||||
trunBox.setDataOffset(88 + (12 * myMeta.tracks[tid].keys[keyNum].getParts()) + 8);
|
||||
|
||||
MP4::AVCC avccBox;
|
||||
avccBox.setPayload(myMeta.tracks[tid].init);
|
||||
for (int j = 0; j < myMeta.tracks[tid].keys[keyNum].getParts(); j++){
|
||||
MP4::trunSampleInformation trunEntry;
|
||||
if (!j){
|
||||
trunEntry.sampleSize = myMeta.tracks[tid].parts[i].getSize() + 14 + avccBox.getSPSLen() + avccBox.getPPSLen();
|
||||
}else{
|
||||
trunEntry.sampleSize = myMeta.tracks[tid].parts[i].getSize();
|
||||
}
|
||||
trunEntry.sampleDuration = myMeta.tracks[tid].parts[i].getDuration();
|
||||
trunEntry.sampleOffset = myMeta.tracks[tid].parts[i].getOffset();
|
||||
trunBox.setSampleInformation(trunEntry, j);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "HEVC"){
|
||||
trunBox.setFlags(MP4::trundataOffset | MP4::trunsampleSize | MP4::trunsampleDuration | MP4::trunfirstSampleFlags | MP4::trunsampleOffsets);
|
||||
trunBox.setFirstSampleFlags(MP4::isKeySample);
|
||||
trunBox.setDataOffset(88 + (12 * myMeta.tracks[tid].keys[keyNum].getParts()) + 8);
|
||||
|
||||
MP4::HVCC hvccBox;
|
||||
hvccBox.setPayload(myMeta.tracks[tid].init);
|
||||
std::deque<MP4::HVCCArrayEntry> content = hvccBox.getArrays();
|
||||
for (int j = 0; j < myMeta.tracks[tid].keys[keyNum].getParts(); j++){
|
||||
MP4::trunSampleInformation trunEntry;
|
||||
trunEntry.sampleSize = myMeta.tracks[tid].parts[i].getSize();
|
||||
if (!j){
|
||||
for (std::deque<MP4::HVCCArrayEntry>::iterator it = content.begin(); it != content.end(); it++){
|
||||
for (std::deque<std::string>::iterator it2 = it->nalUnits.begin(); it2 != it->nalUnits.end(); it2++){
|
||||
trunEntry.sampleSize += 4 + (*it2).size();
|
||||
}
|
||||
}
|
||||
}
|
||||
trunEntry.sampleDuration = myMeta.tracks[tid].parts[i].getDuration();
|
||||
trunEntry.sampleOffset = myMeta.tracks[tid].parts[i].getOffset();
|
||||
trunBox.setSampleInformation(trunEntry, j);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "AAC"){
|
||||
trunBox.setFlags(MP4::trundataOffset | MP4::trunsampleSize | MP4::trunsampleDuration);
|
||||
trunBox.setDataOffset(88 + (8 * myMeta.tracks[tid].keys[keyNum].getParts()) + 8);
|
||||
for (int j = 0; j < myMeta.tracks[tid].keys[keyNum].getParts(); j++){
|
||||
MP4::trunSampleInformation trunEntry;
|
||||
trunEntry.sampleSize = myMeta.tracks[tid].parts[i].getSize();
|
||||
trunEntry.sampleDuration = myMeta.tracks[tid].parts[i].getDuration();
|
||||
trunBox.setSampleInformation(trunEntry, j);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
trafBox.setContent(trunBox, 2);
|
||||
|
||||
moofBox.setContent(trafBox, 1);
|
||||
|
||||
return std::string(moofBox.asBox(), moofBox.boxedSize());
|
||||
}
|
||||
|
||||
std::string OutDashMP4::buildNalUnit(unsigned int len, const char * data){
|
||||
std::stringstream r;
|
||||
r << (char)((len >> 24) & 0xFF);
|
||||
r << (char)((len >> 16) & 0xFF);
|
||||
r << (char)((len >> 8) & 0xFF);
|
||||
r << (char)((len) & 0xFF);
|
||||
r << std::string(data, len);
|
||||
return r.str();
|
||||
}
|
||||
|
||||
void OutDashMP4::buildMdat(unsigned int tid, unsigned int keyNum){
|
||||
MP4::AVCC avccBox;
|
||||
avccBox.setPayload(myMeta.tracks[tid].init);
|
||||
std::stringstream r;
|
||||
int size = fragmentSizes[tid][keyNum] + 8;
|
||||
r << (char)((size >> 24) & 0xFF);
|
||||
r << (char)((size >> 16) & 0xFF);
|
||||
r << (char)((size >> 8) & 0xFF);
|
||||
r << (char)((size) & 0xFF);
|
||||
r << "mdat";
|
||||
H.Chunkify(r.str().data(), r.str().size(), myConn);
|
||||
selectedTracks.clear();
|
||||
selectedTracks.insert(tid);
|
||||
seek(myMeta.tracks[tid].keys[keyNum].getTime());
|
||||
std::string init;
|
||||
char * data;
|
||||
unsigned int dataLen;
|
||||
int partNum = 0;
|
||||
for (int i = 0; i < keyNum; i++){
|
||||
partNum += myMeta.tracks[tid].keys[i].getParts();
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "H264"){
|
||||
init = buildNalUnit(2, "\011\340");
|
||||
H.Chunkify(init, myConn);//09E0
|
||||
init = buildNalUnit(avccBox.getSPSLen(), avccBox.getSPS());
|
||||
H.Chunkify(init, myConn);
|
||||
init = buildNalUnit(avccBox.getPPSLen(), avccBox.getPPS());
|
||||
H.Chunkify(init, myConn);
|
||||
}
|
||||
if (myMeta.tracks[tid].codec == "HEVC"){
|
||||
MP4::HVCC hvccBox;
|
||||
hvccBox.setPayload(myMeta.tracks[tid].init);
|
||||
std::deque<MP4::HVCCArrayEntry> content = hvccBox.getArrays();
|
||||
for (int j = 0; j < myMeta.tracks[tid].keys[keyNum].getParts(); j++){
|
||||
for (std::deque<MP4::HVCCArrayEntry>::iterator it = content.begin(); it != content.end(); it++){
|
||||
for (std::deque<std::string>::iterator it2 = it->nalUnits.begin(); it2 != it->nalUnits.end(); it2++){
|
||||
init = buildNalUnit((*it2).size(), (*it2).c_str());
|
||||
H.Chunkify(init, myConn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < myMeta.tracks[tid].keys[keyNum].getParts(); i++){
|
||||
prepareNext();
|
||||
thisPacket.getString("data", data, dataLen);
|
||||
H.Chunkify(data, dataLen, myConn);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::string OutDashMP4::buildManifest(){
|
||||
initialize();
|
||||
int lastTime = 0;
|
||||
int lastVidTime = 0;
|
||||
int vidKeys = 0;
|
||||
int vidInitTrack = 0;
|
||||
int lastAudTime = 0;
|
||||
int audKeys = 0;
|
||||
int audInitTrack = 0;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it ++){
|
||||
if (it->second.lastms > lastTime){
|
||||
lastTime = it->second.lastms;
|
||||
}
|
||||
if (it->second.codec == "H264" && it->second.lastms > lastVidTime){
|
||||
lastVidTime = it->second.lastms;
|
||||
vidKeys = it->second.keys.size();
|
||||
vidInitTrack = it->first;
|
||||
}
|
||||
if (it->second.codec == "HEVC" && it->second.lastms > lastVidTime){
|
||||
lastVidTime = it->second.lastms;
|
||||
vidKeys = it->second.keys.size();
|
||||
vidInitTrack = it->first;
|
||||
}
|
||||
if (it->second.codec == "AAC" && it->second.lastms > lastAudTime){
|
||||
lastAudTime = it->second.lastms;
|
||||
audKeys = it->second.keys.size();
|
||||
audInitTrack = it->first;
|
||||
}
|
||||
}
|
||||
std::stringstream r;
|
||||
r << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
|
||||
r << "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" type=\"static\" mediaPresentationDuration=\"" << makeTime(lastTime) << "\" minBufferTime=\"PT1.5S\" >" << std::endl;
|
||||
r << " <ProgramInformation><Title>" << streamName << "</Title></ProgramInformation>" << std::endl;
|
||||
r << " <Period start=\"PT0S\">" << std::endl;
|
||||
if (vidInitTrack){
|
||||
r << " <AdaptationSet id=\"0\" mimeType=\"video/mp4\" width=\"" << myMeta.tracks[vidInitTrack].width << "\" height=\"" << myMeta.tracks[vidInitTrack].height << "\" frameRate=\"" << myMeta.tracks[vidInitTrack].fpks / 1000 << "\" segmentAlignment=\"true\" startWithSAP=\"1\" subsegmentAlignment=\"true\" subsegmentStartsWithSAP=\"1\">" << std::endl;
|
||||
r << " <SegmentTemplate timescale=\"1000\" media=\"chunk_$RepresentationID$_$Time$.m4s\" initialization=\"chunk_$RepresentationID$_init.m4s\">" << std::endl;
|
||||
r << " <SegmentTimeline>" << std::endl;
|
||||
for (int i = 0; i < myMeta.tracks[vidInitTrack].keys.size() - 1; i++){
|
||||
r << " <S " << (i == 0 ? "t=\"0\" " : "") << "d=\"" << myMeta.tracks[vidInitTrack].keys[i].getLength() << "\" />" << std::endl;
|
||||
}
|
||||
int lastDur = myMeta.tracks[vidInitTrack].lastms - myMeta.tracks[vidInitTrack].keys.rbegin()->getTime();
|
||||
r << " <S d=\"" << lastDur << "\" />" << std::endl;
|
||||
r << " </SegmentTimeline>" << std::endl;
|
||||
r << " </SegmentTemplate>" << std::endl;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.codec == "H264"){
|
||||
MP4::AVCC avccBox;
|
||||
avccBox.setPayload(it->second.init);
|
||||
r << " <Representation ";
|
||||
r << "id=\"" << it->first << "\" ";
|
||||
r << "codecs=\"avc1.";
|
||||
r << std::hex << std::setw(2) << std::setfill('0') << (int)avccBox.getSPS()[0] << std::dec;
|
||||
r << std::hex << std::setw(2) << std::setfill('0') << (int)avccBox.getSPS()[1] << std::dec;
|
||||
r << std::hex << std::setw(2) << std::setfill('0') << (int)avccBox.getSPS()[2] << std::dec;
|
||||
r << "\" ";
|
||||
r << "bandwidth=\"" << it->second.bps << "\" ";
|
||||
r << "/>" << std::endl;
|
||||
}
|
||||
if (it->second.codec == "HEVC"){
|
||||
r << " <Representation ";
|
||||
r << "id=\"" << it->first << "\" ";
|
||||
r << "codecs=\"hev1.";
|
||||
r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[1] << std::dec;
|
||||
r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[6] << std::dec;
|
||||
r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[7] << std::dec;
|
||||
r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[8] << std::dec;
|
||||
r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[9] << std::dec;
|
||||
r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[10] << std::dec;
|
||||
r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[11] << std::dec;
|
||||
r << std::hex << std::setw(2) << std::setfill('0') << (int)it->second.init[12] << std::dec;
|
||||
r << "\" ";
|
||||
r << "bandwidth=\"" << it->second.bps << "\" ";
|
||||
r << "/>" << std::endl;
|
||||
}
|
||||
}
|
||||
r << " </AdaptationSet>" << std::endl;
|
||||
}
|
||||
if (audInitTrack){
|
||||
r << " <AdaptationSet id=\"1\" mimeType=\"audio/mp4\" segmentAlignment=\"true\" startWithSAP=\"1\" subsegmentAlignment=\"true\" subsegmentStartsWithSAP=\"1\" >" << std::endl;
|
||||
r << " <Role schemeIdUri=\"urn:mpeg:dash:role:2011\" value=\"main\"/>" << std::endl;
|
||||
r << " <SegmentTemplate timescale=\"1000\" media=\"chunk_$RepresentationID$_$Time$.m4s\" initialization=\"chunk_$RepresentationID$_init.m4s\">" << std::endl;
|
||||
|
||||
r << " <SegmentTimeline>" << std::endl;
|
||||
for (int i = 0; i < myMeta.tracks[audInitTrack].keys.size() - 1; i++){
|
||||
r << " <S " << (i == 0 ? "t=\"0\" " : "") << "d=\"" << myMeta.tracks[audInitTrack].keys[i].getLength() << "\" />" << std::endl;
|
||||
}
|
||||
int lastDur = myMeta.tracks[audInitTrack].lastms - myMeta.tracks[audInitTrack].keys.rbegin()->getTime();
|
||||
r << " <S d=\"" << lastDur << "\" />" << std::endl;
|
||||
r << " </SegmentTimeline>" << std::endl;
|
||||
r << " </SegmentTemplate>" << std::endl;
|
||||
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.codec == "AAC"){
|
||||
r << " <Representation ";
|
||||
r << "id=\"" << it->first << "\" ";
|
||||
r << "codecs=\"mp4a.40.2\" ";
|
||||
r << "audioSamplingRate=\"" << it->second.rate << "\" ";
|
||||
r << "bandwidth=\"" << it->second.bps << "\">" << std::endl;
|
||||
r << " <AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"" << it->second.channels << "\" />" << std::endl;
|
||||
r << " </Representation>" << std::endl;
|
||||
}
|
||||
}
|
||||
r << " </AdaptationSet>" << std::endl;
|
||||
}
|
||||
r << " </Period>" << std::endl;
|
||||
r << "</MPD>" << std::endl;
|
||||
|
||||
return r.str();
|
||||
}
|
||||
|
||||
void OutDashMP4::init(Util::Config * cfg){
|
||||
HTTPOutput::init(cfg);
|
||||
capa["name"] = "DASHMP4";
|
||||
capa["desc"] = "Enables HTTP protocol progressive streaming.";
|
||||
capa["url_rel"] = "/dash/$/index.mpd";
|
||||
capa["url_prefix"] = "/dash/$/";
|
||||
capa["socket"] = "http_dash_mp4";
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][0u].append("HEVC");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
capa["methods"][0u]["handler"] = "http";
|
||||
capa["methods"][0u]["type"] = "dash/video/mp4";
|
||||
capa["methods"][0u]["priority"] = 8ll;
|
||||
capa["methods"][0u]["nolive"] = 1;
|
||||
}
|
||||
|
||||
/// Parses a "Range: " header, setting byteStart, byteEnd and seekPoint using data from metadata and tracks to do
|
||||
/// the calculations.
|
||||
/// On error, byteEnd is set to zero.
|
||||
void OutDashMP4::parseRange(std::string header, long long & byteStart, long long & byteEnd){
|
||||
int firstPos = header.find("=") + 1;
|
||||
byteStart = atoll(header.substr(firstPos, header.find("-", firstPos)).c_str());
|
||||
byteEnd = atoll(header.substr(header.find("-", firstPos) + 1).c_str());
|
||||
|
||||
DEBUG_MSG(DLVL_DEVEL, "Range request: %lli-%lli (%s)", byteStart, byteEnd, header.c_str());
|
||||
}
|
||||
|
||||
int OutDashMP4::getKeyFromRange(unsigned int tid, long long int byteStart){
|
||||
unsigned long long int currOffset = 0;
|
||||
for (int i = 0; i < myMeta.tracks[tid].keys.size(); i++){
|
||||
if (byteStart == currOffset){
|
||||
return i;
|
||||
}
|
||||
if (byteStart < currOffset && i > 0){
|
||||
return i - 1;
|
||||
}
|
||||
DEBUG_MSG(DLVL_DEVEL, "%lld > %llu", byteStart, currOffset);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void OutDashMP4::initialize(){
|
||||
HTTPOutput::initialize();
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (!moovBoxes.count(it->first)){
|
||||
moovBoxes[it->first] = buildMoov(it->first);
|
||||
buildSidx(it->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OutDashMP4::onHTTP(){
|
||||
initialize();
|
||||
std::string url = H.url;
|
||||
if (H.method == "OPTIONS"){
|
||||
H.Clean();
|
||||
H.SetHeader("Content-Type", "application/octet-stream");
|
||||
H.SetHeader("Cache-Control", "no-cache");
|
||||
H.SetHeader("MistMultiplex", "No");
|
||||
|
||||
H.SetHeader("Access-Control-Allow-Origin", "*");
|
||||
H.SetHeader("Access-Control-Allow-Methods", "GET, POST");
|
||||
H.SetHeader("Access-Control-Allow-Headers", "Content-Type, X-Requested-With");
|
||||
H.SetHeader("Access-Control-Allow-Credentials", "true");
|
||||
H.SetBody("");
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
H.Clean();
|
||||
return;
|
||||
}
|
||||
if (url.find(".mpd") != std::string::npos){
|
||||
H.Clean();
|
||||
H.SetHeader("Content-Type", "application/xml");
|
||||
H.SetHeader("Cache-Control", "no-cache");
|
||||
H.SetHeader("MistMultiplex", "No");
|
||||
|
||||
H.SetHeader("Access-Control-Allow-Origin", "*");
|
||||
H.SetHeader("Access-Control-Allow-Methods", "GET, POST");
|
||||
H.SetHeader("Access-Control-Allow-Headers", "Content-Type, X-Requested-With");
|
||||
H.SetHeader("Access-Control-Allow-Credentials", "true");
|
||||
|
||||
H.SetBody(buildManifest());
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
DEVEL_MSG("Manifest sent");
|
||||
}else{
|
||||
long long int bench = Util::getMS();
|
||||
int pos = url.find("chunk_") + 6;//put our marker just after the _ beyond chunk
|
||||
int tid = atoi(url.substr(pos).c_str());
|
||||
DEBUG_MSG(DLVL_DEVEL, "Track %d requested", tid);
|
||||
|
||||
H.Clean();
|
||||
H.SetHeader("Content-Type", "video/mp4");
|
||||
H.SetHeader("Cache-Control", "no-cache");
|
||||
H.SetHeader("MistMultiplex", "No");
|
||||
|
||||
H.SetHeader("Access-Control-Allow-Origin", "*");
|
||||
H.SetHeader("Access-Control-Allow-Methods", "GET, POST");
|
||||
H.SetHeader("Access-Control-Allow-Headers", "Content-Type, X-Requested-With");
|
||||
H.SetHeader("Access-Control-Allow-Credentials", "true");
|
||||
H.StartResponse(H, myConn);
|
||||
|
||||
if (url.find("init.m4s") != std::string::npos){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Handling init");
|
||||
buildFtyp(tid);
|
||||
H.Chunkify(moovBoxes[tid], myConn);
|
||||
}else{
|
||||
pos = url.find("_", pos + 1) + 1;
|
||||
int keyId = atoi(url.substr(pos).c_str());
|
||||
DEBUG_MSG(DLVL_DEVEL, "Searching for time %d", keyId);
|
||||
unsigned int keyNum = myMeta.tracks[tid].timeToKeynum(keyId);
|
||||
INFO_MSG("Detected key %d:%d for time %d", tid, keyNum, keyId);
|
||||
buildStyp(tid);
|
||||
std::string tmp = buildSidx(tid, keyNum);
|
||||
H.Chunkify(tmp, myConn);
|
||||
tmp = buildMoof(tid, keyNum);
|
||||
H.Chunkify(tmp, myConn);
|
||||
buildMdat(tid, keyNum);
|
||||
}
|
||||
H.Chunkify("", 0, myConn);
|
||||
H.Clean();
|
||||
INFO_MSG("Done handling request, took %lld ms", Util::getMS() - bench);
|
||||
return;
|
||||
}
|
||||
H.Clean();
|
||||
parseData = false;
|
||||
wantRequest = true;
|
||||
}
|
||||
|
||||
void OutDashMP4::sendNext(){}
|
||||
void OutDashMP4::sendHeader(){}
|
||||
}
|
||||
33
src/output/output_dash_mp4.h
Normal file
33
src/output/output_dash_mp4.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#include "output_http.h"
|
||||
#include <mist/mp4_generic.h>
|
||||
#include <mist/http_parser.h>
|
||||
|
||||
namespace Mist {
|
||||
class OutDashMP4 : public HTTPOutput {
|
||||
public:
|
||||
OutDashMP4(Socket::Connection & conn);
|
||||
~OutDashMP4();
|
||||
static void init(Util::Config * cfg);
|
||||
void onHTTP();
|
||||
void sendNext();
|
||||
void sendHeader();
|
||||
void initialize();
|
||||
protected:
|
||||
std::string makeTime(long long unsigned int time);
|
||||
std::string buildManifest();
|
||||
void buildFtyp(unsigned int trackid);
|
||||
void buildStyp(unsigned int trackid);
|
||||
std::string buildMoov(unsigned int trackid);
|
||||
std::string buildSidx(unsigned int trackid);
|
||||
std::string buildSidx(unsigned int trackid, unsigned int keynum);
|
||||
std::string buildMoof(unsigned int trackid, unsigned int keynum);
|
||||
void buildMdat(unsigned int trackid, unsigned int keynum);
|
||||
std::map<unsigned int, std::map<unsigned int, long long unsigned int> > fragmentSizes;
|
||||
std::string buildNalUnit(unsigned int len, const char * data);
|
||||
void parseRange(std::string header, long long & byteStart, long long & byteEnd);
|
||||
int getKeyFromRange(unsigned int tid, long long int byteStart);
|
||||
std::map<int,std::string> moovBoxes;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::OutDashMP4 mistOut;
|
||||
|
|
@ -57,6 +57,19 @@ namespace Mist {
|
|||
int j = 0;
|
||||
if (myMeta.tracks[tid].fragments.size()){
|
||||
std::deque<DTSC::Fragment>::iterator fragIt = myMeta.tracks[tid].fragments.begin();
|
||||
/*LTS-START*/
|
||||
if (myMeta.live){
|
||||
unsigned int skip = (( myMeta.tracks[tid].fragments.size()-1) * config->getInteger("startpos")) / 1000u;
|
||||
for (unsigned int z = 0; z < skip; ++z){
|
||||
++fragIt;
|
||||
++j;
|
||||
}
|
||||
if (skip && fragIt == myMeta.tracks[tid].fragments.end()){
|
||||
--fragIt;
|
||||
--j;
|
||||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
unsigned int firstTime = myMeta.tracks[tid].getKey(fragIt->getNumber()).getTime();
|
||||
while (fragIt != myMeta.tracks[tid].fragments.end()){
|
||||
if (myMeta.vod || fragIt->getDuration() > 0){
|
||||
|
|
@ -160,6 +173,7 @@ namespace Mist {
|
|||
capa["methods"][0u]["handler"] = "http";
|
||||
capa["methods"][0u]["type"] = "flash/11";
|
||||
capa["methods"][0u]["priority"] = 7ll;
|
||||
cfg->getOption("startpos", true)[0u] = 0ll;
|
||||
}
|
||||
|
||||
void OutHDS::sendNext(){
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Mist {
|
|||
int audioId = -1;
|
||||
std::string audioName;
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.codec == "AAC"){
|
||||
if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3"){
|
||||
audioId = it->first;
|
||||
audioName = it->second.getIdentifier();
|
||||
break;
|
||||
|
|
@ -19,7 +19,7 @@ namespace Mist {
|
|||
}
|
||||
unsigned int vidTracks = 0;
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.codec == "H264"){
|
||||
if (it->second.codec == "H264" || it->second.codec == "HEVC"){
|
||||
vidTracks++;
|
||||
int bWidth = it->second.bps * 2;
|
||||
if (bWidth < 5){
|
||||
|
|
@ -84,6 +84,20 @@ namespace Mist {
|
|||
}
|
||||
//only print the last segment when VoD
|
||||
lines.pop_back();
|
||||
/*LTS-START*/
|
||||
unsigned int skip = (( myMeta.tracks[tid].fragments.size()-1) * config->getInteger("startpos")) / 1000u;
|
||||
while (skippedLines < skip && lines.size()){
|
||||
lines.pop_front();
|
||||
skippedLines++;
|
||||
}
|
||||
if (config->getInteger("listlimit")){
|
||||
unsigned long listlimit = config->getInteger("listlimit");
|
||||
while (lines.size() > listlimit){
|
||||
lines.pop_front();
|
||||
skippedLines++;
|
||||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
}
|
||||
|
||||
result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n";
|
||||
|
|
@ -112,12 +126,22 @@ namespace Mist {
|
|||
capa["desc"] = "Enables HTTP protocol Apple-specific streaming (also known as HLS).";
|
||||
capa["url_rel"] = "/hls/$/index.m3u8";
|
||||
capa["url_prefix"] = "/hls/$/";
|
||||
capa["codecs"][0u][0u].append("HEVC");
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
capa["methods"][0u]["handler"] = "http";
|
||||
capa["methods"][0u]["type"] = "html5/application/vnd.apple.mpegurl";
|
||||
capa["methods"][0u]["priority"] = 9ll;
|
||||
/*LTS-START*/
|
||||
cfg->addOption("listlimit", JSON::fromString("{\"arg\":\"integer\",\"default\":0,\"short\":\"y\",\"long\":\"list-limit\",\"help\":\"Maximum number of parts in live playlists (0 = infinite).\"}"));
|
||||
capa["optional"]["listlimit"]["name"] = "Live playlist limit";
|
||||
capa["optional"]["listlimit"]["help"] = "Maximum number of parts in live playlists. (0 = infinite)";
|
||||
capa["optional"]["listlimit"]["default"] = 0ll;
|
||||
capa["optional"]["listlimit"]["type"] = "uint";
|
||||
capa["optional"]["listlimit"]["option"] = "--list-limit";
|
||||
/*LTS-END*/
|
||||
}
|
||||
|
||||
int OutHLS::canSeekms(unsigned int ms){
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <mist/mp4.h>
|
||||
#include <mist/mp4_ms.h>
|
||||
#include <mist/mp4_generic.h>
|
||||
#include <mist/mp4_encryption.h> /*LTS*/
|
||||
#include <mist/base64.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/stream.h>
|
||||
|
|
@ -261,7 +262,7 @@ namespace Mist {
|
|||
int fragCount = 0;
|
||||
for (unsigned int i = 0; fragCount < 2 && i < myMeta.tracks[tid].keys.size() - 1; i++) {
|
||||
if (myMeta.tracks[tid].keys[i].getTime() > seekTime) {
|
||||
DEBUG_MSG(DLVL_HIGH, "Key %d added to fragRef box, time %ld > %lld", i, myMeta.tracks[tid].keys[i].getTime(), seekTime);
|
||||
DEBUG_MSG(DLVL_HIGH, "Key %d added to fragRef box, time %llu > %lld", i, myMeta.tracks[tid].keys[i].getTime(), seekTime);
|
||||
fragref_box.setTime(fragCount, myMeta.tracks[tid].keys[i].getTime() * 10000);
|
||||
fragref_box.setDuration(fragCount, myMeta.tracks[tid].keys[i].getLength() * 10000);
|
||||
fragref_box.setFragmentCount(++fragCount);
|
||||
|
|
@ -273,6 +274,37 @@ namespace Mist {
|
|||
MP4::MOOF moof_box;
|
||||
moof_box.setContent(mfhd_box, 0);
|
||||
moof_box.setContent(traf_box, 1);
|
||||
/*LTS-START*/
|
||||
if (myMeta.tracks[tid].keys.size() == myMeta.tracks[tid].ivecs.size()) {
|
||||
std::string tmpVec = std::string(myMeta.tracks[tid].ivecs[keyObj.getNumber() - myMeta.tracks[tid].keys[0].getNumber()].getData(), 8);
|
||||
unsigned long long int curVec = binToInt(tmpVec);
|
||||
MP4::UUID_SampleEncryption sEnc;
|
||||
sEnc.setVersion(0);
|
||||
if (myMeta.tracks[tid].type == "audio") {
|
||||
sEnc.setFlags(0);
|
||||
for (int i = 0; i < keyObj.getParts(); i++) {
|
||||
MP4::UUID_SampleEncryption_Sample newSample;
|
||||
newSample.InitializationVector = intToBin(curVec);
|
||||
curVec++;
|
||||
sEnc.setSample(newSample, i);
|
||||
}
|
||||
} else {
|
||||
sEnc.setFlags(2);
|
||||
std::deque<long long int> tmpParts;
|
||||
for (int i = 0; i < keyObj.getParts(); i++) {
|
||||
MP4::UUID_SampleEncryption_Sample newSample;
|
||||
newSample.InitializationVector = intToBin(curVec);
|
||||
curVec++;
|
||||
MP4::UUID_SampleEncryption_Sample_Entry newEntry;
|
||||
newEntry.BytesClear = 5;
|
||||
newEntry.BytesEncrypted = myMeta.tracks[tid].parts[partOffset + i].getSize() - 5;
|
||||
newSample.Entries.push_back(newEntry);
|
||||
sEnc.setSample(newSample, i);
|
||||
}
|
||||
}
|
||||
traf_box.setContent(sEnc, 3);
|
||||
}
|
||||
/*LTS-END*/
|
||||
//Setting the correct offsets.
|
||||
moof_box.setContent(traf_box, 1);
|
||||
trun_box.setDataOffset(moof_box.boxedSize() + 8);
|
||||
|
|
@ -290,10 +322,36 @@ namespace Mist {
|
|||
H.Clean();
|
||||
}
|
||||
|
||||
/*LTS-START*/
|
||||
std::string OutHSS::protectionHeader(JSON::Value & encParams) {
|
||||
std::string xmlGen = "<WRMHEADER xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" version=\"4.0.0.0\"><DATA><PROTECTINFO><KEYLEN>16</KEYLEN><ALGID>AESCTR</ALGID></PROTECTINFO><KID>";
|
||||
xmlGen += encParams["keyid"].asString();
|
||||
xmlGen += "</KID><LA_URL>";
|
||||
xmlGen += encParams["la_url"].asString();
|
||||
xmlGen += "</LA_URL></DATA></WRMHEADER>";
|
||||
std::string tmp = toUTF16(xmlGen);
|
||||
tmp = tmp.substr(2);
|
||||
std::stringstream resGen;
|
||||
resGen << (char)((tmp.size() + 10) & 0xFF);
|
||||
resGen << (char)(((tmp.size() + 10) >> 8) & 0xFF);
|
||||
resGen << (char)(((tmp.size() + 10) >> 16) & 0xFF);
|
||||
resGen << (char)(((tmp.size() + 10) >> 24) & 0xFF);
|
||||
resGen << (char)0x01 << (char)0x00;
|
||||
resGen << (char)0x01 << (char)0x00;
|
||||
resGen << (char)((tmp.size()) & 0xFF);
|
||||
resGen << (char)(((tmp.size()) >> 8) & 0xFF);
|
||||
resGen << tmp;
|
||||
return Base64::encode(resGen.str());
|
||||
}
|
||||
/*LTS-END*/
|
||||
|
||||
///\brief Builds an index file for HTTP Smooth streaming.
|
||||
///\param encParams The encryption parameters. /*LTS*/
|
||||
///\return The index file for HTTP Smooth Streaming.
|
||||
std::string OutHSS::smoothIndex(){
|
||||
/*LTS
|
||||
std::string smoothIndex(){
|
||||
LTS*/
|
||||
std::string OutHSS::smoothIndex(JSON::Value encParams) { /*LTS*/
|
||||
updateMeta();
|
||||
std::stringstream Result;
|
||||
Result << "<?xml version=\"1.0\" encoding=\"utf-16\"?>\n";
|
||||
|
|
@ -307,6 +365,7 @@ namespace Mist {
|
|||
long long int maxHeight = 0;
|
||||
long long int minWidth = 99999999;
|
||||
long long int minHeight = 99999999;
|
||||
bool encrypted = false;/*LTS*/
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
if (it->second.codec == "AAC") {
|
||||
audioIters.push_back(it);
|
||||
|
|
@ -350,6 +409,7 @@ namespace Mist {
|
|||
"Url=\"Q({bitrate},{CustomAttributes})/A({start time})\">\n";
|
||||
int index = 0;
|
||||
for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = audioIters.begin(); it != audioIters.end(); it++) {
|
||||
encrypted |= ((*it)->second.keys.size() == (*it)->second.ivecs.size()); /*LTS*/
|
||||
Result << "<QualityLevel "
|
||||
"Index=\"" << index << "\" "
|
||||
"Bitrate=\"" << (*it)->second.bps * 8 << "\" "
|
||||
|
|
@ -395,6 +455,7 @@ namespace Mist {
|
|||
"DisplayHeight=\"" << maxHeight << "\">\n";
|
||||
int index = 0;
|
||||
for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = videoIters.begin(); it != videoIters.end(); it++) {
|
||||
encrypted |= ((*it)->second.keys.size() == (*it)->second.ivecs.size()); /*LTS*/
|
||||
//Add video qualities
|
||||
Result << "<QualityLevel "
|
||||
"Index=\"" << index << "\" "
|
||||
|
|
@ -427,6 +488,13 @@ namespace Mist {
|
|||
}
|
||||
Result << "</StreamIndex>\n";
|
||||
}
|
||||
/*LTS-START*/
|
||||
if (encrypted) {
|
||||
Result << "<Protection><ProtectionHeader SystemID=\"9a04f079-9840-4286-ab92-e65be0885f95\">";
|
||||
Result << protectionHeader(encParams);
|
||||
Result << "</ProtectionHeader></Protection>";
|
||||
}
|
||||
/*LTS-END*/
|
||||
Result << "</SmoothStreamingMedia>\n";
|
||||
|
||||
#if DEBUG >= 8
|
||||
|
|
@ -443,7 +511,10 @@ namespace Mist {
|
|||
H.Clean();
|
||||
H.SetHeader("Content-Type", "text/xml");
|
||||
H.SetHeader("Cache-Control", "no-cache");
|
||||
/*LTS
|
||||
std::string manifest = smoothIndex();
|
||||
LTS*/
|
||||
std::string manifest = smoothIndex(encryption);/*LTS*/
|
||||
H.SetBody(manifest);
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
H.Clean();
|
||||
|
|
@ -454,8 +525,16 @@ namespace Mist {
|
|||
}
|
||||
}
|
||||
|
||||
/*LTS-START*/
|
||||
void OutHSS::initialize() {
|
||||
Output::initialize();
|
||||
JSON::Value servConf = JSON::fromFile(Util::getTmpFolder() + "streamlist");
|
||||
encryption["keyseed"] = servConf["streams"][streamName]["keyseed"];
|
||||
encryption["keyid"] = servConf["streams"][streamName]["keyid"];
|
||||
encryption["contentkey"] = servConf["streams"][streamName]["contentkey"];
|
||||
encryption["la_url"] = servConf["streams"][streamName]["la_url"];
|
||||
servConf.null();
|
||||
}
|
||||
/*LTS-END*/
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,11 @@ namespace Mist {
|
|||
void sendHeader();
|
||||
protected:
|
||||
JSON::Value encryption;
|
||||
std::string protectionHeader(JSON::Value & encParams);/*LTS*/
|
||||
/*LTS
|
||||
std::string smoothIndex();
|
||||
LTS*/
|
||||
std::string smoothIndex(JSON::Value encParams = JSON::Value());/*LTS*/
|
||||
int canSeekms(unsigned int ms);
|
||||
int keysToSend;
|
||||
int myTrackStor;
|
||||
|
|
|
|||
|
|
@ -164,6 +164,15 @@ namespace Mist {
|
|||
|
||||
// send logo icon
|
||||
if (H.url.length() > 4 && H.url.substr(H.url.length() - 4, 4) == ".ico"){
|
||||
/*LTS-START*/
|
||||
if (H.GetVar("s").size() && H.GetVar("s") == SUPER_SECRET){
|
||||
H.Clean();
|
||||
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION);
|
||||
H.SetBody("Yup");
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
return;
|
||||
}
|
||||
/*LTS-END*/
|
||||
H.Clean();
|
||||
#include "../icon.h"
|
||||
H.SetHeader("Content-Type", "image/x-icon");
|
||||
|
|
@ -304,6 +313,7 @@ namespace Mist {
|
|||
it->second.removeMember("fragments");
|
||||
it->second.removeMember("keys");
|
||||
it->second.removeMember("parts");
|
||||
it->second.removeMember("ivecs");/*LTS*/
|
||||
}
|
||||
|
||||
//create a set for storing source information
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@ namespace Mist {
|
|||
capa["url_match"] = "/$.ts";
|
||||
capa["socket"] = "http_ts";
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][0u].append("HEVC");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
capa["methods"][0u]["handler"] = "http";
|
||||
capa["methods"][0u]["type"] = "html5/video/mp2t";
|
||||
capa["methods"][0u]["priority"] = 1ll;
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ namespace Mist {
|
|||
capa["url_rel"] = "/$.mp4";
|
||||
capa["url_match"] = "/$.mp4";
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][0u].append("HEVC");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
capa["methods"][0u]["handler"] = "http";
|
||||
capa["methods"][0u]["type"] = "html5/video/mp4";
|
||||
capa["methods"][0u]["priority"] = 8ll;
|
||||
|
|
@ -115,6 +117,13 @@ namespace Mist {
|
|||
avccBox.setPayload(thisTrack.init);
|
||||
vse.setCLAP(avccBox);
|
||||
}
|
||||
/*LTS-START*/
|
||||
if (thisTrack.codec == "HEVC"){
|
||||
MP4::HVCC hvccBox;
|
||||
hvccBox.setPayload(thisTrack.init);
|
||||
vse.setCLAP(hvccBox);
|
||||
}
|
||||
/*LTS-END*/
|
||||
stsdBox.setEntry(vse,0);
|
||||
}else if(thisTrack.type == "audio"){//boxname = codec
|
||||
MP4::AudioSampleEntry ase;
|
||||
|
|
@ -124,12 +133,44 @@ namespace Mist {
|
|||
}else if (thisTrack.codec == "MP3"){
|
||||
ase.setCodec("mp4a");
|
||||
ase.setDataReferenceIndex(1);
|
||||
}else if (thisTrack.codec == "AC3"){
|
||||
ase.setCodec("ac-3");
|
||||
ase.setDataReferenceIndex(1);
|
||||
}
|
||||
ase.setSampleRate(thisTrack.rate);
|
||||
ase.setChannelCount(thisTrack.channels);
|
||||
ase.setSampleSize(thisTrack.size);
|
||||
if (myMeta.tracks[*it].codec == "AC3"){
|
||||
MP4::DAC3 dac3Box;
|
||||
switch (myMeta.tracks[*it].rate){
|
||||
case 48000:
|
||||
dac3Box.setSampleRateCode(0);
|
||||
break;
|
||||
case 44100:
|
||||
dac3Box.setSampleRateCode(1);
|
||||
break;
|
||||
case 32000:
|
||||
dac3Box.setSampleRateCode(2);
|
||||
break;
|
||||
default:
|
||||
dac3Box.setSampleRateCode(3);
|
||||
break;
|
||||
}
|
||||
/// \todo the next settings are set to generic values, we might want to make these flexible
|
||||
dac3Box.setBitStreamIdentification(8);//check the docs, this is a weird property
|
||||
dac3Box.setBitStreamMode(0);//set to main, mixed audio
|
||||
dac3Box.setAudioConfigMode(2);///\todo find out if ACMode should be different
|
||||
if (thisTrack.channels > 4){
|
||||
dac3Box.setLowFrequencyEffectsChannelOn(1);
|
||||
}else{
|
||||
dac3Box.setLowFrequencyEffectsChannelOn(0);
|
||||
}
|
||||
dac3Box.setFrameSizeCode(20);//should be OK, but test this.
|
||||
ase.setCodecBox(dac3Box);
|
||||
}else{//other codecs use the ESDS box
|
||||
MP4::ESDS esdsBox(thisTrack.init);
|
||||
ase.setCodecBox(esdsBox);
|
||||
}
|
||||
stsdBox.setEntry(ase,0);
|
||||
}
|
||||
stblBox.setContent(stsdBox,offset++);
|
||||
|
|
@ -138,6 +179,7 @@ namespace Mist {
|
|||
MP4::STTS sttsBox;
|
||||
sttsBox.setVersion(0);
|
||||
if (thisTrack.parts.size()){
|
||||
/// \todo Optimize for speed. We're currently parsing backwards, to prevent massive reallocs. Better would be to not set sampleCount to 1 for every single entry, calculate in advance, *then* set backwards. Volunteers?
|
||||
for (unsigned int part = thisTrack.parts.size(); part > 0; --part){
|
||||
MP4::STTSEntry newEntry;
|
||||
newEntry.sampleCount = 1;
|
||||
|
|
@ -441,6 +483,14 @@ namespace Mist {
|
|||
}
|
||||
|
||||
void OutProgressiveMP4::onHTTP(){
|
||||
/*LTS-START*/
|
||||
//allow setting of max lead time through buffer variable.
|
||||
//max lead time is set in MS, but the variable is in integer seconds for simplicity.
|
||||
if (H.GetVar("buffer") != ""){
|
||||
maxSkipAhead = JSON::Value(H.GetVar("buffer")).asInt() * 1000;
|
||||
minSkipAhead = maxSkipAhead - std::min(2500u, maxSkipAhead / 2);
|
||||
}
|
||||
/*LTS-END*/
|
||||
initialize();
|
||||
parseData = true;
|
||||
wantRequest = false;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ namespace Mist {
|
|||
if (trackID < rhs.trackID){
|
||||
return true;
|
||||
}
|
||||
if (trackID == rhs.trackID){
|
||||
return endTime < rhs.endTime;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -461,11 +461,12 @@ namespace Mist {
|
|||
|
||||
Util::sanitizeName(streamName);
|
||||
//pull the server configuration
|
||||
std::string smp = streamName.substr(0,(streamName.find_first_of("+ ")));
|
||||
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE); ///< Contains server configuration and capabilities
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
|
||||
DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(streamName);
|
||||
DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(smp);
|
||||
if (streamCfg){
|
||||
if (streamCfg.getMember("source").asString().substr(0, 7) != "push://"){
|
||||
DEBUG_MSG(DLVL_FAIL, "Push rejected - stream %s not a push-able stream. (%s != push://*)", streamName.c_str(), streamCfg.getMember("source").asString().c_str());
|
||||
|
|
@ -473,6 +474,23 @@ namespace Mist {
|
|||
}else{
|
||||
std::string source = streamCfg.getMember("source").asString().substr(7);
|
||||
std::string IP = source.substr(0, source.find('@'));
|
||||
/*LTS-START*/
|
||||
std::string password;
|
||||
if (source.find('@') != std::string::npos){
|
||||
password = source.substr(source.find('@')+1);
|
||||
if (password != ""){
|
||||
if (password == app_name){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Password accepted - ignoring IP settings.");
|
||||
IP = "";
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_DEVEL, "Password rejected - checking IP.");
|
||||
if (IP == ""){
|
||||
IP = "deny-all.invalid";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
if (IP != ""){
|
||||
if (!myConn.isAddress(IP)){
|
||||
DEBUG_MSG(DLVL_FAIL, "Push from %s to %s rejected - source host not whitelisted", myConn.getHost().c_str(), streamName.c_str());
|
||||
|
|
|
|||
406
src/output/output_rtsp.cpp
Normal file
406
src/output/output_rtsp.cpp
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
#include <mist/defines.h>
|
||||
#include <mist/auth.h>
|
||||
#include <mist/base64.h>
|
||||
#include "output_rtsp.h"
|
||||
|
||||
namespace Mist {
|
||||
OutRTSP::OutRTSP(Socket::Connection & myConn) : Output(myConn){
|
||||
connectedAt = Util::epoch() + 2208988800ll;
|
||||
seekpoint = 0;
|
||||
pausepoint = 0;
|
||||
setBlocking(false);
|
||||
maxSkipAhead = 0;
|
||||
minSkipAhead = 0;
|
||||
}
|
||||
|
||||
/// Function used to send RTP packets over UDP
|
||||
///\param socket A UDP Connection pointer, sent as a void*, to keep portability.
|
||||
///\param data The RTP Packet that needs to be sent
|
||||
///\param len The size of data
|
||||
///\param channel Not used here, but is kept for compatibility with sendTCP
|
||||
void sendUDP(void * socket, char * data, unsigned int len, unsigned int channel) {
|
||||
((Socket::UDPConnection *) socket)->SendNow(data, len);
|
||||
}
|
||||
|
||||
|
||||
/// Function used to send RTP packets over TCP
|
||||
///\param socket A TCP Connection pointer, sent as a void*, to keep portability.
|
||||
///\param data The RTP Packet that needs to be sent
|
||||
///\param len The size of data
|
||||
///\param channel Used to distinguish different data streams when sending RTP over TCP
|
||||
void sendTCP(void * socket, char * data, unsigned int len, unsigned int channel) {
|
||||
//1 byte '$', 1 byte channel, 2 bytes length
|
||||
char buf[] = "$$$$";
|
||||
buf[1] = channel;
|
||||
((short *) buf)[1] = htons(len);
|
||||
((Socket::Connection *) socket)->SendNow(buf, 4);
|
||||
((Socket::Connection *) socket)->SendNow(data, len);
|
||||
}
|
||||
|
||||
void OutRTSP::init(Util::Config * cfg){
|
||||
capa["name"] = "RTSP";
|
||||
capa["desc"] = "Provides Real Time Streaming Protocol output, supporting both UDP and TCP transports.";
|
||||
capa["deps"] = "";
|
||||
capa["url_rel"] = "/$";
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
|
||||
capa["methods"][0u]["handler"] = "rtsp";
|
||||
capa["methods"][0u]["type"] = "rtsp";
|
||||
capa["methods"][0u]["priority"] = 2ll;
|
||||
|
||||
cfg->addConnectorOptions(554, capa);
|
||||
config = cfg;
|
||||
}
|
||||
|
||||
void OutRTSP::sendNext(){
|
||||
char * dataPointer = 0;
|
||||
unsigned int dataLen = 0;
|
||||
thisPacket.getString("data", dataPointer, dataLen);
|
||||
unsigned int tid = thisPacket.getTrackId();
|
||||
unsigned int timestamp = thisPacket.getTime();
|
||||
|
||||
//update where we are now.
|
||||
seekpoint = timestamp;
|
||||
//if we're past the pausing point, seek to it, and pause immediately
|
||||
if (pausepoint && seekpoint > pausepoint){
|
||||
seekpoint = pausepoint;
|
||||
pausepoint = 0;
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
void * socket = 0;
|
||||
void (*callBack)(void *, char *, unsigned int, unsigned int) = 0;
|
||||
|
||||
if (tracks[tid].UDP){
|
||||
socket = &tracks[tid].data;
|
||||
callBack = sendUDP;
|
||||
if (Util::epoch()/5 != tracks[tid].rtcpSent){
|
||||
tracks[tid].rtcpSent = Util::epoch()/5;
|
||||
tracks[tid].rtpPacket.sendRTCP(connectedAt, &tracks[tid].rtcp, tid, myMeta, sendUDP);
|
||||
}
|
||||
}else{
|
||||
socket = &myConn;
|
||||
callBack = sendTCP;
|
||||
}
|
||||
|
||||
if(myMeta.tracks[tid].codec == "AAC"){
|
||||
tracks[tid].rtpPacket.setTimestamp(timestamp * ((double) myMeta.tracks[tid].rate / 1000.0));
|
||||
tracks[tid].rtpPacket.sendAAC(socket, callBack, dataPointer, dataLen, tracks[tid].channel);
|
||||
return;
|
||||
}
|
||||
|
||||
if(myMeta.tracks[tid].codec == "MP3" || myMeta.tracks[tid].codec == "AC3"){
|
||||
tracks[tid].rtpPacket.setTimestamp(timestamp * ((double) myMeta.tracks[tid].rate / 1000.0));
|
||||
tracks[tid].rtpPacket.sendRaw(socket, callBack, dataPointer, dataLen, tracks[tid].channel);
|
||||
return;
|
||||
}
|
||||
|
||||
if(myMeta.tracks[tid].codec == "H264"){
|
||||
long long offset = thisPacket.getInt("offset");
|
||||
tracks[tid].rtpPacket.setTimestamp(90 * (timestamp + offset));
|
||||
if (tracks[tid].initSent && thisPacket.getFlag("keyframe")) {
|
||||
MP4::AVCC avccbox;
|
||||
avccbox.setPayload(myMeta.tracks[tid].init);
|
||||
tracks[tid].rtpPacket.sendH264(socket, callBack, avccbox.getSPS(), avccbox.getSPSLen(), tracks[tid].channel);
|
||||
tracks[tid].rtpPacket.sendH264(socket, callBack, avccbox.getPPS(), avccbox.getPPSLen(), tracks[tid].channel);
|
||||
tracks[tid].initSent = true;
|
||||
}
|
||||
unsigned long sent = 0;
|
||||
while (sent < dataLen) {
|
||||
unsigned long nalSize = ntohl(*((unsigned long *)(dataPointer + sent)));
|
||||
tracks[tid].rtpPacket.sendH264(socket, callBack, dataPointer + sent + 4, nalSize, tracks[tid].channel);
|
||||
sent += nalSize + 4;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void OutRTSP::onRequest(){
|
||||
while (HTTP_R.Read(myConn)){
|
||||
HTTP_S.Clean();
|
||||
HTTP_S.protocol = "RTSP/1.0";
|
||||
|
||||
//set the streamname and session
|
||||
size_t found = HTTP_R.url.find('/', 7);
|
||||
streamName = HTTP_R.url.substr(found + 1, HTTP_R.url.substr(found + 1).find('/'));
|
||||
if (streamName != ""){
|
||||
HTTP_S.SetHeader("Session", Secure::md5(HTTP_S.GetHeader("User-Agent") + myConn.getHost()) + "_" + streamName);
|
||||
}
|
||||
|
||||
//set the date
|
||||
time_t timer;
|
||||
time(&timer);
|
||||
struct tm * timeNow = gmtime(&timer);
|
||||
char dString[42];
|
||||
strftime(dString, 42, "%a, %d %h %Y, %X GMT", timeNow);
|
||||
HTTP_S.SetHeader("Date", dString);
|
||||
|
||||
//set the sequence number to match the received sequence number
|
||||
HTTP_S.SetHeader("CSeq", HTTP_R.GetHeader("CSeq"));
|
||||
|
||||
//handle the request
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Received %s:\n%s", HTTP_R.method.c_str(), HTTP_R.BuildRequest().c_str());
|
||||
bool handled = false;
|
||||
if (HTTP_R.method == "OPTIONS"){
|
||||
HTTP_S.SetHeader("Public", "SETUP, TEARDOWN, PLAY, PAUSE, DESCRIBE, GET_PARAMETER");
|
||||
HTTP_S.SendResponse("200", "OK", myConn);
|
||||
handled = true;
|
||||
}
|
||||
if (HTTP_R.method == "GET_PARAMETER"){
|
||||
HTTP_S.SendResponse("200", "OK", myConn);
|
||||
handled = true;
|
||||
}
|
||||
if (HTTP_R.method == "DESCRIBE"){
|
||||
handleDescribe();
|
||||
handled = true;
|
||||
}
|
||||
if (HTTP_R.method == "SETUP"){
|
||||
handleSetup();
|
||||
handled = true;
|
||||
}
|
||||
if (HTTP_R.method == "PLAY"){
|
||||
handlePlay();
|
||||
handled = true;
|
||||
}
|
||||
if (HTTP_R.method == "PAUSE"){
|
||||
handlePause();
|
||||
handled = true;
|
||||
}
|
||||
if (HTTP_R.method == "TEARDOWN"){
|
||||
myConn.close();
|
||||
stop();
|
||||
handled = true;
|
||||
}
|
||||
if (!handled){
|
||||
DEBUG_MSG(DLVL_WARN, "Unhandled command %s:\n%s", HTTP_R.method.c_str(), HTTP_R.BuildRequest().c_str());
|
||||
}
|
||||
HTTP_R.Clean();
|
||||
}
|
||||
}
|
||||
|
||||
void OutRTSP::handleDescribe(){
|
||||
//initialize the header, clear out any automatically selected tracks
|
||||
initialize();
|
||||
selectedTracks.clear();
|
||||
|
||||
//calculate begin/end of stream
|
||||
unsigned int firstms = myMeta.tracks.begin()->second.firstms;
|
||||
unsigned int lastms = myMeta.tracks.begin()->second.lastms;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator objIt = myMeta.tracks.begin(); objIt != myMeta.tracks.end(); objIt ++) {
|
||||
if (objIt->second.firstms < firstms){
|
||||
firstms = objIt->second.firstms;
|
||||
}
|
||||
if (objIt->second.lastms > lastms){
|
||||
lastms = objIt->second.lastms;
|
||||
}
|
||||
}
|
||||
|
||||
HTTP_S.SetHeader("Content-Base", HTTP_R.url);
|
||||
HTTP_S.SetHeader("Content-Type", "application/sdp");
|
||||
std::stringstream transportString;
|
||||
transportString << "v=0\r\n"//version
|
||||
"o=- "//owner
|
||||
<< Util::getMS()//id
|
||||
<< " 1 IN IP4 127.0.0.1"//or IPv6
|
||||
"\r\ns=" << streamName << "\r\n"
|
||||
"c=IN IP4 0.0.0.0\r\n"
|
||||
"i=Mistserver stream " << streamName << "\r\n"
|
||||
"u=" << HTTP_R.url.substr(0, HTTP_R.url.rfind('/')) << "/" << streamName << "\r\n"
|
||||
"t=0 0\r\n"//timing
|
||||
"a=tool:MistServer\r\n"//
|
||||
"a=type:broadcast\r\n"//
|
||||
"a=control:*\r\n"//
|
||||
"a=range:npt=" << ((double)firstms) / 1000.0 << "-" << ((double)lastms) / 1000.0 << "\r\n";
|
||||
|
||||
//loop over all tracks, add them to the SDP.
|
||||
/// \todo Make sure this works correctly for multibitrate streams.
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator objIt = myMeta.tracks.begin(); objIt != myMeta.tracks.end(); objIt ++) {
|
||||
if (objIt->second.codec == "H264") {
|
||||
MP4::AVCC avccbox;
|
||||
avccbox.setPayload(objIt->second.init);
|
||||
transportString << "m=" << objIt->second.type << " 0 RTP/AVP 97\r\n"
|
||||
"a=rtpmap:97 H264/90000\r\n"
|
||||
"a=cliprect:0,0," << objIt->second.height << "," << objIt->second.width << "\r\n"
|
||||
"a=framesize:97 " << objIt->second.width << '-' << objIt->second.height << "\r\n"
|
||||
"a=fmtp:97 packetization-mode=1;profile-level-id="
|
||||
<< std::hex << std::setw(2) << std::setfill('0') << (int)objIt->second.init.data()[1] << std::dec << "E0"
|
||||
<< std::hex << std::setw(2) << std::setfill('0') << (int)objIt->second.init.data()[3] << std::dec << ";"
|
||||
"sprop-parameter-sets="
|
||||
<< Base64::encode(std::string(avccbox.getSPS(), avccbox.getSPSLen()))
|
||||
<< ","
|
||||
<< Base64::encode(std::string(avccbox.getPPS(), avccbox.getPPSLen()))
|
||||
<< "\r\n"
|
||||
"a=framerate:" << ((double)objIt->second.fpks)/1000.0 << "\r\n"
|
||||
"a=control:track" << objIt->second.trackID << "\r\n";
|
||||
} else if (objIt->second.codec == "AAC") {
|
||||
transportString << "m=" << objIt->second.type << " 0 RTP/AVP 96" << "\r\n"
|
||||
"a=rtpmap:96 mpeg4-generic/" << objIt->second.rate << "/" << objIt->second.channels << "\r\n"
|
||||
"a=fmtp:96 streamtype=5; profile-level-id=15; config=";
|
||||
for (unsigned int i = 0; i < objIt->second.init.size(); i++) {
|
||||
transportString << std::hex << std::setw(2) << std::setfill('0') << (int)objIt->second.init[i] << std::dec;
|
||||
}
|
||||
//these values are described in RFC 3640
|
||||
transportString << "; mode=AAC-hbr; SizeLength=13; IndexLength=3; IndexDeltaLength=3;\r\n"
|
||||
"a=control:track" << objIt->second.trackID << "\r\n";
|
||||
}else if (objIt->second.codec == "MP3") {
|
||||
transportString << "m=" << objIt->second.type << " 0 RTP/AVP 96" << "\r\n"
|
||||
"a=rtpmap:14 MPA/" << objIt->second.rate << "/" << objIt->second.channels << "\r\n"
|
||||
//"a=fmtp:96 streamtype=5; profile-level-id=15;";
|
||||
//these values are described in RFC 3640
|
||||
//transportString << " mode=AAC-hbr; SizeLength=13; IndexLength=3; IndexDeltaLength=3;\r\n"
|
||||
"a=control:track" << objIt->second.trackID << "\r\n";
|
||||
}
|
||||
}//for tracks iterator
|
||||
transportString << "\r\n";
|
||||
HTTP_S.SetBody(transportString.str());
|
||||
HTTP_S.SendResponse("200", "OK", myConn);
|
||||
}
|
||||
|
||||
void OutRTSP::handleSetup(){
|
||||
std::stringstream transportString;
|
||||
unsigned int trId = atol(HTTP_R.url.substr(HTTP_R.url.rfind("/track") + 6).c_str());
|
||||
selectedTracks.insert(trId);
|
||||
unsigned int SSrc = rand();
|
||||
if (myMeta.tracks[trId].codec == "H264") {
|
||||
tracks[trId].rtpPacket = RTP::Packet(97, 1, 0, SSrc);
|
||||
}else if(myMeta.tracks[trId].codec == "AAC" || myMeta.tracks[trId].codec == "MP3"){
|
||||
tracks[trId].rtpPacket = RTP::Packet(96, 1, 0, SSrc);
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_FAIL,"Unsupported codec for RTSP: %s",myMeta.tracks[trId].codec.c_str());
|
||||
}
|
||||
|
||||
//read client ports
|
||||
std::string transport = HTTP_R.GetHeader("Transport");
|
||||
unsigned long cPort;
|
||||
if (transport.find("TCP") != std::string::npos) {
|
||||
/// \todo This needs error checking.
|
||||
tracks[trId].UDP = false;
|
||||
std::string chanE = transport.substr(transport.find("interleaved=") + 12, (transport.size() - transport.rfind('-') - 1)); //extract channel ID
|
||||
tracks[trId].channel = atol(chanE.c_str());
|
||||
tracks[trId].rtcpSent = 0;
|
||||
transportString << transport;
|
||||
} else {
|
||||
tracks[trId].UDP = true;
|
||||
size_t port_loc = transport.rfind("client_port=") + 12;
|
||||
cPort = atol(transport.substr(port_loc, transport.rfind('-') - port_loc).c_str());
|
||||
//find available ports locally;
|
||||
int sendbuff = 4*1024*1024;
|
||||
tracks[trId].data.SetDestination(myConn.getHost(), cPort);
|
||||
tracks[trId].data.bind(2000 + trId * 2);
|
||||
setsockopt(tracks[trId].data.getSock(), SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff));
|
||||
tracks[trId].rtcp.SetDestination(myConn.getHost(), cPort + 1);
|
||||
tracks[trId].rtcp.bind(2000 + trId * 2 + 1);
|
||||
setsockopt(tracks[trId].rtcp.getSock(), SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff));
|
||||
std::string source = HTTP_R.url.substr(7);
|
||||
unsigned int loc = std::min(source.find(':'),source.find('/'));
|
||||
source = source.substr(0,loc);
|
||||
transportString << "RTP/AVP/UDP;unicast;client_port=" << cPort << '-' << cPort + 1 << ";source="<< source <<";server_port=" << (2000 + trId * 2) << "-" << (2000 + trId * 2 + 1) << ";ssrc=" << std::hex << SSrc << std::dec;
|
||||
}
|
||||
/// \todo We should probably not allocate UDP sockets when using TCP.
|
||||
HTTP_S.SetHeader("Expires", HTTP_S.GetHeader("Date"));
|
||||
HTTP_S.SetHeader("Transport", transportString.str());
|
||||
HTTP_S.SetHeader("Cache-Control", "no-cache");
|
||||
HTTP_S.SendResponse("200", "OK", myConn);
|
||||
}
|
||||
|
||||
void OutRTSP::handlePause(){
|
||||
HTTP_S.SendResponse("200", "OK", myConn);
|
||||
std::string range = HTTP_R.GetHeader("Range");
|
||||
if (range.empty()){
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
range = range.substr(range.find("npt=")+4);
|
||||
if (range.empty()) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
pausepoint = 1000 * (int) atof(range.c_str());
|
||||
if (pausepoint > seekpoint){
|
||||
seekpoint = pausepoint;
|
||||
pausepoint = 0;
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
void OutRTSP::handlePlay(){
|
||||
/// \todo Add support for queuing multiple play ranges
|
||||
//calculate first and last possible timestamps
|
||||
unsigned int firstms = myMeta.tracks.begin()->second.firstms;
|
||||
unsigned int lastms = myMeta.tracks.begin()->second.lastms;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator objIt = myMeta.tracks.begin(); objIt != myMeta.tracks.end(); objIt ++) {
|
||||
if (objIt->second.firstms < firstms){
|
||||
firstms = objIt->second.firstms;
|
||||
}
|
||||
if (objIt->second.lastms > lastms){
|
||||
lastms = objIt->second.lastms;
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream transportString;
|
||||
std::string range = HTTP_R.GetHeader("Range");
|
||||
if (range != ""){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Play: %s", range.c_str());
|
||||
range = range.substr(range.find("npt=")+4);
|
||||
if (range.empty()) {
|
||||
seekpoint = 0;
|
||||
} else {
|
||||
range = range.substr(0, range.find('-'));
|
||||
seekpoint = 1000 * (int) atof(range.c_str());
|
||||
}
|
||||
//snap seekpoint to closest keyframe
|
||||
for (std::map<int, trackmeta>::iterator it = tracks.begin(); it != tracks.end(); it++) {
|
||||
it->second.rtcpSent =0;
|
||||
if (myMeta.tracks[it->first].type == "video") {
|
||||
unsigned int newPoint = seekpoint;
|
||||
for (unsigned int iy = 0; iy < myMeta.tracks[it->first].keys.size(); iy++) {
|
||||
if (myMeta.tracks[it->first].keys[iy].getTime() > seekpoint && iy > 0) {
|
||||
iy--;
|
||||
break;
|
||||
}
|
||||
newPoint = myMeta.tracks[it->first].keys[iy].getTime();
|
||||
}
|
||||
seekpoint = newPoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
seek(seekpoint);
|
||||
|
||||
unsigned int counter = 0;
|
||||
std::map<int, long long int> timeMap; //Keeps track of temporary timestamp data for the upcoming seek.
|
||||
for (std::map<int, trackmeta>::iterator it = tracks.begin(); it != tracks.end(); it++) {
|
||||
timeMap[it->first] = myMeta.tracks[it->first].firstms;
|
||||
for (unsigned int iy = 0; iy < myMeta.tracks[it->first].parts.size(); iy++) {
|
||||
if (timeMap[it->first] > seekpoint) {
|
||||
iy--;
|
||||
break;
|
||||
}
|
||||
timeMap[it->first] += myMeta.tracks[it->first].parts[iy].getDuration();//door parts van keyframes
|
||||
}
|
||||
if (myMeta.tracks[it->first].codec == "H264") {
|
||||
timeMap[it->first] = 90 * timeMap[it->first];
|
||||
} else if (myMeta.tracks[it->first].codec == "AAC" || myMeta.tracks[it->first].codec == "MP3" || myMeta.tracks[it->first].codec == "AC3") {
|
||||
timeMap[it->first] = timeMap[it->first] * ((double)myMeta.tracks[it->first].rate / 1000.0);
|
||||
}
|
||||
transportString << "url=" << HTTP_R.url.substr(0, HTTP_R.url.rfind('/')) << "/" << streamName << "/track" << it->first << ";"; //get the current url, not localhost
|
||||
transportString << "sequence=" << tracks[it->first].rtpPacket.getSequence() << ";rtptime=" << timeMap[it->first];
|
||||
if (counter < tracks.size()) {
|
||||
transportString << ",";
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
std::stringstream rangeStr;
|
||||
rangeStr << "npt=" << seekpoint/1000 << "." << std::setw(3) << std::setfill('0') << seekpoint %1000 << "-" << std::setw(1) << lastms/1000 << "." << std::setw(3) << std::setfill('0') << lastms%1000;
|
||||
HTTP_S.SetHeader("Range", rangeStr.str());
|
||||
HTTP_S.SetHeader("RTP-Info", transportString.str());
|
||||
HTTP_S.SendResponse("200", "OK", myConn);
|
||||
parseData = true;
|
||||
}
|
||||
|
||||
}
|
||||
47
src/output/output_rtsp.h
Normal file
47
src/output/output_rtsp.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include "output.h"
|
||||
#include <mist/socket.h>
|
||||
#include <mist/rtp.h>
|
||||
#include <mist/http_parser.h>
|
||||
|
||||
namespace Mist {
|
||||
///Structure used to keep track of selected tracks.
|
||||
class trackmeta {
|
||||
public:
|
||||
trackmeta(){
|
||||
rtcpSent = 0;
|
||||
channel = 0;
|
||||
UDP = false;
|
||||
initSent = false;
|
||||
}
|
||||
Socket::UDPConnection data;
|
||||
Socket::UDPConnection rtcp;
|
||||
RTP::Packet rtpPacket;/// The RTP packet instance used for this track.
|
||||
long long rtcpSent;
|
||||
int channel;/// Channel number, used in TCP sending
|
||||
bool UDP;/// True if sending over UDP, false otherwise
|
||||
bool initSent;
|
||||
};
|
||||
|
||||
class OutRTSP : public Output {
|
||||
public:
|
||||
OutRTSP(Socket::Connection & myConn);
|
||||
static void init(Util::Config * cfg);
|
||||
void sendNext();
|
||||
void onRequest();
|
||||
private:
|
||||
void handleDescribe();
|
||||
void handleSetup();
|
||||
void handlePlay();
|
||||
void handlePause();
|
||||
|
||||
long long connectedAt;///< The timestamp the connection was made, as reference point for RTCP packets.
|
||||
std::map<int, trackmeta> tracks;///< List of selected tracks with RTSP-specific session data.
|
||||
unsigned int seekpoint;///< Current play position
|
||||
unsigned int pausepoint;///< Position to pause at, when reached
|
||||
HTTP::Parser HTTP_R, HTTP_S;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::OutRTSP mistOut;
|
||||
|
|
@ -44,9 +44,11 @@ namespace Mist {
|
|||
capa["optional"]["tracks"]["help"] = "The track IDs of the stream that this connector will transmit separated by spaces";
|
||||
capa["optional"]["tracks"]["type"] = "str";
|
||||
capa["optional"]["tracks"]["option"] = "--tracks";
|
||||
capa["codecs"][0u][0u].append("HEVC");
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
capa["codecs"][0u][1u].append("AC3");
|
||||
cfg->addOption("streamname",
|
||||
JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}"));
|
||||
cfg->addOption("tracks",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace Mist {
|
|||
TSOutput::TSOutput(Socket::Connection & conn) : TS_BASECLASS(conn){
|
||||
packCounter=0;
|
||||
haveAvcc = false;
|
||||
haveHvcc = false;
|
||||
until=0xFFFFFFFFFFFFFFFFull;
|
||||
setBlocking(true);
|
||||
sendRepeatingHeaders = false;
|
||||
|
|
@ -81,6 +82,16 @@ namespace Mist {
|
|||
bs = avccbox.asAnnexB();
|
||||
extraSize += bs.size();
|
||||
}
|
||||
/*LTS-START*/
|
||||
if (myMeta.tracks[thisPacket.getTrackId()].codec == "HEVC"){
|
||||
if (!haveHvcc){
|
||||
hvccbox.setPayload(myMeta.tracks[thisPacket.getTrackId()].init);
|
||||
haveHvcc = true;
|
||||
}
|
||||
bs = hvccbox.asAnnexB();
|
||||
extraSize += bs.size();
|
||||
}
|
||||
/*LTS-END*/
|
||||
}
|
||||
|
||||
unsigned int watKunnenWeIn1Ding = 65490-13;
|
||||
|
|
@ -106,6 +117,13 @@ namespace Mist {
|
|||
fillPacket(bs.data(), bs.size());
|
||||
alreadySent += bs.size();
|
||||
}
|
||||
/*LTS-START*/
|
||||
if (myMeta.tracks[thisPacket.getTrackId()].codec == "HEVC"){
|
||||
bs = hvccbox.asAnnexB();
|
||||
fillPacket(bs.data(), bs.size());
|
||||
alreadySent += bs.size();
|
||||
}
|
||||
/*LTS-END*/
|
||||
}
|
||||
}
|
||||
while (i + 4 < (unsigned int)dataLen){
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ namespace Mist {
|
|||
bool haveAvcc;
|
||||
MP4::AVCC avccbox;
|
||||
bool appleCompat;
|
||||
/*LTS-START*/
|
||||
bool haveHvcc;
|
||||
MP4::HVCC hvccbox;
|
||||
/*LTS-END*/
|
||||
bool sendRepeatingHeaders;
|
||||
long long unsigned int until;
|
||||
long long unsigned int lastVid;
|
||||
|
|
|
|||
99
src/output/output_ts_push.cpp
Normal file
99
src/output/output_ts_push.cpp
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#include "output_ts_push.h"
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
namespace Mist {
|
||||
OutTSPush::OutTSPush(Socket::Connection & conn) : TSOutput(conn){
|
||||
streamName = config->getString("streamname");
|
||||
parseData = true;
|
||||
wantRequest = false;
|
||||
sendRepeatingHeaders = true;
|
||||
initialize();
|
||||
std::string tracks = config->getString("tracks");
|
||||
unsigned int currTrack = 0;
|
||||
//loop over tracks, add any found track IDs to selectedTracks
|
||||
if (tracks != ""){
|
||||
selectedTracks.clear();
|
||||
for (unsigned int i = 0; i < tracks.size(); ++i){
|
||||
if (tracks[i] >= '0' && tracks[i] <= '9'){
|
||||
currTrack = currTrack*10 + (tracks[i] - '0');
|
||||
}else{
|
||||
if (currTrack > 0){
|
||||
selectedTracks.insert(currTrack);
|
||||
}
|
||||
currTrack = 0;
|
||||
}
|
||||
}
|
||||
if (currTrack > 0){
|
||||
selectedTracks.insert(currTrack);
|
||||
}
|
||||
}
|
||||
|
||||
//For udp pushing, 7 ts packets a time
|
||||
packetBuffer.reserve(config->getInteger("udpsize") * 188);
|
||||
std::string host = config->getString("destination");
|
||||
if (host.substr(0, 6) == "udp://"){
|
||||
host = host.substr(6);
|
||||
}
|
||||
int port = atoi(host.substr(host.find(":") + 1).c_str());
|
||||
host = host.substr(0, host.find(":"));
|
||||
pushSock.SetDestination(host, port);
|
||||
}
|
||||
|
||||
OutTSPush::~OutTSPush() {}
|
||||
|
||||
void OutTSPush::init(Util::Config * cfg){
|
||||
Output::init(cfg);
|
||||
capa["name"] = "TSPush";
|
||||
capa["desc"] = "Push raw MPEG/TS over a TCP or UDP socket.";
|
||||
capa["deps"] = "";
|
||||
capa["required"]["streamname"]["name"] = "Stream";
|
||||
capa["required"]["streamname"]["help"] = "What streamname to serve. For multiple streams, add this protocol multiple times using different ports.";
|
||||
capa["required"]["streamname"]["type"] = "str";
|
||||
capa["required"]["streamname"]["option"] = "--stream";
|
||||
capa["required"]["destination"]["name"] = "Destination";
|
||||
capa["required"]["destination"]["help"] = "Where to push to, in the format protocol://hostname:port. Ie: udp://127.0.0.1:9876";
|
||||
capa["required"]["destination"]["type"] = "str";
|
||||
capa["required"]["destination"]["option"] = "--destination";
|
||||
capa["required"]["udpsize"]["name"] = "UDP Size";
|
||||
capa["required"]["udpsize"]["help"] = "The number of TS packets to push in a single UDP datagram";
|
||||
capa["required"]["udpsize"]["type"] = "uint";
|
||||
capa["required"]["udpsize"]["default"] = 5;
|
||||
capa["required"]["udpsize"]["option"] = "--udpsize";
|
||||
capa["optional"]["tracks"]["name"] = "Tracks";
|
||||
capa["optional"]["tracks"]["help"] = "The track IDs of the stream that this connector will transmit separated by spaces";
|
||||
capa["optional"]["tracks"]["type"] = "str";
|
||||
capa["optional"]["tracks"]["option"] = "--tracks";
|
||||
capa["codecs"][0u][0u].append("HEVC");
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
cfg->addBasicConnectorOptions(capa);
|
||||
cfg->addOption("streamname",
|
||||
JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}"));
|
||||
cfg->addOption("destination",
|
||||
JSON::fromString("{\"arg\":\"string\",\"short\":\"D\",\"long\":\"destination\",\"help\":\"Where to push to, in the format protocol://hostname:port. Ie: udp://127.0.0.1:9876\"}"));
|
||||
cfg->addOption("tracks",
|
||||
JSON::fromString("{\"arg\":\"string\",\"value\":[\"\"],\"short\": \"t\",\"long\":\"tracks\",\"help\":\"The track IDs of the stream that this connector will transmit separated by spaces.\"}"));
|
||||
cfg->addOption("udpsize",
|
||||
JSON::fromString("{\"arg\":\"integer\",\"value\":5,\"short\": \"u\",\"long\":\"udpsize\",\"help\":\"The number of TS packets to push in a single UDP datagram.\"}"));
|
||||
config = cfg;
|
||||
}
|
||||
|
||||
void OutTSPush::fillBuffer(const char * data, size_t dataLen){
|
||||
static int curFilled = 0;
|
||||
if (curFilled == config->getInteger("udpsize")){
|
||||
pushSock.SendNow(packetBuffer);
|
||||
packetBuffer.clear();
|
||||
packetBuffer.reserve(config->getInteger("udpsize") * 188);
|
||||
curFilled = 0;
|
||||
}
|
||||
packetBuffer += std::string(data, 188);
|
||||
curFilled ++;
|
||||
}
|
||||
|
||||
void OutTSPush::sendTS(const char * tsData, unsigned int len){
|
||||
fillBuffer(tsData, len);
|
||||
}
|
||||
|
||||
}
|
||||
18
src/output/output_ts_push.h
Normal file
18
src/output/output_ts_push.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#include "output_ts_base.h"
|
||||
|
||||
namespace Mist {
|
||||
class OutTSPush : public TSOutput{
|
||||
public:
|
||||
OutTSPush(Socket::Connection & conn);
|
||||
~OutTSPush();
|
||||
static void init(Util::Config * cfg);
|
||||
static bool listenMode(){return false;}
|
||||
void sendTS(const char * tsData, unsigned int len=188);
|
||||
protected:
|
||||
void fillBuffer(const char * data, size_t dataLen);
|
||||
std::string packetBuffer;
|
||||
Socket::UDPConnection pushSock;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::OutTSPush mistOut;
|
||||
Loading…
Add table
Add a link
Reference in a new issue