Exemple #1
0
void 
err_handle(rtperror err){
	if (err < 0) {	/* warning */
		fprintf(stderr, "%s\n", RTPStrError(err));
	}
	else if (err) {	/* error */
		fprintf(stderr, "%s\n", RTPStrError(err));	
		exit(1);
	}		
}
/* The library will call this callback (we tell it to, below) whenever some
   change occurs in in the information about a member. */
static void changed_memberinfo_callback(context cid, person id_no,
					memberinfo info, char* str,
					rtpflag flags)
{
  int32 new_ssrc;
  rtperror err;

  err = RTPMemberInfoGetSSRC(cid, id_no, &new_ssrc);
  if (err != RTP_OK){
    fprintf(stderr,
	    "changed_memberinfo_callback: RTPMemberInfoGetSSRC(%ld, %ld): %s\n",
	    cid, id_no, RTPStrError(err));
    return;
  }
  printf("Member number %ld (SSRC 0x%lx) has ", id_no, new_ssrc);
  
  switch(info) {
  case RTP_MI_CNAME:
    printf("CNAME");
    break;
  case RTP_MI_NAME:
    printf("NAME");
    break;
  case RTP_MI_EMAIL:
    printf("EMAIL");
    break;
  case RTP_MI_PHONE:
    printf("PHONE");
    break;
  case RTP_MI_LOC:
    printf("LOC");
    break;
  case RTP_MI_TOOL:
    printf("TOOL");
    break;
  case RTP_MI_NOTE:
    printf("NOTE");
    break;
  case RTP_MI_PRIV:
    printf("PRIV");
    break;
  case RTP_MI_H323_CADDR:
    printf("H323 CADDR");
    break;
  default:
    printf("unknown attribute %d", info);
    break;
  }

  printf(" \"%s\".\n", str);
  return;
}
/* The library will call this callback (we tell it to, below) whenever
 * a member's RTP or RTCP origination address changes */
