Backported various little edits from Pro edition.
This commit is contained in:
parent
ef9938da0c
commit
4c9c6fa7ba
78 changed files with 2334 additions and 1266 deletions
|
@ -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)
|
|
@ -23,7 +23,10 @@ namespace Analysers {
|
|||
std::cerr << "Not a valid DTSC file" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
F.getMeta().toPrettyString(std::cout,0, 0x03);
|
||||
|
||||
if (F.getMeta().vod || F.getMeta().live){
|
||||
F.getMeta().toPrettyString(std::cout,0, 0x03);
|
||||
}
|
||||
|
||||
int bPos = 0;
|
||||
F.seek_bpos(0);
|
||||
|
@ -42,6 +45,10 @@ namespace Analysers {
|
|||
std::cout << "DTSC header: " << F.getPacket().getScan().toPrettyString() << std::endl;
|
||||
break;
|
||||
}
|
||||
case DTSC::DTCM: {
|
||||
std::cout << "DTCM command: " << F.getPacket().getScan().toPrettyString() << std::endl;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
DEBUG_MSG(DLVL_WARN,"Invalid dtsc packet @ bpos %d", bPos);
|
||||
break;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <string.h>
|
||||
#include <mist/mp4.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
///\brief Holds everything unique to the analysers.
|
||||
namespace Analysers {
|
||||
|
@ -25,9 +26,15 @@ namespace Analysers {
|
|||
mp4Buffer.erase(mp4Buffer.size() - 1, 1);
|
||||
|
||||
MP4::Box mp4Data;
|
||||
int dataSize = mp4Buffer.size();
|
||||
int curPos = 0;
|
||||
while (mp4Data.read(mp4Buffer)){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos);
|
||||
std::cerr << mp4Data.toPrettyString(0) << std::endl;
|
||||
curPos += dataSize - mp4Buffer.size();
|
||||
dataSize = mp4Buffer.size();
|
||||
}
|
||||
DEBUG_MSG(DLVL_DEVEL, "Stopped parsing at position %d", curPos);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/// \page api API calls
|
||||
/// \brief Listing of all controller API calls.
|
||||
/// The controller listens for commands through a JSON-based API. This page describes the API in full.
|
||||
///
|
||||
/// A default interface implementing this API as a single HTML page is included in the controller itself. This default interface will be send for invalid API requests, and is thus triggered by default when a browser attempts to access the API port directly.
|
||||
|
@ -20,7 +21,9 @@
|
|||
///
|
||||
/// You may also include a `"callback"` or `"jsonp"` HTTP variable, to trigger JSONP compatibility mode. JSONP is useful for getting around the cross-domain scripting protection in most modern browsers. Developers creating non-JavaScript applications will most likely not want to use JSONP mode, though nothing is stopping you if you really want to.
|
||||
///
|
||||
/// \brief Listing of all controller API calls.
|
||||
|
||||
|
||||
|
||||
|
||||
/// \file controller.cpp
|
||||
/// Contains all code for the controller executable.
|
||||
|
@ -88,6 +91,7 @@ void createAccount (std::string account){
|
|||
/// Status monitoring thread.
|
||||
/// Will check outputs, inputs and converters every five seconds
|
||||
void statusMonitor(void * np){
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
while (Controller::conf.is_active){
|
||||
//this scope prevents the configMutex from being locked constantly
|
||||
{
|
||||
|
@ -99,19 +103,20 @@ void statusMonitor(void * np){
|
|||
changed |= Controller::CheckAllStreams(Controller::Storage["streams"]);
|
||||
|
||||
//check if the config semaphore is stuck, by trying to lock it for 5 attempts of 1 second...
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
if (!configLock.tryWaitOneSecond() && !configLock.tryWaitOneSecond() && !configLock.tryWaitOneSecond() && !configLock.tryWaitOneSecond()){
|
||||
//that failed. We now unlock it, no matter what - and print a warning that it was stuck.
|
||||
WARN_MSG("Configuration semaphore was stuck. Force-unlocking it and re-writing config.");
|
||||
changed = true;
|
||||
}
|
||||
configLock.post();
|
||||
if (changed){
|
||||
if (changed || Controller::configChanged){
|
||||
Controller::writeConfig();
|
||||
Controller::configChanged = false;
|
||||
}
|
||||
}
|
||||
Util::wait(5000);//wait at least 5 seconds
|
||||
}
|
||||
configLock.unlink();
|
||||
}
|
||||
|
||||
///\brief The main entry point for the controller.
|
||||
|
@ -134,10 +139,9 @@ int main(int argc, char ** argv){
|
|||
stored_user["default"] = "root";
|
||||
}
|
||||
Controller::conf = Util::Config(argv[0]);
|
||||
Controller::conf.addOption("listen_port", stored_port);
|
||||
Controller::conf.addOption("listen_interface", stored_interface);
|
||||
Controller::conf.addOption("port", stored_port);
|
||||
Controller::conf.addOption("interface", stored_interface);
|
||||
Controller::conf.addOption("username", stored_user);
|
||||
Controller::conf.addOption("daemonize", JSON::fromString("{\"long\":\"daemon\", \"short\":\"d\", \"default\":0, \"long_off\":\"nodaemon\", \"short_off\":\"n\", \"help\":\"Turns deamon mode on (-d) or off (-n). -d runs quietly in background, -n (default) enables verbose in foreground.\"}"));
|
||||
Controller::conf.addOption("account", JSON::fromString("{\"long\":\"account\", \"short\":\"a\", \"arg\":\"string\" \"default\":\"\", \"help\":\"A username:password string to create a new account with.\"}"));
|
||||
Controller::conf.addOption("logfile", JSON::fromString("{\"long\":\"logfile\", \"short\":\"L\", \"arg\":\"string\" \"default\":\"\",\"help\":\"Redirect all standard output to a log file, provided with an argument\"}"));
|
||||
Controller::conf.addOption("configFile", JSON::fromString("{\"long\":\"config\", \"short\":\"c\", \"arg\":\"string\" \"default\":\"config.json\", \"help\":\"Specify a config file other than default.\"}"));
|
||||
|
@ -183,14 +187,19 @@ int main(int argc, char ** argv){
|
|||
//check for port, interface and username in arguments
|
||||
//if they are not there, take them from config file, if there
|
||||
if (Controller::Storage["config"]["controller"]["port"]){
|
||||
Controller::conf.getOption("listen_port", true)[0u] = Controller::Storage["config"]["controller"]["port"];
|
||||
Controller::conf.getOption("port", true)[0u] = Controller::Storage["config"]["controller"]["port"];
|
||||
}
|
||||
if (Controller::Storage["config"]["controller"]["interface"]){
|
||||
Controller::conf.getOption("listen_interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"];
|
||||
Controller::conf.getOption("interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"];
|
||||
}
|
||||
if (Controller::Storage["config"]["controller"]["username"]){
|
||||
Controller::conf.getOption("username", true)[0u] = Controller::Storage["config"]["controller"]["username"];
|
||||
}
|
||||
{
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.unlink();
|
||||
}
|
||||
Controller::writeConfig();
|
||||
Controller::checkAvailProtocols();
|
||||
createAccount(Controller::conf.getString("account"));
|
||||
|
||||
|
@ -244,16 +253,16 @@ int main(int argc, char ** argv){
|
|||
}else{//logfile is enabled
|
||||
//check for username
|
||||
if ( !Controller::Storage.isMember("account") || Controller::Storage["account"].size() < 1){
|
||||
std::cout << "No login configured. To create one, attempt to login through the web interface on port " << Controller::conf.getInteger("listen_port") << " and follow the instructions." << std::endl;
|
||||
std::cout << "No login configured. To create one, attempt to login through the web interface on port " << Controller::conf.getInteger("port") << " and follow the instructions." << std::endl;
|
||||
}
|
||||
//check for protocols
|
||||
if ( !Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") || Controller::Storage["config"]["protocols"].size() < 1){
|
||||
std::cout << "No protocols enabled, remember to set them up through the web interface on port " << Controller::conf.getInteger("listen_port") << " or API." << std::endl;
|
||||
std::cout << "No protocols enabled, remember to set them up through the web interface on port " << Controller::conf.getInteger("port") << " or API." << std::endl;
|
||||
}
|
||||
}
|
||||
//check for streams - regardless of logfile setting
|
||||
if ( !Controller::Storage.isMember("streams") || Controller::Storage["streams"].size() < 1){
|
||||
std::cout << "No streams configured, remember to set up streams through the web interface on port " << Controller::conf.getInteger("listen_port") << " or API." << std::endl;
|
||||
std::cout << "No streams configured, remember to set up streams through the web interface on port " << Controller::conf.getInteger("port") << " or API." << std::endl;
|
||||
}
|
||||
}//connected to a terminal
|
||||
|
||||
|
@ -274,8 +283,8 @@ int main(int argc, char ** argv){
|
|||
}else{
|
||||
shutdown_reason = "socket problem (API port closed)";
|
||||
}
|
||||
Controller::Log("CONF", "Controller shutting down because of "+shutdown_reason);
|
||||
Controller::conf.is_active = false;
|
||||
Controller::Log("CONF", "Controller shutting down because of "+shutdown_reason);
|
||||
//join all joinable threads
|
||||
statsThread.join();
|
||||
monitorThread.join();
|
||||
|
@ -300,3 +309,4 @@ int main(int argc, char ** argv){
|
|||
std::cout << "Killed all processes, wrote config to disk. Exiting." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -58,9 +58,6 @@
|
|||
/// ~~~~~~~~~~~~~~~
|
||||
void Controller::checkConfig(JSON::Value & in, JSON::Value & out){
|
||||
out = in;
|
||||
if (out["basepath"].asString()[out["basepath"].asString().size() - 1] == '/'){
|
||||
out["basepath"] = out["basepath"].asString().substr(0, out["basepath"].asString().size() - 1);
|
||||
}
|
||||
if (out.isMember("debug")){
|
||||
if (Util::Config::printDebugLevel != out["debug"].asInt()){
|
||||
Util::Config::printDebugLevel = out["debug"].asInt();
|
||||
|
@ -169,6 +166,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
H.SetHeader("X-Info", "To force an API response, request the file /api");
|
||||
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
|
||||
H.SetHeader("Content-Length", server_html_len);
|
||||
H.SetHeader("X-UA-Compatible","IE=edge;chrome=1");
|
||||
H.SendResponse("200", "OK", conn);
|
||||
conn.SendNow(server_html, server_html_len);
|
||||
H.Clean();
|
||||
|
@ -247,6 +245,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
/// ]
|
||||
/// ]
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
///
|
||||
if(Request.isMember("browse")){
|
||||
if(Request["browse"] == ""){
|
||||
Request["browse"] = ".";
|
||||
|
|
|
@ -278,7 +278,11 @@ namespace Controller {
|
|||
unsigned long long c_user, c_nice, c_syst, c_idle, c_total;
|
||||
if (sscanf(line, "cpu %Lu %Lu %Lu %Lu", &c_user, &c_nice, &c_syst, &c_idle) == 4){
|
||||
c_total = c_user + c_nice + c_syst + c_idle;
|
||||
if (c_total - cl_total > 0){
|
||||
capa["cpu_use"] = (long long int)(1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total));
|
||||
}else{
|
||||
capa["cpu_use"] = 0ll;
|
||||
}
|
||||
cl_total = c_total;
|
||||
cl_idle = c_idle;
|
||||
break;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <list>
|
||||
#include <mist/config.h>
|
||||
#include "controller_statistics.h"
|
||||
#include "controller_storage.h"
|
||||
|
||||
// These are used to store "clients" field requests in a bitfield for speedup.
|
||||
#define STAT_CLI_HOST 1
|
||||
|
@ -21,6 +22,8 @@
|
|||
#define STAT_TOT_BPS_UP 4
|
||||
#define STAT_TOT_ALL 0xFF
|
||||
|
||||
#define COUNTABLE_BYTES 128*1024
|
||||
|
||||
|
||||
std::map<Controller::sessIndex, Controller::statSession> Controller::sessions; ///< list of sessions that have statistics data available
|
||||
std::map<unsigned long, Controller::sessIndex> Controller::connToSession; ///< Map of socket IDs to session info.
|
||||
|
@ -37,6 +40,12 @@ Controller::sessIndex::sessIndex(){
|
|||
crc = 0;
|
||||
}
|
||||
|
||||
std::string Controller::sessIndex::toStr(){
|
||||
std::stringstream s;
|
||||
s << host << " " << crc << " " << streamName << " " << connector;
|
||||
return s.str();
|
||||
}
|
||||
|
||||
/// Initializes a sessIndex from a statExchange object, converting binary format IP addresses into strings.
|
||||
/// This extracts the host, stream name, connector and crc field, ignoring everything else.
|
||||
Controller::sessIndex::sessIndex(IPC::statExchange & data){
|
||||
|
@ -80,6 +89,9 @@ bool Controller::sessIndex::operator>= (const Controller::sessIndex &b) const{
|
|||
return !(*this < b);
|
||||
}
|
||||
|
||||
/// \todo Make this prettier.
|
||||
IPC::sharedServer * statPointer = 0;
|
||||
|
||||
|
||||
/// This function runs as a thread and roughly once per second retrieves
|
||||
/// statistics from all connected clients, as well as wipes
|
||||
|
@ -87,9 +99,12 @@ bool Controller::sessIndex::operator>= (const Controller::sessIndex &b) const{
|
|||
void Controller::SharedMemStats(void * config){
|
||||
DEBUG_MSG(DLVL_HIGH, "Starting stats thread");
|
||||
IPC::sharedServer statServer(SHM_STATISTICS, STAT_EX_SIZE, true);
|
||||
statPointer = &statServer;
|
||||
std::set<std::string> inactiveStreams;
|
||||
while(((Util::Config*)config)->is_active){
|
||||
{
|
||||
tthread::lock_guard<tthread::mutex> guard(statsMutex);
|
||||
tthread::lock_guard<tthread::mutex> guard(Controller::configMutex);
|
||||
tthread::lock_guard<tthread::mutex> guard2(statsMutex);
|
||||
//parse current users
|
||||
statServer.parseEach(parseStatistics);
|
||||
//wipe old statistics
|
||||
|
@ -108,8 +123,9 @@ void Controller::SharedMemStats(void * config){
|
|||
}
|
||||
}
|
||||
}
|
||||
Util::sleep(1000);
|
||||
Util::wait(1000);
|
||||
}
|
||||
statPointer = 0;
|
||||
DEBUG_MSG(DLVL_HIGH, "Stopping stats thread");
|
||||
}
|
||||
|
||||
|
@ -147,6 +163,18 @@ void Controller::statSession::wipeOld(unsigned long long cutOff){
|
|||
oldConns.pop_front();
|
||||
}
|
||||
}
|
||||
if (curConns.size()){
|
||||
for (std::map<unsigned long, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
|
||||
while (it->second.log.size() > 1 && it->second.log.begin()->first < cutOff){
|
||||
it->second.log.erase(it->second.log.begin());
|
||||
}
|
||||
if (it->second.log.size()){
|
||||
if (firstSec > it->second.log.begin()->first){
|
||||
firstSec = it->second.log.begin()->first;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Archives the given connection.
|
||||
|
@ -361,6 +389,11 @@ long long Controller::statSession::getBpsUp(unsigned long long t){
|
|||
}
|
||||
}
|
||||
|
||||
Controller::statStorage::statStorage(){
|
||||
removeDown = 0;
|
||||
removeUp = 0;
|
||||
}
|
||||
|
||||
/// Returns true if there is data available for timestamp t.
|
||||
bool Controller::statStorage::hasDataFor(unsigned long long t) {
|
||||
if (!log.size()){return false;}
|
||||
|
@ -390,8 +423,13 @@ void Controller::statStorage::update(IPC::statExchange & data) {
|
|||
statLog tmp;
|
||||
tmp.time = data.time();
|
||||
tmp.lastSecond = data.lastSecond();
|
||||
tmp.down = data.down();
|
||||
tmp.up = data.up();
|
||||
tmp.down = data.down() - removeDown;
|
||||
tmp.up = data.up() - removeUp;
|
||||
if (!log.size() && tmp.down + tmp.up > COUNTABLE_BYTES){
|
||||
//substract the start values if they are too high - this is a resumed connection of some sort
|
||||
removeDown = tmp.down;
|
||||
removeUp = tmp.up;
|
||||
}
|
||||
log[data.now()] = tmp;
|
||||
//wipe data older than approx. STAT_CUTOFF seconds
|
||||
/// \todo Remove least interesting data first.
|
||||
|
@ -420,7 +458,7 @@ void Controller::parseStatistics(char * data, size_t len, unsigned int id){
|
|||
sessions[idx].update(id, tmpEx);
|
||||
//check validity of stats data
|
||||
char counter = (*(data - 1));
|
||||
if (counter == 126 || counter == 127 || counter == 254 || counter == 255){
|
||||
if (counter == 126 || counter == 127){
|
||||
//the data is no longer valid - connection has gone away, store for later
|
||||
sessions[idx].finish(id);
|
||||
connToSession.erase(id);
|
||||
|
@ -432,7 +470,7 @@ bool Controller::hasViewers(std::string streamName){
|
|||
if (sessions.size()){
|
||||
long long currTime = Util::epoch();
|
||||
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
|
||||
if (it->first.streamName == streamName && it->second.hasDataFor(currTime)){
|
||||
if (it->first.streamName == streamName && (it->second.hasDataFor(currTime) || it->second.hasDataFor(currTime-1))){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#pragma once
|
||||
#include <mist/shared_memory.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/defines.h>
|
||||
|
@ -36,11 +37,16 @@ namespace Controller {
|
|||
bool operator<= (const sessIndex &o) const;
|
||||
bool operator< (const sessIndex &o) const;
|
||||
bool operator>= (const sessIndex &o) const;
|
||||
std::string toStr();
|
||||
};
|
||||
|
||||
|
||||
class statStorage {
|
||||
private:
|
||||
long long removeUp;
|
||||
long long removeDown;
|
||||
public:
|
||||
statStorage();
|
||||
void update(IPC::statExchange & data);
|
||||
bool hasDataFor(unsigned long long);
|
||||
statLog & getDataFor(unsigned long long);
|
||||
|
|
|
@ -15,6 +15,8 @@ namespace Controller {
|
|||
JSON::Value Storage; ///< Global storage of data.
|
||||
tthread::mutex configMutex;
|
||||
tthread::mutex logMutex;
|
||||
bool configChanged = false;
|
||||
|
||||
///\brief Store and print a log message.
|
||||
///\param kind The type of message.
|
||||
///\param message The message to be logged.
|
||||
|
@ -70,6 +72,7 @@ namespace Controller {
|
|||
printf("%s", buf);
|
||||
}
|
||||
}
|
||||
Log("LOG", "Logger exiting");
|
||||
fclose(output);
|
||||
close((long long int)err);
|
||||
}
|
||||
|
@ -95,8 +98,8 @@ namespace Controller {
|
|||
}
|
||||
if (!changed){return;}//cancel further processing if no changes
|
||||
|
||||
static IPC::sharedPage mistConfOut("!mistConfig", DEFAULT_CONF_PAGE_SIZE, true);
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
static IPC::sharedPage mistConfOut(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, true);
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
//lock semaphore
|
||||
configLock.wait();
|
||||
//write config
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace Controller {
|
|||
extern JSON::Value Storage; ///< Global storage of data.
|
||||
extern tthread::mutex logMutex;///< Mutex for log thread.
|
||||
extern tthread::mutex configMutex;///< Mutex for server config access.
|
||||
extern bool configChanged; ///< Bool that indicates config must be written to SHM.
|
||||
|
||||
/// Store and print a log message.
|
||||
void Log(std::string kind, std::string message);
|
||||
|
|
|
@ -207,7 +207,7 @@ namespace Controller {
|
|||
//actually delete the streams
|
||||
while (toDelete.size() > 0){
|
||||
std::string deleting = *(toDelete.begin());
|
||||
out.removeMember(deleting);
|
||||
deleteStream(deleting, out);
|
||||
toDelete.erase(deleting);
|
||||
}
|
||||
|
||||
|
@ -226,4 +226,13 @@ namespace Controller {
|
|||
|
||||
}
|
||||
|
||||
void deleteStream(const std::string & name, JSON::Value & out) {
|
||||
if (!out.isMember(name)){
|
||||
return;
|
||||
}
|
||||
Log("STRM", std::string("Deleted stream ") + name);
|
||||
out.removeMember(name);
|
||||
}
|
||||
|
||||
} //Controller namespace
|
||||
|
||||
|
|
|
@ -6,9 +6,11 @@ namespace Controller {
|
|||
bool CheckAllStreams(JSON::Value & data);
|
||||
void CheckStreams(JSON::Value & in, JSON::Value & out);
|
||||
void AddStreams(JSON::Value & in, JSON::Value & out);
|
||||
void deleteStream(const std::string & name, JSON::Value & out);
|
||||
|
||||
struct liveCheck {
|
||||
long long int lastms;
|
||||
long long int last_active;
|
||||
};
|
||||
|
||||
} //Controller namespace
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <mist/stream.h>
|
||||
#include <mist/defines.h>
|
||||
#include "input.h"
|
||||
#include <sstream>
|
||||
|
@ -69,7 +70,7 @@ namespace Mist {
|
|||
}
|
||||
|
||||
void Input::checkHeaderTimes(std::string streamFile){
|
||||
if ( streamFile == "-" ){
|
||||
if (streamFile == "-" || streamFile == "push://") {
|
||||
return;
|
||||
}
|
||||
std::string headerFile = streamFile + ".dtsh";
|
||||
|
@ -99,15 +100,22 @@ namespace Mist {
|
|||
}
|
||||
|
||||
int Input::run() {
|
||||
streamName = config->getString("streamname");
|
||||
if (config->getBool("json")) {
|
||||
std::cout << capa.toString() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (streamName != "") {
|
||||
config->getOption("streamname") = streamName;
|
||||
}
|
||||
streamName = config->getString("streamname");
|
||||
nProxy.streamName = streamName;
|
||||
|
||||
if (!setup()){
|
||||
std::cerr << config->getString("cmd") << " setup failed." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
checkHeaderTimes(config->getString("input"));
|
||||
if (!readHeader()){
|
||||
std::cerr << "Reading header for " << config->getString("input") << " failed." << std::endl;
|
||||
|
@ -115,7 +123,7 @@ namespace Mist {
|
|||
}
|
||||
parseHeader();
|
||||
|
||||
if (!config->getString("streamname").size()){
|
||||
if (!streamName.size()) {
|
||||
convert();
|
||||
}else{
|
||||
serve();
|
||||
|
@ -155,28 +163,32 @@ namespace Mist {
|
|||
}
|
||||
|
||||
void Input::serve(){
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
userPage.init(userPageName, PLAY_EX_SIZE, true);
|
||||
if (!isBuffer){
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
bufferFrame(it->first, 1);
|
||||
}
|
||||
}
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
userPage.init(userPageName, PLAY_EX_SIZE, true);
|
||||
|
||||
DEBUG_MSG(DLVL_DEVEL,"Input for stream %s started", streamName.c_str());
|
||||
|
||||
long long int activityCounter = Util::bootSecs();
|
||||
while ((Util::bootSecs() - activityCounter) < 10 && config->is_active){//10 second timeout
|
||||
Util::wait(1000);
|
||||
removeUnused();
|
||||
while ((Util::bootSecs() - activityCounter) < INPUT_TIMEOUT && config->is_active) { //15 second timeout
|
||||
userPage.parseEach(callbackWrapper);
|
||||
if (userPage.amount){
|
||||
removeUnused();
|
||||
if (userPage.connectedUsers) {
|
||||
if (myMeta.tracks.size()){
|
||||
activityCounter = Util::bootSecs();
|
||||
DEBUG_MSG(DLVL_INSANE, "Connected users: %d", userPage.amount);
|
||||
}
|
||||
DEBUG_MSG(DLVL_INSANE, "Connected users: %d", userPage.connectedUsers);
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_INSANE, "Timer running");
|
||||
}
|
||||
if (config->is_active){
|
||||
Util::wait(1000);
|
||||
}
|
||||
}
|
||||
finish();
|
||||
DEBUG_MSG(DLVL_DEVEL,"Input for stream %s closing clean", streamName.c_str());
|
||||
|
@ -191,7 +203,7 @@ namespace Mist {
|
|||
}
|
||||
removeUnused();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -210,9 +222,9 @@ namespace Mist {
|
|||
bufferRemove(it->first, it2->first);
|
||||
pageCounter[it->first].erase(it2->first);
|
||||
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){
|
||||
(((long long int *)(metaPages[it->first].mapped + i))[0]) = 0;
|
||||
(((long long int *)(nProxy.metaPages[it->first].mapped + i))[0]) = 0;
|
||||
}
|
||||
}
|
||||
change = true;
|
||||
|
@ -253,13 +265,13 @@ namespace Mist {
|
|||
for (int i = 0; i < it->second.keys.size(); i++){
|
||||
if (newData){
|
||||
//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;
|
||||
}
|
||||
pagesByTrack[it->first].rbegin()->second.keyNum++;
|
||||
pagesByTrack[it->first].rbegin()->second.partNum += it->second.keys[i].getParts();
|
||||
pagesByTrack[it->first].rbegin()->second.dataSize += it->second.keySizes[i];
|
||||
if (pagesByTrack[it->first].rbegin()->second.dataSize > FLIP_DATA_PAGE_SIZE){
|
||||
nProxy.pagesByTrack[it->first].rbegin()->second.keyNum++;
|
||||
nProxy.pagesByTrack[it->first].rbegin()->second.partNum += it->second.keys[i].getParts();
|
||||
nProxy.pagesByTrack[it->first].rbegin()->second.dataSize += it->second.keySizes[i];
|
||||
if (nProxy.pagesByTrack[it->first].rbegin()->second.dataSize > FLIP_DATA_PAGE_SIZE) {
|
||||
newData = true;
|
||||
}
|
||||
}
|
||||
|
@ -292,7 +304,7 @@ namespace Mist {
|
|||
}
|
||||
if (myMeta.tracks[tid].keys[bookKeeping[tid].curKey].getParts() + 1 == curData[tid].partNum){
|
||||
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;
|
||||
curData[tid].keyNum = 0;
|
||||
curData[tid].dataSize = 0;
|
||||
|
@ -309,17 +321,17 @@ namespace Mist {
|
|||
getNext(false);
|
||||
}
|
||||
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)){
|
||||
pagesByTrack[it->first][bookKeeping[it->first].first] = curData[it->first];
|
||||
if (curData.count(it->first) && !nProxy.pagesByTrack[it->first].count(bookKeeping[it->first].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++){
|
||||
if (!pagesByTrack.count(it->first)){
|
||||
if (!nProxy.pagesByTrack.count(it->first)) {
|
||||
DEBUG_MSG(DLVL_WARN, "No pages for track %d found", it->first);
|
||||
}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());
|
||||
for (std::map<unsigned long, DTSCPageData>::iterator it2 = pagesByTrack[it->first].begin(); it2 != pagesByTrack[it->first].end(); it2++){
|
||||
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 = 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);
|
||||
}
|
||||
}
|
||||
|
@ -328,29 +340,38 @@ namespace Mist {
|
|||
|
||||
|
||||
bool Input::bufferFrame(unsigned int track, unsigned int keyNum){
|
||||
VERYHIGH_MSG("bufferFrame for stream %s, track %u, key %u", streamName.c_str(), track, keyNum);
|
||||
VERYHIGH_MSG("Buffering stream %s, track %u, key %u", streamName.c_str(), track, keyNum);
|
||||
if (keyNum > myMeta.tracks[track].keys.size()){
|
||||
//End of movie here, returning true to avoid various error messages
|
||||
VERYHIGH_MSG("Key number is higher than total key count. Cancelling bufferFrame");
|
||||
WARN_MSG("Key %llu is higher than total (%llu). Cancelling buffering.", keyNum, myMeta.tracks[track].keys.size());
|
||||
return true;
|
||||
}
|
||||
if (keyNum < 1){keyNum = 1;}
|
||||
//abort in case already buffered
|
||||
int pageNumber = bufferedOnPage(track, keyNum);
|
||||
if (pageNumber){
|
||||
if (keyNum < 1) {
|
||||
keyNum = 1;
|
||||
}
|
||||
if (nProxy.isBuffered(track, keyNum)) {
|
||||
//get corresponding page number
|
||||
int pageNumber = 0;
|
||||
for (std::map<unsigned long, DTSCPageData>::iterator it = nProxy.pagesByTrack[track].begin(); it != nProxy.pagesByTrack[track].end(); it++) {
|
||||
if (it->first <= keyNum) {
|
||||
pageNumber = it->first;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
pageCounter[track][pageNumber] = 15;
|
||||
VERYHIGH_MSG("Track %u, key %u is already buffered in page %d. Cancelling bufferFrame", track, keyNum, pageNumber);
|
||||
return true;
|
||||
}
|
||||
if (!pagesByTrack.count(track)){
|
||||
if (!nProxy.pagesByTrack.count(track)) {
|
||||
WARN_MSG("No pages for track %u found! Cancelling bufferFrame", track);
|
||||
return false;
|
||||
}
|
||||
//Update keynum to point to the corresponding page
|
||||
INFO_MSG("Loading key %u from page %lu", keyNum, (--(pagesByTrack[track].upper_bound(keyNum)))->first);
|
||||
keyNum = (--(pagesByTrack[track].upper_bound(keyNum)))->first;
|
||||
INFO_MSG("Loading key %u from page %lu", keyNum, (--(nProxy.pagesByTrack[track].upper_bound(keyNum)))->first);
|
||||
keyNum = (--(nProxy.pagesByTrack[track].upper_bound(keyNum)))->first;
|
||||
if (!bufferStart(track, keyNum)){
|
||||
WARN_MSG("bufferStart failed! Cancelling bufferFrame", track);
|
||||
WARN_MSG("bufferStart failed! Cancelling bufferFrame");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -359,8 +380,8 @@ namespace Mist {
|
|||
trackSelect(trackSpec.str());
|
||||
seek(myMeta.tracks[track].keys[keyNum - 1].getTime());
|
||||
long long unsigned int stopTime = myMeta.tracks[track].lastms + 1;
|
||||
if ((int)myMeta.tracks[track].keys.size() > keyNum - 1 + pagesByTrack[track][keyNum].keyNum){
|
||||
stopTime = myMeta.tracks[track].keys[keyNum - 1 + pagesByTrack[track][keyNum].keyNum].getTime();
|
||||
if ((int)myMeta.tracks[track].keys.size() > keyNum - 1 + nProxy.pagesByTrack[track][keyNum].keyNum) {
|
||||
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);
|
||||
getNext();
|
||||
|
|
|
@ -20,7 +20,11 @@ namespace Mist {
|
|||
public:
|
||||
Input(Util::Config * cfg);
|
||||
virtual int run();
|
||||
virtual void onCrash(){}
|
||||
virtual void argumentsParsed(){}
|
||||
virtual ~Input() {};
|
||||
|
||||
virtual bool needsLock(){return true;}
|
||||
protected:
|
||||
static void callbackWrapper(char * data, size_t len, unsigned int id);
|
||||
virtual bool setup() = 0;
|
||||
|
@ -29,6 +33,9 @@ namespace Mist {
|
|||
virtual void getNext(bool smart = true) {};
|
||||
virtual void seek(int seekTime){};
|
||||
virtual void finish();
|
||||
virtual bool openStreamSource() { return false; };
|
||||
virtual void closeStreamSource() {};
|
||||
virtual void parseStreamHeader() {};
|
||||
void play(int until = 0);
|
||||
void playOnce();
|
||||
void quitPlay();
|
||||
|
@ -36,11 +43,11 @@ namespace Mist {
|
|||
virtual void removeUnused();
|
||||
virtual void trackSelect(std::string trackSpec){};
|
||||
virtual void userCallback(char * data, size_t len, unsigned int id);
|
||||
|
||||
void serve();
|
||||
void convert();
|
||||
virtual void convert();
|
||||
virtual void serve();
|
||||
|
||||
void parseHeader();
|
||||
|
||||
virtual void parseHeader();
|
||||
bool bufferFrame(unsigned int track, unsigned int keyNum);
|
||||
|
||||
unsigned int packTime;///Media-timestamp of the last packet.
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include "input_buffer.h"
|
||||
|
||||
#ifndef TIMEOUTMULTIPLIER
|
||||
#define TIMEOUTMULTIPLIER 10
|
||||
#define TIMEOUTMULTIPLIER 2
|
||||
#endif
|
||||
|
||||
namespace Mist {
|
||||
|
@ -41,6 +41,8 @@ namespace Mist {
|
|||
singleton = this;
|
||||
bufferTime = 50000;
|
||||
cutTime = 0;
|
||||
hasPush = false;
|
||||
resumeMode = false;
|
||||
}
|
||||
|
||||
inputBuffer::~inputBuffer() {
|
||||
|
@ -48,16 +50,111 @@ namespace Mist {
|
|||
if (myMeta.tracks.size()) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Cleaning up, removing last keyframes");
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
while (removeKey(it->first)) {}
|
||||
std::map<unsigned long, DTSCPageData> & locations = bufferLocations[it->first];
|
||||
if (!nProxy.metaPages.count(it->first) || !nProxy.metaPages[it->first].mapped) {
|
||||
continue;
|
||||
}
|
||||
//First detect all entries on metaPage
|
||||
for (int i = 0; i < 8192; i += 8) {
|
||||
int * tmpOffset = (int *)(nProxy.metaPages[it->first].mapped + i);
|
||||
if (tmpOffset[0] == 0 && tmpOffset[1] == 0) {
|
||||
continue;
|
||||
}
|
||||
unsigned long keyNum = ntohl(tmpOffset[0]);
|
||||
|
||||
//Add an entry into bufferLocations[tNum] for the pages we haven't handled yet.
|
||||
if (!locations.count(keyNum)) {
|
||||
locations[keyNum].curOffset = 0;
|
||||
}
|
||||
locations[keyNum].pageNum = keyNum;
|
||||
locations[keyNum].keyNum = ntohl(tmpOffset[1]);
|
||||
}
|
||||
for (std::map<unsigned long, DTSCPageData>::iterator it2 = locations.begin(); it2 != locations.end(); it2++) {
|
||||
char thisPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), it->first, it2->first);
|
||||
IPC::sharedPage erasePage(thisPageName, 20971520);
|
||||
erasePage.master = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
|
||||
IPC::semaphore liveMeta(pageName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
liveMeta.unlink();
|
||||
}
|
||||
|
||||
|
||||
///Cleans up any left-over data for the current stream
|
||||
void inputBuffer::onCrash(){
|
||||
WARN_MSG("Buffer crashed. Cleaning.");
|
||||
streamName = config->getString("streamname");
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
|
||||
//Set userpage to all 0xFF's, will disconnect all current clients.
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
std::string baseName = pageName;
|
||||
for (long unsigned i = 0; i < 15; ++i){
|
||||
unsigned int size = std::min(((8192 * 2) << i), (32 * 1024 * 1024));
|
||||
IPC::sharedPage tmp(std::string(baseName + (char)(i + (int)'A')), size, false, false);
|
||||
if (tmp.mapped){
|
||||
tmp.master = true;
|
||||
WARN_MSG("Wiping %s", std::string(baseName + (char)(i + (int)'A')).c_str());
|
||||
memset(tmp.mapped, 0xFF, size);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
//Delete the live stream semaphore, if any.
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
|
||||
IPC::semaphore liveMeta(pageName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
liveMeta.unlink();
|
||||
}{
|
||||
//Delete the stream index metapage.
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||
IPC::sharedPage erasePage(pageName, DEFAULT_STRM_PAGE_SIZE, false, false);
|
||||
erasePage.master = true;
|
||||
}
|
||||
//Delete most if not all temporary track metadata pages.
|
||||
for (long unsigned i = 1001; i <= 1024; ++i){
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_META, streamName.c_str(), i);
|
||||
IPC::sharedPage erasePage(pageName, 1024, false, false);
|
||||
erasePage.master = true;
|
||||
}
|
||||
//Delete most if not all track indexes and data pages.
|
||||
for (long unsigned i = 1; i <= 24; ++i){
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), i);
|
||||
IPC::sharedPage indexPage(pageName, SHM_TRACK_INDEX_SIZE, false, false);
|
||||
indexPage.master = true;
|
||||
if (indexPage.mapped){
|
||||
char * mappedPointer = indexPage.mapped;
|
||||
for (int j = 0; j < 8192; j += 8) {
|
||||
int * tmpOffset = (int *)(mappedPointer + j);
|
||||
if (tmpOffset[0] == 0 && tmpOffset[1] == 0){
|
||||
continue;
|
||||
}
|
||||
unsigned long keyNum = ntohl(tmpOffset[0]);
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), i, keyNum);
|
||||
IPC::sharedPage erasePage(pageName, 1024, false, false);
|
||||
erasePage.master = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// \triggers
|
||||
/// The `"STREAM_BUFFER"` trigger is stream-specific, and is ran whenever the buffer changes state between playable (FULL) or not (EMPTY). It cannot be cancelled. It is possible to receive multiple EMPTY calls without FULL calls in between, as EMPTY is always generated when a stream is unloaded from memory, even if this stream never reached playable state in the first place (e.g. a broadcast was cancelled before filling enough buffer to be playable). Its payload is:
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// streamname
|
||||
/// FULL or EMPTY (depending on current state)
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
void inputBuffer::updateMeta() {
|
||||
long long unsigned int firstms = 0xFFFFFFFFFFFFFFFFull;
|
||||
long long unsigned int lastms = 0;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
if (it->second.type == "meta" || !it->second.type.size()){continue;}
|
||||
if (it->second.type == "meta" || !it->second.type.size()) {
|
||||
continue;
|
||||
}
|
||||
if (it->second.init.size()){
|
||||
if (!initData.count(it->first) || initData[it->first] != it->second.init){
|
||||
initData[it->first] = it->second.init;
|
||||
|
@ -81,14 +178,14 @@ namespace Mist {
|
|||
snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
|
||||
IPC::semaphore liveMeta(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
liveMeta.wait();
|
||||
if (!metaPages.count(0) || !metaPages[0].mapped) {
|
||||
if (!nProxy.metaPages.count(0) || !nProxy.metaPages[0].mapped) {
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||
metaPages[0].init(pageName, DEFAULT_META_PAGE_SIZE, true);
|
||||
metaPages[0].master = false;
|
||||
nProxy.metaPages[0].init(pageName, DEFAULT_STRM_PAGE_SIZE, true);
|
||||
nProxy.metaPages[0].master = false;
|
||||
}
|
||||
myMeta.writeTo(metaPages[0].mapped);
|
||||
memset(metaPages[0].mapped + myMeta.getSendLen(), 0, (metaPages[0].len > myMeta.getSendLen() ? std::min(metaPages[0].len - myMeta.getSendLen(), 4ll) : 0));
|
||||
myMeta.writeTo(nProxy.metaPages[0].mapped);
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -118,30 +215,15 @@ namespace Mist {
|
|||
if (bufferLocations[tid].size() > 1) {
|
||||
//Check if the first key starts on the second page or higher
|
||||
if (myMeta.tracks[tid].keys[0].getNumber() >= (++(bufferLocations[tid].begin()))->first || !config->is_active) {
|
||||
//Find page in indexpage and null it
|
||||
for (int i = 0; i < 8192; i += 8) {
|
||||
unsigned int thisKeyNum = ((((long long int *)(metaPages[tid].mapped + i))[0]) >> 32) & 0xFFFFFFFF;
|
||||
if (thisKeyNum == htonl(bufferLocations[tid].begin()->first) && ((((long long int *)(metaPages[tid].mapped + i))[0]) != 0)){
|
||||
(((long long int *)(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);
|
||||
bufferRemove(tid, bufferLocations[tid].begin()->first);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
|
||||
int tmpNum = ntohl(tmpOffset[0]);
|
||||
if (tmpNum == bufferLocations[tid].begin()->first) {
|
||||
tmpOffset[0] = 0;
|
||||
tmpOffset[1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
curPageNum.erase(tid);
|
||||
nProxy.curPageNum.erase(tid);
|
||||
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);
|
||||
curPage[tid].init(thisPageName, 20971520);
|
||||
curPage[tid].master = true;
|
||||
curPage.erase(tid);
|
||||
nProxy.curPage[tid].init(thisPageName, 20971520);
|
||||
nProxy.curPage[tid].master = true;
|
||||
nProxy.curPage.erase(tid);
|
||||
|
||||
bufferLocations[tid].erase(bufferLocations[tid].begin());
|
||||
} else {
|
||||
|
@ -158,13 +240,13 @@ namespace Mist {
|
|||
for (std::map<unsigned long, DTSCPageData>::iterator it = bufferLocations[tid].begin(); it != bufferLocations[tid].end(); it++){
|
||||
char thisPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), tid, it->first);
|
||||
curPage[tid].init(thisPageName, 20971520, false, false);
|
||||
curPage[tid].master = true;
|
||||
curPage.erase(tid);
|
||||
nProxy.curPage[tid].init(thisPageName, 20971520, false, false);
|
||||
nProxy.curPage[tid].master = true;
|
||||
nProxy.curPage.erase(tid);
|
||||
}
|
||||
bufferLocations.erase(tid);
|
||||
metaPages[tid].master = true;
|
||||
metaPages.erase(tid);
|
||||
nProxy.metaPages[tid].master = true;
|
||||
nProxy.metaPages.erase(tid);
|
||||
}
|
||||
|
||||
void inputBuffer::finish() {
|
||||
|
@ -189,11 +271,13 @@ namespace Mist {
|
|||
long long unsigned int time = Util::bootSecs();
|
||||
long long unsigned int compareFirst = 0xFFFFFFFFFFFFFFFFull;
|
||||
long long unsigned int compareLast = 0;
|
||||
std::set<std::string> activeTypes;
|
||||
//for tracks that were updated in the last 5 seconds, get the first and last ms edges.
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it2 = myMeta.tracks.begin(); it2 != myMeta.tracks.end(); it2++) {
|
||||
if ((time - lastUpdated[it2->first]) > 5) {
|
||||
continue;
|
||||
}
|
||||
activeTypes.insert(it2->second.type);
|
||||
if (it2->second.lastms > compareLast) {
|
||||
compareLast = it2->second.lastms;
|
||||
}
|
||||
|
@ -204,7 +288,7 @@ namespace Mist {
|
|||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
//if not updated for an entire buffer duration, or last updated track and this track differ by an entire buffer duration, erase the track.
|
||||
if ((long long int)(time - lastUpdated[it->first]) > (long long int)(bufferTime / 1000) ||
|
||||
(compareLast && (long long int)(time - lastUpdated[it->first]) > 5 && (
|
||||
(compareLast && activeTypes.count(it->second.type) && (long long int)(time - lastUpdated[it->first]) > 5 && (
|
||||
(compareLast < it->second.firstms && (long long int)(it->second.firstms - compareLast) > bufferTime)
|
||||
||
|
||||
(compareFirst > it->second.lastms && (long long int)(compareFirst - it->second.lastms) > bufferTime)
|
||||
|
@ -213,26 +297,26 @@ namespace Mist {
|
|||
unsigned int tid = it->first;
|
||||
//erase this track
|
||||
if ((long long int)(time - lastUpdated[it->first]) > (long long int)(bufferTime / 1000)){
|
||||
INFO_MSG("Erasing track %d because not updated for %ds (> %ds)", it->first, (long long int)(time - lastUpdated[it->first]), (long long int)(bufferTime / 1000));
|
||||
WARN_MSG("Erasing %s track %d (%s/%s) because not updated for %ds (> %ds)", streamName.c_str(), it->first, it->second.type.c_str(), it->second.codec.c_str(), (long long int)(time - lastUpdated[it->first]), (long long int)(bufferTime / 1000));
|
||||
}else{
|
||||
INFO_MSG("Erasing inactive track %u because it was inactive for 5+ seconds and contains data (%us - %us), while active tracks are (%us - %us), which is more than %us seconds apart.", it->first, it->second.firstms / 1000, it->second.lastms / 1000, compareFirst/1000, compareLast/1000, bufferTime / 1000);
|
||||
WARN_MSG("Erasing %s inactive track %u (%s/%s) because it was inactive for 5+ seconds and contains data (%us - %us), while active tracks are (%us - %us), which is more than %us seconds apart.", streamName.c_str(), it->first, it->second.type.c_str(), it->second.codec.c_str(), it->second.firstms / 1000, it->second.lastms / 1000, compareFirst / 1000, compareLast / 1000, bufferTime / 1000);
|
||||
}
|
||||
lastUpdated.erase(tid);
|
||||
/// \todo Consider replacing with eraseTrackDataPages(it->first)?
|
||||
while (bufferLocations[tid].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);
|
||||
curPage[tid].init(thisPageName, 20971520);
|
||||
curPage[tid].master = true;
|
||||
curPage.erase(tid);
|
||||
nProxy.curPage[tid].init(thisPageName, 20971520);
|
||||
nProxy.curPage[tid].master = true;
|
||||
nProxy.curPage.erase(tid);
|
||||
bufferLocations[tid].erase(bufferLocations[tid].begin());
|
||||
}
|
||||
if (pushLocation.count(it->first)){
|
||||
pushLocation.erase(it->first);
|
||||
}
|
||||
curPageNum.erase(it->first);
|
||||
metaPages[it->first].master = true;
|
||||
metaPages.erase(it->first);
|
||||
nProxy.curPageNum.erase(it->first);
|
||||
nProxy.metaPages[it->first].master = true;
|
||||
nProxy.metaPages.erase(it->first);
|
||||
activeTracks.erase(it->first);
|
||||
myMeta.tracks.erase(it->first);
|
||||
changed = true;
|
||||
|
@ -251,8 +335,9 @@ namespace Mist {
|
|||
}
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
//non-video tracks need to have a second keyframe that is <= firstVideo
|
||||
//firstVideo = 1 happens when there are no tracks, in which case we don't care any more
|
||||
if (it->second.type != "video") {
|
||||
if (it->second.keys.size() < 2 || it->second.keys[1].getTime() > firstVideo) {
|
||||
if (it->second.keys.size() < 2 || (it->second.keys[1].getTime() > firstVideo && firstVideo != 1)){
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -270,6 +355,14 @@ namespace Mist {
|
|||
}
|
||||
}
|
||||
updateMeta();
|
||||
static bool everHadPush = false;
|
||||
if (hasPush) {
|
||||
hasPush = false;
|
||||
everHadPush = true;
|
||||
} else if (everHadPush && !resumeMode && config->is_active) {
|
||||
INFO_MSG("Shutting down buffer because resume mode is disabled and the source disconnected");
|
||||
config->is_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void inputBuffer::userCallback(char * data, size_t len, unsigned int id) {
|
||||
|
@ -278,10 +371,10 @@ namespace Mist {
|
|||
//Get the counter of this user
|
||||
char counter = (*(data - 1));
|
||||
//Each user can have at maximum SIMUL_TRACKS elements in their userpage.
|
||||
IPC::userConnection userConn(data);
|
||||
for (int index = 0; index < SIMUL_TRACKS; index++) {
|
||||
char * thisData = data + (index * 6);
|
||||
//Get the track id from the current element
|
||||
unsigned long value = ((long)(thisData[0]) << 24) | ((long)(thisData[1]) << 16) | ((long)(thisData[2]) << 8) | thisData[3];
|
||||
unsigned long value = userConn.getTrackId(index);
|
||||
//Skip value 0xFFFFFFFF as this indicates a previously declined track
|
||||
if (value == 0xFFFFFFFF) {
|
||||
continue;
|
||||
|
@ -294,12 +387,10 @@ namespace Mist {
|
|||
//If the current value indicates a valid trackid, and it is pushed from this user
|
||||
if (pushLocation[value] == data) {
|
||||
//Check for timeouts, and erase the track if necessary
|
||||
if (counter == 126 || counter == 127 || counter == 254 || counter == 255) {
|
||||
if (counter == 126 || counter == 127){
|
||||
pushLocation.erase(value);
|
||||
if (negotiatingTracks.count(value)) {
|
||||
negotiatingTracks.erase(value);
|
||||
metaPages[value].master = true;
|
||||
metaPages.erase(value);
|
||||
}
|
||||
if (activeTracks.count(value)) {
|
||||
updateMeta();
|
||||
|
@ -307,8 +398,8 @@ namespace Mist {
|
|||
activeTracks.erase(value);
|
||||
bufferLocations.erase(value);
|
||||
}
|
||||
metaPages[value].master = true;
|
||||
metaPages.erase(value);
|
||||
nProxy.metaPages[value].master = true;
|
||||
nProxy.metaPages.erase(value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -320,28 +411,24 @@ namespace Mist {
|
|||
//Add the temporary track id to the list of tracks that are currently being negotiated
|
||||
negotiatingTracks.insert(tempMapping);
|
||||
//Write the temporary id to the userpage element
|
||||
thisData[0] = (tempMapping >> 24) & 0xFF;
|
||||
thisData[1] = (tempMapping >> 16) & 0xFF;
|
||||
thisData[2] = (tempMapping >> 8) & 0xFF;
|
||||
thisData[3] = (tempMapping) & 0xFF;
|
||||
userConn.setTrackId(index, tempMapping);
|
||||
//Obtain the original track number for the pushing process
|
||||
unsigned long originalTrack = ((long)(thisData[4]) << 8) | thisData[5];
|
||||
unsigned long originalTrack = userConn.getKeynum(index);
|
||||
//Overwrite it with 0xFFFF
|
||||
thisData[4] = 0xFF;
|
||||
thisData[5] = 0xFF;
|
||||
userConn.setKeynum(index, 0xFFFF);
|
||||
DEBUG_MSG(DLVL_HIGH, "Incoming track %lu from pushing process %d has now been assigned temporary id %llu", originalTrack, id, tempMapping);
|
||||
}
|
||||
|
||||
//The track id is set to the value of a track that we are currently negotiating about
|
||||
if (negotiatingTracks.count(value)) {
|
||||
//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];
|
||||
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 (!metaPages[value].mapped) {
|
||||
if (!nProxy.metaPages[value].mapped) {
|
||||
//remove the negotiation if it has timed out
|
||||
if (++negotiationTimeout[value] >= 1000){
|
||||
negotiatingTracks.erase(value);
|
||||
|
@ -353,13 +440,13 @@ namespace Mist {
|
|||
//The page exist, now we try to read in the metadata of the track
|
||||
|
||||
//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
|
||||
unsigned int tempForReadingMeta = 0;
|
||||
//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
|
||||
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
|
||||
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.
|
||||
|
@ -368,8 +455,8 @@ namespace Mist {
|
|||
if (++negotiationTimeout[value] >= 1000){
|
||||
negotiatingTracks.erase(value);
|
||||
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
||||
metaPages[value].master = true;
|
||||
metaPages.erase(value);
|
||||
nProxy.metaPages[value].master = true;
|
||||
nProxy.metaPages.erase(value);
|
||||
negotiationTimeout.erase(value);
|
||||
}
|
||||
continue;
|
||||
|
@ -381,8 +468,8 @@ namespace Mist {
|
|||
//Remove the "negotiate" status in either case
|
||||
negotiatingTracks.erase(value);
|
||||
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
||||
metaPages[value].master = true;
|
||||
metaPages.erase(value);
|
||||
nProxy.metaPages[value].master = true;
|
||||
nProxy.metaPages.erase(value);
|
||||
|
||||
int finalMap = 3;
|
||||
if (trackMeta.tracks.find(value)->second.type == "video"){finalMap = 1;}
|
||||
|
@ -402,8 +489,8 @@ namespace Mist {
|
|||
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
||||
updateMeta();
|
||||
eraseTrackDataPages(value);
|
||||
metaPages[finalMap].master = true;
|
||||
metaPages.erase(finalMap);
|
||||
nProxy.metaPages[finalMap].master = true;
|
||||
nProxy.metaPages.erase(finalMap);
|
||||
bufferLocations.erase(finalMap);
|
||||
}
|
||||
|
||||
|
@ -415,43 +502,39 @@ namespace Mist {
|
|||
pushLocation[finalMap] = data;
|
||||
//Initialize the metadata for this track if it was not in place yet.
|
||||
if (!myMeta.tracks.count(finalMap)) {
|
||||
DEBUG_MSG(DLVL_HIGH, "Inserting metadata for track number %d", finalMap);
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Inserting metadata for track number %d", finalMap);
|
||||
myMeta.tracks[finalMap] = trackMeta.tracks.begin()->second;
|
||||
myMeta.tracks[finalMap].trackID = finalMap;
|
||||
}
|
||||
//Write the final mapped track number to the user page element
|
||||
thisData[0] = (finalMap >> 24) & 0xFF;
|
||||
thisData[1] = (finalMap >> 16) & 0xFF;
|
||||
thisData[2] = (finalMap >> 8) & 0xFF;
|
||||
thisData[3] = (finalMap) & 0xFF;
|
||||
//Write the key number to start pushing from to to the userpage element.
|
||||
//Write the final mapped track number and keyframe number to the user page element
|
||||
//This is used to resume pushing as well as pushing new tracks
|
||||
unsigned long keyNum = myMeta.tracks[finalMap].keys.size();
|
||||
thisData[4] = (keyNum >> 8) & 0xFF;
|
||||
thisData[5] = keyNum & 0xFF;
|
||||
userConn.setTrackId(index, finalMap);
|
||||
userConn.setKeynum(index, myMeta.tracks[finalMap].keys.size());
|
||||
//Update the metadata to reflect all changes
|
||||
updateMeta();
|
||||
}
|
||||
//If the track is active, and this is the element responsible for pushing it
|
||||
if (activeTracks.count(value) && pushLocation[value] == data) {
|
||||
//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];
|
||||
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, SHM_TRACK_INDEX_SIZE, false, false);
|
||||
}
|
||||
if (metaPages[value].mapped) {
|
||||
if (nProxy.metaPages[value].mapped) {
|
||||
//Update the metadata for this track
|
||||
updateTrackMeta(value);
|
||||
hasPush = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void inputBuffer::updateTrackMeta(unsigned long tNum) {
|
||||
VERYHIGH_MSG("Updating meta for track %d", tNum);
|
||||
//Store a reference for easier access
|
||||
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
|
||||
for (int i = 0; i < 8192; i += 8) {
|
||||
|
@ -460,6 +543,7 @@ namespace Mist {
|
|||
continue;
|
||||
}
|
||||
unsigned long keyNum = ntohl(tmpOffset[0]);
|
||||
INSANE_MSG("Page %d detected, with %d keys", keyNum, ntohl(tmpOffset[1]));
|
||||
|
||||
//Add an entry into bufferLocations[tNum] for the pages we haven't handled yet.
|
||||
if (!locations.count(keyNum)) {
|
||||
|
@ -468,7 +552,6 @@ namespace Mist {
|
|||
locations[keyNum].pageNum = keyNum;
|
||||
locations[keyNum].keyNum = ntohl(tmpOffset[1]);
|
||||
}
|
||||
|
||||
//Since the map is ordered by keynumber, this loop updates the metadata for each page from oldest to newest
|
||||
for (std::map<unsigned long, DTSCPageData>::iterator pageIt = locations.begin(); pageIt != locations.end(); pageIt++) {
|
||||
updateMetaFromPage(tNum, pageIt->first);
|
||||
|
@ -477,6 +560,7 @@ namespace Mist {
|
|||
}
|
||||
|
||||
void inputBuffer::updateMetaFromPage(unsigned long tNum, unsigned long pageNum) {
|
||||
VERYHIGH_MSG("Updating meta for track %d page %d", tNum, pageNum);
|
||||
DTSCPageData & pageData = bufferLocations[tNum][pageNum];
|
||||
|
||||
//If the current page is over its 8mb "splitting" boundary
|
||||
|
@ -491,27 +575,27 @@ namespace Mist {
|
|||
//Otherwise open and parse the page
|
||||
|
||||
//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
|
||||
curPageNum.erase(tNum);
|
||||
nProxy.curPageNum.erase(tNum);
|
||||
char nextPageName[NAME_BUFFER_SIZE];
|
||||
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 (!curPage[tNum].mapped) {
|
||||
if (!nProxy.curPage[tNum].mapped) {
|
||||
WARN_MSG("Could not open page: %s", nextPageName);
|
||||
return;
|
||||
}
|
||||
curPageNum[tNum] = pageNum;
|
||||
nProxy.curPageNum[tNum] = pageNum;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
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
|
||||
if (!tmpPack) {
|
||||
return;
|
||||
|
@ -527,7 +611,7 @@ namespace Mist {
|
|||
//Update the offset on the page with the size of the current packet
|
||||
pageData.curOffset += tmpPack.getDataLen();
|
||||
//Attempt to read in the next packet
|
||||
tmpPack.reInit(curPage[tNum].mapped + pageData.curOffset, 0);
|
||||
tmpPack.reInit(nProxy.curPage[tNum].mapped + pageData.curOffset, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -535,8 +619,8 @@ namespace Mist {
|
|||
std::string strName = config->getString("streamname");
|
||||
Util::sanitizeName(strName);
|
||||
strName = strName.substr(0, (strName.find_first_of("+ ")));
|
||||
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE, false, false); ///< Contains server configuration and capabilities
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, false, false); ///< Contains server configuration and capabilities
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(strName);
|
||||
long long tmpNum;
|
||||
|
@ -553,7 +637,9 @@ namespace Mist {
|
|||
tmpNum = config->getOption("bufferTime").asInt();
|
||||
}
|
||||
}
|
||||
if (tmpNum < 1000){tmpNum = 1000;}
|
||||
if (tmpNum < 1000) {
|
||||
tmpNum = 1000;
|
||||
}
|
||||
//if the new value is different, print a message and apply it
|
||||
if (bufferTime != tmpNum) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Setting bufferTime from %u to new value of %lli", bufferTime, tmpNum);
|
||||
|
|
|
@ -7,9 +7,12 @@ namespace Mist {
|
|||
public:
|
||||
inputBuffer(Util::Config * cfg);
|
||||
~inputBuffer();
|
||||
void onCrash();
|
||||
private:
|
||||
unsigned int bufferTime;
|
||||
unsigned int cutTime;
|
||||
bool hasPush;
|
||||
bool resumeMode;
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
|
@ -33,8 +36,7 @@ namespace Mist {
|
|||
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > bufferLocations;
|
||||
std::map<unsigned long, char *> pushLocation;
|
||||
inputBuffer * singleton;
|
||||
|
||||
//This is used for an ugly fix to prevent metadata from dissapearing in some cases.
|
||||
//This is used for an ugly fix to prevent metadata from disappearing in some cases.
|
||||
std::map<unsigned long, std::string> initData;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
#include <mist/stream.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
#include <mist/util.h>
|
||||
#include <mist/bitfields.h>
|
||||
|
||||
#include "input_dtsc.h"
|
||||
|
||||
namespace Mist {
|
||||
|
@ -14,7 +17,8 @@ namespace Mist {
|
|||
capa["name"] = "DTSC";
|
||||
capa["desc"] = "Enables DTSC Input";
|
||||
capa["priority"] = 9ll;
|
||||
capa["source_match"] = "/*.dtsc";
|
||||
capa["source_match"].append("/*.dtsc");
|
||||
capa["source_match"].append("dtsc://*");
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][0u].append("H263");
|
||||
capa["codecs"][0u][0u].append("VP6");
|
||||
|
@ -24,7 +28,142 @@ namespace Mist {
|
|||
capa["codecs"][0u][1u].append("vorbis");
|
||||
}
|
||||
|
||||
bool inputDTSC::needsLock(){
|
||||
return config->getString("input").substr(0, 7) != "dtsc://";
|
||||
}
|
||||
|
||||
void parseDTSCURI(const std::string & src, std::string & host, uint16_t & port, std::string & password, std::string & streamName) {
|
||||
host = "";
|
||||
port = 4200;
|
||||
password = "";
|
||||
streamName = "";
|
||||
std::deque<std::string> matches;
|
||||
if (Util::stringScan(src, "%s:%s@%s/%s", matches)) {
|
||||
host = matches[0];
|
||||
port = atoi(matches[1].c_str());
|
||||
password = matches[2];
|
||||
streamName = matches[3];
|
||||
return;
|
||||
}
|
||||
//Using default streamname
|
||||
if (Util::stringScan(src, "%s:%s@%s", matches)) {
|
||||
host = matches[0];
|
||||
port = atoi(matches[1].c_str());
|
||||
password = matches[2];
|
||||
return;
|
||||
}
|
||||
//Without password
|
||||
if (Util::stringScan(src, "%s:%s/%s", matches)) {
|
||||
host = matches[0];
|
||||
port = atoi(matches[1].c_str());
|
||||
streamName = matches[2];
|
||||
return;
|
||||
}
|
||||
//Using default port
|
||||
if (Util::stringScan(src, "%s@%s/%s", matches)) {
|
||||
host = matches[0];
|
||||
password = matches[1];
|
||||
streamName = matches[2];
|
||||
return;
|
||||
}
|
||||
//Default port, no password
|
||||
if (Util::stringScan(src, "%s/%s", matches)) {
|
||||
host = matches[0];
|
||||
streamName = matches[1];
|
||||
return;
|
||||
}
|
||||
//No password, default streamname
|
||||
if (Util::stringScan(src, "%s:%s", matches)) {
|
||||
host = matches[0];
|
||||
port = atoi(matches[1].c_str());
|
||||
return;
|
||||
}
|
||||
//Default port and streamname
|
||||
if (Util::stringScan(src, "%s@%s", matches)) {
|
||||
host = matches[0];
|
||||
password = matches[1];
|
||||
return;
|
||||
}
|
||||
//Default port and streamname, no password
|
||||
if (Util::stringScan(src, "%s", matches)) {
|
||||
host = matches[0];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void inputDTSC::parseStreamHeader() {
|
||||
while (srcConn.connected()){
|
||||
srcConn.spool();
|
||||
if (srcConn.Received().available(8)){
|
||||
if (srcConn.Received().copy(4) == "DTCM" || srcConn.Received().copy(4) == "DTSC") {
|
||||
// Command message
|
||||
std::string toRec = srcConn.Received().copy(8);
|
||||
unsigned long rSize = Bit::btohl(toRec.c_str() + 4);
|
||||
if (!srcConn.Received().available(8 + rSize)) {
|
||||
continue; //abort - not enough data yet
|
||||
}
|
||||
//Ignore initial DTCM message, as this is a "hi" message from the server
|
||||
if (srcConn.Received().copy(4) == "DTCM"){
|
||||
srcConn.Received().remove(8 + rSize);
|
||||
}else{
|
||||
std::string dataPacket = srcConn.Received().remove(8+rSize);
|
||||
DTSC::Packet metaPack(dataPacket.data(), dataPacket.size());
|
||||
myMeta.reinit(metaPack);
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
continueNegotiate(it->first, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
INFO_MSG("Received a wrong type of packet - '%s'", srcConn.Received().copy(4).c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool inputDTSC::openStreamSource() {
|
||||
std::string source = config->getString("input");
|
||||
if (source.find("dtsc://") == 0) {
|
||||
source.erase(0, 7);
|
||||
}
|
||||
std::string host;
|
||||
uint16_t port;
|
||||
std::string password;
|
||||
std::string streamName;
|
||||
parseDTSCURI(source, host, port, password, streamName);
|
||||
std::string givenStream = config->getString("streamname");
|
||||
if (streamName == "") {
|
||||
streamName = givenStream;
|
||||
}else{
|
||||
if (givenStream.find("+") != std::string::npos){
|
||||
streamName += givenStream.substr(givenStream.find("+"));
|
||||
}
|
||||
}
|
||||
srcConn = Socket::Connection(host, port, true);
|
||||
if (!srcConn.connected()){
|
||||
return false;
|
||||
}
|
||||
JSON::Value prep;
|
||||
prep["cmd"] = "play";
|
||||
prep["version"] = "MistServer " PACKAGE_VERSION;
|
||||
prep["stream"] = streamName;
|
||||
srcConn.SendNow("DTCM");
|
||||
char sSize[4] = {0, 0, 0, 0};
|
||||
Bit::htobl(sSize, prep.packedSize());
|
||||
srcConn.SendNow(sSize, 4);
|
||||
prep.sendTo(srcConn);
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputDTSC::closeStreamSource(){
|
||||
srcConn.close();
|
||||
}
|
||||
|
||||
bool inputDTSC::setup() {
|
||||
if (!needsLock()) {
|
||||
return true;
|
||||
} else {
|
||||
if (config->getString("input") == "-") {
|
||||
std::cerr << "Input from stdin not yet supported" << std::endl;
|
||||
return false;
|
||||
|
@ -46,10 +185,14 @@ namespace Mist {
|
|||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool inputDTSC::readHeader() {
|
||||
if (!needsLock()) {
|
||||
return true;
|
||||
}
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
|
@ -69,12 +212,62 @@ namespace Mist {
|
|||
}
|
||||
|
||||
void inputDTSC::getNext(bool smart) {
|
||||
if (!needsLock()){
|
||||
thisPacket.reInit(srcConn);
|
||||
if (thisPacket.getVersion() == DTSC::DTCM){
|
||||
std::string cmd;
|
||||
thisPacket.getString("cmd", cmd);
|
||||
if (cmd == "reset"){
|
||||
//Read next packet
|
||||
thisPacket.reInit(srcConn);
|
||||
if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
|
||||
DTSC::Meta newMeta;
|
||||
newMeta.reinit(thisPacket);
|
||||
//Detect new tracks
|
||||
std::set<unsigned int> newTracks;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = newMeta.tracks.begin(); it != newMeta.tracks.end(); it++){
|
||||
if (!myMeta.tracks.count(it->first)){
|
||||
newTracks.insert(it->first);
|
||||
}
|
||||
}
|
||||
|
||||
for (std::set<unsigned int>::iterator it = newTracks.begin(); it != newTracks.end(); it++){
|
||||
INFO_MSG("Adding track %d to internal metadata", *it);
|
||||
myMeta.tracks[*it] = newMeta.tracks[*it];
|
||||
continueNegotiate(*it, true);
|
||||
}
|
||||
|
||||
//Detect removed tracks
|
||||
std::set<unsigned int> deletedTracks;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (!newMeta.tracks.count(it->first)){
|
||||
deletedTracks.insert(it->first);
|
||||
}
|
||||
}
|
||||
|
||||
for(std::set<unsigned int>::iterator it = deletedTracks.begin(); it != deletedTracks.end(); it++){
|
||||
INFO_MSG("Deleting track %d from internal metadata", *it);
|
||||
myMeta.tracks.erase(*it);
|
||||
}
|
||||
|
||||
//Read next packet before returning
|
||||
thisPacket.reInit(srcConn);
|
||||
}else{
|
||||
myMeta = DTSC::Meta();
|
||||
}
|
||||
}else{
|
||||
//Read next packet before returning
|
||||
thisPacket.reInit(srcConn);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if (smart){
|
||||
inFile.seekNext();
|
||||
}else{
|
||||
inFile.parseNext();
|
||||
}
|
||||
thisPacket = inFile.getPacket();
|
||||
}
|
||||
}
|
||||
|
||||
void inputDTSC::seek(int seekTime) {
|
||||
|
|
|
@ -5,8 +5,12 @@ namespace Mist {
|
|||
class inputDTSC : public Input {
|
||||
public:
|
||||
inputDTSC(Util::Config * cfg);
|
||||
bool needsLock();
|
||||
protected:
|
||||
//Private Functions
|
||||
bool openStreamSource();
|
||||
void closeStreamSource();
|
||||
void parseStreamHeader();
|
||||
bool setup();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
|
@ -14,6 +18,8 @@ namespace Mist {
|
|||
void trackSelect(std::string trackSpec);
|
||||
|
||||
DTSC::File inFile;
|
||||
|
||||
Socket::Connection srcConn;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ namespace Mist {
|
|||
}
|
||||
}
|
||||
if (!offset){
|
||||
DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %llu", filePos);
|
||||
DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %lu", filePos);
|
||||
return;
|
||||
}
|
||||
filePos += offset;
|
||||
|
|
|
@ -16,24 +16,34 @@ int main(int argc, char * argv[]) {
|
|||
mistIn conv(&conf);
|
||||
if (conf.parseArgs(argc, argv)) {
|
||||
std::string streamName = conf.getString("streamname");
|
||||
conv.argumentsParsed();
|
||||
|
||||
IPC::semaphore playerLock;
|
||||
if (streamName.size()){
|
||||
playerLock.open(std::string("/lock_" + streamName).c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
if (!playerLock.tryWait()){
|
||||
DEBUG_MSG(DLVL_DEVEL, "A player for stream %s is already running", streamName.c_str());
|
||||
return 1;
|
||||
if (conv.needsLock()){
|
||||
if (streamName.size()){
|
||||
char semName[NAME_BUFFER_SIZE];
|
||||
snprintf(semName, NAME_BUFFER_SIZE, SEM_INPUT, streamName.c_str());
|
||||
playerLock.open(semName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
if (!playerLock.tryWait()){
|
||||
DEBUG_MSG(DLVL_DEVEL, "A player for stream %s is already running", streamName.c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
conf.activate();
|
||||
while (conf.is_active){
|
||||
pid_t pid = fork();
|
||||
if (pid == 0){
|
||||
playerLock.close();
|
||||
if (conv.needsLock()){
|
||||
playerLock.close();
|
||||
}
|
||||
return conv.run();
|
||||
}
|
||||
if (pid == -1){
|
||||
DEBUG_MSG(DLVL_FAIL, "Unable to spawn player process");
|
||||
playerLock.post();
|
||||
if (conv.needsLock()){
|
||||
playerLock.post();
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
//wait for the process to exit
|
||||
|
@ -50,6 +60,11 @@ int main(int argc, char * argv[]) {
|
|||
DEBUG_MSG(DLVL_MEDIUM, "Input for stream %s shut down cleanly", streamName.c_str());
|
||||
break;
|
||||
}
|
||||
#if DEBUG >= DLVL_DEVEL
|
||||
WARN_MSG("Aborting autoclean; this is a development build.");
|
||||
#else
|
||||
conv.onCrash();
|
||||
#endif
|
||||
if (DEBUG >= DLVL_DEVEL){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Input for stream %s uncleanly shut down! Aborting restart; this is a development build.", streamName.c_str());
|
||||
break;
|
||||
|
@ -57,8 +72,11 @@ int main(int argc, char * argv[]) {
|
|||
DEBUG_MSG(DLVL_DEVEL, "Input for stream %s uncleanly shut down! Restarting...", streamName.c_str());
|
||||
}
|
||||
}
|
||||
playerLock.post();
|
||||
playerLock.close();
|
||||
if (conv.needsLock()){
|
||||
playerLock.post();
|
||||
playerLock.unlink();
|
||||
playerLock.close();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
233
src/io.cpp
233
src/io.cpp
|
@ -1,4 +1,9 @@
|
|||
#include <mist/stream.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/auth.h>
|
||||
#include <mist/encode.h>
|
||||
#include <mist/bitfields.h>
|
||||
#include <cstdlib>
|
||||
#include "io.h"
|
||||
|
||||
namespace Mist {
|
||||
|
@ -11,12 +16,29 @@ namespace Mist {
|
|||
//Open the page for the metadata
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||
metaPages[0].init(pageName, myMeta.getSendLen(), true);
|
||||
nProxy.metaPages[0].init(pageName, DEFAULT_STRM_PAGE_SIZE, true);
|
||||
//Make sure we don't delete it on accident
|
||||
metaPages[0].master = false;
|
||||
nProxy.metaPages[0].master = false;
|
||||
|
||||
//Write the metadata to the page
|
||||
myMeta.writeTo(metaPages[0].mapped);
|
||||
myMeta.writeTo(nProxy.metaPages[0].mapped);
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
if (!nProxy.metaPages.count(0)) {
|
||||
initiateMeta();
|
||||
}
|
||||
//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;
|
||||
}
|
||||
}
|
||||
//Negotiate the requested track if needed.
|
||||
return nProxy.bufferStart(tid, pageNumber, myMeta);
|
||||
}
|
||||
|
||||
///Starts the buffering of a new page.
|
||||
|
@ -26,30 +48,38 @@ namespace Mist {
|
|||
///Buffering itself is done by bufferNext().
|
||||
///\param tid The trackid 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) {
|
||||
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
|
||||
if (!metaPages.count(0)) {
|
||||
initiateMeta();
|
||||
}
|
||||
//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;
|
||||
}
|
||||
}
|
||||
bool negotiationProxy::bufferStart(unsigned long tid, unsigned long pageNumber, DTSC::Meta & myMeta) {
|
||||
//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 (trackState[tid] != FILL_ACC) {
|
||||
///\return false if the track has not been accepted (yet)
|
||||
return false;
|
||||
}
|
||||
|
||||
//If the track is accepted, we will have a mapped tid
|
||||
unsigned long mapTid = trackMap[tid];
|
||||
|
||||
//Before we start a new page, make sure we can be heard by the buffer about this.
|
||||
//Otherwise, it might linger forever as a nasty data leak.
|
||||
//Nobody likes nasty data leaks.
|
||||
{
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), mapTid);
|
||||
IPC::sharedPage checkPage(pageName, SHM_TRACK_INDEX_SIZE, false, false);
|
||||
if (!checkPage.mapped){
|
||||
WARN_MSG("Buffer deleted %s@%lu (%s) index. Re-negotiating...", streamName.c_str(), mapTid, myMeta.tracks[tid].codec.c_str());
|
||||
trackState.erase(tid);
|
||||
trackMap.erase(tid);
|
||||
trackOffset.erase(tid);
|
||||
pagesByTrack.erase(tid);
|
||||
metaPages.erase(tid);
|
||||
curPageNum.erase(tid);
|
||||
curPage.erase(tid);
|
||||
return bufferStart(tid, pageNumber, myMeta);
|
||||
}
|
||||
}
|
||||
|
||||
//If we are currently buffering a page, abandon it completely and print a message about this
|
||||
//This page will NEVER be deleted, unless we open it again later.
|
||||
if (curPage.count(tid)) {
|
||||
|
@ -94,6 +124,9 @@ namespace Mist {
|
|||
//Initialize the bookkeeping entry, and set the current offset to 0, to allow for using it in bufferNext()
|
||||
pagesByTrack[tid][pageNumber].curOffset = 0;
|
||||
|
||||
HIGH_MSG("Start buffering page %lu on track %lu~>%lu successful", pageNumber, tid, mapTid);
|
||||
|
||||
|
||||
if (myMeta.live){
|
||||
//Register this page on the meta page
|
||||
//NOTE: It is important that this only happens if the stream is live....
|
||||
|
@ -102,18 +135,17 @@ namespace Mist {
|
|||
int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
|
||||
if ((tmpOffset[0] == 0 && tmpOffset[1] == 0)) {
|
||||
tmpOffset[0] = htonl(curPageNum[tid]);
|
||||
if (pagesByTrack[tid][pageNumber].dataSize == (25 * 1024 * 1024)){
|
||||
tmpOffset[1] = htonl(1000);
|
||||
} else {
|
||||
tmpOffset[1] = htonl(pagesByTrack[tid][pageNumber].keyNum);
|
||||
}
|
||||
tmpOffset[1] = htonl(1000);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inserted){
|
||||
FAIL_MSG("Could not insert page in track index. Aborting.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
HIGH_MSG("Start buffering page %lu on track %lu~>%lu successful", pageNumber, tid, mapTid);
|
||||
///\return true if everything was successful
|
||||
return true;
|
||||
}
|
||||
|
@ -128,13 +160,28 @@ namespace Mist {
|
|||
//A different process will handle this for us
|
||||
return;
|
||||
}
|
||||
unsigned long mapTid = trackMap[tid];
|
||||
if (!pagesByTrack.count(tid)){
|
||||
unsigned long mapTid = nProxy.trackMap[tid];
|
||||
|
||||
DEBUG_MSG(DLVL_HIGH, "Removing page %lu on track %lu~>%lu from the corresponding metaPage", pageNumber, tid, mapTid);
|
||||
int i = 0;
|
||||
for (; i < 1024; i++) {
|
||||
int * tmpOffset = (int *)(nProxy.metaPages[tid].mapped + (i * 8));
|
||||
if (ntohl(tmpOffset[0]) == pageNumber) {
|
||||
tmpOffset[0] = 0;
|
||||
tmpOffset[1] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == 1024){
|
||||
FAIL_MSG("Could not erase page %lu for track %lu->%lu stream %s from track index!", pageNumber, tid, mapTid, streamName.c_str());
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
}
|
||||
//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);
|
||||
return;
|
||||
}
|
||||
|
@ -146,23 +193,14 @@ namespace Mist {
|
|||
#ifdef __CYGWIN__
|
||||
toErase.init(pageName, 26 * 1024 * 1024, false);
|
||||
#else
|
||||
toErase.init(pageName, pagesByTrack[tid][pageNumber].dataSize, false);
|
||||
toErase.init(pageName, nProxy.pagesByTrack[tid][pageNumber].dataSize, false);
|
||||
#endif
|
||||
//Set the master flag so that the page will be destroyed once it leaves scope
|
||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||
IPC::releasePage(pageName);
|
||||
#endif
|
||||
toErase.master = true;
|
||||
|
||||
//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);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
|
||||
if (ntohl(tmpOffset[0]) == pageNumber) {
|
||||
tmpOffset[0] = 0;
|
||||
tmpOffset[1] = 0;
|
||||
}
|
||||
}
|
||||
//Leaving scope here, the page will now be destroyed
|
||||
}
|
||||
|
||||
|
@ -170,7 +208,7 @@ namespace Mist {
|
|||
///Checks whether a key is buffered
|
||||
///\param tid The trackid on which to locate the key
|
||||
///\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 bufferedOnPage(tid, keyNum);
|
||||
}
|
||||
|
@ -178,24 +216,24 @@ namespace Mist {
|
|||
///Returns the pagenumber where this key is buffered on
|
||||
///\param tid The trackid on which to locate the key
|
||||
///\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
|
||||
if (!trackMap.count(tid) || !metaPages.count(tid) || !metaPages[tid].mapped) {
|
||||
///\return 0 if the page has not been mapped yet
|
||||
return 0;
|
||||
}
|
||||
//Loop over the index page
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
int len = metaPages[tid].len / 8;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
|
||||
int pageNum = ntohl(tmpOffset[0]);
|
||||
int keyAmount = ntohl(tmpOffset[1]);
|
||||
unsigned int keyAmount = ntohl(tmpOffset[1]);
|
||||
if (keyAmount == 0){continue;}
|
||||
//Check whether the key is on this page
|
||||
unsigned int pageNum = ntohl(tmpOffset[0]);
|
||||
if (pageNum <= keyNum && keyNum < pageNum + keyAmount) {
|
||||
///\return The pagenumber of the page the key is located on, if the page is registered on the track index page
|
||||
return pageNum;
|
||||
}
|
||||
}
|
||||
///\return 0 if the key was not found
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -205,12 +243,16 @@ namespace Mist {
|
|||
std::string packData = pack.toNetPacked();
|
||||
DTSC::Packet newPack(packData.data(), packData.size());
|
||||
///\note Internally calls bufferNext(DTSC::Packet & pack)
|
||||
bufferNext(newPack);
|
||||
nProxy.bufferNext(newPack, myMeta);
|
||||
}
|
||||
|
||||
///Buffers the next packet on the currently opened page
|
||||
///\param pack The packet to buffer
|
||||
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
|
||||
unsigned long tid = pack.getTrackId();
|
||||
unsigned long mapTid = trackMap[tid];
|
||||
|
@ -261,6 +303,10 @@ namespace Mist {
|
|||
///Registers the data page on the track index page as well
|
||||
///\param tid The trackid of the page to finalize
|
||||
void InOutBase::bufferFinalize(unsigned long tid) {
|
||||
nProxy.bufferFinalize(tid, myMeta);
|
||||
}
|
||||
|
||||
void negotiationProxy::bufferFinalize(unsigned long tid, DTSC::Meta & myMeta){
|
||||
unsigned long mapTid = trackMap[tid];
|
||||
//If no page is open, do nothing
|
||||
if (!curPage.count(tid)) {
|
||||
|
@ -342,6 +388,10 @@ namespace Mist {
|
|||
///Initiates/continues negotiation with the buffer as well
|
||||
///\param packet The packet to buffer
|
||||
void InOutBase::bufferLivePacket(DTSC::Packet & packet){
|
||||
nProxy.bufferLivePacket(packet, myMeta);
|
||||
}
|
||||
|
||||
void negotiationProxy::bufferLivePacket(DTSC::Packet & packet, DTSC::Meta & myMeta){
|
||||
myMeta.vod = false;
|
||||
myMeta.live = true;
|
||||
//Store the trackid for easier access
|
||||
|
@ -353,31 +403,13 @@ namespace Mist {
|
|||
}
|
||||
//If the track is not negotiated yet, start the negotiation
|
||||
if (!trackState.count(tid)) {
|
||||
continueNegotiate(tid);
|
||||
continueNegotiate(tid, myMeta);
|
||||
}
|
||||
//If the track is declined, stop here
|
||||
if (trackState[tid] == FILL_DEC) {
|
||||
INFO_MSG("Track %lu Declined", tid);
|
||||
return;
|
||||
}
|
||||
//Check if a different track is already accepted
|
||||
bool shouldBlock = true;
|
||||
if (pagesByTrack.count(tid) && pagesByTrack[tid].size()) {
|
||||
for (std::map<unsigned long, negotiationState>::iterator it = trackState.begin(); it != trackState.end(); it++) {
|
||||
if (it->second == FILL_ACC) {
|
||||
//If so, we do not block here
|
||||
shouldBlock = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
//Block if no tracks are accepted yet, until we have a definite state
|
||||
if (shouldBlock) {
|
||||
while (trackState[tid] != FILL_DEC && trackState[tid] != FILL_ACC) {
|
||||
INFO_MSG("Blocking on track %lu", tid);
|
||||
continueNegotiate(tid);
|
||||
Util::sleep(500);
|
||||
}
|
||||
}
|
||||
//This update needs to happen whether the track is accepted or not.
|
||||
///\todo Figure out how to act with declined track here
|
||||
bool isKeyframe = false;
|
||||
|
@ -398,21 +430,21 @@ namespace Mist {
|
|||
}
|
||||
//Determine if we need to open the next page
|
||||
int nextPageNum = -1;
|
||||
if (isKeyframe || !pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0) {
|
||||
if (isKeyframe && trackState[tid] == FILL_ACC) {
|
||||
//If there is no page, create it
|
||||
if (!pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0) {
|
||||
nextPageNum = 1;
|
||||
pagesByTrack[tid][1].dataSize = (25 * 1024 * 1024);//Initialize op 25mb
|
||||
pagesByTrack[tid][1].dataSize = DEFAULT_DATA_PAGE_SIZE;//Initialize op 25mb
|
||||
pagesByTrack[tid][1].pageNum = 1;
|
||||
}
|
||||
//Take the last allocated page
|
||||
std::map<unsigned long, DTSCPageData>::reverse_iterator tmpIt = pagesByTrack[tid].rbegin();
|
||||
//Compare on 8 mb boundary
|
||||
if (tmpIt->second.curOffset > (8 * 1024 * 1024)) {
|
||||
if (tmpIt->second.curOffset > FLIP_DATA_PAGE_SIZE) {
|
||||
//Create the book keeping data for the new page
|
||||
nextPageNum = tmpIt->second.pageNum + tmpIt->second.keyNum;
|
||||
INFO_MSG("We should go to next page now, transition from %lu to %d", tmpIt->second.pageNum, nextPageNum);
|
||||
pagesByTrack[tid][nextPageNum].dataSize = (25 * 1024 * 1024);
|
||||
pagesByTrack[tid][nextPageNum].dataSize = DEFAULT_DATA_PAGE_SIZE;
|
||||
pagesByTrack[tid][nextPageNum].pageNum = nextPageNum;
|
||||
}
|
||||
pagesByTrack[tid].rbegin()->second.lastKeyTime = packet.getTime();
|
||||
|
@ -426,6 +458,10 @@ namespace Mist {
|
|||
nextPageNum = 1;
|
||||
}
|
||||
}
|
||||
//If we have no pages by track, we have not received a starting keyframe yet. Drop this packet.
|
||||
if (!pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0){
|
||||
return;
|
||||
}
|
||||
//At this point we can stop parsing when the track is not accepted
|
||||
if (trackState[tid] != FILL_ACC) {
|
||||
return;
|
||||
|
@ -435,16 +471,20 @@ namespace Mist {
|
|||
if (!curPageNum.count(tid) || nextPageNum != curPageNum[tid]) {
|
||||
if (curPageNum.count(tid)) {
|
||||
//Close the currently opened page when it exists
|
||||
bufferFinalize(tid);
|
||||
bufferFinalize(tid, myMeta);
|
||||
}
|
||||
//Open the new page
|
||||
bufferStart(tid, nextPageNum);
|
||||
bufferStart(tid, nextPageNum, myMeta);
|
||||
}
|
||||
//Buffer the packet
|
||||
bufferNext(packet);
|
||||
bufferNext(packet, myMeta);
|
||||
}
|
||||
|
||||
void InOutBase::continueNegotiate(unsigned long tid) {
|
||||
void InOutBase::continueNegotiate(unsigned long tid, bool quickNegotiate) {
|
||||
nProxy.continueNegotiate(tid, myMeta, quickNegotiate);
|
||||
}
|
||||
|
||||
void negotiationProxy::continueNegotiate(unsigned long tid, DTSC::Meta & myMeta, bool quickNegotiate) {
|
||||
if (!tid) {
|
||||
return;
|
||||
}
|
||||
|
@ -457,7 +497,7 @@ namespace Mist {
|
|||
trackState[tid] = FILL_ACC;
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), tid);
|
||||
metaPages[tid].init(pageName, 8 * 1024 * 1024, true);
|
||||
metaPages[tid].init(pageName, SHM_TRACK_INDEX_SIZE, true);
|
||||
metaPages[tid].master = false;
|
||||
return;
|
||||
}
|
||||
|
@ -490,7 +530,7 @@ namespace Mist {
|
|||
if (!userClient.getData()){
|
||||
char userPageName[100];
|
||||
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();
|
||||
if (!tmp) {
|
||||
|
@ -500,12 +540,47 @@ namespace Mist {
|
|||
unsigned long offset = 6 * trackOffset[tid];
|
||||
//If we have a new track to negotiate
|
||||
if (!trackState.count(tid)) {
|
||||
INFO_MSG("Starting negotiation for incoming track %lu, at offset %lu", tid, trackOffset[tid]);
|
||||
memset(tmp + offset, 0, 4);
|
||||
tmp[offset] = 0x80;
|
||||
tmp[offset + 4] = ((tid >> 8) & 0xFF);
|
||||
tmp[offset + 5] = (tid & 0xFF);
|
||||
trackState[tid] = FILL_NEW;
|
||||
if (quickNegotiate){
|
||||
unsigned long finalTid = tid;
|
||||
unsigned short firstPage = 1;
|
||||
MEDIUM_MSG("Buffer has indicated that incoming track %lu should start writing on track %lu, page %lu", tid, finalTid, firstPage);
|
||||
trackMap[tid] = finalTid;
|
||||
if (myMeta.tracks.count(finalTid) && myMeta.tracks[finalTid].lastms){
|
||||
myMeta.tracks[finalTid].lastms = 0;
|
||||
}
|
||||
trackState[tid] = FILL_ACC;
|
||||
|
||||
|
||||
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_META, streamName.c_str(), finalTid);
|
||||
metaPages[tid].init(pageName, 8 * 1024 * 1024, true);
|
||||
metaPages[tid].master = false;
|
||||
DTSC::Meta tmpMeta;
|
||||
tmpMeta.tracks[finalTid] = myMeta.tracks[tid];
|
||||
tmpMeta.tracks[finalTid].trackID = finalTid;
|
||||
JSON::Value tmpVal = tmpMeta.toJSON();
|
||||
std::string tmpStr = tmpVal.toNetPacked();
|
||||
memcpy(metaPages[tid].mapped, tmpStr.data(), tmpStr.size());
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), finalTid);
|
||||
metaPages[tid].init(pageName, SHM_TRACK_INDEX_SIZE, true);
|
||||
metaPages[tid].master = false;
|
||||
Bit::htobl(tmp + offset, finalTid | 0xC0000000);
|
||||
Bit::htobs(tmp + offset + 4, firstPage);
|
||||
}else{
|
||||
INFO_MSG("Starting negotiation for incoming track %lu, at offset %lu", tid, trackOffset[tid]);
|
||||
memset(tmp + offset, 0, 4);
|
||||
tmp[offset] = 0x80;
|
||||
tmp[offset + 4] = ((tid >> 8) & 0xFF);
|
||||
tmp[offset + 5] = (tid & 0xFF);
|
||||
trackState[tid] = FILL_NEW;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||
|
@ -579,7 +654,7 @@ namespace Mist {
|
|||
trackState[tid] = FILL_ACC;
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), finalTid);
|
||||
metaPages[tid].init(pageName, 8 * 1024 * 1024, true);
|
||||
metaPages[tid].init(pageName, SHM_TRACK_INDEX_SIZE, true);
|
||||
metaPages[tid].master = false;
|
||||
break;
|
||||
}
|
||||
|
|
53
src/io.h
53
src/io.h
|
@ -25,6 +25,36 @@ namespace Mist {
|
|||
unsigned long lastKeyTime;///<The last key time encountered on this track.
|
||||
};
|
||||
|
||||
class negotiationProxy {
|
||||
public:
|
||||
negotiationProxy() {}
|
||||
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
|
||||
|
||||
void continueNegotiate(unsigned long tid, DTSC::Meta & myMeta, bool quickNegotiate = false);
|
||||
};
|
||||
|
||||
///\brief Class containing all basic input and output functions.
|
||||
class InOutBase {
|
||||
public:
|
||||
|
@ -36,32 +66,23 @@ namespace Mist {
|
|||
void bufferRemove(unsigned long tid, unsigned long pageNumber);
|
||||
void bufferLivePacket(JSON::Value & packet);
|
||||
void bufferLivePacket(DTSC::Packet & packet);
|
||||
bool isBuffered(unsigned long tid, unsigned long keyNum);
|
||||
unsigned long bufferedOnPage(unsigned long tid, unsigned long keyNum);
|
||||
protected:
|
||||
void continueNegotiate(unsigned long tid, bool quickNegotiate = false);
|
||||
|
||||
|
||||
|
||||
bool standAlone;
|
||||
static Util::Config * config;
|
||||
|
||||
void continueNegotiate(unsigned long tid);
|
||||
negotiationProxy nProxy;
|
||||
|
||||
DTSC::Packet thisPacket;//The current packet that is being parsed
|
||||
|
||||
std::string streamName;///< Name of the stream to connect to
|
||||
IPC::sharedClient userClient;///< Shared memory used for connection to Mixer process.
|
||||
std::string streamName;
|
||||
|
||||
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::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.
|
||||
std::map<unsigned long, std::deque<DTSC::Packet> > trackBuffer; ///< Buffer to be used during active track negotiation
|
||||
DTSC::Meta myMeta;///< Stores either the input or output metadata
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include OUTPUTTYPE
|
||||
#include <mist/config.h>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
int spawnForked(Socket::Connection & S){
|
||||
mistOut tmp(S);
|
||||
|
@ -15,6 +16,7 @@ int main(int argc, char * argv[]) {
|
|||
std::cout << mistOut::capa.toString() << std::endl;
|
||||
return -1;
|
||||
}
|
||||
conf.activate();
|
||||
if (mistOut::listenMode()){
|
||||
conf.serveForkedSocket(spawnForked);
|
||||
}else{
|
||||
|
|
|
@ -44,7 +44,6 @@ namespace Mist {
|
|||
maxSkipAhead = 7500;
|
||||
minSkipAhead = 5000;
|
||||
realTime = 1000;
|
||||
completeKeyReadyTimeOut = false;
|
||||
if (myConn){
|
||||
setBlocking(true);
|
||||
}else{
|
||||
|
@ -57,26 +56,31 @@ namespace Mist {
|
|||
isBlocking = blocking;
|
||||
myConn.setBlocking(isBlocking);
|
||||
}
|
||||
|
||||
Output::~Output(){}
|
||||
|
||||
void Output::updateMeta(){
|
||||
//read metadata from page to myMeta variable
|
||||
if (nProxy.metaPages[0].mapped){
|
||||
IPC::semaphore * liveSem = 0;
|
||||
if (!myMeta.vod){
|
||||
static char liveSemName[NAME_BUFFER_SIZE];
|
||||
snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
|
||||
IPC::semaphore liveMeta(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
bool lock = myMeta.live;
|
||||
if (lock){
|
||||
liveMeta.wait();
|
||||
}
|
||||
if (metaPages[0].mapped){
|
||||
DTSC::Packet tmpMeta(metaPages[0].mapped, metaPages[0].len, true);
|
||||
liveSem = new IPC::semaphore(liveSemName, O_RDWR, ACCESSPERMS, 1);
|
||||
if (*liveSem){
|
||||
liveSem->wait();
|
||||
}else{
|
||||
delete liveSem;
|
||||
liveSem = 0;
|
||||
}
|
||||
}
|
||||
DTSC::Packet tmpMeta(nProxy.metaPages[0].mapped, nProxy.metaPages[0].len, true);
|
||||
if (tmpMeta.getVersion()){
|
||||
myMeta.reinit(tmpMeta);
|
||||
}
|
||||
}
|
||||
if (lock){
|
||||
liveMeta.post();
|
||||
if (liveSem){
|
||||
liveSem->post();
|
||||
delete liveSem;
|
||||
liveSem = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,7 +99,7 @@ namespace Mist {
|
|||
if (isInitialized){
|
||||
return;
|
||||
}
|
||||
if (metaPages[0].mapped){
|
||||
if (nProxy.metaPages[0].mapped){
|
||||
return;
|
||||
}
|
||||
if (streamName.size() < 1){
|
||||
|
@ -120,23 +124,24 @@ namespace Mist {
|
|||
return myConn.getBinHost();
|
||||
}
|
||||
|
||||
bool Output::isReadyForPlay() {
|
||||
if (myMeta.tracks.size()){
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.keys.size() >= 2){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/// Connects or reconnects to the stream.
|
||||
/// Assumes streamName class member has been set already.
|
||||
/// 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.
|
||||
/// Finally, calls updateMeta() and stats()
|
||||
/// After assuring stream is online, clears nProxy.metaPages, then sets nProxy.metaPages[0], statsPage and nProxy.userClient to (hopefully) valid handles.
|
||||
/// Finally, calls updateMeta()
|
||||
void Output::reconnect(){
|
||||
if (!Util::startInput(streamName)){
|
||||
DEBUG_MSG(DLVL_FAIL, "Opening stream failed - aborting initalization");
|
||||
onFail();
|
||||
return;
|
||||
}
|
||||
char pageId[NAME_BUFFER_SIZE];
|
||||
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||
metaPages.clear();
|
||||
metaPages[0].init(pageId, DEFAULT_META_PAGE_SIZE);
|
||||
if (!metaPages[0].mapped){
|
||||
FAIL_MSG("Could not connect to server for %s", streamName.c_str());
|
||||
FAIL_MSG("Opening stream %s failed - aborting initalization", streamName.c_str());
|
||||
onFail();
|
||||
return;
|
||||
}
|
||||
|
@ -144,14 +149,35 @@ namespace Mist {
|
|||
statsPage.finish();
|
||||
}
|
||||
statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
|
||||
if (userClient.getData()){
|
||||
userClient.finish();
|
||||
if (nProxy.userClient.getData()){
|
||||
nProxy.userClient.finish();
|
||||
}
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
stats();
|
||||
nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
char pageId[NAME_BUFFER_SIZE];
|
||||
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||
nProxy.metaPages.clear();
|
||||
nProxy.metaPages[0].init(pageId, DEFAULT_STRM_PAGE_SIZE);
|
||||
if (!nProxy.metaPages[0].mapped){
|
||||
FAIL_MSG("Could not connect to server for %s", streamName.c_str());
|
||||
onFail();
|
||||
return;
|
||||
}
|
||||
stats(true);
|
||||
updateMeta();
|
||||
if (myMeta.live && !isReadyForPlay()){
|
||||
unsigned long long waitUntil = Util::epoch() + 15;
|
||||
while (!isReadyForPlay()){
|
||||
if (Util::epoch() > waitUntil){
|
||||
INFO_MSG("Giving up waiting for playable tracks. Stream: %s, IP: %s", streamName.c_str(), getConnectedHost().c_str());
|
||||
break;
|
||||
}
|
||||
Util::wait(750);
|
||||
stats();
|
||||
updateMeta();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Output::selectDefaultTracks(){
|
||||
|
@ -192,10 +218,12 @@ namespace Mist {
|
|||
}
|
||||
if (!found){
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){
|
||||
if (trit->second.codec == (*itc).asStringRef()){
|
||||
if (trit->second.codec == (*itc).asStringRef() || (*itc).asStringRef() == "*"){
|
||||
genCounter++;
|
||||
found = true;
|
||||
break;
|
||||
if ((*itc).asStringRef() != "*"){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -206,16 +234,16 @@ namespace Mist {
|
|||
if (selCounter + genCounter > bestSoFarCount){
|
||||
bestSoFarCount = selCounter + genCounter;
|
||||
bestSoFar = index;
|
||||
DEBUG_MSG(DLVL_HIGH, "Match (%u/%u): %s", selCounter, selCounter+genCounter, (*it).toString().c_str());
|
||||
HIGH_MSG("Match (%u/%u): %s", selCounter, selCounter+genCounter, (*it).toString().c_str());
|
||||
}
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Not a match for currently selected tracks: %s", (*it).toString().c_str());
|
||||
VERYHIGH_MSG("Not a match for currently selected tracks: %s", (*it).toString().c_str());
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Trying to fill: %s", capa["codecs"][bestSoFar].toString().c_str());
|
||||
MEDIUM_MSG("Trying to fill: %s", capa["codecs"][bestSoFar].toString().c_str());
|
||||
//try to fill as many codecs simultaneously as possible
|
||||
if (capa["codecs"][bestSoFar].size() > 0){
|
||||
jsonForEach(capa["codecs"][bestSoFar], itb) {
|
||||
|
@ -233,10 +261,12 @@ namespace Mist {
|
|||
}
|
||||
if (!found){
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){
|
||||
if (trit->second.codec == (*itc).asStringRef()){
|
||||
if (trit->second.codec == (*itc).asStringRef() || (*itc).asStringRef() == "*"){
|
||||
selectedTracks.insert(trit->first);
|
||||
found = true;
|
||||
break;
|
||||
if ((*itc).asStringRef() != "*"){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -259,6 +289,37 @@ namespace Mist {
|
|||
DEBUG_MSG(DLVL_MEDIUM, "Selected tracks: %s (%lu)", selected.str().c_str(), selectedTracks.size());
|
||||
}
|
||||
|
||||
if (selectedTracks.size() == 0) {
|
||||
INSANE_MSG("We didn't find any tracks which that we can use. selectedTrack.size() is 0.");
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){
|
||||
INSANE_MSG("Found track/codec: %s", trit->second.codec.c_str());
|
||||
}
|
||||
static std::string source;
|
||||
if (!source.size()){
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, false, false); ///< Contains server configuration and capabilities
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
std::string smp = streamName.substr(0, streamName.find_first_of("+ "));
|
||||
//check if smp (everything before + or space) exists
|
||||
DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(smp);
|
||||
if (streamCfg){
|
||||
source = streamCfg.getMember("source").asString();
|
||||
}
|
||||
configLock.post();
|
||||
configLock.close();
|
||||
}
|
||||
if (!myMeta.tracks.size() && (source.find("dtsc://") == 0)){
|
||||
//Wait 5 seconds and try again. Keep a counter, try at most 3 times
|
||||
static int counter = 0;
|
||||
if (counter++ < 10){
|
||||
Util::wait(1000);
|
||||
nProxy.userClient.keepAlive();
|
||||
stats();
|
||||
updateMeta();
|
||||
selectDefaultTracks();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the buffer, sets parseData to false, and generally makes not very much happen at all.
|
||||
|
@ -287,14 +348,15 @@ namespace Mist {
|
|||
}
|
||||
|
||||
int Output::pageNumForKey(long unsigned int trackId, long long int keyNum){
|
||||
if (!metaPages.count(trackId)){
|
||||
if (!nProxy.metaPages.count(trackId) || !nProxy.metaPages[trackId].mapped){
|
||||
char id[NAME_BUFFER_SIZE];
|
||||
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), trackId);
|
||||
metaPages[trackId].init(id, 8 * 1024);
|
||||
nProxy.metaPages[trackId].init(id, SHM_TRACK_INDEX_SIZE);
|
||||
}
|
||||
int len = metaPages[trackId].len / 8;
|
||||
if (!nProxy.metaPages[trackId].mapped){return -1;}
|
||||
int len = nProxy.metaPages[trackId].len / 8;
|
||||
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]);
|
||||
if (amountKey == 0){continue;}
|
||||
long tmpKey = ntohl(tmpOffset[0]);
|
||||
|
@ -304,19 +366,40 @@ namespace Mist {
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// Gets the highest page number available for the given trackId.
|
||||
int Output::pageNumMax(long unsigned int trackId){
|
||||
if (!nProxy.metaPages.count(trackId) || !nProxy.metaPages[trackId].mapped){
|
||||
char id[NAME_BUFFER_SIZE];
|
||||
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), trackId);
|
||||
nProxy.metaPages[trackId].init(id, SHM_TRACK_INDEX_SIZE);
|
||||
}
|
||||
if (!nProxy.metaPages[trackId].mapped){return -1;}
|
||||
int len = nProxy.metaPages[trackId].len / 8;
|
||||
int highest = -1;
|
||||
for (int i = 0; i < len; i++){
|
||||
int * tmpOffset = (int *)(nProxy.metaPages[trackId].mapped + (i * 8));
|
||||
long amountKey = ntohl(tmpOffset[1]);
|
||||
if (amountKey == 0){continue;}
|
||||
long tmpKey = ntohl(tmpOffset[0]);
|
||||
if (tmpKey > highest){highest = tmpKey;}
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
|
||||
void Output::loadPageForKey(long unsigned int trackId, long long int keyNum){
|
||||
if (myMeta.vod && keyNum > myMeta.tracks[trackId].keys.rbegin()->getNumber()){
|
||||
curPage.erase(trackId);
|
||||
INFO_MSG("Seek in track %lu to key %lld aborted, is > %lld", trackId, keyNum, myMeta.tracks[trackId].keys.rbegin()->getNumber());
|
||||
nProxy.curPage.erase(trackId);
|
||||
currKeyOpen.erase(trackId);
|
||||
return;
|
||||
}
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Loading track %lu, containing key %lld", trackId, keyNum);
|
||||
VERYHIGH_MSG("Loading track %lu, containing key %lld", trackId, keyNum);
|
||||
unsigned int timeout = 0;
|
||||
unsigned long pageNum = pageNumForKey(trackId, keyNum);
|
||||
while (pageNum == -1){
|
||||
if (!timeout){
|
||||
DEBUG_MSG(DLVL_HIGH, "Requesting page with key %lu:%lld", trackId, keyNum);
|
||||
HIGH_MSG("Requesting page with key %lu:%lld", trackId, keyNum);
|
||||
}
|
||||
++timeout;
|
||||
//if we've been waiting for this page for 3 seconds, reconnect to the stream - something might be going wrong...
|
||||
|
@ -325,8 +408,8 @@ namespace Mist {
|
|||
reconnect();
|
||||
}
|
||||
if (timeout > 100){
|
||||
DEBUG_MSG(DLVL_FAIL, "Timeout while waiting for requested page %lld for track %lu. Aborting.", keyNum, trackId);
|
||||
curPage.erase(trackId);
|
||||
FAIL_MSG("Timeout while waiting for requested page %lld for track %lu. Aborting.", keyNum, trackId);
|
||||
nProxy.curPage.erase(trackId);
|
||||
currKeyOpen.erase(trackId);
|
||||
return;
|
||||
}
|
||||
|
@ -335,7 +418,7 @@ namespace Mist {
|
|||
}else{
|
||||
nxtKeyNum[trackId] = 0;
|
||||
}
|
||||
stats();
|
||||
stats(true);
|
||||
Util::wait(100);
|
||||
pageNum = pageNumForKey(trackId, keyNum);
|
||||
}
|
||||
|
@ -345,20 +428,20 @@ namespace Mist {
|
|||
}else{
|
||||
nxtKeyNum[trackId] = 0;
|
||||
}
|
||||
stats();
|
||||
nxtKeyNum[trackId] = pageNum;
|
||||
stats(true);
|
||||
|
||||
if (currKeyOpen.count(trackId) && currKeyOpen[trackId] == (unsigned int)pageNum){
|
||||
return;
|
||||
}
|
||||
char id[NAME_BUFFER_SIZE];
|
||||
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), trackId, pageNum);
|
||||
curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE);
|
||||
if (!(curPage[trackId].mapped)){
|
||||
DEBUG_MSG(DLVL_FAIL, "Initializing page %s failed", curPage[trackId].name.c_str());
|
||||
nProxy.curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE);
|
||||
if (!(nProxy.curPage[trackId].mapped)){
|
||||
FAIL_MSG("Initializing page %s failed", nProxy.curPage[trackId].name.c_str());
|
||||
return;
|
||||
}
|
||||
currKeyOpen[trackId] = pageNum;
|
||||
VERYHIGH_MSG("Page %s loaded for %s", id, streamName.c_str());
|
||||
}
|
||||
|
||||
/// Prepares all tracks from selectedTracks for seeking to the specified ms position.
|
||||
|
@ -373,44 +456,58 @@ namespace Mist {
|
|||
if (myMeta.live){
|
||||
updateMeta();
|
||||
}
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Seeking to %llums", pos);
|
||||
MEDIUM_MSG("Seeking to %llums", pos);
|
||||
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
seek(*it, pos);
|
||||
if (myMeta.tracks.count(*it)){
|
||||
seek(*it, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Output::seek(unsigned int tid, unsigned long long pos, bool getNextKey){
|
||||
loadPageForKey(tid, getKeyForTime(tid, pos) + (getNextKey?1:0));
|
||||
if (!curPage.count(tid) || !curPage[tid].mapped){
|
||||
INFO_MSG("Aborting seek to %llums in track %u, not available.", pos, tid);
|
||||
if (myMeta.tracks[tid].lastms < pos){
|
||||
INFO_MSG("Aborting seek to %llums in track %u: past end of track (= %llums).", pos, tid, myMeta.tracks[tid].lastms);
|
||||
return false;
|
||||
}
|
||||
unsigned int keyNum = getKeyForTime(tid, pos);
|
||||
if (myMeta.tracks[tid].getKey(keyNum).getTime() > pos){
|
||||
if (myMeta.live){
|
||||
INFO_MSG("Actually seeking to %d, for %d is not available any more", myMeta.tracks[tid].getKey(keyNum).getTime(), pos);
|
||||
pos = myMeta.tracks[tid].getKey(keyNum).getTime();
|
||||
}
|
||||
}
|
||||
loadPageForKey(tid, keyNum + (getNextKey?1:0));
|
||||
if (!nProxy.curPage.count(tid) || !nProxy.curPage[tid].mapped){
|
||||
INFO_MSG("Aborting seek to %llums in track %u: not available.", pos, tid);
|
||||
return false;
|
||||
}
|
||||
sortedPageInfo tmp;
|
||||
tmp.tid = tid;
|
||||
tmp.offset = 0;
|
||||
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();
|
||||
char * mpd = curPage[tid].mapped;
|
||||
char * mpd = nProxy.curPage[tid].mapped;
|
||||
while ((long long)tmp.time < pos && tmpPack){
|
||||
tmp.offset += tmpPack.getDataLen();
|
||||
tmpPack.reInit(mpd + tmp.offset, 0, true);
|
||||
tmp.time = tmpPack.getTime();
|
||||
}
|
||||
if (tmpPack){
|
||||
HIGH_MSG("Sought to time %d in %s@%u", tmp.time, streamName.c_str(), tid);
|
||||
buffer.insert(tmp);
|
||||
return true;
|
||||
}else{
|
||||
//don't print anything for empty packets - not sign of corruption, just unfinished stream.
|
||||
if (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);
|
||||
if (nProxy.curPage[tid].mapped[tmp.offset] != 0){
|
||||
FAIL_MSG("Noes! Couldn't find packet on track %d because of some kind of corruption error or somesuch.", tid);
|
||||
}else{
|
||||
VERYHIGH_MSG("Track %d no data (key %u @ %u) - waiting...", tid, getKeyForTime(tid, pos) + (getNextKey?1:0), tmp.offset);
|
||||
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);
|
||||
}
|
||||
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));
|
||||
}else{
|
||||
return seek(tid, pos, getNextKey);
|
||||
|
@ -434,9 +531,8 @@ namespace Mist {
|
|||
}
|
||||
|
||||
int Output::run() {
|
||||
DEBUG_MSG(DLVL_MEDIUM, "MistOut client handler started");
|
||||
while (config->is_active && myConn.connected() && (wantRequest || parseData)){
|
||||
stats();
|
||||
DONTEVEN_MSG("MistOut client handler started");
|
||||
while (config->is_active && myConn && (wantRequest || parseData)){
|
||||
if (wantRequest){
|
||||
requestHandler();
|
||||
}
|
||||
|
@ -445,11 +541,67 @@ namespace Mist {
|
|||
initialize();
|
||||
}
|
||||
if ( !sentHeader){
|
||||
DEBUG_MSG(DLVL_DONTEVEN, "sendHeader");
|
||||
DONTEVEN_MSG("sendHeader");
|
||||
sendHeader();
|
||||
}
|
||||
prepareNext();
|
||||
if (!sought){
|
||||
if (myMeta.live){
|
||||
long unsigned int mainTrack = getMainSelectedTrack();
|
||||
//cancel if there are no keys in the main track
|
||||
if (!myMeta.tracks.count(mainTrack) || !myMeta.tracks[mainTrack].keys.size()){break;}
|
||||
//seek to the newest keyframe, unless that is <5s, then seek to the oldest keyframe
|
||||
unsigned long long seekPos = myMeta.tracks[mainTrack].keys.rbegin()->getTime();
|
||||
if (seekPos < 5000){
|
||||
seekPos = myMeta.tracks[mainTrack].keys.begin()->getTime();
|
||||
}
|
||||
seek(seekPos);
|
||||
}else{
|
||||
seek(0);
|
||||
}
|
||||
}
|
||||
if (prepareNext()){
|
||||
if (thisPacket){
|
||||
|
||||
|
||||
//slow down processing, if real time speed is wanted
|
||||
if (realTime){
|
||||
while (thisPacket.getTime() > (((Util::getMS() - firstTime)*1000)+maxSkipAhead)/realTime) {
|
||||
Util::sleep(std::min(thisPacket.getTime() - (((Util::getMS() - firstTime)*1000)+minSkipAhead)/realTime, 1000llu));
|
||||
stats();
|
||||
}
|
||||
}
|
||||
|
||||
//delay the stream until its current keyframe is complete, if only complete keys wanted
|
||||
if (completeKeysOnly){
|
||||
bool completeKeyReady = false;
|
||||
int timeoutTries = 40;//wait default 250ms*40=10 seconds
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.keys.size() >1){
|
||||
int thisTimeoutTries = ((it->second.lastms - it->second.firstms) / (it->second.keys.size()-1)) / 125;
|
||||
if (thisTimeoutTries > timeoutTries) timeoutTries = thisTimeoutTries;
|
||||
}
|
||||
}
|
||||
while(!completeKeyReady && timeoutTries>0){
|
||||
completeKeyReady = true;
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
if (!myMeta.tracks[*it].keys.size() || myMeta.tracks[*it].keys.rbegin()->getTime() + myMeta.tracks[*it].keys.rbegin()->getLength() <= thisPacket.getTime() ){
|
||||
completeKeyReady = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!completeKeyReady){
|
||||
timeoutTries--;//we count down
|
||||
stats();
|
||||
Util::wait(250);
|
||||
updateMeta();
|
||||
}
|
||||
}
|
||||
if (timeoutTries<=0){
|
||||
WARN_MSG("Waiting for key frame timed out");
|
||||
completeKeysOnly = false;
|
||||
}
|
||||
}
|
||||
|
||||
sendNext();
|
||||
}else{
|
||||
if (!onFinish()){
|
||||
|
@ -458,9 +610,12 @@ namespace Mist {
|
|||
}
|
||||
}
|
||||
}
|
||||
DEBUG_MSG(DLVL_MEDIUM, "MistOut client handler shutting down: %s, %s, %s", myConn.connected() ? "conn_active" : "conn_closed", wantRequest ? "want_request" : "no_want_request", parseData ? "parsing_data" : "not_parsing_data");
|
||||
stats();
|
||||
userClient.finish();
|
||||
}
|
||||
MEDIUM_MSG("MistOut client handler shutting down: %s, %s, %s", myConn.connected() ? "conn_active" : "conn_closed", wantRequest ? "want_request" : "no_want_request", parseData ? "parsing_data" : "not_parsing_data");
|
||||
|
||||
stats(true);
|
||||
nProxy.userClient.finish();
|
||||
statsPage.finish();
|
||||
myConn.close();
|
||||
return 0;
|
||||
|
@ -473,250 +628,258 @@ namespace Mist {
|
|||
return 0;
|
||||
}
|
||||
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
if (myMeta.tracks[*it].type == "video"){
|
||||
if (myMeta.tracks.count(*it) && myMeta.tracks[*it].type == "video"){
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
return *(selectedTracks.begin());
|
||||
}
|
||||
|
||||
void Output::prepareNext(){
|
||||
static int nonVideoCount = 0;
|
||||
if (!sought){
|
||||
if (myMeta.live){
|
||||
long unsigned int mainTrack = getMainSelectedTrack();
|
||||
if (myMeta.tracks[mainTrack].keys.size() < 2){
|
||||
if (!myMeta.tracks[mainTrack].keys.size()){
|
||||
myConn.close();
|
||||
return;
|
||||
}else{
|
||||
seek(myMeta.tracks[mainTrack].keys.begin()->getTime());
|
||||
prepareNext();
|
||||
return;
|
||||
}
|
||||
}
|
||||
unsigned long long seekPos = myMeta.tracks[mainTrack].keys.rbegin()->getTime();
|
||||
if (seekPos < 5000){
|
||||
seekPos = 0;
|
||||
}
|
||||
seek(seekPos);
|
||||
}else{
|
||||
seek(0);
|
||||
void Output::dropTrack(uint32_t trackId, std::string reason, bool probablyBad){
|
||||
//depending on whether this is probably bad and the current debug level, print a message
|
||||
unsigned int printLevel = DLVL_INFO;
|
||||
if (probablyBad){
|
||||
printLevel = DLVL_WARN;
|
||||
}
|
||||
DEBUG_MSG(printLevel, "Dropping %s (%s) track %lu@k%lu (nextP=%d, lastP=%d): %s", streamName.c_str(), myMeta.tracks[trackId].codec.c_str(), (long unsigned)trackId, nxtKeyNum[trackId]+1, pageNumForKey(trackId, nxtKeyNum[trackId]+1), pageNumMax(trackId), reason.c_str());
|
||||
//now actually drop the track from the buffer
|
||||
for (std::set<sortedPageInfo>::iterator it = buffer.begin(); it != buffer.end(); ++it){
|
||||
if (it->tid == trackId){
|
||||
buffer.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
selectedTracks.erase(trackId);
|
||||
}
|
||||
|
||||
///Attempts to prepare a new packet for output.
|
||||
///If thisPacket evaluates to false, playback has completed.
|
||||
///Could be called repeatedly in a loop if you really really want a new packet.
|
||||
/// \returns true if thisPacket was filled with the next packet.
|
||||
/// \returns false if we could not reliably determine the next packet yet.
|
||||
bool Output::prepareNext(){
|
||||
static bool atLivePoint = false;
|
||||
static int nonVideoCount = 0;
|
||||
static unsigned int emptyCount = 0;
|
||||
if (!buffer.size()){
|
||||
thisPacket.null();
|
||||
DEBUG_MSG(DLVL_DEVEL, "Buffer completely played out");
|
||||
onFinish();
|
||||
return;
|
||||
INFO_MSG("Buffer completely played out");
|
||||
return true;
|
||||
}
|
||||
sortedPageInfo nxt = *(buffer.begin());
|
||||
buffer.erase(buffer.begin());
|
||||
|
||||
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 (!myMeta.tracks.count(nxt.tid)){
|
||||
dropTrack(nxt.tid, "disappeared from metadata", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
DONTEVEN_MSG("Loading track %u (next=%lu), %llu ms", nxt.tid, nxtKeyNum[nxt.tid], nxt.time);
|
||||
|
||||
//if we're going to read past the end of the data page, load the next page
|
||||
//this only happens for VoD
|
||||
if (nxt.offset >= nProxy.curPage[nxt.tid].len){
|
||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
|
||||
nxt.offset = 0;
|
||||
if (curPage.count(nxt.tid) && curPage[nxt.tid].mapped){
|
||||
if (getDTSCTime(curPage[nxt.tid].mapped, nxt.offset) < nxt.time){
|
||||
ERROR_MSG("Time going backwards in track %u - dropping track.", nxt.tid);
|
||||
if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){
|
||||
if (getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset) < nxt.time){
|
||||
dropTrack(nxt.tid, "time going backwards");
|
||||
}else{
|
||||
nxt.time = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset);
|
||||
nxt.time = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
|
||||
//swap out the next object in the buffer with a new one
|
||||
buffer.erase(buffer.begin());
|
||||
buffer.insert(nxt);
|
||||
}
|
||||
prepareNext();
|
||||
return;
|
||||
}else{
|
||||
dropTrack(nxt.tid, "page load failure", true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!curPage.count(nxt.tid) || !curPage[nxt.tid].mapped){
|
||||
//mapping failure? Drop this track and go to next.
|
||||
//not an error - usually means end of stream.
|
||||
DEBUG_MSG(DLVL_DEVEL, "Track %u no page - dropping track.", nxt.tid);
|
||||
prepareNext();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
//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 (!nxt.time){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Timeless empty packet on track %u - dropping track.", nxt.tid);
|
||||
prepareNext();
|
||||
return;
|
||||
dropTrack(nxt.tid, "timeless empty packet");
|
||||
return false;
|
||||
}
|
||||
//if this is a live stream, we might have just reached the live point.
|
||||
//check where the next key is
|
||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
int nextPage = pageNumForKey(nxt.tid, nxtKeyNum[nxt.tid]+1);
|
||||
//are we live, and the next key hasn't shown up on another page? then we're waiting.
|
||||
//are we live, and the next key hasn't shown up on another page, then we're waiting.
|
||||
if (myMeta.live && currKeyOpen.count(nxt.tid) && (currKeyOpen[nxt.tid] == (unsigned int)nextPage || nextPage == -1)){
|
||||
if (myMeta && ++emptyCount < 42){
|
||||
//we're waiting for new data. Simply retry.
|
||||
buffer.insert(nxt);
|
||||
}else{
|
||||
//after ~10 seconds, give up and drop the track.
|
||||
DEBUG_MSG(DLVL_DEVEL, "Empty packet on track %u @ key %lu (next=%d) - could not reload, dropping track.", nxt.tid, nxtKeyNum[nxt.tid]+1, nextPage);
|
||||
}
|
||||
//keep updating the metadata at 250ms intervals while waiting for more data
|
||||
Util::sleep(250);
|
||||
updateMeta();
|
||||
}else{
|
||||
//if we're not live, we've simply reached the end of the page. Load the next key.
|
||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
|
||||
nxt.offset = 0;
|
||||
if (curPage.count(nxt.tid) && curPage[nxt.tid].mapped){
|
||||
unsigned long long nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset);
|
||||
if (nextTime && nextTime < nxt.time){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Time going backwards in track %u - dropping track.", nxt.tid);
|
||||
if (++emptyCount < 100){
|
||||
Util::wait(250);
|
||||
//we're waiting for new data to show up
|
||||
if (emptyCount % 8 == 0){
|
||||
reconnect();//reconnect every 2 seconds
|
||||
}else{
|
||||
if (nextTime){
|
||||
nxt.time = nextTime;
|
||||
if (emptyCount % 4 == 0){
|
||||
updateMeta();
|
||||
}
|
||||
buffer.insert(nxt);
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Next page for track %u starts at %llu.", nxt.tid, nxt.time);
|
||||
}
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_DEVEL, "Could not load next memory page for track %u - dropping track.", nxt.tid);
|
||||
//after ~25 seconds, give up and drop the track.
|
||||
dropTrack(nxt.tid, "could not reload empty packet");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
prepareNext();
|
||||
return;
|
||||
}
|
||||
thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true);
|
||||
if (thisPacket){
|
||||
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);
|
||||
}
|
||||
if ((myMeta.tracks[nxt.tid].type == "video" && thisPacket.getFlag("keyframe")) || (++nonVideoCount % 30 == 0)){
|
||||
if (myMeta.live){
|
||||
updateMeta();
|
||||
}
|
||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Track %u @ %llums = key %lu", nxt.tid, thisPacket.getTime(), nxtKeyNum[nxt.tid]);
|
||||
}
|
||||
emptyCount = 0;
|
||||
}
|
||||
nxt.offset += thisPacket.getDataLen();
|
||||
if (realTime){
|
||||
while (nxt.time > (((Util::getMS() - firstTime)*1000)+maxSkipAhead)/realTime) {
|
||||
Util::sleep(nxt.time - (((Util::getMS() - firstTime)*1000)+minSkipAhead)/realTime);
|
||||
}
|
||||
}
|
||||
//delay the stream until its current keyframe is complete
|
||||
if (completeKeysOnly){
|
||||
bool completeKeyReady = false;
|
||||
int timeoutTries = 40;//attempts to updateMeta before timeOut and moving on; default is approximately 10 seconds
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.keys.size() >1){
|
||||
int thisTimeoutTries = ((it->second.lastms - it->second.firstms) / (it->second.keys.size()-1)) / 125;
|
||||
if (thisTimeoutTries > timeoutTries) timeoutTries = thisTimeoutTries;
|
||||
}
|
||||
}
|
||||
while(!completeKeyReady && timeoutTries>0){
|
||||
completeKeyReady = true;
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
if (!myMeta.tracks[*it].keys.size() || myMeta.tracks[*it].keys.rbegin()->getTime() + myMeta.tracks[*it].keys.rbegin()->getLength() <= nxt.time ){
|
||||
completeKeyReady = false;
|
||||
break;
|
||||
|
||||
//We've simply reached the end of the page. Load the next key = next page.
|
||||
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
|
||||
nxt.offset = 0;
|
||||
if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){
|
||||
unsigned long long nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
|
||||
if (nextTime && nextTime < nxt.time){
|
||||
dropTrack(nxt.tid, "time going backwards");
|
||||
}else{
|
||||
if (nextTime){
|
||||
nxt.time = nextTime;
|
||||
}
|
||||
//swap out the next object in the buffer with a new one
|
||||
buffer.erase(buffer.begin());
|
||||
buffer.insert(nxt);
|
||||
MEDIUM_MSG("Next page for track %u starts at %llu.", nxt.tid, nxt.time);
|
||||
}
|
||||
if (!completeKeyReady){
|
||||
if (completeKeyReadyTimeOut){
|
||||
INSANE_MSG("Complete Key not ready and time-out is being skipped");
|
||||
timeoutTries = 0;
|
||||
}else{
|
||||
INSANE_MSG("Timeout: %d",timeoutTries);
|
||||
timeoutTries--;//we count down
|
||||
stats();
|
||||
Util::wait(250);
|
||||
updateMeta();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (timeoutTries<=0){
|
||||
if (!completeKeyReadyTimeOut){
|
||||
INFO_MSG("Wait for keyframe Timeout triggered! Ended to avoid endless loops");
|
||||
}
|
||||
completeKeyReadyTimeOut = true;
|
||||
}else{
|
||||
//untimeout handling
|
||||
completeKeyReadyTimeOut = false;
|
||||
dropTrack(nxt.tid, "page load failure");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (curPage[nxt.tid]){
|
||||
if (nxt.offset < curPage[nxt.tid].len){
|
||||
unsigned long long nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset);
|
||||
if (nextTime){
|
||||
nxt.time = nextTime;
|
||||
}else{
|
||||
++nxt.time;
|
||||
|
||||
//we've handled all special cases - at this point the packet should exist
|
||||
//let's load it
|
||||
thisPacket.reInit(nProxy.curPage[nxt.tid].mapped + nxt.offset, 0, true);
|
||||
//if it failed, drop the track and continue
|
||||
if (!thisPacket){
|
||||
dropTrack(nxt.tid, "packet load failure");
|
||||
return false;
|
||||
}
|
||||
emptyCount = 0;//valid packet - reset empty counter
|
||||
|
||||
//if there's a timestamp mismatch, print this.
|
||||
//except for live, where we never know the time in advance
|
||||
if (thisPacket.getTime() != nxt.time && nxt.time && !atLivePoint){
|
||||
static int warned = 0;
|
||||
if (warned < 5){
|
||||
WARN_MSG("Loaded %s track %ld@%llu in stead of %u@%llu (%dms, %s)", streamName.c_str(), thisPacket.getTrackId(), thisPacket.getTime(), nxt.tid, nxt.time, (int)((long long)thisPacket.getTime() - (long long)nxt.time), myMeta.tracks[nxt.tid].codec.c_str());
|
||||
if (++warned == 5){
|
||||
WARN_MSG("Further warnings about time mismatches printed on HIGH level.");
|
||||
}
|
||||
}else{
|
||||
HIGH_MSG("Loaded %s track %ld@%llu in stead of %u@%llu (%dms, %s)", streamName.c_str(), thisPacket.getTrackId(), thisPacket.getTime(), nxt.tid, nxt.time, (int)((long long)thisPacket.getTime() - (long long)nxt.time), myMeta.tracks[nxt.tid].codec.c_str());
|
||||
}
|
||||
buffer.insert(nxt);
|
||||
}
|
||||
stats();
|
||||
|
||||
//when live, every keyframe, check correctness of the keyframe number
|
||||
if (myMeta.live && thisPacket.getFlag("keyframe")){
|
||||
//Check whether returned keyframe is correct. If not, wait for approximately 10 seconds while checking.
|
||||
//Failure here will cause tracks to drop due to inconsistent internal state.
|
||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
int counter = 0;
|
||||
while(counter < 40 && myMeta.tracks[nxt.tid].getKey(nxtKeyNum[nxt.tid]).getTime() != thisPacket.getTime()){
|
||||
if (counter++){
|
||||
//Only sleep 250ms if this is not the first updatemeta try
|
||||
Util::wait(250);
|
||||
}
|
||||
updateMeta();
|
||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
}
|
||||
if (myMeta.tracks[nxt.tid].getKey(nxtKeyNum[nxt.tid]).getTime() != thisPacket.getTime()){
|
||||
WARN_MSG("Keyframe value is not correct - state will now be inconsistent.");
|
||||
}
|
||||
EXTREME_MSG("Track %u @ %llums = key %lu", nxt.tid, thisPacket.getTime(), nxtKeyNum[nxt.tid]);
|
||||
}
|
||||
|
||||
//always assume we're not at the live point
|
||||
atLivePoint = false;
|
||||
//we assume the next packet is the next on this same page
|
||||
nxt.offset += thisPacket.getDataLen();
|
||||
if (nxt.offset < nProxy.curPage[nxt.tid].len){
|
||||
unsigned long long nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
|
||||
if (nextTime){
|
||||
nxt.time = nextTime;
|
||||
}else{
|
||||
++nxt.time;
|
||||
//no packet -> we are at the live point
|
||||
atLivePoint = true;
|
||||
}
|
||||
}
|
||||
|
||||
//exchange the current packet in the buffer for the next one
|
||||
buffer.erase(buffer.begin());
|
||||
buffer.insert(nxt);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Output::stats(){
|
||||
static bool setHost = true;
|
||||
if (!isInitialized){
|
||||
return;
|
||||
/// Returns the name as it should be used in statistics.
|
||||
/// Outputs used as an input should return INPUT, outputs used for automation should return OUTPUT, others should return their proper name.
|
||||
/// The default implementation is usually good enough for all the non-INPUT types.
|
||||
std::string Output::getStatsName(){
|
||||
if (config->hasOption("target") && config->getString("target").size()){
|
||||
return "OUTPUT";
|
||||
}else{
|
||||
return capa["name"].asStringRef();
|
||||
}
|
||||
}
|
||||
|
||||
void Output::stats(bool force){
|
||||
//cancel stats update if not initialized
|
||||
if (!isInitialized){return;}
|
||||
//also cancel if it has been less than a second since the last update
|
||||
//unless force is set to true
|
||||
unsigned long long int now = Util::epoch();
|
||||
if (now == lastStats && !force){return;}
|
||||
lastStats = now;
|
||||
|
||||
EXTREME_MSG("Writing stats: %s, %s, %lu", getConnectedHost().c_str(), streamName.c_str(), crc & 0xFFFFFFFFu);
|
||||
if (statsPage.getData()){
|
||||
unsigned long long int now = Util::epoch();
|
||||
if (now != lastStats){
|
||||
lastStats = now;
|
||||
IPC::statExchange tmpEx(statsPage.getData());
|
||||
tmpEx.now(now);
|
||||
if (setHost){
|
||||
tmpEx.host(getConnectedBinHost());
|
||||
setHost = false;
|
||||
}
|
||||
tmpEx.crc(crc);
|
||||
tmpEx.streamName(streamName);
|
||||
tmpEx.connector(capa["name"].asString());
|
||||
tmpEx.up(myConn.dataUp());
|
||||
tmpEx.down(myConn.dataDown());
|
||||
tmpEx.time(now - myConn.connTime());
|
||||
if (thisPacket){
|
||||
tmpEx.lastSecond(thisPacket.getTime());
|
||||
}else{
|
||||
tmpEx.lastSecond(0);
|
||||
}
|
||||
statsPage.keepAlive();
|
||||
IPC::statExchange tmpEx(statsPage.getData());
|
||||
tmpEx.now(now);
|
||||
if (tmpEx.host() == std::string("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", 16)){
|
||||
tmpEx.host(getConnectedBinHost());
|
||||
}
|
||||
tmpEx.crc(crc);
|
||||
tmpEx.streamName(streamName);
|
||||
tmpEx.connector(getStatsName());
|
||||
tmpEx.up(myConn.dataUp());
|
||||
tmpEx.down(myConn.dataDown());
|
||||
tmpEx.time(now - myConn.connTime());
|
||||
if (thisPacket){
|
||||
tmpEx.lastSecond(thisPacket.getTime());
|
||||
}else{
|
||||
tmpEx.lastSecond(0);
|
||||
}
|
||||
statsPage.keepAlive();
|
||||
}
|
||||
int tNum = 0;
|
||||
if (!userClient.getData()){
|
||||
if (!nProxy.userClient.getData()){
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
if (!userClient.getData()){
|
||||
DEBUG_MSG(DLVL_WARN, "Player connection failure - aborting output");
|
||||
nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
if (!nProxy.userClient.getData()){
|
||||
WARN_MSG("Player connection failure - aborting output");
|
||||
myConn.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!trackMap.size()){
|
||||
if (!nProxy.userClient.isAlive()){
|
||||
myConn.close();
|
||||
return;
|
||||
}
|
||||
if (!nProxy.trackMap.size()){
|
||||
IPC::userConnection userConn(nProxy.userClient.getData());
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end() && tNum < SIMUL_TRACKS; it++){
|
||||
unsigned int tId = *it;
|
||||
char * thisData = userClient.getData() + (6 * tNum);
|
||||
thisData[0] = ((tId >> 24) & 0xFF);
|
||||
thisData[1] = ((tId >> 16) & 0xFF);
|
||||
thisData[2] = ((tId >> 8) & 0xFF);
|
||||
thisData[3] = ((tId) & 0xFF);
|
||||
thisData[4] = ((nxtKeyNum[tId] >> 8) & 0xFF);
|
||||
thisData[5] = ((nxtKeyNum[tId]) & 0xFF);
|
||||
userConn.setTrackId(tNum, *it);
|
||||
userConn.setKeynum(tNum, nxtKeyNum[*it]);
|
||||
tNum ++;
|
||||
}
|
||||
}
|
||||
userClient.keepAlive();
|
||||
nProxy.userClient.keepAlive();
|
||||
if (tNum > SIMUL_TRACKS){
|
||||
DEBUG_MSG(DLVL_WARN, "Too many tracks selected, using only first %d", SIMUL_TRACKS);
|
||||
WARN_MSG("Too many tracks selected, using only first %d", SIMUL_TRACKS);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -730,4 +893,24 @@ namespace Mist {
|
|||
//just set the sentHeader bool to true, by default
|
||||
sentHeader = true;
|
||||
}
|
||||
|
||||
bool Output::connectToFile(std::string file) {
|
||||
int flags = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
||||
int mode = O_RDWR | O_CREAT | O_TRUNC;
|
||||
int outFile = open(file.c_str(), mode, flags);
|
||||
if (outFile < 0) {
|
||||
ERROR_MSG("Failed to open file %s, error: %s", file.c_str(), strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
int r = dup2(outFile, myConn.getSocket());
|
||||
if (r == -1) {
|
||||
ERROR_MSG("Failed to create an alias for the socket using dup2: %s.", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
close(outFile);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -37,13 +37,12 @@ namespace Mist {
|
|||
public:
|
||||
//constructor and destructor
|
||||
Output(Socket::Connection & conn);
|
||||
virtual ~Output();
|
||||
//static members for initialization and capabilities
|
||||
static void init(Util::Config * cfg);
|
||||
static JSON::Value capa;
|
||||
//non-virtual generic functions
|
||||
int run();
|
||||
void stats();
|
||||
void stats(bool force = false);
|
||||
void seek(unsigned long long pos);
|
||||
bool seek(unsigned int tid, unsigned long long pos, bool getNextKey = false);
|
||||
void stop();
|
||||
|
@ -51,10 +50,13 @@ namespace Mist {
|
|||
long unsigned int getMainSelectedTrack();
|
||||
void updateMeta();
|
||||
void selectDefaultTracks();
|
||||
bool connectToFile(std::string file);
|
||||
static bool listenMode(){return true;}
|
||||
virtual bool isReadyForPlay();
|
||||
//virtuals. The optional virtuals have default implementations that do as little as possible.
|
||||
virtual void sendNext() {}//REQUIRED! Others are optional.
|
||||
virtual void prepareNext();
|
||||
bool prepareNext();
|
||||
virtual void dropTrack(uint32_t trackId, std::string reason, bool probablyBad = true);
|
||||
virtual void onRequest();
|
||||
virtual bool onFinish() {
|
||||
return false;
|
||||
|
@ -68,21 +70,21 @@ namespace Mist {
|
|||
std::map<unsigned long, unsigned int> currKeyOpen;
|
||||
void loadPageForKey(long unsigned int trackId, long long int keyNum);
|
||||
int pageNumForKey(long unsigned int trackId, long long int keyNum);
|
||||
int pageNumMax(long unsigned int trackId);
|
||||
unsigned int lastStats;///<Time of last sending of stats.
|
||||
long long unsigned int firstTime;///< Time of first packet after last seek. Used for real-time sending.
|
||||
std::map<unsigned long, unsigned long> nxtKeyNum;///< Contains the number of the next key, for page seeking purposes.
|
||||
std::set<sortedPageInfo> buffer;///< A sorted list of next-to-be-loaded packets.
|
||||
bool sought;///<If a seek has been done, this is set to true. Used for seeking on prepareNext().
|
||||
bool completeKeyReadyTimeOut;//a bool to see if there has been a keyframe TimeOut for complete keys in Live
|
||||
protected://these are to be messed with by child classes
|
||||
|
||||
virtual std::string getConnectedHost();
|
||||
virtual std::string getConnectedBinHost();
|
||||
|
||||
virtual std::string getStatsName();
|
||||
virtual bool hasSessionIDs(){return false;}
|
||||
|
||||
IPC::sharedClient statsPage;///< Shared memory used for statistics reporting.
|
||||
bool isBlocking;///< If true, indicates that myConn is blocking.
|
||||
unsigned int crc;///< Checksum, if any, for usage in the stats.
|
||||
uint32_t crc;///< Checksum, if any, for usage in the stats.
|
||||
unsigned int getKeyForTime(long unsigned int trackId, long long timeStamp);
|
||||
|
||||
//stream delaying variables
|
||||
|
@ -103,3 +105,4 @@ namespace Mist {
|
|||
};
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -159,7 +159,7 @@ namespace Mist {
|
|||
capa["codecs"][0u][1u].append("G711mu");
|
||||
capa["methods"][0u]["handler"] = "http";
|
||||
capa["methods"][0u]["type"] = "flash/11";
|
||||
capa["methods"][0u]["priority"] = 7ll;
|
||||
capa["methods"][0u]["priority"] = 6ll;
|
||||
capa["methods"][0u]["player_url"] = "/flashplayer.swf";
|
||||
}
|
||||
|
||||
|
@ -229,7 +229,7 @@ namespace Mist {
|
|||
myConn.close();
|
||||
break;
|
||||
}
|
||||
Util::sleep(500);
|
||||
Util::wait(500);
|
||||
updateMeta();
|
||||
}
|
||||
mstime = myMeta.tracks[tid].getKey(myMeta.tracks[tid].fragments[fragNum - myMeta.tracks[tid].missedFrags].getNumber()).getTime();
|
||||
|
|
|
@ -3,17 +3,26 @@
|
|||
#include <unistd.h>
|
||||
|
||||
namespace Mist {
|
||||
bool OutHLS::isReadyForPlay() {
|
||||
if (myMeta.tracks.size()){
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.fragments.size() >= 3){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
///\brief Builds an index file for HTTP Live streaming.
|
||||
///\return The index file for HTTP Live Streaming.
|
||||
std::string OutHLS::liveIndex(){
|
||||
std::stringstream result;
|
||||
result << "#EXTM3U\r\n";
|
||||
int audioId = -1;
|
||||
std::string audioName;
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.codec == "AAC"){
|
||||
audioId = it->first;
|
||||
audioName = it->second.getIdentifier();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +42,7 @@ namespace Mist {
|
|||
if (audioId != -1){
|
||||
result << "_" << audioId;
|
||||
}
|
||||
result << "/index.m3u8\r\n";
|
||||
result << "/index.m3u8?sessId=" << getpid() << "\r\n";
|
||||
}
|
||||
}
|
||||
if (!vidTracks && audioId){
|
||||
|
@ -44,13 +53,13 @@ namespace Mist {
|
|||
return result.str();
|
||||
}
|
||||
|
||||
std::string OutHLS::liveIndex(int tid){
|
||||
std::string OutHLS::liveIndex(int tid, std::string & sessId) {
|
||||
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);
|
||||
INFO_MSG("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++){
|
||||
|
@ -66,15 +75,18 @@ namespace Mist {
|
|||
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();
|
||||
std::stringstream line;
|
||||
long long duration = it->getDuration();
|
||||
if (duration <= 0){
|
||||
duration = myMeta.tracks[tid].lastms - starttime;
|
||||
}
|
||||
line << "#EXTINF:" << ((duration + 500) / 1000) << ", no desc\r\n" << starttime << "_" << duration + starttime << ".ts\r\n";
|
||||
lines.push_back(line.str());
|
||||
char lineBuf[400];
|
||||
if (sessId.size()){
|
||||
snprintf(lineBuf, 400, "#EXTINF:%lld, no desc\r\n%lld_%lld.ts?sessId=%s\r\n", ((duration + 500) / 1000), starttime, starttime + duration, sessId.c_str());
|
||||
}else{
|
||||
snprintf(lineBuf, 400, "#EXTINF:%lld, no desc\r\n%lld_%lld.ts\r\n", ((duration + 500) / 1000), starttime, starttime + duration);
|
||||
}
|
||||
lines.push_back(lineBuf);
|
||||
}
|
||||
|
||||
unsigned int skippedLines = 0;
|
||||
if (myMeta.live){
|
||||
//only print the last segment when VoD
|
||||
|
@ -135,7 +147,7 @@ namespace Mist {
|
|||
|
||||
void OutHLS::onHTTP(){
|
||||
std::string method = H.method;
|
||||
|
||||
std::string sessId = H.GetVar("sessId");
|
||||
|
||||
if (H.url == "/crossdomain.xml"){
|
||||
H.Clean();
|
||||
|
@ -152,12 +164,24 @@ namespace Mist {
|
|||
H.Clean(); //clean for any possible next requests
|
||||
return;
|
||||
} //crossdomain.xml
|
||||
|
||||
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();
|
||||
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);
|
||||
bool VLCworkaround = false;
|
||||
if (H.GetHeader("User-Agent").substr(0, 3) == "VLC"){
|
||||
|
@ -167,6 +191,7 @@ namespace Mist {
|
|||
VLCworkaround = true;
|
||||
}
|
||||
}
|
||||
|
||||
initialize();
|
||||
if (H.url.find(".m3u") == std::string::npos){
|
||||
std::string tmpStr = H.getUrl().substr(5+streamName.size());
|
||||
|
@ -189,6 +214,11 @@ namespace Mist {
|
|||
selectedTracks.insert(vidTrack);
|
||||
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){
|
||||
unsigned int timeout = 0;
|
||||
|
@ -202,7 +232,7 @@ namespace Mist {
|
|||
myConn.close();
|
||||
break;
|
||||
}
|
||||
Util::sleep(500);
|
||||
Util::wait(500);
|
||||
updateMeta();
|
||||
}
|
||||
}while (myConn && seekable > 0);
|
||||
|
@ -266,7 +296,7 @@ namespace Mist {
|
|||
manifest = liveIndex();
|
||||
}else{
|
||||
int selectId = atoi(request.substr(0,request.find("/")).c_str());
|
||||
manifest = liveIndex(selectId);
|
||||
manifest = liveIndex(selectId, sessId);
|
||||
}
|
||||
H.SetBody(manifest);
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
|
|
|
@ -9,9 +9,11 @@ namespace Mist {
|
|||
static void init(Util::Config * cfg);
|
||||
void sendTS(const char * tsData, unsigned int len=188);
|
||||
void onHTTP();
|
||||
bool isReadyForPlay();
|
||||
protected:
|
||||
bool hasSessionIDs(){return true;}
|
||||
std::string liveIndex();
|
||||
std::string liveIndex(int tid);
|
||||
std::string liveIndex(int tid, std::string & sessId);
|
||||
int canSeekms(unsigned int ms);
|
||||
int keysToSend;
|
||||
unsigned int vidTrack;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <mist/mp4_generic.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/bitfields.h>
|
||||
#include <mist/checksum.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
@ -56,11 +57,9 @@ namespace Mist {
|
|||
capa["methods"][0u]["handler"] = "http";
|
||||
capa["methods"][0u]["type"] = "html5/application/vnd.ms-ss";
|
||||
capa["methods"][0u]["priority"] = 9ll;
|
||||
capa["methods"][0u]["nolive"] = 1;
|
||||
capa["methods"][1u]["handler"] = "http";
|
||||
capa["methods"][1u]["type"] = "silverlight";
|
||||
capa["methods"][1u]["priority"] = 1ll;
|
||||
capa["methods"][1u]["nolive"] = 1;
|
||||
}
|
||||
|
||||
void OutHSS::sendNext() {
|
||||
|
@ -132,7 +131,7 @@ namespace Mist {
|
|||
myConn.close();
|
||||
break;
|
||||
}
|
||||
Util::sleep(500);
|
||||
Util::wait(500);
|
||||
updateMeta();
|
||||
}
|
||||
}while (myConn && seekable > 0);
|
||||
|
@ -201,11 +200,11 @@ namespace Mist {
|
|||
|
||||
//Wrap everything in mp4 boxes
|
||||
MP4::MFHD mfhd_box;
|
||||
mfhd_box.setSequenceNumber(((keyObj.getNumber() - 1) * 2) + tid);///\todo Urgent: Check this for multitrack... :P wtf... :P
|
||||
mfhd_box.setSequenceNumber(((keyObj.getNumber() - 1) * 2) + (myMeta.tracks[tid].type == "video" ? 1 : 2));
|
||||
|
||||
MP4::TFHD tfhd_box;
|
||||
tfhd_box.setFlags(MP4::tfhdSampleFlag);
|
||||
tfhd_box.setTrackID(tid);
|
||||
tfhd_box.setTrackID((myMeta.tracks[tid].type == "video" ? 1 : 2));
|
||||
if (myMeta.tracks[tid].type == "video") {
|
||||
tfhd_box.setDefaultSampleFlags(0x00004001);
|
||||
} else {
|
||||
|
@ -254,19 +253,24 @@ namespace Mist {
|
|||
//If the stream is live, we want to have a fragref box if possible
|
||||
//////HEREHEREHERE
|
||||
if (myMeta.live) {
|
||||
MP4::UUID_TFXD tfxd_box;
|
||||
tfxd_box.setTime(keyObj.getTime());
|
||||
tfxd_box.setDuration(keyObj.getLength());
|
||||
traf_box.setContent(tfxd_box, 3);
|
||||
|
||||
MP4::UUID_TrackFragmentReference fragref_box;
|
||||
fragref_box.setVersion(1);
|
||||
fragref_box.setFragmentCount(0);
|
||||
int fragCount = 0;
|
||||
for (unsigned int i = 0; fragCount < 2 && i < myMeta.tracks[tid].keys.size() - 1; i++) {
|
||||
if (myMeta.tracks[tid].keys[i].getTime() > seekTime) {
|
||||
DEBUG_MSG(DLVL_HIGH, "Key %d added to fragRef box, time %ld > %lld", i, myMeta.tracks[tid].keys[i].getTime(), seekTime);
|
||||
DEBUG_MSG(DLVL_HIGH, "Key %d added to fragRef box, time %llu > %lld", i, myMeta.tracks[tid].keys[i].getTime(), seekTime);
|
||||
fragref_box.setTime(fragCount, myMeta.tracks[tid].keys[i].getTime() * 10000);
|
||||
fragref_box.setDuration(fragCount, myMeta.tracks[tid].keys[i].getLength() * 10000);
|
||||
fragref_box.setFragmentCount(++fragCount);
|
||||
}
|
||||
}
|
||||
traf_box.setContent(fragref_box, 3);
|
||||
traf_box.setContent(fragref_box, 4);
|
||||
}
|
||||
|
||||
MP4::MOOF moof_box;
|
||||
|
@ -467,9 +471,4 @@ namespace Mist {
|
|||
sendHeader();
|
||||
}
|
||||
}
|
||||
|
||||
void OutHSS::initialize() {
|
||||
Output::initialize();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,10 +9,8 @@ namespace Mist {
|
|||
static void init(Util::Config * cfg);
|
||||
void onHTTP();
|
||||
void sendNext();
|
||||
void initialize();/*LTS*/
|
||||
void sendHeader();
|
||||
protected:
|
||||
JSON::Value encryption;
|
||||
std::string smoothIndex();
|
||||
int canSeekms(unsigned int ms);
|
||||
int keysToSend;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "output_http.h"
|
||||
#include <mist/stream.h>
|
||||
#include <mist/checksum.h>
|
||||
#include <set>
|
||||
|
||||
namespace Mist {
|
||||
HTTPOutput::HTTPOutput(Socket::Connection & conn) : Output(conn) {
|
||||
|
@ -104,9 +105,9 @@ namespace Mist {
|
|||
}
|
||||
|
||||
//loop over the connectors
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE);
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE);
|
||||
DTSC::Scan capa = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("capabilities").getMember("connectors");
|
||||
unsigned int capa_ctr = capa.getSize();
|
||||
for (unsigned int i = 0; i < capa_ctr; ++i){
|
||||
|
@ -171,7 +172,7 @@ namespace Mist {
|
|||
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());
|
||||
streamName = H.GetVar("stream");
|
||||
userClient.finish();
|
||||
nProxy.userClient.finish();
|
||||
statsPage.finish();
|
||||
reConnector(handler);
|
||||
H.Clean();
|
||||
|
@ -209,8 +210,19 @@ namespace Mist {
|
|||
|
||||
void HTTPOutput::onRequest(){
|
||||
while (H.Read(myConn)){
|
||||
std::string ua = H.GetHeader("User-Agent");
|
||||
crc = checksum::crc32(0, ua.data(), ua.size());
|
||||
if (hasSessionIDs()){
|
||||
if (H.GetVar("sessId").size()){
|
||||
std::string ua = H.GetVar("sessId");
|
||||
crc = checksum::crc32(0, ua.data(), ua.size());
|
||||
}else{
|
||||
std::string ua = JSON::Value((long long)getpid()).asString();
|
||||
crc = checksum::crc32(0, ua.data(), ua.size());
|
||||
}
|
||||
}else{
|
||||
std::string ua = H.GetHeader("User-Agent") + H.GetHeader("X-Playback-Session-Id");
|
||||
crc = checksum::crc32(0, ua.data(), ua.size());
|
||||
}
|
||||
|
||||
INFO_MSG("Received request %s", H.getUrl().c_str());
|
||||
selectedTracks.clear();
|
||||
if (H.GetVar("audio") != ""){
|
||||
|
@ -239,6 +251,7 @@ namespace Mist {
|
|||
for (std::set<unsigned long>::iterator it = toRemove.begin(); it != toRemove.end(); it++){
|
||||
selectedTracks.erase(*it);
|
||||
}
|
||||
|
||||
onHTTP();
|
||||
if (!H.bufferChunks){
|
||||
H.Clean();
|
||||
|
@ -275,9 +288,9 @@ namespace Mist {
|
|||
for (int i=0; i<20; i++){argarr[i] = 0;}
|
||||
int id = -1;
|
||||
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE);
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE);
|
||||
DTSC::Scan prots = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("config").getMember("protocols");
|
||||
unsigned int prots_ctr = prots.getSize();
|
||||
|
||||
|
|
|
@ -248,9 +248,9 @@ namespace Mist {
|
|||
|
||||
std::string port, url_rel;
|
||||
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE);
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE);
|
||||
DTSC::Scan prtcls = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("config").getMember("protocols");
|
||||
DTSC::Scan capa = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("capabilities").getMember("connectors").getMember("RTMP");
|
||||
unsigned int pro_cnt = prtcls.getSize();
|
||||
|
@ -320,11 +320,13 @@ namespace Mist {
|
|||
}
|
||||
response = "// Generating info code for stream " + streamName + "\n\nif (!mistvideo){var mistvideo = {};}\n";
|
||||
JSON::Value json_resp;
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::semaphore metaLocker(std::string("liveMeta@" + streamName).c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
static char liveSemName[NAME_BUFFER_SIZE];
|
||||
snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
|
||||
IPC::semaphore metaLocker(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
bool metaLock = false;
|
||||
configLock.wait();
|
||||
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE);
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE);
|
||||
DTSC::Scan strm = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(streamName).getMember("meta");
|
||||
IPC::sharedPage streamIndex;
|
||||
if (!strm){
|
||||
|
@ -333,7 +335,7 @@ namespace Mist {
|
|||
if (Util::startInput(streamName)){
|
||||
char pageId[NAME_BUFFER_SIZE];
|
||||
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||
streamIndex.init(pageId, DEFAULT_META_PAGE_SIZE);
|
||||
streamIndex.init(pageId, DEFAULT_STRM_PAGE_SIZE);
|
||||
if (streamIndex.mapped){
|
||||
metaLock = true;
|
||||
metaLocker.wait();
|
||||
|
|
|
@ -25,13 +25,19 @@ namespace Mist {
|
|||
}
|
||||
|
||||
void OutHTTPTS::onHTTP(){
|
||||
std::string method = H.method;
|
||||
|
||||
initialize();
|
||||
H.Clean();
|
||||
H.SetHeader("Content-Type", "video/mp2t");
|
||||
H.setCORSHeaders();
|
||||
if(method == "OPTIONS" || method == "HEAD"){
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
H.Clean();
|
||||
return;
|
||||
}
|
||||
H.StartResponse(H, myConn);
|
||||
parseData = true;
|
||||
wantRequest = false;
|
||||
H.Clean(); //clean for any possible next requests
|
||||
}
|
||||
|
||||
void OutHTTPTS::sendTS(const char * tsData, unsigned int len){
|
||||
|
|
|
@ -9,13 +9,6 @@ namespace Mist {
|
|||
static void init(Util::Config * cfg);
|
||||
void onHTTP();
|
||||
void sendTS(const char * tsData, unsigned int len=188);
|
||||
protected:
|
||||
int keysToSend;
|
||||
long long int playUntil;
|
||||
long long unsigned int lastVid;
|
||||
long long unsigned int until;
|
||||
unsigned int vidTrack;
|
||||
unsigned int audTrack;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Mist {
|
||||
OutProgressiveFLV::OutProgressiveFLV(Socket::Connection & conn) : HTTPOutput(conn){}
|
||||
OutProgressiveFLV::~OutProgressiveFLV() {}
|
||||
|
||||
void OutProgressiveFLV::init(Util::Config * cfg){
|
||||
HTTPOutput::init(cfg);
|
||||
|
|
|
@ -5,7 +5,6 @@ namespace Mist {
|
|||
class OutProgressiveFLV : public HTTPOutput {
|
||||
public:
|
||||
OutProgressiveFLV(Socket::Connection & conn);
|
||||
~OutProgressiveFLV();
|
||||
static void init(Util::Config * cfg);
|
||||
void onHTTP();
|
||||
void sendNext();
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Mist {
|
||||
OutProgressiveMP3::OutProgressiveMP3(Socket::Connection & conn) : HTTPOutput(conn){}
|
||||
OutProgressiveMP3::~OutProgressiveMP3(){}
|
||||
|
||||
void OutProgressiveMP3::init(Util::Config * cfg){
|
||||
HTTPOutput::init(cfg);
|
||||
|
|
|
@ -5,7 +5,6 @@ namespace Mist {
|
|||
class OutProgressiveMP3 : public HTTPOutput {
|
||||
public:
|
||||
OutProgressiveMP3(Socket::Connection & conn);
|
||||
~OutProgressiveMP3();
|
||||
static void init(Util::Config * cfg);
|
||||
void onHTTP();
|
||||
void sendNext();
|
||||
|
|
|
@ -4,11 +4,11 @@ namespace Mist {
|
|||
OutRaw::OutRaw(Socket::Connection & conn) : Output(conn) {
|
||||
streamName = config->getString("streamname");
|
||||
initialize();
|
||||
selectedTracks.clear();
|
||||
std::string tracks = config->getString("tracks");
|
||||
if (tracks.size()){
|
||||
selectedTracks.clear();
|
||||
unsigned int currTrack = 0;
|
||||
//loop over tracks, add any found track IDs to selectedTracks
|
||||
if (tracks != ""){
|
||||
for (unsigned int i = 0; i < tracks.size(); ++i){
|
||||
if (tracks[i] >= '0' && tracks[i] <= '9'){
|
||||
currTrack = currTrack*10 + (tracks[i] - '0');
|
||||
|
@ -46,8 +46,7 @@ namespace Mist {
|
|||
capa["optional"]["seek"]["help"] = "The time in milliseconds to seek to, 0 by default.";
|
||||
capa["optional"]["seek"]["type"] = "int";
|
||||
capa["optional"]["seek"]["option"] = "--seek";
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][0u].append("*");
|
||||
cfg->addOption("streamname",
|
||||
JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}"));
|
||||
cfg->addOption("tracks",
|
||||
|
@ -68,3 +67,4 @@ namespace Mist {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
namespace Mist {
|
||||
OutRTMP::OutRTMP(Socket::Connection & conn) : Output(conn) {
|
||||
setBlocking(true);
|
||||
while (!conn.Received().available(1537) && conn.connected()) {
|
||||
while (!conn.Received().available(1537) && conn.connected() && config->is_active) {
|
||||
conn.spool();
|
||||
}
|
||||
if (!conn){
|
||||
if (!conn || !config->is_active){
|
||||
return;
|
||||
}
|
||||
RTMPStream::handshake_in.append(conn.Received().remove(1537));
|
||||
|
@ -21,21 +21,41 @@ namespace Mist {
|
|||
|
||||
if (RTMPStream::doHandshake()) {
|
||||
conn.SendNow(RTMPStream::handshake_out);
|
||||
while (!conn.Received().available(1536) && conn.connected()) {
|
||||
while (!conn.Received().available(1536) && conn.connected() && config->is_active) {
|
||||
conn.spool();
|
||||
}
|
||||
conn.Received().remove(1536);
|
||||
RTMPStream::rec_cnt += 1536;
|
||||
DEBUG_MSG(DLVL_HIGH, "Handshake success!");
|
||||
HIGH_MSG("Handshake success");
|
||||
} else {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Handshake fail!");
|
||||
MEDIUM_MSG("Handshake fail (this is not a problem, usually)");
|
||||
}
|
||||
setBlocking(false);
|
||||
maxSkipAhead = 1500;
|
||||
minSkipAhead = 500;
|
||||
}
|
||||
|
||||
OutRTMP::~OutRTMP() {}
|
||||
bool OutRTMP::isReadyForPlay(){
|
||||
if (isPushing){
|
||||
return true;
|
||||
}
|
||||
if (myMeta.tracks.size()){
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.keys.size() >= 2){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string OutRTMP::getStatsName(){
|
||||
if (isPushing){
|
||||
return "INPUT";
|
||||
}else{
|
||||
return Output::getStatsName();
|
||||
}
|
||||
}
|
||||
|
||||
void OutRTMP::parseVars(std::string data){
|
||||
std::string varname;
|
||||
|
@ -94,7 +114,7 @@ namespace Mist {
|
|||
void OutRTMP::init(Util::Config * cfg) {
|
||||
Output::init(cfg);
|
||||
capa["name"] = "RTMP";
|
||||
capa["desc"] = "Enables the RTMP protocol which is used by Adobe Flash Player.";
|
||||
capa["desc"] = "Enables ingest and output over Adobe's RTMP protocol.";
|
||||
capa["deps"] = "";
|
||||
capa["url_rel"] = "/play/$";
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
|
@ -114,13 +134,28 @@ namespace Mist {
|
|||
capa["codecs"][0u][1u].append("G711mu");
|
||||
capa["methods"][0u]["handler"] = "rtmp";
|
||||
capa["methods"][0u]["type"] = "flash/10";
|
||||
capa["methods"][0u]["priority"] = 6ll;
|
||||
capa["methods"][0u]["priority"] = 7ll;
|
||||
capa["methods"][0u]["player_url"] = "/flashplayer.swf";
|
||||
cfg->addConnectorOptions(1935, capa);
|
||||
config = cfg;
|
||||
}
|
||||
|
||||
void OutRTMP::sendNext() {
|
||||
|
||||
//If there are now more selectable tracks, select the new track and do a seek to the current timestamp
|
||||
//Set sentHeader to false to force it to send init data
|
||||
if (selectedTracks.size() < 2 && myMeta.tracks.size() > 1){
|
||||
size_t prevTrackCount = selectedTracks.size();
|
||||
selectDefaultTracks();
|
||||
if (selectedTracks.size() > prevTrackCount){
|
||||
INFO_MSG("Picked up new track - selecting it and resetting state.");
|
||||
sentHeader = false;
|
||||
seek(thisPacket.getTime());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
char rtmpheader[] = {0, //byte 0 = cs_id | ch_type
|
||||
0, 0, 0, //bytes 1-3 = timestamp
|
||||
0, 0, 0, //bytes 4-6 = length
|
||||
|
@ -312,9 +347,7 @@ namespace Mist {
|
|||
///\param messageType The type of message.
|
||||
///\param streamId The ID of the AMF stream.
|
||||
void OutRTMP::sendCommand(AMF::Object & amfReply, int messageType, int streamId) {
|
||||
#if DEBUG >= 8
|
||||
std::cerr << amfReply.Print() << std::endl;
|
||||
#endif
|
||||
HIGH_MSG("Sending: %s", amfReply.Print().c_str());
|
||||
if (messageType == 17) {
|
||||
myConn.SendNow(RTMPStream::SendChunk(3, messageType, streamId, (char)0 + amfReply.Pack()));
|
||||
} else {
|
||||
|
@ -327,38 +360,13 @@ namespace Mist {
|
|||
///\param messageType The type of message.
|
||||
///\param streamId The ID of the AMF stream.
|
||||
void OutRTMP::parseAMFCommand(AMF::Object & amfData, int messageType, int streamId) {
|
||||
#if DEBUG >= 5
|
||||
fprintf(stderr, "Received command: %s\n", amfData.Print().c_str());
|
||||
#endif
|
||||
#if DEBUG >= 8
|
||||
fprintf(stderr, "AMF0 command: %s\n", amfData.getContentP(0)->StrValue().c_str());
|
||||
#endif
|
||||
MEDIUM_MSG("Received command: %s", amfData.Print().c_str());
|
||||
HIGH_MSG("AMF0 command: %s", amfData.getContentP(0)->StrValue().c_str());
|
||||
if (amfData.getContentP(0)->StrValue() == "connect") {
|
||||
double objencoding = 0;
|
||||
if (amfData.getContentP(2)->getContentP("objectEncoding")) {
|
||||
objencoding = amfData.getContentP(2)->getContentP("objectEncoding")->NumValue();
|
||||
}
|
||||
#if DEBUG >= 6
|
||||
int tmpint;
|
||||
if (amfData.getContentP(2)->getContentP("videoCodecs")) {
|
||||
tmpint = (int)amfData.getContentP(2)->getContentP("videoCodecs")->NumValue();
|
||||
if (tmpint & 0x04) {
|
||||
fprintf(stderr, "Sorensen video support detected\n");
|
||||
}
|
||||
if (tmpint & 0x80) {
|
||||
fprintf(stderr, "H264 video support detected\n");
|
||||
}
|
||||
}
|
||||
if (amfData.getContentP(2)->getContentP("audioCodecs")) {
|
||||
tmpint = (int)amfData.getContentP(2)->getContentP("audioCodecs")->NumValue();
|
||||
if (tmpint & 0x04) {
|
||||
fprintf(stderr, "MP3 audio support detected\n");
|
||||
}
|
||||
if (tmpint & 0x400) {
|
||||
fprintf(stderr, "AAC audio support detected\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
app_name = amfData.getContentP(2)->getContentP("tcUrl")->StrValue();
|
||||
app_name = app_name.substr(app_name.find('/', 7) + 1);
|
||||
RTMPStream::chunk_snd_max = 4096;
|
||||
|
@ -467,7 +475,7 @@ namespace Mist {
|
|||
} //getStreamLength
|
||||
if ((amfData.getContentP(0)->StrValue() == "publish")) {
|
||||
if (amfData.getContentP(3)) {
|
||||
streamName = amfData.getContentP(3)->StrValue();
|
||||
streamName = Encodings::URL::decode(amfData.getContentP(3)->StrValue());
|
||||
|
||||
if (streamName.find('/')){
|
||||
streamName = streamName.substr(0, streamName.find('/'));
|
||||
|
@ -485,32 +493,33 @@ namespace Mist {
|
|||
|
||||
Util::sanitizeName(streamName);
|
||||
//pull the server configuration
|
||||
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE); ///< Contains server configuration and capabilities
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE); ///< Contains server configuration and capabilities
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
|
||||
DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(streamName);
|
||||
if (streamCfg){
|
||||
if (streamCfg.getMember("source").asString().substr(0, 7) != "push://"){
|
||||
DEBUG_MSG(DLVL_FAIL, "Push rejected - stream %s not a push-able stream. (%s != push://*)", streamName.c_str(), streamCfg.getMember("source").asString().c_str());
|
||||
FAIL_MSG("Push rejected - stream %s not a push-able stream. (%s != push://*)", streamName.c_str(), streamCfg.getMember("source").asString().c_str());
|
||||
myConn.close();
|
||||
}else{
|
||||
std::string source = streamCfg.getMember("source").asString().substr(7);
|
||||
std::string IP = source.substr(0, source.find('@'));
|
||||
if (IP != ""){
|
||||
if (!myConn.isAddress(IP)){
|
||||
DEBUG_MSG(DLVL_FAIL, "Push from %s to %s rejected - source host not whitelisted", getConnectedHost().c_str(), streamName.c_str());
|
||||
FAIL_MSG("Push from %s to %s rejected - source host not whitelisted", getConnectedHost().c_str(), streamName.c_str());
|
||||
myConn.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_FAIL, "Push from %s rejected - stream '%s' not configured.", getConnectedHost().c_str(), streamName.c_str());
|
||||
FAIL_MSG("Push from %s rejected - stream '%s' not configured.", getConnectedHost().c_str(), streamName.c_str());
|
||||
myConn.close();
|
||||
}
|
||||
configLock.post();
|
||||
configLock.close();
|
||||
if (!myConn){return;}//do not initialize if rejected
|
||||
isPushing = true;
|
||||
initialize();
|
||||
}
|
||||
//send a _result reply
|
||||
|
@ -698,16 +707,22 @@ namespace Mist {
|
|||
}
|
||||
return;
|
||||
} //seek
|
||||
if (amfData.getContentP(0)->StrValue() == "_error") {
|
||||
WARN_MSG("Received error response: %s", amfData.Print().c_str());
|
||||
return;
|
||||
}
|
||||
if ((amfData.getContentP(0)->StrValue() == "_result") || (amfData.getContentP(0)->StrValue() == "onFCPublish") || (amfData.getContentP(0)->StrValue() == "onStatus")) {
|
||||
//Results are ignored. We don't really care.
|
||||
return;
|
||||
}
|
||||
|
||||
#if DEBUG >= 2
|
||||
fprintf(stderr, "AMF0 command not processed!\n%s\n", amfData.Print().c_str());
|
||||
#endif
|
||||
WARN_MSG("AMF0 command not processed: %s", amfData.Print().c_str());
|
||||
//send a _result reply
|
||||
AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER);
|
||||
amfReply.addContent(AMF::Object("", "_error")); //result success
|
||||
amfReply.addContent(amfData.getContent(1)); //same transaction ID
|
||||
amfReply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||
amfReply.addContent(AMF::Object("Command not implemented or recognized", "")); //stream ID?
|
||||
amfReply.addContent(AMF::Object("", amfData.getContentP(0)->StrValue())); //null - command info
|
||||
amfReply.addContent(AMF::Object("", "Command not implemented or recognized")); //stream ID?
|
||||
sendCommand(amfReply, messageType, streamId);
|
||||
} //parseAMFCommand
|
||||
|
||||
|
@ -734,9 +749,7 @@ namespace Mist {
|
|||
|
||||
switch (next.msg_type_id) {
|
||||
case 0: //does not exist
|
||||
#if DEBUG >= 2
|
||||
fprintf(stderr, "UNKN: Received a zero-type message. Possible data corruption? Aborting!\n");
|
||||
#endif
|
||||
WARN_MSG("UNKN: Received a zero-type message. Possible data corruption? Aborting!");
|
||||
while (inputBuffer.size()) {
|
||||
inputBuffer.get().clear();
|
||||
}
|
||||
|
@ -745,20 +758,14 @@ namespace Mist {
|
|||
break; //happens when connection breaks unexpectedly
|
||||
case 1: //set chunk size
|
||||
RTMPStream::chunk_rec_max = ntohl(*(int *)next.data.c_str());
|
||||
#if DEBUG >= 5
|
||||
fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max);
|
||||
#endif
|
||||
MEDIUM_MSG("CTRL: Set chunk size: %i", RTMPStream::chunk_rec_max);
|
||||
break;
|
||||
case 2: //abort message - we ignore this one
|
||||
#if DEBUG >= 5
|
||||
fprintf(stderr, "CTRL: Abort message\n");
|
||||
#endif
|
||||
MEDIUM_MSG("CTRL: Abort message");
|
||||
//4 bytes of stream id to drop
|
||||
break;
|
||||
case 3: //ack
|
||||
#if DEBUG >= 8
|
||||
fprintf(stderr, "CTRL: Acknowledgement\n");
|
||||
#endif
|
||||
VERYHIGH_MSG("CTRL: Acknowledgement");
|
||||
RTMPStream::snd_window_at = ntohl(*(int *)next.data.c_str());
|
||||
RTMPStream::snd_window_at = RTMPStream::snd_cnt;
|
||||
break;
|
||||
|
@ -773,49 +780,43 @@ namespace Mist {
|
|||
//6 = pingrequest, 4 bytes data
|
||||
//7 = pingresponse, 4 bytes data
|
||||
//we don't need to process this
|
||||
#if DEBUG >= 5
|
||||
short int ucmtype = ntohs(*(short int *)next.data.c_str());
|
||||
switch (ucmtype) {
|
||||
case 0:
|
||||
fprintf(stderr, "CTRL: UCM StreamBegin %i\n", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
MEDIUM_MSG("CTRL: UCM StreamBegin %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
break;
|
||||
case 1:
|
||||
fprintf(stderr, "CTRL: UCM StreamEOF %i\n", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
MEDIUM_MSG("CTRL: UCM StreamEOF %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
break;
|
||||
case 2:
|
||||
fprintf(stderr, "CTRL: UCM StreamDry %i\n", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
MEDIUM_MSG("CTRL: UCM StreamDry %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
break;
|
||||
case 3:
|
||||
fprintf(stderr, "CTRL: UCM SetBufferLength %i %i\n", ntohl(*((int *)(next.data.c_str() + 2))), ntohl(*((int *)(next.data.c_str() + 6))));
|
||||
MEDIUM_MSG("CTRL: UCM SetBufferLength %i %i", ntohl(*((int *)(next.data.c_str() + 2))), ntohl(*((int *)(next.data.c_str() + 6))));
|
||||
break;
|
||||
case 4:
|
||||
fprintf(stderr, "CTRL: UCM StreamIsRecorded %i\n", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
MEDIUM_MSG("CTRL: UCM StreamIsRecorded %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
break;
|
||||
case 6:
|
||||
fprintf(stderr, "CTRL: UCM PingRequest %i\n", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
MEDIUM_MSG("CTRL: UCM PingRequest %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
break;
|
||||
case 7:
|
||||
fprintf(stderr, "CTRL: UCM PingResponse %i\n", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
MEDIUM_MSG("CTRL: UCM PingResponse %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "CTRL: UCM Unknown (%hi)\n", ucmtype);
|
||||
MEDIUM_MSG("CTRL: UCM Unknown (%hi)", ucmtype);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case 5: //window size of other end
|
||||
#if DEBUG >= 5
|
||||
fprintf(stderr, "CTRL: Window size\n");
|
||||
#endif
|
||||
MEDIUM_MSG("CTRL: Window size");
|
||||
RTMPStream::rec_window_size = ntohl(*(int *)next.data.c_str());
|
||||
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
|
||||
myConn.SendNow(RTMPStream::SendCTL(3, RTMPStream::rec_cnt)); //send ack (msg 3)
|
||||
break;
|
||||
case 6:
|
||||
#if DEBUG >= 5
|
||||
fprintf(stderr, "CTRL: Set peer bandwidth\n");
|
||||
#endif
|
||||
MEDIUM_MSG("CTRL: Set peer bandwidth");
|
||||
//4 bytes window size, 1 byte limit type (ignored)
|
||||
RTMPStream::snd_window_size = ntohl(*(int *)next.data.c_str());
|
||||
myConn.SendNow(RTMPStream::SendCTL(5, RTMPStream::snd_window_size)); //send window acknowledgement size (msg 5)
|
||||
|
@ -838,32 +839,31 @@ 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) ));
|
||||
if ( !pack_out.isNull()){
|
||||
if (!userClient.getData()){
|
||||
if (!nProxy.userClient.getData()){
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
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());
|
||||
nProxy.streamName = streamName;
|
||||
bufferLivePacket(pack_out);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 15:
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Received AMF3 data message");
|
||||
MEDIUM_MSG("Received AMF3 data message");
|
||||
break;
|
||||
case 16:
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Received AMF3 shared object");
|
||||
MEDIUM_MSG("Received AMF3 shared object");
|
||||
break;
|
||||
case 17: {
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Received AMF3 command message");
|
||||
MEDIUM_MSG("Received AMF3 command message");
|
||||
if (next.data[0] != 0) {
|
||||
next.data = next.data.substr(1);
|
||||
amf3data = AMF::parse3(next.data);
|
||||
#if DEBUG >= 5
|
||||
amf3data.Print();
|
||||
#endif
|
||||
MEDIUM_MSG("AMF3: %s", amf3data.Print().c_str());
|
||||
} else {
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Received AMF3-0 command message");
|
||||
MEDIUM_MSG("Received AMF3-0 command message");
|
||||
next.data = next.data.substr(1);
|
||||
amfdata = AMF::parse(next.data);
|
||||
parseAMFCommand(amfdata, 17, next.msg_stream_id);
|
||||
|
@ -871,7 +871,7 @@ namespace Mist {
|
|||
}
|
||||
break;
|
||||
case 19:
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Received AMF0 shared object");
|
||||
MEDIUM_MSG("Received AMF0 shared object");
|
||||
break;
|
||||
case 20: { //AMF0 command message
|
||||
amfdata = AMF::parse(next.data);
|
||||
|
@ -879,10 +879,10 @@ namespace Mist {
|
|||
}
|
||||
break;
|
||||
case 22:
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Received aggregate message");
|
||||
MEDIUM_MSG("Received aggregate message");
|
||||
break;
|
||||
default:
|
||||
DEBUG_MSG(DLVL_FAIL, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.");
|
||||
FAIL_MSG("Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,17 +9,19 @@ namespace Mist {
|
|||
class OutRTMP : public Output {
|
||||
public:
|
||||
OutRTMP(Socket::Connection & conn);
|
||||
~OutRTMP();
|
||||
static void init(Util::Config * cfg);
|
||||
void onRequest();
|
||||
void sendNext();
|
||||
void sendHeader();
|
||||
bool isReadyForPlay();
|
||||
protected:
|
||||
bool isPushing;
|
||||
void parseVars(std::string data);
|
||||
std::string app_name;
|
||||
void parseChunk(Socket::Buffer & inputBuffer);
|
||||
void parseAMFCommand(AMF::Object & amfData, int messageType, int streamId);
|
||||
void sendCommand(AMF::Object & amfReply, int messageType, int streamId);
|
||||
virtual std::string getStatsName();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -40,17 +40,16 @@ namespace Mist {
|
|||
capa["required"]["streamname"]["help"] = "What streamname to serve. For multiple streams, add this protocol multiple times using different ports.";
|
||||
capa["required"]["streamname"]["type"] = "str";
|
||||
capa["required"]["streamname"]["option"] = "--stream";
|
||||
capa["required"]["streamname"]["short"] = "s";
|
||||
capa["optional"]["tracks"]["name"] = "Tracks";
|
||||
capa["optional"]["tracks"]["help"] = "The track IDs of the stream that this connector will transmit separated by spaces";
|
||||
capa["optional"]["tracks"]["type"] = "str";
|
||||
capa["optional"]["tracks"]["option"] = "--tracks";
|
||||
capa["optional"]["tracks"]["short"] = "t";
|
||||
capa["optional"]["tracks"]["default"] = "";
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
cfg->addOption("streamname",
|
||||
JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}"));
|
||||
cfg->addOption("tracks",
|
||||
JSON::fromString("{\"arg\":\"string\",\"value\":[\"\"],\"short\": \"t\",\"long\":\"tracks\",\"help\":\"The track IDs of the stream that this connector will transmit separated by spaces.\"}"));
|
||||
cfg->addConnectorOptions(8888, capa);
|
||||
config = cfg;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace Mist {
|
|||
|
||||
if (packData.getBytesFree() == 184){
|
||||
packData.clear();
|
||||
packData.setPID(0x100 - 1 + thisPacket.getTrackId());
|
||||
packData.setPID(thisPacket.getTrackId());
|
||||
packData.setContinuityCounter(++contCounters[packData.getPID()]);
|
||||
if (first[thisPacket.getTrackId()]){
|
||||
packData.setUnitStart(1);
|
||||
|
@ -125,7 +125,7 @@ namespace Mist {
|
|||
break;
|
||||
}
|
||||
if (alreadySent + 4 > watKunnenWeIn1Ding){
|
||||
nalLead = 4 - watKunnenWeIn1Ding-alreadySent;
|
||||
nalLead = 4 - (watKunnenWeIn1Ding-alreadySent);
|
||||
fillPacket("\000\000\000\001",watKunnenWeIn1Ding-alreadySent);
|
||||
i += watKunnenWeIn1Ding-alreadySent;
|
||||
alreadySent += watKunnenWeIn1Ding-alreadySent;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue