///\brief Main function for the TS Connector ///\param conn A socket describing the connection the client. ///\param streamName The stream to connect to. ///\return The exit code of the connector. int tsConnector(Socket::Connection conn, std::string streamName, std::string trackIDs){ std::string ToPack; TS::Packet PackData; std::string DTMIData; int PacketNumber = 0; long long unsigned int TimeStamp = 0; int ThisNaluSize; char VideoCounter = 0; char AudioCounter = 0; bool WritePesHeader; bool IsKeyFrame; bool FirstKeyFrame = true; bool FirstIDRInKeyFrame; MP4::AVCC avccbox; bool haveAvcc = false; DTSC::Stream Strm; bool inited = false; Socket::Connection ss; while (conn.connected()){ if ( !inited){ ss = Util::Stream::getStream(streamName); if ( !ss.connected()){ #if DEBUG >= 1 fprintf(stderr, "Could not connect to server!\n"); #endif conn.close(); break; } if(trackIDs == ""){ // no track ids given? Find the first video and first audio track (if available) and use those! int videoID = -1; int audioID = -1; Strm.waitForMeta(ss); if (Strm.metadata.isMember("tracks")){ for (JSON::ObjIter trackIt = Strm.metadata["tracks"].ObjBegin(); trackIt != Strm.metadata["tracks"].ObjEnd(); trackIt++){ if (audioID == -1 && trackIt->second["type"].asString() == "audio"){ audioID = trackIt->second["trackid"].asInt(); if( trackIDs != ""){ trackIDs += " " + trackIt->second["trackid"].asString(); }else{ trackIDs = trackIt->second["trackid"].asString(); } } if (videoID == -1 && trackIt->second["type"].asString() == "video"){ videoID = trackIt->second["trackid"].asInt(); if( trackIDs != ""){ trackIDs += " " + trackIt->second["trackid"].asString(); }else{ trackIDs = trackIt->second["trackid"].asString(); } } } // for iterator } // if isMember("tracks") } // if trackIDs == "" std::string cmd = "t " + trackIDs + "\ns 0\np\n"; ss.SendNow( cmd ); inited = true; } if (ss.spool()){ while (Strm.parsePacket(ss.Received())){ std::stringstream TSBuf; Socket::Buffer ToPack; //write PAT and PMT TS packets if (PacketNumber == 0){ PackData.DefaultPAT(); TSBuf.write(PackData.ToString(), 188); PackData.DefaultPMT(); TSBuf.write(PackData.ToString(), 188); PacketNumber += 2; } int PIDno = 0; char * ContCounter = 0; if (Strm.lastType() == DTSC::VIDEO){ if ( !haveAvcc){ avccbox.setPayload(Strm.getTrackById(Strm.getPacket()["trackid"].asInt())["init"].asString()); haveAvcc = true; } IsKeyFrame = Strm.getPacket().isMember("keyframe"); if (IsKeyFrame){ TimeStamp = (Strm.getPacket()["time"].asInt() * 27000); } ToPack.append(avccbox.asAnnexB()); while (Strm.lastData().size() > 4){ ThisNaluSize = (Strm.lastData()[0] << 24) + (Strm.lastData()[1] << 16) + (Strm.lastData()[2] << 8) + Strm.lastData()[3]; Strm.lastData().replace(0, 4, TS::NalHeader, 4); if (ThisNaluSize + 4 == Strm.lastData().size()){ ToPack.append(Strm.lastData()); break; }else{ ToPack.append(Strm.lastData().c_str(), ThisNaluSize + 4); Strm.lastData().erase(0, ThisNaluSize + 4); } } ToPack.prepend(TS::Packet::getPESVideoLeadIn(0ul, Strm.getPacket()["time"].asInt() * 90)); PIDno = 0x100 - 1 + Strm.getPacket()["trackid"].asInt(); ContCounter = &VideoCounter; }else if (Strm.lastType() == DTSC::AUDIO){ ToPack.append(TS::GetAudioHeader(Strm.lastData().size(), Strm.getTrackById(Strm.getPacket()["trackid"].asInt())["init"].asString())); ToPack.append(Strm.lastData()); ToPack.prepend(TS::Packet::getPESAudioLeadIn(ToPack.bytes(1073741824ul), Strm.getPacket()["time"].asInt() * 90)); PIDno = 0x100 - 1 + Strm.getPacket()["trackid"].asInt(); ContCounter = &AudioCounter; IsKeyFrame = false; } //initial packet PackData.Clear(); PackData.PID(PIDno); PackData.ContinuityCounter(( *ContCounter)++); PackData.UnitStart(1); if (IsKeyFrame){ PackData.RandomAccess(1); PackData.PCR(TimeStamp); } unsigned int toSend = PackData.AddStuffing(ToPack.bytes(184)); std::string gonnaSend = ToPack.remove(toSend); PackData.FillFree(gonnaSend); TSBuf.write(PackData.ToString(), 188); PacketNumber++; //rest of packets while (ToPack.size()){ PackData.Clear(); PackData.PID(PIDno); PackData.ContinuityCounter(( *ContCounter)++); toSend = PackData.AddStuffing(ToPack.bytes(184)); gonnaSend = ToPack.remove(toSend); PackData.FillFree(gonnaSend); TSBuf.write(PackData.ToString(), 188); PacketNumber++; } TSBuf.flush(); if (TSBuf.str().size()){ conn.SendNow(TSBuf.str().c_str(), TSBuf.str().size()); TSBuf.str(""); } TSBuf.str(""); PacketNumber = 0; } }else{ Util::sleep(1000); conn.spool(); } } return 0; }
void OutTS::sendNext(){ Socket::Buffer ToPack; char * ContCounter = 0; bool IsKeyFrame = false; char * dataPointer = 0; int dataLen = 0; currentPacket.getString("data", dataPointer, dataLen); //detect packet type, and put converted data into ToPack. if (myMeta.tracks[currentPacket.getTrackId()].type == "video"){ ToPack.append(TS::Packet::getPESVideoLeadIn(0ul, currentPacket.getTime() * 90)); IsKeyFrame = currentPacket.getInt("keyframe"); if (IsKeyFrame){ if (!haveAvcc){ avccbox.setPayload(myMeta.tracks[currentPacket.getTrackId()].init); haveAvcc = true; } ToPack.append(avccbox.asAnnexB()); } unsigned int i = 0; while (i + 4 < (unsigned int)dataLen){ unsigned int ThisNaluSize = (dataPointer[i] << 24) + (dataPointer[i+1] << 16) + (dataPointer[i+2] << 8) + dataPointer[i+3]; if (ThisNaluSize + i + 4 > (unsigned int)dataLen){ DEBUG_MSG(DLVL_WARN, "Too big NALU detected (%u > %d) - skipping!", ThisNaluSize + i + 4, dataLen); break; } ToPack.append("\000\000\000\001", 4); i += 4; ToPack.append(dataPointer + i, ThisNaluSize); i += ThisNaluSize; } ContCounter = &VideoCounter; }else if (myMeta.tracks[currentPacket.getTrackId()].type == "audio"){ ToPack.append(TS::Packet::getPESAudioLeadIn(7+dataLen, currentPacket.getTime() * 90)); ToPack.append(TS::GetAudioHeader(dataLen, myMeta.tracks[currentPacket.getTrackId()].init)); ToPack.append(dataPointer, dataLen); ContCounter = &AudioCounter; } bool first = true; //send TS packets while (ToPack.size()){ PackData.Clear(); /// \todo Update according to sendHeader()'s generated data. //0x100 - 1 + currentPacket.getTrackId() if (myMeta.tracks[currentPacket.getTrackId()].type == "video"){ PackData.PID(0x100); }else{ PackData.PID(0x101); } PackData.ContinuityCounter((*ContCounter)++); if (first){ PackData.UnitStart(1); if (IsKeyFrame){ PackData.RandomAccess(1); PackData.PCR(currentPacket.getTime() * 27000); } first = false; } unsigned int toSend = PackData.AddStuffing(ToPack.bytes(184)); std::string gonnaSend = ToPack.remove(toSend); PackData.FillFree(gonnaSend); myConn.SendNow(PackData.ToString(), 188); } }
///\brief Main function for the TS Connector ///\param conn A socket describing the connection the client. ///\param streamName The stream to connect to. ///\return The exit code of the connector. int tsConnector(Socket::Connection conn, std::string streamName){ std::string ToPack; TS::Packet PackData; std::string DTMIData; int PacketNumber = 0; long long unsigned int TimeStamp = 0; int ThisNaluSize; char VideoCounter = 0; char AudioCounter = 0; bool WritePesHeader; bool IsKeyFrame; bool FirstKeyFrame = true; bool FirstIDRInKeyFrame; MP4::AVCC avccbox; bool haveAvcc = false; DTSC::Stream Strm; bool inited = false; Socket::Connection ss; while (conn.connected()){ if ( !inited){ ss = Util::Stream::getStream(streamName); if ( !ss.connected()){ #if DEBUG >= 1 fprintf(stderr, "Could not connect to server!\n"); #endif conn.close(); break; } ss.SendNow("p\n"); inited = true; } if (ss.spool()){ while (Strm.parsePacket(ss.Received())){ if ( !haveAvcc){ avccbox.setPayload(Strm.metadata["video"]["init"].asString()); haveAvcc = true; } std::stringstream TSBuf; Socket::Buffer ToPack; //write PAT and PMT TS packets if (PacketNumber == 0){ PackData.DefaultPAT(); TSBuf.write(PackData.ToString(), 188); PackData.DefaultPMT(); TSBuf.write(PackData.ToString(), 188); PacketNumber += 2; } int PIDno = 0; char * ContCounter = 0; if (Strm.lastType() == DTSC::VIDEO){ IsKeyFrame = Strm.getPacket(0).isMember("keyframe"); if (IsKeyFrame){ TimeStamp = (Strm.getPacket(0)["time"].asInt() * 27000); } ToPack.append(avccbox.asAnnexB()); while (Strm.lastData().size()){ ThisNaluSize = (Strm.lastData()[0] << 24) + (Strm.lastData()[1] << 16) + (Strm.lastData()[2] << 8) + Strm.lastData()[3]; Strm.lastData().replace(0, 4, TS::NalHeader, 4); if (ThisNaluSize + 4 == Strm.lastData().size()){ ToPack.append(Strm.lastData()); break; }else{ ToPack.append(Strm.lastData().c_str(), ThisNaluSize + 4); Strm.lastData().erase(0, ThisNaluSize + 4); } } ToPack.prepend(TS::Packet::getPESVideoLeadIn(0ul, Strm.getPacket(0)["time"].asInt() * 90)); PIDno = 0x100; ContCounter = &VideoCounter; }else if (Strm.lastType() == DTSC::AUDIO){ ToPack.append(TS::GetAudioHeader(Strm.lastData().size(), Strm.metadata["audio"]["init"].asString())); ToPack.append(Strm.lastData()); ToPack.prepend(TS::Packet::getPESAudioLeadIn(ToPack.bytes(1073741824ul), Strm.getPacket(0)["time"].asInt() * 90)); PIDno = 0x101; ContCounter = &AudioCounter; } //initial packet PackData.Clear(); PackData.PID(PIDno); PackData.ContinuityCounter(( *ContCounter)++); PackData.UnitStart(1); if (IsKeyFrame){ PackData.RandomAccess(1); PackData.PCR(TimeStamp); } unsigned int toSend = PackData.AddStuffing(ToPack.bytes(184)); std::string gonnaSend = ToPack.remove(toSend); PackData.FillFree(gonnaSend); TSBuf.write(PackData.ToString(), 188); PacketNumber++; //rest of packets while (ToPack.size()){ PackData.Clear(); PackData.PID(PIDno); PackData.ContinuityCounter(( *ContCounter)++); toSend = PackData.AddStuffing(ToPack.bytes(184)); gonnaSend = ToPack.remove(toSend); PackData.FillFree(gonnaSend); TSBuf.write(PackData.ToString(), 188); PacketNumber++; } TSBuf.flush(); if (TSBuf.str().size()){ conn.SendNow(TSBuf.str().c_str(), TSBuf.str().size()); TSBuf.str(""); } TSBuf.str(""); PacketNumber = 0; } } } return 0; }
///\brief Gets and parses one RTMP chunk at a time. ///\param inputBuffer A buffer filled with chunk data. void parseChunk(Socket::Buffer & inputBuffer){ //for DTSC conversion static JSON::Value meta_out; static std::stringstream prebuffer; // Temporary buffer before sending real data static bool sending = false; static unsigned int counter = 0; //for chunk parsing static RTMPStream::Chunk next; static FLV::Tag F; static AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER); static AMF::Object amfelem("empty", AMF::AMF0_DDV_CONTAINER); static AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER); static AMF::Object3 amf3elem("empty", AMF::AMF3_DDV_CONTAINER); while (next.Parse(inputBuffer)){ //send ACK if we received a whole window if ((RTMPStream::rec_cnt - RTMPStream::rec_window_at > RTMPStream::rec_window_size)){ RTMPStream::rec_window_at = RTMPStream::rec_cnt; Socket.Send(RTMPStream::SendCTL(3, RTMPStream::rec_cnt)); //send ack (msg 3) } switch (next.msg_type_id){ case 0: //does not exist #if DEBUG >= 2 fprintf(stderr, "UNKN: Received a zero-type message. Possible data corruption? Aborting!\n"); #endif while (inputBuffer.size()){ inputBuffer.get().clear(); } ss.close(); Socket.close(); break; //happens when connection breaks unexpectedly case 1: //set chunk size RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str()); #if DEBUG >= 5 fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max); #endif break; case 2: //abort message - we ignore this one #if DEBUG >= 5 fprintf(stderr, "CTRL: Abort message\n"); #endif //4 bytes of stream id to drop break; case 3: //ack #if DEBUG >= 8 fprintf(stderr, "CTRL: Acknowledgement\n"); #endif RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str()); RTMPStream::snd_window_at = RTMPStream::snd_cnt; break; case 4: { //2 bytes event type, rest = event data //types: //0 = stream begin, 4 bytes ID //1 = stream EOF, 4 bytes ID //2 = stream dry, 4 bytes ID //3 = setbufferlen, 4 bytes ID, 4 bytes length //4 = streamisrecorded, 4 bytes ID //6 = pingrequest, 4 bytes data //7 = pingresponse, 4 bytes data //we don't need to process this #if DEBUG >= 5 short int ucmtype = ntohs(*(short int*)next.data.c_str()); switch (ucmtype){ case 0: fprintf(stderr, "CTRL: UCM StreamBegin %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break; case 1: fprintf(stderr, "CTRL: UCM StreamEOF %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break; case 2: fprintf(stderr, "CTRL: UCM StreamDry %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break; case 3: fprintf(stderr, "CTRL: UCM SetBufferLength %i %i\n", ntohl(*((int*)(next.data.c_str()+2))), ntohl(*((int*)(next.data.c_str()+6)))); break; case 4: fprintf(stderr, "CTRL: UCM StreamIsRecorded %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break; case 6: fprintf(stderr, "CTRL: UCM PingRequest %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break; case 7: fprintf(stderr, "CTRL: UCM PingResponse %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break; default: fprintf(stderr, "CTRL: UCM Unknown (%hi)\n", ucmtype); break; } #endif } break; case 5: //window size of other end #if DEBUG >= 5 fprintf(stderr, "CTRL: Window size\n"); #endif RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str()); RTMPStream::rec_window_at = RTMPStream::rec_cnt; Socket.Send(RTMPStream::SendCTL(3, RTMPStream::rec_cnt)); //send ack (msg 3) break; case 6: #if DEBUG >= 5 fprintf(stderr, "CTRL: Set peer bandwidth\n"); #endif //4 bytes window size, 1 byte limit type (ignored) RTMPStream::snd_window_size = ntohl(*(int*)next.data.c_str()); Socket.Send(RTMPStream::SendCTL(5, RTMPStream::snd_window_size)); //send window acknowledgement size (msg 5) break; case 8: //audio data case 9: //video data case 18: //meta data if (ss.connected()){ if (streamReset){ //reset push data to empty, in case stream properties change meta_out.null(); prebuffer.str(""); sending = false; counter = 0; streamReset = false; } F.ChunkLoader(next); JSON::Value pack_out = F.toJSON(meta_out); if ( !pack_out.isNull()){ if ( !sending){ counter++; if (counter > 8){ sending = true; ss.SendNow(meta_out.toNetPacked()); ss.SendNow(prebuffer.str().c_str(), prebuffer.str().size()); //write buffer prebuffer.str(""); //clear buffer ss.SendNow(pack_out.toNetPacked()); }else{ prebuffer << pack_out.toNetPacked(); } }else{ ss.SendNow(pack_out.toNetPacked()); } } }else{ #if DEBUG >= 5 fprintf(stderr, "Received useless media data\n"); #endif Socket.close(); } break; case 15: #if DEBUG >= 5 fprintf(stderr, "Received AFM3 data message\n"); #endif break; case 16: #if DEBUG >= 5 fprintf(stderr, "Received AFM3 shared object\n"); #endif break; case 17: { #if DEBUG >= 5 fprintf(stderr, "Received AFM3 command message\n"); #endif if (next.data[0] != 0){ next.data = next.data.substr(1); amf3data = AMF::parse3(next.data); #if DEBUG >= 5 amf3data.Print(); #endif }else{ #if DEBUG >= 5 fprintf(stderr, "Received AFM3-0 command message\n"); #endif next.data = next.data.substr(1); amfdata = AMF::parse(next.data); parseAMFCommand(amfdata, 17, next.msg_stream_id); } //parsing AMF0-style } break; case 19: #if DEBUG >= 5 fprintf(stderr, "Received AFM0 shared object\n"); #endif break; case 20: { //AMF0 command message amfdata = AMF::parse(next.data); parseAMFCommand(amfdata, 20, next.msg_stream_id); } break; case 22: #if DEBUG >= 5 fprintf(stderr, "Received aggregate message\n"); #endif break; default: #if DEBUG >= 1 fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n"); #endif stopParsing = true; break; } } } //parseChunk