static void changed_member_address_callback(context cid,
					    person id_no,
					    char *addr, char *port,
					    int is_rtcp)
{
  int32 new_ssrc;
  rtperror err;
  err = RTPMemberInfoGetSSRC(cid, id_no, &new_ssrc);
  if (err != RTP_OK){
    fprintf(stderr,
	    "changed_member_address_callback: RTPMemberInfoGetSSRC(%ld, %ld): %s\n",
	    cid, id_no, RTPStrError(err));
    return;
  }
  printf("Member number %ld (SSRC 0x%lx) has ", id_no, new_ssrc);
  if (is_rtcp)
    printf("RTCP");
  else
    printf("RTP");

  printf(" source address %s:%s\n", addr, port);
}
int main(int argc, char *argv[])
{
  /* The context id the RTP library gives us to keep track of our RTP
     stream. */
  context cid;
  /* The two socket fds (RTP and RTCP) we need to select on. */
  socktype sock[2];
  /* The highest file descriptor we've seen, of those we need to select
     on. */
  int nfds;

  /* The audio file we're playing. */
  FILE *input;
  /* Play 20 ms packets outgoing. */
#define BUFFER_SIZE 160
  /* The buffer for our outgoing data. */
  char buffer[BUFFER_SIZE];
  /* Maximum size of a UDP packet. */
#define RECEIVE_BUFFER_SIZE 65536
  /* The buffer incoming data accumulates in.  We don't actually care
   * about the contents of this data, but we must call RTPReceive() so our
   * sender reports and receiver reports are correct. */
  char receive_buffer[RECEIVE_BUFFER_SIZE];

  /* Some values for keeping track of amount of data read from the file */
  int bytes_read, total_read, last_read;
  /* Some values for keeping track of when to send packets. */
  struct timeval start_time_tv, now_tv;
  double start_time, play_time, now;
  /* Information about the media stream we're using. */
  double sample_rate, byte_rate;

  /* The amount to increment the RTP timestamp for a given packet */
  int tsinc;
  /* The RTP marker bit; initially, it's set. */
  char marker = 1;

  /* Error values that the RTP library returns. */
  rtperror err;
 



  /* 8khz 8-bit mulaw */
  sample_rate = byte_rate = 1/8000.0;

  if (argc < 3) {
    fprintf(stderr, "usage: %s file port\n", argv[0]);
    exit(2);
  }

  input = fopen(argv[1], "r");
  if (input == NULL) {
    perror(argv[1]);
    exit(1);
  }

  if ((nfds = setup_connection(argv[2], &cid, sock)) < 0) {
    exit(1);
  }
  
  total_read = 0;
  last_read = BUFFER_SIZE; /* Arbitrary initial setting. */
  gettimeofday(&start_time_tv, NULL);
  start_time = tv2dbl(start_time_tv);

  while((bytes_read = fread(buffer, 1, BUFFER_SIZE, input)) > 0) {
    /* The tsinc (time-stamp increment) is equal to the number of samples
     * played in the *previous* packet -- it's the amount to increment the 
     * RTP timestamp from the previous packet to this one. */
    tsinc = last_read * (sample_rate/byte_rate);
    /* Send the data we just read to the network.
     * We're assuming here that the payload is always Mulaw-8.
     * See comments above for explanations of the other arguments. */
    err = RTPSend(cid, tsinc, marker, PAYLOAD_TYPE_MULAW_8, buffer,
		  bytes_read);
    if (err != RTP_OK) {
      fprintf(stderr, "%s\n", RTPStrError(err));
      exit(1);
    }
    /* The marker bit doesn't get set for any packet but the first. */
    marker = 0;

    total_read += bytes_read;
    last_read = bytes_read;

    /* Schedule the times to play packets as an absolute offset from
     * our start time, rather than a relative offset from the initial
     * packet.  (We're less vulnerable to drifting clocks that way). */
    play_time = start_time + (total_read * byte_rate);

    /* Handle RTP events and received RTP and RTCP packets until the next
     * play time. */
    while (gettimeofday(&now_tv, NULL),
	   (now = tv2dbl(now_tv)) < play_time) {
      int event;
      int retval, i;
      double timeout;
      struct timeval timeout_tv;
      fd_set sockets;

      /* If we have pending events which are coming before the next packet,
       * timeout for them rather than for the next packet to play. */
      if (evt_queue != NULL && evt_queue->event_time < play_time) {
	event = 1;
	timeout = evt_queue->event_time - now;
      }
      else {
	event = 0;
	timeout = play_time - now;
      }
      if (timeout < 0) timeout = 0;
      timeout_tv = dbl2tv(timeout);
      
      FD_ZERO(&sockets);
      FD_SET(sock[0], &sockets);
      FD_SET(sock[1], &sockets);

      retval = select(nfds + 1, &sockets, NULL, NULL, &timeout_tv);
      if (retval < 0) { /* select returned an error */
	perror("select");
	exit(1);
      }
      else if (retval > 0) { /* select reported readable fd's. */
	for (i = 0; i < 2; i++) {
	  if (FD_ISSET(sock[i], &sockets)) {
	    int recbuflen = RECEIVE_BUFFER_SIZE;
	    /* We don't care about the contents of the received packet;
	     * we're only a sender.  However, we still have to call
	     * RTPReceive to ensure that our sender reports and 
	     * receiver reports are correct. */
	    err = RTPReceive(cid, sock[i], receive_buffer, &recbuflen);
	    if (err != RTP_OK && err != RTP_PACKET_LOOPBACK) {
	      fprintf(stderr, "RTPReceive %s (%d): %s\n",
		      i ? "RTCP" : "RTP", sock[i],
		      RTPStrError(err));
	    }
	  }
	}
      }
      else { /* retval == 0, select timed out */
	if (event) {
	  struct evt_queue_elt *next;
	  gettimeofday(&now_tv, NULL);
	  now = tv2dbl(now_tv);
	  while (evt_queue != NULL && evt_queue->event_time <= now) {
	    /* There is a pending RTP event (currently this means there's
	     * an RTCP packet to send), so run it. */
	    RTPExecute(evt_queue->cid, evt_queue->event_opaque);
	    /* Advance the queue */
	    next = evt_queue->next;
	    free(evt_queue);
	    evt_queue = next;
	  }
	}
	else
	  break; /* Time for the next audio packet */
      }
    }
  }

  /* We're done sending now, so close the connection. */
  /* This sends the RTCP BYE packet and closes our sockets. */
  if ((err = RTPCloseConnection(cid, "Goodbye!")) != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
  }

  /* De-allocate all the data structures reserved for the connection.
   * (This would also close the connection, as well, if we hadn't just done
   * it above.) */
  if ((err = RTPDestroy(cid)) != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
  }

  return 0;
}
/* Set up an RTP Library connection.
 * Return the maximum file descriptor number used, or -1 on error.
 * remote should contain the address to connect to in
 * host/port[/ttl] format.  (If ttl is not specified, '1' is used.)
 * cid will contain the context ID of the RTP session on sucessful return.
 * sock will contain the file descriptors of the RTP and RTCP ports.
 *
 * BUG: on failure, we should tear down the partially-completed
 * connections. */
