Merge branch 'master' of github.com:DDVTECH/DMS
Conflicts: Buffer/main.cpp
This commit is contained in:
commit
80ac6ff8c5
21 changed files with 2305 additions and 705 deletions
|
@ -1,4 +1,4 @@
|
|||
SRC = main.cpp ../util/json/json_reader.cpp ../util/json/json_value.cpp ../util/json/json_writer.cpp ../util/socket.cpp ../util/flv_tag.cpp
|
||||
SRC = main.cpp ../util/json/json_reader.cpp ../util/json/json_value.cpp ../util/json/json_writer.cpp ../util/socket.cpp ../util/dtsc.cpp
|
||||
OBJ = $(SRC:.cpp=.o)
|
||||
OUT = DDV_Buffer
|
||||
INCLUDES =
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SRC = main.cpp ../util/socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/util.cpp
|
||||
SRC = main.cpp ../util/socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/dtsc.cpp ../util/util.cpp
|
||||
OBJ = $(SRC:.cpp=.o)
|
||||
OUT = DDV_Conn_HTTP
|
||||
INCLUDES =
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <ctime>
|
||||
#include "../util/socket.h"
|
||||
#include "../util/http_parser.h"
|
||||
#include "../util/dtsc.h"
|
||||
#include "../util/flv_tag.h"
|
||||
#include "../util/MP4/interface.cpp"
|
||||
#include "../util/amf.h"
|
||||
|
@ -149,26 +150,110 @@ namespace Connector_HTTP{
|
|||
return Result;
|
||||
}//BuildManifest
|
||||
|
||||
/// Handles Progressive download streaming requests
|
||||
void Progressive(FLV::Tag & tag, HTTP::Parser HTTP_S, Socket::Connection & conn, DTSC::Stream & Strm){
|
||||
static bool progressive_has_sent_header = false;
|
||||
if (!progressive_has_sent_header){
|
||||
HTTP_S.Clean();//troep opruimen die misschien aanwezig is...
|
||||
HTTP_S.SetHeader("Content-Type", "video/x-flv");//FLV files hebben altijd dit content-type.
|
||||
//HTTP_S.SetHeader("Transfer-Encoding", "chunked");
|
||||
HTTP_S.protocol = "HTTP/1.0";
|
||||
HTTP_S.SendResponse(conn, "200", "OK");//geen SetBody = unknown length! Dat willen we hier.
|
||||
//HTTP_S.SendBodyPart(CONN_fd, FLVHeader, 13);//schrijf de FLV header
|
||||
conn.write(FLV::Header, 13);
|
||||
FLV::Tag tmp;
|
||||
tmp.DTSCMetaInit(Strm);
|
||||
conn.write(tmp.data, tmp.len);
|
||||
if (Strm.metadata.getContentP("audio") && Strm.metadata.getContentP("audio")->getContentP("init")){
|
||||
tmp.DTSCAudioInit(Strm);
|
||||
conn.write(tmp.data, tmp.len);
|
||||
}
|
||||
if (Strm.metadata.getContentP("video") && Strm.metadata.getContentP("video")->getContentP("init")){
|
||||
tmp.DTSCVideoInit(Strm);
|
||||
conn.write(tmp.data, tmp.len);
|
||||
}
|
||||
progressive_has_sent_header = true;
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "Sent progressive FLV header\n");
|
||||
#endif
|
||||
}
|
||||
//HTTP_S.SendBodyPart(CONN_fd, tag->data, tag->len);//schrijf deze FLV tag onbewerkt weg
|
||||
conn.write(tag.data, tag.len);
|
||||
}
|
||||
|
||||
/// Handles Flash Dynamic HTTP streaming requests
|
||||
void FlashDynamic(FLV::Tag & tag, HTTP::Parser HTTP_S, Socket::Connection & conn, DTSC::Stream & Strm){
|
||||
static std::queue<std::string> Flash_FragBuffer;
|
||||
static unsigned int Flash_StartTime = 0;
|
||||
static std::string FlashBuf;
|
||||
static std::string FlashMeta;
|
||||
static bool FlashFirstVideo = false;
|
||||
static bool FlashFirstAudio = false;
|
||||
static bool Flash_ManifestSent = false;
|
||||
static int Flash_RequestPending = 0;
|
||||
if (tag.tagTime() > 0){
|
||||
if (Flash_StartTime == 0){
|
||||
Flash_StartTime = tag.tagTime();
|
||||
}
|
||||
tag.tagTime(tag.tagTime() - Flash_StartTime);
|
||||
}
|
||||
if (tag.data[0] != 0x12 ) {
|
||||
if (tag.isKeyframe){
|
||||
if (FlashBuf != "" && !FlashFirstVideo && !FlashFirstAudio){
|
||||
Flash_FragBuffer.push(FlashBuf);
|
||||
#if DEBUG >= 4
|
||||
fprintf(stderr, "Received a fragment. Now %i in buffer.\n", (int)Flash_FragBuffer.size());
|
||||
#endif
|
||||
}
|
||||
FlashBuf.clear();
|
||||
FlashFirstVideo = true;
|
||||
FlashFirstAudio = true;
|
||||
}
|
||||
/// \todo Check metadata for video/audio, append if needed.
|
||||
/*
|
||||
if (FlashFirstVideo && (tag.data[0] == 0x09) && (Video_Init.len > 0)){
|
||||
Video_Init.tagTime(tag.tagTime());
|
||||
FlashBuf.append(Video_Init.data, Video_Init.len);
|
||||
FlashFirstVideo = false;
|
||||
}
|
||||
if (FlashFirstAudio && (tag.data[0] == 0x08) && (Audio_Init.len > 0)){
|
||||
Audio_Init.tagTime(tag.tagTime());
|
||||
FlashBuf.append(Audio_Init.data, Audio_Init.len);
|
||||
FlashFirstAudio = false;
|
||||
}
|
||||
#if DEBUG >= 5
|
||||
fprintf(stderr, "Received a tag of type %2hhu and length %i\n", tag.data[0], tag.len);
|
||||
#endif
|
||||
if ((Video_Init.len > 0) && (Audio_Init.len > 0)){
|
||||
FlashBuf.append(tag.data,tag.len);
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
/*
|
||||
FlashMeta = "";
|
||||
FlashMeta.append(tag.data+11,tag.len-15);
|
||||
if( !Flash_ManifestSent ) {
|
||||
HTTP_S.Clean();
|
||||
HTTP_S.SetHeader("Content-Type","text/xml");
|
||||
HTTP_S.SetHeader("Cache-Control","no-cache");
|
||||
HTTP_S.SetBody(BuildManifest(FlashMeta, Movie, tag.tagTime()));
|
||||
HTTP_S.SendResponse(conn, "200", "OK");
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Main function for Connector_HTTP
|
||||
int Connector_HTTP(Socket::Connection conn){
|
||||
int handler = HANDLER_PROGRESSIVE;///< The handler used for processing this request.
|
||||
bool ready4data = false;///< Set to true when streaming is to begin.
|
||||
bool inited = false;
|
||||
bool progressive_has_sent_header = false;
|
||||
Socket::Connection ss(-1);
|
||||
std::string streamname;
|
||||
std::string extension;
|
||||
std::string FlashBuf;
|
||||
std::string FlashMeta;
|
||||
bool Flash_ManifestSent = false;
|
||||
int Flash_RequestPending = 0;
|
||||
unsigned int Flash_StartTime;
|
||||
std::queue<std::string> Flash_FragBuffer;
|
||||
FLV::Tag tag;///< Temporary tag buffer for incoming video data.
|
||||
FLV::Tag Audio_Init;///< Audio initialization data, if available.
|
||||
FLV::Tag Video_Init;///< Video initialization data, if available.
|
||||
bool FlashFirstVideo = false;
|
||||
bool FlashFirstAudio = false;
|
||||
FLV::Tag tag;///< Temporary tag buffer.
|
||||
std::string recBuffer = "";
|
||||
DTSC::Stream Strm;///< Incoming stream buffer.
|
||||
HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender.
|
||||
|
||||
std::string Movie = "";
|
||||
|
@ -179,7 +264,7 @@ namespace Connector_HTTP{
|
|||
unsigned int lastStats = 0;
|
||||
//int CurrentFragment = -1; later herbruiken?
|
||||
|
||||
while (conn.connected() && !FLV::Parse_Error){
|
||||
while (conn.connected()){
|
||||
//only parse input if available or not yet init'ed
|
||||
if (HTTP_R.Read(conn, ready4data)){
|
||||
handler = HANDLER_PROGRESSIVE;
|
||||
|
@ -209,8 +294,11 @@ namespace Connector_HTTP{
|
|||
printf( "URL: %s\n", HTTP_R.url.c_str());
|
||||
printf( "Movie: %s, Quality: %s, Seg %d Frag %d\n", Movie.c_str(), Quality.c_str(), Segment, ReqFragment);
|
||||
#endif
|
||||
/// \todo Handle these requests properly...
|
||||
/*
|
||||
Flash_ManifestSent = true;//stop manifest from being sent multiple times
|
||||
Flash_RequestPending++;
|
||||
*/
|
||||
}else{
|
||||
Movie = HTTP_R.url.substr(1);
|
||||
Movie = Movie.substr(0,Movie.find("/"));
|
||||
|
@ -255,6 +343,8 @@ namespace Connector_HTTP{
|
|||
#endif
|
||||
inited = true;
|
||||
}
|
||||
/// \todo Send pending flash requests...
|
||||
/*
|
||||
if ((Flash_RequestPending > 0) && !Flash_FragBuffer.empty()){
|
||||
HTTP_S.Clean();
|
||||
HTTP_S.SetHeader("Content-Type","video/mp4");
|
||||
|
@ -284,97 +374,16 @@ namespace Connector_HTTP{
|
|||
break;
|
||||
case 0: break;//not ready yet
|
||||
default:
|
||||
if (tag.SockLoader(ss)){//able to read a full packet?
|
||||
if (handler == HANDLER_FLASH){
|
||||
if (tag.tagTime() > 0){
|
||||
if (Flash_StartTime == 0){
|
||||
Flash_StartTime = tag.tagTime();
|
||||
}
|
||||
tag.tagTime(tag.tagTime() - Flash_StartTime);
|
||||
if (ss.iread(recBuffer)){
|
||||
if (Strm.parsePacket(recBuffer)){
|
||||
tag.DTSCLoader(Strm);
|
||||
if (handler == HANDLER_FLASH){
|
||||
FlashDynamic(tag, HTTP_S, conn, Strm);
|
||||
}
|
||||
if (tag.data[0] != 0x12 ) {
|
||||
if ( (tag.data[0] == 0x09) && tag.isInitData()){
|
||||
if (((tag.data[11] & 0x0f) == 7) && (tag.data[12] == 0)){
|
||||
tag.tagTime(0);//timestamp to zero
|
||||
Video_Init = tag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((tag.data[0] == 0x08) && tag.isInitData()){
|
||||
if (((tag.data[11] & 0xf0) >> 4) == 10){//aac packet
|
||||
tag.tagTime(0);//timestamp to zero
|
||||
Audio_Init = tag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tag.isKeyframe){
|
||||
if (FlashBuf != "" && !FlashFirstVideo && !FlashFirstAudio){
|
||||
Flash_FragBuffer.push(FlashBuf);
|
||||
#if DEBUG >= 4
|
||||
fprintf(stderr, "Received a fragment. Now %i in buffer.\n", (int)Flash_FragBuffer.size());
|
||||
#endif
|
||||
}
|
||||
FlashBuf.clear();
|
||||
FlashFirstVideo = true;
|
||||
FlashFirstAudio = true;
|
||||
}
|
||||
if (FlashFirstVideo){
|
||||
if (Video_Init.len > 0){
|
||||
Video_Init.tagTime(tag.tagTime());
|
||||
FlashBuf.append(Video_Init.data, Video_Init.len);
|
||||
}
|
||||
FlashFirstVideo = false;
|
||||
if (Audio_Init.len > 0){
|
||||
Audio_Init.tagTime(tag.tagTime());
|
||||
FlashBuf.append(Audio_Init.data, Audio_Init.len);
|
||||
}
|
||||
FlashFirstAudio = false;
|
||||
}
|
||||
#if DEBUG >= 5
|
||||
fprintf(stderr, "Received a tag of type %2hhu and length %i\n", tag.data[0], tag.len);
|
||||
#endif
|
||||
if (!FlashFirstVideo && !FlashFirstAudio){
|
||||
FlashBuf.append(tag.data,tag.len);
|
||||
}
|
||||
} else {
|
||||
FlashMeta = "";
|
||||
FlashMeta.append(tag.data+11,tag.len-15);
|
||||
if( !Flash_ManifestSent ) {
|
||||
HTTP_S.Clean();
|
||||
HTTP_S.SetHeader("Content-Type","text/xml");
|
||||
HTTP_S.SetHeader("Cache-Control","no-cache");
|
||||
HTTP_S.SetBody(BuildManifest(FlashMeta, Movie, tag.tagTime()));
|
||||
HTTP_S.SendResponse(conn, "200", "OK");
|
||||
}
|
||||
if (handler == HANDLER_PROGRESSIVE){
|
||||
Progressive(tag, HTTP_S, conn, Strm);
|
||||
}
|
||||
}
|
||||
if (handler == HANDLER_PROGRESSIVE){
|
||||
if (!progressive_has_sent_header){
|
||||
HTTP_S.Clean();//troep opruimen die misschien aanwezig is...
|
||||
if (extension == ".mp3"){
|
||||
HTTP_S.SetHeader("Content-Type", "audio/mpeg3");//MP3 files hebben dit content-type.
|
||||
HTTP_S.protocol = "HTTP/1.0";
|
||||
HTTP_S.SendResponse(conn, "200", "OK");//geen SetBody = unknown length! Dat willen we hier.
|
||||
}else{
|
||||
HTTP_S.SetHeader("Content-Type", "video/x-flv");//FLV files hebben altijd dit content-type.
|
||||
//HTTP_S.SetHeader("Transfer-Encoding", "chunked");
|
||||
HTTP_S.protocol = "HTTP/1.0";
|
||||
HTTP_S.SendResponse(conn, "200", "OK");//geen SetBody = unknown length! Dat willen we hier.
|
||||
//HTTP_S.SendBodyPart(CONN_fd, FLVHeader, 13);//schrijf de FLV header
|
||||
conn.write(FLV::Header, 13);
|
||||
}
|
||||
progressive_has_sent_header = true;
|
||||
}
|
||||
if (extension == ".mp3"){
|
||||
if (tag.data[0] == 0x08){
|
||||
if (((tag.data[11] & 0xf0) >> 4) == 2){//mp3 packet
|
||||
conn.write(tag.data+12, tag.len-16);//write only the MP3 data of the tag
|
||||
}
|
||||
}
|
||||
}else{
|
||||
conn.write(tag.data, tag.len);
|
||||
}
|
||||
}//PROGRESSIVE handler
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
#include "rtp.h"
|
||||
|
||||
/// Reads a single NALU from std::cin. Expected is H.264 Bytestream format.
|
||||
/// Function was used as a way of debugging data. FLV does not contain all the metadata we need, so we had to try different approaches.
|
||||
/// \return The Nalu data.
|
||||
/// \todo Throw this function away when everything works, it is not needed.
|
||||
std::string ReadNALU( ) {
|
||||
static char Separator[3] = { (char)0x00, (char)0x00, (char)0x01 };
|
||||
std::string Buffer;
|
||||
|
@ -38,62 +40,95 @@ std::string ReadNALU( ) {
|
|||
return Result;
|
||||
}
|
||||
|
||||
/// The main function of the connector
|
||||
/// \param conn A connection with the client
|
||||
/// The main function of the connector.
|
||||
/// Used by server_setup.cpp in the bottom of the file, to start up the Connector.
|
||||
/// This function contains the while loop the accepts connections, and sends them data.
|
||||
/// \param conn A connection with the client.
|
||||
int RTSP_Handler( Socket::Connection conn ) {
|
||||
FLV::Tag tag;///< Temporary tag buffer for incoming video data.
|
||||
/// \todo Convert this to DTSC::DTMI, with an additional DTSC::Stream/
|
||||
FLV::Tag tag;// Temporary tag buffer for incoming video data.
|
||||
bool PlayVideo = false;
|
||||
bool PlayAudio = true;
|
||||
//JRTPlib Objects to handle the RTP connection, which runs "parallel" to RTSP.
|
||||
jrtplib::RTPSession VideoSession;
|
||||
jrtplib::RTPSessionParams VideoParams;
|
||||
jrtplib::RTPUDPv6TransmissionParams VideoTransParams;
|
||||
std::string PreviousRequest = "";
|
||||
Socket::Connection ss(-1);
|
||||
HTTP::Parser HTTP_R, HTTP_S;
|
||||
//Some clients appear to expect a single request per connection. Don't know which ones.
|
||||
bool PerRequest = false;
|
||||
//The main loop of the function
|
||||
while(conn.connected() && !FLV::Parse_Error) {
|
||||
if( HTTP_R.Read(conn ) ) {
|
||||
//send Debug info to stderr.
|
||||
//send the appropriate responses on RTSP Commands.
|
||||
fprintf( stderr, "REQUEST:\n%s\n", HTTP_R.BuildRequest().c_str() );
|
||||
HTTP_S.protocol = "RTSP/1.0";
|
||||
if( HTTP_R.method == "OPTIONS" ) {
|
||||
//Always return the requested CSeq value.
|
||||
HTTP_S.SetHeader( "CSeq", HTTP_R.GetHeader( "CSeq" ).c_str() );
|
||||
//The minimal set of options required for RTSP, add new options here as well if we want to support these.
|
||||
HTTP_S.SetHeader( "Public", "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY" );
|
||||
//End the HTTP body, IMPORTANT!! Connection hangs otherwise!!
|
||||
HTTP_S.SetBody( "\r\n\r\n" );
|
||||
fprintf( stderr, "RESPONSE:\n%s\n", HTTP_S.BuildResponse( "200", "OK" ).c_str() );
|
||||
conn.write( HTTP_S.BuildResponse( "200", "OK" ) );
|
||||
} else if ( HTTP_R.method == "DESCRIBE" ) {
|
||||
///\todo Implement DESCRIBE option.
|
||||
//Don't know if a 501 response is seen as valid. If it is, don't bother changing it.
|
||||
if( HTTP_R.GetHeader( "Accept" ).find( "application/sdp" ) == std::string::npos ) {
|
||||
fprintf( stderr, "RESPONSE:\n%s\n", HTTP_S.BuildResponse( "501", "Not Implemented" ).c_str() );
|
||||
conn.write( HTTP_S.BuildResponse( "501", "Not Implemented" ) );
|
||||
} else {
|
||||
HTTP_S.SetHeader( "CSeq", HTTP_R.GetHeader( "CSeq" ).c_str() );
|
||||
HTTP_S.SetHeader( "Content-Type", "application/sdp" );
|
||||
/// \todo Retrieve presence of video and audio data, and process into response
|
||||
/// \todo Retrieve Packetization mode ( is 0 for now ). Where can I retrieve this?
|
||||
/// \todo Retrieve presence of video and audio data, and process into response. Can now easily be done through DTSC::DTMI
|
||||
/// \todo Retrieve Packetization mode ( is 0 for now ). I suppose this is the H264 packetization mode. Can maybe be retrieved from the docs on H64.
|
||||
/// \todo Send a valid SDP file.
|
||||
/// \todo Add audio to SDP file.
|
||||
//This is just a dummy with data that was supposedly right for our teststream.
|
||||
//SDP Docs: http://tools.ietf.org/html/rfc4566
|
||||
//v=0
|
||||
//o=- 0 0 IN IP4 ddvtech.com
|
||||
//s=Fifa Test
|
||||
//c=IN IP4 127.0.0.1
|
||||
//t=0 0
|
||||
//a=recvonly
|
||||
//m=video 0 RTP/AVP 98
|
||||
//a=control:rtsp://localhost/fifa/video
|
||||
//a=rtpmap:98 H264/90000
|
||||
//a=fmtp:98 packetization-mode=0
|
||||
HTTP_S.SetBody( "v=0\r\no=- 0 0 IN IP4 ddvtech.com\r\ns=Fifa Test\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\na=recvonly\r\nm=video 0 RTP/AVP 98\r\na=control:rtsp://localhost/fifa/video\r\na=rtpmap:98 H264/90000\r\na=fmtp:98 packetization-mode=0\r\n\r\n");//m=audio 0 RTP/AAP 96\r\na=control:rtsp://localhost/fifa/audio\r\na=rtpmap:96 mpeg4-generic/16000/2\r\n\r\n");
|
||||
fprintf( stderr, "RESPONSE:\n%s\n", HTTP_S.BuildResponse( "200", "OK" ).c_str() );
|
||||
conn.write( HTTP_S.BuildResponse( "200", "OK" ) );
|
||||
}
|
||||
} else if ( HTTP_R.method == "SETUP" ) {
|
||||
std::string temp = HTTP_R.GetHeader("Transport");
|
||||
//Extract the random UTP pair for video data ( RTP/RTCP)
|
||||
int ClientRTPLoc = temp.find( "client_port=" ) + 12;
|
||||
int PortSpacer = temp.find( "-", ClientRTPLoc );
|
||||
int RTPClientPort = atoi( temp.substr( ClientRTPLoc, ( PortSpacer - ClientRTPLoc ) ).c_str() );
|
||||
if( HTTP_S.GetHeader( "Session" ) != "" ) {
|
||||
//Return an error if a second client tries to connect with an already running stream.
|
||||
fprintf( stderr, "RESPONSE:\n%s\n", HTTP_S.BuildResponse( "459", "Aggregate Operation Not Allowed" ).c_str() );
|
||||
conn.write( HTTP_S.BuildResponse( "459", "Aggregate Operation Not Allowed" ) );
|
||||
} else {
|
||||
HTTP_S.SetHeader( "CSeq", HTTP_R.GetHeader( "CSeq" ).c_str() );
|
||||
HTTP_S.SetHeader( "Session", time(NULL) );
|
||||
/// \todo "Random" generation of server_ports
|
||||
if( HTTP_R.url.find( "audio" ) != std::string::npos ) {
|
||||
HTTP_S.SetHeader( "Transport", HTTP_R.GetHeader( "Transport" ) + ";server_port=50002-50003" );
|
||||
} else {
|
||||
/// \todo Add support for audio
|
||||
// if( HTTP_R.url.find( "audio" ) != std::string::npos ) {
|
||||
// HTTP_S.SetHeader( "Transport", HTTP_R.GetHeader( "Transport" ) + ";server_port=50002-50003" );
|
||||
// } else {
|
||||
//send video data
|
||||
HTTP_S.SetHeader( "Transport", HTTP_R.GetHeader( "Transport" ) + ";server_port=50000-50001" );
|
||||
//Stub data for testing purposes. This should now be extracted somehow from DTSC::DTMI
|
||||
VideoParams.SetOwnTimestampUnit( ( 1.0 / 29.917 ) * 90000.0 );
|
||||
VideoParams.SetMaximumPacketSize( 10000 );
|
||||
//pick the right port here
|
||||
//pick the right port here
|
||||
VideoTransParams.SetPortbase( 50000 );
|
||||
//create a JRTPlib session
|
||||
int VideoStatus = VideoSession.Create( VideoParams, &VideoTransParams, jrtplib::RTPTransmitter::IPv6UDPProto );
|
||||
if( VideoStatus < 0 ) {
|
||||
std::cerr << jrtplib::RTPGetErrorString( VideoStatus ) << std::endl;
|
||||
|
@ -101,13 +136,15 @@ int RTSP_Handler( Socket::Connection conn ) {
|
|||
} else {
|
||||
std::cerr << "Created video session\n";
|
||||
}
|
||||
/// \todo retrieve other client than localhost --> Socket::Connection has no support for this yet?
|
||||
|
||||
|
||||
/// \todo Connect with clients other than localhost
|
||||
uint8_t localip[32];
|
||||
int status = inet_pton( AF_INET6, conn.getHost().c_str(), localip ) ;
|
||||
//Debug info
|
||||
std::cerr << "Status: " << status << "\n";
|
||||
jrtplib::RTPIPv6Address addr(localip,RTPClientPort);
|
||||
|
||||
//add the destination address to the VideoSession
|
||||
VideoStatus = VideoSession.AddDestination(addr);
|
||||
if (VideoStatus < 0) {
|
||||
std::cerr << jrtplib::RTPGetErrorString(VideoStatus) << std::endl;
|
||||
|
@ -115,19 +152,24 @@ int RTSP_Handler( Socket::Connection conn ) {
|
|||
} else {
|
||||
std::cerr << "Destination Set\n";
|
||||
}
|
||||
//Stub data for testing purposes.
|
||||
//Payload type should confirm with the SDP File. 98 == H264 / AVC
|
||||
VideoSession.SetDefaultPayloadType(98);
|
||||
VideoSession.SetDefaultMark(false);
|
||||
//We have no idea if this timestamp has to correspond with the OwnTimeStampUnit() above.
|
||||
VideoSession.SetDefaultTimestampIncrement( ( 1.0 / 29.917 ) * 90000 );
|
||||
}
|
||||
// }
|
||||
HTTP_S.SetBody( "\r\n\r\n" );
|
||||
fprintf( stderr, "RESPONSE:\n%s\n", HTTP_S.BuildResponse( "200", "OK" ).c_str() );
|
||||
conn.write( HTTP_S.BuildResponse( "200", "OK" ) );
|
||||
}
|
||||
} else if( HTTP_R.method == "PLAY" ) {
|
||||
if( HTTP_R.GetHeader( "Range" ).substr(0,4) != "npt=" ) {
|
||||
//We do not support this, whatever it is. Not needed for minimal compliance.
|
||||
fprintf( stderr, "RESPONSE:\n%s\n", HTTP_S.BuildResponse( "501", "Not Implemented" ).c_str() );
|
||||
conn.write( HTTP_S.BuildResponse( "501", "Not Implemented" ) );
|
||||
} else {
|
||||
//Initializes for actual streaming over the SETUP connection.
|
||||
HTTP_S.SetHeader( "CSeq", HTTP_R.GetHeader( "CSeq" ).c_str() );
|
||||
HTTP_S.SetHeader( "Session", HTTP_R.GetHeader( "Session" ) );
|
||||
HTTP_S.SetHeader( "Range", HTTP_R.GetHeader( "Range" ) );
|
||||
|
@ -135,15 +177,20 @@ int RTSP_Handler( Socket::Connection conn ) {
|
|||
HTTP_S.SetBody( "\r\n\r\n" );
|
||||
fprintf( stderr, "RESPONSE:\n%s\n", HTTP_S.BuildResponse( "200", "OK" ).c_str() );
|
||||
conn.write( HTTP_S.BuildResponse( "200", "OK" ) );
|
||||
//Used further down, to start streaming video.
|
||||
//PlayAudio = true;
|
||||
PlayVideo = true;
|
||||
}
|
||||
} else if( HTTP_R.method == "TEARDOWN" ) {
|
||||
//If we were sending any stream data at this point, stop it, but keep the setup.
|
||||
HTTP_S.SetHeader( "CSeq", HTTP_R.GetHeader( "CSeq" ).c_str() );
|
||||
HTTP_S.SetBody( "\r\n\r\n" );
|
||||
fprintf( stderr, "RESPONSE:\n%s\n", HTTP_S.BuildResponse( "200", "OK" ).c_str() );
|
||||
conn.write( HTTP_S.BuildResponse( "200", "OK" ) );
|
||||
//PlayAudio = false;
|
||||
PlayVideo = false;
|
||||
} else {
|
||||
//We do not implement other commands ( yet )
|
||||
fprintf( stderr, "RESPONSE:\n%s\n", HTTP_S.BuildResponse( "501", "Not Implemented" ).c_str() );
|
||||
conn.write( HTTP_S.BuildResponse( "501", "Not Implemented" ) );
|
||||
}
|
||||
|
@ -154,14 +201,18 @@ int RTSP_Handler( Socket::Connection conn ) {
|
|||
}
|
||||
}
|
||||
if( PlayVideo ) {
|
||||
/// \todo Select correct source
|
||||
/// \todo Select correct source. This should become the DTSC::DTMI or the DTSC::Stream, whatever seems more natural.
|
||||
std::string VideoBuf = ReadNALU( );
|
||||
if( VideoBuf == "" ) {
|
||||
//videobuffer is empty, no more data.
|
||||
jrtplib::RTPTime delay = jrtplib::RTPTime(10.0);
|
||||
VideoSession.BYEDestroy(delay,"Out of data",11);
|
||||
conn.close();
|
||||
} else {
|
||||
//Send a single NALU (H264 block) here.
|
||||
VideoSession.SendPacket( VideoBuf.c_str(), VideoBuf.size(), 98, false, ( 1.0 / 29.917 ) * 90000 );
|
||||
//we can add delays here as follows:
|
||||
//don't know if these are nescecary or not, but good for testing nonetheless
|
||||
// jrtplib::RTPTime delay( ( 1.0 / 29.917 ) * 90000 );
|
||||
// jrtplib::RTPTime::Wait( delay );
|
||||
}
|
||||
|
@ -170,7 +221,11 @@ int RTSP_Handler( Socket::Connection conn ) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
//Set Default Port
|
||||
#define DEFAULT_PORT 554
|
||||
//Set the function that should be forked for each client
|
||||
#define MAINHANDLER RTSP_Handler
|
||||
//Set the section in the Config file, though we will not use this yet
|
||||
#define CONFIGSECT RTSP
|
||||
//Include the main functionality, as well as fork support and everything.
|
||||
#include "../util/server_setup.cpp"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SRC = main.cpp ../util/socket.cpp ../util/flv_tag.cpp
|
||||
SRC = main.cpp ../util/socket.cpp ../util/dtsc.cpp ../util/util.cpp
|
||||
OBJ = $(SRC:.cpp=.o)
|
||||
OUT = DDV_Conn_TS
|
||||
INCLUDES =
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/epoll.h>
|
||||
#include "../util/socket.h"
|
||||
#include "../util/flv_tag.h"
|
||||
#include "../util/dtsc.h"
|
||||
|
||||
/// A simple class to create a single Transport Packet
|
||||
class Transport_Packet {
|
||||
|
@ -216,37 +216,39 @@ void SendPMT( Socket::Connection conn ) {
|
|||
|
||||
/// Wraps one or more NALU packets into transport packets
|
||||
/// \param tag The FLV Tag
|
||||
std::vector<Transport_Packet> WrapNalus( FLV::Tag tag ) {
|
||||
std::vector<Transport_Packet> WrapNalus( DTSC::DTMI Tag ) {
|
||||
static int ContinuityCounter = 0;
|
||||
static int Previous_Tag = 0;
|
||||
static int First_PCR = getNowMS();
|
||||
static int Previous_PCR = 0;
|
||||
static char LeadIn[4] = { (char)0x00, (char)0x00, (char)0x00, (char)0x001 };
|
||||
int Current_PCR;
|
||||
int Current_Tag;
|
||||
|
||||
std::string Data = Tag.getContentP("data")->StrValue();
|
||||
|
||||
int Offset = 0;
|
||||
Transport_Packet TS;
|
||||
int Sent = 0;
|
||||
int PacketAmount = ( ( tag.len - (188 - 35 ) ) / 184 ) + 2;
|
||||
|
||||
int PacketAmount = ceil( ( ( Data.size() - (188 - 35 ) ) / 184 ) + 1 );//Minus first packet, + round up and first packet.
|
||||
std::vector<Transport_Packet> Result;
|
||||
char LeadIn[4] = { (char)0x00, (char)0x00, (char)0x00, (char)0x01 };
|
||||
TS = Transport_Packet( true, 0x100 );
|
||||
TS.SetContinuityCounter( ContinuityCounter );
|
||||
ContinuityCounter = ( ( ContinuityCounter + 1 ) & 0x0F );
|
||||
Current_PCR = getNowMS();
|
||||
Current_Tag = ( tag.data[7] << 24 ) + ( tag.data[4] << 16 ) + ( tag.data[5] << 8 ) + ( tag.data[6] );
|
||||
Current_Tag = Tag.getContentP("time")->NumValue();
|
||||
if( true ) { //Current_PCR - Previous_PCR >= 1 ) {
|
||||
TS.SetAdaptationField( Current_PCR - First_PCR );
|
||||
Offset = 8;
|
||||
Previous_PCR = Current_PCR;
|
||||
}
|
||||
TS.SetPesHeader( 4 + Offset, tag.len - 16 , Current_Tag, Previous_Tag );
|
||||
TS.SetPesHeader( 4 + Offset, Data.size() , Current_Tag, Previous_Tag );
|
||||
Previous_Tag = Current_Tag;
|
||||
TS.SetPayload( LeadIn, 31, 23 + Offset );
|
||||
TS.SetPayload( &tag.data[16], 157 - Offset, 27 + Offset );
|
||||
TS.SetPayload( LeadIn, 4, 23 + Offset );
|
||||
TS.SetPayload( &Data[16], 157 - Offset, 27 + Offset );
|
||||
Sent = 157 - Offset;
|
||||
Result.push_back( TS );
|
||||
while( Sent + 176 < tag.len - 16 ) {
|
||||
while( Sent + 176 < Data.size() ) {
|
||||
// for( int i = 0; i < (PacketAmount - 1); i++ ) {
|
||||
TS = Transport_Packet( false, 0x100 );
|
||||
TS.SetContinuityCounter( ContinuityCounter );
|
||||
|
@ -258,11 +260,11 @@ std::vector<Transport_Packet> WrapNalus( FLV::Tag tag ) {
|
|||
Offset = 8;
|
||||
Previous_PCR = Current_PCR;
|
||||
}
|
||||
TS.SetPayload( &tag.data[16 + Sent], 184 - Offset, 4 + Offset );
|
||||
TS.SetPayload( &Data[16 + Sent], 184 - Offset, 4 + Offset );
|
||||
Sent += 184 - Offset;
|
||||
Result.push_back( TS );
|
||||
}
|
||||
if( Sent < ( tag.len - 16 ) ) {
|
||||
if( Sent < ( Data.size() ) ) {
|
||||
Current_PCR = getNowMS();
|
||||
Offset = 0;
|
||||
if( true ) { //now - Previous_PCR >= 5 ) {
|
||||
|
@ -271,16 +273,16 @@ std::vector<Transport_Packet> WrapNalus( FLV::Tag tag ) {
|
|||
Previous_PCR = Current_PCR;
|
||||
}
|
||||
std::cerr << "Wrapping packet: last packet length\n";
|
||||
std::cerr << "\tTotal:\t\t" << tag.len - 16 << "\n";
|
||||
std::cerr << "\tTotal:\t\t" << Data.size() << "\n";
|
||||
std::cerr << "\tSent:\t\t" << Sent << "\n";
|
||||
int To_Send = ( tag.len - 16 ) - Sent;
|
||||
int To_Send = ( Data.size() ) - Sent;
|
||||
std::cerr << "\tTo Send:\t" << To_Send << "\n";
|
||||
std::cerr << "\tStuffing:\t" << 176 - To_Send << "\n";
|
||||
char Stuffing = (char)0xFF;
|
||||
for( int i = 0; i < ( 176 - To_Send ); i++ ) {
|
||||
TS.SetPayload( &Stuffing, 1, 4 + Offset + i );
|
||||
}
|
||||
TS.SetPayload( &tag.data[16 + Sent], 176 - To_Send , 4 + Offset + ( 176 - To_Send ) );
|
||||
TS.SetPayload( &Data[16 + Sent], 176 - To_Send , 4 + Offset + ( 176 - To_Send ) );
|
||||
Result.push_back( TS );
|
||||
}
|
||||
return Result;
|
||||
|
@ -301,12 +303,13 @@ void Transport_Packet::Write( Socket::Connection conn ) {
|
|||
/// The main function of the connector
|
||||
/// \param conn A connection with the client
|
||||
int TS_Handler( Socket::Connection conn ) {
|
||||
FLV::Tag tag;///< Temporary tag buffer for incoming video data.
|
||||
DTSC::Stream stream;
|
||||
// FLV::Tag tag;///< Temporary tag buffer for incoming video data.
|
||||
bool inited = false;
|
||||
bool firstvideo = true;
|
||||
Socket::Connection ss(-1);
|
||||
int zet = 0;
|
||||
while(conn.connected() && !FLV::Parse_Error) {
|
||||
while(conn.connected()) {// && !FLV::Parse_Error) {
|
||||
if( !inited ) {
|
||||
ss = Socket::Connection("/tmp/shared_socket_fifa");
|
||||
if (!ss.connected()){
|
||||
|
@ -331,27 +334,22 @@ int TS_Handler( Socket::Connection conn ) {
|
|||
break;
|
||||
case 0: break;//not ready yet
|
||||
default:
|
||||
if (tag.SockLoader(ss)){//able to read a full packet?
|
||||
if( tag.data[ 0 ] == 0x09 ) {
|
||||
if( ( ( tag.data[ 11 ] & 0x0F ) == 7 ) ) { //&& ( tag.data[ 12 ] == 1 ) ) {
|
||||
fprintf(stderr, "Video contains NALU\n" );
|
||||
// if( firstvideo ) {
|
||||
// firstvideo = false;
|
||||
// } else {
|
||||
SendPAT( conn );
|
||||
SendPMT( conn );
|
||||
std::vector<Transport_Packet> Meh = WrapNalus( tag );
|
||||
for( int i = 0; i < Meh.size( ); i++ ) {
|
||||
Meh[i].Write( conn );
|
||||
}
|
||||
std::cerr << "Item: " << ++zet << "\n";
|
||||
// }
|
||||
}
|
||||
ss.spool();
|
||||
if ( stream.parsePacket( conn.Received() ) ) {
|
||||
if( stream.lastType() == DTSC::VIDEO ) {
|
||||
fprintf(stderr, "Video contains NALU\n" );
|
||||
SendPAT( conn );
|
||||
SendPMT( conn );
|
||||
std::vector<Transport_Packet> AllNalus = WrapNalus( stream.getPacket(0) );
|
||||
for( int i = 0; i < AllNalus.size( ); i++ ) {
|
||||
AllNalus[i].Write( conn );
|
||||
}
|
||||
std::cerr << "Item: " << ++zet << "\n";
|
||||
}
|
||||
if( tag.data[ 0 ] == 0x08 ) {
|
||||
if( ( tag.data[ 11 ] == 0xAF ) && ( tag.data[ 12 ] == 0x01 ) ) {
|
||||
if( stream.lastType() == DTSC::AUDIO ) {
|
||||
// if( ( tag.data[ 11 ] == 0xAF ) && ( tag.data[ 12 ] == 0x01 ) ) {
|
||||
fprintf(stderr, "Audio Contains Raw AAC\n");
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
173
jquery.js
vendored
Executable file → Normal file
173
jquery.js
vendored
Executable file → Normal file
File diff suppressed because one or more lines are too long
821
server-rel.html
Normal file
821
server-rel.html
Normal file
|
@ -0,0 +1,821 @@
|
|||
<!doctype html>
|
||||
|
||||
<html lang='en'>
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset='utf-8' />
|
||||
|
||||
<title>Server Manager - not connected</title>
|
||||
|
||||
<script src='jquery.js'></script><!-- TODO inline -->
|
||||
<script>var MD5=function(j){function RotateLeft(a,b){return(a<<b)|(a>>>(32-b))}function AddUnsigned(a,b){var c,lY4,lX8,lY8,lResult;lX8=(a&0x80000000);lY8=(b&0x80000000);c=(a&0x40000000);lY4=(b&0x40000000);lResult=(a&0x3FFFFFFF)+(b&0x3FFFFFFF);if(c&lY4){return(lResult^0x80000000^lX8^lY8)}if(c|lY4){if(lResult&0x40000000){return(lResult^0xC0000000^lX8^lY8)}else{return(lResult^0x40000000^lX8^lY8)}}else{return(lResult^lX8^lY8)}}function F(x,y,z){return(x&y)|((~x)&z)}function G(x,y,z){return(x&z)|(y&(~z))}function H(x,y,z){return(x^y^z)}function I(x,y,z){return(y^(x|(~z)))}function FF(a,b,c,d,x,s,e){a=AddUnsigned(a,AddUnsigned(AddUnsigned(F(b,c,d),x),e));return AddUnsigned(RotateLeft(a,s),b)};function GG(a,b,c,d,x,s,e){a=AddUnsigned(a,AddUnsigned(AddUnsigned(G(b,c,d),x),e));return AddUnsigned(RotateLeft(a,s),b)};function HH(a,b,c,d,x,s,e){a=AddUnsigned(a,AddUnsigned(AddUnsigned(H(b,c,d),x),e));return AddUnsigned(RotateLeft(a,s),b)};function II(a,b,c,d,x,s,e){a=AddUnsigned(a,AddUnsigned(AddUnsigned(I(b,c,d),x),e));return AddUnsigned(RotateLeft(a,s),b)};function ConvertToWordArray(a){var b;var c=a.length;var d=c+8;var e=(d-(d%64))/64;var f=(e+1)*16;var g=Array(f-1);var h=0;var i=0;while(i<c){b=(i-(i%4))/4;h=(i%4)*8;g[b]=(g[b]|(a.charCodeAt(i)<<h));i++}b=(i-(i%4))/4;h=(i%4)*8;g[b]=g[b]|(0x80<<h);g[f-2]=c<<3;g[f-1]=c>>>29;return g};function WordToHex(a){var b="",WordToHexValue_temp="",lByte,lCount;for(lCount=0;lCount<=3;lCount++){lByte=(a>>>(lCount*8))&255;WordToHexValue_temp="0"+lByte.toString(16);b=b+WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2)}return b};function Utf8Encode(a){a=a.replace(/\r\n/g,"\n");var b="";for(var n=0;n<a.length;n++){var c=a.charCodeAt(n);if(c<128){b+=String.fromCharCode(c)}else if((c>127)&&(c<2048)){b+=String.fromCharCode((c>>6)|192);b+=String.fromCharCode((c&63)|128)}else{b+=String.fromCharCode((c>>12)|224);b+=String.fromCharCode(((c>>6)&63)|128);b+=String.fromCharCode((c&63)|128)}}return b};var x=Array();var k,AA,BB,CC,DD,a,b,c,d;var l=7,S12=12,S13=17,S14=22;var m=5,S22=9,S23=14,S24=20;var o=4,S32=11,S33=16,S34=23;var p=6,S42=10,S43=15,S44=21;j=Utf8Encode(j);x=ConvertToWordArray(j);a=0x67452301;b=0xEFCDAB89;c=0x98BADCFE;d=0x10325476;for(k=0;k<x.length;k+=16){AA=a;BB=b;CC=c;DD=d;a=FF(a,b,c,d,x[k+0],l,0xD76AA478);d=FF(d,a,b,c,x[k+1],S12,0xE8C7B756);c=FF(c,d,a,b,x[k+2],S13,0x242070DB);b=FF(b,c,d,a,x[k+3],S14,0xC1BDCEEE);a=FF(a,b,c,d,x[k+4],l,0xF57C0FAF);d=FF(d,a,b,c,x[k+5],S12,0x4787C62A);c=FF(c,d,a,b,x[k+6],S13,0xA8304613);b=FF(b,c,d,a,x[k+7],S14,0xFD469501);a=FF(a,b,c,d,x[k+8],l,0x698098D8);d=FF(d,a,b,c,x[k+9],S12,0x8B44F7AF);c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1);b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE);a=FF(a,b,c,d,x[k+12],l,0x6B901122);d=FF(d,a,b,c,x[k+13],S12,0xFD987193);c=FF(c,d,a,b,x[k+14],S13,0xA679438E);b=FF(b,c,d,a,x[k+15],S14,0x49B40821);a=GG(a,b,c,d,x[k+1],m,0xF61E2562);d=GG(d,a,b,c,x[k+6],S22,0xC040B340);c=GG(c,d,a,b,x[k+11],S23,0x265E5A51);b=GG(b,c,d,a,x[k+0],S24,0xE9B6C7AA);a=GG(a,b,c,d,x[k+5],m,0xD62F105D);d=GG(d,a,b,c,x[k+10],S22,0x2441453);c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681);b=GG(b,c,d,a,x[k+4],S24,0xE7D3FBC8);a=GG(a,b,c,d,x[k+9],m,0x21E1CDE6);d=GG(d,a,b,c,x[k+14],S22,0xC33707D6);c=GG(c,d,a,b,x[k+3],S23,0xF4D50D87);b=GG(b,c,d,a,x[k+8],S24,0x455A14ED);a=GG(a,b,c,d,x[k+13],m,0xA9E3E905);d=GG(d,a,b,c,x[k+2],S22,0xFCEFA3F8);c=GG(c,d,a,b,x[k+7],S23,0x676F02D9);b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A);a=HH(a,b,c,d,x[k+5],o,0xFFFA3942);d=HH(d,a,b,c,x[k+8],S32,0x8771F681);c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122);b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C);a=HH(a,b,c,d,x[k+1],o,0xA4BEEA44);d=HH(d,a,b,c,x[k+4],S32,0x4BDECFA9);c=HH(c,d,a,b,x[k+7],S33,0xF6BB4B60);b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70);a=HH(a,b,c,d,x[k+13],o,0x289B7EC6);d=HH(d,a,b,c,x[k+0],S32,0xEAA127FA);c=HH(c,d,a,b,x[k+3],S33,0xD4EF3085);b=HH(b,c,d,a,x[k+6],S34,0x4881D05);a=HH(a,b,c,d,x[k+9],o,0xD9D4D039);d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5);c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8);b=HH(b,c,d,a,x[k+2],S34,0xC4AC5665);a=II(a,b,c,d,x[k+0],p,0xF4292244);d=II(d,a,b,c,x[k+7],S42,0x432AFF97);c=II(c,d,a,b,x[k+14],S43,0xAB9423A7);b=II(b,c,d,a,x[k+5],S44,0xFC93A039);a=II(a,b,c,d,x[k+12],p,0x655B59C3);d=II(d,a,b,c,x[k+3],S42,0x8F0CCC92);c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D);b=II(b,c,d,a,x[k+1],S44,0x85845DD1);a=II(a,b,c,d,x[k+8],p,0x6FA87E4F);d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0);c=II(c,d,a,b,x[k+6],S43,0xA3014314);b=II(b,c,d,a,x[k+13],S44,0x4E0811A1);a=II(a,b,c,d,x[k+4],p,0xF7537E82);d=II(d,a,b,c,x[k+11],S42,0xBD3AF235);c=II(c,d,a,b,x[k+2],S43,0x2AD7D2BB);b=II(b,c,d,a,x[k+9],S44,0xEB86D391);a=AddUnsigned(a,AA);b=AddUnsigned(b,BB);c=AddUnsigned(c,CC);d=AddUnsigned(d,DD)}var q=WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d);return q.toLowerCase()};</script>
|
||||
|
||||
<style>
|
||||
|
||||
body
|
||||
{
|
||||
background-color: #fafafa;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#page
|
||||
{
|
||||
overflow: hidden;
|
||||
margin: 10px -170px 0 230px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.floatright
|
||||
{
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* content - tables */
|
||||
|
||||
table
|
||||
{
|
||||
width: 100%;
|
||||
/*margin-top: 5px;*/
|
||||
/*border-spacing: 0;*/
|
||||
}
|
||||
|
||||
table
|
||||
{
|
||||
border-spacing: 0 4px;
|
||||
}
|
||||
|
||||
table thead
|
||||
{
|
||||
background-color: #c8c8c8;
|
||||
}
|
||||
|
||||
table th, table td
|
||||
{
|
||||
height: 30px;
|
||||
padding: 0 0 0 30px;
|
||||
}
|
||||
|
||||
table th
|
||||
{
|
||||
color: #505050;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table td
|
||||
{
|
||||
color: #505050;
|
||||
}
|
||||
|
||||
|
||||
tbody tr:nth-child(even)
|
||||
{
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
tbody tr:nth-child(odd)
|
||||
{
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* login stuff */
|
||||
|
||||
#login
|
||||
{
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
#login > input
|
||||
{
|
||||
display: block;
|
||||
margin: 5px 0 13px 0;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
/* connect button */
|
||||
|
||||
#login > button
|
||||
{
|
||||
float: right;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* input general */
|
||||
input, select
|
||||
{
|
||||
padding: 5px;
|
||||
color: #505050;
|
||||
border: 1px solid #b4b4b4;
|
||||
}
|
||||
|
||||
button
|
||||
{
|
||||
height: 30px;
|
||||
background-color: #505050;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
|
||||
tbody button,
|
||||
tbody select,
|
||||
tbody input
|
||||
{
|
||||
padding: 2px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
tbody td button
|
||||
{
|
||||
padding: 2px 7px 2px 7px;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tbody td.center
|
||||
{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.outsidetable td
|
||||
{
|
||||
padding-top: 30px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* header */
|
||||
|
||||
#header
|
||||
{
|
||||
margin: 30px 0 0 0;
|
||||
width: 100%;
|
||||
background-color: #b4b4b4;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#header-title
|
||||
{
|
||||
padding: 0 0 0 30px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#header-status
|
||||
{
|
||||
float: right;
|
||||
padding: 0 30px 0 0;
|
||||
}
|
||||
|
||||
#header-title, #header-status
|
||||
{
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.disconnected
|
||||
{
|
||||
color: #cc3333;
|
||||
}
|
||||
|
||||
.connected
|
||||
{
|
||||
color: #14991a;
|
||||
}
|
||||
|
||||
.loggingin
|
||||
{
|
||||
color: #ee8833;
|
||||
}
|
||||
|
||||
#header
|
||||
{
|
||||
color: #fafafa;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* navigation */
|
||||
|
||||
#nav
|
||||
{
|
||||
float: left;
|
||||
width: 200px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
|
||||
#nav li
|
||||
{
|
||||
display: block;
|
||||
color: #b4b4b4;
|
||||
line-height: 30px;
|
||||
padding: 0 0 0 30px;
|
||||
margin: 5px 0 5px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#nav li:hover, #nav .selected
|
||||
{
|
||||
color: #505050;
|
||||
background-color: #c8c8c8;
|
||||
}
|
||||
|
||||
#nav #logoutbutton
|
||||
{
|
||||
color: #cc3333;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* fonts */
|
||||
|
||||
#header-title > span, #header-connection, #header-host,
|
||||
#nav,
|
||||
th,
|
||||
#login > button,
|
||||
p,
|
||||
label
|
||||
{
|
||||
font: normal bold 11pt Arial, sans-serif;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#login > input
|
||||
{
|
||||
font: normal bold 11pt Arial, sans-serif;
|
||||
}
|
||||
|
||||
td
|
||||
{
|
||||
font: normal normal 10pt Arial, sans-serif;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id='header'>
|
||||
|
||||
<div id='header-title'>
|
||||
<span>Mistserver Manager</span>
|
||||
</div>
|
||||
|
||||
<div id='header-status'>
|
||||
<span id='header-connection' class='disconnected'>Disconnected</span>
|
||||
<span id='header-host'></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<ul id='nav'>
|
||||
<li class='selected'>overview</li>
|
||||
<li>protocols</li>
|
||||
<li>streams</li>
|
||||
<li>limits</li>
|
||||
<li>logs</li>
|
||||
<li id='logoutbutton'>disconnect</li>
|
||||
</ul>
|
||||
|
||||
<div id='page'></div>
|
||||
|
||||
</body>
|
||||
|
||||
<script>
|
||||
|
||||
// creds and local copy of the settings
|
||||
var settings =
|
||||
{
|
||||
server: '',
|
||||
credentials:
|
||||
{
|
||||
username: "",
|
||||
password: "",
|
||||
authstring: ""
|
||||
},
|
||||
settings: {}
|
||||
};
|
||||
|
||||
var ltypes =
|
||||
[
|
||||
['kb_total', 'Total bandwidth'],
|
||||
['kbps_max', 'Current bandwidth'],
|
||||
['users', 'Concurrent users'],
|
||||
['streams', 'Cocurrent streams'],
|
||||
['geo', 'Geolimited'],
|
||||
['host', 'Hostlimited'],
|
||||
['time', 'Timelimited'],
|
||||
['duration', 'Duration'],
|
||||
['str_kbps_min', 'Minimum bitrate'],
|
||||
['str_kbps_max', 'Maximum bitrate']
|
||||
];
|
||||
|
||||
$(document).ready(function()
|
||||
{
|
||||
$('#nav').children().each(function()
|
||||
{
|
||||
$(this).click(function()
|
||||
{
|
||||
// remove currently selected
|
||||
$('#nav').children().each(function()
|
||||
{
|
||||
$(this).attr('class', '');
|
||||
});
|
||||
// select this one
|
||||
$(this).attr('class', 'selected');
|
||||
// show correct tab
|
||||
showTab($(this).text());
|
||||
});
|
||||
});
|
||||
|
||||
// onload show login 'tab' and hide menu
|
||||
showTab('login');
|
||||
$('#nav').css('visibility', 'hidden');
|
||||
});
|
||||
|
||||
|
||||
// format date to something pretty
|
||||
function formatDate(date)
|
||||
{
|
||||
var d = new Date(date * 1000);
|
||||
|
||||
return [
|
||||
('00' + d.getMonth()).slice(-2),
|
||||
('00' + d.getDate()).slice(-2),
|
||||
d.getFullYear()
|
||||
].join('/') + ' ' + [
|
||||
('00' + d.getHours()).slice(-2),
|
||||
('00' + d.getMinutes()).slice(-2),
|
||||
('00' + d.getSeconds()).slice(-2)
|
||||
].join(':');
|
||||
}
|
||||
|
||||
|
||||
function shortToLongLimit(name)
|
||||
{
|
||||
console.log('getting the long name for the limit "' + name + '"');
|
||||
|
||||
var i;
|
||||
|
||||
for(i = 0; i < ltypes.length; i++)
|
||||
{
|
||||
if(name == ltypes[i][0])
|
||||
{
|
||||
return ltypes[i][1];
|
||||
}
|
||||
}
|
||||
|
||||
console.log('ERROR! short limit ', name, ' not in list, returning _short name_!');
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
// connect to server and set/get settings
|
||||
function loadSettings(callback)
|
||||
{
|
||||
var errorstr = '',
|
||||
data = $.extend(settings.settings,
|
||||
{
|
||||
'authorize':
|
||||
{
|
||||
'username': settings.credentials.username,
|
||||
'password': MD5(MD5(settings.credentials.password) + settings.credentials.authstring)
|
||||
}
|
||||
});
|
||||
|
||||
console.log('SEND', data);
|
||||
|
||||
$.ajax(
|
||||
{
|
||||
'url': settings.server,
|
||||
'data':
|
||||
{
|
||||
"command": JSON.stringify(data)
|
||||
},
|
||||
'dataType': 'jsonp',
|
||||
'timeout': 2500,
|
||||
'error': function()
|
||||
{
|
||||
//setHeaderState('disconnected');
|
||||
showTab('disconnect');
|
||||
},
|
||||
'success': function(d)
|
||||
{
|
||||
console.log('RECV', d);
|
||||
|
||||
if(d && d['authorize'] && d['authorize']['challenge'])
|
||||
{
|
||||
if (settings.credentials.authstring != d['authorize']['challenge'])
|
||||
{
|
||||
settings.credentials.authstring = d['authorize']['challenge'];
|
||||
console.log('need to reload settings with new auth string');
|
||||
loadSettings(callback);
|
||||
return;
|
||||
}else{
|
||||
errorstr = 'wrong credentials';
|
||||
}
|
||||
}else{
|
||||
settings.settings = $.extend(true, {
|
||||
"config":
|
||||
{
|
||||
"host": "",
|
||||
"limits": [],
|
||||
"name": "",
|
||||
"protocols": {},
|
||||
"status": "",
|
||||
"version": ""
|
||||
},
|
||||
"streams": {},
|
||||
"log": {},
|
||||
"statistics": {}
|
||||
}, d);
|
||||
|
||||
console.log('new (shinyness) object:', settings.settings);
|
||||
}
|
||||
|
||||
if(callback)
|
||||
{
|
||||
callback(errorstr);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function setHeaderState(state)
|
||||
{
|
||||
var text, cname, title;
|
||||
|
||||
switch(state)
|
||||
{
|
||||
case 'logingin': text = 'connecting...'; cname = 'loggingin'; title = 'connecting to ' + settings.server; break;
|
||||
case 'disconnected': text = 'disconnected'; cname = 'disconnected'; title = 'disconnected'; break;
|
||||
case 'connected': text = 'connected'; cname = 'connected'; title = 'connected to ' + settings.server; break;
|
||||
}
|
||||
|
||||
document.title = 'Server Manager - ' + title;
|
||||
|
||||
$('#header-connection').attr('class', cname);
|
||||
$('#header-connection').text(text);
|
||||
$('#header-host').text(settings.server.replace('http://', ''));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// show tab
|
||||
function showTab(name)
|
||||
{
|
||||
// clear page
|
||||
$('#page').html('');
|
||||
|
||||
switch(name)
|
||||
{
|
||||
case 'login':
|
||||
|
||||
//TODO fixme login creds
|
||||
var host = $('<input>').attr('type', 'text').attr('placeholder', 'http://localhost:4242');
|
||||
var user = $('<input>').attr('type', 'text').attr('placeholder', 'USERNAME').attr('value', 'testaccount');
|
||||
var pass = $('<input>').attr('type', 'password').attr('placeholder', 'PASSWORD').attr('value', 'pisvlek');
|
||||
var conn = $('<button>').click(function()
|
||||
{
|
||||
// get login info
|
||||
settings.credentials.username = user.val();
|
||||
settings.credentials.password = pass.val();
|
||||
settings.server = host.val() || host.attr('placeholder');
|
||||
|
||||
// try to login
|
||||
setHeaderState('logingin');
|
||||
|
||||
loadSettings(function(errorstr)
|
||||
{
|
||||
if(errorstr == '')
|
||||
{
|
||||
setHeaderState('connected');
|
||||
|
||||
$('#nav').css('visibility', 'visible');
|
||||
|
||||
showTab('overview');
|
||||
|
||||
// show overview as current tab - this only happens when logging out and then in
|
||||
$('#nav').children().each(function()
|
||||
{
|
||||
if($(this).text() != 'overview')
|
||||
{
|
||||
$(this).attr('class', '');
|
||||
}else{
|
||||
$(this).attr('class', 'selected');
|
||||
}
|
||||
});
|
||||
|
||||
console.log('logged in!');
|
||||
}else{
|
||||
setHeaderState('disconnected');
|
||||
$('#header-host').text('');
|
||||
console.log('error logging in: ' + errorstr);
|
||||
}
|
||||
});
|
||||
}).text('login');
|
||||
|
||||
$('#page').append(
|
||||
$('<div>').attr('id', 'login').append(host).append(user).append(pass).append(conn)
|
||||
);
|
||||
break;
|
||||
|
||||
|
||||
|
||||
|
||||
case 'overview':
|
||||
|
||||
$('#page').append($('<p>').text('TODO overview'));
|
||||
|
||||
break;
|
||||
|
||||
|
||||
|
||||
|
||||
case 'protocols':
|
||||
|
||||
$table = $('<table>');
|
||||
$table.html("<thead><th>Protocol</th><th>Port</th><th></th></thead>");
|
||||
$tbody = $('<tbody>');
|
||||
|
||||
var tr, protocol;
|
||||
|
||||
// remove old stuff
|
||||
$tbody.html('');
|
||||
|
||||
for(protocol in settings.settings.config.protocols)
|
||||
{
|
||||
tr = $('<tr>').attr('id', 'protocol-' + protocol);
|
||||
|
||||
tr.append( $('<td>').text( protocol ) );
|
||||
tr.append( $('<td>').text( settings.settings.config.protocols[protocol].port ) );
|
||||
|
||||
tr.append( $('<td>').attr('class', 'center').append( $('<button>').click(function()
|
||||
{
|
||||
var id = $(this).parent().parent().attr('id').replace('protocol-', '');
|
||||
delete settings.settings.config.protocols[protocol];
|
||||
$(this).parent().parent().remove();
|
||||
loadSettings();
|
||||
}).text('delete') ) );
|
||||
|
||||
$tbody.append(tr);
|
||||
}
|
||||
|
||||
|
||||
// add new limit!
|
||||
$nprot = $('<tr>').attr('class', 'outsidetable');
|
||||
// protocol select
|
||||
$pname = $('<select>').attr('id', 'new-protocol-name');
|
||||
$pname.append( $('<option>').attr('value', 'HTTP').text('HTTP') );
|
||||
$pname.append( $('<option>').attr('value', 'RTMP').text('RTMP') );
|
||||
|
||||
$nprot.append( $('<td>').append($pname) );
|
||||
// val
|
||||
$nprot.append( $('<td>').append( $('<input>').attr('type', 'number').attr('id', 'new-protocol-val') ) );
|
||||
|
||||
$nprot.append(
|
||||
$('<td>').attr('class', 'center').append(
|
||||
$('<button>').click(function()
|
||||
{
|
||||
settings.settings.config.protocols[$('#new-protocol-name :selected').val()] =
|
||||
{
|
||||
port: $('#new-protocol-val').val()
|
||||
};
|
||||
|
||||
loadSettings(function()
|
||||
{
|
||||
showTab('protocols');
|
||||
});
|
||||
|
||||
}).text('add new')
|
||||
)
|
||||
);
|
||||
|
||||
$tbody.append($nprot);
|
||||
$table.append($tbody);
|
||||
$('#page').append($table);
|
||||
|
||||
break;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
case 'streams':
|
||||
|
||||
$streamselector = $('<select>').attr('id', 'streams-selector');
|
||||
|
||||
$streamselector.append( $('<option>').attr('disabled', 'disabled').text('Select a stream...') );
|
||||
|
||||
for(stream in settings.settings.streams)
|
||||
{
|
||||
$streamselector.append( $('<option>').attr('value', stream).text(settings.settings.streams[stream].name) );
|
||||
}
|
||||
|
||||
$streamselector.change(function()
|
||||
{
|
||||
var sname = $('#streams-selector :selected').val();
|
||||
var stream = settings.settings.streams[sname];
|
||||
|
||||
$('#page').append( $('<p>').text('TODO stream settings: ' + sname + ' / ' + stream) );
|
||||
/* // TODO
|
||||
$('#page').append( $('<label>').attr('for', 'stream-name').text('Name').append( $('<input>').attr('id', 'stream-name').attr('value', stream.name) ) );
|
||||
$('#page').append( $('<label>').attr('for', 'stream-group').text('Group').append( $('<input>').attr('id', 'stream-group').attr('value', stream.group) ) );
|
||||
|
||||
$('#page').append(
|
||||
$('<div>').text('Channel').append(
|
||||
$('<label>').attr('for', 'stream-channel-url').text('URL').append( $('<input>').attr('id', 'stream-channel-url').attr('value', stream.channel.URL) )
|
||||
).append(
|
||||
$('<label>').attr('for', 'stream-channel-account').text('Account').append( $('<input>').attr('id', 'stream-channel-account').attr('value', stream.channel.account) )
|
||||
)
|
||||
);
|
||||
*/
|
||||
|
||||
});
|
||||
|
||||
$('#page').append( $streamselector );
|
||||
|
||||
break;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
case 'limits':
|
||||
$table = $('<table>');
|
||||
$table.html("<thead><th>Type</th><th>Hard/soft limit</th><th>Value</th><th></th></thead>");
|
||||
$tbody = $('<tbody>');
|
||||
|
||||
var i, tr, limit,
|
||||
len = settings.settings.config.limits.length;
|
||||
|
||||
// remove old stuff
|
||||
$tbody.html('');
|
||||
|
||||
for(i = 0; i < len; i++)
|
||||
{
|
||||
tr = $('<tr>').attr('id', 'limits-' + i);
|
||||
limit = settings.settings.config.limits[i];
|
||||
|
||||
tr.append( $('<td>').text( shortToLongLimit(limit.name) ) );
|
||||
tr.append( $('<td>').text( limit.type ) );
|
||||
tr.append( $('<td>').text( limit.val ) );
|
||||
|
||||
tr.append( $('<td>').attr('class', 'center').append( $('<button>').click(function()
|
||||
{
|
||||
var id = $(this).parent().parent().attr('id').replace('limits-', '');
|
||||
settings.settings.config.limits.splice(id, 1);
|
||||
$(this).parent().parent().remove();
|
||||
|
||||
loadSettings();
|
||||
}).text('delete') ) );
|
||||
|
||||
$tbody.append(tr);
|
||||
}
|
||||
|
||||
// add new limit!
|
||||
$nltr = $('<tr>').attr('class', 'outsidetable');
|
||||
|
||||
// type select
|
||||
$ltype = $('<select>').attr('id', 'new-limit-type');
|
||||
for(i = 0; i < ltypes.length; i++)
|
||||
{
|
||||
$ltype.append( $('<option>').attr('value', ltypes[i][0]).text(ltypes[i][1]) );
|
||||
}
|
||||
$nltr.append( $('<td>').append( $ltype ) );
|
||||
// hard/soft
|
||||
$nltr.append( $('<td>').append( $('<select>').attr('id', 'new-limit-hs').append( $('<option>').attr('value', 'hard').text('Hard limit') ).append( $('<option>').attr('value', 'soft').text('Soft limit') ) ) );
|
||||
// val
|
||||
$nltr.append( $('<td>').append( $('<input>').attr('type', 'text').attr('id', 'new-limit-val') ) );
|
||||
|
||||
$nltr.append(
|
||||
$('<td>').attr('class', 'center').append(
|
||||
$('<button>').click(function()
|
||||
{
|
||||
settings.settings.config.limits.push(
|
||||
{
|
||||
name: $('#new-limit-type :selected').val(),
|
||||
type: $('#new-limit-hs :selected').val(),
|
||||
val: $('#new-limit-val').val()
|
||||
});
|
||||
|
||||
loadSettings(function()
|
||||
{
|
||||
showTab('limits');
|
||||
});
|
||||
|
||||
}).text('add new')
|
||||
)
|
||||
);
|
||||
$tbody.append($nltr);
|
||||
|
||||
$table.append($tbody);
|
||||
$('#page').append($table);
|
||||
|
||||
break;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
case 'logs':
|
||||
$table = $('<table>');
|
||||
$table.html("<thead><th>Date</th><th>Type</th><th>Message</th></thead>");
|
||||
$tbody = $('<tbody>');
|
||||
|
||||
var i, cur, $tr,
|
||||
logs = settings.settings.log,
|
||||
len = logs.length;
|
||||
|
||||
if(len >= 2 && settings.settings.log[0][0] < settings.settings.log[len - 1][0])
|
||||
{
|
||||
logs.reverse();
|
||||
}
|
||||
|
||||
$tbody.html('');
|
||||
|
||||
for(i = 0; i < len; i++)
|
||||
{
|
||||
cur = settings.settings.log[i];
|
||||
|
||||
$tr = $('<tr>').append(
|
||||
$('<td>').text(formatDate(cur[0]))
|
||||
).append(
|
||||
$('<td>').text(cur[1])
|
||||
).append(
|
||||
$('<td>').text(cur[2])
|
||||
);
|
||||
|
||||
$tbody.append($tr);
|
||||
}
|
||||
|
||||
$table.append($tbody);
|
||||
$('#page').append($table);
|
||||
|
||||
$('#page').append(
|
||||
$('<button>').attr('class', 'floatright').click(function()
|
||||
{
|
||||
settings.settings.clearstatlogs = 1;
|
||||
loadSettings(function()
|
||||
{
|
||||
showTab('logs');
|
||||
});
|
||||
}).text('Purge logs')
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
case 'disconnect':
|
||||
showTab('login');
|
||||
setHeaderState('disconnected');
|
||||
$('#nav').css('visibility', 'hidden');
|
||||
|
||||
settings =
|
||||
{
|
||||
server: '',
|
||||
credentials:
|
||||
{
|
||||
username: "",
|
||||
password: "",
|
||||
authstring: ""
|
||||
},
|
||||
settings: {}
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</html>
|
161
server.html
161
server.html
|
@ -25,7 +25,7 @@
|
|||
h2
|
||||
{
|
||||
margin: 0 0 10px 0;
|
||||
background-color: #000;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
font-size: 1.2em;
|
||||
padding: 5px;
|
||||
|
@ -52,13 +52,13 @@
|
|||
width: 25%;
|
||||
}
|
||||
|
||||
#stream-limit-table
|
||||
#stream-limit-table, #logs table
|
||||
{
|
||||
width: 100%;
|
||||
margin: 10px 0 20px 15px;
|
||||
}
|
||||
|
||||
#stream-limit-table th
|
||||
#stream-limit-table th, #logs th, #limits-table th
|
||||
{
|
||||
text-align: left;
|
||||
}
|
||||
|
@ -69,12 +69,6 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
#limits-table th
|
||||
{
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
#limits p, #protocols p
|
||||
{
|
||||
margin: 20px 0 5px 15px;
|
||||
|
@ -170,6 +164,16 @@
|
|||
margin: 10px 0 5px 15px;
|
||||
}
|
||||
|
||||
.errorlogin
|
||||
{
|
||||
color: #c33;
|
||||
}
|
||||
|
||||
.correctlogin
|
||||
{
|
||||
color: #393;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
|
@ -189,17 +193,17 @@
|
|||
|
||||
<label for='username'>
|
||||
Username:
|
||||
<input type='text' id='username' value='test' />
|
||||
<input type='text' id='username' value='testaccount' />
|
||||
</label>
|
||||
|
||||
<label for='password'>
|
||||
Password:
|
||||
<input type='password' id='password' value='test' />
|
||||
<input type='password' id='password' value='pisvlek' />
|
||||
</label>
|
||||
|
||||
<label for='server'>
|
||||
Server:
|
||||
<input type='text' id='server' value='http://localhost:7342' />
|
||||
<input type='text' id='server' value='http://localhost:4242' />
|
||||
</label>
|
||||
|
||||
<button id='login'>login</button>
|
||||
|
@ -212,11 +216,11 @@
|
|||
<h2>Config</h2>
|
||||
|
||||
<label for='config-host'>
|
||||
Host: <input type='text' id='config-host' value='' /> <span id='save-config-host'>(save)</span>
|
||||
Host: <input type='text' id='config-host' value='' /> <button id='save-config-host'>save</button>
|
||||
</label>
|
||||
|
||||
<label for='config-name'>
|
||||
Name: <input type='text' id='config-name' value='' /> <span id='save-config-name'>(save)</span>
|
||||
Name: <input type='text' id='config-name' value='' /> <button id='save-config-name'>save</button>
|
||||
</label>
|
||||
|
||||
<span id='config-status'>Status: </span> <button id='config-status-disable'>DISABLE</button>
|
||||
|
@ -307,6 +311,24 @@
|
|||
</div>
|
||||
|
||||
|
||||
<div id='logs'>
|
||||
|
||||
<h2>Logs</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Date</th>
|
||||
<th>Type</th>
|
||||
<th>Message</th>
|
||||
</thead>
|
||||
<tbody id='log-list'>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
|
@ -347,7 +369,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
return 'error';
|
||||
return '[error] name "' + name + '" has no entry in rep (@350)!';
|
||||
}
|
||||
|
||||
|
||||
|
@ -392,7 +414,22 @@
|
|||
}
|
||||
}else{
|
||||
//console.log('no challenge - we\'re logged in! = OK');
|
||||
settings.settings = d;
|
||||
settings.settings = $.extend(true, {
|
||||
"config":
|
||||
{
|
||||
"host": "",
|
||||
"limits": [],
|
||||
"name": "",
|
||||
"protocols": {},
|
||||
"status": "",
|
||||
"version": ""
|
||||
},
|
||||
"streams": {},
|
||||
"log": {},
|
||||
"statistics": {}
|
||||
}, d);
|
||||
|
||||
console.log('new shinyness object:', settings.settings);
|
||||
}
|
||||
|
||||
if(callback)
|
||||
|
@ -421,20 +458,30 @@
|
|||
settings.server = $('#server').val();
|
||||
|
||||
$('#status').text('logging in...');
|
||||
$('#status').attr('class', '');
|
||||
|
||||
loadSettings(function(errorstr)
|
||||
{
|
||||
if(errorstr == '')
|
||||
{
|
||||
$('#status').text('logged in');
|
||||
$('#status').attr('class', 'correctlogin');
|
||||
|
||||
$('div').show();
|
||||
|
||||
fillHTML();
|
||||
//console.log('logged in!');
|
||||
/*
|
||||
setInterval(function()
|
||||
{
|
||||
loadSettings(fillLogs);
|
||||
}, 5000);
|
||||
*/
|
||||
console.log('logged in!');
|
||||
}else{
|
||||
$('#status').text('disconnected - ' + errorstr);
|
||||
$('div').hide();
|
||||
$('#status').attr('class', 'errorlogin');
|
||||
$('div:not(#current-status):not(#connect)').hide();
|
||||
//$('div').hide();
|
||||
//console.log('error logging in: ' + errorstr);
|
||||
}
|
||||
});
|
||||
|
@ -501,6 +548,9 @@
|
|||
|
||||
// streams
|
||||
fillStreams();
|
||||
|
||||
// log
|
||||
fillLogs();
|
||||
}
|
||||
|
||||
|
||||
|
@ -534,7 +584,7 @@
|
|||
$(this).parent().remove();
|
||||
|
||||
loadSettings(fillLimits);
|
||||
}).text('(delete)');
|
||||
}).html('<button>delete</button>');
|
||||
|
||||
cur.append($('<td>').text(limitShortToLong(lim.name)));
|
||||
cur.append($('<td>').text(lim.type));
|
||||
|
@ -559,7 +609,7 @@
|
|||
|
||||
li.text(protocol + ' on port ' + settings.settings.config.protocols[protocol].port);
|
||||
|
||||
li.append($('<span>').click(function()
|
||||
li.append($('<button>').click(function()
|
||||
{
|
||||
var prot = $(this).parent().attr('id').replace('protocol-', '');
|
||||
console.log(prot);
|
||||
|
@ -569,7 +619,7 @@
|
|||
$(this).parent().remove();
|
||||
|
||||
loadSettings(fillProtocols);
|
||||
}).text('<remove>'));
|
||||
}).text('remove'));
|
||||
|
||||
$('#protocol-list').append(li);
|
||||
}
|
||||
|
@ -584,7 +634,7 @@
|
|||
|
||||
for(stream in settings.settings.streams)
|
||||
{
|
||||
var d = $('<span>').text(' <delete>').attr('id', 'stream-delete-' + stream).click(function()
|
||||
var d = $('<button>').text('delete').attr('id', 'stream-delete-' + stream).click(function()
|
||||
{
|
||||
var id = $(this).attr('id').replace('stream-delete-', '');
|
||||
console.log('delete this stream', id);
|
||||
|
@ -598,10 +648,10 @@
|
|||
|
||||
cur = settings.settings.streams[stream];
|
||||
|
||||
li.append( $('<div>').text('Name:').append(
|
||||
li.append( $('<div>').text('Name: ').append(
|
||||
$('<input>').attr('type', 'text').attr('id', 'stream-name-' + stream).attr('value', cur.name)
|
||||
).append(
|
||||
$('<span>').text('<save>').click(function(x)
|
||||
$('<button>').text('save').click(function(x)
|
||||
{
|
||||
return function()
|
||||
{
|
||||
|
@ -615,7 +665,23 @@
|
|||
|
||||
|
||||
|
||||
li.append($('<div>').text('Group: ' + cur.group));
|
||||
//li.append($('<div>').text('Group: ' + cur.group));
|
||||
|
||||
li.append( $('<div>').text('Group: ').append(
|
||||
$('<input>').attr('type', 'text').attr('id', 'stream-group-' + stream).attr('value', cur.group)
|
||||
).append(
|
||||
$('<button>').text('save').click(function(x)
|
||||
{
|
||||
return function()
|
||||
{
|
||||
var group = $('#stream-group-' + x).val();
|
||||
|
||||
settings.settings.streams[x].group = group;
|
||||
loadSettings(fillStreams);
|
||||
}
|
||||
}(stream))
|
||||
) );
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -632,7 +698,7 @@
|
|||
$('<input>').attr('type', 'text').attr('id', 'stream-channel-account-' + stream).attr('value', cur.channel.account)
|
||||
)
|
||||
);
|
||||
channel.append($('<span>').attr('class', 'mleft25').text('(save)').click(function()
|
||||
channel.append($('<button>').attr('class', 'mleft25').text('save').click(function()
|
||||
{
|
||||
var cname = $(this).parent().attr('id').replace('stream-channel-', ''),
|
||||
url = $('#stream-channel-url-' + cname).val(),
|
||||
|
@ -665,7 +731,9 @@
|
|||
)
|
||||
).append(tbody);
|
||||
|
||||
for(var i = 0; i < cur.limits.length; i++)
|
||||
var cll = cur.limits ? cur.limits.length : 0;
|
||||
|
||||
for(var i = 0; i < cll; i++)
|
||||
{
|
||||
var climit = $('<tr>').attr('id', 'stream-limit-' + stream + '-' + i);
|
||||
|
||||
|
@ -680,7 +748,7 @@
|
|||
|
||||
loadSettings(fillStreams);
|
||||
|
||||
}).text('(delete)');
|
||||
}).html('<button>delete</button>');
|
||||
|
||||
climit.append($('<td>').text(limitShortToLong(cur.limits[i].name)));
|
||||
climit.append($('<td>').text(cur.limits[i].type));
|
||||
|
@ -755,7 +823,7 @@
|
|||
spreset.append( $('<div>').text('Command: ').append(
|
||||
$('<input>').attr('id', 'stream-preset-cmd-' + stream).attr('type', 'text').val(settings.settings.streams[stream].preset.cmd)
|
||||
).append(
|
||||
$('<span>').text('<save>').click(function()
|
||||
$('<button>').text('save').click(function()
|
||||
{
|
||||
var stream = $(this).parent().parent().attr('id').replace('stream-preset-', '');
|
||||
var cmd = $('#stream-preset-cmd-' + stream).val();
|
||||
|
@ -770,7 +838,7 @@
|
|||
spreset.append( $('<div>').text('Description: ').append(
|
||||
$('<input>').attr('id', 'stream-preset-desc-' + stream).attr('type', 'text').val(settings.settings.streams[stream].preset.desc)
|
||||
).append(
|
||||
$('<span>').text('<save>').click(function()
|
||||
$('<button>').text('save').click(function()
|
||||
{
|
||||
var stream = $(this).parent().parent().attr('id').replace('stream-preset-', '');
|
||||
var desc = $('#stream-preset-desc-' + stream).val();
|
||||
|
@ -785,7 +853,7 @@
|
|||
spreset.append( $('<div>').text('Name: ').append(
|
||||
$('<input>').attr('id', 'stream-preset-name-' + stream).attr('type', 'text').val(settings.settings.streams[stream].preset.name)
|
||||
).append(
|
||||
$('<span>').text('<save>').click(function()
|
||||
$('<button>').text('save').click(function()
|
||||
{
|
||||
var stream = $(this).parent().parent().attr('id').replace('stream-preset-', '');
|
||||
var name = $('#stream-preset-name-' + stream).val();
|
||||
|
@ -805,7 +873,7 @@
|
|||
|
||||
// new stream
|
||||
var nspreset = $('<div>').attr('id', 'new-stream').text('New stream').append(
|
||||
$('<div>').text('ID:').append(
|
||||
$('<div>').text('Name: ').append(
|
||||
$('<input>').attr('id', 'new-stream-name').attr('type', 'text')
|
||||
).append(
|
||||
$('<button>').text('create').click(function()
|
||||
|
@ -818,7 +886,7 @@
|
|||
"channel": {"URL": "", "account": ""},
|
||||
"group": "",
|
||||
"limits": [],
|
||||
"name": "",
|
||||
"name": $('#new-stream-name').val(),
|
||||
"preset": {"cmd": "", "name": "", "desc": ""},
|
||||
"status": ""
|
||||
}
|
||||
|
@ -834,6 +902,33 @@
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function fillLogs()
|
||||
{
|
||||
var i, cur, tr,
|
||||
tbody = $('#log-list'),
|
||||
logs = settings.settings.log.reverse(),
|
||||
len = logs.length;
|
||||
|
||||
tbody.html('');
|
||||
|
||||
for(i = 0; i < len; i++)
|
||||
{
|
||||
cur = settings.settings.log[i];
|
||||
|
||||
tr = $('<tr>').append(
|
||||
$('<td>').text(new Date(cur[0] * 1000).toUTCString())
|
||||
).append(
|
||||
$('<td>').text(cur[1])
|
||||
).append(
|
||||
$('<td>').text(cur[2])
|
||||
);
|
||||
|
||||
tbody.append(tr);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
|
23
tools/DTSC_Analyser/Makefile
Normal file
23
tools/DTSC_Analyser/Makefile
Normal file
|
@ -0,0 +1,23 @@
|
|||
SRC = main.cpp ../../util/dtsc.cpp
|
||||
OBJ = $(SRC:.cpp=.o)
|
||||
OUT = DTSC_Info
|
||||
INCLUDES =
|
||||
DEBUG = 4
|
||||
OPTIMIZE = -g
|
||||
CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG)
|
||||
CC = $(CROSS)g++
|
||||
LD = $(CROSS)ld
|
||||
AR = $(CROSS)ar
|
||||
LIBS =
|
||||
.SUFFIXES: .cpp
|
||||
.PHONY: clean default
|
||||
default: $(OUT)
|
||||
.cpp.o:
|
||||
$(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@
|
||||
$(OUT): $(OBJ)
|
||||
$(CC) $(LIBS) -o $(OUT) $(OBJ)
|
||||
clean:
|
||||
rm -rf $(OBJ) $(OUT) Makefile.bak *~
|
||||
install: $(OUT)
|
||||
cp -f ./$(OUT) /usr/bin/
|
||||
|
38
tools/DTSC_Analyser/main.cpp
Normal file
38
tools/DTSC_Analyser/main.cpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
/// \file DTSC_Analyser/main.cpp
|
||||
/// Contains the code for the DTSC Analysing tool.
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include "../../util/dtsc.h" //DTSC support
|
||||
|
||||
/// Reads DTSC from stdin and outputs human-readable information to stderr.
|
||||
int main() {
|
||||
DTSC::Stream Strm;
|
||||
|
||||
std::string inBuffer;
|
||||
char charBuffer[1024*10];
|
||||
unsigned int charCount;
|
||||
bool doneheader = false;
|
||||
|
||||
while(std::cin.good()){
|
||||
//invalidate the current buffer
|
||||
std::cin.read(charBuffer, 1024*10);
|
||||
charCount = std::cin.gcount();
|
||||
inBuffer.append(charBuffer, charCount);
|
||||
if (Strm.parsePacket(inBuffer)){
|
||||
if (!doneheader){
|
||||
doneheader = true;
|
||||
Strm.metadata.Print();
|
||||
}
|
||||
Strm.getPacket().Print();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
23
tools/FLV2DTSC/Makefile
Normal file
23
tools/FLV2DTSC/Makefile
Normal file
|
@ -0,0 +1,23 @@
|
|||
SRC = main.cpp ../../util/flv_tag.cpp ../../util/dtsc.cpp ../../util/amf.cpp ../../util/socket.cpp
|
||||
OBJ = $(SRC:.cpp=.o)
|
||||
OUT = DDV_FLV2DTSC
|
||||
INCLUDES =
|
||||
DEBUG = 4
|
||||
OPTIMIZE = -g
|
||||
CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG)
|
||||
CC = $(CROSS)g++
|
||||
LD = $(CROSS)ld
|
||||
AR = $(CROSS)ar
|
||||
LIBS =
|
||||
.SUFFIXES: .cpp
|
||||
.PHONY: clean default
|
||||
default: $(OUT)
|
||||
.cpp.o:
|
||||
$(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@
|
||||
$(OUT): $(OBJ)
|
||||
$(CC) $(LIBS) -o $(OUT) $(OBJ)
|
||||
clean:
|
||||
rm -rf $(OBJ) $(OUT) Makefile.bak *~
|
||||
install: $(OUT)
|
||||
cp -f ./$(OUT) /usr/bin/
|
||||
|
258
tools/FLV2DTSC/main.cpp
Normal file
258
tools/FLV2DTSC/main.cpp
Normal file
|
@ -0,0 +1,258 @@
|
|||
/// \file FLV2DTSC/main.cpp
|
||||
/// Contains the code that will transform any valid FLV input into valid DTSC.
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include "../../util/flv_tag.h" //FLV support
|
||||
#include "../../util/dtsc.h" //DTSC support
|
||||
#include "../../util/amf.h" //AMF support
|
||||
|
||||
// String onMetaData
|
||||
// ECMA Array
|
||||
// Bool hasVideo 1
|
||||
// Number videocodecid 4 (2 = H263, 4 = VP6, 7 = H264)
|
||||
// Number width 320
|
||||
// Number height 240
|
||||
// Number framerate 23.976 (/ 1000)
|
||||
// Number videodatarate 500.349 (kbps)
|
||||
// Bool hasAudio 1
|
||||
// Bool stereo 1
|
||||
// Number audiodelay 0
|
||||
// Number audiosamplerate 11025
|
||||
// Number audiosamplesize 16
|
||||
// Number audiocodecid 2 (2 = MP3, 10 = AAC)
|
||||
// Number audiodatarate 64.3269 (kbps)
|
||||
|
||||
|
||||
/// Holds all code that converts filetypes to DTSC.
|
||||
namespace Converters{
|
||||
|
||||
/// Inserts std::string type metadata into the passed DTMI object.
|
||||
/// \arg meta The DTMI object to put the metadata into.
|
||||
/// \arg cat Metadata category to insert into.
|
||||
/// \arg elem Element name to put into the category.
|
||||
/// \arg val Value to put into the element name.
|
||||
void Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, std::string val){
|
||||
if (meta.getContentP(cat) == 0){meta.addContent(DTSC::DTMI(cat));}
|
||||
meta.getContentP(cat)->addContent(DTSC::DTMI(elem, val));
|
||||
std::cerr << "Metadata " << cat << "." << elem << " = " << val << std::endl;
|
||||
}
|
||||
|
||||
/// Inserts uint64_t type metadata into the passed DTMI object.
|
||||
/// \arg meta The DTMI object to put the metadata into.
|
||||
/// \arg cat Metadata category to insert into.
|
||||
/// \arg elem Element name to put into the category.
|
||||
/// \arg val Value to put into the element name.
|
||||
void Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, uint64_t val){
|
||||
if (meta.getContentP(cat) == 0){meta.addContent(DTSC::DTMI(cat));}
|
||||
meta.getContentP(cat)->addContent(DTSC::DTMI(elem, val));
|
||||
std::cerr << "Metadata " << cat << "." << elem << " = " << val << std::endl;
|
||||
}
|
||||
|
||||
/// Returns true if the named category and elementname are available in the metadata.
|
||||
/// \arg meta The DTMI object to check.
|
||||
/// \arg cat Metadata category to check.
|
||||
/// \arg elem Element name to check.
|
||||
bool Meta_Has(DTSC::DTMI & meta, std::string cat, std::string elem){
|
||||
if (meta.getContentP(cat) == 0){return false;}
|
||||
if (meta.getContentP(cat)->getContentP(elem) == 0){return false;}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Reads FLV from STDIN, outputs DTSC to STDOUT.
|
||||
int FLV2DTSC() {
|
||||
FLV::Tag FLV_in; // Temporary storage for incoming FLV data.
|
||||
AMF::Object meta_in; // Temporary storage for incoming metadata.
|
||||
DTSC::DTMI meta_out; // Storage for outgoing DTMI header data.
|
||||
DTSC::DTMI pack_out; // Storage for outgoing DTMI data.
|
||||
std::stringstream prebuffer; // Temporary buffer before sending real data
|
||||
bool sending = false;
|
||||
unsigned int counter = 0;
|
||||
|
||||
while (!feof(stdin)){
|
||||
if (FLV_in.FileLoader(stdin)){
|
||||
if (!sending){
|
||||
counter++;
|
||||
if (counter > 10){
|
||||
sending = true;
|
||||
meta_out.Pack(true);
|
||||
meta_out.packed.replace(0, 4, DTSC::Magic_Header);
|
||||
std::cout << meta_out.packed;
|
||||
std::cout << prebuffer.rdbuf();
|
||||
prebuffer.str("");
|
||||
std::cerr << "Buffer done, starting real-time output..." << std::endl;
|
||||
}
|
||||
}
|
||||
if (FLV_in.data[0] == 0x12){
|
||||
meta_in = AMF::parse((unsigned char*)FLV_in.data+11, FLV_in.len-15);
|
||||
if (meta_in.getContentP(0) && (meta_in.getContentP(0)->StrValue() == "onMetaData") && meta_in.getContentP(1)){
|
||||
AMF::Object * tmp = meta_in.getContentP(1);
|
||||
if (tmp->getContentP("videocodecid")){
|
||||
switch ((unsigned int)tmp->getContentP("videocodecid")->NumValue()){
|
||||
case 2: Meta_Put(meta_out, "video", "codec", "H263"); break;
|
||||
case 4: Meta_Put(meta_out, "video", "codec", "VP6"); break;
|
||||
case 7: Meta_Put(meta_out, "video", "codec", "H264"); break;
|
||||
default: Meta_Put(meta_out, "video", "codec", "?"); break;
|
||||
}
|
||||
}
|
||||
if (tmp->getContentP("audiocodecid")){
|
||||
switch ((unsigned int)tmp->getContentP("audiocodecid")->NumValue()){
|
||||
case 2: Meta_Put(meta_out, "audio", "codec", "MP3"); break;
|
||||
case 10: Meta_Put(meta_out, "audio", "codec", "AAC"); break;
|
||||
default: Meta_Put(meta_out, "audio", "codec", "?"); break;
|
||||
}
|
||||
}
|
||||
if (tmp->getContentP("width")){
|
||||
Meta_Put(meta_out, "video", "width", tmp->getContentP("width")->NumValue());
|
||||
}
|
||||
if (tmp->getContentP("height")){
|
||||
Meta_Put(meta_out, "video", "height", tmp->getContentP("height")->NumValue());
|
||||
}
|
||||
if (tmp->getContentP("framerate")){
|
||||
Meta_Put(meta_out, "video", "fpks", tmp->getContentP("framerate")->NumValue()*1000);
|
||||
}
|
||||
if (tmp->getContentP("videodatarate")){
|
||||
Meta_Put(meta_out, "video", "bps", (tmp->getContentP("videodatarate")->NumValue()*1024)/8);
|
||||
}
|
||||
if (tmp->getContentP("audiodatarate")){
|
||||
Meta_Put(meta_out, "audio", "bps", (tmp->getContentP("audiodatarate")->NumValue()*1024)/8);
|
||||
}
|
||||
if (tmp->getContentP("audiosamplerate")){
|
||||
Meta_Put(meta_out, "audio", "rate", tmp->getContentP("audiosamplerate")->NumValue());
|
||||
}
|
||||
if (tmp->getContentP("audiosamplesize")){
|
||||
Meta_Put(meta_out, "audio", "size", tmp->getContentP("audiosamplesize")->NumValue());
|
||||
}
|
||||
if (tmp->getContentP("stereo")){
|
||||
if (tmp->getContentP("stereo")->NumValue() == 1){
|
||||
Meta_Put(meta_out, "audio", "channels", 2);
|
||||
}else{
|
||||
Meta_Put(meta_out, "audio", "channels", 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (FLV_in.data[0] == 0x08){
|
||||
char audiodata = FLV_in.data[11];
|
||||
if (FLV_in.needsInitData() && FLV_in.isInitData()){
|
||||
if ((audiodata & 0xF0) == 0xA0){
|
||||
Meta_Put(meta_out, "audio", "init", std::string((char*)FLV_in.data+13, (size_t)FLV_in.len-17));
|
||||
}else{
|
||||
Meta_Put(meta_out, "audio", "init", std::string((char*)FLV_in.data+12, (size_t)FLV_in.len-16));
|
||||
}
|
||||
continue;//skip rest of parsing, get next tag.
|
||||
}
|
||||
pack_out = DTSC::DTMI("audio", DTSC::DTMI_ROOT);
|
||||
pack_out.addContent(DTSC::DTMI("datatype", "audio"));
|
||||
pack_out.addContent(DTSC::DTMI("time", FLV_in.tagTime()));
|
||||
if (!Meta_Has(meta_out, "audio", "codec")){
|
||||
switch (audiodata & 0xF0){
|
||||
case 0x20: Meta_Put(meta_out, "audio", "codec", "MP3"); break;
|
||||
case 0xA0: Meta_Put(meta_out, "audio", "codec", "AAC"); break;
|
||||
default: Meta_Put(meta_out, "audio", "codec", "?"); break;
|
||||
}
|
||||
}
|
||||
if (!Meta_Has(meta_out, "audio", "rate")){
|
||||
switch (audiodata & 0x0C){
|
||||
case 0x0: Meta_Put(meta_out, "audio", "rate", 5512); break;
|
||||
case 0x4: Meta_Put(meta_out, "audio", "rate", 11025); break;
|
||||
case 0x8: Meta_Put(meta_out, "audio", "rate", 22050); break;
|
||||
case 0xC: Meta_Put(meta_out, "audio", "rate", 44100); break;
|
||||
}
|
||||
}
|
||||
if (!Meta_Has(meta_out, "audio", "size")){
|
||||
switch (audiodata & 0x02){
|
||||
case 0x0: Meta_Put(meta_out, "audio", "size", 8); break;
|
||||
case 0x2: Meta_Put(meta_out, "audio", "size", 16); break;
|
||||
}
|
||||
}
|
||||
if (!Meta_Has(meta_out, "audio", "channels")){
|
||||
switch (audiodata & 0x01){
|
||||
case 0x0: Meta_Put(meta_out, "audio", "channels", 1); break;
|
||||
case 0x1: Meta_Put(meta_out, "audio", "channels", 2); break;
|
||||
}
|
||||
}
|
||||
if ((audiodata & 0xF0) == 0xA0){
|
||||
pack_out.addContent(DTSC::DTMI("data", std::string((char*)FLV_in.data+13, (size_t)FLV_in.len-17)));
|
||||
}else{
|
||||
pack_out.addContent(DTSC::DTMI("data", std::string((char*)FLV_in.data+12, (size_t)FLV_in.len-16)));
|
||||
}
|
||||
if (sending){
|
||||
std::cout << pack_out.Pack(true);
|
||||
}else{
|
||||
prebuffer << pack_out.Pack(true);
|
||||
}
|
||||
}
|
||||
if (FLV_in.data[0] == 0x09){
|
||||
char videodata = FLV_in.data[11];
|
||||
if (FLV_in.needsInitData() && FLV_in.isInitData()){
|
||||
if ((videodata & 0x0F) == 7){
|
||||
Meta_Put(meta_out, "video", "init", std::string((char*)FLV_in.data+16, (size_t)FLV_in.len-20));
|
||||
}else{
|
||||
Meta_Put(meta_out, "video", "init", std::string((char*)FLV_in.data+12, (size_t)FLV_in.len-16));
|
||||
}
|
||||
continue;//skip rest of parsing, get next tag.
|
||||
}
|
||||
if (!Meta_Has(meta_out, "video", "codec")){
|
||||
switch (videodata & 0x0F){
|
||||
case 2: Meta_Put(meta_out, "video", "codec", "H263"); break;
|
||||
case 4: Meta_Put(meta_out, "video", "codec", "VP6"); break;
|
||||
case 7: Meta_Put(meta_out, "video", "codec", "H264"); break;
|
||||
default: Meta_Put(meta_out, "video", "codec", "?"); break;
|
||||
}
|
||||
}
|
||||
pack_out = DTSC::DTMI("video", DTSC::DTMI_ROOT);
|
||||
pack_out.addContent(DTSC::DTMI("datatype", "video"));
|
||||
switch (videodata & 0xF0){
|
||||
case 0x10: pack_out.addContent(DTSC::DTMI("keyframe", 1)); break;
|
||||
case 0x20: pack_out.addContent(DTSC::DTMI("interframe", 1)); break;
|
||||
case 0x30: pack_out.addContent(DTSC::DTMI("disposableframe", 1)); break;
|
||||
case 0x40: pack_out.addContent(DTSC::DTMI("keyframe", 1)); break;
|
||||
case 0x50: continue; break;//the video info byte we just throw away - useless to us...
|
||||
}
|
||||
if ((videodata & 0x0F) == 7){
|
||||
switch (FLV_in.data[12]){
|
||||
case 1: pack_out.addContent(DTSC::DTMI("nalu", 1)); break;
|
||||
case 2: pack_out.addContent(DTSC::DTMI("nalu_end", 1)); break;
|
||||
}
|
||||
int offset = (FLV_in.data[13] << 16) + (FLV_in.data[14] << 8) + FLV_in.data[15];
|
||||
offset = (offset << 8) >> 8;
|
||||
pack_out.addContent(DTSC::DTMI("offset", offset));
|
||||
}
|
||||
pack_out.addContent(DTSC::DTMI("time", FLV_in.tagTime()));
|
||||
pack_out.addContent(DTSC::DTMI("data", std::string((char*)FLV_in.data+12, (size_t)FLV_in.len-16)));
|
||||
if (sending){
|
||||
std::cout << pack_out.Pack(true);
|
||||
}else{
|
||||
prebuffer << pack_out.Pack(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the FLV input is very short, do output it correctly...
|
||||
if (!sending){
|
||||
std::cerr << "EOF - outputting buffer..." << std::endl;
|
||||
meta_out.Pack(true);
|
||||
meta_out.packed.replace(0, 4, DTSC::Magic_Header);
|
||||
std::cout << meta_out.packed;
|
||||
std::cout << prebuffer.rdbuf();
|
||||
}
|
||||
std::cerr << "Done!" << std::endl;
|
||||
|
||||
return 0;
|
||||
}//FLV2DTSC
|
||||
|
||||
};//Buffer namespace
|
||||
|
||||
/// Entry point for FLV2DTSC, simply calls Converters::FLV2DTSC().
|
||||
int main(){
|
||||
return Converters::FLV2DTSC();
|
||||
}//main
|
248
util/dtmi.cpp
248
util/dtmi.cpp
|
@ -1,248 +0,0 @@
|
|||
/// \file dtmi.cpp
|
||||
/// Holds all code for DDVTECH MediaInfo parsing/generation.
|
||||
|
||||
#include "dtmi.h"
|
||||
#include <cstdio> //needed for stderr only
|
||||
|
||||
/// Returns the std::string Indice for the current object, if available.
|
||||
/// Returns an empty string if no indice exists.
|
||||
std::string DTSC::DTMI::Indice(){return myIndice;};
|
||||
|
||||
/// Returns the DTSC::DTMItype AMF0 object type for this object.
|
||||
DTSC::DTMItype DTSC::DTMI::GetType(){return myType;};
|
||||
|
||||
/// Returns the numeric value of this object, if available.
|
||||
/// If this object holds no numeric value, 0 is returned.
|
||||
uint64_t DTSC::DTMI::NumValue(){return numval;};
|
||||
|
||||
/// Returns the std::string value of this object, if available.
|
||||
/// If this object holds no string value, an empty string is returned.
|
||||
std::string DTSC::DTMI::StrValue(){return strval;};
|
||||
|
||||
/// Returns the C-string value of this object, if available.
|
||||
/// If this object holds no string value, an empty C-string is returned.
|
||||
const char * DTSC::DTMI::Str(){return strval.c_str();};
|
||||
|
||||
/// Returns a count of the amount of objects this object currently holds.
|
||||
/// If this object is not a container type, this function will always return 0.
|
||||
int DTSC::DTMI::hasContent(){return contents.size();};
|
||||
|
||||
/// Adds an DTSC::DTMI to this object. Works for all types, but only makes sense for container types.
|
||||
void DTSC::DTMI::addContent(DTSC::DTMI c){contents.push_back(c);};
|
||||
|
||||
/// Returns a pointer to the object held at indice i.
|
||||
/// Returns AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice.
|
||||
/// \param i The indice of the object in this container.
|
||||
DTSC::DTMI* DTSC::DTMI::getContentP(int i){return &contents.at(i);};
|
||||
|
||||
/// Returns a copy of the object held at indice i.
|
||||
/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice.
|
||||
/// \param i The indice of the object in this container.
|
||||
DTSC::DTMI DTSC::DTMI::getContent(int i){return contents.at(i);};
|
||||
|
||||
/// Returns a pointer to the object held at indice s.
|
||||
/// Returns NULL if no object is held at this indice.
|
||||
/// \param s The indice of the object in this container.
|
||||
DTSC::DTMI* DTSC::DTMI::getContentP(std::string s){
|
||||
for (std::vector<DTSC::DTMI>::iterator it = contents.begin(); it != contents.end(); it++){
|
||||
if (it->Indice() == s){return &(*it);}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
/// Returns a copy of the object held at indice s.
|
||||
/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice.
|
||||
/// \param s The indice of the object in this container.
|
||||
DTSC::DTMI DTSC::DTMI::getContent(std::string s){
|
||||
for (std::vector<DTSC::DTMI>::iterator it = contents.begin(); it != contents.end(); it++){
|
||||
if (it->Indice() == s){return *it;}
|
||||
}
|
||||
return DTSC::DTMI("error", DTMI::DTMI_ROOT);
|
||||
};
|
||||
|
||||
/// Default constructor.
|
||||
/// Simply fills the data with DTSC::DTMI("error", AMF0_DDV_CONTAINER)
|
||||
DTSC::DTMI::DTMI(){
|
||||
*this = DTSC::DTMI("error", DTMI::DTMI_ROOT);
|
||||
};//default constructor
|
||||
|
||||
/// Constructor for numeric objects.
|
||||
/// The object type is by default AMF::AMF0_NUMBER, but this can be forced to a different value.
|
||||
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
|
||||
/// \param val The numeric value of this object. Numeric AMF0 objects only support double-type values.
|
||||
/// \param setType The object type to force this object to.
|
||||
DTSC::DTMI::DTMI(std::string indice, double val, DTSC::DTMItype setType){//num type initializer
|
||||
myIndice = indice;
|
||||
myType = setType;
|
||||
strval = "";
|
||||
numval = val;
|
||||
};
|
||||
|
||||
/// Constructor for string objects.
|
||||
/// The object type is by default AMF::AMF0_STRING, but this can be forced to a different value.
|
||||
/// There is no need to manually change the type to AMF::AMF0_LONGSTRING, this will be done automatically.
|
||||
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
|
||||
/// \param val The string value of this object.
|
||||
/// \param setType The object type to force this object to.
|
||||
DTSC::DTMI::DTMI(std::string indice, std::string val, DTSC::DTMItype setType){//str type initializer
|
||||
myIndice = indice;
|
||||
myType = setType;
|
||||
strval = val;
|
||||
numval = 0;
|
||||
};
|
||||
|
||||
/// Constructor for container objects.
|
||||
/// The object type is by default AMF::AMF0_OBJECT, but this can be forced to a different value.
|
||||
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
|
||||
/// \param setType The object type to force this object to.
|
||||
DTSC::DTMI::DTMI(std::string indice, DTSC::DTMItype setType){//object type initializer
|
||||
myIndice = indice;
|
||||
myType = setType;
|
||||
strval = "";
|
||||
numval = 0;
|
||||
};
|
||||
|
||||
/// Prints the contents of this object to std::cerr.
|
||||
/// If this object contains other objects, it will call itself recursively
|
||||
/// and print all nested content in a nice human-readable format.
|
||||
void DTSC::DTMI::Print(std::string indent){
|
||||
std::cerr << indent;
|
||||
// print my type
|
||||
switch (myType){
|
||||
case DTMItype::DTMI_INT: std::cerr << "Integer"; break;
|
||||
case DTMItype::DTMI_STRING: std::cerr << "String"; break;
|
||||
case DTMItype::DTMI_OBJECT: std::cerr << "Object"; break;
|
||||
case DTMItype::DTMI_OBJ_END: std::cerr << "Object end"; break;
|
||||
case DTMItype::DTMI_ROOT: std::cerr << "Root Node"; break;
|
||||
}
|
||||
// print my string indice, if available
|
||||
std::cerr << " " << myIndice << " ";
|
||||
// print my numeric or string contents
|
||||
switch (myType){
|
||||
case DTMItype::DTMI_INT: std::cerr << numval; break;
|
||||
case DTMItype::DTMI_STRING: std::cerr << strval; break;
|
||||
default: break;//we don't care about the rest, and don't want a compiler warning...
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
// if I hold other objects, print those too, recursively.
|
||||
if (contents.size() > 0){
|
||||
for (std::vector<DTSC::DTMI>::iterator it = contents.begin(); it != contents.end(); it++){it->Print(indent+" ");}
|
||||
}
|
||||
};//print
|
||||
|
||||
/// Packs the AMF object to a std::string for transfer over the network.
|
||||
/// If the object is a container type, this function will call itself recursively and contain all contents.
|
||||
std::string DTSC::DTMI::Pack(){
|
||||
std::string r = "";
|
||||
//skip output of DDV container types, they do not exist. Only output their contents.
|
||||
if (myType != DTMItype::DTMI_ROOT){r += myType;}
|
||||
//output the properly formatted data stream for this object's contents.
|
||||
switch (myType){
|
||||
case DTMItype::DTMI_INT:
|
||||
r += *(((char*)&numval)+7); r += *(((char*)&numval)+6);
|
||||
r += *(((char*)&numval)+5); r += *(((char*)&numval)+4);
|
||||
r += *(((char*)&numval)+3); r += *(((char*)&numval)+2);
|
||||
r += *(((char*)&numval)+1); r += *(((char*)&numval));
|
||||
break;
|
||||
case DTMItype::DTMI_STRING:
|
||||
r += strval.size() / (256*256*256);
|
||||
r += strval.size() / (256*256);
|
||||
r += strval.size() / 256;
|
||||
r += strval.size() % 256;
|
||||
r += strval;
|
||||
break;
|
||||
case DTMItype::DTMI_OBJECT:
|
||||
if (contents.size() > 0){
|
||||
for (std::vector<DTSC::DTMI>::iterator it = contents.begin(); it != contents.end(); it++){
|
||||
r += it->Indice().size() / 256;
|
||||
r += it->Indice().size() % 256;
|
||||
r += it->Indice();
|
||||
r += it->Pack();
|
||||
}
|
||||
}
|
||||
r += (char)0; r += (char)0; r += (char)9;
|
||||
break;
|
||||
case DTMItype::DTMI_ROOT://only send contents
|
||||
if (contents.size() > 0){
|
||||
for (std::vector<DTSC::DTMI>::iterator it = contents.begin(); it != contents.end(); it++){
|
||||
r += it->Pack();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return r;
|
||||
};//pack
|
||||
|
||||
/// Parses a single AMF0 type - used recursively by the AMF::parse() functions.
|
||||
/// This function updates i every call with the new position in the data.
|
||||
/// \param data The raw data to parse.
|
||||
/// \param len The size of the raw data.
|
||||
/// \param i Current parsing position in the raw data.
|
||||
/// \param name Indice name for any new object created.
|
||||
/// \returns A single DTSC::DTMI, parsed from the raw data.
|
||||
DTSC::DTMI DTSC::parseOneDTMI(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name){
|
||||
std::string tmpstr;
|
||||
unsigned int tmpi = 0;
|
||||
unsigned char tmpdbl[8];
|
||||
#if DEBUG >= 10
|
||||
fprintf(stderr, "Note: AMF type %hhx found. %i bytes left\n", data[i], len-i);
|
||||
#endif
|
||||
switch (data[i]){
|
||||
case DTMI::DTMI_INT:
|
||||
tmpdbl[7] = data[i+1];
|
||||
tmpdbl[6] = data[i+2];
|
||||
tmpdbl[5] = data[i+3];
|
||||
tmpdbl[4] = data[i+4];
|
||||
tmpdbl[3] = data[i+5];
|
||||
tmpdbl[2] = data[i+6];
|
||||
tmpdbl[1] = data[i+7];
|
||||
tmpdbl[0] = data[i+8];
|
||||
i+=9;//skip 8(a double)+1 forwards
|
||||
return DTSC::DTMI(name, *(uint64_t*)tmpdbl, AMF::AMF0_NUMBER);
|
||||
break;
|
||||
case DTMI::DTMI_STRING:
|
||||
tmpi = data[i+1]*256*256*256+data[i+2]*256*256+data[i+3]*256+data[i+4];//set tmpi to UTF-8-long length
|
||||
tmpstr.clear();//clean tmpstr, just to be sure
|
||||
tmpstr.append((const char *)data+i+5, (size_t)tmpi);//add the string data
|
||||
i += tmpi + 5;//skip length+size+1 forwards
|
||||
return DTSC::DTMI(name, tmpstr, AMF::AMF0_LONGSTRING);
|
||||
break;
|
||||
case DTMI::DTMI_OBJECT:{
|
||||
++i;
|
||||
DTSC::DTMI ret(name, DTMI::DTMI_OBJECT);
|
||||
while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x000009)
|
||||
tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length
|
||||
tmpstr.clear();//clean tmpstr, just to be sure
|
||||
tmpstr.append((const char*)data+i+2, (size_t)tmpi);//add the string data
|
||||
i += tmpi + 2;//skip length+size forwards
|
||||
ret.addContent(AMF::parseOne(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr
|
||||
}
|
||||
i += 3;//skip 0x000009
|
||||
return ret;
|
||||
} break;
|
||||
}
|
||||
#if DEBUG >= 2
|
||||
fprintf(stderr, "Error: Unimplemented DTMI type %hhx - returning.\n", data[i]);
|
||||
#endif
|
||||
return DTSC::DTMI("error", DTMI::DTMI_ROOT);
|
||||
}//parseOne
|
||||
|
||||
/// Parses a C-string to a valid DTSC::DTMI.
|
||||
/// This function will find all AMF objects in the string and return
|
||||
/// them all packed in a single AMF::AMF0_DDV_CONTAINER DTSC::DTMI.
|
||||
DTSC::DTMI DTSC::parseDTMI(const unsigned char * data, unsigned int len){
|
||||
DTSC::DTMI ret("returned", DTMI::DTMI_ROOT);//container type
|
||||
unsigned int i = 0, j = 0;
|
||||
while (i < len){
|
||||
ret.addContent(AMF::parseOne(data, len, i, ""));
|
||||
if (i > j){j = i;}else{return ret;}
|
||||
}
|
||||
return ret;
|
||||
}//parse
|
||||
|
||||
/// Parses a std::string to a valid DTSC::DTMI.
|
||||
/// This function will find all AMF objects in the string and return
|
||||
/// them all packed in a single AMF::AMF0_DDV_CONTAINER DTSC::DTMI.
|
||||
DTSC::DTMI DTSC::parseDTMI(std::string data){
|
||||
return parse((const unsigned char*)data.c_str(), data.size());
|
||||
}//parse
|
58
util/dtmi.h
58
util/dtmi.h
|
@ -1,58 +0,0 @@
|
|||
/// \file dtmi.h
|
||||
/// Holds all headers for DDVTECH MediaInfo parsing/generation.
|
||||
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
//#include <string.h>
|
||||
#include <string>
|
||||
|
||||
/// Holds all DDVTECH Stream Container classes and parsers.
|
||||
namespace DTSC{
|
||||
|
||||
/// Enumerates all possible DTMI types.
|
||||
enum DTMItype {
|
||||
DTMI_INT = 0x01, ///< Unsigned 64-bit integer.
|
||||
DTMI_STRING = 0x02, ///< String, equivalent to the AMF longstring type.
|
||||
DTMI_OBJECT = 0xE0, ///< Object, equivalent to the AMF object type.
|
||||
DTMI_OBJ_END = 0xEE, ///< End of object marker.
|
||||
DTMI_ROOT = 0xFF ///< Root node for all DTMI data.
|
||||
};
|
||||
|
||||
/// Recursive class that holds DDVTECH MediaInfo.
|
||||
class DTMI {
|
||||
public:
|
||||
std::string Indice();
|
||||
DTMItype GetType();
|
||||
uint64_t NumValue();
|
||||
std::string StrValue();
|
||||
const char * Str();
|
||||
int hasContent();
|
||||
void addContent(DTMI c);
|
||||
DTMI* getContentP(int i);
|
||||
DTMI getContent(int i);
|
||||
DTMI* getContentP(std::string s);
|
||||
DTMI getContent(std::string s);
|
||||
DTMI();
|
||||
DTMI(std::string indice, double val, DTMItype setType = DTMI_INT);
|
||||
DTMI(std::string indice, std::string val, DTMItype setType = DTMI_STRING);
|
||||
DTMI(std::string indice, DTMItype setType = DTMI_OBJECT);
|
||||
void Print(std::string indent = "");
|
||||
std::string Pack();
|
||||
protected:
|
||||
std::string myIndice; ///< Holds this objects indice, if any.
|
||||
DTMItype myType; ///< Holds this objects AMF0 type.
|
||||
std::string strval; ///< Holds this objects string value, if any.
|
||||
uint64_t numval; ///< Holds this objects numeric value, if any.
|
||||
std::vector<DTMI> contents; ///< Holds this objects contents, if any (for container types).
|
||||
};//AMFType
|
||||
|
||||
/// Parses a C-string to a valid DTSC::DTMI.
|
||||
DTMI parseDTMI(const unsigned char * data, unsigned int len);
|
||||
/// Parses a std::string to a valid DTSC::DTMI.
|
||||
DTMI parseDTMI(std::string data);
|
||||
/// Parses a single DTMI type - used recursively by the DTSC::parseDTMI() functions.
|
||||
DTMI parseOneDTMI(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name);
|
||||
|
||||
};//DTSC namespace
|
||||
|
414
util/dtsc.cpp
414
util/dtsc.cpp
|
@ -2,33 +2,51 @@
|
|||
/// Holds all code for DDVTECH Stream Container parsing/generation.
|
||||
|
||||
#include "dtsc.h"
|
||||
#include "string.h" //for memcmp
|
||||
#include "arpa/inet.h" //for htonl/ntohl
|
||||
#include <string.h> //for memcmp
|
||||
#include <arpa/inet.h> //for htonl/ntohl
|
||||
#include <stdio.h> //for fprint, stderr
|
||||
|
||||
char * DTSC::Magic_Header = "DTSC";
|
||||
char * DTSC::Magic_Packet = "DTPD";
|
||||
char DTSC::Magic_Header[] = "DTSC";
|
||||
char DTSC::Magic_Packet[] = "DTPD";
|
||||
|
||||
/// Initializes a DTSC::Stream with only one packet buffer.
|
||||
DTSC::Stream::Stream(){
|
||||
datapointer = 0;
|
||||
buffercount = 1;
|
||||
}
|
||||
|
||||
/// Initializes a DTSC::Stream with a minimum of rbuffers packet buffers.
|
||||
/// The actual buffer count may not at all times be the requested amount.
|
||||
DTSC::Stream::Stream(unsigned int rbuffers){
|
||||
datapointer = 0;
|
||||
if (rbuffers < 1){rbuffers = 1;}
|
||||
buffercount = rbuffers;
|
||||
}
|
||||
|
||||
/// Attempts to parse a packet from the given std::string buffer.
|
||||
/// Returns true if successful, removing the parsed part from the buffer string.
|
||||
/// Returns false if invalid or not enough data is in the buffer.
|
||||
/// \arg buffer The std::string buffer to attempt to parse.
|
||||
bool DTSC::Stream::parsePacket(std::string & buffer){
|
||||
uint32_t len;
|
||||
if (buffer.length() > 8){
|
||||
if (memcmp(buffer.c_str(), DTSC::Magic_Header, 4) == 0){
|
||||
len = ntohl(((uint32_t *)buffer.c_str())[1]);
|
||||
if (buffer.length() < len+8){return false;}
|
||||
metadata = DTSC::parseDTMI(buffer.c_str() + 8, len);
|
||||
metadata = DTSC::parseDTMI((unsigned char*)buffer.c_str() + 8, len);
|
||||
buffer.erase(0, len+8);
|
||||
return false;
|
||||
}
|
||||
if (memcmp(buffer.c_str(), DTSC::Magic_Packet, 4) == 0){
|
||||
len = ntohl(((uint32_t *)buffer.c_str())[1]);
|
||||
if (buffer.length() < len+8){return false;}
|
||||
lastPacket = DTSC::parseDTMI(buffer.c_str() + 8, len);
|
||||
buffers.push_front(DTSC::DTMI("empty", DTMI_ROOT));
|
||||
buffers.front() = DTSC::parseDTMI((unsigned char*)buffer.c_str() + 8, len);
|
||||
datapointertype = INVALID;
|
||||
if (lastPacket.getContentP("data")){
|
||||
datapointer = lastPacket.getContentP("data")->StrValue.c_str();
|
||||
if (lastPacket.getContentP("datatype")){
|
||||
std::string tmp = lastPacket.getContentP("datatype")->StrValue();
|
||||
if (buffers.front().getContentP("data")){
|
||||
datapointer = &(buffers.front().getContentP("data")->StrValue());
|
||||
if (buffers.front().getContentP("datatype")){
|
||||
std::string tmp = buffers.front().getContentP("datatype")->StrValue();
|
||||
if (tmp == "video"){datapointertype = VIDEO;}
|
||||
if (tmp == "audio"){datapointertype = AUDIO;}
|
||||
if (tmp == "meta"){datapointertype = META;}
|
||||
|
@ -36,23 +54,395 @@ bool DTSC::Stream::parsePacket(std::string & buffer){
|
|||
}else{
|
||||
datapointer = 0;
|
||||
}
|
||||
buffer.erase(0, len+8);
|
||||
while (buffers.size() > buffercount){buffers.pop_back();}
|
||||
advanceRings();
|
||||
return true;
|
||||
}
|
||||
#if DEBUG >= 2
|
||||
std::cerr << "Error: Invalid DTMI data! I *will* get stuck!" << std::endl;
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
char * DTSC::Stream::lastData(){
|
||||
return datapointer;
|
||||
/// Returns a direct pointer to the data attribute of the last received packet, if available.
|
||||
/// Returns NULL if no valid pointer or packet is available.
|
||||
std::string & DTSC::Stream::lastData(){
|
||||
return *datapointer;
|
||||
}
|
||||
|
||||
/// Returns the packed in this buffer number.
|
||||
/// \arg num Buffer number.
|
||||
DTSC::DTMI & DTSC::Stream::getPacket(unsigned int num){
|
||||
return buffers[num];
|
||||
}
|
||||
|
||||
/// Returns the type of the last received packet.
|
||||
DTSC::datatype DTSC::Stream::lastType(){
|
||||
return datapointertype;
|
||||
}
|
||||
|
||||
/// Returns true if the current stream contains at least one video track.
|
||||
bool DTSC::Stream::hasVideo(){
|
||||
return (metadata.getContentP("video") != 0);
|
||||
}
|
||||
|
||||
/// Returns true if the current stream contains at least one audio track.
|
||||
bool DTSC::Stream::hasAudio(){
|
||||
return (metadata.getContentP("audio") != 0);
|
||||
}
|
||||
|
||||
/// Returns a packed DTSC packet, ready to sent over the network.
|
||||
std::string & DTSC::Stream::outPacket(unsigned int num){
|
||||
buffers[num].Pack(true);
|
||||
return buffers[num].packed;
|
||||
}
|
||||
|
||||
/// Returns a packed DTSC header, ready to sent over the network.
|
||||
std::string & DTSC::Stream::outHeader(){
|
||||
if ((metadata.packed.length() < 4) || !metadata.netpacked){
|
||||
metadata.Pack(true);
|
||||
metadata.packed.replace(0, 4, Magic_Header);
|
||||
}
|
||||
return metadata.packed;
|
||||
}
|
||||
|
||||
/// advances all given out and internal Ring classes to point to the new buffer, after one has been added.
|
||||
/// Also updates the internal keyframes ring, as well as marking rings as starved if they are.
|
||||
/// Unsets waiting rings, updating them with their new buffer number.
|
||||
void DTSC::Stream::advanceRings(){
|
||||
std::deque<DTSC::Ring>::iterator dit;
|
||||
std::set<DTSC::Ring *>::iterator sit;
|
||||
for (sit = rings.begin(); sit != rings.end(); sit++){
|
||||
(*sit)->b++;
|
||||
if ((*sit)->waiting){(*sit)->waiting = false; (*sit)->b = 0;}
|
||||
if ((*sit)->starved || ((*sit)->b >= buffers.size())){(*sit)->starved = true; (*sit)->b = 0;}
|
||||
}
|
||||
for (dit = keyframes.begin(); dit != keyframes.end(); dit++){
|
||||
dit->b++;
|
||||
if (dit->b >= buffers.size()){keyframes.erase(dit); break;}
|
||||
}
|
||||
if ((lastType() == VIDEO) && (buffers.front().getContentP("keyframe"))){
|
||||
keyframes.push_front(DTSC::Ring(0));
|
||||
}
|
||||
//increase buffer size if no keyframes available
|
||||
if ((buffercount > 1) && (keyframes.size() < 1)){buffercount++;}
|
||||
}
|
||||
|
||||
/// Constructs a new Ring, at the given buffer position.
|
||||
/// \arg v Position for buffer.
|
||||
DTSC::Ring::Ring(unsigned int v){
|
||||
b = v;
|
||||
waiting = false;
|
||||
starved = false;
|
||||
}
|
||||
|
||||
/// Requests a new Ring, which will be created and added to the internal Ring list.
|
||||
/// This Ring will be kept updated so it always points to valid data or has the starved boolean set.
|
||||
/// Don't forget to call dropRing() for all requested Ring classes that are no longer neccessary!
|
||||
DTSC::Ring * DTSC::Stream::getRing(){
|
||||
DTSC::Ring * tmp;
|
||||
if (keyframes.size() == 0){
|
||||
tmp = new DTSC::Ring(0);
|
||||
}else{
|
||||
tmp = new DTSC::Ring(keyframes[0].b);
|
||||
}
|
||||
rings.insert(tmp);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/// Deletes a given out Ring class from memory and internal Ring list.
|
||||
/// Checks for NULL pointers and invalid pointers, silently discarding them.
|
||||
void DTSC::Stream::dropRing(DTSC::Ring * ptr){
|
||||
if (rings.find(ptr) != rings.end()){
|
||||
rings.erase(ptr);
|
||||
delete ptr;
|
||||
}
|
||||
}
|
||||
|
||||
/// Properly cleans up the object for erasing.
|
||||
/// Drops all Ring classes that have been given out.
|
||||
DTSC::Stream::~Stream(){
|
||||
std::set<DTSC::Ring *>::iterator sit;
|
||||
for (sit = rings.begin(); sit != rings.end(); sit++){delete (*sit);}
|
||||
}
|
||||
|
||||
/// Returns the std::string Indice for the current object, if available.
|
||||
/// Returns an empty string if no indice exists.
|
||||
std::string DTSC::DTMI::Indice(){return myIndice;};
|
||||
|
||||
/// Returns the DTSC::DTMItype AMF0 object type for this object.
|
||||
DTSC::DTMItype DTSC::DTMI::GetType(){return myType;};
|
||||
|
||||
/// Returns the numeric value of this object, if available.
|
||||
/// If this object holds no numeric value, 0 is returned.
|
||||
uint64_t & DTSC::DTMI::NumValue(){return numval;};
|
||||
|
||||
/// Returns the std::string value of this object, if available.
|
||||
/// If this object holds no string value, an empty string is returned.
|
||||
std::string & DTSC::DTMI::StrValue(){return strval;};
|
||||
|
||||
/// Returns the C-string value of this object, if available.
|
||||
/// If this object holds no string value, an empty C-string is returned.
|
||||
const char * DTSC::DTMI::Str(){return strval.c_str();};
|
||||
|
||||
/// Returns a count of the amount of objects this object currently holds.
|
||||
/// If this object is not a container type, this function will always return 0.
|
||||
int DTSC::DTMI::hasContent(){return contents.size();};
|
||||
|
||||
/// Adds an DTSC::DTMI to this object. Works for all types, but only makes sense for container types.
|
||||
/// This function resets DTMI::packed to an empty string, forcing a repack on the next call to DTMI::Pack.
|
||||
/// If the indice name already exists, replaces the indice.
|
||||
void DTSC::DTMI::addContent(DTSC::DTMI c){
|
||||
std::vector<DTMI>::iterator it;
|
||||
for (it = contents.begin(); it != contents.end(); it++){
|
||||
if (it->Indice() == c.Indice()){
|
||||
contents.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
contents.push_back(c); packed = "";
|
||||
};
|
||||
|
||||
/// Returns a pointer to the object held at indice i.
|
||||
/// Returns AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice.
|
||||
/// \param i The indice of the object in this container.
|
||||
DTSC::DTMI* DTSC::DTMI::getContentP(int i){return &contents.at(i);};
|
||||
|
||||
/// Returns a copy of the object held at indice i.
|
||||
/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice.
|
||||
/// \param i The indice of the object in this container.
|
||||
DTSC::DTMI DTSC::DTMI::getContent(int i){return contents.at(i);};
|
||||
|
||||
/// Returns a pointer to the object held at indice s.
|
||||
/// Returns NULL if no object is held at this indice.
|
||||
/// \param s The indice of the object in this container.
|
||||
DTSC::DTMI* DTSC::DTMI::getContentP(std::string s){
|
||||
for (std::vector<DTSC::DTMI>::iterator it = contents.begin(); it != contents.end(); it++){
|
||||
if (it->Indice() == s){return &(*it);}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
/// Returns a copy of the object held at indice s.
|
||||
/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice.
|
||||
/// \param s The indice of the object in this container.
|
||||
DTSC::DTMI DTSC::DTMI::getContent(std::string s){
|
||||
for (std::vector<DTSC::DTMI>::iterator it = contents.begin(); it != contents.end(); it++){
|
||||
if (it->Indice() == s){return *it;}
|
||||
}
|
||||
return DTSC::DTMI("error", DTMI_ROOT);
|
||||
};
|
||||
|
||||
/// Default constructor.
|
||||
/// Simply fills the data with DTSC::DTMI("error", AMF0_DDV_CONTAINER)
|
||||
DTSC::DTMI::DTMI(){
|
||||
*this = DTSC::DTMI("error", DTMI_ROOT);
|
||||
};//default constructor
|
||||
|
||||
/// Constructor for numeric objects.
|
||||
/// The object type is by default DTMItype::DTMI_INT, but this can be forced to a different value.
|
||||
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
|
||||
/// \param val The numeric value of this object. Numeric objects only support uint64_t values.
|
||||
/// \param setType The object type to force this object to.
|
||||
DTSC::DTMI::DTMI(std::string indice, uint64_t val, DTSC::DTMItype setType){//num type initializer
|
||||
myIndice = indice;
|
||||
myType = setType;
|
||||
strval = "";
|
||||
numval = val;
|
||||
};
|
||||
|
||||
/// Constructor for string objects.
|
||||
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
|
||||
/// \param val The string value of this object.
|
||||
/// \param setType The object type to force this object to.
|
||||
DTSC::DTMI::DTMI(std::string indice, std::string val, DTSC::DTMItype setType){//str type initializer
|
||||
myIndice = indice;
|
||||
myType = setType;
|
||||
strval = val;
|
||||
numval = 0;
|
||||
};
|
||||
|
||||
/// Constructor for container objects.
|
||||
/// \param indice The string indice of this object in its container, or empty string if none.
|
||||
/// \param setType The object type to force this object to.
|
||||
DTSC::DTMI::DTMI(std::string indice, DTSC::DTMItype setType){//object type initializer
|
||||
myIndice = indice;
|
||||
myType = setType;
|
||||
strval = "";
|
||||
numval = 0;
|
||||
};
|
||||
|
||||
/// Prints the contents of this object to std::cerr.
|
||||
/// If this object contains other objects, it will call itself recursively
|
||||
/// and print all nested content in a nice human-readable format.
|
||||
void DTSC::DTMI::Print(std::string indent){
|
||||
std::cerr << indent;
|
||||
// print my type
|
||||
switch (myType){
|
||||
case DTMI_INT: std::cerr << "Integer"; break;
|
||||
case DTMI_STRING: std::cerr << "String"; break;
|
||||
case DTMI_OBJECT: std::cerr << "Object"; break;
|
||||
case DTMI_OBJ_END: std::cerr << "Object end"; break;
|
||||
case DTMI_ROOT: std::cerr << "Root Node"; break;
|
||||
}
|
||||
// print my string indice, if available
|
||||
std::cerr << " " << myIndice << " ";
|
||||
// print my numeric or string contents
|
||||
switch (myType){
|
||||
case DTMI_INT: std::cerr << numval; break;
|
||||
case DTMI_STRING:
|
||||
if (strval.length() > 200 || ((strval.length() > 1) && ( (strval[0] < 'A') || (strval[0] > 'z') ) )){
|
||||
std::cerr << strval.length() << " bytes of data";
|
||||
}else{
|
||||
std::cerr << strval;
|
||||
}
|
||||
break;
|
||||
default: break;//we don't care about the rest, and don't want a compiler warning...
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
// if I hold other objects, print those too, recursively.
|
||||
if (contents.size() > 0){
|
||||
for (std::vector<DTSC::DTMI>::iterator it = contents.begin(); it != contents.end(); it++){it->Print(indent+" ");}
|
||||
}
|
||||
};//print
|
||||
|
||||
/// Packs the DTMI to a std::string for transfer over the network.
|
||||
/// If a packed version already exists, does not regenerate it.
|
||||
/// If the object is a container type, this function will call itself recursively and contain all contents.
|
||||
/// \arg netpack If true, will pack as a full DTMI packet, if false only as the contents without header.
|
||||
std::string DTSC::DTMI::Pack(bool netpack){
|
||||
if (packed != ""){
|
||||
if (netpacked == netpack){return packed;}
|
||||
if (netpacked){
|
||||
packed.erase(0, 8);
|
||||
}else{
|
||||
unsigned int size = htonl(packed.length());
|
||||
packed.insert(0, (char*)&size, 4);
|
||||
packed.insert(0, Magic_Packet);
|
||||
}
|
||||
netpacked = !netpacked;
|
||||
return packed;
|
||||
}
|
||||
std::string r = "";
|
||||
r += myType;
|
||||
//output the properly formatted data stream for this object's contents.
|
||||
switch (myType){
|
||||
case DTMI_INT:
|
||||
r += *(((char*)&numval)+7); r += *(((char*)&numval)+6);
|
||||
r += *(((char*)&numval)+5); r += *(((char*)&numval)+4);
|
||||
r += *(((char*)&numval)+3); r += *(((char*)&numval)+2);
|
||||
r += *(((char*)&numval)+1); r += *(((char*)&numval));
|
||||
break;
|
||||
case DTMI_STRING:
|
||||
r += strval.size() / (256*256*256);
|
||||
r += strval.size() / (256*256);
|
||||
r += strval.size() / 256;
|
||||
r += strval.size() % 256;
|
||||
r += strval;
|
||||
break;
|
||||
case DTMI_OBJECT:
|
||||
case DTMI_ROOT:
|
||||
if (contents.size() > 0){
|
||||
for (std::vector<DTSC::DTMI>::iterator it = contents.begin(); it != contents.end(); it++){
|
||||
r += it->Indice().size() / 256;
|
||||
r += it->Indice().size() % 256;
|
||||
r += it->Indice();
|
||||
r += it->Pack();
|
||||
}
|
||||
}
|
||||
r += (char)0x0; r += (char)0x0; r += (char)0xEE;
|
||||
break;
|
||||
case DTMI_OBJ_END:
|
||||
break;
|
||||
}
|
||||
packed = r;
|
||||
netpacked = netpack;
|
||||
if (netpacked){
|
||||
unsigned int size = htonl(packed.length());
|
||||
packed.insert(0, (char*)&size, 4);
|
||||
packed.insert(0, Magic_Packet);
|
||||
}
|
||||
return packed;
|
||||
};//pack
|
||||
|
||||
/// Parses a single AMF0 type - used recursively by the AMF::parse() functions.
|
||||
/// This function updates i every call with the new position in the data.
|
||||
/// \param data The raw data to parse.
|
||||
/// \param len The size of the raw data.
|
||||
/// \param i Current parsing position in the raw data.
|
||||
/// \param name Indice name for any new object created.
|
||||
/// \returns A single DTSC::DTMI, parsed from the raw data.
|
||||
DTSC::DTMI DTSC::parseOneDTMI(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name){
|
||||
unsigned int tmpi = 0;
|
||||
unsigned char tmpdbl[8];
|
||||
#if DEBUG >= 10
|
||||
fprintf(stderr, "Note: AMF type %hhx found. %i bytes left\n", data[i], len-i);
|
||||
#endif
|
||||
switch (data[i]){
|
||||
case DTMI_INT:
|
||||
tmpdbl[7] = data[i+1];
|
||||
tmpdbl[6] = data[i+2];
|
||||
tmpdbl[5] = data[i+3];
|
||||
tmpdbl[4] = data[i+4];
|
||||
tmpdbl[3] = data[i+5];
|
||||
tmpdbl[2] = data[i+6];
|
||||
tmpdbl[1] = data[i+7];
|
||||
tmpdbl[0] = data[i+8];
|
||||
i+=9;//skip 8(an uint64_t)+1 forwards
|
||||
return DTSC::DTMI(name, *(uint64_t*)tmpdbl, DTMI_INT);
|
||||
break;
|
||||
case DTMI_STRING:{
|
||||
tmpi = data[i+1]*256*256*256+data[i+2]*256*256+data[i+3]*256+data[i+4];//set tmpi to UTF-8-long length
|
||||
std::string tmpstr = std::string((const char *)data+i+5, (size_t)tmpi);//set the string data
|
||||
i += tmpi + 5;//skip length+size+1 forwards
|
||||
return DTSC::DTMI(name, tmpstr, DTMI_STRING);
|
||||
} break;
|
||||
case DTMI_ROOT:{
|
||||
++i;
|
||||
DTSC::DTMI ret(name, DTMI_ROOT);
|
||||
while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x0000EE)
|
||||
tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length
|
||||
std::string tmpstr = std::string((const char *)data+i+2, (size_t)tmpi);//set the string data
|
||||
i += tmpi + 2;//skip length+size forwards
|
||||
ret.addContent(parseOneDTMI(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr
|
||||
}
|
||||
i += 3;//skip 0x0000EE
|
||||
return ret;
|
||||
} break;
|
||||
case DTMI_OBJECT:{
|
||||
++i;
|
||||
DTSC::DTMI ret(name, DTMI_OBJECT);
|
||||
while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x0000EE)
|
||||
tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length
|
||||
std::string tmpstr = std::string((const char *)data+i+2, (size_t)tmpi);//set the string data
|
||||
i += tmpi + 2;//skip length+size forwards
|
||||
ret.addContent(parseOneDTMI(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr
|
||||
}
|
||||
i += 3;//skip 0x0000EE
|
||||
return ret;
|
||||
} break;
|
||||
}
|
||||
#if DEBUG >= 2
|
||||
fprintf(stderr, "Error: Unimplemented DTMI type %hhx - returning.\n", data[i]);
|
||||
#endif
|
||||
return DTSC::DTMI("error", DTMI_ROOT);
|
||||
}//parseOne
|
||||
|
||||
/// Parses a C-string to a valid DTSC::DTMI.
|
||||
/// This function will find one DTMI object in the string and return it.
|
||||
DTSC::DTMI DTSC::parseDTMI(const unsigned char * data, unsigned int len){
|
||||
DTSC::DTMI ret;//container type
|
||||
unsigned int i = 0;
|
||||
ret = parseOneDTMI(data, len, i, "");
|
||||
ret.packed = std::string((char*)data, (size_t)len);
|
||||
ret.netpacked = false;
|
||||
return ret;
|
||||
}//parse
|
||||
|
||||
/// Parses a std::string to a valid DTSC::DTMI.
|
||||
/// This function will find one DTMI object in the string and return it.
|
||||
DTSC::DTMI DTSC::parseDTMI(std::string data){
|
||||
return parseDTMI((const unsigned char*)data.c_str(), data.size());
|
||||
}//parse
|
||||
|
|
135
util/dtsc.h
135
util/dtsc.h
|
@ -2,46 +2,141 @@
|
|||
/// Holds all headers for DDVTECH Stream Container parsing/generation.
|
||||
|
||||
#pragma once
|
||||
#include "dtmi.h"
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <stdint.h> //for uint64_t
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <set>
|
||||
|
||||
// Video:
|
||||
// Codec (string)
|
||||
|
||||
// Audio:
|
||||
// Codec (string)
|
||||
// Samping rate (int, Hz)
|
||||
// Sample Size (int, bytesize)
|
||||
// Channels (int, channelcount)
|
||||
|
||||
/// Holds all DDVTECH Stream Container classes and parsers.
|
||||
///Video:
|
||||
/// - codec (string: H264, H263, VP6)
|
||||
/// - width (int, pixels)
|
||||
/// - height (int, pixels)
|
||||
/// - fpks (int, frames per kilosecond (FPS * 1000))
|
||||
/// - bps (int, bytes per second)
|
||||
/// - init (string, init data)
|
||||
///
|
||||
///Audio:
|
||||
/// - codec (string: AAC, MP3)
|
||||
/// - rate (int, Hz)
|
||||
/// - size (int, bitsize)
|
||||
/// - bps (int, bytes per second)
|
||||
/// - channels (int, channelcount)
|
||||
/// - init (string, init data)
|
||||
///
|
||||
///All packets:
|
||||
/// - datatype (string: audio, video, meta (unused))
|
||||
/// - data (string: data)
|
||||
/// - time (int: ms into video)
|
||||
///
|
||||
///Video packets:
|
||||
/// - keyframe (int, if set, is a seekable keyframe)
|
||||
/// - interframe (int, if set, is a non-seekable interframe)
|
||||
/// - disposableframe (int, if set, is a disposable interframe)
|
||||
///
|
||||
///H264 video packets:
|
||||
/// - nalu (int, if set, is a nalu)
|
||||
/// - nalu_end (int, if set, is a end-of-sequence)
|
||||
/// - offset (int, unsigned version of signed int! Holds the ms offset between timestamp and proper display time for B-frames)
|
||||
namespace DTSC{
|
||||
|
||||
/// Enumerates all possible DTMI types.
|
||||
enum DTMItype {
|
||||
DTMI_INT = 0x01, ///< Unsigned 64-bit integer.
|
||||
DTMI_STRING = 0x02, ///< String, equivalent to the AMF longstring type.
|
||||
DTMI_OBJECT = 0xE0, ///< Object, equivalent to the AMF object type.
|
||||
DTMI_OBJ_END = 0xEE, ///< End of object marker.
|
||||
DTMI_ROOT = 0xFF ///< Root node for all DTMI data.
|
||||
};
|
||||
|
||||
/// Recursive class that holds DDVTECH MediaInfo.
|
||||
class DTMI {
|
||||
public:
|
||||
std::string Indice();
|
||||
DTMItype GetType();
|
||||
uint64_t & NumValue();
|
||||
std::string & StrValue();
|
||||
const char * Str();
|
||||
int hasContent();
|
||||
void addContent(DTMI c);
|
||||
DTMI* getContentP(int i);
|
||||
DTMI getContent(int i);
|
||||
DTMI* getContentP(std::string s);
|
||||
DTMI getContent(std::string s);
|
||||
DTMI();
|
||||
DTMI(std::string indice, uint64_t val, DTMItype setType = DTMI_INT);
|
||||
DTMI(std::string indice, std::string val, DTMItype setType = DTMI_STRING);
|
||||
DTMI(std::string indice, DTMItype setType = DTMI_OBJECT);
|
||||
void Print(std::string indent = "");
|
||||
std::string Pack(bool netpack = false);
|
||||
bool netpacked;
|
||||
std::string packed;
|
||||
protected:
|
||||
std::string myIndice; ///< Holds this objects indice, if any.
|
||||
DTMItype myType; ///< Holds this objects AMF0 type.
|
||||
std::string strval; ///< Holds this objects string value, if any.
|
||||
uint64_t numval; ///< Holds this objects numeric value, if any.
|
||||
std::vector<DTMI> contents; ///< Holds this objects contents, if any (for container types).
|
||||
};//AMFType
|
||||
|
||||
/// Parses a C-string to a valid DTSC::DTMI.
|
||||
DTMI parseDTMI(const unsigned char * data, unsigned int len);
|
||||
/// Parses a std::string to a valid DTSC::DTMI.
|
||||
DTMI parseDTMI(std::string data);
|
||||
/// Parses a single DTMI type - used recursively by the DTSC::parseDTMI() functions.
|
||||
DTMI parseOneDTMI(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name);
|
||||
|
||||
/// This enum holds all possible datatypes for DTSC packets.
|
||||
enum datatype {
|
||||
AUDIO, ///< Stream Audio data
|
||||
VIDEO, ///< Stream Video data
|
||||
META, ///< Stream Metadata
|
||||
INVALID ///< Anything else or no data available.
|
||||
}
|
||||
};
|
||||
|
||||
char * Magic_Header; ///< The magic bytes for a DTSC header
|
||||
char * Magic_Packet; ///< The magic bytes for a DTSC packet
|
||||
extern char Magic_Header[]; ///< The magic bytes for a DTSC header
|
||||
extern char Magic_Packet[]; ///< The magic bytes for a DTSC packet
|
||||
|
||||
/// Holds temporary data for a DTSC stream and provides functions to access/store it.
|
||||
/// A part from the DTSC::Stream ringbuffer.
|
||||
/// Holds information about a buffer that will stay consistent
|
||||
class Ring {
|
||||
public:
|
||||
Ring(unsigned int v);
|
||||
unsigned int b; ///< Holds current number of buffer. May and is intended to change unexpectedly!
|
||||
bool waiting; ///< If true, this Ring is currently waiting for a buffer fill.
|
||||
bool starved; ///< If true, this Ring can no longer receive valid data.
|
||||
};
|
||||
|
||||
/// Holds temporary data for a DTSC stream and provides functions to utilize it.
|
||||
/// Optionally also acts as a ring buffer of a certain requested size.
|
||||
/// If ring buffering mode is enabled, it will automatically grow in size to always contain at least one keyframe.
|
||||
class Stream {
|
||||
public:
|
||||
Stream();
|
||||
~Stream();
|
||||
Stream(unsigned int buffers);
|
||||
DTSC::DTMI metadata;
|
||||
DRSC::DTMI lastPacket;
|
||||
DTSC::DTMI & getPacket(unsigned int num = 0);
|
||||
datatype lastType();
|
||||
char * lastData();
|
||||
std::string & lastData();
|
||||
bool hasVideo();
|
||||
bool hasAudio();
|
||||
bool parsePacket(std::string & buffer);
|
||||
private:
|
||||
char * datapointer;
|
||||
std::string & outPacket(unsigned int num);
|
||||
std::string & outHeader();
|
||||
Ring * getRing();
|
||||
void dropRing(Ring * ptr);
|
||||
private:
|
||||
std::deque<DTSC::DTMI> buffers;
|
||||
std::set<DTSC::Ring *> rings;
|
||||
std::deque<DTSC::Ring> keyframes;
|
||||
void advanceRings();
|
||||
std::string * datapointer;
|
||||
datatype datapointertype;
|
||||
}
|
||||
|
||||
|
||||
|
||||
unsigned int buffercount;
|
||||
};
|
||||
};
|
||||
|
|
264
util/flv_tag.cpp
264
util/flv_tag.cpp
|
@ -2,6 +2,7 @@
|
|||
/// Holds all code for the FLV namespace.
|
||||
|
||||
#include "flv_tag.h"
|
||||
#include "amf.h"
|
||||
#include "rtmpchunks.h"
|
||||
#include <stdio.h> //for Tag::FileLoader
|
||||
#include <unistd.h> //for Tag::FileLoader
|
||||
|
@ -109,7 +110,7 @@ std::string FLV::Tag::tagType(){
|
|||
case 4: R += "VP6"; break;
|
||||
case 5: R += "VP6Alpha"; break;
|
||||
case 6: R += "ScreenVideo2"; break;
|
||||
case 7: R += "AVC"; break;
|
||||
case 7: R += "H264"; break;
|
||||
default: R += "unknown"; break;
|
||||
}
|
||||
R += " video ";
|
||||
|
@ -245,6 +246,265 @@ FLV::Tag & FLV::Tag::operator= (const FLV::Tag& O){
|
|||
return *this;
|
||||
}//assignment operator
|
||||
|
||||
/// FLV loader function from DTSC.
|
||||
/// Takes the DTSC data and makes it into FLV.
|
||||
bool FLV::Tag::DTSCLoader(DTSC::Stream & S){
|
||||
switch (S.lastType()){
|
||||
case DTSC::VIDEO:
|
||||
len = S.lastData().length() + 16;
|
||||
if (S.metadata.getContentP("video") && S.metadata.getContentP("video")->getContentP("codec")){
|
||||
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H264"){len += 4;}
|
||||
}
|
||||
break;
|
||||
case DTSC::AUDIO:
|
||||
len = S.lastData().length() + 16;
|
||||
if (S.metadata.getContentP("audio") && S.metadata.getContentP("audio")->getContentP("codec")){
|
||||
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){len += 1;}
|
||||
}
|
||||
break;
|
||||
case DTSC::META:
|
||||
len = S.lastData().length() + 15;
|
||||
break;
|
||||
default://ignore all other types (there are currently no other types...)
|
||||
break;
|
||||
}
|
||||
if (len > 0){
|
||||
if (!data){
|
||||
data = (char*)malloc(len);
|
||||
buf = len;
|
||||
}else{
|
||||
if (buf < len){
|
||||
data = (char*)realloc(data, len);
|
||||
buf = len;
|
||||
}
|
||||
}
|
||||
switch (S.lastType()){
|
||||
case DTSC::VIDEO:
|
||||
if ((unsigned int)len == S.lastData().length() + 16){
|
||||
memcpy(data+12, S.lastData().c_str(), S.lastData().length());
|
||||
}else{
|
||||
memcpy(data+16, S.lastData().c_str(), S.lastData().length());
|
||||
if (S.getPacket().getContentP("nalu")){data[12] = 1;}else{data[12] = 2;}
|
||||
int offset = S.getPacket().getContentP("offset")->NumValue();
|
||||
data[13] = (offset >> 16) & 0xFF;
|
||||
data[14] = (offset >> 8) & 0XFF;
|
||||
data[15] = offset & 0xFF;
|
||||
}
|
||||
data[11] = 0;
|
||||
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H264"){data[11] += 7;}
|
||||
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H263"){data[11] += 2;}
|
||||
if (S.getPacket().getContentP("keyframe")){data[11] += 0x10;}
|
||||
if (S.getPacket().getContentP("interframe")){data[11] += 0x20;}
|
||||
if (S.getPacket().getContentP("disposableframe")){data[11] += 0x30;}
|
||||
break;
|
||||
case DTSC::AUDIO:
|
||||
if ((unsigned int)len == S.lastData().length() + 16){
|
||||
memcpy(data+12, S.lastData().c_str(), S.lastData().length());
|
||||
}else{
|
||||
memcpy(data+13, S.lastData().c_str(), S.lastData().length());
|
||||
data[12] = 1;//raw AAC data, not sequence header
|
||||
}
|
||||
data[11] = 0;
|
||||
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){data[11] += 0xA0;}
|
||||
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "MP3"){data[11] += 0x20;}
|
||||
if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 11025){data[11] += 0x04;}
|
||||
if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 22050){data[11] += 0x08;}
|
||||
if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 44100){data[11] += 0x0C;}
|
||||
if (S.metadata.getContentP("audio")->getContentP("size")->NumValue() == 16){data[11] += 0x02;}
|
||||
if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){data[11] += 0x01;}
|
||||
break;
|
||||
case DTSC::META:
|
||||
memcpy(data+11, S.lastData().c_str(), S.lastData().length());
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
setLen();
|
||||
switch (S.lastType()){
|
||||
case DTSC::VIDEO: data[0] = 0x09; break;
|
||||
case DTSC::AUDIO: data[0] = 0x08; break;
|
||||
case DTSC::META: data[0] = 0x12; break;
|
||||
default: break;
|
||||
}
|
||||
data[1] = ((len-15) >> 16) & 0xFF;
|
||||
data[2] = ((len-15) >> 8) & 0xFF;
|
||||
data[3] = (len-15) & 0xFF;
|
||||
tagTime(S.getPacket().getContentP("time")->NumValue());
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Helper function that properly sets the tag length from the internal len variable.
|
||||
void FLV::Tag::setLen(){
|
||||
int len4 = len - 4;
|
||||
int i = len-1;
|
||||
data[--i] = (len4) & 0xFF;
|
||||
len4 >>= 8;
|
||||
data[--i] = (len4) & 0xFF;
|
||||
len4 >>= 8;
|
||||
data[--i] = (len4) & 0xFF;
|
||||
len4 >>= 8;
|
||||
data[--i] = (len4) & 0xFF;
|
||||
}
|
||||
|
||||
/// FLV Video init data loader function from DTSC.
|
||||
/// Takes the DTSC Video init data and makes it into FLV.
|
||||
/// Assumes init data is available - so check before calling!
|
||||
bool FLV::Tag::DTSCVideoInit(DTSC::Stream & S){
|
||||
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H264"){
|
||||
len = S.metadata.getContentP("video")->getContentP("init")->StrValue().length() + 20;
|
||||
}
|
||||
if (len > 0){
|
||||
if (!data){
|
||||
data = (char*)malloc(len);
|
||||
buf = len;
|
||||
}else{
|
||||
if (buf < len){
|
||||
data = (char*)realloc(data, len);
|
||||
buf = len;
|
||||
}
|
||||
}
|
||||
memcpy(data+16, S.metadata.getContentP("video")->getContentP("init")->StrValue().c_str(), len-20);
|
||||
data[12] = 0;//H264 sequence header
|
||||
data[13] = 0;
|
||||
data[14] = 0;
|
||||
data[15] = 0;
|
||||
data[11] = 0x17;//H264 keyframe (0x07 & 0x10)
|
||||
}
|
||||
setLen();
|
||||
data[0] = 0x09;
|
||||
data[1] = ((len-15) >> 16) & 0xFF;
|
||||
data[2] = ((len-15) >> 8) & 0xFF;
|
||||
data[3] = (len-15) & 0xFF;
|
||||
tagTime(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// FLV Audio init data loader function from DTSC.
|
||||
/// Takes the DTSC Audio init data and makes it into FLV.
|
||||
/// Assumes init data is available - so check before calling!
|
||||
bool FLV::Tag::DTSCAudioInit(DTSC::Stream & S){
|
||||
len = 0;
|
||||
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){
|
||||
len = S.metadata.getContentP("audio")->getContentP("init")->StrValue().length() + 17;
|
||||
}
|
||||
if (len > 0){
|
||||
if (!data){
|
||||
data = (char*)malloc(len);
|
||||
buf = len;
|
||||
}else{
|
||||
if (buf < len){
|
||||
data = (char*)realloc(data, len);
|
||||
buf = len;
|
||||
}
|
||||
}
|
||||
memcpy(data+13, S.metadata.getContentP("audio")->getContentP("init")->StrValue().c_str(), len-17);
|
||||
data[12] = 0;//AAC sequence header
|
||||
data[11] = 0;
|
||||
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){data[11] += 0xA0;}
|
||||
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "MP3"){data[11] += 0x20;}
|
||||
if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 11000){data[11] += 0x04;}
|
||||
if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 22000){data[11] += 0x08;}
|
||||
if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 44000){data[11] += 0x0C;}
|
||||
if (S.metadata.getContentP("audio")->getContentP("size")->NumValue() == 16){data[11] += 0x02;}
|
||||
if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){data[11] += 0x01;}
|
||||
}
|
||||
setLen();
|
||||
switch (S.lastType()){
|
||||
case DTSC::VIDEO: data[0] = 0x09; break;
|
||||
case DTSC::AUDIO: data[0] = 0x08; break;
|
||||
case DTSC::META: data[0] = 0x12; break;
|
||||
default: break;
|
||||
}
|
||||
data[0] = 0x08;
|
||||
data[1] = ((len-15) >> 16) & 0xFF;
|
||||
data[2] = ((len-15) >> 8) & 0xFF;
|
||||
data[3] = (len-15) & 0xFF;
|
||||
tagTime(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// FLV metadata loader function from DTSC.
|
||||
/// Takes the DTSC metadata and makes it into FLV.
|
||||
/// Assumes metadata is available - so check before calling!
|
||||
bool FLV::Tag::DTSCMetaInit(DTSC::Stream & S){
|
||||
AMF::Object amfdata("root", AMF::AMF0_DDV_CONTAINER);
|
||||
|
||||
amfdata.addContent(AMF::Object("", "onMetaData"));
|
||||
amfdata.addContent(AMF::Object("", AMF::AMF0_ECMA_ARRAY));
|
||||
if (S.metadata.getContentP("video")){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("hasVideo", 1, AMF::AMF0_BOOL));
|
||||
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H264"){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 7, AMF::AMF0_NUMBER));
|
||||
}
|
||||
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "VP6"){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 4, AMF::AMF0_NUMBER));
|
||||
}
|
||||
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H263"){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 2, AMF::AMF0_NUMBER));
|
||||
}
|
||||
if (S.metadata.getContentP("video")->getContentP("width")){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("width", S.metadata.getContentP("video")->getContentP("width")->NumValue(), AMF::AMF0_NUMBER));
|
||||
}
|
||||
if (S.metadata.getContentP("video")->getContentP("height")){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("height", S.metadata.getContentP("video")->getContentP("height")->NumValue(), AMF::AMF0_NUMBER));
|
||||
}
|
||||
if (S.metadata.getContentP("video")->getContentP("fpks")){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("framerate", (double)S.metadata.getContentP("video")->getContentP("fpks")->NumValue() / 1000.0, AMF::AMF0_NUMBER));
|
||||
}
|
||||
if (S.metadata.getContentP("video")->getContentP("bps")){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("videodatarate", ((double)S.metadata.getContentP("video")->getContentP("bps")->NumValue() * 8.0) / 1024.0, AMF::AMF0_NUMBER));
|
||||
}
|
||||
}
|
||||
if (S.metadata.getContentP("audio")){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("hasAudio", 1, AMF::AMF0_BOOL));
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("audiodelay", 0, AMF::AMF0_NUMBER));
|
||||
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", 10, AMF::AMF0_NUMBER));
|
||||
}
|
||||
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "MP3"){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", 2, AMF::AMF0_NUMBER));
|
||||
}
|
||||
if (S.metadata.getContentP("audio")->getContentP("channels")){
|
||||
if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("stereo", 1, AMF::AMF0_BOOL));
|
||||
}else{
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("stereo", 0, AMF::AMF0_BOOL));
|
||||
}
|
||||
}
|
||||
if (S.metadata.getContentP("audio")->getContentP("rate")){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("audiosamplerate", S.metadata.getContentP("audio")->getContentP("rate")->NumValue(), AMF::AMF0_NUMBER));
|
||||
}
|
||||
if (S.metadata.getContentP("audio")->getContentP("size")){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("audiosamplesize", S.metadata.getContentP("audio")->getContentP("size")->NumValue(), AMF::AMF0_NUMBER));
|
||||
}
|
||||
if (S.metadata.getContentP("audio")->getContentP("bps")){
|
||||
amfdata.getContentP(1)->addContent(AMF::Object("audiodatarate", ((double)S.metadata.getContentP("audio")->getContentP("bps")->NumValue() * 8.0) / 1024.0, AMF::AMF0_NUMBER));
|
||||
}
|
||||
}
|
||||
|
||||
std::string tmp = amfdata.Pack();
|
||||
len = tmp.length() + 15;
|
||||
if (len > 0){
|
||||
if (!data){
|
||||
data = (char*)malloc(len);
|
||||
buf = len;
|
||||
}else{
|
||||
if (buf < len){
|
||||
data = (char*)realloc(data, len);
|
||||
buf = len;
|
||||
}
|
||||
}
|
||||
memcpy(data+11, tmp.c_str(), len-15);
|
||||
}
|
||||
setLen();
|
||||
data[0] = 0x12;
|
||||
data[1] = ((len-15) >> 16) & 0xFF;
|
||||
data[2] = ((len-15) >> 8) & 0xFF;
|
||||
data[3] = (len-15) & 0xFF;
|
||||
tagTime(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// FLV loader function from chunk.
|
||||
/// Copies the contents and wraps it in a FLV header.
|
||||
bool FLV::Tag::ChunkLoader(const RTMPStream::Chunk& O){
|
||||
|
@ -261,7 +521,7 @@ bool FLV::Tag::ChunkLoader(const RTMPStream::Chunk& O){
|
|||
}
|
||||
memcpy(data+11, &(O.data[0]), O.len);
|
||||
}
|
||||
((unsigned int *)(data+len-4))[0] = O.len;
|
||||
setLen();
|
||||
data[0] = O.msg_type_id;
|
||||
data[3] = O.len & 0xFF;
|
||||
data[2] = (O.len >> 8) & 0xFF;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
#include "socket.h"
|
||||
#include "dtsc.h"
|
||||
#include <string>
|
||||
|
||||
//forward declaration of RTMPStream::Chunk to avoid circular dependencies.
|
||||
|
@ -38,6 +39,10 @@ namespace FLV {
|
|||
Tag(const RTMPStream::Chunk& O); ///<Copy constructor from a RTMP chunk.
|
||||
//loader functions
|
||||
bool ChunkLoader(const RTMPStream::Chunk& O);
|
||||
bool DTSCLoader(DTSC::Stream & S);
|
||||
bool DTSCVideoInit(DTSC::Stream & S);
|
||||
bool DTSCAudioInit(DTSC::Stream & S);
|
||||
bool DTSCMetaInit(DTSC::Stream & S);
|
||||
bool MemLoader(char * D, unsigned int S, unsigned int & P);
|
||||
bool SockLoader(int sock);
|
||||
bool SockLoader(Socket::Connection sock);
|
||||
|
@ -46,6 +51,7 @@ namespace FLV {
|
|||
int buf; ///< Maximum length of buffer space.
|
||||
bool done; ///< Body reading done?
|
||||
unsigned int sofar; ///< How many bytes are read sofar?
|
||||
void setLen();
|
||||
//loader helper functions
|
||||
bool MemReadUntil(char * buffer, unsigned int count, unsigned int & sofar, char * D, unsigned int S, unsigned int & P);
|
||||
bool SockReadUntil(char * buffer, unsigned int count, unsigned int & sofar, Socket::Connection & sock);
|
||||
|
|
|
@ -270,7 +270,7 @@ bool Socket::Connection::write(const void * buffer, int len){
|
|||
/// \param buffer Location of the buffer to read to.
|
||||
/// \param len Amount of bytes to read.
|
||||
/// \returns True if the whole read was succesfull, false otherwise.
|
||||
bool Socket::Connection::read(void * buffer, int len){
|
||||
bool Socket::Connection::read(const void * buffer, int len){
|
||||
int sofar = 0;
|
||||
if (sock < 0){return false;}
|
||||
while (sofar != len){
|
||||
|
@ -309,9 +309,9 @@ bool Socket::Connection::read(void * buffer, int len){
|
|||
}//Socket::Connection::read
|
||||
|
||||
/// Read call that is compatible with file access syntax. This function simply calls the other read function.
|
||||
bool Socket::Connection::read(void * buffer, int width, int count){return read(buffer, width*count);}
|
||||
bool Socket::Connection::read(const void * buffer, int width, int count){return read(buffer, width*count);}
|
||||
/// Write call that is compatible with file access syntax. This function simply calls the other write function.
|
||||
bool Socket::Connection::write(void * buffer, int width, int count){return write(buffer, width*count);}
|
||||
bool Socket::Connection::write(const void * buffer, int width, int count){return write(buffer, width*count);}
|
||||
/// Write call that is compatible with std::string. This function simply calls the other write function.
|
||||
bool Socket::Connection::write(const std::string data){return write(data.c_str(), data.size());}
|
||||
|
||||
|
@ -320,7 +320,7 @@ bool Socket::Connection::write(const std::string data){return write(data.c_str()
|
|||
/// \param buffer Location of the buffer to write from.
|
||||
/// \param len Amount of bytes to write.
|
||||
/// \returns The amount of bytes actually written.
|
||||
int Socket::Connection::iwrite(void * buffer, int len){
|
||||
int Socket::Connection::iwrite(const void * buffer, int len){
|
||||
if (sock < 0){return 0;}
|
||||
int r = send(sock, buffer, len, 0);
|
||||
if (r < 0){
|
||||
|
|
|
@ -37,15 +37,15 @@ namespace Socket{
|
|||
bool canWrite(); ///< Calls poll() on the socket, checking if data can be written.
|
||||
signed int ready(); ///< Returns the ready-state for this socket.
|
||||
bool connected(); ///< Returns the connected-state for this socket.
|
||||
bool read(void * buffer, int len); ///< Reads data from socket.
|
||||
bool read(void * buffer, int width, int count); ///< Read call that is compatible with file access syntax.
|
||||
bool read(const void * buffer, int len); ///< Reads data from socket.
|
||||
bool read(const void * buffer, int width, int count); ///< Read call that is compatible with file access syntax.
|
||||
bool write(const void * buffer, int len); ///< Writes data to socket.
|
||||
bool write(void * buffer, int width, int count); ///< Write call that is compatible with file access syntax.
|
||||
bool write(const void * buffer, int width, int count); ///< Write call that is compatible with file access syntax.
|
||||
bool write(const std::string data); ///< Write call that is compatible with std::string.
|
||||
int iwrite(void * buffer, int len); ///< Incremental write call.
|
||||
int iwrite(const void * buffer, int len); ///< Incremental write call.
|
||||
int iread(void * buffer, int len); ///< Incremental read call.
|
||||
bool read(std::string & buffer); ///< Read call that is compatible with std::string.
|
||||
bool swrite(std::string & buffer); ///< Read call that is compatible with std::string.
|
||||
bool swrite(std::string & buffer); ///< Write call that is compatible with std::string.
|
||||
bool iread(std::string & buffer); ///< Incremental write call that is compatible with std::string.
|
||||
bool iwrite(std::string & buffer); ///< Write call that is compatible with std::string.
|
||||
void spool(); ///< Updates the downbuffer and upbuffer internal variables.
|
||||
|
|
Loading…
Add table
Reference in a new issue