///\brief Sends a RTMP command either in AMF or AMF3 mode. ///\param amfReply The data to be sent over RTMP. ///\param messageType The type of message. ///\param streamId The ID of the AMF stream. void sendCommand(AMF::Object & amfReply, int messageType, int streamId){ #if DEBUG >= 8 std::cerr << amfReply.Print() << std::endl; #endif if (messageType == 17){ Socket.SendNow(RTMPStream::SendChunk(3, messageType, streamId, (char)0 + amfReply.Pack())); }else{ Socket.SendNow(RTMPStream::SendChunk(3, messageType, streamId, amfReply.Pack())); } } //sendCommand
void Connector_RTMP::sendCommand(AMF::Object & amfreply, int messagetype, int stream_id){ #if DEBUG >= 4 std::cerr << amfreply.Print() << std::endl; #endif if (messagetype == 17){ Socket.SendNow(RTMPStream::SendChunk(3, messagetype, stream_id, (char)0+amfreply.Pack())); }else{ Socket.SendNow(RTMPStream::SendChunk(3, messagetype, stream_id, amfreply.Pack())); } }//sendCommand
///\brief Debugging tool for AMF data. /// /// Expects AMF data through stdin, outputs human-readable information to stderr. ///\return The return code of the analyser. int analyseAMF(){ std::string amfBuffer; //Read all of std::cin to amfBuffer while (std::cin.good()){ amfBuffer += std::cin.get(); } //Strip the invalid last character amfBuffer.erase((amfBuffer.end() - 1)); //Parse into an AMF::Object AMF::Object amfData = AMF::parse(amfBuffer); //Print the output. std::cerr << amfData.Print() << std::endl; return 0; }
/// Returns a std::string describing the tag in detail. /// The string includes information about whether the tag is /// audio, video or metadata, what encoding is used, and the details /// of the encoding itself. std::string FLV::Tag::tagType() { std::stringstream R; R << len << " bytes of "; switch (data[0]) { case 0x09: R << getVideoCodec() << " video "; switch (data[11] & 0xF0) { case 0x10: R << "keyframe"; break; case 0x20: R << "iframe"; break; case 0x30: R << "disposableiframe"; break; case 0x40: R << "generatedkeyframe"; break; case 0x50: R << "videoinfo"; break; } if ((data[11] & 0x0F) == 7) { switch (data[12]) { case 0: R << " header"; break; case 1: R << " NALU"; break; case 2: R << " endofsequence"; break; } } break; case 0x08: R << getAudioCodec(); switch (data[11] & 0x0C) { case 0x0: R << " 5.5kHz"; break; case 0x4: R << " 11kHz"; break; case 0x8: R << " 22kHz"; break; case 0xC: R << " 44kHz"; break; } switch (data[11] & 0x02) { case 0: R << " 8bit"; break; case 2: R << " 16bit"; break; } switch (data[11] & 0x01) { case 0: R << " mono"; break; case 1: R << " stereo"; break; } R << " audio"; if ((data[12] == 0) && ((data[11] & 0xF0) == 0xA0)) { R << " initdata"; } break; case 0x12: { R << "(meta)data: "; AMF::Object metadata = AMF::parse((unsigned char *)data + 11, len - 15); R << metadata.Print(); break; } default: R << "unknown"; break; } return R.str(); } //FLV::Tag::tagtype
void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int stream_id){ #if DEBUG >= 4 fprintf(stderr, "Received command: %s\n", amfdata.Print().c_str()); #endif #if DEBUG >= 3 fprintf(stderr, "AMF0 command: %s\n", amfdata.getContentP(0)->StrValue().c_str()); #endif if (amfdata.getContentP(0)->StrValue() == "connect"){ double objencoding = 0; if (amfdata.getContentP(2)->getContentP("objectEncoding")){ objencoding = amfdata.getContentP(2)->getContentP("objectEncoding")->NumValue(); } #if DEBUG >= 4 int tmpint; if (amfdata.getContentP(2)->getContentP("videoCodecs")){ tmpint = (int)amfdata.getContentP(2)->getContentP("videoCodecs")->NumValue(); if (tmpint & 0x04){fprintf(stderr, "Sorensen video support detected\n");} if (tmpint & 0x80){fprintf(stderr, "H264 video support detected\n");} } if (amfdata.getContentP(2)->getContentP("audioCodecs")){ tmpint = (int)amfdata.getContentP(2)->getContentP("audioCodecs")->NumValue(); if (tmpint & 0x04){fprintf(stderr, "MP3 audio support detected\n");} if (tmpint & 0x400){fprintf(stderr, "AAC audio support detected\n");} } #endif RTMPStream::chunk_snd_max = 4096; Socket.Send(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max));//send chunk size max (msg 1) Socket.Send(RTMPStream::SendCTL(5, RTMPStream::snd_window_size));//send window acknowledgement size (msg 5) Socket.Send(RTMPStream::SendCTL(6, RTMPStream::rec_window_size));//send rec window acknowledgement size (msg 6) Socket.Send(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1 //send a _result reply AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); amfreply.addContent(AMF::Object("", "_result"));//result success amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(AMF::Object(""));//server properties amfreply.getContentP(2)->addContent(AMF::Object("fmsVer", "FMS/3,5,5,2004")); amfreply.getContentP(2)->addContent(AMF::Object("capabilities", (double)31)); amfreply.getContentP(2)->addContent(AMF::Object("mode", (double)1)); amfreply.addContent(AMF::Object(""));//info amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); amfreply.getContentP(3)->addContent(AMF::Object("code", "NetConnection.Connect.Success")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Connection succeeded.")); amfreply.getContentP(3)->addContent(AMF::Object("clientid", 1337)); amfreply.getContentP(3)->addContent(AMF::Object("objectEncoding", objencoding)); //amfreply.getContentP(3)->addContent(AMF::Object("data", AMF::AMF0_ECMA_ARRAY)); //amfreply.getContentP(3)->getContentP(4)->addContent(AMF::Object("version", "3,5,4,1004")); sendCommand(amfreply, messagetype, stream_id); //send onBWDone packet - no clue what it is, but real server sends it... //amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER); //amfreply.addContent(AMF::Object("", "onBWDone"));//result //amfreply.addContent(amfdata.getContent(1));//same transaction ID //amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null //sendCommand(amfreply, messagetype, stream_id); return; }//connect if (amfdata.getContentP(0)->StrValue() == "createStream"){ //send a _result reply AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); amfreply.addContent(AMF::Object("", "_result"));//result success amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)1));//stream ID - we use 1 sendCommand(amfreply, messagetype, stream_id); Socket.Send(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1 return; }//createStream if ((amfdata.getContentP(0)->StrValue() == "closeStream") || (amfdata.getContentP(0)->StrValue() == "deleteStream")){ if (SS.connected()){SS.close();} return; } if ((amfdata.getContentP(0)->StrValue() == "getStreamLength") || (amfdata.getContentP(0)->StrValue() == "getMovLen")){ //send a _result reply AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); amfreply.addContent(AMF::Object("", "_result"));//result success amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)0));//zero length sendCommand(amfreply, messagetype, stream_id); return; }//getStreamLength if ((amfdata.getContentP(0)->StrValue() == "publish")){ if (amfdata.getContentP(3)){ streamname = amfdata.getContentP(3)->StrValue(); /// \todo implement push for MistPlayer or restrict and change to getLive SS = Util::Stream::getStream(streamname); if (!SS.connected()){ #if DEBUG >= 1 fprintf(stderr, "Could not connect to server!\n"); #endif Socket.close();//disconnect user return; } SS.Send("P "); SS.Send(Socket.getHost().c_str()); SS.Send("\n"); nostats = true; #if DEBUG >= 4 fprintf(stderr, "Connected to buffer, starting to send data...\n"); #endif } //send a _result reply AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); amfreply.addContent(AMF::Object("", "_result"));//result success amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", 1, AMF::AMF0_BOOL));//publish success? sendCommand(amfreply, messagetype, stream_id); Socket.Send(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1 //send a status reply amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER); amfreply.addContent(AMF::Object("", "onStatus"));//status reply amfreply.addContent(AMF::Object("", 0, AMF::AMF0_NUMBER));//same transaction ID amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object(""));//info amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Publish.Start")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Stream is now published!")); amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); sendCommand(amfreply, messagetype, stream_id); return; }//getStreamLength if (amfdata.getContentP(0)->StrValue() == "checkBandwidth"){ //send a _result reply AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); amfreply.addContent(AMF::Object("", "_result"));//result success amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info sendCommand(amfreply, messagetype, stream_id); return; }//checkBandwidth if ((amfdata.getContentP(0)->StrValue() == "play") || (amfdata.getContentP(0)->StrValue() == "play2")){ //set reply number and stream name, actual reply is sent up in the SS.spool() handler play_trans = amfdata.getContentP(1)->NumValue(); play_msgtype = messagetype; play_streamid = stream_id; streamname = amfdata.getContentP(3)->StrValue(); Connector_RTMP::ready4data = true;//start sending video data! return; }//play if ((amfdata.getContentP(0)->StrValue() == "seek")){ //set reply number and stream name, actual reply is sent up in the SS.spool() handler play_trans = amfdata.getContentP(1)->NumValue(); play_msgtype = messagetype; play_streamid = stream_id; stream_inited = false; AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); amfreply.addContent(AMF::Object("", "onStatus"));//status reply amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object(""));//info amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Seek.Notify")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Seeking to the specified time")); amfreply.getContentP(3)->addContent(AMF::Object("details", "DDV")); amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); sendCommand(amfreply, play_msgtype, play_streamid); SS.Send("s "); SS.Send(JSON::Value((long long int)amfdata.getContentP(3)->NumValue()).asString().c_str()); SS.Send("\n"); return; }//seek if ((amfdata.getContentP(0)->StrValue() == "pauseRaw") || (amfdata.getContentP(0)->StrValue() == "pause")){ if (amfdata.getContentP(3)->NumValue()){ SS.Send("q\n");//quit playing //send a status reply AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); amfreply.addContent(AMF::Object("", "onStatus"));//status reply amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object(""));//info amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Pause.Notify")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Pausing playback")); amfreply.getContentP(3)->addContent(AMF::Object("details", "DDV")); amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); sendCommand(amfreply, play_msgtype, play_streamid); }else{ SS.Send("p\n");//start playing //send a status reply AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); amfreply.addContent(AMF::Object("", "onStatus"));//status reply amfreply.addContent(amfdata.getContent(1));//same transaction ID amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info amfreply.addContent(AMF::Object(""));//info amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Unpause.Notify")); amfreply.getContentP(3)->addContent(AMF::Object("description", "Resuming playback")); amfreply.getContentP(3)->addContent(AMF::Object("details", "DDV")); amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); sendCommand(amfreply, play_msgtype, play_streamid); } return; }//seek #if DEBUG >= 2 fprintf(stderr, "AMF0 command not processed! :(\n"); #endif }//parseAMFCommand