static int setup_connection(char *remote, context *cid, int sock[2])
{
  /* Error values that the RTP library returns. */
  rtperror err;
  /* The port of the remote address */
  u_int16 port;
  /* The time-to-live value to set if we're talking to a multicast
   * address. */
  unsigned char ttl = 1;
  /* The sockaddr that hpt returns to us.  Note that we don't actually
   * pass this value to the library; it's just a convenient structure. */
  /* The type the library uses to keep track of sockets. */
  socktype sockt;
  /* The highest file descriptor we've dealt with. */
  int nfds = 0;

  /* Some values for creating the CNAME (see below). */
  char *username;
  char hostname[MAXHOSTNAMELEN];
  char cname[MAXHOSTNAMELEN+32];
  struct timeval tp;
  person uid, conid;
#ifdef WIN32
  WORD wVersionRequested;
  WSADATA wsaData;
  int err2;
 
  wVersionRequested = MAKEWORD( 2, 0 );
 
  err2 = WSAStartup( wVersionRequested, &wsaData );
  if ( err2 != 0 ) {
	  /* Tell the user that we couldn't find a usable */
	  /* WinSock DLL.                                  */
	  return(-1);
  }
#endif
  /* Translate host/port[/ttl] structure into sockaddr_in and ttl value. */
  if (hpt(remote, &port, &ttl) < 0) {
    fprintf(stderr, "%s: bad address\n", remote);
    return -1;
  }

  /* Create the RTP session -- allocate data structures.
   * This doesn't open any sockets. */
  err = RTPCreate(cid);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }

  /* Set the RTP Session's host address to send to. */
  err = RTPSessionAddSendAddr(*cid, remote, port, ttl);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }

  /* Set the RTP Session's local receive address */
  /* We set this to the dest for the following reason:
     - It's necessary if the dest is a multicast group, so we join the group
     - It's okay if the dest is unicast, since setting a dest which isn't a
       local address is ignored. */
  err = RTPSessionSetReceiveAddr(*cid, remote, port);


  /* Set up our CNAME (canonical name): should be of the form user@host */
  username = getenv("USER");
  if (gethostname(hostname, MAXHOSTNAMELEN) < 0) {
    perror("gethostname");
    return -1;
  }
  if (username) { sprintf(cname, "%s@%s", username, hostname); }
  else { strcpy(cname, hostname); }

  /* Member 0 is the local member (us) */
  err = RTPMemberInfoSetSDES(*cid, 0, RTP_MI_CNAME, cname);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }

  /* Set up our NAME (standard display name) */
  err = RTPMemberInfoSetSDES(*cid, 0, RTP_MI_NAME, "RTP Example Server");
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }

  /* Add a single contributor */

  gettimeofday(&tp, NULL);
  srand(tp.tv_usec);
  conid = rand();
  err = RTPSessionAddToContributorList(*cid, (int) conid);
  if (err) {
    fprintf(stderr, "%s %s\n", RTPStrError(err), RTPDebugStr());
    return -1;
  }
  err = RTPSessionGetUniqueIDForCSRC(*cid,conid,&uid);
  if (err) {
    fprintf(stderr, "%s %s\n", RTPStrError(err), RTPDebugStr());
    return -1;
  }
  sprintf(cname, "Contributor %d", (int) conid);
  err = RTPMemberInfoSetSDES(*cid, uid, RTP_MI_CNAME, cname);
  if (err) {
    fprintf(stderr, "%s %s\n", RTPStrError(err), RTPDebugStr());
    return -1;
  }
  err = RTPMemberInfoSetSDES(*cid, uid, RTP_MI_NAME, cname);
  if (err) {
    fprintf(stderr, "%s %s\n", RTPStrError(err), RTPDebugStr());
    return -1;
  }


  /* Open the connection.  We're now live on the network. */
  err = RTPOpenConnection(*cid);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }

  /* Get the socket that RTP data is transmitted on, so we can select() on
   * it. */
  /* Note that the value passed to it is a socktype *, not an int *. */
  err = RTPSessionGetRTPSocket(*cid, &sockt);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }
  sock[0] = sockt;
  nfds = 0;
