Merge branch 'master' of github.com:DDVTECH/DMS

Conflicts:
	Buffer/main.cpp
This commit is contained in:
Thulinma 2012-04-03 20:35:55 +02:00
commit 80ac6ff8c5
21 changed files with 2305 additions and 705 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because one or more lines are too long

821
server-rel.html Normal file
View 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>

View file

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

View 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/

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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