Recording, HLS Push, UDP (Multicast) Input, Threaded TS
This commit is contained in:
parent
1c3e143709
commit
c25a533729
29 changed files with 1809 additions and 815 deletions
|
@ -251,7 +251,7 @@ makeAnalyser(DTSC dtsc)
|
||||||
makeAnalyser(AMF amf)
|
makeAnalyser(AMF amf)
|
||||||
makeAnalyser(MP4 mp4)
|
makeAnalyser(MP4 mp4)
|
||||||
makeAnalyser(OGG ogg)
|
makeAnalyser(OGG ogg)
|
||||||
makeAnalyser(RTP rtp) #LTS
|
#makeAnalyser(RTP rtp) #LTS
|
||||||
makeAnalyser(RTSP rtsp_rtp) #LTS
|
makeAnalyser(RTSP rtsp_rtp) #LTS
|
||||||
makeAnalyser(TS ts) #LTS
|
makeAnalyser(TS ts) #LTS
|
||||||
makeAnalyser(TSStream tsstream) #LTS
|
makeAnalyser(TSStream tsstream) #LTS
|
||||||
|
@ -278,8 +278,8 @@ macro(makeInput inputName format)
|
||||||
if (";${ARGN};" MATCHES ";nolock;")#Currently only used in TSStream
|
if (";${ARGN};" MATCHES ";nolock;")#Currently only used in TSStream
|
||||||
list(APPEND my_definitions "INPUT_NOLOCK")
|
list(APPEND my_definitions "INPUT_NOLOCK")
|
||||||
endif()
|
endif()
|
||||||
if (";${ARGN};" MATCHES ";live;")#Currently only used in TSStream
|
if (";${ARGN};" MATCHES ";tslive;")
|
||||||
list(APPEND my_definitions "INPUT_LIVE")
|
list(APPEND my_definitions "TSLIVE_INPUT")
|
||||||
endif()
|
endif()
|
||||||
list(APPEND my_definitions "INPUTTYPE=\"input_${format}.h\"")
|
list(APPEND my_definitions "INPUTTYPE=\"input_${format}.h\"")
|
||||||
|
|
||||||
|
@ -305,7 +305,7 @@ makeInput(Buffer buffer)
|
||||||
makeInput(ISMV ismv)#LTS
|
makeInput(ISMV ismv)#LTS
|
||||||
makeInput(MP4 mp4)#LTS
|
makeInput(MP4 mp4)#LTS
|
||||||
makeInput(TS ts)#LTS
|
makeInput(TS ts)#LTS
|
||||||
makeInput(TSStream ts nolock live)#LTS
|
makeInput(TSStream ts nolock tslive)#LTS
|
||||||
makeInput(Folder folder folder)#LTS
|
makeInput(Folder folder folder)#LTS
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
|
@ -313,9 +313,9 @@ makeInput(Folder folder folder)#LTS
|
||||||
########################################
|
########################################
|
||||||
macro(makeOutput outputName format)
|
macro(makeOutput outputName format)
|
||||||
#Parse all extra arguments, for http and ts flags
|
#Parse all extra arguments, for http and ts flags
|
||||||
|
SET (tsBaseClass Output)
|
||||||
if (";${ARGN};" MATCHES ";http;")
|
if (";${ARGN};" MATCHES ";http;")
|
||||||
SET(httpOutput src/output/output_http.cpp)
|
SET(httpOutput src/output/output_http.cpp)
|
||||||
SET(tsBaseClass Output)
|
|
||||||
if (";${ARGN};" MATCHES ";ts;")
|
if (";${ARGN};" MATCHES ";ts;")
|
||||||
SET(tsBaseClass HTTPOutput)
|
SET(tsBaseClass HTTPOutput)
|
||||||
endif()
|
endif()
|
||||||
|
@ -356,6 +356,7 @@ makeOutput(JSON json http)
|
||||||
makeOutput(TS ts ts)
|
makeOutput(TS ts ts)
|
||||||
makeOutput(HTTPTS httpts http ts)
|
makeOutput(HTTPTS httpts http ts)
|
||||||
makeOutput(HLS hls http ts)
|
makeOutput(HLS hls http ts)
|
||||||
|
makeOutput(Push push)#LTS
|
||||||
makeOutput(RTSP rtsp)#LTS
|
makeOutput(RTSP rtsp)#LTS
|
||||||
makeOutput(TSPush ts_push ts)#LTS
|
makeOutput(TSPush ts_push ts)#LTS
|
||||||
makeOutput(DASH dash_mp4 http)#LTS
|
makeOutput(DASH dash_mp4 http)#LTS
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
add_library ( mist SHARED
|
|
||||||
amf.cpp
|
|
||||||
amf.h
|
|
||||||
auth.cpp
|
|
||||||
auth.h
|
|
||||||
base64.cpp
|
|
||||||
base64.h
|
|
||||||
bitfields.cpp
|
|
||||||
bitfields.h
|
|
||||||
bitstream.cpp
|
|
||||||
bitstream.h
|
|
||||||
checksum.h
|
|
||||||
CMakeLists.txt
|
|
||||||
config.cpp
|
|
||||||
config.h
|
|
||||||
converter.cpp
|
|
||||||
converter.h
|
|
||||||
defines.h
|
|
||||||
dtsc.cpp
|
|
||||||
dtsc.h
|
|
||||||
dtscmeta.cpp
|
|
||||||
filesystem.cpp
|
|
||||||
filesystem.h
|
|
||||||
flv_tag.cpp
|
|
||||||
flv_tag.h
|
|
||||||
ftp.cpp
|
|
||||||
ftp.h
|
|
||||||
http_parser.cpp
|
|
||||||
http_parser.h
|
|
||||||
json.cpp
|
|
||||||
json.h
|
|
||||||
mp4_adobe.cpp
|
|
||||||
mp4_adobe.h
|
|
||||||
mp4.cpp
|
|
||||||
mp4_generic.cpp
|
|
||||||
mp4_generic.h
|
|
||||||
mp4.h
|
|
||||||
mp4_ms.cpp
|
|
||||||
mp4_ms.h
|
|
||||||
nal.cpp
|
|
||||||
nal.h
|
|
||||||
ogg.cpp
|
|
||||||
ogg.h
|
|
||||||
procs.cpp
|
|
||||||
procs.h
|
|
||||||
rtmpchunks.cpp
|
|
||||||
rtmpchunks.h
|
|
||||||
shared_memory.cpp
|
|
||||||
shared_memory.h
|
|
||||||
socket.cpp
|
|
||||||
socket.h
|
|
||||||
stream.cpp
|
|
||||||
stream.h
|
|
||||||
theora.cpp
|
|
||||||
theora.h
|
|
||||||
timing.cpp
|
|
||||||
timing.h
|
|
||||||
tinythread.cpp
|
|
||||||
tinythread.h
|
|
||||||
ts_packet.cpp
|
|
||||||
ts_packet.h
|
|
||||||
vorbis.cpp
|
|
||||||
vorbis.h
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries( mist
|
|
||||||
-lpthread
|
|
||||||
-lrt
|
|
||||||
)
|
|
|
@ -298,7 +298,7 @@ namespace DTSC {
|
||||||
int getSendLen();
|
int getSendLen();
|
||||||
void send(Socket::Connection & conn);
|
void send(Socket::Connection & conn);
|
||||||
void writeTo(char *& p);
|
void writeTo(char *& p);
|
||||||
JSON::Value toJSON();
|
JSON::Value toJSON(bool skipBinary = false);
|
||||||
std::deque<Fragment> fragments;
|
std::deque<Fragment> fragments;
|
||||||
std::deque<Key> keys;
|
std::deque<Key> keys;
|
||||||
std::deque<unsigned long> keySizes;
|
std::deque<unsigned long> keySizes;
|
||||||
|
|
|
@ -1772,9 +1772,10 @@ namespace DTSC {
|
||||||
}
|
}
|
||||||
|
|
||||||
///\brief Converts a track to a JSON::Value
|
///\brief Converts a track to a JSON::Value
|
||||||
JSON::Value Track::toJSON() {
|
JSON::Value Track::toJSON(bool skipBinary) {
|
||||||
JSON::Value result;
|
JSON::Value result;
|
||||||
std::string tmp;
|
std::string tmp;
|
||||||
|
if (!skipBinary) {
|
||||||
tmp.reserve(fragments.size() * PACKED_FRAGMENT_SIZE);
|
tmp.reserve(fragments.size() * PACKED_FRAGMENT_SIZE);
|
||||||
for (std::deque<Fragment>::iterator it = fragments.begin(); it != fragments.end(); it++) {
|
for (std::deque<Fragment>::iterator it = fragments.begin(); it != fragments.end(); it++) {
|
||||||
tmp.append(it->getData(), PACKED_FRAGMENT_SIZE);
|
tmp.append(it->getData(), PACKED_FRAGMENT_SIZE);
|
||||||
|
@ -1809,6 +1810,8 @@ namespace DTSC {
|
||||||
}
|
}
|
||||||
result["ivecs"] = tmp;
|
result["ivecs"] = tmp;
|
||||||
/*LTS-END*/
|
/*LTS-END*/
|
||||||
|
result["init"] = init;
|
||||||
|
}
|
||||||
result["trackid"] = trackID;
|
result["trackid"] = trackID;
|
||||||
result["firstms"] = (long long)firstms;
|
result["firstms"] = (long long)firstms;
|
||||||
result["lastms"] = (long long)lastms;
|
result["lastms"] = (long long)lastms;
|
||||||
|
@ -1818,7 +1821,6 @@ namespace DTSC {
|
||||||
}
|
}
|
||||||
result["codec"] = codec;
|
result["codec"] = codec;
|
||||||
result["type"] = type;
|
result["type"] = type;
|
||||||
result["init"] = init;
|
|
||||||
if (type == "audio") {
|
if (type == "audio") {
|
||||||
result["rate"] = rate;
|
result["rate"] = rate;
|
||||||
result["size"] = size;
|
result["size"] = size;
|
||||||
|
|
|
@ -180,13 +180,11 @@ void HTTP::Parser::StartResponse(HTTP::Parser & request, Socket::Connection & co
|
||||||
StartResponse("200", "OK", request, conn, bufferAllChunks);
|
StartResponse("200", "OK", request, conn, bufferAllChunks);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// After receiving a header with this object, this function call will:
|
/// After receiving a header with this object, and after a call with SendResponse/SendRequest with this object, this function call will:
|
||||||
/// - Forward the headers to the 'to' Socket::Connection.
|
|
||||||
/// - Retrieve all the body from the 'from' Socket::Connection.
|
/// - Retrieve all the body from the 'from' Socket::Connection.
|
||||||
/// - Forward those contents as-is to the 'to' Socket::Connection.
|
/// - Forward those contents as-is to the 'to' Socket::Connection.
|
||||||
/// It blocks until completed or either of the connections reaches an error state.
|
/// It blocks until completed or either of the connections reaches an error state.
|
||||||
void HTTP::Parser::Proxy(Socket::Connection & from, Socket::Connection & to) {
|
void HTTP::Parser::Proxy(Socket::Connection & from, Socket::Connection & to) {
|
||||||
SendResponse(url, method, to);
|
|
||||||
if (getChunks) {
|
if (getChunks) {
|
||||||
unsigned int proxyingChunk = 0;
|
unsigned int proxyingChunk = 0;
|
||||||
while (to.connected() && from.connected()) {
|
while (to.connected() && from.connected()) {
|
||||||
|
|
|
@ -15,14 +15,21 @@
|
||||||
#include "bitfields.h"
|
#include "bitfields.h"
|
||||||
#include "timing.h"
|
#include "timing.h"
|
||||||
|
|
||||||
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
|
#include <windows.h>
|
||||||
|
#include <aclapi.h>
|
||||||
|
#include <accctrl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
namespace IPC {
|
namespace IPC {
|
||||||
|
|
||||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
static std::map<std::string, sharedPage> preservedPages;
|
static std::map<std::string, sharedPage> preservedPages;
|
||||||
void preservePage(std::string p){
|
void preservePage(std::string p) {
|
||||||
preservedPages[p].init(p, 0, false, false);
|
preservedPages[p].init(p, 0, false, false);
|
||||||
}
|
}
|
||||||
void releasePage(std::string p){
|
void releasePage(std::string p) {
|
||||||
preservedPages.erase(p);
|
preservedPages.erase(p);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -108,7 +115,7 @@ namespace IPC {
|
||||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
return mySem != 0;
|
return mySem != 0;
|
||||||
#else
|
#else
|
||||||
return mySem != SEM_FAILED;
|
return mySem && mySem != SEM_FAILED;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,23 +129,34 @@ namespace IPC {
|
||||||
void semaphore::open(const char * name, int oflag, mode_t mode, unsigned int value) {
|
void semaphore::open(const char * name, int oflag, mode_t mode, unsigned int value) {
|
||||||
close();
|
close();
|
||||||
int timer = 0;
|
int timer = 0;
|
||||||
while (!(*this) && timer++ < 10){
|
while (!(*this) && timer++ < 10) {
|
||||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
std::string semaName = "Global\\";
|
std::string semaName = "Global\\";
|
||||||
semaName += name;
|
semaName += name;
|
||||||
if (oflag & O_CREAT){
|
if (oflag & O_CREAT) {
|
||||||
if (oflag & O_EXCL){
|
if (oflag & O_EXCL) {
|
||||||
//attempt opening, if succes, close handle and return false;
|
//attempt opening, if succes, close handle and return false;
|
||||||
HANDLE tmpSem = OpenSemaphore(0, false, semaName.c_str());
|
HANDLE tmpSem = OpenMutex(SYNCHRONIZE, false, semaName.c_str());
|
||||||
if (tmpSem){
|
if (tmpSem) {
|
||||||
CloseHandle(tmpSem);
|
CloseHandle(tmpSem);
|
||||||
mySem = 0;
|
mySem = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mySem = CreateSemaphore(0, value, 1 , semaName.c_str());
|
SECURITY_ATTRIBUTES security = getSecurityAttributes();
|
||||||
}else{
|
mySem = CreateMutex(&security, true, semaName.c_str());
|
||||||
mySem = OpenSemaphore(0, false, semaName.c_str());
|
if (value){
|
||||||
|
ReleaseMutex(mySem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mySem = OpenMutex(SYNCHRONIZE, false, semaName.c_str());
|
||||||
|
}
|
||||||
|
if (!(*this)) {
|
||||||
|
if (GetLastError() == ERROR_FILE_NOT_FOUND){//Error code 2
|
||||||
|
Util::wait(500);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (oflag & O_CREAT) {
|
if (oflag & O_CREAT) {
|
||||||
|
@ -146,17 +164,16 @@ namespace IPC {
|
||||||
} else {
|
} else {
|
||||||
mySem = sem_open(name, oflag);
|
mySem = sem_open(name, oflag);
|
||||||
}
|
}
|
||||||
#endif
|
if (!(*this)) {
|
||||||
if (!(*this)){
|
if (errno == ENOENT) {
|
||||||
if (errno == ENOENT){
|
|
||||||
Util::wait(500);
|
Util::wait(500);
|
||||||
}else{
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
if (!(*this)){
|
if (!(*this)) {
|
||||||
DEBUG_MSG(DLVL_VERYHIGH, "Attempt to open semaphore %s: %s", name, strerror(errno));
|
|
||||||
}
|
}
|
||||||
myName = (char *)name;
|
myName = (char *)name;
|
||||||
}
|
}
|
||||||
|
@ -177,7 +194,7 @@ namespace IPC {
|
||||||
void semaphore::post() {
|
void semaphore::post() {
|
||||||
if (*this) {
|
if (*this) {
|
||||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
ReleaseSemaphore(mySem, 1, 0);
|
ReleaseMutex(mySem);
|
||||||
#else
|
#else
|
||||||
sem_post(mySem);
|
sem_post(mySem);
|
||||||
#endif
|
#endif
|
||||||
|
@ -203,7 +220,7 @@ namespace IPC {
|
||||||
int result;
|
int result;
|
||||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
result = WaitForSingleObject(mySem, 0);//wait at most 1ms
|
result = WaitForSingleObject(mySem, 0);//wait at most 1ms
|
||||||
if (result == 0x80){
|
if (result == 0x80) {
|
||||||
WARN_MSG("Consistency error caught on semaphore %s", myName.c_str());
|
WARN_MSG("Consistency error caught on semaphore %s", myName.c_str());
|
||||||
result = 0;
|
result = 0;
|
||||||
}
|
}
|
||||||
|
@ -218,7 +235,7 @@ namespace IPC {
|
||||||
int result;
|
int result;
|
||||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
result = WaitForSingleObject(mySem, 1000);//wait at most 1s
|
result = WaitForSingleObject(mySem, 1000);//wait at most 1s
|
||||||
if (result == 0x80){
|
if (result == 0x80) {
|
||||||
WARN_MSG("Consistency error caught on semaphore %s", myName.c_str());
|
WARN_MSG("Consistency error caught on semaphore %s", myName.c_str());
|
||||||
result = 0;
|
result = 0;
|
||||||
}
|
}
|
||||||
|
@ -268,12 +285,38 @@ namespace IPC {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
|
SECURITY_ATTRIBUTES semaphore::getSecurityAttributes() {
|
||||||
|
///\todo We really should clean this up sometime probably
|
||||||
|
///We currently have everything static, because the result basically depends on everything
|
||||||
|
static SECURITY_ATTRIBUTES result;
|
||||||
|
static bool resultValid = false;
|
||||||
|
static SECURITY_DESCRIPTOR securityDescriptor;
|
||||||
|
if (resultValid) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
InitializeSecurityDescriptor(&securityDescriptor, SECURITY_DESCRIPTOR_REVISION);
|
||||||
|
if (!SetSecurityDescriptorDacl(&securityDescriptor, TRUE, NULL, FALSE)){
|
||||||
|
FAIL_MSG("Failed to set pSecurityDescriptor: %u", GetLastError());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||||
|
result.lpSecurityDescriptor = &securityDescriptor;
|
||||||
|
result.bInheritHandle = FALSE;
|
||||||
|
|
||||||
|
resultValid = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
///brief Creates a shared page
|
///brief Creates a shared page
|
||||||
///\param name_ The name of the page to be created
|
///\param name_ The name of the page to be created
|
||||||
///\param len_ The size to make the page
|
///\param len_ The size to make the page
|
||||||
///\param master_ Whether to create or merely open the page
|
///\param master_ Whether to create or merely open the page
|
||||||
///\param autoBackoff When only opening the page, wait for it to appear or fail
|
///\param autoBackoff When only opening the page, wait for it to appear or fail
|
||||||
sharedPage::sharedPage(std::string name_, unsigned int len_, bool master_, bool autoBackoff){
|
sharedPage::sharedPage(std::string name_, unsigned int len_, bool master_, bool autoBackoff) {
|
||||||
handle = 0;
|
handle = 0;
|
||||||
len = 0;
|
len = 0;
|
||||||
master = false;
|
master = false;
|
||||||
|
@ -302,7 +345,7 @@ namespace IPC {
|
||||||
if (mapped && len) {
|
if (mapped && len) {
|
||||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
//under Cygwin, the mapped location is shifted by 4 to contain the page size.
|
//under Cygwin, the mapped location is shifted by 4 to contain the page size.
|
||||||
UnmapViewOfFile(mapped-4);
|
UnmapViewOfFile(mapped - 4);
|
||||||
#else
|
#else
|
||||||
munmap(mapped, len);
|
munmap(mapped, len);
|
||||||
#endif
|
#endif
|
||||||
|
@ -315,7 +358,7 @@ namespace IPC {
|
||||||
void sharedPage::close() {
|
void sharedPage::close() {
|
||||||
unmap();
|
unmap();
|
||||||
if (handle > 0) {
|
if (handle > 0) {
|
||||||
INSANE_MSG("Closing page %s in %s mode", name.c_str(), master?"master":"client");
|
INSANE_MSG("Closing page %s in %s mode", name.c_str(), master ? "master" : "client");
|
||||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
#else
|
#else
|
||||||
|
@ -353,11 +396,11 @@ namespace IPC {
|
||||||
master = master_;
|
master = master_;
|
||||||
mapped = 0;
|
mapped = 0;
|
||||||
if (name.size()) {
|
if (name.size()) {
|
||||||
INSANE_MSG("Opening page %s in %s mode %s auto-backoff", name.c_str(), master?"master":"client", autoBackoff?"with":"without");
|
INSANE_MSG("Opening page %s in %s mode %s auto-backoff", name.c_str(), master ? "master" : "client", autoBackoff ? "with" : "without");
|
||||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
if (master) {
|
if (master) {
|
||||||
//Under cygwin, all pages are 4 bytes longer than claimed.
|
//Under cygwin, all pages are 4 bytes longer than claimed.
|
||||||
handle = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, len+4, name.c_str());
|
handle = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, len + 4, name.c_str());
|
||||||
} else {
|
} else {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
do {
|
do {
|
||||||
|
@ -378,10 +421,10 @@ namespace IPC {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//Under cygwin, the extra 4 bytes contain the real size of the page.
|
//Under cygwin, the extra 4 bytes contain the real size of the page.
|
||||||
if (master){
|
if (master) {
|
||||||
((unsigned int*)mapped)[0] = len_;
|
((unsigned int *)mapped)[0] = len_;
|
||||||
}else{
|
} else {
|
||||||
len = ((unsigned int*)mapped)[0];
|
len = ((unsigned int *)mapped)[0];
|
||||||
}
|
}
|
||||||
//Now shift by those 4 bytes.
|
//Now shift by those 4 bytes.
|
||||||
mapped += 4;
|
mapped += 4;
|
||||||
|
@ -401,7 +444,7 @@ namespace IPC {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (handle == -1) {
|
if (handle == -1) {
|
||||||
if (!master_ && autoBackoff){
|
if (!master_ && autoBackoff) {
|
||||||
FAIL_MSG("shm_open for page %s failed: %s", name.c_str(), strerror(errno));
|
FAIL_MSG("shm_open for page %s failed: %s", name.c_str(), strerror(errno));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -616,8 +659,8 @@ namespace IPC {
|
||||||
|
|
||||||
///\brief Sets the host of this connection
|
///\brief Sets the host of this connection
|
||||||
void statExchange::host(std::string name) {
|
void statExchange::host(std::string name) {
|
||||||
if (name.size() < 16){
|
if (name.size() < 16) {
|
||||||
memset(data+32, 0, 16);
|
memset(data + 32, 0, 16);
|
||||||
}
|
}
|
||||||
memcpy(data + 32, name.c_str(), std::min((int)name.size(), 16));
|
memcpy(data + 32, name.c_str(), std::min((int)name.size(), 16));
|
||||||
}
|
}
|
||||||
|
@ -630,7 +673,7 @@ namespace IPC {
|
||||||
///\brief Sets the name of the stream this user is viewing
|
///\brief Sets the name of the stream this user is viewing
|
||||||
void statExchange::streamName(std::string name) {
|
void statExchange::streamName(std::string name) {
|
||||||
size_t splitChar = name.find_first_of("+ ");
|
size_t splitChar = name.find_first_of("+ ");
|
||||||
if (splitChar != std::string::npos){
|
if (splitChar != std::string::npos) {
|
||||||
name[splitChar] = '+';
|
name[splitChar] = '+';
|
||||||
}
|
}
|
||||||
memcpy(data + 48, name.c_str(), std::min((int)name.size(), 100));
|
memcpy(data + 48, name.c_str(), std::min((int)name.size(), 100));
|
||||||
|
@ -730,7 +773,7 @@ namespace IPC {
|
||||||
|
|
||||||
///\brief Creates the next page with the correct size
|
///\brief Creates the next page with the correct size
|
||||||
void sharedServer::newPage() {
|
void sharedServer::newPage() {
|
||||||
sharedPage tmp(std::string(baseName.substr(1) + (char)(myPages.size() + (int)'A')), std::min(((8192 * 2)<< myPages.size()), (32 * 1024 * 1024)), true);
|
sharedPage tmp(std::string(baseName.substr(1) + (char)(myPages.size() + (int)'A')), std::min(((8192 * 2) << myPages.size()), (32 * 1024 * 1024)), true);
|
||||||
myPages.insert(tmp);
|
myPages.insert(tmp);
|
||||||
tmp.master = false;
|
tmp.master = false;
|
||||||
DEBUG_MSG(DLVL_VERYHIGH, "Created a new page: %s", tmp.name.c_str());
|
DEBUG_MSG(DLVL_VERYHIGH, "Created a new page: %s", tmp.name.c_str());
|
||||||
|
@ -784,7 +827,7 @@ namespace IPC {
|
||||||
}
|
}
|
||||||
semGuard tmpGuard(&mySemaphore);
|
semGuard tmpGuard(&mySemaphore);
|
||||||
unsigned int id = 0;
|
unsigned int id = 0;
|
||||||
unsigned int userCount=0;
|
unsigned int userCount = 0;
|
||||||
unsigned int emptyCount = 0;
|
unsigned int emptyCount = 0;
|
||||||
for (std::set<sharedPage>::iterator it = myPages.begin(); it != myPages.end(); it++) {
|
for (std::set<sharedPage>::iterator it = myPages.begin(); it != myPages.end(); it++) {
|
||||||
if (!it->mapped || !it->len) {
|
if (!it->mapped || !it->len) {
|
||||||
|
@ -796,15 +839,15 @@ namespace IPC {
|
||||||
while (offset + payLen + (hasCounter ? 1 : 0) <= it->len) {
|
while (offset + payLen + (hasCounter ? 1 : 0) <= it->len) {
|
||||||
if (hasCounter) {
|
if (hasCounter) {
|
||||||
if (it->mapped[offset] != 0) {
|
if (it->mapped[offset] != 0) {
|
||||||
char * counter = it->mapped+offset;
|
char * counter = it->mapped + offset;
|
||||||
//increase the count if needed
|
//increase the count if needed
|
||||||
++userCount;
|
++userCount;
|
||||||
if (id >= amount) {
|
if (id >= amount) {
|
||||||
amount = id + 1;
|
amount = id + 1;
|
||||||
DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount);
|
DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount);
|
||||||
}
|
}
|
||||||
unsigned short tmpPID = *((unsigned short *)(it->mapped+1+offset+payLen-2));
|
unsigned short tmpPID = *((unsigned short *)(it->mapped + 1 + offset + payLen - 2));
|
||||||
if(!Util::Procs::isRunning(tmpPID) && !(*counter == 126 || *counter == 127 || *counter == 254 || *counter == 255)){
|
if (!Util::Procs::isRunning(tmpPID) && !(*counter == 126 || *counter == 127 || *counter == 254 || *counter == 255)) {
|
||||||
WARN_MSG("process disappeared, timing out. (pid %d)", tmpPID);
|
WARN_MSG("process disappeared, timing out. (pid %d)", tmpPID);
|
||||||
*counter = 126; //if process is already dead, instant timeout.
|
*counter = 126; //if process is already dead, instant timeout.
|
||||||
}
|
}
|
||||||
|
@ -823,12 +866,12 @@ namespace IPC {
|
||||||
DEBUG_MSG(DLVL_WARN, "Client %u disconnect timed out", id);
|
DEBUG_MSG(DLVL_WARN, "Client %u disconnect timed out", id);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
#ifndef NOCRASHCHECK
|
#ifndef NOCRASHCHECK
|
||||||
if (tmpPID){
|
if (tmpPID) {
|
||||||
if(*counter > 10 && *counter < 126 ){
|
if (*counter > 10 && *counter < 126) {
|
||||||
if(*counter < 30){
|
if (*counter < 30) {
|
||||||
if (*counter > 15){
|
if (*counter > 15) {
|
||||||
WARN_MSG("Process %d is unresponsive",tmpPID);
|
WARN_MSG("Process %d is unresponsive", tmpPID);
|
||||||
}
|
}
|
||||||
Util::Procs::Stop(tmpPID); //soft kill
|
Util::Procs::Stop(tmpPID); //soft kill
|
||||||
} else {
|
} else {
|
||||||
|
@ -837,7 +880,7 @@ namespace IPC {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (*counter == 127 || *counter == 126 || *counter == 255 || *counter == 254) {
|
if (*counter == 127 || *counter == 126 || *counter == 255 || *counter == 254) {
|
||||||
|
@ -886,16 +929,16 @@ namespace IPC {
|
||||||
offset += payLen + (hasCounter ? 1 : 0);
|
offset += payLen + (hasCounter ? 1 : 0);
|
||||||
id ++;
|
id ++;
|
||||||
}
|
}
|
||||||
if(userCount==0) {
|
if (userCount == 0) {
|
||||||
++emptyCount;
|
++emptyCount;
|
||||||
} else {
|
} else {
|
||||||
emptyCount=0;
|
emptyCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( emptyCount > 1){
|
if (emptyCount > 1) {
|
||||||
deletePage();
|
deletePage();
|
||||||
} else if( !emptyCount ){
|
} else if (!emptyCount) {
|
||||||
newPage();
|
newPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -911,6 +954,7 @@ namespace IPC {
|
||||||
offsetOnPage = 0;
|
offsetOnPage = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///\brief Copy constructor for sharedClients
|
///\brief Copy constructor for sharedClients
|
||||||
///\param rhs The client ro copy
|
///\param rhs The client ro copy
|
||||||
sharedClient::sharedClient(const sharedClient & rhs) {
|
sharedClient::sharedClient(const sharedClient & rhs) {
|
||||||
|
@ -956,7 +1000,7 @@ namespace IPC {
|
||||||
///\param name The basename of the server to connect to
|
///\param name The basename of the server to connect to
|
||||||
///\param len The size of the payload to allocate
|
///\param len The size of the payload to allocate
|
||||||
///\param withCounter Whether or not this payload has a counter
|
///\param withCounter Whether or not this payload has a counter
|
||||||
sharedClient::sharedClient(std::string name, int len, bool withCounter) : baseName("/"+name), payLen(len), offsetOnPage(-1), hasCounter(withCounter) {
|
sharedClient::sharedClient(std::string name, int len, bool withCounter) : baseName("/" + name), payLen(len), offsetOnPage(-1), hasCounter(withCounter) {
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
//note: O_CREAT is only needed for mac, probably
|
//note: O_CREAT is only needed for mac, probably
|
||||||
mySemaphore.open(baseName.c_str(), O_RDWR | O_CREAT, 0);
|
mySemaphore.open(baseName.c_str(), O_RDWR | O_CREAT, 0);
|
||||||
|
@ -967,6 +1011,7 @@ namespace IPC {
|
||||||
DEBUG_MSG(DLVL_FAIL, "Creating semaphore %s failed: %s", baseName.c_str(), strerror(errno));
|
DEBUG_MSG(DLVL_FAIL, "Creating semaphore %s failed: %s", baseName.c_str(), strerror(errno));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
//Empty is used to compare for emptyness. This is not needed when the page uses a counter
|
||||||
char * empty = 0;
|
char * empty = 0;
|
||||||
if (!hasCounter) {
|
if (!hasCounter) {
|
||||||
empty = (char *)malloc(payLen * sizeof(char));
|
empty = (char *)malloc(payLen * sizeof(char));
|
||||||
|
@ -976,12 +1021,12 @@ namespace IPC {
|
||||||
}
|
}
|
||||||
memset(empty, 0, payLen);
|
memset(empty, 0, payLen);
|
||||||
}
|
}
|
||||||
while (offsetOnPage == -1){
|
while (offsetOnPage == -1) {
|
||||||
{
|
{
|
||||||
semGuard tmpGuard(&mySemaphore);
|
semGuard tmpGuard(&mySemaphore);
|
||||||
for (char i = 'A'; i <= 'Z'; i++) {
|
for (char i = 'A'; i <= 'Z'; i++) {
|
||||||
myPage.init(baseName.substr(1) + i, (4096 << (i - 'A')), false, false);
|
myPage.init(baseName.substr(1) + i, (4096 << (i - 'A')), false, false);
|
||||||
if (!myPage.mapped){
|
if (!myPage.mapped) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
@ -990,7 +1035,7 @@ namespace IPC {
|
||||||
offsetOnPage = offset;
|
offsetOnPage = offset;
|
||||||
if (hasCounter) {
|
if (hasCounter) {
|
||||||
myPage.mapped[offset] = 1;
|
myPage.mapped[offset] = 1;
|
||||||
*((unsigned short *)(myPage.mapped+1+offset+len-2))=getpid();
|
*((unsigned short *)(myPage.mapped + 1 + offset + len - 2)) = getpid();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1001,12 +1046,14 @@ namespace IPC {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (offsetOnPage == -1){
|
if (offsetOnPage == -1) {
|
||||||
Util::wait(500);
|
Util::wait(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (empty) {
|
||||||
free(empty);
|
free(empty);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///\brief The deconstructor
|
///\brief The deconstructor
|
||||||
sharedClient::~sharedClient() {
|
sharedClient::~sharedClient() {
|
||||||
|
@ -1062,7 +1109,7 @@ namespace IPC {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long userConnection::getTrackId(size_t offset) const {
|
unsigned long userConnection::getTrackId(size_t offset) const {
|
||||||
if (offset >= SIMUL_TRACKS){
|
if (offset >= SIMUL_TRACKS) {
|
||||||
WARN_MSG("Trying to get track id for entry %lu, while there are only %d entries allowed", offset, SIMUL_TRACKS);
|
WARN_MSG("Trying to get track id for entry %lu, while there are only %d entries allowed", offset, SIMUL_TRACKS);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1070,7 +1117,7 @@ namespace IPC {
|
||||||
}
|
}
|
||||||
|
|
||||||
void userConnection::setTrackId(size_t offset, unsigned long trackId) const {
|
void userConnection::setTrackId(size_t offset, unsigned long trackId) const {
|
||||||
if (offset >= SIMUL_TRACKS){
|
if (offset >= SIMUL_TRACKS) {
|
||||||
WARN_MSG("Trying to set track id for entry %lu, while there are only %d entries allowed", offset, SIMUL_TRACKS);
|
WARN_MSG("Trying to set track id for entry %lu, while there are only %d entries allowed", offset, SIMUL_TRACKS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1079,7 +1126,7 @@ namespace IPC {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long userConnection::getKeynum(size_t offset) const {
|
unsigned long userConnection::getKeynum(size_t offset) const {
|
||||||
if (offset >= SIMUL_TRACKS){
|
if (offset >= SIMUL_TRACKS) {
|
||||||
WARN_MSG("Trying to get keynum for entry %lu, while there are only %d entries allowed", offset, SIMUL_TRACKS);
|
WARN_MSG("Trying to get keynum for entry %lu, while there are only %d entries allowed", offset, SIMUL_TRACKS);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1087,7 +1134,7 @@ namespace IPC {
|
||||||
}
|
}
|
||||||
|
|
||||||
void userConnection::setKeynum(size_t offset, unsigned long keynum) {
|
void userConnection::setKeynum(size_t offset, unsigned long keynum) {
|
||||||
if (offset >= SIMUL_TRACKS){
|
if (offset >= SIMUL_TRACKS) {
|
||||||
WARN_MSG("Trying to set keynum for entry %lu, while there are only %d entries allowed", offset, SIMUL_TRACKS);
|
WARN_MSG("Trying to set keynum for entry %lu, while there are only %d entries allowed", offset, SIMUL_TRACKS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,8 @@ namespace IPC {
|
||||||
void unlink();
|
void unlink();
|
||||||
private:
|
private:
|
||||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
|
///\todo Maybe sometime implement anything else than 777
|
||||||
|
static SECURITY_ATTRIBUTES getSecurityAttributes();
|
||||||
HANDLE mySem;
|
HANDLE mySem;
|
||||||
#else
|
#else
|
||||||
sem_t * mySem;
|
sem_t * mySem;
|
||||||
|
|
|
@ -690,6 +690,10 @@ namespace TS {
|
||||||
return data[0];
|
return data[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProgramMappingEntry::setStreamType(int newType){
|
||||||
|
data[0] = newType;
|
||||||
|
}
|
||||||
|
|
||||||
std::string ProgramMappingEntry::getCodec() const{
|
std::string ProgramMappingEntry::getCodec() const{
|
||||||
switch (getStreamType()){
|
switch (getStreamType()){
|
||||||
case 0x01:
|
case 0x01:
|
||||||
|
@ -740,10 +744,25 @@ namespace TS {
|
||||||
return ((data[1] << 8) | data[2]) & 0x1FFF;
|
return ((data[1] << 8) | data[2]) & 0x1FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProgramMappingEntry::setElementaryPid(int newElementaryPid) {
|
||||||
|
data[1] = newElementaryPid >> 8 & 0x1F;
|
||||||
|
data[2] = newElementaryPid & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
int ProgramMappingEntry::getESInfoLength() const{
|
int ProgramMappingEntry::getESInfoLength() const{
|
||||||
return ((data[3] << 8) | data[4]) & 0x0FFF;
|
return ((data[3] << 8) | data[4]) & 0x0FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char * ProgramMappingEntry::getESInfo() const{
|
||||||
|
return data + 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProgramMappingEntry::setESInfo(const std::string & newInfo){
|
||||||
|
data[3] = (newInfo.size() >> 8) & 0x0F;
|
||||||
|
data[4] = newInfo.size() & 0xFF;
|
||||||
|
memcpy(data + 5, newInfo.data(), newInfo.size());
|
||||||
|
}
|
||||||
|
|
||||||
void ProgramMappingEntry::advance(){
|
void ProgramMappingEntry::advance(){
|
||||||
if (!(*this)) {
|
if (!(*this)) {
|
||||||
return;
|
return;
|
||||||
|
@ -884,14 +903,6 @@ namespace TS {
|
||||||
strBuf[loc+1] = (char)newVal;
|
strBuf[loc+1] = (char)newVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
short ProgramMappingTable::getProgramCount() const{
|
|
||||||
return (getSectionLength() - 13) / 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramMappingTable::setProgramCount(short newVal) {
|
|
||||||
setSectionLength(newVal * 5 + 13);
|
|
||||||
}
|
|
||||||
|
|
||||||
ProgramMappingEntry ProgramMappingTable::getEntry(int index) const{
|
ProgramMappingEntry ProgramMappingTable::getEntry(int index) const{
|
||||||
int dataOffset = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset();
|
int dataOffset = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset();
|
||||||
ProgramMappingEntry res((char*)(strBuf + dataOffset + 13 + getProgramInfoLength()), (char*)(strBuf + dataOffset + getSectionLength()) );
|
ProgramMappingEntry res((char*)(strBuf + dataOffset + 13 + getProgramInfoLength()), (char*)(strBuf + dataOffset + getSectionLength()) );
|
||||||
|
@ -901,59 +912,6 @@ namespace TS {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
char ProgramMappingTable::getStreamType(short index) const{
|
|
||||||
if (index > getProgramCount()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength();
|
|
||||||
return strBuf[loc + (index * 5)];
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramMappingTable::setStreamType(char newVal, short index) {
|
|
||||||
if (index > getProgramCount()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); //TODO
|
|
||||||
updPos(loc+(index*5)+1);
|
|
||||||
strBuf[loc + (index * 5)] = newVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
short ProgramMappingTable::getElementaryPID(short index) const{
|
|
||||||
if (index > getProgramCount()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength();
|
|
||||||
return (((short)strBuf[loc + (index * 5) + 1] & 0x1F) << 8) | strBuf[loc + (index * 5) + 2];
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramMappingTable::setElementaryPID(short newVal, short index) {
|
|
||||||
if (index > getProgramCount()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength();
|
|
||||||
updPos(loc+(index*5)+3);
|
|
||||||
strBuf[loc + (index * 5)+1] = ((newVal >> 8) & 0x1F )| 0xE0;
|
|
||||||
strBuf[loc + (index * 5)+2] = (char)newVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
short ProgramMappingTable::getESInfoLength(short index) const{
|
|
||||||
if (index > getProgramCount()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength();
|
|
||||||
return (((short)strBuf[loc + (index * 5) + 3] & 0x0F) << 8) | strBuf[loc + (index * 5) + 4];
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramMappingTable::setESInfoLength(short newVal, short index) {
|
|
||||||
if (index > getProgramCount()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength();
|
|
||||||
updPos(loc+(index*5)+5);
|
|
||||||
strBuf[loc + (index * 5)+3] = ((newVal >> 8) & 0x0F) | 0xF0;
|
|
||||||
strBuf[loc + (index * 5)+4] = (char)newVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ProgramMappingTable::getCRC() const{
|
int ProgramMappingTable::getCRC() const{
|
||||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + getSectionLength();
|
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + getSectionLength();
|
||||||
return ((int)(strBuf[loc]) << 24) | ((int)(strBuf[loc + 1]) << 16) | ((int)(strBuf[loc + 2]) << 8) | strBuf[loc + 3];
|
return ((int)(strBuf[loc]) << 24) | ((int)(strBuf[loc + 1]) << 16) | ((int)(strBuf[loc + 2]) << 8) | strBuf[loc + 3];
|
||||||
|
@ -1011,7 +969,14 @@ namespace TS {
|
||||||
PMT.setPID(4096);
|
PMT.setPID(4096);
|
||||||
PMT.setTableId(2);
|
PMT.setTableId(2);
|
||||||
//section length met 2 tracks: 0xB017
|
//section length met 2 tracks: 0xB017
|
||||||
PMT.setSectionLength(0xB00D + (selectedTracks.size() * 5));
|
int sectionLen = 0;
|
||||||
|
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||||
|
sectionLen += 5;
|
||||||
|
if (myMeta.tracks[*it].codec == "ID3"){
|
||||||
|
sectionLen += myMeta.tracks[*it].init.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PMT.setSectionLength(0xB00D + sectionLen);
|
||||||
PMT.setProgramNumber(1);
|
PMT.setProgramNumber(1);
|
||||||
PMT.setVersionNumber(0);
|
PMT.setVersionNumber(0);
|
||||||
PMT.setCurrentNextIndicator(0);
|
PMT.setCurrentNextIndicator(0);
|
||||||
|
@ -1028,24 +993,27 @@ namespace TS {
|
||||||
if (vidTrack == -1){
|
if (vidTrack == -1){
|
||||||
vidTrack = *(selectedTracks.begin());
|
vidTrack = *(selectedTracks.begin());
|
||||||
}
|
}
|
||||||
PMT.setPCRPID(0x100 + vidTrack - 1);
|
PMT.setPCRPID(vidTrack);
|
||||||
PMT.setProgramInfoLength(0);
|
PMT.setProgramInfoLength(0);
|
||||||
short id = 0;
|
short id = 0;
|
||||||
|
ProgramMappingEntry entry = PMT.getEntry(0);
|
||||||
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||||
|
entry.setElementaryPid(*it);
|
||||||
if (myMeta.tracks[*it].codec == "H264"){
|
if (myMeta.tracks[*it].codec == "H264"){
|
||||||
PMT.setStreamType(0x1B,id);
|
entry.setStreamType(0x1B);
|
||||||
}else if (myMeta.tracks[*it].codec == "HEVC"){
|
}else if (myMeta.tracks[*it].codec == "HEVC"){
|
||||||
PMT.setStreamType(0x24,id);
|
entry.setStreamType(0x24);
|
||||||
}else if (myMeta.tracks[*it].codec == "AAC"){
|
}else if (myMeta.tracks[*it].codec == "AAC"){
|
||||||
PMT.setStreamType(0x0F,id);
|
entry.setStreamType(0x0F);
|
||||||
}else if (myMeta.tracks[*it].codec == "MP3"){
|
}else if (myMeta.tracks[*it].codec == "MP3"){
|
||||||
PMT.setStreamType(0x03,id);
|
entry.setStreamType(0x03);
|
||||||
}else if (myMeta.tracks[*it].codec == "AC3"){
|
}else if (myMeta.tracks[*it].codec == "AC3"){
|
||||||
PMT.setStreamType(0x81,id);
|
entry.setStreamType(0x81);
|
||||||
|
}else if (myMeta.tracks[*it].codec == "ID3"){
|
||||||
|
entry.setStreamType(0x15);
|
||||||
|
entry.setESInfo(myMeta.tracks[*it].init);
|
||||||
}
|
}
|
||||||
PMT.setElementaryPID(0x100 + (*it) - 1, id);
|
entry.advance();
|
||||||
PMT.setESInfoLength(0,id);
|
|
||||||
id++;
|
|
||||||
}
|
}
|
||||||
PMT.calcCRC();
|
PMT.calcCRC();
|
||||||
return PMT.checkAndGetBuffer();
|
return PMT.checkAndGetBuffer();
|
||||||
|
|
|
@ -107,11 +107,14 @@ namespace TS {
|
||||||
operator bool() const;
|
operator bool() const;
|
||||||
|
|
||||||
int getStreamType() const;
|
int getStreamType() const;
|
||||||
|
void setStreamType(int newType);
|
||||||
std::string getCodec() const;
|
std::string getCodec() const;
|
||||||
std::string getStreamTypeString() const;
|
std::string getStreamTypeString() const;
|
||||||
int getElementaryPid() const;
|
int getElementaryPid() const;
|
||||||
|
void setElementaryPid(int newElementaryPid);
|
||||||
int getESInfoLength() const;
|
int getESInfoLength() const;
|
||||||
char * getESInfo() const;
|
const char * getESInfo() const;
|
||||||
|
void setESInfo(const std::string & newInfo);
|
||||||
void advance();
|
void advance();
|
||||||
private:
|
private:
|
||||||
char* data;
|
char* data;
|
||||||
|
@ -142,15 +145,7 @@ namespace TS {
|
||||||
void setPCRPID(short newVal);
|
void setPCRPID(short newVal);
|
||||||
short getProgramInfoLength() const;
|
short getProgramInfoLength() const;
|
||||||
void setProgramInfoLength(short newVal);
|
void setProgramInfoLength(short newVal);
|
||||||
short getProgramCount() const;
|
|
||||||
void setProgramCount(short newVal);
|
|
||||||
ProgramMappingEntry getEntry(int index) const;
|
ProgramMappingEntry getEntry(int index) const;
|
||||||
void setStreamType(char newVal, short index);
|
|
||||||
char getStreamType(short index) const;
|
|
||||||
void setElementaryPID(short newVal, short index);
|
|
||||||
short getElementaryPID(short index) const;
|
|
||||||
void setESInfoLength(short newVal,short index);
|
|
||||||
short getESInfoLength(short index) const;
|
|
||||||
int getCRC() const;
|
int getCRC() const;
|
||||||
void calcCRC();
|
void calcCRC();
|
||||||
std::string toPrettyString(size_t indent) const;
|
std::string toPrettyString(size_t indent) const;
|
||||||
|
|
|
@ -4,8 +4,25 @@
|
||||||
#include "h265.h"
|
#include "h265.h"
|
||||||
#include "nal.h"
|
#include "nal.h"
|
||||||
#include "mp4_generic.h"
|
#include "mp4_generic.h"
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
namespace TS {
|
namespace TS {
|
||||||
|
Stream::Stream(bool _threaded){
|
||||||
|
threaded = _threaded;
|
||||||
|
if (threaded){
|
||||||
|
globalSem.open("MstTSInputLock", O_CREAT | O_EXCL | O_RDWR, ACCESSPERMS, 1);
|
||||||
|
if (!globalSem) {
|
||||||
|
globalSem.open("MstTSInputLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||||
|
}
|
||||||
|
if (!globalSem) {
|
||||||
|
FAIL_MSG("Creating semaphore failed: %s", strerror(errno));
|
||||||
|
threaded = false;
|
||||||
|
DEBUG_MSG(DLVL_FAIL, "Creating semaphore failed: %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Stream::parse(char * newPack, unsigned long long bytePos) {
|
void Stream::parse(char * newPack, unsigned long long bytePos) {
|
||||||
Packet newPacket;
|
Packet newPacket;
|
||||||
newPacket.FromPointer(newPack);
|
newPacket.FromPointer(newPack);
|
||||||
|
@ -13,39 +30,122 @@ namespace TS {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stream::clear(){
|
void Stream::clear(){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
pesStreams.clear();
|
pesStreams.clear();
|
||||||
pesPositions.clear();
|
pesPositions.clear();
|
||||||
payloadSize.clear();
|
|
||||||
outPackets.clear();
|
outPackets.clear();
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::add(char * newPack, unsigned long long bytePos) {
|
||||||
|
Packet newPacket;
|
||||||
|
newPacket.FromPointer(newPack);
|
||||||
|
add(newPacket, bytePos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::add(Packet & newPack, unsigned long long bytePos) {
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stream::parse(Packet & newPack, unsigned long long bytePos) {
|
|
||||||
int tid = newPack.getPID();
|
int tid = newPack.getPID();
|
||||||
|
pesStreams[tid].push_back(newPack);
|
||||||
|
pesPositions[tid].push_back(bytePos);
|
||||||
|
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Stream::isDataTrack(unsigned long tid){
|
||||||
if (tid == 0){
|
if (tid == 0){
|
||||||
associationTable = newPack;
|
return false;
|
||||||
pmtTracks.clear();
|
}
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
|
bool result = !pmtTracks.count(tid);
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::parse(unsigned long tid) {
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
|
if (!pesStreams.count(tid) || pesStreams[tid].size() == 0){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::deque<Packet> & trackPackets = pesStreams[tid];
|
||||||
|
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Handle PAT packets
|
||||||
|
if (tid == 0){
|
||||||
|
///\todo Keep track of updates in PAT instead of keeping only the last PAT as a reference
|
||||||
|
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
|
associationTable = trackPackets.back();
|
||||||
|
lastPAT = Util::bootSecs();
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int pmtCount = associationTable.getProgramCount();
|
int pmtCount = associationTable.getProgramCount();
|
||||||
for (int i = 0; i < pmtCount; i++){
|
for (int i = 0; i < pmtCount; i++){
|
||||||
pmtTracks.insert(associationTable.getProgramPID(i));
|
pmtTracks.insert(associationTable.getProgramPID(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
|
pesStreams.erase(0);
|
||||||
|
pesPositions.erase(0);
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//If we are here, the packet is not a PAT.
|
|
||||||
//First check if it is listed in the PAT as a PMT track.
|
//Handle PMT packets
|
||||||
int pmtCount = associationTable.getProgramCount();
|
|
||||||
if (pmtTracks.count(tid)){
|
if (pmtTracks.count(tid)){
|
||||||
mappingTable[tid] = newPack;
|
///\todo Keep track of updates in PMT instead of keeping only the last PMT per program as a reference
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
|
mappingTable[tid] = trackPackets.back();
|
||||||
|
lastPMT[tid] = Util::bootSecs();
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
ProgramMappingEntry entry = mappingTable[tid].getEntry(0);
|
ProgramMappingEntry entry = mappingTable[tid].getEntry(0);
|
||||||
while (entry){
|
while (entry){
|
||||||
unsigned long pid = entry.getElementaryPid();
|
unsigned long pid = entry.getElementaryPid();
|
||||||
switch(entry.getStreamType()){
|
unsigned long sType = entry.getStreamType();
|
||||||
|
switch(sType){
|
||||||
case H264:
|
case H264:
|
||||||
case AAC:
|
case AAC:
|
||||||
case HEVC:
|
case HEVC:
|
||||||
case H265:
|
case H265:
|
||||||
case AC3:
|
case AC3:
|
||||||
if (!pidToCodec.count(pid)){
|
case ID3:
|
||||||
pidToCodec[pid] = entry.getStreamType();
|
pidToCodec[pid] = sType;
|
||||||
|
if (sType == ID3){
|
||||||
|
metaInit[pid] = std::string(entry.getESInfo(), entry.getESInfoLength());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -53,57 +153,107 @@ namespace TS {
|
||||||
}
|
}
|
||||||
entry.advance();
|
entry.advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
|
pesStreams.erase(tid);
|
||||||
|
pesPositions.erase(tid);
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//If it is not a PMT, check the list of all PMTs to see if this is a new PES track.
|
|
||||||
bool inPMT = false;
|
if (threaded){
|
||||||
for (std::map<unsigned long, ProgramMappingTable>::iterator it = mappingTable.begin(); it!= mappingTable.end(); it++){
|
globalSem.wait();
|
||||||
ProgramMappingEntry entry = it->second.getEntry(0);
|
|
||||||
while (entry){
|
|
||||||
if (tid == entry.getElementaryPid()){
|
|
||||||
inPMT = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
entry.advance();
|
|
||||||
|
bool parsePes = false;
|
||||||
|
|
||||||
|
int packNum = 1;
|
||||||
|
std::deque<Packet> & inStream = pesStreams[tid];
|
||||||
|
std::deque<Packet>::iterator curPack = inStream.begin();
|
||||||
|
curPack++;
|
||||||
|
while (curPack != inStream.end() && !curPack->getUnitStart()){
|
||||||
|
curPack++;
|
||||||
|
packNum++;
|
||||||
}
|
}
|
||||||
if (inPMT){
|
if (curPack != inStream.end()){
|
||||||
break;
|
parsePes = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
}
|
}
|
||||||
if (!inPMT){
|
|
||||||
HIGH_MSG("Encountered a packet on track %d, but the track is not registered in any PMT", tid);
|
if (parsePes){
|
||||||
return;
|
|
||||||
}
|
|
||||||
pesStreams[tid].push_back(newPack);
|
|
||||||
pesPositions[tid].push_back(bytePos);
|
|
||||||
if (!newPack.getUnitStart() || pesStreams[tid].size() == 1){
|
|
||||||
payloadSize[tid] += newPack.getPayloadLength();
|
|
||||||
}
|
|
||||||
parsePES(tid);
|
parsePES(tid);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::parse(Packet & newPack, unsigned long long bytePos) {
|
||||||
|
add(newPack, bytePos);
|
||||||
|
|
||||||
|
int tid = newPack.getPID();
|
||||||
|
parse(tid);
|
||||||
|
}
|
||||||
|
|
||||||
bool Stream::hasPacketOnEachTrack() const {
|
bool Stream::hasPacketOnEachTrack() const {
|
||||||
if (!pidToCodec.size()){
|
if (threaded){
|
||||||
return false;
|
globalSem.wait();
|
||||||
|
}
|
||||||
|
if (!pidToCodec.size() || pidToCodec.size() != outPackets.size()){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
}
|
}
|
||||||
if (outPackets.size() != pidToCodec.size()){
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (std::map<unsigned long, unsigned long>::const_iterator it = pidToCodec.begin(); it != pidToCodec.end(); it++){
|
for (std::map<unsigned long, unsigned long>::const_iterator it = pidToCodec.begin(); it != pidToCodec.end(); it++){
|
||||||
if (!outPackets.count(it->first) || !outPackets.at(it->first).size()){
|
if (!hasPacket(it->first)){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Stream::hasPacket(unsigned long tid) const {
|
bool Stream::hasPacket(unsigned long tid) const {
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
if (!pesStreams.count(tid)){
|
if (!pesStreams.count(tid)){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (outPackets.count(tid) && outPackets.at(tid).size()){
|
if (outPackets.count(tid) && outPackets.at(tid).size()){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
std::deque<Packet>::const_iterator curPack = pesStreams.at(tid).begin();
|
||||||
|
curPack++;
|
||||||
|
while (curPack != pesStreams.at(tid).end() && !curPack->getUnitStart()){
|
||||||
|
curPack++;
|
||||||
|
}
|
||||||
|
if (curPack != pesStreams.at(tid).end()){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,32 +269,63 @@ namespace TS {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stream::parsePES(unsigned long tid){
|
void Stream::parsePES(unsigned long tid){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
std::deque<Packet> & inStream = pesStreams[tid];
|
std::deque<Packet> & inStream = pesStreams[tid];
|
||||||
std::deque<unsigned long long> & inPositions = pesPositions[tid];
|
std::deque<unsigned long long> & inPositions = pesPositions[tid];
|
||||||
if (inStream.size() == 1){
|
if (inStream.size() == 1){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!inStream.back().getUnitStart()){
|
//Find number of packets before unit Start
|
||||||
|
int packNum = 1;
|
||||||
|
|
||||||
|
std::deque<Packet>::iterator curPack = inStream.begin();
|
||||||
|
curPack++;
|
||||||
|
while (curPack != inStream.end() && !curPack->getUnitStart()){
|
||||||
|
curPack++;
|
||||||
|
packNum++;
|
||||||
|
}
|
||||||
|
if (curPack == inStream.end()){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long long bPos = inPositions.front();
|
unsigned long long bPos = inPositions.front();
|
||||||
//Create a buffer for the current PES, and remove it from the pesStreams buffer.
|
//Create a buffer for the current PES, and remove it from the pesStreams buffer.
|
||||||
int paySize = payloadSize[tid];
|
int paySize = 0;
|
||||||
char * payload = (char*)malloc(paySize);
|
|
||||||
int offset = 0;
|
curPack = inStream.begin();
|
||||||
int packNum = inStream.size() - 1;
|
|
||||||
std::deque<Packet>::iterator curPack = inStream.begin();
|
|
||||||
for (int i = 0; i < packNum; i++){
|
for (int i = 0; i < packNum; i++){
|
||||||
memcpy(payload + offset, curPack->getPayload(), curPack->getPayloadLength());
|
paySize += curPack->getPayloadLength();
|
||||||
offset += curPack->getPayloadLength();
|
curPack++;
|
||||||
|
}
|
||||||
|
char * payload = (char*)malloc(paySize);
|
||||||
|
paySize = 0;
|
||||||
|
curPack = inStream.begin();
|
||||||
|
int lastCtr = curPack->getContinuityCounter() - 1;
|
||||||
|
for (int i = 0; i < packNum; i++){
|
||||||
|
if (curPack->getContinuityCounter() - lastCtr != 1 && curPack->getContinuityCounter()){
|
||||||
|
INFO_MSG("Parsing a pes on track %d, missed %d packets", tid, curPack->getContinuityCounter() - lastCtr - 1);
|
||||||
|
}
|
||||||
|
lastCtr = curPack->getContinuityCounter();
|
||||||
|
memcpy(payload + paySize, curPack->getPayload(), curPack->getPayloadLength());
|
||||||
|
paySize += curPack->getPayloadLength();
|
||||||
curPack++;
|
curPack++;
|
||||||
}
|
}
|
||||||
inStream.erase(inStream.begin(), curPack);
|
inStream.erase(inStream.begin(), curPack);
|
||||||
inPositions.erase(inPositions.begin(), inPositions.begin() + packNum);
|
inPositions.erase(inPositions.begin(), inPositions.begin() + packNum);
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
|
||||||
//Parse the PES header
|
//Parse the PES header
|
||||||
offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
while(offset < paySize){
|
while(offset < paySize){
|
||||||
const char * pesHeader = payload + offset;
|
const char * pesHeader = payload + offset;
|
||||||
|
@ -203,6 +384,9 @@ namespace TS {
|
||||||
//Parse all the ADTS packets
|
//Parse all the ADTS packets
|
||||||
unsigned long offsetInPes = 0;
|
unsigned long offsetInPes = 0;
|
||||||
unsigned long samplesRead = 0;
|
unsigned long samplesRead = 0;
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
while (offsetInPes < realPayloadSize){
|
while (offsetInPes < realPayloadSize){
|
||||||
outPackets[tid].push_back(DTSC::Packet());
|
outPackets[tid].push_back(DTSC::Packet());
|
||||||
aac::adts adtsPack(pesPayload + offsetInPes, realPayloadSize - offsetInPes);
|
aac::adts adtsPack(pesPayload + offsetInPes, realPayloadSize - offsetInPes);
|
||||||
|
@ -213,10 +397,19 @@ namespace TS {
|
||||||
samplesRead += adtsPack.getSampleCount();
|
samplesRead += adtsPack.getSampleCount();
|
||||||
offsetInPes += adtsPack.getHeaderSize() + adtsPack.getPayloadSize();
|
offsetInPes += adtsPack.getHeaderSize() + adtsPack.getPayloadSize();
|
||||||
}
|
}
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pidToCodec[tid] == ID3 || pidToCodec[tid] == AC3){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
}
|
}
|
||||||
if (pidToCodec[tid] == AC3){
|
|
||||||
outPackets[tid].push_back(DTSC::Packet());
|
outPackets[tid].push_back(DTSC::Packet());
|
||||||
outPackets[tid].back().genericFill(timeStamp, timeOffset, tid, pesPayload, realPayloadSize, bPos, 0);
|
outPackets[tid].back().genericFill(timeStamp, timeOffset, tid, pesPayload, realPayloadSize, bPos, 0);
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (pidToCodec[tid] == H264 || pidToCodec[tid] == HEVC || pidToCodec[tid] == H265){
|
if (pidToCodec[tid] == H264 || pidToCodec[tid] == HEVC || pidToCodec[tid] == H265){
|
||||||
//Convert from annex b
|
//Convert from annex b
|
||||||
|
@ -239,11 +432,23 @@ namespace TS {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x07: {
|
case 0x07: {
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
spsInfo[tid] = std::string(parsedData + dataOffset + 4, it->nalSize);
|
spsInfo[tid] = std::string(parsedData + dataOffset + 4, it->nalSize);
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x08: {
|
case 0x08: {
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
ppsInfo[tid] = std::string(parsedData + dataOffset + 4, it->nalSize);
|
ppsInfo[tid] = std::string(parsedData + dataOffset + 4, it->nalSize);
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: break;
|
default: break;
|
||||||
|
@ -264,7 +469,13 @@ namespace TS {
|
||||||
case 32:
|
case 32:
|
||||||
case 33:
|
case 33:
|
||||||
case 34: {
|
case 34: {
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
hevcInfo[tid].addUnit(parsedData + dataOffset);
|
hevcInfo[tid].addUnit(parsedData + dataOffset);
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: break;
|
default: break;
|
||||||
|
@ -272,8 +483,14 @@ namespace TS {
|
||||||
}
|
}
|
||||||
dataOffset += 4 + it->nalSize;
|
dataOffset += 4 + it->nalSize;
|
||||||
}
|
}
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
outPackets[tid].push_back(DTSC::Packet());
|
outPackets[tid].push_back(DTSC::Packet());
|
||||||
outPackets[tid].back().genericFill(timeStamp, timeOffset, tid, parsedData, parsedSize, bPos, isKeyFrame);
|
outPackets[tid].back().genericFill(timeStamp, timeOffset, tid, parsedData, parsedSize, bPos, isKeyFrame);
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
free(parsedData);
|
free(parsedData);
|
||||||
}
|
}
|
||||||
//We are done with the realpayload size, reverse calculation so we know the correct offset increase.
|
//We are done with the realpayload size, reverse calculation so we know the correct offset increase.
|
||||||
|
@ -285,7 +502,6 @@ namespace TS {
|
||||||
offset += realPayloadSize + 6;
|
offset += realPayloadSize + 6;
|
||||||
}
|
}
|
||||||
free(payload);
|
free(payload);
|
||||||
payloadSize[tid] = inStream.front().getPayloadLength();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stream::getPacket(unsigned long tid, DTSC::Packet & pack) {
|
void Stream::getPacket(unsigned long tid, DTSC::Packet & pack) {
|
||||||
|
@ -295,17 +511,55 @@ namespace TS {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
|
bool packetReady = outPackets.count(tid) && outPackets[tid].size();
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!packetReady){
|
||||||
|
parse(tid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
|
packetReady = outPackets.count(tid) && outPackets[tid].size();
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!packetReady){
|
||||||
|
ERROR_MSG("Obtaining a packet on track %lu failed", tid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
pack = outPackets[tid].front();
|
pack = outPackets[tid].front();
|
||||||
outPackets[tid].pop_front();
|
outPackets[tid].pop_front();
|
||||||
|
|
||||||
if (!outPackets[tid].size()){
|
if (!outPackets[tid].size()){
|
||||||
outPackets.erase(tid);
|
outPackets.erase(tid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stream::getEarliestPacket(DTSC::Packet & pack){
|
void Stream::getEarliestPacket(DTSC::Packet & pack){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
pack.null();
|
pack.null();
|
||||||
if (!hasPacketOnEachTrack()){
|
if (!hasPacketOnEachTrack()){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,12 +572,21 @@ namespace TS {
|
||||||
packTime = it->second.front().getTime();
|
packTime = it->second.front().getTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
|
||||||
getPacket(packTrack, pack);
|
getPacket(packTrack, pack);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stream::initializeMetadata(DTSC::Meta & meta) {
|
void Stream::initializeMetadata(DTSC::Meta & meta, unsigned long tid) {
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
for (std::map<unsigned long, unsigned long>::const_iterator it = pidToCodec.begin(); it != pidToCodec.end(); it++){
|
for (std::map<unsigned long, unsigned long>::const_iterator it = pidToCodec.begin(); it != pidToCodec.end(); it++){
|
||||||
|
if (tid && it->first != tid){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!meta.tracks.count(it->first) && it->second == H264){
|
if (!meta.tracks.count(it->first) && it->second == H264){
|
||||||
if (!spsInfo.count(it->first) || !ppsInfo.count(it->first)){
|
if (!spsInfo.count(it->first) || !ppsInfo.count(it->first)){
|
||||||
continue;
|
continue;
|
||||||
|
@ -357,6 +620,12 @@ namespace TS {
|
||||||
meta.tracks[it->first].trackID = it->first;
|
meta.tracks[it->first].trackID = it->first;
|
||||||
meta.tracks[it->first].init = hevcInfo[it->first].generateHVCC();
|
meta.tracks[it->first].init = hevcInfo[it->first].generateHVCC();
|
||||||
}
|
}
|
||||||
|
if (!meta.tracks.count(it->first) && it->second == ID3){
|
||||||
|
meta.tracks[it->first].type = "meta";
|
||||||
|
meta.tracks[it->first].codec = "ID3";
|
||||||
|
meta.tracks[it->first].trackID = it->first;
|
||||||
|
meta.tracks[it->first].init = metaInit[it->first];
|
||||||
|
}
|
||||||
if (!meta.tracks.count(it->first) && it->second == AC3){
|
if (!meta.tracks.count(it->first) && it->second == AC3){
|
||||||
meta.tracks[it->first].type = "audio";
|
meta.tracks[it->first].type = "audio";
|
||||||
meta.tracks[it->first].codec = "AC3";
|
meta.tracks[it->first].codec = "AC3";
|
||||||
|
@ -379,5 +648,63 @@ namespace TS {
|
||||||
meta.tracks[it->first].init = std::string(audioInit, 2);
|
meta.tracks[it->first].init = std::string(audioInit, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<unsigned long> Stream::getActiveTracks() {
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
|
std::set<unsigned long> result;
|
||||||
|
//Track 0 is always active
|
||||||
|
result.insert(0);
|
||||||
|
//IF PAT updated in the last 5 seconds, check for contents
|
||||||
|
if (Util::bootSecs() - lastPAT < 5){
|
||||||
|
int pmtCount = associationTable.getProgramCount();
|
||||||
|
//For each PMT
|
||||||
|
for (int i = 0; i < pmtCount; i++){
|
||||||
|
int pid = associationTable.getProgramPID(i);
|
||||||
|
//Add PMT track
|
||||||
|
result.insert(pid);
|
||||||
|
//IF PMT updated in last 5 seconds, check for contents
|
||||||
|
if (Util::bootSecs() - lastPMT[pid] < 5){
|
||||||
|
ProgramMappingEntry entry = mappingTable[pid].getEntry(0);
|
||||||
|
//Add all tracks in PMT
|
||||||
|
while (entry){
|
||||||
|
switch(entry.getStreamType()){
|
||||||
|
case H264:
|
||||||
|
case AAC:
|
||||||
|
case HEVC:
|
||||||
|
case H265:
|
||||||
|
case AC3:
|
||||||
|
case ID3:
|
||||||
|
result.insert(entry.getElementaryPid());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
entry.advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stream::eraseTrack(unsigned long tid){
|
||||||
|
if (threaded){
|
||||||
|
globalSem.wait();
|
||||||
|
}
|
||||||
|
pesStreams.erase(tid);
|
||||||
|
pesPositions.erase(tid);
|
||||||
|
outPackets.erase(tid);
|
||||||
|
if (threaded){
|
||||||
|
globalSem.post();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
#include "ts_packet.h"
|
#include "ts_packet.h"
|
||||||
#include "adts.h"
|
#include "adts.h"
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <set>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include "h265.h"
|
#include "h265.h"
|
||||||
|
|
||||||
|
#include "shared_memory.h"
|
||||||
|
|
||||||
namespace TS {
|
namespace TS {
|
||||||
enum codecType {
|
enum codecType {
|
||||||
H264 = 0x1B,
|
H264 = 0x1B,
|
||||||
|
@ -11,32 +14,47 @@ namespace TS {
|
||||||
AC3 = 0x81,
|
AC3 = 0x81,
|
||||||
MP3 = 0x03,
|
MP3 = 0x03,
|
||||||
HEVC = 0x06,
|
HEVC = 0x06,
|
||||||
H265 = 0x24
|
H265 = 0x24,
|
||||||
|
ID3 = 0x15
|
||||||
};
|
};
|
||||||
|
|
||||||
class Stream{
|
class Stream{
|
||||||
public:
|
public:
|
||||||
|
Stream(bool _threaded = false);
|
||||||
|
void add(char * newPack, unsigned long long bytePos = 0);
|
||||||
|
void add(Packet & newPack, unsigned long long bytePos = 0);
|
||||||
void parse(Packet & newPack, unsigned long long bytePos);
|
void parse(Packet & newPack, unsigned long long bytePos);
|
||||||
void parse(char * newPack, unsigned long long bytePos);
|
void parse(char * newPack, unsigned long long bytePos);
|
||||||
|
void parse(unsigned long tid);
|
||||||
bool hasPacketOnEachTrack() const;
|
bool hasPacketOnEachTrack() const;
|
||||||
bool hasPacket(unsigned long tid) const;
|
bool hasPacket(unsigned long tid) const;
|
||||||
void getPacket(unsigned long tid, DTSC::Packet & pack);
|
void getPacket(unsigned long tid, DTSC::Packet & pack);
|
||||||
void getEarliestPacket(DTSC::Packet & pack);
|
void getEarliestPacket(DTSC::Packet & pack);
|
||||||
void initializeMetadata(DTSC::Meta & meta);
|
void initializeMetadata(DTSC::Meta & meta, unsigned long tid = 0);
|
||||||
void clear();
|
void clear();
|
||||||
|
void eraseTrack(unsigned long tid);
|
||||||
|
bool isDataTrack(unsigned long tid);
|
||||||
|
std::set<unsigned long> getActiveTracks();
|
||||||
private:
|
private:
|
||||||
|
unsigned long long lastPAT;
|
||||||
ProgramAssociationTable associationTable;
|
ProgramAssociationTable associationTable;
|
||||||
|
|
||||||
|
std::map<unsigned long, unsigned long long> lastPMT;
|
||||||
std::map<unsigned long, ProgramMappingTable> mappingTable;
|
std::map<unsigned long, ProgramMappingTable> mappingTable;
|
||||||
|
|
||||||
std::map<unsigned long, std::deque<Packet> > pesStreams;
|
std::map<unsigned long, std::deque<Packet> > pesStreams;
|
||||||
std::map<unsigned long, std::deque<unsigned long long> > pesPositions;
|
std::map<unsigned long, std::deque<unsigned long long> > pesPositions;
|
||||||
std::map<unsigned long, unsigned long> payloadSize;
|
|
||||||
std::map<unsigned long, std::deque<DTSC::Packet> > outPackets;
|
std::map<unsigned long, std::deque<DTSC::Packet> > outPackets;
|
||||||
std::map<unsigned long, unsigned long> pidToCodec;
|
std::map<unsigned long, unsigned long> pidToCodec;
|
||||||
std::map<unsigned long, aac::adts > adtsInfo;
|
std::map<unsigned long, aac::adts > adtsInfo;
|
||||||
std::map<unsigned long, std::string > spsInfo;
|
std::map<unsigned long, std::string > spsInfo;
|
||||||
std::map<unsigned long, std::string > ppsInfo;
|
std::map<unsigned long, std::string > ppsInfo;
|
||||||
std::map<unsigned long, h265::initData > hevcInfo;
|
std::map<unsigned long, h265::initData > hevcInfo;
|
||||||
|
std::map<unsigned long, std::string> metaInit;
|
||||||
|
|
||||||
|
mutable IPC::semaphore globalSem;
|
||||||
|
|
||||||
|
bool threaded;
|
||||||
|
|
||||||
std::set<unsigned long> pmtTracks;
|
std::set<unsigned long> pmtTracks;
|
||||||
|
|
||||||
|
|
|
@ -1,101 +0,0 @@
|
||||||
macro(makeAnalyser analyserName format)
|
|
||||||
add_executable( MistAnalyser${analyserName} analysers/${format}_analyser.cpp )
|
|
||||||
target_link_libraries( MistAnalyser${analyserName} mist )
|
|
||||||
endmacro()
|
|
||||||
|
|
||||||
macro(makeInput inputName format)
|
|
||||||
add_executable( MistIn${inputName} input/mist_in.cpp input/input.cpp input/input_${format}.cpp )
|
|
||||||
set_target_properties( MistIn${inputName} PROPERTIES COMPILE_DEFINITIONS INPUTTYPE=\"input_${format}.h\")
|
|
||||||
target_link_libraries( MistIn${inputName} mist )
|
|
||||||
endmacro()
|
|
||||||
|
|
||||||
macro(makeOutput outputName format)
|
|
||||||
#check if 'http' is one of the argyments, if yes, this is an http output
|
|
||||||
if (";${ARGN};" MATCHES ";http;")
|
|
||||||
SET(httpOutput output/output_http.cpp)
|
|
||||||
if (";${ARGN};" MATCHES ";ts;")
|
|
||||||
SET(tsBaseClass HTTPOutput)
|
|
||||||
else()
|
|
||||||
SET(tsBaseClass Output)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
if (";${ARGN};" MATCHES ";ts;")
|
|
||||||
SET(tsOutput output/output_ts_base.cpp)
|
|
||||||
endif()
|
|
||||||
add_executable( MistOut${outputName} output/mist_out.cpp output/output.cpp ${httpOutput} ${tsOutput} output/output_${format}.cpp )
|
|
||||||
set_target_properties( MistOut${outputName} PROPERTIES COMPILE_DEFINITIONS "OUTPUTTYPE=\"output_${format}.h\";TS_BASECLASS=${tsBaseClass}")
|
|
||||||
target_link_libraries( MistOut${outputName} mist )
|
|
||||||
endmacro()
|
|
||||||
|
|
||||||
makeAnalyser(RTMP rtmp)
|
|
||||||
makeAnalyser(FLV flv)
|
|
||||||
makeAnalyser(DTSC dtsc)
|
|
||||||
makeAnalyser(AMF amf)
|
|
||||||
makeAnalyser(MP4 mp4)
|
|
||||||
makeAnalyser(OGG ogg)
|
|
||||||
|
|
||||||
makeInput(DTSC dtsc)
|
|
||||||
makeInput(MP3 mp3)
|
|
||||||
makeInput(FLV flv)
|
|
||||||
makeInput(OGG ogg)
|
|
||||||
makeInput(Buffer buffer)
|
|
||||||
|
|
||||||
makeOutput(RTMP rtmp)
|
|
||||||
makeOutput(OGG progressive_ogg http)
|
|
||||||
makeOutput(FLV progressive_flv http)
|
|
||||||
makeOutput(MP4 progressive_mp4 http)
|
|
||||||
makeOutput(MP3 progressive_mp3 http)
|
|
||||||
makeOutput(HSS hss http)
|
|
||||||
makeOutput(HDS hds http)
|
|
||||||
makeOutput(SRT srt http)
|
|
||||||
makeOutput(JSON json http)
|
|
||||||
makeOutput(TS ts ts)
|
|
||||||
makeOutput(HTTPTS httpts http ts)
|
|
||||||
makeOutput(HLS hls http ts)
|
|
||||||
|
|
||||||
#get the bitlength of this system
|
|
||||||
execute_process(COMMAND getconf LONG_BIT OUTPUT_VARIABLE RELEASE_RAW )
|
|
||||||
#strip off the trailing spaces and newline
|
|
||||||
string(STRIP ${RELEASE_RAW} RELEASE)
|
|
||||||
set(RELEASE \"${RELEASE}\" )
|
|
||||||
|
|
||||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
|
||||||
add_executable( sourcery sourcery.cpp )
|
|
||||||
|
|
||||||
add_custom_target( embedcode
|
|
||||||
ALL
|
|
||||||
./sourcery ${CMAKE_CURRENT_SOURCE_DIR}/embed.js embed_js ${CMAKE_CURRENT_BINARY_DIR}/embed.js.h
|
|
||||||
DEPENDS sourcery ${CMAKE_CURRENT_SOURCE_DIR}/embed.js
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
|
|
||||||
add_custom_target( localSettingsPage
|
|
||||||
ALL
|
|
||||||
./sourcery ${BINARY_DIR}/lsp/server.html server_html ${CMAKE_CURRENT_BINARY_DIR}/server.html.h
|
|
||||||
DEPENDS sourcery lsp
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
|
|
||||||
add_executable( MistOutHTTP output/mist_out.cpp output/output.cpp output/output_http.cpp output/output_http_internal.cpp)
|
|
||||||
set_target_properties( MistOutHTTP PROPERTIES COMPILE_DEFINITIONS "OUTPUTTYPE=\"output_http_internal.h\"")
|
|
||||||
add_dependencies(MistOutHTTP embedcode)
|
|
||||||
target_link_libraries( MistOutHTTP mist )
|
|
||||||
|
|
||||||
add_executable( MistController
|
|
||||||
controller/controller.cpp
|
|
||||||
controller/controller_api.h
|
|
||||||
controller/controller_api.cpp
|
|
||||||
controller/controller_capabilities.h
|
|
||||||
controller/controller_capabilities.cpp
|
|
||||||
controller/controller_connectors.h
|
|
||||||
controller/controller_connectors.cpp
|
|
||||||
controller/controller_statistics.h
|
|
||||||
controller/controller_statistics.cpp
|
|
||||||
controller/controller_storage.h
|
|
||||||
controller/controller_storage.cpp
|
|
||||||
controller/controller_streams.h
|
|
||||||
controller/controller_streams.cpp
|
|
||||||
)
|
|
||||||
set_target_properties( MistController PROPERTIES COMPILE_DEFINITIONS RELEASE=${RELEASE})
|
|
||||||
target_link_libraries( MistController mist )
|
|
||||||
add_dependencies(MistController localSettingsPage)
|
|
|
@ -64,6 +64,7 @@ namespace Controller {
|
||||||
std::string udpPort = data["udpport"].asString();
|
std::string udpPort = data["udpport"].asString();
|
||||||
//Check running
|
//Check running
|
||||||
if (!inputProcesses.count(name) || !Util::Procs::isRunning(inputProcesses[name])){
|
if (!inputProcesses.count(name) || !Util::Procs::isRunning(inputProcesses[name])){
|
||||||
|
std::string multicast = data["multicastinterface"].asString();
|
||||||
// False: start TS input
|
// False: start TS input
|
||||||
INFO_MSG("No TS Input running on port %s for stream %s, starting it", udpPort.c_str(), name.c_str());
|
INFO_MSG("No TS Input running on port %s for stream %s, starting it", udpPort.c_str(), name.c_str());
|
||||||
std::deque<std::string> command;
|
std::deque<std::string> command;
|
||||||
|
@ -72,6 +73,8 @@ namespace Controller {
|
||||||
command.push_back(name);
|
command.push_back(name);
|
||||||
command.push_back("-p");
|
command.push_back("-p");
|
||||||
command.push_back(udpPort);
|
command.push_back(udpPort);
|
||||||
|
command.push_back("-M");
|
||||||
|
command.push_back(multicast);
|
||||||
command.push_back(URL);
|
command.push_back(URL);
|
||||||
int stdIn = 0;
|
int stdIn = 0;
|
||||||
int stdOut = 1;
|
int stdOut = 1;
|
||||||
|
|
|
@ -29,11 +29,7 @@ namespace Mist {
|
||||||
|
|
||||||
Input::Input(Util::Config * cfg) : InOutBase() {
|
Input::Input(Util::Config * cfg) : InOutBase() {
|
||||||
config = cfg;
|
config = cfg;
|
||||||
#ifdef INPUT_LIVE
|
|
||||||
standAlone = false;
|
|
||||||
#else
|
|
||||||
standAlone = true;
|
standAlone = true;
|
||||||
#endif
|
|
||||||
|
|
||||||
JSON::Value option;
|
JSON::Value option;
|
||||||
option["long"] = "json";
|
option["long"] = "json";
|
||||||
|
@ -90,7 +86,7 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Input::checkHeaderTimes(std::string streamFile) {
|
void Input::checkHeaderTimes(std::string streamFile) {
|
||||||
if (streamFile == "-") {
|
if (streamFile == "-" || streamFile == "push://") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string headerFile = streamFile + ".dtsh";
|
std::string headerFile = streamFile + ".dtsh";
|
||||||
|
@ -124,6 +120,7 @@ namespace Mist {
|
||||||
config->getOption("streamname") = streamName;
|
config->getOption("streamname") = streamName;
|
||||||
}
|
}
|
||||||
streamName = config->getString("streamname");
|
streamName = config->getString("streamname");
|
||||||
|
nProxy.streamName = streamName;
|
||||||
if (config->getBool("json")) {
|
if (config->getBool("json")) {
|
||||||
std::cout << capa.toString() << std::endl;
|
std::cout << capa.toString() << std::endl;
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -132,26 +129,19 @@ namespace Mist {
|
||||||
std::cerr << config->getString("cmd") << " setup failed." << std::endl;
|
std::cerr << config->getString("cmd") << " setup failed." << std::endl;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
//Do not read the header if this is a live stream
|
|
||||||
#ifndef INPUT_LIVE
|
|
||||||
checkHeaderTimes(config->getString("input"));
|
checkHeaderTimes(config->getString("input"));
|
||||||
if (!readHeader()) {
|
if (!readHeader()) {
|
||||||
std::cerr << "Reading header for " << config->getString("input") << " failed." << std::endl;
|
std::cerr << "Reading header for " << config->getString("input") << " failed." << std::endl;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
parseHeader();
|
parseHeader();
|
||||||
#endif
|
|
||||||
|
|
||||||
//Live inputs only have a serve() mode
|
if (!streamName.size()) {
|
||||||
#ifndef INPUT_LIVE
|
|
||||||
if (!config->getString("streamname").size()) {
|
|
||||||
convert();
|
convert();
|
||||||
} else {
|
} else {
|
||||||
#endif
|
|
||||||
serve();
|
serve();
|
||||||
#ifndef INPUT_LIVE
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,44 +193,6 @@ namespace Mist {
|
||||||
void Input::serve(){
|
void Input::serve(){
|
||||||
char userPageName[NAME_BUFFER_SIZE];
|
char userPageName[NAME_BUFFER_SIZE];
|
||||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||||
#ifdef INPUT_LIVE
|
|
||||||
unsigned int giveUpCounter = 0;
|
|
||||||
while (!Util::startInput(streamName) && config->is_active && ++giveUpCounter < 20) {
|
|
||||||
Util::sleep(500);
|
|
||||||
}
|
|
||||||
if (giveUpCounter >= 20){
|
|
||||||
FAIL_MSG("Could not start buffer for stream '%s', aborting stream input!", streamName.c_str());
|
|
||||||
config->is_active = false;
|
|
||||||
}
|
|
||||||
userClient = IPC::sharedClient(userPageName, 30, true);
|
|
||||||
getNext();
|
|
||||||
while (thisPacket || config->is_active) {
|
|
||||||
unsigned long tid = thisPacket.getTrackId();
|
|
||||||
//Check for eligibility of track
|
|
||||||
IPC::userConnection userConn(userClient.getData());
|
|
||||||
if (trackOffset.count(tid) && !userConn.getTrackId(trackOffset[tid])) {
|
|
||||||
trackOffset.erase(tid);
|
|
||||||
trackState.erase(tid);
|
|
||||||
trackMap.erase(tid);
|
|
||||||
trackBuffer.erase(tid);
|
|
||||||
pagesByTrack.erase(tid);
|
|
||||||
metaPages.erase(tid);
|
|
||||||
curPageNum.erase(tid);
|
|
||||||
curPage.erase(tid);
|
|
||||||
INFO_MSG("Erasing track %d", tid);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (thisPacket) {
|
|
||||||
continueNegotiate(thisPacket.getTrackId());
|
|
||||||
bufferLivePacket(thisPacket);
|
|
||||||
} else {
|
|
||||||
Util::sleep(100);
|
|
||||||
}
|
|
||||||
getNext();
|
|
||||||
userClient.keepAlive();
|
|
||||||
}
|
|
||||||
userClient.finish();
|
|
||||||
#else
|
|
||||||
/*LTS-START*/
|
/*LTS-START*/
|
||||||
if(Triggers::shouldTrigger("STREAM_READY", config->getString("streamname"))){
|
if(Triggers::shouldTrigger("STREAM_READY", config->getString("streamname"))){
|
||||||
std::string payload = config->getString("streamname")+"\n" +capa["name"].asStringRef()+"\n";
|
std::string payload = config->getString("streamname")+"\n" +capa["name"].asStringRef()+"\n";
|
||||||
|
@ -283,7 +235,6 @@ namespace Mist {
|
||||||
Util::sleep(1000);
|
Util::sleep(1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
finish();
|
finish();
|
||||||
DEBUG_MSG(DLVL_DEVEL, "Input for stream %s closing clean", streamName.c_str());
|
DEBUG_MSG(DLVL_DEVEL, "Input for stream %s closing clean", streamName.c_str());
|
||||||
//end player functionality
|
//end player functionality
|
||||||
|
@ -297,7 +248,7 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
removeUnused();
|
removeUnused();
|
||||||
if (standAlone) {
|
if (standAlone) {
|
||||||
for (std::map<unsigned long, IPC::sharedPage>::iterator it = metaPages.begin(); it != metaPages.end(); it++) {
|
for (std::map<unsigned long, IPC::sharedPage>::iterator it = nProxy.metaPages.begin(); it != nProxy.metaPages.end(); it++) {
|
||||||
it->second.master = true;
|
it->second.master = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,9 +267,9 @@ namespace Mist {
|
||||||
bufferRemove(it->first, it2->first);
|
bufferRemove(it->first, it2->first);
|
||||||
pageCounter[it->first].erase(it2->first);
|
pageCounter[it->first].erase(it2->first);
|
||||||
for (int i = 0; i < 8192; i += 8) {
|
for (int i = 0; i < 8192; i += 8) {
|
||||||
unsigned int thisKeyNum = ntohl(((((long long int *)(metaPages[it->first].mapped + i))[0]) >> 32) & 0xFFFFFFFF);
|
unsigned int thisKeyNum = ntohl(((((long long int *)(nProxy.metaPages[it->first].mapped + i))[0]) >> 32) & 0xFFFFFFFF);
|
||||||
if (thisKeyNum == it2->first) {
|
if (thisKeyNum == it2->first) {
|
||||||
(((long long int *)(metaPages[it->first].mapped + i))[0]) = 0;
|
(((long long int *)(nProxy.metaPages[it->first].mapped + i))[0]) = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
change = true;
|
change = true;
|
||||||
|
@ -359,13 +310,13 @@ namespace Mist {
|
||||||
for (int i = 0; i < it->second.keys.size(); i++) {
|
for (int i = 0; i < it->second.keys.size(); i++) {
|
||||||
if (newData) {
|
if (newData) {
|
||||||
//i+1 because keys are 1-indexed
|
//i+1 because keys are 1-indexed
|
||||||
pagesByTrack[it->first][i + 1].firstTime = it->second.keys[i].getTime();
|
nProxy.pagesByTrack[it->first][i + 1].firstTime = it->second.keys[i].getTime();
|
||||||
newData = false;
|
newData = false;
|
||||||
}
|
}
|
||||||
pagesByTrack[it->first].rbegin()->second.keyNum++;
|
nProxy.pagesByTrack[it->first].rbegin()->second.keyNum++;
|
||||||
pagesByTrack[it->first].rbegin()->second.partNum += it->second.keys[i].getParts();
|
nProxy.pagesByTrack[it->first].rbegin()->second.partNum += it->second.keys[i].getParts();
|
||||||
pagesByTrack[it->first].rbegin()->second.dataSize += it->second.keySizes[i];
|
nProxy.pagesByTrack[it->first].rbegin()->second.dataSize += it->second.keySizes[i];
|
||||||
if (pagesByTrack[it->first].rbegin()->second.dataSize > FLIP_DATA_PAGE_SIZE) {
|
if (nProxy.pagesByTrack[it->first].rbegin()->second.dataSize > FLIP_DATA_PAGE_SIZE) {
|
||||||
newData = true;
|
newData = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -398,7 +349,7 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
if (myMeta.tracks[tid].keys[bookKeeping[tid].curKey].getParts() + 1 == curData[tid].partNum) {
|
if (myMeta.tracks[tid].keys[bookKeeping[tid].curKey].getParts() + 1 == curData[tid].partNum) {
|
||||||
if (curData[tid].dataSize > FLIP_DATA_PAGE_SIZE) {
|
if (curData[tid].dataSize > FLIP_DATA_PAGE_SIZE) {
|
||||||
pagesByTrack[tid][bookKeeping[tid].first] = curData[tid];
|
nProxy.pagesByTrack[tid][bookKeeping[tid].first] = curData[tid];
|
||||||
bookKeeping[tid].first += curData[tid].keyNum;
|
bookKeeping[tid].first += curData[tid].keyNum;
|
||||||
curData[tid].keyNum = 0;
|
curData[tid].keyNum = 0;
|
||||||
curData[tid].dataSize = 0;
|
curData[tid].dataSize = 0;
|
||||||
|
@ -415,17 +366,17 @@ namespace Mist {
|
||||||
getNext(false);
|
getNext(false);
|
||||||
}
|
}
|
||||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||||
if (curData.count(it->first) && !pagesByTrack[it->first].count(bookKeeping[it->first].first)) {
|
if (curData.count(it->first) && !nProxy.pagesByTrack[it->first].count(bookKeeping[it->first].first)) {
|
||||||
pagesByTrack[it->first][bookKeeping[it->first].first] = curData[it->first];
|
nProxy.pagesByTrack[it->first][bookKeeping[it->first].first] = curData[it->first];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||||
if (!pagesByTrack.count(it->first)) {
|
if (!nProxy.pagesByTrack.count(it->first)) {
|
||||||
DEBUG_MSG(DLVL_WARN, "No pages for track %d found", it->first);
|
DEBUG_MSG(DLVL_WARN, "No pages for track %d found", it->first);
|
||||||
} else {
|
} else {
|
||||||
DEBUG_MSG(DLVL_MEDIUM, "Track %d (%s) split into %lu pages", it->first, myMeta.tracks[it->first].codec.c_str(), pagesByTrack[it->first].size());
|
DEBUG_MSG(DLVL_MEDIUM, "Track %d (%s) split into %lu pages", it->first, myMeta.tracks[it->first].codec.c_str(), nProxy.pagesByTrack[it->first].size());
|
||||||
for (std::map<unsigned long, DTSCPageData>::iterator it2 = pagesByTrack[it->first].begin(); it2 != pagesByTrack[it->first].end(); it2++) {
|
for (std::map<unsigned long, DTSCPageData>::iterator it2 = nProxy.pagesByTrack[it->first].begin(); it2 != nProxy.pagesByTrack[it->first].end(); it2++) {
|
||||||
DEBUG_MSG(DLVL_VERYHIGH, "Page %lu-%lu, (%llu bytes)", it2->first, it2->first + it2->second.keyNum - 1, it2->second.dataSize);
|
DEBUG_MSG(DLVL_VERYHIGH, "Page %lu-%lu, (%llu bytes)", it2->first, it2->first + it2->second.keyNum - 1, it2->second.dataSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -443,10 +394,10 @@ namespace Mist {
|
||||||
if (keyNum < 1) {
|
if (keyNum < 1) {
|
||||||
keyNum = 1;
|
keyNum = 1;
|
||||||
}
|
}
|
||||||
if (isBuffered(track, keyNum)) {
|
if (nProxy.isBuffered(track, keyNum)) {
|
||||||
//get corresponding page number
|
//get corresponding page number
|
||||||
int pageNumber = 0;
|
int pageNumber = 0;
|
||||||
for (std::map<unsigned long, DTSCPageData>::iterator it = pagesByTrack[track].begin(); it != pagesByTrack[track].end(); it++) {
|
for (std::map<unsigned long, DTSCPageData>::iterator it = nProxy.pagesByTrack[track].begin(); it != nProxy.pagesByTrack[track].end(); it++) {
|
||||||
if (it->first <= keyNum) {
|
if (it->first <= keyNum) {
|
||||||
pageNumber = it->first;
|
pageNumber = it->first;
|
||||||
} else {
|
} else {
|
||||||
|
@ -457,13 +408,13 @@ namespace Mist {
|
||||||
VERYHIGH_MSG("Track %u, key %u is already buffered in page %d. Cancelling bufferFrame", track, keyNum, pageNumber);
|
VERYHIGH_MSG("Track %u, key %u is already buffered in page %d. Cancelling bufferFrame", track, keyNum, pageNumber);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!pagesByTrack.count(track)) {
|
if (!nProxy.pagesByTrack.count(track)) {
|
||||||
WARN_MSG("No pages for track %u found! Cancelling bufferFrame", track);
|
WARN_MSG("No pages for track %u found! Cancelling bufferFrame", track);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//Update keynum to point to the corresponding page
|
//Update keynum to point to the corresponding page
|
||||||
INFO_MSG("Loading key %u from page %lu", keyNum, (--(pagesByTrack[track].upper_bound(keyNum)))->first);
|
INFO_MSG("Loading key %u from page %lu", keyNum, (--(nProxy.pagesByTrack[track].upper_bound(keyNum)))->first);
|
||||||
keyNum = (--(pagesByTrack[track].upper_bound(keyNum)))->first;
|
keyNum = (--(nProxy.pagesByTrack[track].upper_bound(keyNum)))->first;
|
||||||
if (!bufferStart(track, keyNum)) {
|
if (!bufferStart(track, keyNum)) {
|
||||||
WARN_MSG("bufferStart failed! Cancelling bufferFrame");
|
WARN_MSG("bufferStart failed! Cancelling bufferFrame");
|
||||||
return false;
|
return false;
|
||||||
|
@ -474,8 +425,8 @@ namespace Mist {
|
||||||
trackSelect(trackSpec.str());
|
trackSelect(trackSpec.str());
|
||||||
seek(myMeta.tracks[track].keys[keyNum - 1].getTime());
|
seek(myMeta.tracks[track].keys[keyNum - 1].getTime());
|
||||||
long long unsigned int stopTime = myMeta.tracks[track].lastms + 1;
|
long long unsigned int stopTime = myMeta.tracks[track].lastms + 1;
|
||||||
if ((int)myMeta.tracks[track].keys.size() > keyNum - 1 + pagesByTrack[track][keyNum].keyNum) {
|
if ((int)myMeta.tracks[track].keys.size() > keyNum - 1 + nProxy.pagesByTrack[track][keyNum].keyNum) {
|
||||||
stopTime = myMeta.tracks[track].keys[keyNum - 1 + pagesByTrack[track][keyNum].keyNum].getTime();
|
stopTime = myMeta.tracks[track].keys[keyNum - 1 + nProxy.pagesByTrack[track][keyNum].keyNum].getTime();
|
||||||
}
|
}
|
||||||
DEBUG_MSG(DLVL_HIGH, "Playing from %llu to %llu", myMeta.tracks[track].keys[keyNum - 1].getTime(), stopTime);
|
DEBUG_MSG(DLVL_HIGH, "Playing from %llu to %llu", myMeta.tracks[track].keys[keyNum - 1].getTime(), stopTime);
|
||||||
getNext();
|
getNext();
|
||||||
|
|
|
@ -89,17 +89,34 @@ namespace Mist {
|
||||||
capa["optional"]["segmentsize"]["type"] = "uint";
|
capa["optional"]["segmentsize"]["type"] = "uint";
|
||||||
capa["optional"]["segmentsize"]["default"] = 5000LL;
|
capa["optional"]["segmentsize"]["default"] = 5000LL;
|
||||||
option.null();
|
option.null();
|
||||||
option["arg"] = "integer";
|
|
||||||
|
option["arg"] = "string";
|
||||||
option["long"] = "udp-port";
|
option["long"] = "udp-port";
|
||||||
option["short"] = "U";
|
option["short"] = "U";
|
||||||
option["help"] = "The UDP port on which to listen for TS Packets";
|
option["help"] = "The UDP port on which to listen for TS Packets";
|
||||||
option["value"].append(0LL);
|
option["value"].append("");
|
||||||
config->addOption("udpport", option);
|
config->addOption("udpport", option);
|
||||||
capa["optional"]["udpport"]["name"] = "TS/UDP port";
|
capa["optional"]["udpport"]["name"] = "TS/UDP port";
|
||||||
capa["optional"]["udpport"]["help"] = "The UDP port on which to listen for TS Packets, or 0 for disabling TS Input";
|
capa["optional"]["udpport"]["help"] = "The UDP port on which to listen for TS Packets, or 0 for disabling TS Input, optionally prefixed with the interface IP to listen on.";
|
||||||
capa["optional"]["udpport"]["option"] = "--udp-port";
|
capa["optional"]["udpport"]["option"] = "--udp-port";
|
||||||
capa["optional"]["udpport"]["type"] = "uint";
|
capa["optional"]["udpport"]["type"] = "str";
|
||||||
capa["optional"]["udpport"]["default"] = 0LL;
|
capa["optional"]["udpport"]["default"] = "";
|
||||||
|
option.null();
|
||||||
|
|
||||||
|
option["arg"] = "string";
|
||||||
|
option["long"] = "multicast-interface";
|
||||||
|
option["short"] = "M";
|
||||||
|
option["help"] = "The interface(s)s on which to listen for UDP Multicast packets, space separated.";
|
||||||
|
option["value"].append("");
|
||||||
|
config->addOption("multicastinterface", option);
|
||||||
|
capa["optional"]["multicastinterface"]["name"] = "TS Multicast interface";
|
||||||
|
capa["optional"]["multicastinterface"]["help"] = "The interface(s) on which to listen for UDP Multicast packets, comma separated.";
|
||||||
|
capa["optional"]["multicastinterface"]["option"] = "--multicast-interface";
|
||||||
|
capa["optional"]["multicastinterface"]["type"] = "str";
|
||||||
|
capa["optional"]["multicastinterface"]["default"] = "";
|
||||||
|
option.null();
|
||||||
|
|
||||||
|
|
||||||
/*LTS-end*/
|
/*LTS-end*/
|
||||||
capa["source_match"] = "push://*";
|
capa["source_match"] = "push://*";
|
||||||
capa["priority"] = 9ll;
|
capa["priority"] = 9ll;
|
||||||
|
@ -130,12 +147,12 @@ namespace Mist {
|
||||||
DEBUG_MSG(DLVL_DEVEL, "Cleaning up, removing last keyframes");
|
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++){
|
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||||
std::map<unsigned long, DTSCPageData> & locations = bufferLocations[it->first];
|
std::map<unsigned long, DTSCPageData> & locations = bufferLocations[it->first];
|
||||||
if (!metaPages.count(it->first) || !metaPages[it->first].mapped){
|
if (!nProxy.metaPages.count(it->first) || !nProxy.metaPages[it->first].mapped){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
//First detect all entries on metaPage
|
//First detect all entries on metaPage
|
||||||
for (int i = 0; i < 8192; i += 8){
|
for (int i = 0; i < 8192; i += 8){
|
||||||
int * tmpOffset = (int *)(metaPages[it->first].mapped + i);
|
int * tmpOffset = (int *)(nProxy.metaPages[it->first].mapped + i);
|
||||||
if (tmpOffset[0] == 0 && tmpOffset[1] == 0){
|
if (tmpOffset[0] == 0 && tmpOffset[1] == 0){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -206,14 +223,14 @@ namespace Mist {
|
||||||
snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
|
snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
|
||||||
IPC::semaphore liveMeta(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
IPC::semaphore liveMeta(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||||
liveMeta.wait();
|
liveMeta.wait();
|
||||||
if (!metaPages.count(0) || !metaPages[0].mapped){
|
if (!nProxy.metaPages.count(0) || !nProxy.metaPages[0].mapped){
|
||||||
char pageName[NAME_BUFFER_SIZE];
|
char pageName[NAME_BUFFER_SIZE];
|
||||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||||
metaPages[0].init(pageName, DEFAULT_META_PAGE_SIZE, true);
|
nProxy.metaPages[0].init(pageName, DEFAULT_META_PAGE_SIZE, true);
|
||||||
metaPages[0].master = false;
|
nProxy.metaPages[0].master = false;
|
||||||
}
|
}
|
||||||
myMeta.writeTo(metaPages[0].mapped);
|
myMeta.writeTo(nProxy.metaPages[0].mapped);
|
||||||
memset(metaPages[0].mapped + myMeta.getSendLen(), 0, (metaPages[0].len > myMeta.getSendLen() ? std::min(metaPages[0].len - myMeta.getSendLen(), 4ll) : 0));
|
memset(nProxy.metaPages[0].mapped + myMeta.getSendLen(), 0, (nProxy.metaPages[0].len > myMeta.getSendLen() ? std::min(nProxy.metaPages[0].len - myMeta.getSendLen(), 4ll) : 0));
|
||||||
liveMeta.post();
|
liveMeta.post();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,15 +313,15 @@ namespace Mist {
|
||||||
if (myMeta.tracks[tid].keys[0].getNumber() >= (++(bufferLocations[tid].begin()))->first || !config->is_active){
|
if (myMeta.tracks[tid].keys[0].getNumber() >= (++(bufferLocations[tid].begin()))->first || !config->is_active){
|
||||||
//Find page in indexpage and null it
|
//Find page in indexpage and null it
|
||||||
for (int i = 0; i < 8192; i += 8){
|
for (int i = 0; i < 8192; i += 8){
|
||||||
unsigned int thisKeyNum = ((((long long int *)(metaPages[tid].mapped + i))[0]) >> 32) & 0xFFFFFFFF;
|
unsigned int thisKeyNum = ((((long long int *)(nProxy.metaPages[tid].mapped + i))[0]) >> 32) & 0xFFFFFFFF;
|
||||||
if (thisKeyNum == htonl(bufferLocations[tid].begin()->first) && ((((long long int *)(metaPages[tid].mapped + i))[0]) != 0)){
|
if (thisKeyNum == htonl(bufferLocations[tid].begin()->first) && ((((long long int *)(nProxy.metaPages[tid].mapped + i))[0]) != 0)){
|
||||||
(((long long int *)(metaPages[tid].mapped + i))[0]) = 0;
|
(((long long int *)(nProxy.metaPages[tid].mapped + i))[0]) = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DEBUG_MSG(DLVL_DEVEL, "Erasing track %d, keys %lu-%lu from buffer", tid, bufferLocations[tid].begin()->first, bufferLocations[tid].begin()->first + bufferLocations[tid].begin()->second.keyNum - 1);
|
DEBUG_MSG(DLVL_DEVEL, "Erasing track %d, keys %lu-%lu from buffer", tid, bufferLocations[tid].begin()->first, bufferLocations[tid].begin()->first + bufferLocations[tid].begin()->second.keyNum - 1);
|
||||||
bufferRemove(tid, bufferLocations[tid].begin()->first);
|
bufferRemove(tid, bufferLocations[tid].begin()->first);
|
||||||
for (int i = 0; i < 1024; i++){
|
for (int i = 0; i < 1024; i++){
|
||||||
int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
|
int * tmpOffset = (int *)(nProxy.metaPages[tid].mapped + (i * 8));
|
||||||
int tmpNum = ntohl(tmpOffset[0]);
|
int tmpNum = ntohl(tmpOffset[0]);
|
||||||
if (tmpNum == bufferLocations[tid].begin()->first){
|
if (tmpNum == bufferLocations[tid].begin()->first){
|
||||||
tmpOffset[0] = 0;
|
tmpOffset[0] = 0;
|
||||||
|
@ -312,12 +329,12 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
curPageNum.erase(tid);
|
nProxy.curPageNum.erase(tid);
|
||||||
char thisPageName[NAME_BUFFER_SIZE];
|
char thisPageName[NAME_BUFFER_SIZE];
|
||||||
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), (unsigned long)tid, bufferLocations[tid].begin()->first);
|
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), (unsigned long)tid, bufferLocations[tid].begin()->first);
|
||||||
curPage[tid].init(thisPageName, 20971520);
|
nProxy.curPage[tid].init(thisPageName, 20971520);
|
||||||
curPage[tid].master = true;
|
nProxy.curPage[tid].master = true;
|
||||||
curPage.erase(tid);
|
nProxy.curPage.erase(tid);
|
||||||
|
|
||||||
bufferLocations[tid].erase(bufferLocations[tid].begin());
|
bufferLocations[tid].erase(bufferLocations[tid].begin());
|
||||||
} else {
|
} else {
|
||||||
|
@ -334,13 +351,13 @@ namespace Mist {
|
||||||
for (std::map<unsigned long, DTSCPageData>::iterator it = bufferLocations[tid].begin(); it != bufferLocations[tid].end(); it++){
|
for (std::map<unsigned long, DTSCPageData>::iterator it = bufferLocations[tid].begin(); it != bufferLocations[tid].end(); it++){
|
||||||
char thisPageName[NAME_BUFFER_SIZE];
|
char thisPageName[NAME_BUFFER_SIZE];
|
||||||
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), tid, it->first);
|
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), tid, it->first);
|
||||||
curPage[tid].init(thisPageName, 20971520, false, false);
|
nProxy.curPage[tid].init(thisPageName, 20971520, false, false);
|
||||||
curPage[tid].master = true;
|
nProxy.curPage[tid].master = true;
|
||||||
curPage.erase(tid);
|
nProxy.curPage.erase(tid);
|
||||||
}
|
}
|
||||||
bufferLocations.erase(tid);
|
bufferLocations.erase(tid);
|
||||||
metaPages[tid].master = true;
|
nProxy.metaPages[tid].master = true;
|
||||||
metaPages.erase(tid);
|
nProxy.metaPages.erase(tid);
|
||||||
}
|
}
|
||||||
|
|
||||||
void inputBuffer::finish() {
|
void inputBuffer::finish() {
|
||||||
|
@ -410,9 +427,9 @@ namespace Mist {
|
||||||
while (bufferLocations[tid].size()){
|
while (bufferLocations[tid].size()){
|
||||||
char thisPageName[NAME_BUFFER_SIZE];
|
char thisPageName[NAME_BUFFER_SIZE];
|
||||||
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), (unsigned long)tid, bufferLocations[tid].begin()->first);
|
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), (unsigned long)tid, bufferLocations[tid].begin()->first);
|
||||||
curPage[tid].init(thisPageName, 20971520);
|
nProxy.curPage[tid].init(thisPageName, 20971520);
|
||||||
curPage[tid].master = true;
|
nProxy.curPage[tid].master = true;
|
||||||
curPage.erase(tid);
|
nProxy.curPage.erase(tid);
|
||||||
bufferLocations[tid].erase(bufferLocations[tid].begin());
|
bufferLocations[tid].erase(bufferLocations[tid].begin());
|
||||||
}
|
}
|
||||||
if (pushLocation.count(it->first)){
|
if (pushLocation.count(it->first)){
|
||||||
|
@ -427,9 +444,9 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
pushLocation.erase(it->first);
|
pushLocation.erase(it->first);
|
||||||
}
|
}
|
||||||
curPageNum.erase(it->first);
|
nProxy.curPageNum.erase(it->first);
|
||||||
metaPages[it->first].master = true;
|
nProxy.metaPages[it->first].master = true;
|
||||||
metaPages.erase(it->first);
|
nProxy.metaPages.erase(it->first);
|
||||||
activeTracks.erase(it->first);
|
activeTracks.erase(it->first);
|
||||||
myMeta.tracks.erase(it->first);
|
myMeta.tracks.erase(it->first);
|
||||||
changed = true;
|
changed = true;
|
||||||
|
@ -522,8 +539,8 @@ namespace Mist {
|
||||||
activeTracks.erase(value);
|
activeTracks.erase(value);
|
||||||
bufferLocations.erase(value);
|
bufferLocations.erase(value);
|
||||||
}
|
}
|
||||||
metaPages[value].master = true;
|
nProxy.metaPages[value].master = true;
|
||||||
metaPages.erase(value);
|
nProxy.metaPages.erase(value);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -546,13 +563,13 @@ namespace Mist {
|
||||||
//The track id is set to the value of a track that we are currently negotiating about
|
//The track id is set to the value of a track that we are currently negotiating about
|
||||||
if (negotiatingTracks.count(value)){
|
if (negotiatingTracks.count(value)){
|
||||||
//If the metadata page for this track is not yet registered, initialize it
|
//If the metadata page for this track is not yet registered, initialize it
|
||||||
if (!metaPages.count(value) || !metaPages[value].mapped){
|
if (!nProxy.metaPages.count(value) || !nProxy.metaPages[value].mapped){
|
||||||
char tempMetaName[NAME_BUFFER_SIZE];
|
char tempMetaName[NAME_BUFFER_SIZE];
|
||||||
snprintf(tempMetaName, NAME_BUFFER_SIZE, SHM_TRACK_META, config->getString("streamname").c_str(), value);
|
snprintf(tempMetaName, NAME_BUFFER_SIZE, SHM_TRACK_META, config->getString("streamname").c_str(), value);
|
||||||
metaPages[value].init(tempMetaName, 8388608, false, false);
|
nProxy.metaPages[value].init(tempMetaName, 8388608, false, false);
|
||||||
}
|
}
|
||||||
//If this tracks metdata page is not initialize, skip the entire element for now. It will be instantiated later
|
//If this tracks metdata page is not initialize, skip the entire element for now. It will be instantiated later
|
||||||
if (!metaPages[value].mapped) {
|
if (!nProxy.metaPages[value].mapped) {
|
||||||
//remove the negotiation if it has timed out
|
//remove the negotiation if it has timed out
|
||||||
if (++negotiationTimeout[value] >= 1000){
|
if (++negotiationTimeout[value] >= 1000){
|
||||||
negotiatingTracks.erase(value);
|
negotiatingTracks.erase(value);
|
||||||
|
@ -564,13 +581,13 @@ namespace Mist {
|
||||||
//The page exist, now we try to read in the metadata of the track
|
//The page exist, now we try to read in the metadata of the track
|
||||||
|
|
||||||
//Store the size of the dtsc packet to read.
|
//Store the size of the dtsc packet to read.
|
||||||
unsigned int len = ntohl(((int *)metaPages[value].mapped)[1]);
|
unsigned int len = ntohl(((int *)nProxy.metaPages[value].mapped)[1]);
|
||||||
//Temporary variable, won't be used again
|
//Temporary variable, won't be used again
|
||||||
unsigned int tempForReadingMeta = 0;
|
unsigned int tempForReadingMeta = 0;
|
||||||
//Read in the metadata through a temporary JSON object
|
//Read in the metadata through a temporary JSON object
|
||||||
///\todo Optimize this part. Find a way to not have to store the metadata in JSON first, but read it from the page immediately
|
///\todo Optimize this part. Find a way to not have to store the metadata in JSON first, but read it from the page immediately
|
||||||
JSON::Value tempJSONForMeta;
|
JSON::Value tempJSONForMeta;
|
||||||
JSON::fromDTMI((const unsigned char *)metaPages[value].mapped + 8, len, tempForReadingMeta, tempJSONForMeta);
|
JSON::fromDTMI((const unsigned char *)nProxy.metaPages[value].mapped + 8, len, tempForReadingMeta, tempJSONForMeta);
|
||||||
//Construct a metadata object for the current track
|
//Construct a metadata object for the current track
|
||||||
DTSC::Meta trackMeta(tempJSONForMeta);
|
DTSC::Meta trackMeta(tempJSONForMeta);
|
||||||
//If the track metadata does not contain the negotiated track, assume the metadata is currently being written, and skip the element for now. It will be instantiated in the next call.
|
//If the track metadata does not contain the negotiated track, assume the metadata is currently being written, and skip the element for now. It will be instantiated in the next call.
|
||||||
|
@ -579,8 +596,8 @@ namespace Mist {
|
||||||
if (++negotiationTimeout[value] >= 1000){
|
if (++negotiationTimeout[value] >= 1000){
|
||||||
negotiatingTracks.erase(value);
|
negotiatingTracks.erase(value);
|
||||||
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
||||||
metaPages[value].master = true;
|
nProxy.metaPages[value].master = true;
|
||||||
metaPages.erase(value);
|
nProxy.metaPages.erase(value);
|
||||||
negotiationTimeout.erase(value);
|
negotiationTimeout.erase(value);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
@ -603,8 +620,8 @@ namespace Mist {
|
||||||
//Remove the "negotiate" status in either case
|
//Remove the "negotiate" status in either case
|
||||||
negotiatingTracks.erase(value);
|
negotiatingTracks.erase(value);
|
||||||
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
||||||
metaPages[value].master = true;
|
nProxy.metaPages[value].master = true;
|
||||||
metaPages.erase(value);
|
nProxy.metaPages.erase(value);
|
||||||
|
|
||||||
//Check if the track collides, and whether the track it collides with is active.
|
//Check if the track collides, and whether the track it collides with is active.
|
||||||
if (collidesWith != -1 && activeTracks.count(collidesWith)){/*LTS*/
|
if (collidesWith != -1 && activeTracks.count(collidesWith)){/*LTS*/
|
||||||
|
@ -639,8 +656,8 @@ namespace Mist {
|
||||||
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
||||||
updateMeta();
|
updateMeta();
|
||||||
eraseTrackDataPages(value);
|
eraseTrackDataPages(value);
|
||||||
metaPages[finalMap].master = true;
|
nProxy.metaPages[finalMap].master = true;
|
||||||
metaPages.erase(finalMap);
|
nProxy.metaPages.erase(finalMap);
|
||||||
bufferLocations.erase(finalMap);
|
bufferLocations.erase(finalMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -666,12 +683,12 @@ namespace Mist {
|
||||||
//If the track is active, and this is the element responsible for pushing it
|
//If the track is active, and this is the element responsible for pushing it
|
||||||
if (activeTracks.count(value) && pushLocation[value] == data){
|
if (activeTracks.count(value) && pushLocation[value] == data){
|
||||||
//Open the track index page if we dont have it open yet
|
//Open the track index page if we dont have it open yet
|
||||||
if (!metaPages.count(value) || !metaPages[value].mapped){
|
if (!nProxy.metaPages.count(value) || !nProxy.metaPages[value].mapped){
|
||||||
char firstPage[NAME_BUFFER_SIZE];
|
char firstPage[NAME_BUFFER_SIZE];
|
||||||
snprintf(firstPage, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, config->getString("streamname").c_str(), value);
|
snprintf(firstPage, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, config->getString("streamname").c_str(), value);
|
||||||
metaPages[value].init(firstPage, 8192, false, false);
|
nProxy.metaPages[value].init(firstPage, 8192, false, false);
|
||||||
}
|
}
|
||||||
if (metaPages[value].mapped){
|
if (nProxy.metaPages[value].mapped){
|
||||||
//Update the metadata for this track
|
//Update the metadata for this track
|
||||||
updateTrackMeta(value);
|
updateTrackMeta(value);
|
||||||
hasPush = true;
|
hasPush = true;
|
||||||
|
@ -684,7 +701,7 @@ namespace Mist {
|
||||||
VERYHIGH_MSG("Updating meta for track %d", tNum);
|
VERYHIGH_MSG("Updating meta for track %d", tNum);
|
||||||
//Store a reference for easier access
|
//Store a reference for easier access
|
||||||
std::map<unsigned long, DTSCPageData> & locations = bufferLocations[tNum];
|
std::map<unsigned long, DTSCPageData> & locations = bufferLocations[tNum];
|
||||||
char * mappedPointer = metaPages[tNum].mapped;
|
char * mappedPointer = nProxy.metaPages[tNum].mapped;
|
||||||
|
|
||||||
//First detect all entries on metaPage
|
//First detect all entries on metaPage
|
||||||
for (int i = 0; i < 8192; i += 8) {
|
for (int i = 0; i < 8192; i += 8) {
|
||||||
|
@ -725,27 +742,27 @@ namespace Mist {
|
||||||
//Otherwise open and parse the page
|
//Otherwise open and parse the page
|
||||||
|
|
||||||
//Open the page if it is not yet open
|
//Open the page if it is not yet open
|
||||||
if (!curPageNum.count(tNum) || curPageNum[tNum] != pageNum || !curPage[tNum].mapped){
|
if (!nProxy.curPageNum.count(tNum) || nProxy.curPageNum[tNum] != pageNum || !nProxy.curPage[tNum].mapped){
|
||||||
//DO NOT ERASE THE PAGE HERE, master is not set to true
|
//DO NOT ERASE THE PAGE HERE, master is not set to true
|
||||||
curPageNum.erase(tNum);
|
nProxy.curPageNum.erase(tNum);
|
||||||
char nextPageName[NAME_BUFFER_SIZE];
|
char nextPageName[NAME_BUFFER_SIZE];
|
||||||
snprintf(nextPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), tNum, pageNum);
|
snprintf(nextPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), tNum, pageNum);
|
||||||
curPage[tNum].init(nextPageName, 20971520);
|
nProxy.curPage[tNum].init(nextPageName, 20971520);
|
||||||
//If the page can not be opened, stop here
|
//If the page can not be opened, stop here
|
||||||
if (!curPage[tNum].mapped){
|
if (!nProxy.curPage[tNum].mapped){
|
||||||
WARN_MSG("Could not open page: %s", nextPageName);
|
WARN_MSG("Could not open page: %s", nextPageName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
curPageNum[tNum] = pageNum;
|
nProxy.curPageNum[tNum] = pageNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DTSC::Packet tmpPack;
|
DTSC::Packet tmpPack;
|
||||||
if (!curPage[tNum].mapped[pageData.curOffset]){
|
if (!nProxy.curPage[tNum].mapped[pageData.curOffset]){
|
||||||
VERYHIGH_MSG("No packet on page %lu for track %lu, waiting...", pageNum, tNum);
|
VERYHIGH_MSG("No packet on page %lu for track %lu, waiting...", pageNum, tNum);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tmpPack.reInit(curPage[tNum].mapped + pageData.curOffset, 0);
|
tmpPack.reInit(nProxy.curPage[tNum].mapped + pageData.curOffset, 0);
|
||||||
//No new data has been written on the page since last update
|
//No new data has been written on the page since last update
|
||||||
if (!tmpPack){
|
if (!tmpPack){
|
||||||
return;
|
return;
|
||||||
|
@ -761,7 +778,7 @@ namespace Mist {
|
||||||
//Update the offset on the page with the size of the current packet
|
//Update the offset on the page with the size of the current packet
|
||||||
pageData.curOffset += tmpPack.getDataLen();
|
pageData.curOffset += tmpPack.getDataLen();
|
||||||
//Attempt to read in the next packet
|
//Attempt to read in the next packet
|
||||||
tmpPack.reInit(curPage[tNum].mapped + pageData.curOffset, 0);
|
tmpPack.reInit(nProxy.curPage[tNum].mapped + pageData.curOffset, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,16 @@
|
||||||
#include <mist/flv_tag.h>
|
#include <mist/flv_tag.h>
|
||||||
#include <mist/defines.h>
|
#include <mist/defines.h>
|
||||||
#include <mist/ts_packet.h>
|
#include <mist/ts_packet.h>
|
||||||
|
#include <mist/timing.h>
|
||||||
#include <mist/mp4_generic.h>
|
#include <mist/mp4_generic.h>
|
||||||
#include "input_ts.h"
|
#include "input_ts.h"
|
||||||
|
|
||||||
|
#include <mist/tinythread.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// \todo Implement this trigger equivalent...
|
/// \todo Implement this trigger equivalent...
|
||||||
/*
|
/*
|
||||||
if(Triggers::shouldTrigger("STREAM_PUSH", smp)){
|
if(Triggers::shouldTrigger("STREAM_PUSH", smp)){
|
||||||
|
@ -27,10 +34,82 @@ if(Triggers::shouldTrigger("STREAM_PUSH", smp)){
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#ifdef TSLIVE_INPUT
|
||||||
|
std::string globalStreamName;
|
||||||
|
TS::Stream liveStream(true);
|
||||||
|
Util::Config * cfgPointer = NULL;
|
||||||
|
|
||||||
|
#define THREAD_TIMEOUT 15
|
||||||
|
std::map<unsigned long long, unsigned long long> threadTimer;
|
||||||
|
|
||||||
|
std::set<unsigned long> claimableThreads;
|
||||||
|
|
||||||
|
void parseThread(void * ignored) {
|
||||||
|
|
||||||
|
std::string semName = "MstInTSStreamClaim" + globalStreamName;
|
||||||
|
IPC::semaphore lock(semName.c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||||
|
|
||||||
|
int tid = -1;
|
||||||
|
lock.wait();
|
||||||
|
if (claimableThreads.size()) {
|
||||||
|
tid = *claimableThreads.begin();
|
||||||
|
claimableThreads.erase(claimableThreads.begin());
|
||||||
|
}
|
||||||
|
lock.post();
|
||||||
|
if (tid == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (liveStream.isDataTrack(tid)){
|
||||||
|
if (!Util::startInput(globalStreamName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Mist::negotiationProxy myProxy;
|
||||||
|
myProxy.streamName = globalStreamName;
|
||||||
|
DTSC::Meta myMeta;
|
||||||
|
|
||||||
|
if (liveStream.isDataTrack(tid)){
|
||||||
|
char userPageName[NAME_BUFFER_SIZE];
|
||||||
|
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, globalStreamName.c_str());
|
||||||
|
myProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
threadTimer[tid] = Util::bootSecs();
|
||||||
|
while (Util::bootSecs() - threadTimer[tid] < THREAD_TIMEOUT && cfgPointer->is_active) {
|
||||||
|
liveStream.parse(tid);
|
||||||
|
if (liveStream.hasPacket(tid)){
|
||||||
|
liveStream.initializeMetadata(myMeta, tid);
|
||||||
|
DTSC::Packet pack;
|
||||||
|
liveStream.getPacket(tid, pack);
|
||||||
|
if (pack && myMeta.tracks.count(tid)){
|
||||||
|
myProxy.bufferLivePacket(pack, myMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
lock.wait();
|
||||||
|
threadTimer[tid] = Util::bootSecs();
|
||||||
|
lock.post();
|
||||||
|
}
|
||||||
|
if (liveStream.isDataTrack(tid)){
|
||||||
|
myProxy.userClient.keepAlive();
|
||||||
|
}
|
||||||
|
if (!liveStream.hasPacket(tid)){
|
||||||
|
Util::sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lock.wait();
|
||||||
|
threadTimer.erase(tid);
|
||||||
|
lock.post();
|
||||||
|
liveStream.eraseTrack(tid);
|
||||||
|
myProxy.userClient.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Mist {
|
namespace Mist {
|
||||||
|
|
||||||
|
|
||||||
/// Constructor of TS Input
|
/// Constructor of TS Input
|
||||||
/// \arg cfg Util::Config that contains all current configurations.
|
/// \arg cfg Util::Config that contains all current configurations.
|
||||||
inputTS::inputTS(Util::Config * cfg) : Input(cfg) {
|
inputTS::inputTS(Util::Config * cfg) : Input(cfg) {
|
||||||
|
@ -44,44 +123,80 @@ namespace Mist {
|
||||||
capa["codecs"][0u][1u].append("AC3");
|
capa["codecs"][0u][1u].append("AC3");
|
||||||
|
|
||||||
capa["optional"]["port"]["name"] = "UDP Port";
|
capa["optional"]["port"]["name"] = "UDP Port";
|
||||||
capa["optional"]["port"]["help"] = "The udp port on which to listen for incoming UDP Packets";
|
capa["optional"]["port"]["help"] = "The UDP port on which to listen for incoming UDP Packets, optionally prefixed by the interface IP.";
|
||||||
capa["optional"]["port"]["type"] = "uint";
|
capa["optional"]["port"]["type"] = "string";
|
||||||
capa["optional"]["port"]["default"] = 9876;
|
capa["optional"]["port"]["default"] = "9876";
|
||||||
capa["optional"]["port"]["option"] = "--port";
|
capa["optional"]["port"]["option"] = "--port";
|
||||||
cfg->addOption("port",
|
cfg->addOption("port",
|
||||||
JSON::fromString("{\"arg\":\"integer\",\"value\":9876,\"short\":\"p\",\"long\":\"port\",\"help\":\"The udp port on which to listen for incoming UDP Packets.\"}"));
|
JSON::fromString("{\"arg\":\"string\",\"value\":9876,\"short\":\"p\",\"long\":\"port\",\"help\":\"The UDP port on which to listen for incoming UDP Packets, optionally prefixed by the interface IP.\"}"));
|
||||||
|
|
||||||
|
capa["optional"]["multicastinterface"]["name"] = "TS Multicast interface";
|
||||||
|
capa["optional"]["multicastinterface"]["help"] = "The interface(s) on which to listen for UDP Multicast packets, comma separated.";
|
||||||
|
capa["optional"]["multicastinterface"]["option"] = "--multicast-interface";
|
||||||
|
capa["optional"]["multicastinterface"]["type"] = "str";
|
||||||
|
capa["optional"]["multicastinterface"]["default"] = "";
|
||||||
|
cfg->addOption("multicastinterface",
|
||||||
|
JSON::fromString("{\"arg\":\"string\",\"value\":\"\",\"short\":\"M\",\"long\":\"multicast-interface\",\"help\":\"The interfaces on which to listen for UDP Multicast packets, space separatered.\"}"));
|
||||||
|
|
||||||
pushing = false;
|
pushing = false;
|
||||||
inFile = NULL;
|
inFile = NULL;
|
||||||
|
|
||||||
|
#ifdef TSLIVE_INPUT
|
||||||
|
standAlone = false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
inputTS::~inputTS() {
|
inputTS::~inputTS() {
|
||||||
if (inFile){
|
if (inFile) {
|
||||||
fclose(inFile);
|
fclose(inFile);
|
||||||
}
|
}
|
||||||
|
#ifdef TSLIVE_INPUT
|
||||||
|
std::string semName = "MstInTSStreamClaim" + globalStreamName;
|
||||||
|
IPC::semaphore lock(semName.c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||||
|
lock.wait();
|
||||||
|
threadTimer.clear();
|
||||||
|
claimableThreads.clear();
|
||||||
|
lock.post();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TSLIVE_INPUT
|
||||||
|
|
||||||
|
///Live Setup of TS Input
|
||||||
|
bool inputTS::setup() {
|
||||||
|
INFO_MSG("Setup start");
|
||||||
|
if (config->getString("input") == "-") {
|
||||||
|
inFile = stdin;
|
||||||
|
} else {
|
||||||
|
pushing = true;
|
||||||
|
udpCon.setBlocking(false);
|
||||||
|
std::string ipPort = config->getString("port");
|
||||||
|
size_t colon = ipPort.rfind(':');
|
||||||
|
if (colon != std::string::npos) {
|
||||||
|
udpCon.bind(JSON::Value(ipPort.substr(colon + 1)).asInt(), ipPort.substr(0, colon), config->getString("multicastinterface"));
|
||||||
|
} else {
|
||||||
|
udpCon.bind(JSON::Value(ipPort).asInt(), "", config->getString("multicastinterface"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
INFO_MSG("Setup complete");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
///Setup of TS Input
|
///Setup of TS Input
|
||||||
bool inputTS::setup() {
|
bool inputTS::setup() {
|
||||||
#ifdef INPUT_LIVE
|
if (config->getString("input") != "-") {
|
||||||
if (config->getString("input") == "-") {
|
|
||||||
inFile = stdin;
|
|
||||||
}else{
|
|
||||||
pushing = true;
|
|
||||||
udpCon.setBlocking(false);
|
|
||||||
udpCon.bind(config->getInteger("port"));
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (config->getString("input") != "-"){
|
|
||||||
inFile = fopen(config->getString("input").c_str(), "r");
|
inFile = fopen(config->getString("input").c_str(), "r");
|
||||||
}
|
}
|
||||||
if (!inFile) {
|
if (!inFile) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
///Track selector of TS Input
|
///Track selector of TS Input
|
||||||
///\arg trackSpec specifies which tracks are to be selected
|
///\arg trackSpec specifies which tracks are to be selected
|
||||||
///\todo test whether selecting a subset of tracks work
|
///\todo test whether selecting a subset of tracks work
|
||||||
|
@ -100,18 +215,25 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef TSLIVE_INPUT
|
||||||
|
//This implementation in used in the live version of TS input, where no header is available in advance.
|
||||||
|
//Reading the header returns true in this case, to continue parsing the actual stream.
|
||||||
|
bool inputTS::readHeader() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#else
|
||||||
///Reads headers from a TS stream, and saves them into metadata
|
///Reads headers from a TS stream, and saves them into metadata
|
||||||
///It works by going through the entire TS stream, and every time
|
///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
|
///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,
|
///for a specific track to metadata. After the entire stream has been read,
|
||||||
///it writes the remaining metadata.
|
///it writes the remaining metadata.
|
||||||
///\todo Find errors, perhaps parts can be made more modular
|
///\todo Find errors, perhaps parts can be made more modular
|
||||||
bool inputTS::readHeader(){
|
bool inputTS::readHeader() {
|
||||||
if (!inFile) {
|
if (!inFile) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
DTSC::File tmp(config->getString("input") + ".dtsh");
|
DTSC::File tmp(config->getString("input") + ".dtsh");
|
||||||
if (tmp){
|
if (tmp) {
|
||||||
myMeta = tmp.getMeta();
|
myMeta = tmp.getMeta();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -121,11 +243,11 @@ namespace Mist {
|
||||||
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
long long int lastBpos = 0;
|
long long int lastBpos = 0;
|
||||||
while (packet.FromFile(inFile) && !feof(inFile)){
|
while (packet.FromFile(inFile) && !feof(inFile)) {
|
||||||
tsStream.parse(packet, lastBpos);
|
tsStream.parse(packet, lastBpos);
|
||||||
lastBpos = ftell(inFile);
|
lastBpos = ftell(inFile);
|
||||||
while(tsStream.hasPacketOnEachTrack()){
|
while (tsStream.hasPacketOnEachTrack()) {
|
||||||
if (first){
|
if (first) {
|
||||||
tsStream.initializeMetadata(myMeta);
|
tsStream.initializeMetadata(myMeta);
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
@ -142,75 +264,70 @@ namespace Mist {
|
||||||
oFile.close();
|
oFile.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
///Gets the next packet that is to be sent
|
///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,
|
///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.
|
///but the seeking and finding data is not yet ready.
|
||||||
///\todo Finish the implementation
|
///\todo Finish the implementation
|
||||||
void inputTS::getNext(bool smart){
|
void inputTS::getNext(bool smart) {
|
||||||
|
INSANE_MSG("Getting next");
|
||||||
thisPacket.null();
|
thisPacket.null();
|
||||||
bool hasPacket = (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) : tsStream.hasPacketOnEachTrack());
|
bool hasPacket = (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) : tsStream.hasPacketOnEachTrack());
|
||||||
while (!hasPacket && (pushing || !feof(inFile)) && config->is_active){
|
while (!hasPacket && (pushing || !feof(inFile)) && config->is_active) {
|
||||||
if (!pushing) {
|
if (!pushing) {
|
||||||
unsigned int bPos = ftell(inFile);
|
unsigned int bPos = ftell(inFile);
|
||||||
tsBuf.FromFile(inFile);
|
tsBuf.FromFile(inFile);
|
||||||
if (selectedTracks.count(tsBuf.getPID())){
|
if (selectedTracks.count(tsBuf.getPID())) {
|
||||||
tsStream.parse(tsBuf, bPos);
|
tsStream.parse(tsBuf, bPos);
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
while (udpCon.Receive()){
|
while (udpCon.Receive()) {
|
||||||
udpDataBuffer.append(udpCon.data, udpCon.data_len);
|
udpDataBuffer.append(udpCon.data, udpCon.data_len);
|
||||||
while (udpDataBuffer.size() > 188 && (udpDataBuffer[0] != 0x47 || udpDataBuffer[188] != 0x47)){
|
while (udpDataBuffer.size() > 188 && (udpDataBuffer[0] != 0x47 || udpDataBuffer[188] != 0x47)) {
|
||||||
size_t syncPos = udpDataBuffer.find("\107", 1);
|
size_t syncPos = udpDataBuffer.find("\107", 1);
|
||||||
udpDataBuffer.erase(0, syncPos);
|
udpDataBuffer.erase(0, syncPos);
|
||||||
}
|
}
|
||||||
while (udpDataBuffer.size() >= 188){
|
while (udpDataBuffer.size() >= 188) {
|
||||||
tsBuf.FromPointer(udpDataBuffer.data());
|
tsBuf.FromPointer(udpDataBuffer.data());
|
||||||
tsStream.parse(tsBuf, 0);
|
tsStream.parse(tsBuf, 0);
|
||||||
udpDataBuffer.erase(0,188);
|
udpDataBuffer.erase(0, 188);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (userClient.getData()){
|
|
||||||
userClient.keepAlive();
|
|
||||||
}
|
|
||||||
Util::sleep(500);
|
Util::sleep(500);
|
||||||
}
|
}
|
||||||
if (userClient.getData()){
|
|
||||||
userClient.keepAlive();
|
|
||||||
}
|
|
||||||
hasPacket = (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) : tsStream.hasPacketOnEachTrack());
|
hasPacket = (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) : tsStream.hasPacketOnEachTrack());
|
||||||
}
|
}
|
||||||
if (!hasPacket){
|
if (!hasPacket) {
|
||||||
if(inFile && !feof(inFile)){
|
if (inFile && !feof(inFile)) {
|
||||||
getNext();
|
getNext();
|
||||||
}
|
}
|
||||||
if (pushing){
|
if (pushing) {
|
||||||
sleep(500);
|
sleep(500);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (selectedTracks.size() == 1){
|
if (selectedTracks.size() == 1) {
|
||||||
tsStream.getPacket(*selectedTracks.begin(), thisPacket);
|
tsStream.getPacket(*selectedTracks.begin(), thisPacket);
|
||||||
}else{
|
} else {
|
||||||
tsStream.getEarliestPacket(thisPacket);
|
tsStream.getEarliestPacket(thisPacket);
|
||||||
}
|
}
|
||||||
tsStream.initializeMetadata(myMeta);
|
tsStream.initializeMetadata(myMeta);
|
||||||
if (!myMeta.tracks.count(thisPacket.getTrackId())){
|
if (!myMeta.tracks.count(thisPacket.getTrackId())) {
|
||||||
getNext();
|
getNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void inputTS::readPMT(){
|
void inputTS::readPMT() {
|
||||||
//save current file position
|
//save current file position
|
||||||
int bpos = ftell(inFile);
|
int bpos = ftell(inFile);
|
||||||
if (fseek(inFile, 0, SEEK_SET)){
|
if (fseek(inFile, 0, SEEK_SET)) {
|
||||||
FAIL_MSG("Seek to 0 failed");
|
FAIL_MSG("Seek to 0 failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TS::Packet tsBuffer;
|
TS::Packet tsBuffer;
|
||||||
while (!tsStream.hasPacketOnEachTrack() && tsBuffer.FromFile(inFile)){
|
while (!tsStream.hasPacketOnEachTrack() && tsBuffer.FromFile(inFile)) {
|
||||||
tsStream.parse(tsBuffer, 0);
|
tsStream.parse(tsBuffer, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,29 +336,104 @@ namespace Mist {
|
||||||
|
|
||||||
|
|
||||||
//Restore original file position
|
//Restore original file position
|
||||||
if (fseek(inFile, bpos, SEEK_SET)){
|
if (fseek(inFile, bpos, SEEK_SET)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///Seeks to a specific time
|
///Seeks to a specific time
|
||||||
void inputTS::seek(int seekTime){
|
void inputTS::seek(int seekTime) {
|
||||||
tsStream.clear();
|
tsStream.clear();
|
||||||
readPMT();
|
readPMT();
|
||||||
unsigned long seekPos = 0xFFFFFFFFull;
|
unsigned long seekPos = 0xFFFFFFFFull;
|
||||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) {
|
||||||
unsigned long thisBPos = 0;
|
unsigned long thisBPos = 0;
|
||||||
for (std::deque<DTSC::Key>::iterator keyIt = myMeta.tracks[*it].keys.begin(); keyIt != myMeta.tracks[*it].keys.end(); keyIt++){
|
for (std::deque<DTSC::Key>::iterator keyIt = myMeta.tracks[*it].keys.begin(); keyIt != myMeta.tracks[*it].keys.end(); keyIt++) {
|
||||||
if (keyIt->getTime() > seekTime){
|
if (keyIt->getTime() > seekTime) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
thisBPos = keyIt->getBpos();
|
thisBPos = keyIt->getBpos();
|
||||||
}
|
}
|
||||||
if (thisBPos < seekPos){
|
if (thisBPos < seekPos) {
|
||||||
seekPos = thisBPos;
|
seekPos = thisBPos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fseek(inFile, seekPos, SEEK_SET);//seek to the correct position
|
fseek(inFile, seekPos, SEEK_SET);//seek to the correct position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TSLIVE_INPUT
|
||||||
|
void inputTS::serve() {
|
||||||
|
cfgPointer = config;
|
||||||
|
globalStreamName = streamName;
|
||||||
|
unsigned long long threadCheckTimer = Util::bootSecs();
|
||||||
|
while (config->is_active) {
|
||||||
|
if (!pushing) {
|
||||||
|
unsigned int bPos = ftell(inFile);
|
||||||
|
int ctr = 0;
|
||||||
|
while (ctr < 20 && tsBuf.FromFile(inFile)){
|
||||||
|
liveStream.add(tsBuf);
|
||||||
|
ctr++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while (udpCon.Receive()) {
|
||||||
|
int offset = 0;
|
||||||
|
//Try to read full TS Packets
|
||||||
|
//Assumption here is made that one UDP Datagram consists of complete TS Packets.
|
||||||
|
//Assumption made because of possible packet loss issues
|
||||||
|
while ((udpCon.data_len - offset) >= 188) {
|
||||||
|
//Watch out! We push here to a global, in order for threads to be able to access it.
|
||||||
|
liveStream.add(udpCon.data + offset);
|
||||||
|
offset += 188;
|
||||||
|
}
|
||||||
|
if (offset < udpCon.data_len) {
|
||||||
|
WARN_MSG("%d bytes left in datagram", udpCon.data_len - offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Check for and spawn threads here.
|
||||||
|
if (Util::bootSecs() - threadCheckTimer > 2) {
|
||||||
|
std::set<unsigned long> activeTracks = liveStream.getActiveTracks();
|
||||||
|
std::string semName = "MstInTSStreamClaim" + globalStreamName;
|
||||||
|
IPC::semaphore lock(semName.c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||||
|
lock.wait();
|
||||||
|
for (std::set<unsigned long>::iterator it = activeTracks.begin(); it != activeTracks.end(); it++) {
|
||||||
|
if (threadTimer.count(*it) && ((Util::bootSecs() - threadTimer[*it]) > (2 * THREAD_TIMEOUT))) {
|
||||||
|
WARN_MSG("Thread for track %d timed out %d seconds ago without a clean shutdown.", *it, Util::bootSecs() - threadTimer[*it]);
|
||||||
|
threadTimer.erase(*it);
|
||||||
|
}
|
||||||
|
if (!threadTimer.count(*it)) {
|
||||||
|
|
||||||
|
//Add to list of unclaimed threads
|
||||||
|
claimableThreads.insert(*it);
|
||||||
|
|
||||||
|
//Spawn thread here.
|
||||||
|
tthread::thread thisThread(parseThread, 0);
|
||||||
|
thisThread.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lock.post();
|
||||||
|
threadCheckTimer = Util::bootSecs();
|
||||||
|
}
|
||||||
|
Util::sleep(100);
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
INFO_MSG("Input for stream %s closing clean", streamName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void inputTS::finish() {
|
||||||
|
std::string semName = "MstInTSStreamClaim" + globalStreamName;
|
||||||
|
IPC::semaphore lock(semName.c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||||
|
|
||||||
|
|
||||||
|
int threadCount = 0;
|
||||||
|
do {
|
||||||
|
lock.wait();
|
||||||
|
threadCount = threadTimer.size();
|
||||||
|
lock.post();
|
||||||
|
} while (threadCount);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,17 @@ namespace Mist {
|
||||||
void trackSelect(std::string trackSpec);
|
void trackSelect(std::string trackSpec);
|
||||||
void readPMT();
|
void readPMT();
|
||||||
|
|
||||||
|
#ifdef TSLIVE_INPUT
|
||||||
|
//Live tsinput does not have a header, so parseheader should do nothing
|
||||||
|
void parseHeader() { }
|
||||||
|
//In case of live TS Input, we override the default serve function
|
||||||
|
void serve();
|
||||||
|
void finish();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FILE * inFile;///<The input file with ts data
|
FILE * inFile;///<The input file with ts data
|
||||||
TS::Stream tsStream;///<Used for parsing the incoming ts stream
|
TS::Stream tsStream;///<Used for parsing the incoming ts stream
|
||||||
|
|
||||||
|
|
93
src/io.cpp
93
src/io.cpp
|
@ -16,17 +16,17 @@ namespace Mist {
|
||||||
//Open the page for the metadata
|
//Open the page for the metadata
|
||||||
char pageName[NAME_BUFFER_SIZE];
|
char pageName[NAME_BUFFER_SIZE];
|
||||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||||
metaPages[0].init(pageName, myMeta.getSendLen(), true);
|
nProxy.metaPages[0].init(pageName, myMeta.getSendLen(), true);
|
||||||
//Make sure we don't delete it on accident
|
//Make sure we don't delete it on accident
|
||||||
metaPages[0].master = false;
|
nProxy.metaPages[0].master = false;
|
||||||
|
|
||||||
//Write the metadata to the page
|
//Write the metadata to the page
|
||||||
myMeta.writeTo(metaPages[0].mapped);
|
myMeta.writeTo(nProxy.metaPages[0].mapped);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*LTS-START*/
|
/*LTS-START*/
|
||||||
void InOutBase::initiateEncryption(){
|
void negotiationProxy::initiateEncryption(){
|
||||||
static bool encInit = false;
|
static bool encInit = false;
|
||||||
if (encInit){
|
if (encInit){
|
||||||
return;
|
return;
|
||||||
|
@ -58,6 +58,24 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
/*LTS-END*/
|
/*LTS-END*/
|
||||||
|
|
||||||
|
|
||||||
|
bool InOutBase::bufferStart(unsigned long tid, unsigned long pageNumber) {
|
||||||
|
VERYHIGH_MSG("bufferStart for stream %s, track %lu, page %lu", streamName.c_str(), tid, pageNumber);
|
||||||
|
//Initialize the stream metadata if it does not yet exist
|
||||||
|
#ifndef TSLIVE_INPUT
|
||||||
|
if (!nProxy.metaPages.count(0)) {
|
||||||
|
initiateMeta();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
//If we are a stand-alone player skip track negotiation, as there will be nothing to negotiate with.
|
||||||
|
if (standAlone) {
|
||||||
|
if (!nProxy.trackMap.count(tid)) {
|
||||||
|
nProxy.trackMap[tid] = tid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nProxy.bufferStart(tid, pageNumber, myMeta);
|
||||||
|
}
|
||||||
|
|
||||||
///Starts the buffering of a new page.
|
///Starts the buffering of a new page.
|
||||||
///
|
///
|
||||||
///Does not do any actual buffering, just sets the right bits for buffering to go right.
|
///Does not do any actual buffering, just sets the right bits for buffering to go right.
|
||||||
|
@ -65,23 +83,10 @@ namespace Mist {
|
||||||
///Buffering itself is done by bufferNext().
|
///Buffering itself is done by bufferNext().
|
||||||
///\param tid The trackid of the page to start buffering
|
///\param tid The trackid of the page to start buffering
|
||||||
///\param pageNumber The number of the page to start buffering
|
///\param pageNumber The number of the page to start buffering
|
||||||
bool InOutBase::bufferStart(unsigned long tid, unsigned long pageNumber) {
|
bool negotiationProxy::bufferStart(unsigned long tid, unsigned long pageNumber, DTSC::Meta & myMeta) {
|
||||||
VERYHIGH_MSG("bufferStart for stream %s, track %lu, page %lu", streamName.c_str(), tid, pageNumber);
|
|
||||||
initiateEncryption();
|
initiateEncryption();
|
||||||
//Initialize the stream metadata if it does not yet exist
|
|
||||||
#ifndef INPUT_LIVE
|
|
||||||
if (!metaPages.count(0)) {
|
|
||||||
initiateMeta();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
//If we are a stand-alone player skip track negotiation, as there will be nothing to negotiate with.
|
|
||||||
if (standAlone) {
|
|
||||||
if (!trackMap.count(tid)) {
|
|
||||||
trackMap[tid] = tid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Negotiate the requested track if needed.
|
//Negotiate the requested track if needed.
|
||||||
continueNegotiate(tid);
|
continueNegotiate(tid, myMeta);
|
||||||
|
|
||||||
//If the negotation state for this track is not 'Accepted', stop buffering this page, maybe try again later.
|
//If the negotation state for this track is not 'Accepted', stop buffering this page, maybe try again later.
|
||||||
if (trackState[tid] != FILL_ACC) {
|
if (trackState[tid] != FILL_ACC) {
|
||||||
|
@ -142,6 +147,9 @@ namespace Mist {
|
||||||
//Initialize the bookkeeping entry, and set the current offset to 0, to allow for using it in bufferNext()
|
//Initialize the bookkeeping entry, and set the current offset to 0, to allow for using it in bufferNext()
|
||||||
pagesByTrack[tid][pageNumber].curOffset = 0;
|
pagesByTrack[tid][pageNumber].curOffset = 0;
|
||||||
|
|
||||||
|
HIGH_MSG("Start buffering page %lu on track %lu~>%lu successful", pageNumber, tid, mapTid);
|
||||||
|
|
||||||
|
|
||||||
if (myMeta.live){
|
if (myMeta.live){
|
||||||
//Register this page on the meta page
|
//Register this page on the meta page
|
||||||
//NOTE: It is important that this only happens if the stream is live....
|
//NOTE: It is important that this only happens if the stream is live....
|
||||||
|
@ -150,7 +158,7 @@ namespace Mist {
|
||||||
int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
|
int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
|
||||||
if ((tmpOffset[0] == 0 && tmpOffset[1] == 0)) {
|
if ((tmpOffset[0] == 0 && tmpOffset[1] == 0)) {
|
||||||
tmpOffset[0] = htonl(curPageNum[tid]);
|
tmpOffset[0] = htonl(curPageNum[tid]);
|
||||||
if (pagesByTrack[tid][pageNumber].dataSize == (25 * 1024 * 1024)){
|
if (pagesByTrack[tid][pageNumber].dataSize == DEFAULT_DATA_PAGE_SIZE){
|
||||||
tmpOffset[1] = htonl(1000);
|
tmpOffset[1] = htonl(1000);
|
||||||
} else {
|
} else {
|
||||||
tmpOffset[1] = htonl(pagesByTrack[tid][pageNumber].keyNum);
|
tmpOffset[1] = htonl(pagesByTrack[tid][pageNumber].keyNum);
|
||||||
|
@ -161,7 +169,7 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HIGH_MSG("Start buffering page %lu on track %lu~>%lu successful", pageNumber, tid, mapTid);
|
|
||||||
///\return true if everything was successful
|
///\return true if everything was successful
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -176,13 +184,13 @@ namespace Mist {
|
||||||
//A different process will handle this for us
|
//A different process will handle this for us
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
unsigned long mapTid = trackMap[tid];
|
unsigned long mapTid = nProxy.trackMap[tid];
|
||||||
if (!pagesByTrack.count(tid)){
|
if (!nProxy.pagesByTrack.count(tid)){
|
||||||
// If there is no pagesByTrack entry, the pages are managed in local code and not through io.cpp (e.g.: MistInBuffer)
|
// If there is no pagesByTrack entry, the pages are managed in local code and not through io.cpp (e.g.: MistInBuffer)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//If the given pagenumber is not a valid page on this track, do nothing
|
//If the given pagenumber is not a valid page on this track, do nothing
|
||||||
if (!pagesByTrack[tid].count(pageNumber)){
|
if (!nProxy.pagesByTrack[tid].count(pageNumber)){
|
||||||
INFO_MSG("Can't remove page %lu on track %lu~>%lu as it is not a valid page number.", pageNumber, tid, mapTid);
|
INFO_MSG("Can't remove page %lu on track %lu~>%lu as it is not a valid page number.", pageNumber, tid, mapTid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -194,7 +202,7 @@ namespace Mist {
|
||||||
#ifdef __CYGWIN__
|
#ifdef __CYGWIN__
|
||||||
toErase.init(pageName, 26 * 1024 * 1024, false);
|
toErase.init(pageName, 26 * 1024 * 1024, false);
|
||||||
#else
|
#else
|
||||||
toErase.init(pageName, pagesByTrack[tid][pageNumber].dataSize, false);
|
toErase.init(pageName, nProxy.pagesByTrack[tid][pageNumber].dataSize, false);
|
||||||
#endif
|
#endif
|
||||||
//Set the master flag so that the page will be destroyed once it leaves scope
|
//Set the master flag so that the page will be destroyed once it leaves scope
|
||||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||||
|
@ -204,7 +212,7 @@ namespace Mist {
|
||||||
//Remove the page from the tracks index page
|
//Remove the page from the tracks index page
|
||||||
DEBUG_MSG(DLVL_HIGH, "Removing page %lu on track %lu~>%lu from the corresponding metaPage", pageNumber, tid, mapTid);
|
DEBUG_MSG(DLVL_HIGH, "Removing page %lu on track %lu~>%lu from the corresponding metaPage", pageNumber, tid, mapTid);
|
||||||
for (int i = 0; i < 1024; i++) {
|
for (int i = 0; i < 1024; i++) {
|
||||||
int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
|
int * tmpOffset = (int *)(nProxy.metaPages[tid].mapped + (i * 8));
|
||||||
if (ntohl(tmpOffset[0]) == pageNumber) {
|
if (ntohl(tmpOffset[0]) == pageNumber) {
|
||||||
tmpOffset[0] = 0;
|
tmpOffset[0] = 0;
|
||||||
tmpOffset[1] = 0;
|
tmpOffset[1] = 0;
|
||||||
|
@ -217,7 +225,7 @@ namespace Mist {
|
||||||
///Checks whether a key is buffered
|
///Checks whether a key is buffered
|
||||||
///\param tid The trackid on which to locate the key
|
///\param tid The trackid on which to locate the key
|
||||||
///\param keyNum The number of the keyframe to find
|
///\param keyNum The number of the keyframe to find
|
||||||
bool InOutBase::isBuffered(unsigned long tid, unsigned long keyNum) {
|
bool negotiationProxy::isBuffered(unsigned long tid, unsigned long keyNum) {
|
||||||
///\return The result of bufferedOnPage(tid, keyNum)
|
///\return The result of bufferedOnPage(tid, keyNum)
|
||||||
return bufferedOnPage(tid, keyNum);
|
return bufferedOnPage(tid, keyNum);
|
||||||
}
|
}
|
||||||
|
@ -225,7 +233,7 @@ namespace Mist {
|
||||||
///Returns the pagenumber where this key is buffered on
|
///Returns the pagenumber where this key is buffered on
|
||||||
///\param tid The trackid on which to locate the key
|
///\param tid The trackid on which to locate the key
|
||||||
///\param keyNum The number of the keyframe to find
|
///\param keyNum The number of the keyframe to find
|
||||||
unsigned long InOutBase::bufferedOnPage(unsigned long tid, unsigned long keyNum) {
|
unsigned long negotiationProxy::bufferedOnPage(unsigned long tid, unsigned long keyNum) {
|
||||||
//Check whether the track is accepted
|
//Check whether the track is accepted
|
||||||
if (!trackMap.count(tid) || !metaPages.count(tid) || !metaPages[tid].mapped) {
|
if (!trackMap.count(tid) || !metaPages.count(tid) || !metaPages[tid].mapped) {
|
||||||
///\return 0 if the page has not been mapped yet
|
///\return 0 if the page has not been mapped yet
|
||||||
|
@ -252,12 +260,16 @@ namespace Mist {
|
||||||
std::string packData = pack.toNetPacked();
|
std::string packData = pack.toNetPacked();
|
||||||
DTSC::Packet newPack(packData.data(), packData.size());
|
DTSC::Packet newPack(packData.data(), packData.size());
|
||||||
///\note Internally calls bufferNext(DTSC::Packet & pack)
|
///\note Internally calls bufferNext(DTSC::Packet & pack)
|
||||||
bufferNext(newPack);
|
nProxy.bufferNext(newPack, myMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
///Buffers the next packet on the currently opened page
|
///Buffers the next packet on the currently opened page
|
||||||
///\param pack The packet to buffer
|
///\param pack The packet to buffer
|
||||||
void InOutBase::bufferNext(DTSC::Packet & pack) {
|
void InOutBase::bufferNext(DTSC::Packet & pack) {
|
||||||
|
nProxy.bufferNext(pack, myMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void negotiationProxy::bufferNext(DTSC::Packet & pack, DTSC::Meta & myMeta) {
|
||||||
//Save the trackid of the track for easier access
|
//Save the trackid of the track for easier access
|
||||||
unsigned long tid = pack.getTrackId();
|
unsigned long tid = pack.getTrackId();
|
||||||
unsigned long mapTid = trackMap[tid];
|
unsigned long mapTid = trackMap[tid];
|
||||||
|
@ -330,6 +342,10 @@ namespace Mist {
|
||||||
///Registers the data page on the track index page as well
|
///Registers the data page on the track index page as well
|
||||||
///\param tid The trackid of the page to finalize
|
///\param tid The trackid of the page to finalize
|
||||||
void InOutBase::bufferFinalize(unsigned long tid) {
|
void InOutBase::bufferFinalize(unsigned long tid) {
|
||||||
|
nProxy.bufferFinalize(tid, myMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void negotiationProxy::bufferFinalize(unsigned long tid, DTSC::Meta & myMeta){
|
||||||
unsigned long mapTid = trackMap[tid];
|
unsigned long mapTid = trackMap[tid];
|
||||||
//If no page is open, do nothing
|
//If no page is open, do nothing
|
||||||
if (!curPage.count(tid)) {
|
if (!curPage.count(tid)) {
|
||||||
|
@ -411,6 +427,10 @@ namespace Mist {
|
||||||
///Initiates/continues negotiation with the buffer as well
|
///Initiates/continues negotiation with the buffer as well
|
||||||
///\param packet The packet to buffer
|
///\param packet The packet to buffer
|
||||||
void InOutBase::bufferLivePacket(DTSC::Packet & packet){
|
void InOutBase::bufferLivePacket(DTSC::Packet & packet){
|
||||||
|
nProxy.bufferLivePacket(packet, myMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void negotiationProxy::bufferLivePacket(DTSC::Packet & packet, DTSC::Meta & myMeta){
|
||||||
myMeta.vod = false;
|
myMeta.vod = false;
|
||||||
myMeta.live = true;
|
myMeta.live = true;
|
||||||
//Store the trackid for easier access
|
//Store the trackid for easier access
|
||||||
|
@ -422,7 +442,7 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
//If the track is not negotiated yet, start the negotiation
|
//If the track is not negotiated yet, start the negotiation
|
||||||
if (!trackState.count(tid)) {
|
if (!trackState.count(tid)) {
|
||||||
continueNegotiate(tid);
|
continueNegotiate(tid, myMeta);
|
||||||
}
|
}
|
||||||
//If the track is declined, stop here
|
//If the track is declined, stop here
|
||||||
if (trackState[tid] == FILL_DEC) {
|
if (trackState[tid] == FILL_DEC) {
|
||||||
|
@ -443,7 +463,7 @@ namespace Mist {
|
||||||
if (shouldBlock) {
|
if (shouldBlock) {
|
||||||
while (trackState[tid] != FILL_DEC && trackState[tid] != FILL_ACC) {
|
while (trackState[tid] != FILL_DEC && trackState[tid] != FILL_ACC) {
|
||||||
INFO_MSG("Blocking on track %lu", tid);
|
INFO_MSG("Blocking on track %lu", tid);
|
||||||
continueNegotiate(tid);
|
continueNegotiate(tid, myMeta);
|
||||||
Util::sleep(500);
|
Util::sleep(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -504,16 +524,19 @@ namespace Mist {
|
||||||
if (!curPageNum.count(tid) || nextPageNum != curPageNum[tid]) {
|
if (!curPageNum.count(tid) || nextPageNum != curPageNum[tid]) {
|
||||||
if (curPageNum.count(tid)) {
|
if (curPageNum.count(tid)) {
|
||||||
//Close the currently opened page when it exists
|
//Close the currently opened page when it exists
|
||||||
bufferFinalize(tid);
|
bufferFinalize(tid, myMeta);
|
||||||
}
|
}
|
||||||
//Open the new page
|
//Open the new page
|
||||||
bufferStart(tid, nextPageNum);
|
bufferStart(tid, nextPageNum, myMeta);
|
||||||
}
|
}
|
||||||
//Buffer the packet
|
//Buffer the packet
|
||||||
bufferNext(packet);
|
bufferNext(packet, myMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InOutBase::continueNegotiate(unsigned long tid) {
|
void InOutBase::continueNegotiate(unsigned long tid) {
|
||||||
|
nProxy.continueNegotiate(tid, myMeta);
|
||||||
|
}
|
||||||
|
void negotiationProxy::continueNegotiate(unsigned long tid, DTSC::Meta & myMeta) {
|
||||||
if (!tid) {
|
if (!tid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -559,7 +582,7 @@ namespace Mist {
|
||||||
if (!userClient.getData()){
|
if (!userClient.getData()){
|
||||||
char userPageName[100];
|
char userPageName[100];
|
||||||
sprintf(userPageName, SHM_USERS, streamName.c_str());
|
sprintf(userPageName, SHM_USERS, streamName.c_str());
|
||||||
userClient = IPC::sharedClient(userPageName, 30, true);
|
userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||||
}
|
}
|
||||||
char * tmp = userClient.getData();
|
char * tmp = userClient.getData();
|
||||||
if (!tmp) {
|
if (!tmp) {
|
||||||
|
|
64
src/io.h
64
src/io.h
|
@ -26,6 +26,42 @@ namespace Mist {
|
||||||
unsigned long lastKeyTime;///<The last key time encountered on this track.
|
unsigned long lastKeyTime;///<The last key time encountered on this track.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class negotiationProxy {
|
||||||
|
public:
|
||||||
|
negotiationProxy() : encrypt(false) {}
|
||||||
|
void initiateEncryption();//LTS
|
||||||
|
bool bufferStart(unsigned long tid, unsigned long pageNumber, DTSC::Meta & myMeta);
|
||||||
|
void bufferNext(DTSC::Packet & pack, DTSC::Meta & myMeta);
|
||||||
|
void bufferFinalize(unsigned long tid, DTSC::Meta &myMeta);
|
||||||
|
void bufferLivePacket(DTSC::Packet & packet, DTSC::Meta & myMeta);
|
||||||
|
bool isBuffered(unsigned long tid, unsigned long keyNum);
|
||||||
|
unsigned long bufferedOnPage(unsigned long tid, unsigned long keyNum);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > pagesByTrack;///<Holds the data for all pages of a track. Based on unmapped tids
|
||||||
|
|
||||||
|
//Negotiation stuff (from unmapped tid's)
|
||||||
|
std::map<unsigned long, unsigned long> trackOffset; ///< Offset of data on user page
|
||||||
|
std::map<unsigned long, negotiationState> trackState; ///< State of the negotiation for tracks
|
||||||
|
std::map<unsigned long, unsigned long> trackMap;///<Determines which input track maps onto which "final" track
|
||||||
|
std::map<unsigned long, IPC::sharedPage> metaPages;///< For each track, holds the page that describes which dataPages are mapped
|
||||||
|
std::map<unsigned long, unsigned long> curPageNum;///< For each track, holds the number page that is currently being written.
|
||||||
|
std::map<unsigned long, IPC::sharedPage> curPage;///< For each track, holds the page that is currently being written.
|
||||||
|
|
||||||
|
IPC::sharedClient userClient;///< Shared memory used for connection to Mixer process.
|
||||||
|
|
||||||
|
std::string streamName;///< Name of the stream to connect to
|
||||||
|
|
||||||
|
bool encrypt;
|
||||||
|
Encryption::verimatrixData vmData;
|
||||||
|
std::map<int,unsigned long long int> iVecs;
|
||||||
|
IPC::sharedPage encryptionPage;
|
||||||
|
|
||||||
|
void continueNegotiate(unsigned long tid, DTSC::Meta & myMeta);
|
||||||
|
};
|
||||||
|
|
||||||
///\brief Class containing all basic input and output functions.
|
///\brief Class containing all basic input and output functions.
|
||||||
class InOutBase {
|
class InOutBase {
|
||||||
public:
|
public:
|
||||||
|
@ -37,37 +73,23 @@ namespace Mist {
|
||||||
void bufferRemove(unsigned long tid, unsigned long pageNumber);
|
void bufferRemove(unsigned long tid, unsigned long pageNumber);
|
||||||
void bufferLivePacket(JSON::Value & packet);
|
void bufferLivePacket(JSON::Value & packet);
|
||||||
void bufferLivePacket(DTSC::Packet & packet);
|
void bufferLivePacket(DTSC::Packet & packet);
|
||||||
bool isBuffered(unsigned long tid, unsigned long keyNum);
|
|
||||||
unsigned long bufferedOnPage(unsigned long tid, unsigned long keyNum);
|
|
||||||
protected:
|
protected:
|
||||||
|
void continueNegotiate(unsigned long tid);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool standAlone;
|
bool standAlone;
|
||||||
static Util::Config * config;
|
static Util::Config * config;
|
||||||
void initiateEncryption();//LTS
|
|
||||||
|
|
||||||
void continueNegotiate(unsigned long tid);
|
negotiationProxy nProxy;
|
||||||
|
|
||||||
DTSC::Packet thisPacket;//The current packet that is being parsed
|
DTSC::Packet thisPacket;//The current packet that is being parsed
|
||||||
|
|
||||||
std::string streamName;///< Name of the stream to connect to
|
std::string streamName;
|
||||||
IPC::sharedClient userClient;///< Shared memory used for connection to Mixer process.
|
|
||||||
|
|
||||||
DTSC::Meta myMeta;///< Stores either the input or output metadata
|
|
||||||
|
|
||||||
std::set<unsigned long> selectedTracks;///< Stores the track id's that are either selected for playback or input
|
std::set<unsigned long> selectedTracks;///< Stores the track id's that are either selected for playback or input
|
||||||
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > pagesByTrack;///<Holds the data for all pages of a track. Based on unmapped tids
|
|
||||||
|
|
||||||
//Negotiation stuff (from unmapped tid's)
|
DTSC::Meta myMeta;///< Stores either the input or output metadata
|
||||||
std::map<unsigned long, unsigned long> trackOffset; ///< Offset of data on user page
|
|
||||||
std::map<unsigned long, negotiationState> trackState; ///< State of the negotiation for tracks
|
|
||||||
std::map<unsigned long, unsigned long> trackMap;///<Determines which input track maps onto which "final" track
|
|
||||||
std::map<unsigned long, IPC::sharedPage> metaPages;///< For each track, holds the page that describes which dataPages are mapped
|
|
||||||
std::map<unsigned long, unsigned long> curPageNum;///< For each track, holds the number page that is currently being written.
|
|
||||||
std::map<unsigned long, IPC::sharedPage> curPage;///< For each track, holds the page that is currently being written.
|
|
||||||
std::map<unsigned long, std::deque<DTSC::Packet> > trackBuffer; ///< Buffer to be used during active track negotiation
|
|
||||||
|
|
||||||
bool encrypt;
|
|
||||||
Encryption::verimatrixData vmData;
|
|
||||||
std::map<int,unsigned long long int> iVecs;
|
|
||||||
IPC::sharedPage encryptionPage;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ int main(int argc, char * argv[]) {
|
||||||
std::cout << mistOut::capa.toString() << std::endl;
|
std::cout << mistOut::capa.toString() << std::endl;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
conf.activate();
|
||||||
if (mistOut::listenMode()){
|
if (mistOut::listenMode()){
|
||||||
conf.serveForkedSocket(spawnForked);
|
conf.serveForkedSocket(spawnForked);
|
||||||
}else{
|
}else{
|
||||||
|
|
|
@ -163,8 +163,8 @@ namespace Mist {
|
||||||
if (lock){
|
if (lock){
|
||||||
liveMeta.wait();
|
liveMeta.wait();
|
||||||
}
|
}
|
||||||
if (metaPages[0].mapped){
|
if (nProxy.metaPages[0].mapped){
|
||||||
DTSC::Packet tmpMeta(metaPages[0].mapped, metaPages[0].len, true);
|
DTSC::Packet tmpMeta(nProxy.metaPages[0].mapped, nProxy.metaPages[0].len, true);
|
||||||
if (tmpMeta.getVersion()){
|
if (tmpMeta.getVersion()){
|
||||||
myMeta.reinit(tmpMeta);
|
myMeta.reinit(tmpMeta);
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,7 @@ namespace Mist {
|
||||||
if (isInitialized){
|
if (isInitialized){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (metaPages[0].mapped){
|
if (nProxy.metaPages[0].mapped){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (streamName.size() < 1){
|
if (streamName.size() < 1){
|
||||||
|
@ -233,8 +233,8 @@ namespace Mist {
|
||||||
/// Connects or reconnects to the stream.
|
/// Connects or reconnects to the stream.
|
||||||
/// Assumes streamName class member has been set already.
|
/// Assumes streamName class member has been set already.
|
||||||
/// Will start input if not currently active, calls onFail() if this does not succeed.
|
/// Will start input if not currently active, calls onFail() if this does not succeed.
|
||||||
/// After assuring stream is online, clears metaPages, then sets metaPages[0], statsPage and userClient to (hopefully) valid handles.
|
/// After assuring stream is online, clears nProxy.metaPages, then sets nProxy.metaPages[0], statsPage and nProxy.userClient to (hopefully) valid handles.
|
||||||
/// Finally, calls updateMeta() and stats()
|
/// Finally, calls updateMeta()
|
||||||
void Output::reconnect(){
|
void Output::reconnect(){
|
||||||
if (!Util::startInput(streamName)){
|
if (!Util::startInput(streamName)){
|
||||||
DEBUG_MSG(DLVL_FAIL, "Opening stream failed - aborting initalization");
|
DEBUG_MSG(DLVL_FAIL, "Opening stream failed - aborting initalization");
|
||||||
|
@ -243,9 +243,9 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
char pageId[NAME_BUFFER_SIZE];
|
char pageId[NAME_BUFFER_SIZE];
|
||||||
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||||
metaPages.clear();
|
nProxy.metaPages.clear();
|
||||||
metaPages[0].init(pageId, DEFAULT_META_PAGE_SIZE);
|
nProxy.metaPages[0].init(pageId, DEFAULT_META_PAGE_SIZE);
|
||||||
if (!metaPages[0].mapped){
|
if (!nProxy.metaPages[0].mapped){
|
||||||
FAIL_MSG("Could not connect to server for %s", streamName.c_str());
|
FAIL_MSG("Could not connect to server for %s", streamName.c_str());
|
||||||
onFail();
|
onFail();
|
||||||
return;
|
return;
|
||||||
|
@ -254,13 +254,12 @@ namespace Mist {
|
||||||
statsPage.finish();
|
statsPage.finish();
|
||||||
}
|
}
|
||||||
statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
|
statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
|
||||||
if (userClient.getData()){
|
if (nProxy.userClient.getData()){
|
||||||
userClient.finish();
|
nProxy.userClient.finish();
|
||||||
}
|
}
|
||||||
char userPageName[NAME_BUFFER_SIZE];
|
char userPageName[NAME_BUFFER_SIZE];
|
||||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||||
userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||||
stats();
|
|
||||||
updateMeta();
|
updateMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,14 +408,14 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
|
|
||||||
int Output::pageNumForKey(long unsigned int trackId, long long int keyNum){
|
int Output::pageNumForKey(long unsigned int trackId, long long int keyNum){
|
||||||
if (!metaPages.count(trackId)){
|
if (!nProxy.metaPages.count(trackId)){
|
||||||
char id[NAME_BUFFER_SIZE];
|
char id[NAME_BUFFER_SIZE];
|
||||||
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), trackId);
|
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), trackId);
|
||||||
metaPages[trackId].init(id, 8 * 1024);
|
nProxy.metaPages[trackId].init(id, 8 * 1024);
|
||||||
}
|
}
|
||||||
int len = metaPages[trackId].len / 8;
|
int len = nProxy.metaPages[trackId].len / 8;
|
||||||
for (int i = 0; i < len; i++){
|
for (int i = 0; i < len; i++){
|
||||||
int * tmpOffset = (int *)(metaPages[trackId].mapped + (i * 8));
|
int * tmpOffset = (int *)(nProxy.metaPages[trackId].mapped + (i * 8));
|
||||||
long amountKey = ntohl(tmpOffset[1]);
|
long amountKey = ntohl(tmpOffset[1]);
|
||||||
if (amountKey == 0){continue;}
|
if (amountKey == 0){continue;}
|
||||||
long tmpKey = ntohl(tmpOffset[0]);
|
long tmpKey = ntohl(tmpOffset[0]);
|
||||||
|
@ -429,7 +428,7 @@ namespace Mist {
|
||||||
|
|
||||||
void Output::loadPageForKey(long unsigned int trackId, long long int keyNum){
|
void Output::loadPageForKey(long unsigned int trackId, long long int keyNum){
|
||||||
if (myMeta.vod && keyNum > myMeta.tracks[trackId].keys.rbegin()->getNumber()){
|
if (myMeta.vod && keyNum > myMeta.tracks[trackId].keys.rbegin()->getNumber()){
|
||||||
curPage.erase(trackId);
|
nProxy.curPage.erase(trackId);
|
||||||
currKeyOpen.erase(trackId);
|
currKeyOpen.erase(trackId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -448,7 +447,7 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
if (timeout > 100){
|
if (timeout > 100){
|
||||||
DEBUG_MSG(DLVL_FAIL, "Timeout while waiting for requested page %lld for track %lu. Aborting.", keyNum, trackId);
|
DEBUG_MSG(DLVL_FAIL, "Timeout while waiting for requested page %lld for track %lu. Aborting.", keyNum, trackId);
|
||||||
curPage.erase(trackId);
|
nProxy.curPage.erase(trackId);
|
||||||
currKeyOpen.erase(trackId);
|
currKeyOpen.erase(trackId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -475,9 +474,9 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
char id[NAME_BUFFER_SIZE];
|
char id[NAME_BUFFER_SIZE];
|
||||||
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), trackId, pageNum);
|
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), trackId, pageNum);
|
||||||
curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE);
|
nProxy.curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE);
|
||||||
if (!(curPage[trackId].mapped)){
|
if (!(nProxy.curPage[trackId].mapped)){
|
||||||
DEBUG_MSG(DLVL_FAIL, "Initializing page %s failed", curPage[trackId].name.c_str());
|
DEBUG_MSG(DLVL_FAIL, "Initializing page %s failed", nProxy.curPage[trackId].name.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currKeyOpen[trackId] = pageNum;
|
currKeyOpen[trackId] = pageNum;
|
||||||
|
@ -503,7 +502,7 @@ namespace Mist {
|
||||||
|
|
||||||
bool Output::seek(unsigned int tid, unsigned long long pos, bool getNextKey){
|
bool Output::seek(unsigned int tid, unsigned long long pos, bool getNextKey){
|
||||||
loadPageForKey(tid, getKeyForTime(tid, pos) + (getNextKey?1:0));
|
loadPageForKey(tid, getKeyForTime(tid, pos) + (getNextKey?1:0));
|
||||||
if (!curPage.count(tid) || !curPage[tid].mapped){
|
if (!nProxy.curPage.count(tid) || !nProxy.curPage[tid].mapped){
|
||||||
INFO_MSG("Aborting seek to %llums in track %u, not available.", pos, tid);
|
INFO_MSG("Aborting seek to %llums in track %u, not available.", pos, tid);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -511,9 +510,9 @@ namespace Mist {
|
||||||
tmp.tid = tid;
|
tmp.tid = tid;
|
||||||
tmp.offset = 0;
|
tmp.offset = 0;
|
||||||
DTSC::Packet tmpPack;
|
DTSC::Packet tmpPack;
|
||||||
tmpPack.reInit(curPage[tid].mapped + tmp.offset, 0, true);
|
tmpPack.reInit(nProxy.curPage[tid].mapped + tmp.offset, 0, true);
|
||||||
tmp.time = tmpPack.getTime();
|
tmp.time = tmpPack.getTime();
|
||||||
char * mpd = curPage[tid].mapped;
|
char * mpd = nProxy.curPage[tid].mapped;
|
||||||
while ((long long)tmp.time < pos && tmpPack){
|
while ((long long)tmp.time < pos && tmpPack){
|
||||||
tmp.offset += tmpPack.getDataLen();
|
tmp.offset += tmpPack.getDataLen();
|
||||||
tmpPack.reInit(mpd + tmp.offset, 0, true);
|
tmpPack.reInit(mpd + tmp.offset, 0, true);
|
||||||
|
@ -524,15 +523,15 @@ namespace Mist {
|
||||||
return true;
|
return true;
|
||||||
}else{
|
}else{
|
||||||
//don't print anything for empty packets - not sign of corruption, just unfinished stream.
|
//don't print anything for empty packets - not sign of corruption, just unfinished stream.
|
||||||
if (curPage[tid].mapped[tmp.offset] != 0){
|
if (nProxy.curPage[tid].mapped[tmp.offset] != 0){
|
||||||
DEBUG_MSG(DLVL_FAIL, "Noes! Couldn't find packet on track %d because of some kind of corruption error or somesuch.", tid);
|
DEBUG_MSG(DLVL_FAIL, "Noes! Couldn't find packet on track %d because of some kind of corruption error or somesuch.", tid);
|
||||||
}else{
|
}else{
|
||||||
VERYHIGH_MSG("Track %d no data (key %u @ %u) - waiting...", tid, getKeyForTime(tid, pos) + (getNextKey?1:0), tmp.offset);
|
VERYHIGH_MSG("Track %d no data (key %u @ %u) - waiting...", tid, getKeyForTime(tid, pos) + (getNextKey?1:0), tmp.offset);
|
||||||
unsigned int i = 0;
|
unsigned int i = 0;
|
||||||
while (curPage[tid].mapped[tmp.offset] == 0 && ++i < 42){
|
while (nProxy.curPage[tid].mapped[tmp.offset] == 0 && ++i < 42){
|
||||||
Util::wait(100);
|
Util::wait(100);
|
||||||
}
|
}
|
||||||
if (curPage[tid].mapped[tmp.offset] == 0){
|
if (nProxy.curPage[tid].mapped[tmp.offset] == 0){
|
||||||
FAIL_MSG("Track %d no data (key %u) - timeout", tid, getKeyForTime(tid, pos) + (getNextKey?1:0));
|
FAIL_MSG("Track %d no data (key %u) - timeout", tid, getKeyForTime(tid, pos) + (getNextKey?1:0));
|
||||||
}else{
|
}else{
|
||||||
return seek(tid, pos, getNextKey);
|
return seek(tid, pos, getNextKey);
|
||||||
|
@ -879,7 +878,7 @@ namespace Mist {
|
||||||
/*LTS-END*/
|
/*LTS-END*/
|
||||||
|
|
||||||
stats();
|
stats();
|
||||||
userClient.finish();
|
nProxy.userClient.finish();
|
||||||
statsPage.finish();
|
statsPage.finish();
|
||||||
myConn.close();
|
myConn.close();
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -941,15 +940,15 @@ namespace Mist {
|
||||||
|
|
||||||
DEBUG_MSG(DLVL_DONTEVEN, "Loading track %u (next=%lu), %llu ms", nxt.tid, nxtKeyNum[nxt.tid], nxt.time);
|
DEBUG_MSG(DLVL_DONTEVEN, "Loading track %u (next=%lu), %llu ms", nxt.tid, nxtKeyNum[nxt.tid], nxt.time);
|
||||||
|
|
||||||
if (nxt.offset >= curPage[nxt.tid].len){
|
if (nxt.offset >= nProxy.curPage[nxt.tid].len){
|
||||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||||
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
|
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
|
||||||
nxt.offset = 0;
|
nxt.offset = 0;
|
||||||
if (curPage.count(nxt.tid) && curPage[nxt.tid].mapped){
|
if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){
|
||||||
if (getDTSCTime(curPage[nxt.tid].mapped, nxt.offset) < nxt.time){
|
if (getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset) < nxt.time){
|
||||||
ERROR_MSG("Time going backwards in track %u - dropping track.", nxt.tid);
|
ERROR_MSG("Time going backwards in track %u - dropping track.", nxt.tid);
|
||||||
}else{
|
}else{
|
||||||
nxt.time = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset);
|
nxt.time = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
|
||||||
buffer.insert(nxt);
|
buffer.insert(nxt);
|
||||||
}
|
}
|
||||||
prepareNext();
|
prepareNext();
|
||||||
|
@ -957,7 +956,7 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!curPage.count(nxt.tid) || !curPage[nxt.tid].mapped){
|
if (!nProxy.curPage.count(nxt.tid) || !nProxy.curPage[nxt.tid].mapped){
|
||||||
//mapping failure? Drop this track and go to next.
|
//mapping failure? Drop this track and go to next.
|
||||||
//not an error - usually means end of stream.
|
//not an error - usually means end of stream.
|
||||||
DEBUG_MSG(DLVL_DEVEL, "Track %u no page - dropping track.", nxt.tid);
|
DEBUG_MSG(DLVL_DEVEL, "Track %u no page - dropping track.", nxt.tid);
|
||||||
|
@ -966,7 +965,7 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
|
|
||||||
//have we arrived at the end of the memory page? (4 zeroes mark the end)
|
//have we arrived at the end of the memory page? (4 zeroes mark the end)
|
||||||
if (!memcmp(curPage[nxt.tid].mapped + nxt.offset, "\000\000\000\000", 4)){
|
if (!memcmp(nProxy.curPage[nxt.tid].mapped + nxt.offset, "\000\000\000\000", 4)){
|
||||||
//if we don't currently know where we are, we're lost. We should drop the track.
|
//if we don't currently know where we are, we're lost. We should drop the track.
|
||||||
if (!nxt.time){
|
if (!nxt.time){
|
||||||
DEBUG_MSG(DLVL_DEVEL, "Timeless empty packet on track %u - dropping track.", nxt.tid);
|
DEBUG_MSG(DLVL_DEVEL, "Timeless empty packet on track %u - dropping track.", nxt.tid);
|
||||||
|
@ -994,8 +993,8 @@ namespace Mist {
|
||||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||||
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
|
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
|
||||||
nxt.offset = 0;
|
nxt.offset = 0;
|
||||||
if (curPage.count(nxt.tid) && curPage[nxt.tid].mapped){
|
if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){
|
||||||
unsigned long long nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset);
|
unsigned long long nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
|
||||||
if (nextTime && nextTime < nxt.time){
|
if (nextTime && nextTime < nxt.time){
|
||||||
DEBUG_MSG(DLVL_DEVEL, "Time going backwards in track %u - dropping track.", nxt.tid);
|
DEBUG_MSG(DLVL_DEVEL, "Time going backwards in track %u - dropping track.", nxt.tid);
|
||||||
}else{
|
}else{
|
||||||
|
@ -1012,7 +1011,7 @@ namespace Mist {
|
||||||
prepareNext();
|
prepareNext();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true);
|
thisPacket.reInit(nProxy.curPage[nxt.tid].mapped + nxt.offset, 0, true);
|
||||||
if (thisPacket){
|
if (thisPacket){
|
||||||
if (thisPacket.getTime() != nxt.time && nxt.time){
|
if (thisPacket.getTime() != nxt.time && nxt.time){
|
||||||
WARN_MSG("Loaded track %ld@%llu instead of %ld@%llu", thisPacket.getTrackId(), thisPacket.getTime(), nxt.tid, nxt.time);
|
WARN_MSG("Loaded track %ld@%llu instead of %ld@%llu", thisPacket.getTrackId(), thisPacket.getTime(), nxt.tid, nxt.time);
|
||||||
|
@ -1073,9 +1072,9 @@ namespace Mist {
|
||||||
completeKeyReadyTimeOut = false;
|
completeKeyReadyTimeOut = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (curPage[nxt.tid]){
|
if (nProxy.curPage[nxt.tid]){
|
||||||
if (nxt.offset < curPage[nxt.tid].len){
|
if (nxt.offset < nProxy.curPage[nxt.tid].len){
|
||||||
unsigned long long nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset);
|
unsigned long long nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
|
||||||
if (nextTime){
|
if (nextTime){
|
||||||
nxt.time = nextTime;
|
nxt.time = nextTime;
|
||||||
}else{
|
}else{
|
||||||
|
@ -1123,25 +1122,25 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int tNum = 0;
|
int tNum = 0;
|
||||||
if (!userClient.getData()){
|
if (!nProxy.userClient.getData()){
|
||||||
char userPageName[NAME_BUFFER_SIZE];
|
char userPageName[NAME_BUFFER_SIZE];
|
||||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||||
userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||||
if (!userClient.getData()){
|
if (!nProxy.userClient.getData()){
|
||||||
DEBUG_MSG(DLVL_WARN, "Player connection failure - aborting output");
|
DEBUG_MSG(DLVL_WARN, "Player connection failure - aborting output");
|
||||||
myConn.close();
|
myConn.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!trackMap.size()){
|
if (!nProxy.trackMap.size()){
|
||||||
IPC::userConnection userConn(userClient.getData());
|
IPC::userConnection userConn(nProxy.userClient.getData());
|
||||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end() && tNum < SIMUL_TRACKS; it++){
|
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end() && tNum < SIMUL_TRACKS; it++){
|
||||||
userConn.setTrackId(tNum, *it);
|
userConn.setTrackId(tNum, *it);
|
||||||
userConn.setKeynum(tNum, nxtKeyNum[*it]);
|
userConn.setKeynum(tNum, nxtKeyNum[*it]);
|
||||||
tNum ++;
|
tNum ++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
userClient.keepAlive();
|
nProxy.userClient.keepAlive();
|
||||||
if (tNum > SIMUL_TRACKS){
|
if (tNum > SIMUL_TRACKS){
|
||||||
DEBUG_MSG(DLVL_WARN, "Too many tracks selected, using only first %d", SIMUL_TRACKS);
|
DEBUG_MSG(DLVL_WARN, "Too many tracks selected, using only first %d", SIMUL_TRACKS);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,38 +5,36 @@
|
||||||
namespace Mist {
|
namespace Mist {
|
||||||
///\brief Builds an index file for HTTP Live streaming.
|
///\brief Builds an index file for HTTP Live streaming.
|
||||||
///\return The index file for HTTP Live Streaming.
|
///\return The index file for HTTP Live Streaming.
|
||||||
std::string OutHLS::liveIndex(){
|
std::string OutHLS::liveIndex() {
|
||||||
std::stringstream result;
|
std::stringstream result;
|
||||||
result << "#EXTM3U\r\n";
|
result << "#EXTM3U\r\n";
|
||||||
int audioId = -1;
|
int audioId = -1;
|
||||||
std::string audioName;
|
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||||
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3") {
|
||||||
if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3"){
|
|
||||||
audioId = it->first;
|
audioId = it->first;
|
||||||
audioName = it->second.getIdentifier();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unsigned int vidTracks = 0;
|
unsigned int vidTracks = 0;
|
||||||
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||||
if (it->second.codec == "H264" || it->second.codec == "HEVC"){
|
if (it->second.codec == "H264" || it->second.codec == "HEVC") {
|
||||||
vidTracks++;
|
vidTracks++;
|
||||||
int bWidth = it->second.bps;
|
int bWidth = it->second.bps;
|
||||||
if (bWidth < 5){
|
if (bWidth < 5) {
|
||||||
bWidth = 5;
|
bWidth = 5;
|
||||||
}
|
}
|
||||||
if (audioId != -1){
|
if (audioId != -1) {
|
||||||
bWidth += myMeta.tracks[audioId].bps;
|
bWidth += myMeta.tracks[audioId].bps;
|
||||||
}
|
}
|
||||||
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8) << "\r\n";
|
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8) << "\r\n";
|
||||||
result << it->first;
|
result << it->first;
|
||||||
if (audioId != -1){
|
if (audioId != -1) {
|
||||||
result << "_" << audioId;
|
result << "_" << audioId;
|
||||||
}
|
}
|
||||||
result << "/index.m3u8\r\n";
|
result << "/index.m3u8\r\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!vidTracks && audioId){
|
if (!vidTracks && audioId) {
|
||||||
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[audioId].bps * 8) << "\r\n";
|
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[audioId].bps * 8) << "\r\n";
|
||||||
result << audioId << "/index.m3u8\r\n";
|
result << audioId << "/index.m3u8\r\n";
|
||||||
}
|
}
|
||||||
|
@ -44,50 +42,146 @@ namespace Mist {
|
||||||
return result.str();
|
return result.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string OutHLS::liveIndex(int tid){
|
std::string OutHLS::pushLiveIndex(){
|
||||||
updateMeta();
|
|
||||||
std::stringstream result;
|
std::stringstream result;
|
||||||
//parse single track
|
result << "#EXTM3U\r\n";
|
||||||
int longestFragment = 0;
|
std::set<unsigned int> audioTracks;
|
||||||
if (!myMeta.tracks[tid].fragments.size()){
|
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||||
|
if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3") {
|
||||||
|
audioTracks.insert(it->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!audioTracks.size()){
|
||||||
|
audioTracks.insert(-1);
|
||||||
|
}
|
||||||
|
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" || it->second.codec == "HEVC") {
|
||||||
|
for (std::set<unsigned int>::iterator audIt = audioTracks.begin(); audIt != audioTracks.end(); audIt++){
|
||||||
|
vidTracks++;
|
||||||
|
int bWidth = it->second.bps;
|
||||||
|
if (bWidth < 5) {
|
||||||
|
bWidth = 5;
|
||||||
|
}
|
||||||
|
if (*audIt != -1) {
|
||||||
|
bWidth += myMeta.tracks[*audIt].bps;
|
||||||
|
}
|
||||||
|
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8) << "\r\n";
|
||||||
|
result << it->first;
|
||||||
|
if (*audIt != -1) {
|
||||||
|
result << "_" << *audIt;
|
||||||
|
}
|
||||||
|
result << "/index.m3u8\r\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!vidTracks && audioTracks.size()) {
|
||||||
|
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[*audioTracks.begin()].bps * 8) << "\r\n";
|
||||||
|
result << *audioTracks.begin() << "/index.m3u8\r\n";
|
||||||
|
}
|
||||||
|
DEBUG_MSG(DLVL_HIGH, "Sending this index: %s", result.str().c_str());
|
||||||
|
return result.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OutHLS::pushLiveIndex(int tid, unsigned long bTime, unsigned long eTime){
|
||||||
|
updateMeta();
|
||||||
|
if (!myMeta.tracks[tid].fragments.size()) {
|
||||||
DEBUG_MSG(DLVL_FAIL, "liveIndex called with track %d, which has no fragments!", tid);
|
DEBUG_MSG(DLVL_FAIL, "liveIndex called with track %d, which has no fragments!", tid);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); (it + 1) != myMeta.tracks[tid].fragments.end(); it++){
|
std::stringstream result;
|
||||||
if (it->getDuration() > longestFragment){
|
//parse single track
|
||||||
|
int longestFragment = 0;
|
||||||
|
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); (it + 1) != myMeta.tracks[tid].fragments.end(); it++) {
|
||||||
|
if (it->getDuration() > longestFragment) {
|
||||||
longestFragment = it->getDuration();
|
longestFragment = it->getDuration();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((myMeta.tracks[tid].lastms - myMeta.tracks[tid].firstms) / myMeta.tracks[tid].fragments.size() > longestFragment){
|
if ((myMeta.tracks[tid].lastms - myMeta.tracks[tid].firstms) / myMeta.tracks[tid].fragments.size() > longestFragment) {
|
||||||
longestFragment = (myMeta.tracks[tid].lastms - myMeta.tracks[tid].firstms) / myMeta.tracks[tid].fragments.size();
|
longestFragment = (myMeta.tracks[tid].lastms - myMeta.tracks[tid].firstms) / myMeta.tracks[tid].fragments.size();
|
||||||
}
|
}
|
||||||
result << "#EXTM3U\r\n#EXT-X-TARGETDURATION:" << (longestFragment / 1000) + 1 << "\r\n";
|
result << "#EXTM3U\r\n#EXT-X-TARGETDURATION:" << (longestFragment / 1000) + 1 << "\r\n";
|
||||||
|
|
||||||
std::deque<std::string> lines;
|
std::deque<std::string> lines;
|
||||||
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); it != myMeta.tracks[tid].fragments.end(); it++){
|
unsigned int skippedLines = 0;
|
||||||
|
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); it != myMeta.tracks[tid].fragments.end(); it++) {
|
||||||
long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime();
|
long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime();
|
||||||
std::stringstream line;
|
|
||||||
long long duration = it->getDuration();
|
long long duration = it->getDuration();
|
||||||
if (duration <= 0){
|
if (duration <= 0) {
|
||||||
duration = myMeta.tracks[tid].lastms - starttime;
|
duration = myMeta.tracks[tid].lastms - starttime;
|
||||||
}
|
}
|
||||||
line << "#EXTINF:" << ((duration + 500) / 1000) << ", no desc\r\n" << starttime << "_" << duration + starttime << ".ts\r\n";
|
if (starttime < bTime){
|
||||||
lines.push_back(line.str());
|
skippedLines++;
|
||||||
|
}
|
||||||
|
if (starttime >= bTime && (starttime + duration) <= eTime){
|
||||||
|
char lineBuf[400];
|
||||||
|
snprintf(lineBuf, 400, "#EXTINF:%lld, no desc\r\n%lld_%lld.ts\r\n", ((duration + 500) / 1000), starttime, starttime + duration);
|
||||||
|
lines.push_back(lineBuf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n";
|
||||||
|
|
||||||
|
while (lines.size()) {
|
||||||
|
result << lines.front();
|
||||||
|
lines.pop_front();
|
||||||
|
}
|
||||||
|
if (!myMeta.live && eTime >= myMeta.tracks[tid].lastms) {
|
||||||
|
result << "#EXT-X-ENDLIST\r\n";
|
||||||
|
}
|
||||||
|
DEBUG_MSG(DLVL_HIGH, "Sending this index: %s", result.str().c_str());
|
||||||
|
return result.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string OutHLS::liveIndex(int tid) {
|
||||||
|
updateMeta();
|
||||||
|
std::stringstream result;
|
||||||
|
//parse single track
|
||||||
|
int longestFragment = 0;
|
||||||
|
if (!myMeta.tracks[tid].fragments.size()) {
|
||||||
|
DEBUG_MSG(DLVL_FAIL, "liveIndex called with track %d, which has no fragments!", tid);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); (it + 1) != myMeta.tracks[tid].fragments.end(); it++) {
|
||||||
|
if (it->getDuration() > longestFragment) {
|
||||||
|
longestFragment = it->getDuration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((myMeta.tracks[tid].lastms - myMeta.tracks[tid].firstms) / myMeta.tracks[tid].fragments.size() > longestFragment) {
|
||||||
|
longestFragment = (myMeta.tracks[tid].lastms - myMeta.tracks[tid].firstms) / myMeta.tracks[tid].fragments.size();
|
||||||
|
}
|
||||||
|
result << "#EXTM3U\r\n#EXT-X-TARGETDURATION:" << (longestFragment / 1000) + 1 << "\r\n";
|
||||||
|
|
||||||
|
std::deque<std::string> lines;
|
||||||
|
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); it != myMeta.tracks[tid].fragments.end(); it++) {
|
||||||
|
long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime();
|
||||||
|
long long duration = it->getDuration();
|
||||||
|
if (duration <= 0) {
|
||||||
|
duration = myMeta.tracks[tid].lastms - starttime;
|
||||||
|
}
|
||||||
|
char lineBuf[400];
|
||||||
|
snprintf(lineBuf, 400, "#EXTINF:%lld, no desc\r\n%lld_%lld,ts\r\n", ((duration + 500) / 1000), starttime, starttime + duration);
|
||||||
|
lines.push_back(lineBuf);
|
||||||
|
}
|
||||||
|
//skip the first fragment if live and there are more than 2 fragments.
|
||||||
unsigned int skippedLines = 0;
|
unsigned int skippedLines = 0;
|
||||||
if (myMeta.live){
|
if (myMeta.live) {
|
||||||
//only print the last segment when VoD
|
if (lines.size() > 2) {
|
||||||
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();
|
lines.pop_front();
|
||||||
skippedLines++;
|
skippedLines++;
|
||||||
}
|
}
|
||||||
if (config->getInteger("listlimit")){
|
//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");
|
unsigned long listlimit = config->getInteger("listlimit");
|
||||||
while (lines.size() > listlimit){
|
while (lines.size() > listlimit) {
|
||||||
lines.pop_front();
|
lines.pop_front();
|
||||||
skippedLines++;
|
skippedLines++;
|
||||||
}
|
}
|
||||||
|
@ -97,30 +191,93 @@ namespace Mist {
|
||||||
|
|
||||||
result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n";
|
result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n";
|
||||||
|
|
||||||
while (lines.size()){
|
while (lines.size()) {
|
||||||
result << lines.front();
|
result << lines.front();
|
||||||
lines.pop_front();
|
lines.pop_front();
|
||||||
}
|
}
|
||||||
if ( !myMeta.live){
|
if (!myMeta.live) {
|
||||||
result << "#EXT-X-ENDLIST\r\n";
|
result << "#EXT-X-ENDLIST\r\n";
|
||||||
}
|
}
|
||||||
DEBUG_MSG(DLVL_HIGH, "Sending this index: %s", result.str().c_str());
|
DEBUG_MSG(DLVL_HIGH, "Sending this index: %s", result.str().c_str());
|
||||||
return result.str();
|
return result.str();
|
||||||
} //liveIndex
|
} //liveIndex
|
||||||
|
|
||||||
|
std::string OutHLS::generatePushList() {
|
||||||
|
updateMeta();
|
||||||
|
std::set<unsigned int> videoTracks;
|
||||||
|
std::set<unsigned int> audioTracks;
|
||||||
|
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||||
|
if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3") {
|
||||||
|
audioTracks.insert(it->first);
|
||||||
|
}
|
||||||
|
if (it->second.codec == "H264" || it->second.codec == "H265"){
|
||||||
|
videoTracks.insert(it->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JSON::Value result;
|
||||||
|
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||||
|
std::stringstream tid;
|
||||||
|
tid << it->second.trackID;
|
||||||
|
result["tracks"][tid.str()] = it->second.toJSON(true);
|
||||||
|
}
|
||||||
|
for(std::set<unsigned int>::iterator it = videoTracks.begin(); it != videoTracks.end(); it++){
|
||||||
|
for(std::set<unsigned int>::iterator it2 = audioTracks.begin(); it2 != audioTracks.end(); it2++){
|
||||||
|
JSON::Value quality;
|
||||||
|
std::stringstream identifier;
|
||||||
|
identifier << "/" << *it << "_" << *it2;
|
||||||
|
quality["index"] = "/push" + identifier.str() + "/index_\%llu_\%llu.m3u8";
|
||||||
|
quality["segment"] = identifier.str() + "/\%llu_\%llu.ts";
|
||||||
|
quality["video"] = *it;
|
||||||
|
quality["audio"] = *it2;
|
||||||
|
quality["id"] = identifier.str();
|
||||||
|
std::deque<DTSC::Fragment>::iterator it3 = myMeta.tracks[*it].fragments.begin();
|
||||||
|
for (int i = 0; i < 2; i++){
|
||||||
|
if (it3 != myMeta.tracks[*it].fragments.end()){
|
||||||
|
++it3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; it3 != myMeta.tracks[*it].fragments.end(); it3++) {
|
||||||
|
if (myMeta.live && it3 == (myMeta.tracks[*it].fragments.end() - 1)){
|
||||||
|
//Skip the current last fragment if we are live
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
long long int starttime = myMeta.tracks[*it].getKey(it3->getNumber()).getTime();
|
||||||
|
std::stringstream line;
|
||||||
|
long long duration = it3->getDuration();
|
||||||
|
if (duration <= 0) {
|
||||||
|
duration = myMeta.tracks[*it].lastms - starttime;
|
||||||
|
}
|
||||||
|
std::stringstream segmenturl;
|
||||||
|
segmenturl << identifier.str() << "/" << starttime << "_" << duration + starttime << ".ts";
|
||||||
|
JSON::Value segment;
|
||||||
|
//segment["url"] = segmenturl.str();
|
||||||
|
segment["time"] = starttime;
|
||||||
|
segment["duration"] = duration;
|
||||||
|
segment["number"] = (unsigned int)it3->getNumber();
|
||||||
|
quality["segments"].append(segment);
|
||||||
|
}
|
||||||
|
result["qualities"].append(quality);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toString();;
|
||||||
|
}
|
||||||
|
|
||||||
OutHLS::OutHLS(Socket::Connection & conn) : TSOutput(conn){
|
|
||||||
|
|
||||||
|
|
||||||
|
OutHLS::OutHLS(Socket::Connection & conn) : TSOutput(conn) {
|
||||||
realTime = 0;
|
realTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
OutHLS::~OutHLS() {}
|
OutHLS::~OutHLS() {}
|
||||||
|
|
||||||
void OutHLS::init(Util::Config * cfg){
|
void OutHLS::init(Util::Config * cfg) {
|
||||||
HTTPOutput::init(cfg);
|
HTTPOutput::init(cfg);
|
||||||
capa["name"] = "HLS";
|
capa["name"] = "HLS";
|
||||||
capa["desc"] = "Enables HTTP protocol Apple-specific streaming (also known as HLS).";
|
capa["desc"] = "Enables HTTP protocol Apple-specific streaming (also known as HLS).";
|
||||||
capa["url_rel"] = "/hls/$/index.m3u8";
|
capa["url_rel"] = "/hls/$/index.m3u8";
|
||||||
capa["url_prefix"] = "/hls/$/";
|
capa["url_prefix"] = "/hls/$/";
|
||||||
|
capa["url_pushlist"] = "/hls/$/push/list";
|
||||||
capa["codecs"][0u][0u].append("HEVC");
|
capa["codecs"][0u][0u].append("HEVC");
|
||||||
capa["codecs"][0u][0u].append("H264");
|
capa["codecs"][0u][0u].append("H264");
|
||||||
capa["codecs"][0u][1u].append("AAC");
|
capa["codecs"][0u][1u].append("AAC");
|
||||||
|
@ -139,29 +296,29 @@ namespace Mist {
|
||||||
/*LTS-END*/
|
/*LTS-END*/
|
||||||
}
|
}
|
||||||
|
|
||||||
int OutHLS::canSeekms(unsigned int ms){
|
int OutHLS::canSeekms(unsigned int ms) {
|
||||||
//no tracks? Frame too new by definition.
|
//no tracks? Frame too new by definition.
|
||||||
if ( !myMeta.tracks.size()){
|
if (!myMeta.tracks.size()) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
//check main track
|
//loop trough all the tracks
|
||||||
DTSC::Track & mainTrack = myMeta.tracks[*selectedTracks.begin()];
|
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||||
//return "too late" if one track is past this point
|
//return "too late" if one track is past this point
|
||||||
if (ms < mainTrack.firstms){
|
if (ms < it->second.firstms) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
//return "too early" if one track is not yet at this point
|
//return "too early" if one track is not yet at this point
|
||||||
if (ms > mainTrack.lastms){
|
if (ms > it->second.lastms) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OutHLS::onHTTP(){
|
void OutHLS::onHTTP() {
|
||||||
std::string method = H.method;
|
std::string method = H.method;
|
||||||
|
|
||||||
|
if (H.url == "/crossdomain.xml") {
|
||||||
if (H.url == "/crossdomain.xml"){
|
|
||||||
H.Clean();
|
H.Clean();
|
||||||
H.SetHeader("Content-Type", "text/xml");
|
H.SetHeader("Content-Type", "text/xml");
|
||||||
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
|
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
|
||||||
|
@ -177,26 +334,80 @@ namespace Mist {
|
||||||
return;
|
return;
|
||||||
} //crossdomain.xml
|
} //crossdomain.xml
|
||||||
|
|
||||||
if (H.url.find("hls") == std::string::npos){
|
if (H.method == "OPTIONS") {
|
||||||
|
H.Clean();
|
||||||
|
H.SetHeader("Content-Type", "application/octet-stream");
|
||||||
|
H.SetHeader("Cache-Control", "no-cache");
|
||||||
|
H.setCORSHeaders();
|
||||||
|
H.SetBody("");
|
||||||
|
H.SendResponse("200", "OK", myConn);
|
||||||
|
H.Clean();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (H.url.find("hls") == std::string::npos) {
|
||||||
myConn.close();
|
myConn.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
appleCompat = (H.GetHeader("User-Agent").find("iPad") != std::string::npos) || (H.GetHeader("User-Agent").find("iPod") != std::string::npos)|| (H.GetHeader("User-Agent").find("iPhone") != std::string::npos);
|
|
||||||
|
appleCompat = (H.GetHeader("User-Agent").find("iPad") != std::string::npos) || (H.GetHeader("User-Agent").find("iPod") != std::string::npos) || (H.GetHeader("User-Agent").find("iPhone") != std::string::npos);
|
||||||
bool VLCworkaround = false;
|
bool VLCworkaround = false;
|
||||||
if (H.GetHeader("User-Agent").substr(0, 3) == "VLC"){
|
if (H.GetHeader("User-Agent").substr(0, 3) == "VLC") {
|
||||||
std::string vlcver = H.GetHeader("User-Agent").substr(4);
|
std::string vlcver = H.GetHeader("User-Agent").substr(4);
|
||||||
if (vlcver[0] == '0' || vlcver[0] == '1' || (vlcver[0] == '2' && vlcver[2] < '2')){
|
if (vlcver[0] == '0' || vlcver[0] == '1' || (vlcver[0] == '2' && vlcver[2] < '2')) {
|
||||||
DEBUG_MSG(DLVL_INFO, "Enabling VLC version < 2.2.0 bug workaround.");
|
DEBUG_MSG(DLVL_INFO, "Enabling VLC version < 2.2.0 bug workaround.");
|
||||||
VLCworkaround = true;
|
VLCworkaround = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initialize();
|
initialize();
|
||||||
if (H.url.find(".m3u") == std::string::npos){
|
if (H.url.substr(5 + streamName.size(), 5) == "/push"){
|
||||||
std::string tmpStr = H.getUrl().substr(5+streamName.size());
|
std::string relPushUrl = H.url.substr(10 + streamName.size());
|
||||||
|
H.Clean();
|
||||||
|
if (relPushUrl == "/list"){
|
||||||
|
H.SetBody(generatePushList());
|
||||||
|
H.SendResponse("200", "OK", myConn);
|
||||||
|
H.Clean();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (relPushUrl.find(".m3u8") != std::string::npos) {
|
||||||
|
H.SetHeader("Content-Type", "audio/x-mpegurl");
|
||||||
|
} else {
|
||||||
|
H.SetHeader("Content-Type", "audio/mpegurl");
|
||||||
|
}
|
||||||
|
if (relPushUrl == "/index.m3u8"){
|
||||||
|
H.SetHeader("Cache-Control", "no-cache");
|
||||||
|
H.setCORSHeaders();
|
||||||
|
H.SetBody(pushLiveIndex());
|
||||||
|
H.SendResponse("200", "OK", myConn);
|
||||||
|
H.Clean(); //clean for any possible next requests
|
||||||
|
return;
|
||||||
|
}else {
|
||||||
|
unsigned int vTrack;
|
||||||
|
unsigned int aTrack;
|
||||||
|
unsigned long long bTime;
|
||||||
|
unsigned long long eTime;
|
||||||
|
if (sscanf(relPushUrl.c_str(), "/%u_%u/index_%llu_%llu.m3u", &vTrack, &aTrack, &bTime, &eTime) == 4) {
|
||||||
|
if (eTime < bTime){
|
||||||
|
eTime = bTime;
|
||||||
|
}
|
||||||
|
H.SetHeader("Cache-Control", "no-cache");
|
||||||
|
H.setCORSHeaders();
|
||||||
|
H.SetBody(pushLiveIndex(vTrack, bTime, eTime));
|
||||||
|
H.SendResponse("200", "OK", myConn);
|
||||||
|
H.Clean(); //clean for any possible next requests
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
H.SetBody("The HLS URL wasn't understood - what did you want, exactly?\n");
|
||||||
|
myConn.SendNow(H.BuildResponse("404", "URL mismatch"));
|
||||||
|
H.Clean(); //clean for any possible next requests
|
||||||
|
return;
|
||||||
|
}else if (H.url.find(".m3u") == std::string::npos) {
|
||||||
|
std::string tmpStr = H.getUrl().substr(5 + streamName.size());
|
||||||
long long unsigned int from;
|
long long unsigned int from;
|
||||||
if (sscanf(tmpStr.c_str(), "/%u_%u/%llu_%llu.ts", &vidTrack, &audTrack, &from, &until) != 4){
|
if (sscanf(tmpStr.c_str(), "/%u_%u/%llu_%llu.ts", &vidTrack, &audTrack, &from, &until) != 4) {
|
||||||
if (sscanf(tmpStr.c_str(), "/%u/%llu_%llu.ts", &vidTrack, &from, &until) != 3){
|
if (sscanf(tmpStr.c_str(), "/%u/%llu_%llu.ts", &vidTrack, &from, &until) != 3) {
|
||||||
DEBUG_MSG(DLVL_MEDIUM, "Could not parse URL: %s", H.getUrl().c_str());
|
DEBUG_MSG(DLVL_MEDIUM, "Could not parse URL: %s", H.getUrl().c_str());
|
||||||
H.Clean();
|
H.Clean();
|
||||||
H.setCORSHeaders();
|
H.setCORSHeaders();
|
||||||
|
@ -204,33 +415,38 @@ namespace Mist {
|
||||||
myConn.SendNow(H.BuildResponse("404", "URL mismatch"));
|
myConn.SendNow(H.BuildResponse("404", "URL mismatch"));
|
||||||
H.Clean(); //clean for any possible next requests
|
H.Clean(); //clean for any possible next requests
|
||||||
return;
|
return;
|
||||||
}else{
|
} else {
|
||||||
selectedTracks.clear();
|
selectedTracks.clear();
|
||||||
selectedTracks.insert(vidTrack);
|
selectedTracks.insert(vidTrack);
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
selectedTracks.clear();
|
selectedTracks.clear();
|
||||||
selectedTracks.insert(vidTrack);
|
selectedTracks.insert(vidTrack);
|
||||||
selectedTracks.insert(audTrack);
|
selectedTracks.insert(audTrack);
|
||||||
}
|
}
|
||||||
|
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||||
|
if (it->second.codec == "ID3"){
|
||||||
|
selectedTracks.insert(it->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (myMeta.live){
|
if (myMeta.live) {
|
||||||
unsigned int timeout = 0;
|
unsigned int timeout = 0;
|
||||||
int seekable;
|
int seekable;
|
||||||
do {
|
do {
|
||||||
seekable = canSeekms(from);
|
seekable = canSeekms(from);
|
||||||
/// \todo Detection of out-of-range parts.
|
/// \todo Detection of out-of-range parts.
|
||||||
if (seekable > 0){
|
if (seekable > 0) {
|
||||||
//time out after 21 seconds
|
//time out after 21 seconds
|
||||||
if (++timeout > 42){
|
if (++timeout > 42) {
|
||||||
myConn.close();
|
myConn.close();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Util::sleep(500);
|
Util::sleep(500);
|
||||||
updateMeta();
|
updateMeta();
|
||||||
}
|
}
|
||||||
}while (myConn && seekable > 0);
|
} while (myConn && seekable > 0);
|
||||||
if (seekable < 0){
|
if (seekable < 0) {
|
||||||
H.Clean();
|
H.Clean();
|
||||||
H.setCORSHeaders();
|
H.setCORSHeaders();
|
||||||
H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be served.\n");
|
H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be served.\n");
|
||||||
|
@ -256,12 +472,12 @@ namespace Mist {
|
||||||
H.StartResponse(H, myConn, VLCworkaround);
|
H.StartResponse(H, myConn, VLCworkaround);
|
||||||
|
|
||||||
unsigned int fragCounter = myMeta.tracks[vidTrack].missedFrags;
|
unsigned int fragCounter = myMeta.tracks[vidTrack].missedFrags;
|
||||||
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[vidTrack].fragments.begin(); it != myMeta.tracks[vidTrack].fragments.end(); it++){
|
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[vidTrack].fragments.begin(); it != myMeta.tracks[vidTrack].fragments.end(); it++) {
|
||||||
long long int starttime = myMeta.tracks[vidTrack].getKey(it->getNumber()).getTime();
|
long long int starttime = myMeta.tracks[vidTrack].getKey(it->getNumber()).getTime();
|
||||||
if (starttime <= from && starttime + it->getDuration() > from){
|
if (starttime <= from && starttime + it->getDuration() > from) {
|
||||||
EXTREME_MSG("setting continuity counter for PAT/PMT to %d",fragCounter);
|
EXTREME_MSG("setting continuity counter for PAT/PMT to %d", fragCounter);
|
||||||
contCounters[0]=fragCounter; //PAT continuity counter
|
contCounters[0] = fragCounter; //PAT continuity counter
|
||||||
contCounters[4096]=fragCounter; //PMT continuity counter
|
contCounters[4096] = fragCounter; //PMT continuity counter
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++fragCounter;
|
++fragCounter;
|
||||||
|
@ -269,13 +485,13 @@ namespace Mist {
|
||||||
packCounter = 0;
|
packCounter = 0;
|
||||||
parseData = true;
|
parseData = true;
|
||||||
wantRequest = false;
|
wantRequest = false;
|
||||||
}else{
|
} else {
|
||||||
initialize();
|
initialize();
|
||||||
std::string request = H.url.substr(H.url.find("/", 5) + 1);
|
std::string request = H.url.substr(H.url.find("/", 5) + 1);
|
||||||
H.Clean();
|
H.Clean();
|
||||||
if (H.url.find(".m3u8") != std::string::npos){
|
if (H.url.find(".m3u8") != std::string::npos) {
|
||||||
H.SetHeader("Content-Type", "audio/x-mpegurl");
|
H.SetHeader("Content-Type", "audio/x-mpegurl");
|
||||||
}else{
|
} else {
|
||||||
H.SetHeader("Content-Type", "audio/mpegurl");
|
H.SetHeader("Content-Type", "audio/mpegurl");
|
||||||
}
|
}
|
||||||
H.SetHeader("Cache-Control", "no-cache");
|
H.SetHeader("Cache-Control", "no-cache");
|
||||||
|
@ -286,10 +502,10 @@ namespace Mist {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string manifest;
|
std::string manifest;
|
||||||
if (request.find("/") == std::string::npos){
|
if (request.find("/") == std::string::npos) {
|
||||||
manifest = liveIndex();
|
manifest = liveIndex();
|
||||||
}else{
|
} else {
|
||||||
int selectId = atoi(request.substr(0,request.find("/")).c_str());
|
int selectId = atoi(request.substr(0, request.find("/")).c_str());
|
||||||
manifest = liveIndex(selectId);
|
manifest = liveIndex(selectId);
|
||||||
}
|
}
|
||||||
H.SetBody(manifest);
|
H.SetBody(manifest);
|
||||||
|
@ -298,7 +514,7 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void OutHLS::sendTS(const char * tsData, unsigned int len){
|
void OutHLS::sendTS(const char * tsData, unsigned int len) {
|
||||||
H.Chunkify(tsData, len, myConn);
|
H.Chunkify(tsData, len, myConn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,13 @@ namespace Mist {
|
||||||
protected:
|
protected:
|
||||||
std::string liveIndex();
|
std::string liveIndex();
|
||||||
std::string liveIndex(int tid);
|
std::string liveIndex(int tid);
|
||||||
|
|
||||||
|
|
||||||
|
std::string pushLiveIndex();
|
||||||
|
std::string pushLiveIndex(int tid, unsigned long bTime, unsigned long eTime);
|
||||||
|
|
||||||
|
|
||||||
|
std::string generatePushList();
|
||||||
int canSeekms(unsigned int ms);
|
int canSeekms(unsigned int ms);
|
||||||
int keysToSend;
|
int keysToSend;
|
||||||
unsigned int vidTrack;
|
unsigned int vidTrack;
|
||||||
|
|
|
@ -280,7 +280,7 @@ namespace Mist {
|
||||||
moof_box.setContent(mfhd_box, 0);
|
moof_box.setContent(mfhd_box, 0);
|
||||||
moof_box.setContent(traf_box, 1);
|
moof_box.setContent(traf_box, 1);
|
||||||
/*LTS-START*/
|
/*LTS-START*/
|
||||||
if (encrypt){
|
if (nProxy.encrypt){
|
||||||
MP4::UUID_SampleEncryption sEnc;
|
MP4::UUID_SampleEncryption sEnc;
|
||||||
sEnc.setVersion(0);
|
sEnc.setVersion(0);
|
||||||
if (myMeta.tracks[tid].type == "audio") {
|
if (myMeta.tracks[tid].type == "audio") {
|
||||||
|
@ -340,10 +340,10 @@ namespace Mist {
|
||||||
//Load the encryption data page
|
//Load the encryption data page
|
||||||
char pageName[NAME_BUFFER_SIZE];
|
char pageName[NAME_BUFFER_SIZE];
|
||||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_ENCRYPT, streamName.c_str());
|
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_ENCRYPT, streamName.c_str());
|
||||||
encryptionPage.init(pageName, 8 * 1024 * 1024, false, false);
|
nProxy.encryptionPage.init(pageName, 8 * 1024 * 1024, false, false);
|
||||||
if (encryptionPage.mapped) {
|
if (nProxy.encryptionPage.mapped) {
|
||||||
vmData.read(encryptionPage.mapped);
|
nProxy.vmData.read(nProxy.encryptionPage.mapped);
|
||||||
encrypt = true;
|
nProxy.encrypt = true;
|
||||||
}
|
}
|
||||||
encryptionLoaded = true;
|
encryptionLoaded = true;
|
||||||
}
|
}
|
||||||
|
@ -352,9 +352,9 @@ namespace Mist {
|
||||||
std::string OutHSS::protectionHeader() {
|
std::string OutHSS::protectionHeader() {
|
||||||
loadEncryption();
|
loadEncryption();
|
||||||
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>";
|
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 += vmData.keyid;
|
xmlGen += nProxy.vmData.keyid;
|
||||||
xmlGen += "</KID><LA_URL>";
|
xmlGen += "</KID><LA_URL>";
|
||||||
xmlGen += vmData.laurl;
|
xmlGen += nProxy.vmData.laurl;
|
||||||
xmlGen += "</LA_URL></DATA></WRMHEADER>";
|
xmlGen += "</LA_URL></DATA></WRMHEADER>";
|
||||||
std::string tmp = toUTF16(xmlGen);
|
std::string tmp = toUTF16(xmlGen);
|
||||||
tmp = tmp.substr(2);
|
tmp = tmp.substr(2);
|
||||||
|
@ -512,7 +512,7 @@ namespace Mist {
|
||||||
Result << "</StreamIndex>\n";
|
Result << "</StreamIndex>\n";
|
||||||
}
|
}
|
||||||
/*LTS-START*/
|
/*LTS-START*/
|
||||||
if (encrypt) {
|
if (nProxy.encrypt) {
|
||||||
Result << "<Protection><ProtectionHeader SystemID=\"9a04f079-9840-4286-ab92-e65be0885f95\">";
|
Result << "<Protection><ProtectionHeader SystemID=\"9a04f079-9840-4286-ab92-e65be0885f95\">";
|
||||||
Result << protectionHeader();
|
Result << protectionHeader();
|
||||||
Result << "</ProtectionHeader></Protection>";
|
Result << "</ProtectionHeader></Protection>";
|
||||||
|
|
|
@ -173,7 +173,7 @@ namespace Mist {
|
||||||
if (handler != capa["name"].asStringRef() || H.GetVar("stream") != streamName){
|
if (handler != capa["name"].asStringRef() || H.GetVar("stream") != streamName){
|
||||||
DEBUG_MSG(DLVL_MEDIUM, "Switching from %s (%s) to %s (%s)", capa["name"].asStringRef().c_str(), streamName.c_str(), handler.c_str(), H.GetVar("stream").c_str());
|
DEBUG_MSG(DLVL_MEDIUM, "Switching from %s (%s) to %s (%s)", capa["name"].asStringRef().c_str(), streamName.c_str(), handler.c_str(), H.GetVar("stream").c_str());
|
||||||
streamName = H.GetVar("stream");
|
streamName = H.GetVar("stream");
|
||||||
userClient.finish();
|
nProxy.userClient.finish();
|
||||||
statsPage.finish();
|
statsPage.finish();
|
||||||
reConnector(handler);
|
reConnector(handler);
|
||||||
H.Clean();
|
H.Clean();
|
||||||
|
@ -396,8 +396,6 @@ namespace Mist {
|
||||||
return trustedProxies.count(ip);
|
return trustedProxies.count(ip);
|
||||||
}
|
}
|
||||||
/*LTS-END*/
|
/*LTS-END*/
|
||||||
|
|
||||||
|
|
||||||
/*begin-roxlu*/
|
/*begin-roxlu*/
|
||||||
void HTTPOutput::sendResponse(std::string message, std::string code) {
|
void HTTPOutput::sendResponse(std::string message, std::string code) {
|
||||||
|
|
||||||
|
|
346
src/output/output_push.cpp
Normal file
346
src/output/output_push.cpp
Normal file
|
@ -0,0 +1,346 @@
|
||||||
|
#include "output_push.h"
|
||||||
|
#include <mist/http_parser.h>
|
||||||
|
#include <mist/shared_memory.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <mist/tinythread.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define PUSH_INDEX_SIZE 5 //Build index based on most recent X segments
|
||||||
|
|
||||||
|
Util::Config * pConf;
|
||||||
|
std::string sName;
|
||||||
|
|
||||||
|
std::string baseURL;
|
||||||
|
|
||||||
|
long long srcPort;
|
||||||
|
std::string srcHost;
|
||||||
|
|
||||||
|
std::string dstHost;
|
||||||
|
long long dstPort;
|
||||||
|
std::string dstUrl;
|
||||||
|
|
||||||
|
//Used to keep track of all segments that can be pushed
|
||||||
|
std::map<std::string, std::map<int, std::string> > pushableSegments;
|
||||||
|
//Used to keep track of the timestamp of each pushableSegment
|
||||||
|
std::map<std::string, std::map<int, int> > pushableTimes;
|
||||||
|
//Used to keep track of the duration of each pushableSegment
|
||||||
|
std::map<std::string, std::map<int, int> > pushableDurations;
|
||||||
|
|
||||||
|
|
||||||
|
//For each quality, store the latest number found in the push list
|
||||||
|
std::map<std::string, int> latestNumber;
|
||||||
|
|
||||||
|
//For each quality, store whether it is currently being pushed.
|
||||||
|
std::map<std::string, bool> parsing;
|
||||||
|
|
||||||
|
//For each quality, store an fprint-style string of the relative url to the index_<beginTime>_<endTime>.m3u8
|
||||||
|
std::map<std::string, std::string> qualityIndex;
|
||||||
|
//For each quality, store an fprint-style string of the relative url to the segment.
|
||||||
|
std::map<std::string, std::string> qualitySegment;
|
||||||
|
|
||||||
|
//For each quality, store the last PUSH_INDEX_SIZE - 1 timestamps. Used to generate a time-constrained index.m3u8.
|
||||||
|
std::map<std::string, std::deque<int> > qualityBeginTimes;
|
||||||
|
|
||||||
|
|
||||||
|
//Parses a uri of the form 'http://<host>[:<port>]/<url>, and split it into variables
|
||||||
|
void parseURI(const std::string & uri, std::string & host, long long & port, std::string & url){
|
||||||
|
int loc = 0;
|
||||||
|
if (uri.find("http://") == 0){
|
||||||
|
loc += 7;
|
||||||
|
}
|
||||||
|
host = uri.substr(loc, uri.find_first_of(":/", 7) - 7);
|
||||||
|
loc += host.size();
|
||||||
|
if (uri[loc] == ':'){
|
||||||
|
port = atoll(uri.c_str() + loc + 1);
|
||||||
|
loc = uri.find("/", loc);
|
||||||
|
}
|
||||||
|
url = uri.substr(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Do an HTTP request, and route it into a post request on a different socket.
|
||||||
|
void proxyToPost(Socket::Connection & src, const std::string & srcUrl, Socket::Connection & dst, const std::string & dstUrl){
|
||||||
|
INFO_MSG("Routing %s to %s", srcUrl.c_str(), dstUrl.c_str());
|
||||||
|
|
||||||
|
//Send the initial request
|
||||||
|
HTTP::Parser H;
|
||||||
|
H.url = srcUrl;
|
||||||
|
H.SendRequest(src);
|
||||||
|
H.Clean();
|
||||||
|
|
||||||
|
//Read only the headers of the reply
|
||||||
|
H.headerOnly = true;
|
||||||
|
while (src.connected()){
|
||||||
|
if (src.Received().size() || src.spool()){
|
||||||
|
if (H.Read(src)){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
H.headerOnly = false;
|
||||||
|
|
||||||
|
INFO_MSG("Reply from %s: %s %s", src.getHost().c_str(), H.url.c_str(), H.method.c_str());
|
||||||
|
|
||||||
|
//Change the headers of the reply to form a post request
|
||||||
|
H.method = "POST";
|
||||||
|
H.url = dstUrl;
|
||||||
|
H.protocol = "HTTP/1.1";
|
||||||
|
H.SetHeader("Host", dstHost);
|
||||||
|
//Start the post request
|
||||||
|
H.SendRequest(dst);
|
||||||
|
//Route the original payload.
|
||||||
|
H.Proxy(src, dst);
|
||||||
|
|
||||||
|
H.Clean();
|
||||||
|
while (dst.connected()){
|
||||||
|
if (dst.Received().size() || dst.spool()){
|
||||||
|
if (H.Read(dst)){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
INFO_MSG("Reply from %s: %s %s", dst.getHost().c_str(), H.url.c_str(), H.method.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///Push the first registered segment for this quality
|
||||||
|
void pushFirstElement(std::string qId) {
|
||||||
|
std::string semName = "MstPushLock" + sName;
|
||||||
|
IPC::semaphore pushLock(semName.c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||||
|
|
||||||
|
std::string url;
|
||||||
|
int time;
|
||||||
|
int beginTime;
|
||||||
|
int duration;
|
||||||
|
//Wait for exclusive access to all globals
|
||||||
|
pushLock.wait();
|
||||||
|
//Retrieve all globals for the segment to be pushed
|
||||||
|
if (pushableSegments[qId].size()){
|
||||||
|
url = pushableSegments[qId].begin()->second;
|
||||||
|
time = pushableTimes[qId].begin()->second;
|
||||||
|
duration = pushableDurations[qId].begin()->second;
|
||||||
|
if (qualityBeginTimes[qId].size()){
|
||||||
|
beginTime = qualityBeginTimes[qId].front();
|
||||||
|
}else{
|
||||||
|
beginTime = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Give up exclusive access to all globals
|
||||||
|
pushLock.post();
|
||||||
|
//Return if we do not have a segment to push
|
||||||
|
if (url == ""){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create both source and destination connections
|
||||||
|
Socket::Connection srcConn(srcHost, srcPort, true);
|
||||||
|
Socket::Connection dstConn(dstHost, dstPort, true);
|
||||||
|
|
||||||
|
//Set the locations to push to for this segment
|
||||||
|
std::string srcLocation = baseURL + url;
|
||||||
|
std::string dstLocation = dstUrl.substr(0, dstUrl.rfind("/")) + url;
|
||||||
|
|
||||||
|
//Push the segment
|
||||||
|
proxyToPost(srcConn, srcLocation, dstConn, dstLocation);
|
||||||
|
|
||||||
|
|
||||||
|
srcConn = Socket::Connection(srcHost, srcPort, true);
|
||||||
|
|
||||||
|
//Set the location to push to for the index containing this segment.
|
||||||
|
//The index will contain (at most) the last PUSH_INDEX_SIZE segments.
|
||||||
|
char srcIndex[200];
|
||||||
|
snprintf(srcIndex, 200, qualityIndex[qId].c_str(), beginTime , time + duration);
|
||||||
|
srcLocation = baseURL + srcIndex;
|
||||||
|
dstLocation = dstLocation.substr(0, dstLocation.rfind("/")) + "/index.m3u8";
|
||||||
|
|
||||||
|
//Push the index
|
||||||
|
proxyToPost(srcConn, srcLocation, dstConn, dstLocation);
|
||||||
|
|
||||||
|
|
||||||
|
srcConn = Socket::Connection(srcHost, srcPort, true);
|
||||||
|
|
||||||
|
//Set the location to push to for the global index containing all qualities.
|
||||||
|
srcLocation = baseURL + "/push/index.m3u8";
|
||||||
|
dstLocation = dstLocation.substr(0, dstLocation.rfind("/"));
|
||||||
|
dstLocation = dstLocation.substr(0, dstLocation.rfind("/")) + "/index.m3u8";
|
||||||
|
|
||||||
|
//Push the global index
|
||||||
|
proxyToPost(srcConn, srcLocation, dstConn, dstLocation);
|
||||||
|
|
||||||
|
//Close both connections
|
||||||
|
///\todo Make the dstConn "persistent" for each thread?
|
||||||
|
srcConn.close();
|
||||||
|
dstConn.close();
|
||||||
|
|
||||||
|
//Wait for exclusive access to all globals
|
||||||
|
pushLock.wait();
|
||||||
|
//Update all globals to indicate the segment has been pushed correctly
|
||||||
|
pushableSegments[qId].erase(pushableSegments[qId].begin());
|
||||||
|
pushableTimes[qId].erase(pushableTimes[qId].begin());
|
||||||
|
pushableDurations[qId].erase(pushableDurations[qId].begin());
|
||||||
|
qualityBeginTimes[qId].push_back(time);
|
||||||
|
//Remove the first elements fromt he beginTimes map to make sure we have PUSH_INDEX_SIZE elements in our index.
|
||||||
|
//We use -1 here, because we use the segment to currently push as well as everything stored in the map
|
||||||
|
while (qualityBeginTimes[qId].size() > PUSH_INDEX_SIZE - 1){
|
||||||
|
qualityBeginTimes[qId].pop_front();
|
||||||
|
}
|
||||||
|
//Give up exclusive access to all globals
|
||||||
|
pushLock.post();
|
||||||
|
}
|
||||||
|
|
||||||
|
///Thread used to push data.
|
||||||
|
void pushThread(void * nullPointer){
|
||||||
|
std::string myThread;
|
||||||
|
|
||||||
|
//Attempt to claim a non-claimed quality.
|
||||||
|
std::string semName = "MstPushClaim" + sName;
|
||||||
|
IPC::semaphore pushThreadLock(semName.c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||||
|
|
||||||
|
pushThreadLock.wait();
|
||||||
|
for (std::map<std::string, std::map<int, std::string> >::iterator it = pushableSegments.begin(); it != pushableSegments.end(); it++){
|
||||||
|
if (it->second.size()){//Make sure we dont try to "claim" pushing an empty track
|
||||||
|
if (!parsing.count(it->first) || !parsing[it->first]){
|
||||||
|
INFO_MSG("Claiming thread %s", it->first.c_str());
|
||||||
|
myThread = it->first;
|
||||||
|
parsing[it->first] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pushThreadLock.post();
|
||||||
|
|
||||||
|
//Return if we were unable to claim a quality
|
||||||
|
if (myThread == ""){
|
||||||
|
INFO_MSG("No thread claimed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//While this output is active, push the first element in the list
|
||||||
|
while (pConf->is_active){
|
||||||
|
pushFirstElement(myThread);
|
||||||
|
if (!pushableSegments[myThread].size()){
|
||||||
|
Util::sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsing[myThread] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace Mist {
|
||||||
|
|
||||||
|
OutPush::OutPush(Socket::Connection & conn) : Output(conn){
|
||||||
|
config->activate();
|
||||||
|
}
|
||||||
|
OutPush::~OutPush(){}
|
||||||
|
|
||||||
|
void OutPush::requestHandler() {
|
||||||
|
//Set aal basic data only the first time.
|
||||||
|
if (streamName == ""){
|
||||||
|
srcPort = 80;
|
||||||
|
parseURI(config->getString("pushlist"), srcHost, srcPort, pushURL);
|
||||||
|
dstPort = 80;
|
||||||
|
parseURI(config->getString("destination"), dstHost, dstPort, dstUrl);
|
||||||
|
|
||||||
|
|
||||||
|
//Strip "/push/list" from the URL
|
||||||
|
baseURL = pushURL.substr(0, pushURL.rfind("/"));
|
||||||
|
baseURL = baseURL.substr(0, baseURL.rfind("/"));
|
||||||
|
|
||||||
|
//Locate the streamname from the pushURL
|
||||||
|
int loc = baseURL.find("/", 1) + 1;
|
||||||
|
streamName = pushURL.substr(loc, pushURL.rfind("/") - loc);
|
||||||
|
sName = streamName;
|
||||||
|
|
||||||
|
INFO_MSG("host: %s, port %lld, url %s, baseURL %s, streamName %s", srcHost.c_str(), srcPort, pushURL.c_str(), baseURL.c_str(), streamName.c_str());
|
||||||
|
}
|
||||||
|
//Reconnect when disconnected
|
||||||
|
if (!listConn.connected()){
|
||||||
|
listConn = Socket::Connection(srcHost, srcPort, true);
|
||||||
|
}
|
||||||
|
//Request the push list
|
||||||
|
if (listConn.connected()){
|
||||||
|
HTTP::Parser hReq;
|
||||||
|
hReq.url = baseURL + "/push/list";
|
||||||
|
hReq.SendRequest(listConn);
|
||||||
|
hReq.Clean();
|
||||||
|
//Read the entire response, not just the headers!
|
||||||
|
while (!hReq.Read(listConn) && listConn.connected()){
|
||||||
|
Util::sleep(100);
|
||||||
|
listConn.spool();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Construct and parse the json list
|
||||||
|
JSON::Value reply = JSON::fromString(hReq.body);
|
||||||
|
int numQualities = reply["qualities"].size();
|
||||||
|
for (int i = 0; i < numQualities; i++){
|
||||||
|
JSON::Value & qRef = reply["qualities"][i];
|
||||||
|
std::string qId = qRef["id"].asString();
|
||||||
|
|
||||||
|
//Set both the index and segment urls when not yet set.
|
||||||
|
if (!qualityIndex.count(qId)){
|
||||||
|
qualityIndex[qId] = qRef["index"].asString();
|
||||||
|
qualitySegment[qId] = qRef["segment"].asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Save latest segment number before parsing
|
||||||
|
int curLatestNumber = latestNumber[qId];
|
||||||
|
|
||||||
|
//Loop over all segments
|
||||||
|
for (int j = 0; j < qRef["segments"].size(); j++){
|
||||||
|
JSON::Value & segRef = qRef["segments"][j];
|
||||||
|
int thisNumber = segRef["number"].asInt();
|
||||||
|
|
||||||
|
//Check if this segment is newer than the newest segment before parsing
|
||||||
|
if (thisNumber > curLatestNumber){
|
||||||
|
//If it is the highest so far, store its number
|
||||||
|
if (thisNumber > latestNumber[qId]){
|
||||||
|
latestNumber[qId] = thisNumber;
|
||||||
|
}
|
||||||
|
//If it is not yet added, add it.
|
||||||
|
if (!pushableSegments[qId].count(thisNumber)){
|
||||||
|
char segmentUrl[200];
|
||||||
|
//The qualitySegment map contains a printf-style string
|
||||||
|
snprintf(segmentUrl, 200, qualitySegment[qId].c_str(), segRef["time"].asInt(), segRef["time"].asInt() + segRef["duration"].asInt());
|
||||||
|
pushableSegments[qId][segRef["number"].asInt()] = segmentUrl;
|
||||||
|
pushableTimes[qId][segRef["number"].asInt()] = segRef["time"].asInt();
|
||||||
|
pushableDurations[qId][segRef["number"].asInt()] = segRef["duration"].asInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Calculate how many qualities are not yet being pushed
|
||||||
|
int threadsToSpawn = pushableSegments.size();
|
||||||
|
for (std::map<std::string, std::map<int, std::string> >::iterator it = pushableSegments.begin(); it != pushableSegments.end(); it++){
|
||||||
|
if (parsing.count(it->first) && parsing[it->first]){
|
||||||
|
threadsToSpawn --;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//And start a thread for each unpushed quality.
|
||||||
|
//Threads determine which quality to push for themselves.
|
||||||
|
for (int i = 0; i < threadsToSpawn; i++){
|
||||||
|
tthread::thread thisThread(pushThread, 0);
|
||||||
|
thisThread.detach();
|
||||||
|
}
|
||||||
|
Util::sleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutPush::init(Util::Config * cfg){
|
||||||
|
Output::init(cfg);
|
||||||
|
capa["name"] = "Push";
|
||||||
|
capa["desc"] = "Enables HTTP Pushing.";
|
||||||
|
capa["required"]["pushlist"]["name"] = "URL location of the pushing list";
|
||||||
|
capa["required"]["pushlist"]["help"] = "This is the location that will be checked for pushable data.";
|
||||||
|
capa["required"]["pushlist"]["option"] = "--pushlist";
|
||||||
|
capa["required"]["pushlist"]["type"] = "str";
|
||||||
|
cfg->addOption("pushlist", JSON::fromString("{\"arg\":\"string\",\"short\":\"p\",\"long\":\"pushlist\",\"help\":\"This is the location that will be checked for pushable data.\"}"));
|
||||||
|
capa["required"]["destination"]["name"] = "URL location of the destination";
|
||||||
|
capa["required"]["destination"]["help"] = "This is the location that the date will be pushed to.";
|
||||||
|
capa["required"]["destination"]["option"] = "--destination";
|
||||||
|
capa["required"]["destination"]["type"] = "str";
|
||||||
|
cfg->addOption("destination", JSON::fromString("{\"arg\":\"string\",\"short\":\"D\",\"long\":\"destination\",\"help\":\"This is the location that the data will be checked for pushed to.\"}"));
|
||||||
|
cfg->addBasicConnectorOptions(capa);
|
||||||
|
pConf = cfg;
|
||||||
|
config = cfg;
|
||||||
|
}
|
||||||
|
}
|
19
src/output/output_push.h
Normal file
19
src/output/output_push.h
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#include <mist/socket.h>
|
||||||
|
|
||||||
|
#include "output.h"
|
||||||
|
|
||||||
|
namespace Mist {
|
||||||
|
class OutPush : public Output {
|
||||||
|
public:
|
||||||
|
OutPush(Socket::Connection & conn);
|
||||||
|
~OutPush();
|
||||||
|
static bool listenMode(){return false;}
|
||||||
|
virtual void requestHandler();
|
||||||
|
static void init(Util::Config * cfg);
|
||||||
|
protected:
|
||||||
|
Socket::Connection listConn;
|
||||||
|
std::string pushURL;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef Mist::OutPush mistOut;
|
|
@ -903,12 +903,13 @@ namespace Mist {
|
||||||
}
|
}
|
||||||
JSON::Value pack_out = F.toJSON(myMeta, *amf_storage, next.cs_id*3 + (F.data[0] == 0x09 ? 0 : (F.data[0] == 0x08 ? 1 : 2) ));
|
JSON::Value pack_out = F.toJSON(myMeta, *amf_storage, next.cs_id*3 + (F.data[0] == 0x09 ? 0 : (F.data[0] == 0x08 ? 1 : 2) ));
|
||||||
if ( !pack_out.isNull()){
|
if ( !pack_out.isNull()){
|
||||||
if (!userClient.getData()){
|
if (!nProxy.userClient.getData()){
|
||||||
char userPageName[NAME_BUFFER_SIZE];
|
char userPageName[NAME_BUFFER_SIZE];
|
||||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||||
userClient = IPC::sharedClient(userPageName, 30, true);
|
nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||||
}
|
}
|
||||||
continueNegotiate(pack_out["trackid"].asInt());
|
continueNegotiate(pack_out["trackid"].asInt());
|
||||||
|
nProxy.streamName = streamName;
|
||||||
bufferLivePacket(pack_out);
|
bufferLivePacket(pack_out);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -33,7 +33,7 @@ namespace Mist {
|
||||||
|
|
||||||
if (packData.getBytesFree() == 184){
|
if (packData.getBytesFree() == 184){
|
||||||
packData.clear();
|
packData.clear();
|
||||||
packData.setPID(0x100 - 1 + thisPacket.getTrackId());
|
packData.setPID(thisPacket.getTrackId());
|
||||||
packData.setContinuityCounter(++contCounters[packData.getPID()]);
|
packData.setContinuityCounter(++contCounters[packData.getPID()]);
|
||||||
if (first[thisPacket.getTrackId()]){
|
if (first[thisPacket.getTrackId()]){
|
||||||
packData.setUnitStart(1);
|
packData.setUnitStart(1);
|
||||||
|
|
Loading…
Add table
Reference in a new issue