#ifdef __unix
  if (nfds < sockt) nfds = sockt;
#endif

  /* Get the socket that RTCP control information is transmitted on, so we
     can select() on it. */
  err = RTPSessionGetRTCPSocket(*cid, &sockt);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }
  sock[1] = sockt;
#ifdef __unix
  if (nfds < sockt) nfds = sockt;
#endif

  return nfds;
}
int main(int argc, char *argv[])
{
  /* The context id the RTP library gives us to keep track of our RTP
     stream. */
  context cid;
  /* The two socket fds (RTP and RTCP) we need to select on. */
  socktype sock[2];
  /* The highest file descriptor we've seen, of those we need to select
     on. */
  int nfds;

  /* Maximum size of a UDP packet. */
#define RECEIVE_BUFFER_SIZE 65536
  /* The buffer incoming data accumulates in.  We don't actually care
   * about the contents of this data, but we must call RTPReceive() so our
   * sender reports and receiver reports are correct. */
  char receive_buffer[RECEIVE_BUFFER_SIZE];

  /* Some values for keeping track of when to send packets. */
  struct timeval now_tv;
  double now;

  /* Error values that the RTP library returns. */
  rtperror err = RTP_OK;
#ifdef WIN32
  WORD wVersionRequested;
  WSADATA wsaData;
  int err2;
 
  wVersionRequested = MAKEWORD( 2, 0 );
 
  err2 = WSAStartup( wVersionRequested, &wsaData );
  if ( err2 != 0 ) {
	  /* Tell the user that we couldn't find a usable */
	  /* WinSock DLL.                                  */
	  return(-1);
  }
#endif
  if (argc < 2) {
    fprintf(stderr, "usage: %s address/port/ttl\n", argv[0]);
    exit(2);
  }

  if ((nfds = setup_connection(argv[1], &cid, sock)) < 0) {
    exit(1);
  }

  /* Set up our signal handler. */
  signal(SIGINT, done);
  signal(SIGTERM, done);

#ifdef __unix
  signal(SIGHUP, done);
#endif
  
  while (!finished) {
    int event;
    int retval, i;
    double timeout;
    struct timeval timeout_tv, *timeout_tvp;
    fd_set sockets;

    gettimeofday(&now_tv, NULL);
    now = tv2dbl(now_tv);
    
    /* If we have pending events which are coming before the next packet,
     * timeout for them rather than for the next packet to play. */
    if (evt_queue != NULL) {
      event = 1;
      timeout = evt_queue->event_time - now;
      if (timeout < 0) timeout = 0;
      timeout_tv = dbl2tv(timeout);
      timeout_tvp = &timeout_tv;
    }
    else {
      event = 0;
      timeout_tvp = NULL;
    }
    
    FD_ZERO(&sockets);
    FD_SET(sock[0], &sockets);
    FD_SET(sock[1], &sockets);
    
    retval = select(nfds + 1, &sockets, NULL, NULL, timeout_tvp);
    if (retval < 0) { /* select returned an error */

#ifdef __unix
      if (errno == EINTR) {
	/* We probably got a signal */
	continue;
      }
#endif
      /* Otherwise something's wrong */
      perror("select");
      exit(1);
    }
    else if (retval > 0) { /* select reported readable fd's. */
      for (i = 0; i < 2; i++) {
	if (FD_ISSET(sock[i], &sockets)) {
	  int recbuflen = RECEIVE_BUFFER_SIZE;
	  /* We don't care about the contents of the received packet per se.
	   * However, all of our callbacks, reporting information about other
	   * members, are called by code that occurs underneath RTPReceive.
	   * Plus this makes sure all our reciver reports are correct. */
	  err = RTPReceive(cid, sock[i], receive_buffer, &recbuflen);
	  /* Packet loopback is normal with multicast */
	  if (err != RTP_OK && err != RTP_PACKET_LOOPBACK) {
	    fprintf(stderr, "RTPReceive %s (%d): %s\n",
		    i ? "RTCP" : "RTP", sock[i],
		    RTPStrError(err));
	  }

	  if(i == 1) PrintReporting(cid);
	}
      }
    }
    else { /* retval == 0, select timed out */
      if (event) {
	struct evt_queue_elt *next;
	gettimeofday(&now_tv, NULL);
	now = tv2dbl(now_tv);
	while (evt_queue != NULL && evt_queue->event_time <= now) {
	  /* There is a pending RTP event (currently this means there's
	   * an RTCP packet to send), so run it. */
	  RTPExecute(evt_queue->cid, evt_queue->event_opaque);
	  /* Advance the queue */
	  next = evt_queue->next;
	  free(evt_queue);
	  evt_queue = next;
	}
      }
      else
	continue; /* This shouldn't happen, so just loop. */
    }
  }

  /* We're done sending now, so close the connection. */
  /* This sends the RTCP BYE packet and closes our sockets. */
  if ((err = RTPCloseConnection(cid,"Goodbye!")) != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
  }

  /* De-allocate all the data structures reserved for the connection.
   * (This would also close the connection, as well, if we hadn't just done
   * it above.) */
  if ((err = RTPDestroy(cid)) != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
  }

  return 0;
}
void PrintReporting(context cid) {

  member_iterator iter;
  person p2;
  memberstatus ms;
  senderstatus sender;
  rtperror err;
  receiver_report_iterator iter2;
  receiver_report rr;
  ntp64 ntpt;
  int32 rtps;

 if(RTPSessionGetMemberList(cid, &iter) != RTP_OK) {
    printf("Detected error in getting member list\n");
  }

 do {

   RTPCurrentMember(cid, &iter, &p2);
   RTPMemberInfoGetStatus(cid, p2, &ms, &sender);

   if(sender != RTP_SENDER_NOT) {
     
     err = RTPSenderInfoGetFirstReceiverReport(cid, p2, &iter2, &rr);
     if((err != RTP_OK) && (err != RTP_END_OF_LIST)) {
       fprintf(stderr,"Error obtaining receiver report\n");
       fprintf(stderr, "%s\n", RTPStrError(err));
       exit(-1);
     }
     
     printf("Person %d:\n",(int) p2);

     while(err == RTP_OK) {
       printf("   Reported by %u\n",(int) rr.reporter);
       printf("     Fraction Lost: %u\n",rr.fraction_lost);
       printf("     Cum lost: %lu\n",rr.cum_lost);
       printf("     Highest SN: %lu\n",rr.highest_seqno);
       printf("     Jitter: %lu\n",rr.jitter);
       printf("     Last SR: %lu\n",rr.last_sr);
       printf("     DLSR: %lu\n",rr.delay_last_sr);

       err = RTPSenderInfoGetNextReceiverReport(cid, p2, &iter2, &rr);
       if((err != RTP_OK) && (err != RTP_END_OF_LIST)) {
	 fprintf(stderr,"Error obtaining receiver report\n");
	 fprintf(stderr, "%s\n", RTPStrError(err));
	 exit(-1);
       }

     }

     err = RTPSenderInfoGetLocalReception(cid, p2, &rr);
     if(err == RTP_OK) {
       printf("   Local observation:\n");
       printf("     Fraction Lost: %u\n",rr.fraction_lost);
       printf("     Cum lost: %lu\n",rr.cum_lost);
       printf("     Highest SN: %lu\n",rr.highest_seqno);
       printf("     Jitter: %lu\n",rr.jitter);
       printf("     Last SR: %lu\n",rr.last_sr);
       printf("     DLSR: %lu\n",rr.delay_last_sr);
     }
     
     printf("   Info from SR:\n");
     RTPMemberInfoGetNTP(cid, p2, &ntpt);
     printf("    NTP Stamp: %lu.%lu\n", ntpt.secs, ntpt.frac);
     RTPMemberInfoGetRTP(cid, p2, &rtps);
     printf("    RTP Stamp: %lu\n", rtps);
     RTPMemberInfoGetPktCount(cid, p2, &rtps);
     printf("    Packet count: %lu\n", rtps);
     RTPMemberInfoGetOctCount(cid, p2, &rtps);
     printf("    Octet count: %lu\n", rtps);
       
   } else {

     printf("Person %d:\n",(int) p2);
     printf("   Not a sender\n");
   }

 } while(RTPNextMember(cid, &iter, &p2) != RTP_END_OF_LIST);

}
/* Set up an RTP Library connection.
 * Return the maximum file descriptor number used, or -1 on error.
 * remote should contain the address to connect to in
 * host/port[/ttl] format.  (If ttl is not specified, '1' is used.)
 * cid will contain the context ID of the RTP session on sucessful return.
 * sock will contain the file descriptors of the RTP and RTCP ports.
 *
 * BUG: on failure, we should tear down the partially-completed
 * connections. */
