Backported various little edits from Pro edition.

This commit is contained in:
Thulinma 2016-05-30 15:17:54 +02:00
parent ef9938da0c
commit 4c9c6fa7ba
78 changed files with 2334 additions and 1266 deletions

View file

@ -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)

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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"] = ".";

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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();

View file

@ -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.

View file

@ -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);

View file

@ -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;
};
}

View file

@ -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) {

View file

@ -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;
};
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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
};
}

View file

@ -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{

View file

@ -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;
}
}

View file

@ -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 {
};
}

View file

@ -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();

View file

@ -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);

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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();

View file

@ -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();

View file

@ -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){

View file

@ -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;
};
}

View file

@ -2,7 +2,6 @@
namespace Mist {
OutProgressiveFLV::OutProgressiveFLV(Socket::Connection & conn) : HTTPOutput(conn){}
OutProgressiveFLV::~OutProgressiveFLV() {}
void OutProgressiveFLV::init(Util::Config * cfg){
HTTPOutput::init(cfg);

View file

@ -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();

View file

@ -2,7 +2,6 @@
namespace Mist {
OutProgressiveMP3::OutProgressiveMP3(Socket::Connection & conn) : HTTPOutput(conn){}
OutProgressiveMP3::~OutProgressiveMP3(){}
void OutProgressiveMP3::init(Util::Config * cfg){
HTTPOutput::init(cfg);

View file

@ -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();

View file

@ -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 {
}
}

View file

@ -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;
}
}

View file

@ -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();
};
}

View file

@ -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;
}

View file

@ -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;