diff options
Diffstat (limited to 'TAO/orbsvcs/orbsvcs/AV/RTCP.cpp')
-rw-r--r-- | TAO/orbsvcs/orbsvcs/AV/RTCP.cpp | 826 |
1 files changed, 826 insertions, 0 deletions
diff --git a/TAO/orbsvcs/orbsvcs/AV/RTCP.cpp b/TAO/orbsvcs/orbsvcs/AV/RTCP.cpp new file mode 100644 index 00000000000..e0466db3a3c --- /dev/null +++ b/TAO/orbsvcs/orbsvcs/AV/RTCP.cpp @@ -0,0 +1,826 @@ +/** + * Copyright (c) 1994-1995 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and the Network Research Group at + * Lawrence Berkeley Laboratory. + * 4. Neither the name of the University nor of the Laboratory may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +// $Id$ +#include "orbsvcs/AV/ntp-time.h" +#include "orbsvcs/AV/RTCP.h" +#include "orbsvcs/AV/media_timer.h" +#include "tao/debug.h" +#include "orbsvcs/AV/global.h" +#include "orbsvcs/AV/md5.h" + +#include "orbsvcs/AV/RTCP_Packet.h" +#include "ace/OS_NS_time.h" +#include "ace/OS_NS_strings.h" + +TAO_BEGIN_VERSIONED_NAMESPACE_DECL + +int +TAO_AV_RTCP_Callback::receive_control_frame (ACE_Message_Block *data, + const ACE_Addr &peer_address) +{ + int length = static_cast<int> (data->length ()); + int more = length; + char *buf_ptr = data->rd_ptr (); + char first_rtcp_packet = 1; + RTCP_Channel_In *c; + + // This code performs the RTCP Header validity checks detailed in RFC 1889 + // Appendix A.2 + + while (more > 0) + { + // the second byte of the control packet is the type + switch ((unsigned char)buf_ptr[length - more + 1]) + { + case RTCP_PT_SR: + { + RTCP_SR_Packet sr(&buf_ptr[length-more], + &more); + + if (!sr.is_valid(first_rtcp_packet)) + ACE_DEBUG ((LM_DEBUG, + "TAO_AV_RTCP_Callback::receive_control_frame - " + "warning invalid rtcp packet\n")); + + if (this->inputs_.find (sr.ssrc (), c) == -1) + { + ACE_NEW_RETURN (c, + RTCP_Channel_In (sr.ssrc (), + &peer_address), + -1); + this->inputs_.bind (sr.ssrc (), c); + } + c->updateStatistics (&sr); + + if (TAO_debug_level > 0) + sr.dump (); + break; + } + case RTCP_PT_RR: + { + RTCP_RR_Packet rr(&buf_ptr[length-more], + &more); + + if (!rr.is_valid(first_rtcp_packet)) + ACE_DEBUG ((LM_DEBUG, + "TAO_AV_RTCP_Callback::receive_control_frame - " + "warning invalid rtcp packet\n")); + + if (this->inputs_.find (rr.ssrc (), c) == -1) + { + ACE_NEW_RETURN (c, + RTCP_Channel_In (rr.ssrc (), + &peer_address), + -1); + this->inputs_.bind (rr.ssrc (), c); + } + + c->updateStatistics (&rr); + + if (TAO_debug_level > 0) + rr.dump (); + break; + } + case RTCP_PT_SDES: + { + RTCP_SDES_Packet sdes (&buf_ptr[length-more], + &more); + + if (!sdes.is_valid(first_rtcp_packet)) + ACE_DEBUG ((LM_DEBUG, + "TAO_AV_RTCP_Callback::receive_control_frame - " + "warning invalid rtcp packet\n")); + + if (TAO_debug_level > 0) + sdes.dump (); + break; + } + case RTCP_PT_BYE: + { + RTCP_BYE_Packet bye (&buf_ptr[length-more], + &more); + + if (!bye.is_valid(first_rtcp_packet)) + ACE_DEBUG ((LM_DEBUG, + "TAO_AV_RTCP_Callback::receive_control_frame - " + "warning invalid rtcp packet\n")); + + // Inform the listener that a source(s) has left the session + ACE_UINT32 *ssrc_list; + unsigned char length; + + bye.ssrc_list(&ssrc_list, length); + + for (int i=0; i<length; i++) + { + RTCP_Channel_In *c = 0; + + // remove the channel from the list + this->inputs_.unbind(ssrc_list[i], c); + + if (c != 0) + delete c; + } + + if (TAO_debug_level > 0) + bye.dump (); + + break; + } + case RTCP_PT_APP: + // If we receive one of these, ignore it. + ACE_DEBUG ((LM_DEBUG, + "TAO_AV_RTCP_Callback::receive_control_frame - " + "APP packet - ignore\n")); + more -= (4 + (ACE_UINT16)buf_ptr[length - more + 2]); + break; + default: + ACE_DEBUG ((LM_DEBUG, + "TAO_AV_RTCP_Callback::receive_control_frame - " + "UNKNOWN packet type %u; ignore the rest\n", + (int)buf_ptr[length - more + 1])); + more = 0; + } + + first_rtcp_packet = 0; + + } + + if (more != 0) + ACE_DEBUG ((LM_DEBUG, + "TAO_AV_RTCP_Callback::receive_control_frame - " + "Error in overall packet length\n")); + return 0; +} + +ACE_INT32 random32 (int); + +ACE_UINT32 +TAO_AV_RTCP::alloc_srcid (ACE_UINT32 addr) +{ + md5_string s; + + s.type = addr; + s.tv = ACE_OS::gettimeofday (); + s.pid = ACE_OS::getpid(); + s.pgid = ACE_OS::getpgid(s.pid); + s.ppid = ACE_OS::getppid(); + s.uid = ACE_OS::getuid(); + s.gid = ACE_OS::getgid(); + + unsigned char *string_val = (unsigned char *) &s; + int length = sizeof(s); + + MD5_CTX context; + union + { + char c[16]; + u_long x[4]; + } digest; + ACE_UINT32 r; + int i; + + MD5Init (&context); + MD5Update (&context, string_val, length); + MD5Final ((unsigned char*)&digest, &context); + r=0; + for (i=0; i<3; i++) + r ^= digest.x[i]; + + return r; + +/* used to be this + ACE_Time_Value tv = ACE_OS::gettimeofday (); + ACE_UINT32 srcid = ACE_UINT32 (tv.sec () + tv.usec ()); + srcid += (ACE_UINT32)ACE_OS::getuid(); + srcid += (ACE_UINT32)ACE_OS::getpid(); + srcid += addr; + return (srcid); +*/ +} + + +double +TAO_AV_RTCP::rtcp_interval (int members, + int senders, + double rtcp_bw, + int we_sent, + int packet_size, + int *avg_rtcp_size, + int initial) +{ + // Minimum time between RTCP packets from this site (in sec.). + // This time prevents the reports from 'clumping' when sessions + // are small and the law of large numbers isn't helping to smooth + // out the traffic. It also keeps the report interval from + // becoming ridiculously small during transient outages like a + // network partition. +// double const RTCP_MIN_TIME = 5.0; (from RTP.h) + + // Fraction of the RTCP bandwidth to be shared among active + // senders. (This fraction was chosen so that in a typical + // session with one or two active senders, the computed report + // time would be roughly equal to the minimum report time so that + // we don't unnecessarily slow down receiver reports.) The + // receiver fraction must be 1 - the sender fraction. +// double const RTCP_SENDER_BW_FRACTION = 0.25; (from RTP.h) +// double const RTCP_RCVR_BW_FRACTION = (1-RTCP_SENDER_BW_FRACTION); (from RTP.h) + + // Gain (smoothing constant) for the low-pass filter that + // estimates the average RTCP packet size +// double const RTCP_SIZE_GAIN = (1.0/16.0); (from RTP.h) + + double t; + double rtcp_min_time = RTCP_MIN_RPT_TIME; + int n; // number of members for computation + + // Very first call at application start-up uses half the min + // delay for quicker notification while still allowing some time + // before reporting for randomization and to learn about other + // sources so the report interval will converge to the correct + // interval more quickly. The average RTCP size is initialized + // to 128 octets which is conservative (it assumes everyone else + // is generating SRs instead of RRs: 20 IP + 8 UDP + 52 SR + 48 + // SDES CNAME). + if (initial) + { + // initialize the random number generator + ACE_OS::srand(ACE_OS::time(0L)); + + rtcp_min_time /= 2; + *avg_rtcp_size = 128; + } + + // If there were active senders, give them at least a minimum + // share of the RTCP bandwidth. Otherwise all participants share + // the RTCP bandwidth equally. + n = members; + if ((senders > 0) && (senders < members*RTCP_SENDER_BW_FRACTION)) + { + if (we_sent) + { + rtcp_bw *= RTCP_SENDER_BW_FRACTION; + n = senders; + } + else + { + rtcp_bw *= RTCP_RECEIVER_BW_FRACTION; + n -= senders; + } + } + + // Update the average size estimate by the size of the report + // packet we just sent. + *avg_rtcp_size += (int)((packet_size - *avg_rtcp_size)*RTCP_SIZE_GAIN); + + // The effective number of sites times the average packet size is + // the total number of octets sent when each site sends a report. + // Dividing this by the effective bandwidth gives the time + // interval over which those packets must be sent in order to + // meet the bandwidth target, with a minimum enforced. In that + // time interval we send one report so this time is also our + // average time between reports. + t = (*avg_rtcp_size) * n / rtcp_bw; + if (t < rtcp_min_time) + t = rtcp_min_time; + + // To avoid traffic bursts from unintended synchronization with + // other sites, we then pick our actual next report interval as a + // random number uniformly distributed between 0.5*t and 1.5*t. + + // TODO: this may not be right. need a random number between 0 and 1 + int max_rand = 32768; + + return t * ((double)ACE_OS::rand()/max_rand + 0.5); +// return t * (drand48() + 0.5); +} + + + +// TAO_AV_RTCP_Flow_Factory +TAO_AV_RTCP_Flow_Factory::TAO_AV_RTCP_Flow_Factory (void) +{ +} + +TAO_AV_RTCP_Flow_Factory::~TAO_AV_RTCP_Flow_Factory (void) +{ +} + +int +TAO_AV_RTCP_Flow_Factory::match_protocol (const char *flow_string) +{ + if (ACE_OS::strncasecmp (flow_string,"RTCP",4) == 0) + return 1; + + return 0; +} + +int +TAO_AV_RTCP_Flow_Factory::init (int /* argc */, + char * /* argv */ []) +{ + return 0; +} + +TAO_AV_Protocol_Object* +TAO_AV_RTCP_Flow_Factory::make_protocol_object (TAO_FlowSpec_Entry * /*entry*/, + TAO_Base_StreamEndPoint * /*endpoint*/, + TAO_AV_Flow_Handler *handler, + TAO_AV_Transport *transport) +{ + TAO_AV_Callback *client_cb = 0; + TAO_AV_RTCP_Callback *rtcp_cb = 0; + + // TODO: need to handle a client callback at some point +// endpoint->get_control_callback (entry->flowname (), +// client_cb); + + TAO_AV_Protocol_Object *object = 0; + ACE_NEW_RETURN (object, + TAO_AV_RTCP_Object (client_cb, + rtcp_cb, + transport), + 0); + + rtcp_cb->open (object, handler); + + return object; +} + +// TAO_AV_RTCP_Object +int +TAO_AV_RTCP_Object::handle_input (void) +{ + size_t bufsiz = 2*this->transport_->mtu (); + ACE_Message_Block data (bufsiz); + + int n = this->transport_->recv (data.rd_ptr (),bufsiz); + if (n == 0) + { + if (TAO_debug_level > 0) + ACE_DEBUG ((LM_ERROR, "TAO_AV_RTCP::handle_input:connection closed\n")); + return -1; + } + if (n < 0) + { + if (TAO_debug_level > 0) + ACE_DEBUG ((LM_ERROR,"TAO_AV_RTCP::handle_input:recv error\n")); + return -1; + } + data.wr_ptr (n); + ACE_Addr *peer_addr = this->transport_->get_peer_addr (); + this->callback_->receive_control_frame (&data,*peer_addr); + return 0; +} + +int +TAO_AV_RTCP_Object::send_frame (ACE_Message_Block *frame, + TAO_AV_frame_info * /*frame_info*/) +{ + return this->transport_->send (frame); +} + +int +TAO_AV_RTCP_Object::send_frame (const iovec *iov, + int iovcnt, + TAO_AV_frame_info * /*frame_info*/) +{ + return this->transport_->send (iov, + iovcnt); +} + +int +TAO_AV_RTCP_Object::send_frame (const char*, + size_t) +{ + return 0; +} + +TAO_AV_RTCP_Object::TAO_AV_RTCP_Object (TAO_AV_Callback *client_cb, + TAO_AV_RTCP_Callback *&rtcp_cb, + TAO_AV_Transport *transport) + :TAO_AV_Protocol_Object (&rtcp_cb_, transport) +{ + rtcp_cb = &this->rtcp_cb_; + this->client_cb_ = client_cb; + +} + +TAO_AV_RTCP_Object::~TAO_AV_RTCP_Object (void) +{ +} + +int +TAO_AV_RTCP_Object::destroy (void) +{ + this->callback_->handle_destroy (); + delete this; + + return 0; +} + +int +TAO_AV_RTCP_Object::set_policies (const TAO_AV_PolicyList &/*policy_list*/) +{ + return -1; +} + +int +TAO_AV_RTCP_Object::start (void) +{ + return this->callback_->handle_start (); +} + +int +TAO_AV_RTCP_Object::stop (void) +{ + return this->callback_->handle_stop (); +} + +int +TAO_AV_RTCP_Object::handle_control_input (ACE_Message_Block *frame, + const ACE_Addr &peer_address) +{ + return this->callback_->receive_frame (frame, + 0, + peer_address); +} + +int +TAO_AV_RTCP_Object::handle_control_output (ACE_Message_Block *frame) +{ + TAO_AV_RTCP_Callback *cb = dynamic_cast<TAO_AV_RTCP_Callback*> (this->callback_); + + return cb->send_frame (frame); +} + +void +TAO_AV_RTCP_Object::ts_offset (ACE_UINT32 ts_offset) +{ + TAO_AV_RTCP_Callback *cb = dynamic_cast<TAO_AV_RTCP_Callback*> (this->callback_); + cb->ts_offset (ts_offset); +} + +// TAO_AV_RTCP_Callback +TAO_AV_RTCP_Callback::TAO_AV_RTCP_Callback (void) + :is_initial_timeout_(1), + packet_size_(0) +{ + char cname[256]; + char host[256]; + ACE_OS::hostname(host, sizeof(host)); + + // TODO: determine username auto-magically? + ACE_OS::sprintf(cname, "username@%s", host); + + this->output_.cname(cname); +} + +TAO_AV_RTCP_Callback::~TAO_AV_RTCP_Callback (void) +{ +} + +void +TAO_AV_RTCP_Callback::schedule (int ms) +{ + this->timeout_ = ms; +} + +int +TAO_AV_RTCP_Callback::handle_start (void) +{ + return 0; +} + +int +TAO_AV_RTCP_Callback::handle_stop (void) +{ + return this->send_report(1); +} + +int +TAO_AV_RTCP_Callback::handle_timeout (void * /*arg*/) +{ + return this->send_report(0); +} + +int +TAO_AV_RTCP_Callback::send_report (int bye) +{ + // get the RTCP control object in order to get the ssrc + TAO_AV_RTCP_Object *rtcp_prot_obj = dynamic_cast<TAO_AV_RTCP_Object*> (this->protocol_object_); + ACE_UINT32 my_ssrc = rtcp_prot_obj->ssrc (); + + RTCP_Packet *cp; + RTCP_SDES_Packet sdes; + ACE_CString value = ""; + ACE_CString note = ""; + unsigned char sdes_type = 0; + RTCP_BYE_Packet *bye_packet = 0; // only used for bye + ACE_UINT32 ssrc_list[1]; // only used for bye + + // get an iterator for the incoming channels. + ACE_Hash_Map_Iterator<ACE_UINT32, RTCP_Channel_In*, ACE_Null_Mutex> iter (this->inputs_); + iter = this->inputs_.begin(); + + // first send an SR/RR + RR_Block *blocks = 0; + RR_Block *b_iter = 0; + RR_Block *b_ptr = 0; + + while (iter != this->inputs_.end() ) + { + if (!b_iter) + { + b_ptr = (*iter).int_id_->getRRBlock (); + if (b_ptr) + { + blocks = b_ptr; + b_iter = b_ptr; + } + } + else + { + b_ptr = (*iter).int_id_->getRRBlock (); + if (b_ptr) + { + b_iter->next_ = b_ptr; + } + } + + iter++; + } + + if (b_iter) + b_iter->next_ = 0; + + if (this->output_.active ()) + { + // get the NTP timestamp + ACE_Time_Value unix_now = ACE_OS::gettimeofday (); + TAO_AV_RTCP::ntp64 ntp_now = ntp64time (unix_now); + ACE_UINT32 rtp_ts = unix_now.sec () * 8000 + unix_now.usec () / 125 + + this->timestamp_offset_; + ACE_NEW_RETURN(cp, + RTCP_SR_Packet (my_ssrc, + ntp_now.upper, + ntp_now.lower, + rtp_ts, + this->output_.packets_sent (), + this->output_.octets_sent (), + blocks), + -1); + } + else + { + ACE_NEW_RETURN(cp, + RTCP_RR_Packet (my_ssrc, + blocks), + -1); + } + + /* + * We always send a cname plus one other sdes + * There's a schedule for what we send sequenced by sdes_seq_: + * - send 'email' every 0th & 4th packet + * - send 'note' every 2nd packet + * - send 'tool' every 6th packet + * - send 'name' in all the odd slots + * (if 'note' is not the empty string, we switch the roles + * of name & note) + */ + + // TODO: need capability to change these settings + switch (this->sdes_count_%8) + { + case 0: + case 4: + value = "tao-users@wustl.edu"; + sdes_type = RTCP_SDES_EMAIL; + break; + case 2: + if (note.length () > 0) + { + value = "Joe User"; + sdes_type = RTCP_SDES_NAME; + } + else + { + value = "An important note..."; + sdes_type = RTCP_SDES_NOTE; + } + break; + case 6: + value = "TAO A/V Service"; + sdes_type = RTCP_SDES_TOOL; + break; + case 1: + case 3: + case 5: + case 7: + if (note.length () == 0) + { + value = "Joe User"; + sdes_type = RTCP_SDES_NAME; + } + else + { + value = "An important note..."; + sdes_type = RTCP_SDES_NOTE; + } + break; + } + + ++this->sdes_count_; + + sdes.add_item (my_ssrc, + RTCP_SDES_CNAME, + static_cast<unsigned char> (ACE_OS::strlen(this->output_.cname())), + this->output_.cname()); + if (bye) + { + ssrc_list[0] = rtcp_prot_obj->ssrc (); + + ACE_NEW_RETURN (bye_packet, + RTCP_BYE_Packet(ssrc_list, + sizeof(ssrc_list)/sizeof(ssrc_list[0]), + "Got bored."), + -1); + } + else + { + unsigned char length = (unsigned char)(value.length() & 0xFF); + sdes.add_item (my_ssrc, sdes_type, length, value.c_str ()); + } + + // create the message block + char *cp_ptr; + char *sdes_ptr; + char *bye_ptr = 0; + ACE_UINT16 cp_length; + ACE_UINT16 sdes_length; + ACE_UINT16 bye_length = 0; + cp->get_packet_data (&cp_ptr, cp_length); + sdes.get_packet_data (&sdes_ptr, sdes_length); + if (bye_packet) + bye_packet->get_packet_data(&bye_ptr, bye_length); + + ACE_Message_Block mb (cp_length + sdes_length + bye_length); + + memcpy (mb.wr_ptr (), cp_ptr, cp_length); + mb.wr_ptr (cp_length); + memcpy (mb.wr_ptr (), sdes_ptr, sdes_length); + mb.wr_ptr (sdes_length); + if (bye_length) + { + memcpy (mb.wr_ptr (), bye_ptr, bye_length); + mb.wr_ptr (bye_length); + } + + // send the report + this->protocol_object_->send_frame (&mb); + + this->packet_size_ = cp_length + sdes_length + bye_length; + + delete cp; + if (bye_packet) + delete bye_packet; + + return 0; +} + +void +//TAO_AV_RTCP_Callback::get_timeout (ACE_Time_Value *tv, +TAO_AV_RTCP_Callback::get_timeout (ACE_Time_Value *&tv, + void *& /*arg*/) +{ + int senders = 0; + int members = 1; // count self as member + + // TODO: this should be 5% of the session bw + double rtcp_bw = 1000; + double interval; + + ACE_Hash_Map_Iterator<ACE_UINT32, RTCP_Channel_In*, ACE_Null_Mutex> iter (this->inputs_); + iter = this->inputs_.begin(); + + if (this->output_.active ()) + senders++; + + // determine the number of senders and members of this session + while (iter != this->inputs_.end ()) + { + if ((*iter).int_id_->active ()) + { + if ((*iter).int_id_->sender ()) + senders++; + members++; + } + iter++; + } + + // Here we do the RTCP timeout calculation. + interval = TAO_AV_RTCP::rtcp_interval (members, // members + senders, // senders + rtcp_bw, // rtcp_bw + this->output_.active (), // we_sent + this->packet_size_, // packet_size + &this->avg_rtcp_size_, // avg_rtcp_size + this->is_initial_timeout_); // initial) + + this->is_initial_timeout_ = 0; + + ACE_NEW (tv, + ACE_Time_Value); + + tv->sec ((int)interval); + tv->usec ((int)((interval - (int)interval) * 1000000)); +} + +int +TAO_AV_RTCP_Callback::handle_destroy (void) +{ + return 0; +} + +int +TAO_AV_RTCP_Callback::receive_frame (ACE_Message_Block *frame, + TAO_AV_frame_info *, + const ACE_Addr &peer_address) +{ + RTCP_Channel_In *c; + + RTP_Packet packet (frame->rd_ptr(), static_cast<int> (frame->length())); + + if (this->inputs_.find (packet.ssrc(), c) < 0) + { + ACE_NEW_RETURN (c, + RTCP_Channel_In (packet.ssrc(), + &peer_address), + -1); + + this->inputs_.bind (packet.ssrc(), c); + } + + c->recv_rtp_packet (frame, &peer_address); + return 0; +} + +int +TAO_AV_RTCP_Callback::send_frame (ACE_Message_Block *frame) +{ + RTP_Packet packet (frame->rd_ptr(), static_cast<int> (frame->length())); + this->output_.updateStatistics (&packet); + + return 0; +} + +void +TAO_AV_RTCP_Callback::ts_offset (ACE_UINT32 offset) +{ + this->timestamp_offset_ = offset; +} + +TAO_END_VERSIONED_NAMESPACE_DECL + +ACE_FACTORY_DEFINE (TAO_AV, TAO_AV_RTCP_Flow_Factory) +ACE_STATIC_SVC_DEFINE (TAO_AV_RTCP_Flow_Factory, + ACE_TEXT ("RTCP_Flow_Factory"), + ACE_SVC_OBJ_T, + &ACE_SVC_NAME (TAO_AV_RTCP_Flow_Factory), + ACE_Service_Type::DELETE_THIS | + ACE_Service_Type::DELETE_OBJ, + 0) + |