static int setup_connection(char *remote, context *cid, int sock[2])
{
  /* Error values that the RTP library returns. */
  rtperror err;
  /* The port of the remote address */
  u_int16 port;
  /* The time-to-live value to set if we're talking to a multicast
   * address. */
  unsigned char ttl = 1;
  /* The sockaddr that hpt returns to us.  Note that we don't actually
   * pass this value to the library; it's just a convenient structure. */
  /* The type the library uses to keep track of sockets. */
  socktype sockt;
  /* The highest file descriptor we've dealt with. */
  int nfds = 0;

  /* Some values for creating the CNAME (see below). */
  char *username;

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 50
#endif

  char hostname[MAXHOSTNAMELEN];
  char cname[MAXHOSTNAMELEN+32];

  /* Translate host/port[/ttl] structure into sockaddr_in and ttl value. */
  if (hpt(remote, &port, &ttl) < 0) {
    fprintf(stderr, "%s: bad address\n", remote);
    return -1;
  }

  /* Create the RTP session -- allocate data structures.
   * This doesn't open any sockets. */
  err = RTPCreate(cid);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }

  /* Tell the library to use our callbacks. */
  /* UpdateMember: */
  err = RTPSetUpdateMemberCallBack(*cid, &update_member_callback);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }
  /* ChangedMemberInfo: */
  err = RTPSetChangedMemberInfoCallBack(*cid, &changed_memberinfo_callback);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }
  /* RevertingID: */
  err = RTPSetRevertingIDCallBack(*cid, &reverting_id_callback);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }
  /* ChangedMemberAddress: */
  err = RTPSetChangedMemberAddressCallBack(*cid, &changed_member_address_callback);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }
  /* SendError: */
  err = RTPSetSendErrorCallBack(*cid, &send_error_callback);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }

  /* Set the RTP Session's host address to send to. */
  err = RTPSessionAddSendAddr(*cid, remote, port, ttl);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }

  /* Set the RTP Session's local receive address */
  /* We set this to the dest for the following reason:
     - It's necessary if the dest is a multicast group, so we join the group
     - It's okay if the dest is unicast, since setting a dest which isn't a
       local address is ignored. */
  err = RTPSessionSetReceiveAddr(*cid, remote, port);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }

  /* Set up our CNAME (canonical name): should be of the form user@host */
  username = getenv("USER");
  if (gethostname(hostname, MAXHOSTNAMELEN) < 0) {
    perror("gethostname");
    return -1;
  }
  if (username) { sprintf(cname, "%s@%s", username, hostname); }
  else { strcpy(cname, hostname); }

  /* Member 0 is the local member (us) */
  err = RTPMemberInfoSetSDES(*cid, 0, RTP_MI_CNAME, cname);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }

  /* Set up our NAME (standard display name) */
  err = RTPMemberInfoSetSDES(*cid, 0, RTP_MI_NAME, "RTP Example Listener");
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }

  /* Open the connection.  We're now live on the network. */
  err = RTPOpenConnection(*cid);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }

  /* Get the socket that RTP data is transmitted on, so we can select() on
   * it. */
  /* Note that the value passed to it is a socktype *, not an int *. */
  err = RTPSessionGetRTPSocket(*cid, &sockt);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }
  sock[0] = sockt;
  nfds = 0;
