/* vjnet - low level network I/O for VeeJay * * (C) 2005-2007 Niels Elburg * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mcastreceiver.h" #include #include #include #include #include "packet.h" #include #ifdef STRICT_CHECKING #include #endif typedef struct { packet_header_t hdr; frame_info_t inf; uint8_t *buf; int len; int count; int rdy; } packet_buffer_t; #define PACKET_SLOTS 3 typedef struct { packet_buffer_t **slot; int in_slot; long last; } packet_slot_t; mcast_receiver *mcast_new_receiver( const char *group_name, int port ) { mcast_receiver *v = (mcast_receiver*) vj_calloc(sizeof(mcast_receiver)); if(!v) return NULL; int on = 1; struct ip_mreq mcast_req; int i; veejay_memset( &mcast_req, 0, sizeof(mcast_req )); veejay_memset( &(v->addr), 0, sizeof(struct sockaddr_in) ); v->group = (char*) strdup( group_name ); v->port = port; v->sock_fd = socket( AF_INET, SOCK_DGRAM, 0 ); if(v->sock_fd < 0) { veejay_msg(0, "Unable to get a datagram socket: %s", strerror(errno)); if(v->group) free(v->group); if(v) free(v); return NULL; } #ifdef SO_REUSEADDR if ( setsockopt( v->sock_fd, SOL_SOCKET, SO_REUSEADDR, &on,sizeof(on))<0) { veejay_msg(0, "Unable to set SO_REUSEADDR: %s", strerror(errno)); if(v->group) free(v->group); if(v) free(v); return NULL; } #endif #ifdef SO_REUSEPORT if ( setsockopt( v->sock_fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on))<0) { veejay_msg(0, "Unable to set SO_REUSEPORT: %s", strerror(errno)); if(v->group) free(v->group); if(v) free(v); return NULL; } #endif v->addr.sin_addr.s_addr = htonl( INADDR_ANY ); v->addr.sin_port = htons( v->port ); if( bind( v->sock_fd, (struct sockaddr*) &(v->addr), sizeof(struct sockaddr_in))<0) { veejay_msg(0, "Unable to bind to port %d : %s", v->port, strerror(errno)); if(v->group) free(v->group); if(v) free(v); return NULL; } mcast_req.imr_multiaddr.s_addr = inet_addr( v->group ); mcast_req.imr_interface.s_addr = htonl( INADDR_ANY ); if( setsockopt( v->sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mcast_req, sizeof(mcast_req) ) < 0 ) { veejay_msg(0, "Unable to join multicast group %s (port=%d)",group_name,port, strerror(errno)); if(v->group) free(v->group); if(v) free(v); return NULL; } packet_slot_t *q = (packet_slot_t*) vj_calloc(sizeof(packet_slot_t)); q->slot = (packet_buffer_t**) vj_calloc(sizeof(packet_buffer_t*) * PACKET_SLOTS ); for( i = 0; i < PACKET_SLOTS ; i ++ ) q->slot[i] = (packet_buffer_t*) vj_calloc(sizeof(packet_buffer_t)); v->next = (void*)q; return v; } int mcast_receiver_set_peer( mcast_receiver *v, const char *hostname ) { struct hostent *host; host = gethostbyname( hostname ); if(host) { v->addr.sin_family = host->h_addrtype; if( host->h_length > (int) sizeof(v->addr.sin_addr)) host->h_length = sizeof( v->addr.sin_addr ); memcpy( &(v->addr.sin_addr), host->h_addr, host->h_length ); } else { v->addr.sin_family = AF_INET; if( !inet_aton( hostname, &(v->addr.sin_addr) ) ) { veejay_msg(0, "Invalid host '%s'", hostname ); return 0; } } return 1; } int mcast_poll( mcast_receiver *v ) { #ifdef STRICT_CHECKING assert( v != NULL ); #endif fd_set fds; struct timeval tv; memset( &tv, 0, sizeof(tv) ); FD_ZERO( &fds ); FD_SET( v->sock_fd, &fds ); if( select( v->sock_fd + 1, &fds, 0,0, &tv ) <= 0 ) return 0; return 1; } int mcast_recv( mcast_receiver *v, void *buf, int len ) { int n = recv( v->sock_fd, buf, len, 0 ); if ( n == -1 ) veejay_msg(0, "Multicast receive error: %s", strerror(errno)); return n; } int mcast_recv_packet_frame( mcast_receiver *v ) { uint8_t chunk[PACKET_PAYLOAD_SIZE]; packet_slot_t *q = (packet_slot_t*) v->next; packet_header_t hdr; frame_info_t inf; int res = recv(v->sock_fd, chunk, PACKET_PAYLOAD_SIZE, 0 ); if( res <= 0 ) { if(res == - 1) veejay_msg(VEEJAY_MSG_ERROR, "Error receiving multicast packet:%s", strerror(errno)); return 0; } hdr = packet_get_header(chunk); packet_get_info( &inf, chunk ); //@ choose slot to fill int i; int d_slot = -1; for(i = 0; i < PACKET_SLOTS; i ++ ) { if( q->slot[i]->hdr.usec == hdr.usec ) { d_slot = i; #ifdef STRICT_CHECKING assert( q->slot[i]->len == inf.len ); #endif break; } } if( d_slot == -1) { //@ find slot with count == 0 (unused) for(i = 0; i < PACKET_SLOTS; i ++ ) { if(q->slot[i]->count == 0 ) { d_slot = i; break; } } } //@ no slots available if( d_slot == -1) { veejay_msg(VEEJAY_MSG_WARNING, "All packet slots in use, cannot keep pace! Dropping oldest in queue."); //@ drop oldest packet in slot long oldest = LONG_MAX; int o = 0; for(i = 0; i < PACKET_SLOTS; i ++ ) { if(q->slot[i]->hdr.usec < oldest ) { o = i; oldest = q->slot[i]->hdr.usec; } } d_slot = o; #ifdef STRICT_CHECKING veejay_msg(VEEJAY_MSG_DEBUG, "Dropping frame in slot %d (%d/%d packets)", d_slot, q->slot[d_slot]->count,q->slot[d_slot]->hdr.length ); #endif free(q->slot[d_slot]->buf); q->slot[d_slot]->buf = NULL; q->slot[d_slot]->count = 0; veejay_memset( &(q->slot[d_slot]->hdr), 0,sizeof(packet_header_t)); veejay_memset( &(q->slot[d_slot]->inf), 0,sizeof(frame_info_t)); q->slot[d_slot]->rdy = 0; } //@ destination slot packet_buffer_t *pb = q->slot[d_slot]; if(pb->buf == NULL) { //@ allocate buffer if needed pb->buf = (uint8_t*) vj_malloc(sizeof(uint8_t) * inf.width * inf.height * 3); } pb->len = inf.len; uint8_t *dst = pb->buf + (CHUNK_SIZE * hdr.seq_num ); packet_get_data( &hdr, chunk, dst ); pb->count ++; //@ save info/hdr veejay_memcpy( &(pb->hdr), &hdr, sizeof(packet_header_t)); veejay_memcpy( &(pb->inf), &inf, sizeof(frame_info_t)); if( pb->count >= hdr.length ) { pb->rdy = 1; q->last = hdr.usec; return 2; } return 1; } uint8_t *mcast_recv_frame( mcast_receiver *v, int *dw, int *dh, int *dfmt, int *len ) { packet_slot_t *q = (packet_slot_t*) v->next; int i,n; for(i = 0; i < PACKET_SLOTS; i ++ ) { //@ find rdy frames or too-old-frames and free them if( q->slot[i]->rdy == 1 || q->slot[i]->hdr.usec < q->last ) { free(q->slot[i]->buf); q->slot[i]->buf = NULL; q->slot[i]->count = 0; veejay_memset( &(q->slot[i]->hdr), 0,sizeof(packet_header_t)); veejay_memset( &(q->slot[i]->inf), 0,sizeof(frame_info_t)); q->slot[i]->rdy = 0; } } //@ is there something todo // if( mcast_poll_timeout( v, 1000 ) == 0 ) // return NULL; while( (n = mcast_recv_packet_frame(v) ) ) { if( n == 2 ) { #ifdef STRICT_CHECKING #endif break; //@ full frame } } int d_slot = -1; //@ find packet buffer with complete frame int full_frame = 0; long t1 = 0; for(i = 0; i < PACKET_SLOTS; i ++ ) { if( q->slot[i]->rdy == 1 ) { full_frame = 1; t1 = q->slot[i]->hdr.usec; d_slot = i; break; } } //@ find newer packet buffer with complete frame for(i = 0; i < PACKET_SLOTS; i ++ ) { if( q->slot[i]->rdy == 1 && q->slot[i]->hdr.usec > t1) { full_frame = 1; d_slot = i; } } /* #ifdef STRICT_CHECKING //@debug queue for( i = 0; i < PACKET_SLOTS; i ++ ) { packet_buffer_t *p = q->slot[i]; veejay_msg(VEEJAY_MSG_DEBUG, "Slot %d: %d bytes, %d/%d queued, rdy=%d, t1=%ld", i, p->len, p->count,p->hdr.length, p->rdy,(long) p->hdr.usec ); } #endif */ //@ return newest full frame if( full_frame ) { packet_buffer_t *pb = q->slot[d_slot]; *dw = pb->inf.width; *dh = pb->inf.height; *dfmt=pb->inf.fmt; *len =pb->len; return pb->buf; } return NULL; } void mcast_close_receiver( mcast_receiver *v ) { if(v) { close(v->sock_fd); if(v->group) free(v->group); v->group = NULL; int i; packet_slot_t *q = (packet_slot_t*) v->next; if( q ) { for( i = 0; i < PACKET_SLOTS; i ++ ){ packet_buffer_t *r = q->slot[i]; if( r->buf ) { free(r->buf); } free(r); } free(q->slot); free(q); } } }