#ifdef __unix
  if (nfds < sockt) nfds = sockt;
#endif

  /* Get the socket that RTCP control information is transmitted on, so we
     can select() on it. */
  err = RTPSessionGetRTCPSocket(*cid, &sockt);
  if (err != RTP_OK) {
    fprintf(stderr, "%s\n", RTPStrError(err));
    return -1;
  }
  sock[1] = sockt;

#ifdef __unix
  if (nfds < sockt) nfds = sockt;
#endif

  return nfds;
}
/* The library will call this callback (we tell it to, below) whenever some
   change occurs in a member's state. */
static void update_member_callback(context cid, person id_no, rtpflag flags,
				   char *str)
{
  int32 new_ssrc;
  rtperror err;
  err = RTPMemberInfoGetSSRC(cid, id_no, &new_ssrc);
  if (err != RTP_OK){
    fprintf(stderr,
	    "update_member_callback: RTPMemberInfoGetSSRC(%ld, %ld): %s\n",
	    cid, id_no, RTPStrError(err));
    return;
  }
  printf("Member number %ld (SSRC 0x%lx) ", id_no, new_ssrc);
  switch(flags) {
  case RTP_FLAG_NEW_MEMBER:
    printf("joined.\n");
    return;
  case RTP_FLAG_NEW_SENDER:
    printf("was confirmed as a sender.\n");
    return;
  case RTP_FLAG_EXPIRED_MEMBER:
    printf("timed out.\n");
    return;
  case RTP_FLAG_EXPIRED_SENDER:
    printf("timed out as a sender.\n");
    return;
  case RTP_FLAG_MEMBER_LEAVES:
    printf("left: reason \"%s\".\n", str);
    return;
  case RTP_FLAG_OBSERVE_COLLISION:
    printf("collided!\n");
    return;
  case RTP_FLAG_A_CSRC_COLLIDES:
    printf("collided with a csrc member\n");
    return;
  case RTP_FLAG_UNIQUE_ID_REMAP:
    printf("collided and has now returned\n");
    return;
  case RTP_FLAG_MEMBER_ALIVE:
    printf("is alive.\n");
    return;
  case RTP_FLAG_MEMBER_CONFIRMED:
    printf("was confirmed as a group member.\n");
    return;
  case RTP_FLAG_DELETED_PENDING:
    printf("was deleted (having timed out, and never having been confirmed).\n");
    return;
  case RTP_FLAG_DELETED_MEMBER:
    printf("was deleted (having timed out some time ago).\n");
    return;
  case RTP_FLAG_ADDRESS_CHANGES:
    printf("address changed\n");
    return;
  case RTP_FLAG_COLLIDE_WITH_ME:
    printf("collided with local member\n");
    return;
  case RTP_FLAG_PURPORTED_SENDER:
    printf("is purportedly a sender.\n");
    return;
  case RTP_FLAG_DELETED_SENDER:
    printf("is having its sender state destroyed.\n");
    return;
  default:
    printf("had unknown state change %d.\n", flags);
    return;
  }
}