git-svn-id: http://subversion.jackaudio.org/jack/jack2/trunk/jackmp@3693 0c269be4-1314-0410-8aa9-9f06e86f4224tags/v1.9.4
| @@ -0,0 +1,96 @@ | |||
| /* | |||
| Copyright (C) 2001 Paul Davis | |||
| Copyright (C) 2008 Romain Moret at Grame | |||
| 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. | |||
| */ | |||
| #ifndef __JackNetDriver__ | |||
| #define __JackNetDriver__ | |||
| #include "JackAudioDriver.h" | |||
| #include "netjack.h" | |||
| #include "netjack_packet.h" | |||
| namespace Jack | |||
| { | |||
| /** | |||
| \Brief This class describes the Net Backend | |||
| */ | |||
| class JackNetOneDriver : public JackAudioDriver | |||
| { | |||
| private: | |||
| netjack_driver_state_t netj; | |||
| void | |||
| render_payload_to_jack_ports_float ( void *packet_payload, jack_nframes_t net_period_down, JSList *capture_ports, JSList *capture_srcs, jack_nframes_t nframes, int dont_htonl_floats); | |||
| void | |||
| render_jack_ports_to_payload_float (JSList *playback_ports, JSList *playback_srcs, jack_nframes_t nframes, void *packet_payload, jack_nframes_t net_period_up, int dont_htonl_floats ); | |||
| #ifdef HAVE_CELT | |||
| void | |||
| render_payload_to_jack_ports_celt (void *packet_payload, jack_nframes_t net_period_down, JSList *capture_ports, JSList *capture_srcs, jack_nframes_t nframes); | |||
| void | |||
| render_jack_ports_to_payload_celt (JSList *playback_ports, JSList *playback_srcs, jack_nframes_t nframes, void *packet_payload, jack_nframes_t net_period_up); | |||
| #endif | |||
| void | |||
| render_payload_to_jack_ports (int bitdepth, void *packet_payload, jack_nframes_t net_period_down, JSList *capture_ports, JSList *capture_srcs, jack_nframes_t nframes, int dont_htonl_floats); | |||
| void | |||
| render_jack_ports_to_payload (int bitdepth, JSList *playback_ports, JSList *playback_srcs, jack_nframes_t nframes, void *packet_payload, jack_nframes_t net_period_up, int dont_htonl_floats); | |||
| public: | |||
| JackNetOneDriver ( const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table, | |||
| int port, int mtu, int capture_ports, int playback_ports, int midi_input_ports, int midi_output_ports, | |||
| int sample_rate, int period_size, int resample_factor, | |||
| char* net_name, uint transport_sync, int bitdepth, int use_autoconfig, | |||
| int latency, int redundancy, int dont_htonl_floats, int always_deadline ); | |||
| ~JackNetOneDriver(); | |||
| int Open ( jack_nframes_t frames_per_cycle, jack_nframes_t rate, bool capturing, bool playing, | |||
| int inchannels, int outchannels, bool monitor, const char* capture_driver_name, | |||
| const char* playback_driver_name, jack_nframes_t capture_latency, jack_nframes_t playback_latency ); | |||
| int Close(); | |||
| int Attach(); | |||
| int Detach(); | |||
| int Read(); | |||
| int Write(); | |||
| bool Initialize(); | |||
| int AllocPorts(); | |||
| void FreePorts(); | |||
| // BufferSize can't be changed | |||
| bool IsFixedBufferSize() | |||
| { | |||
| return true; | |||
| } | |||
| int SetBufferSize ( jack_nframes_t buffer_size ) | |||
| { | |||
| return -1; | |||
| } | |||
| int SetSampleRate ( jack_nframes_t sample_rate ) | |||
| { | |||
| return -1; | |||
| } | |||
| }; | |||
| } | |||
| #endif | |||
| @@ -38,7 +38,8 @@ JackTransportEngine::JackTransportEngine(): JackAtomicArrayState<jack_position_t | |||
| { | |||
| fTransportState = JackTransportStopped; | |||
| fTransportCmd = fPreviousCmd = TransportCommandStop; | |||
| fSyncTimeout = 2000000; /* 2 second default */ | |||
| fSyncTimeout = 10000000; /* 10 seconds default... | |||
| in case of big netjack1 roundtrip */ | |||
| fSyncTimeLeft = 0; | |||
| fTimeBaseMaster = -1; | |||
| fWriteCounter = 0; | |||
| @@ -0,0 +1,689 @@ | |||
| /* -*- mode: c; c-file-style: "linux"; -*- */ | |||
| /* | |||
| NetJack Abstraction. | |||
| Copyright (C) 2008 Pieter Palmers <pieterpalmers@users.sourceforge.net> | |||
| Copyright (C) 2006 Torben Hohn <torbenh@gmx.de> | |||
| Copyright (C) 2003 Robert Ham <rah@bash.sh> | |||
| Copyright (C) 2001 Paul Davis | |||
| 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. | |||
| $Id: net_driver.c,v 1.17 2006/04/16 20:16:10 torbenh Exp $ | |||
| */ | |||
| #include <math.h> | |||
| #include <stdio.h> | |||
| #include <memory.h> | |||
| #include <unistd.h> | |||
| #include <stdlib.h> | |||
| #include <errno.h> | |||
| #include <stdarg.h> | |||
| #include <jack/types.h> | |||
| #include "jack/jslist.h" | |||
| #include <sys/types.h> | |||
| #ifdef WIN32 | |||
| #include <winsock.h> | |||
| #include <malloc.h> | |||
| #else | |||
| #include <sys/socket.h> | |||
| #include <netinet/in.h> | |||
| #endif | |||
| #include "netjack.h" | |||
| //#include "config.h" | |||
| #if HAVE_SAMPLERATE | |||
| #include <samplerate.h> | |||
| #endif | |||
| #if HAVE_CELT | |||
| #include <celt/celt.h> | |||
| #endif | |||
| #include "netjack.h" | |||
| #include "netjack_packet.h" | |||
| // JACK2 | |||
| #include "jack/control.h" | |||
| #define MIN(x,y) ((x)<(y) ? (x) : (y)) | |||
| static int sync_state = 1; | |||
| static jack_transport_state_t last_transport_state; | |||
| static int | |||
| net_driver_sync_cb(jack_transport_state_t state, jack_position_t *pos, void *data) | |||
| { | |||
| int retval = sync_state; | |||
| if (state == JackTransportStarting && last_transport_state != JackTransportStarting) { | |||
| retval = 0; | |||
| } | |||
| // if (state == JackTransportStarting) | |||
| // jack_info("Starting sync_state = %d", sync_state); | |||
| last_transport_state = state; | |||
| return retval; | |||
| } | |||
| void netjack_wait( netjack_driver_state_t *netj ) | |||
| { | |||
| int we_have_the_expected_frame = 0; | |||
| jack_nframes_t next_frame_avail; | |||
| jack_time_t packet_recv_time_stamp; | |||
| jacknet_packet_header *pkthdr; | |||
| if( !netj->next_deadline_valid ) { | |||
| netj->next_deadline = jack_get_time() + netj->deadline_offset; | |||
| netj->next_deadline_valid = 1; | |||
| } | |||
| // Increment expected frame here. | |||
| netj->expected_framecnt += 1; | |||
| //jack_log( "expect %d", netj->expected_framecnt ); | |||
| // Now check if required packet is already in the cache. | |||
| // then poll (have deadline calculated) | |||
| // then drain socket, rinse and repeat. | |||
| while(1) { | |||
| if( packet_cache_get_next_available_framecnt( global_packcache, netj->expected_framecnt, &next_frame_avail) ) { | |||
| if( next_frame_avail == netj->expected_framecnt ) { | |||
| we_have_the_expected_frame = 1; | |||
| if( !netj->always_deadline ) | |||
| break; | |||
| } | |||
| } | |||
| if( ! netjack_poll_deadline( netj->sockfd, netj->next_deadline ) ) { | |||
| break; | |||
| } | |||
| packet_cache_drain_socket( global_packcache, netj->sockfd ); | |||
| } | |||
| // check if we know who to send our packets too. | |||
| if (!netj->srcaddress_valid) | |||
| if( global_packcache->master_address_valid ) { | |||
| memcpy (&(netj->syncsource_address), &(global_packcache->master_address), sizeof( struct sockaddr_in ) ); | |||
| netj->srcaddress_valid = 1; | |||
| } | |||
| // XXX: switching mode unconditionally is stupid. | |||
| // if we were running free perhaps we like to behave differently | |||
| // ie. fastforward one packet etc. | |||
| // well... this is the first packet we see. hmm.... dunno ;S | |||
| // it works... so... | |||
| netj->running_free = 0; | |||
| if( !we_have_the_expected_frame ) | |||
| jack_error( "netxrun... %d", netj->expected_framecnt ); | |||
| if( we_have_the_expected_frame ) { | |||
| netj->time_to_deadline = netj->next_deadline - jack_get_time() - netj->period_usecs; | |||
| packet_cache_retreive_packet_pointer( global_packcache, netj->expected_framecnt, (char **) &(netj->rx_buf), netj->rx_bufsize , &packet_recv_time_stamp); | |||
| pkthdr = (jacknet_packet_header *) netj->rx_buf; | |||
| packet_header_ntoh(pkthdr); | |||
| netj->deadline_goodness = (int)pkthdr->sync_state; | |||
| netj->packet_data_valid = 1; | |||
| // TODO: Queue state could be taken into account. | |||
| // But needs more processing, cause, when we are running as | |||
| // fast as we can, recv_time_offset can be zero, which is | |||
| // good. | |||
| // need to add (now-deadline) and check that. | |||
| /* | |||
| if( recv_time_offset < netj->period_usecs ) | |||
| //netj->next_deadline -= netj->period_usecs*netj->latency/100; | |||
| netj->next_deadline += netj->period_usecs/1000; | |||
| */ | |||
| if( netj->deadline_goodness != MASTER_FREEWHEELS ) { | |||
| if( netj->deadline_goodness < (netj->period_usecs/4+10*(int)netj->period_usecs*netj->latency/100) ) { | |||
| netj->deadline_offset -= netj->period_usecs/100; | |||
| //jack_log( "goodness: %d, Adjust deadline: --- %d\n", netj->deadline_goodness, (int) netj->period_usecs*netj->latency/100 ); | |||
| } | |||
| if( netj->deadline_goodness > (netj->period_usecs/4+10*(int)netj->period_usecs*netj->latency/100) ) { | |||
| netj->deadline_offset += netj->period_usecs/100; | |||
| //jack_log( "goodness: %d, Adjust deadline: +++ %d\n", netj->deadline_goodness, (int) netj->period_usecs*netj->latency/100 ); | |||
| } | |||
| } | |||
| netj->next_deadline = jack_get_time() + netj->deadline_offset; | |||
| } else { | |||
| netj->time_to_deadline = 0; | |||
| netj->next_deadline = jack_get_time() + netj->deadline_offset; | |||
| // bah... the packet is not there. | |||
| // either | |||
| // - it got lost. | |||
| // - its late | |||
| // - sync source is not sending anymore. | |||
| // lets check if we have the next packets, we will just run a cycle without data. | |||
| // in that case. | |||
| if( packet_cache_get_next_available_framecnt( global_packcache, netj->expected_framecnt, &next_frame_avail) ) | |||
| { | |||
| jack_nframes_t offset = next_frame_avail - netj->expected_framecnt; | |||
| //XXX: hmm... i need to remember why resync_threshold wasnt right. | |||
| //if( offset < netj->resync_threshold ) | |||
| if( offset < 10 ) { | |||
| // ok. dont do nothing. we will run without data. | |||
| // this seems to be one or 2 lost packets. | |||
| // | |||
| // this can also be reordered packet jitter. | |||
| // (maybe this is not happening in real live) | |||
| // but it happens in netem. | |||
| netj->packet_data_valid = 0; | |||
| // I also found this happening, when the packet queue, is too full. | |||
| // but wtf ? use a smaller latency. this link can handle that ;S | |||
| if( packet_cache_get_fill( global_packcache, netj->expected_framecnt ) > 80.0 ) | |||
| netj->next_deadline -= netj->period_usecs/2; | |||
| } else { | |||
| // the diff is too high. but we have a packet in the future. | |||
| // lets resync. | |||
| netj->expected_framecnt = next_frame_avail; | |||
| packet_cache_retreive_packet_pointer( global_packcache, netj->expected_framecnt, (char **) &(netj->rx_buf), netj->rx_bufsize, NULL ); | |||
| pkthdr = (jacknet_packet_header *) netj->rx_buf; | |||
| packet_header_ntoh(pkthdr); | |||
| //netj->deadline_goodness = 0; | |||
| netj->deadline_goodness = (int)pkthdr->sync_state - (int)netj->period_usecs * offset; | |||
| netj->next_deadline_valid = 0; | |||
| netj->packet_data_valid = 1; | |||
| } | |||
| } else { | |||
| // no packets in buffer. | |||
| netj->packet_data_valid = 0; | |||
| //printf( "frame %d No Packet in queue. num_lost_packets = %d \n", netj->expected_framecnt, netj->num_lost_packets ); | |||
| if( netj->num_lost_packets < 5 ) { | |||
| // ok. No Packet in queue. The packet was either lost, | |||
| // or we are running too fast. | |||
| // | |||
| // Adjusting the deadline unconditionally resulted in | |||
| // too many xruns on master. | |||
| // But we need to adjust for the case we are running too fast. | |||
| // So lets check if the last packet is there now. | |||
| // | |||
| // It would not be in the queue anymore, if it had been | |||
| // retrieved. This might break for redundancy, but | |||
| // i will make the packet cache drop redundant packets, | |||
| // that have already been retreived. | |||
| // | |||
| if( packet_cache_get_highest_available_framecnt( global_packcache, &next_frame_avail) ) { | |||
| if( next_frame_avail == (netj->expected_framecnt - 1) ) { | |||
| // Ok. the last packet is there now. | |||
| // and it had not been retrieved. | |||
| // | |||
| // TODO: We are still dropping 2 packets. | |||
| // perhaps we can adjust the deadline | |||
| // when (num_packets lost == 0) | |||
| // This might still be too much. | |||
| netj->next_deadline += netj->period_usecs; | |||
| } | |||
| } | |||
| } else if( (netj->num_lost_packets <= 100) ) { | |||
| // lets try adjusting the deadline harder, for some packets, we might have just ran 2 fast. | |||
| netj->next_deadline += netj->period_usecs*netj->latency/8; | |||
| } else { | |||
| // But now we can check for any new frame available. | |||
| // | |||
| if( packet_cache_get_highest_available_framecnt( global_packcache, &next_frame_avail) ) { | |||
| netj->expected_framecnt = next_frame_avail; | |||
| packet_cache_retreive_packet_pointer( global_packcache, netj->expected_framecnt, (char **) &(netj->rx_buf), netj->rx_bufsize, NULL ); | |||
| pkthdr = (jacknet_packet_header *) netj->rx_buf; | |||
| packet_header_ntoh(pkthdr); | |||
| netj->deadline_goodness = pkthdr->sync_state; | |||
| netj->next_deadline_valid = 0; | |||
| netj->packet_data_valid = 1; | |||
| netj->running_free = 0; | |||
| jack_info( "resync after freerun... %d\n", netj->expected_framecnt ); | |||
| } else { | |||
| // give up. lets run freely. | |||
| // XXX: hmm... | |||
| netj->running_free = 1; | |||
| // when we really dont see packets. | |||
| // reset source address. and open possibility for new master. | |||
| // maybe dsl reconnect. Also restart of netsource without fix | |||
| // reply address changes port. | |||
| if (netj->num_lost_packets > 200 ) { | |||
| netj->srcaddress_valid = 0; | |||
| packet_cache_reset_master_address( global_packcache ); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| if( !netj->packet_data_valid ) | |||
| netj->num_lost_packets += 1; | |||
| else { | |||
| netj->num_lost_packets = 0; | |||
| } | |||
| } | |||
| void netjack_send_silence( netjack_driver_state_t *netj, int syncstate ) | |||
| { | |||
| int tx_size = get_sample_size(netj->bitdepth) * netj->playback_channels * netj->net_period_up + sizeof(jacknet_packet_header); | |||
| unsigned int *packet_buf, *packet_bufX; | |||
| packet_buf = alloca( tx_size); | |||
| jacknet_packet_header *tx_pkthdr = (jacknet_packet_header *)packet_buf; | |||
| jacknet_packet_header *rx_pkthdr = (jacknet_packet_header *)netj->rx_buf; | |||
| //framecnt = rx_pkthdr->framecnt; | |||
| netj->reply_port = rx_pkthdr->reply_port; | |||
| // offset packet_bufX by the packetheader. | |||
| packet_bufX = packet_buf + sizeof(jacknet_packet_header) / sizeof(jack_default_audio_sample_t); | |||
| tx_pkthdr->sync_state = syncstate; | |||
| tx_pkthdr->framecnt = netj->expected_framecnt; | |||
| // memset 0 the payload. | |||
| int payload_size = get_sample_size(netj->bitdepth) * netj->playback_channels * netj->net_period_up; | |||
| memset(packet_bufX, 0, payload_size); | |||
| packet_header_hton(tx_pkthdr); | |||
| if (netj->srcaddress_valid) | |||
| { | |||
| int r; | |||
| if (netj->reply_port) | |||
| netj->syncsource_address.sin_port = htons(netj->reply_port); | |||
| for( r=0; r<netj->redundancy; r++ ) | |||
| netjack_sendto(netj->outsockfd, (char *)packet_buf, tx_size, | |||
| 0, (struct sockaddr*)&(netj->syncsource_address), sizeof(struct sockaddr_in), netj->mtu); | |||
| } | |||
| } | |||
| void netjack_attach( netjack_driver_state_t *netj ) | |||
| { | |||
| //puts ("net_driver_attach"); | |||
| jack_port_t * port; | |||
| char buf[32]; | |||
| unsigned int chn; | |||
| int port_flags; | |||
| if (netj->handle_transport_sync) | |||
| jack_set_sync_callback(netj->client, (JackSyncCallback) net_driver_sync_cb, NULL); | |||
| port_flags = JackPortIsOutput | JackPortIsPhysical | JackPortIsTerminal; | |||
| for (chn = 0; chn < netj->capture_channels_audio; chn++) { | |||
| snprintf (buf, sizeof(buf) - 1, "capture_%u", chn + 1); | |||
| port = jack_port_register (netj->client, buf, | |||
| JACK_DEFAULT_AUDIO_TYPE, | |||
| port_flags, 0); | |||
| if (!port) { | |||
| jack_error ("NET: cannot register port for %s", buf); | |||
| break; | |||
| } | |||
| netj->capture_ports = | |||
| jack_slist_append (netj->capture_ports, port); | |||
| if( netj->bitdepth == CELT_MODE ) { | |||
| #if HAVE_CELT | |||
| #if HAVE_CELT_API_0_7 | |||
| celt_int32 lookahead; | |||
| CELTMode *celt_mode = celt_mode_create( netj->sample_rate, netj->period_size, NULL ); | |||
| netj->capture_srcs = jack_slist_append(netj->capture_srcs, celt_decoder_create( celt_mode, 1, NULL ) ); | |||
| #else | |||
| celt_int32_t lookahead; | |||
| CELTMode *celt_mode = celt_mode_create( netj->sample_rate, 1, netj->period_size, NULL ); | |||
| netj->capture_srcs = jack_slist_append(netj->capture_srcs, celt_decoder_create( celt_mode ) ); | |||
| #endif | |||
| celt_mode_info( celt_mode, CELT_GET_LOOKAHEAD, &lookahead ); | |||
| netj->codec_latency = 2*lookahead; | |||
| #endif | |||
| } else { | |||
| #if HAVE_SAMPLERATE | |||
| netj->capture_srcs = jack_slist_append(netj->capture_srcs, src_new(SRC_LINEAR, 1, NULL)); | |||
| #endif | |||
| } | |||
| } | |||
| for (chn = netj->capture_channels_audio; chn < netj->capture_channels; chn++) { | |||
| snprintf (buf, sizeof(buf) - 1, "capture_%u", chn + 1); | |||
| port = jack_port_register (netj->client, buf, | |||
| JACK_DEFAULT_MIDI_TYPE, | |||
| port_flags, 0); | |||
| if (!port) { | |||
| jack_error ("NET: cannot register port for %s", buf); | |||
| break; | |||
| } | |||
| netj->capture_ports = | |||
| jack_slist_append (netj->capture_ports, port); | |||
| } | |||
| port_flags = JackPortIsInput | JackPortIsPhysical | JackPortIsTerminal; | |||
| for (chn = 0; chn < netj->playback_channels_audio; chn++) { | |||
| snprintf (buf, sizeof(buf) - 1, "playback_%u", chn + 1); | |||
| port = jack_port_register (netj->client, buf, | |||
| JACK_DEFAULT_AUDIO_TYPE, | |||
| port_flags, 0); | |||
| if (!port) { | |||
| jack_error ("NET: cannot register port for %s", buf); | |||
| break; | |||
| } | |||
| netj->playback_ports = | |||
| jack_slist_append (netj->playback_ports, port); | |||
| if( netj->bitdepth == CELT_MODE ) { | |||
| #if HAVE_CELT | |||
| #if HAVE_CELT_API_0_7 | |||
| CELTMode *celt_mode = celt_mode_create( netj->sample_rate, netj->period_size, NULL ); | |||
| netj->playback_srcs = jack_slist_append(netj->playback_srcs, celt_encoder_create( celt_mode, 1, NULL ) ); | |||
| #else | |||
| CELTMode *celt_mode = celt_mode_create( netj->sample_rate, 1, netj->period_size, NULL ); | |||
| netj->playback_srcs = jack_slist_append(netj->playback_srcs, celt_encoder_create( celt_mode ) ); | |||
| #endif | |||
| #endif | |||
| } else { | |||
| #if HAVE_SAMPLERATE | |||
| netj->playback_srcs = jack_slist_append(netj->playback_srcs, src_new(SRC_LINEAR, 1, NULL)); | |||
| #endif | |||
| } | |||
| } | |||
| for (chn = netj->playback_channels_audio; chn < netj->playback_channels; chn++) { | |||
| snprintf (buf, sizeof(buf) - 1, "playback_%u", chn + 1); | |||
| port = jack_port_register (netj->client, buf, | |||
| JACK_DEFAULT_MIDI_TYPE, | |||
| port_flags, 0); | |||
| if (!port) { | |||
| jack_error ("NET: cannot register port for %s", buf); | |||
| break; | |||
| } | |||
| netj->playback_ports = | |||
| jack_slist_append (netj->playback_ports, port); | |||
| } | |||
| jack_activate (netj->client); | |||
| } | |||
| void netjack_detach( netjack_driver_state_t *netj ) | |||
| { | |||
| JSList * node; | |||
| for (node = netj->capture_ports; node; node = jack_slist_next (node)) | |||
| jack_port_unregister (netj->client, | |||
| ((jack_port_t *) node->data)); | |||
| jack_slist_free (netj->capture_ports); | |||
| netj->capture_ports = NULL; | |||
| for (node = netj->playback_ports; node; node = jack_slist_next (node)) | |||
| jack_port_unregister (netj->client, | |||
| ((jack_port_t *) node->data)); | |||
| jack_slist_free (netj->playback_ports); | |||
| netj->playback_ports = NULL; | |||
| } | |||
| netjack_driver_state_t *netjack_init (netjack_driver_state_t *netj, | |||
| jack_client_t * client, | |||
| const char *name, | |||
| unsigned int capture_ports, | |||
| unsigned int playback_ports, | |||
| unsigned int capture_ports_midi, | |||
| unsigned int playback_ports_midi, | |||
| jack_nframes_t sample_rate, | |||
| jack_nframes_t period_size, | |||
| unsigned int listen_port, | |||
| unsigned int transport_sync, | |||
| unsigned int resample_factor, | |||
| unsigned int resample_factor_up, | |||
| unsigned int bitdepth, | |||
| unsigned int use_autoconfig, | |||
| unsigned int latency, | |||
| unsigned int redundancy, | |||
| int dont_htonl_floats, | |||
| int always_deadline) | |||
| { | |||
| // Fill in netj values. | |||
| // might be subject to autoconfig... | |||
| // so dont calculate anything with them... | |||
| netj->sample_rate = sample_rate; | |||
| netj->period_size = period_size; | |||
| netj->dont_htonl_floats = dont_htonl_floats; | |||
| netj->listen_port = listen_port; | |||
| netj->capture_channels = capture_ports + capture_ports_midi; | |||
| netj->capture_channels_audio = capture_ports; | |||
| netj->capture_channels_midi = capture_ports_midi; | |||
| netj->capture_ports = NULL; | |||
| netj->playback_channels = playback_ports + playback_ports_midi; | |||
| netj->playback_channels_audio = playback_ports; | |||
| netj->playback_channels_midi = playback_ports_midi; | |||
| netj->playback_ports = NULL; | |||
| netj->codec_latency = 0; | |||
| netj->handle_transport_sync = transport_sync; | |||
| netj->mtu = 1400; | |||
| netj->latency = latency; | |||
| netj->redundancy = redundancy; | |||
| netj->use_autoconfig = use_autoconfig; | |||
| netj->always_deadline = always_deadline; | |||
| netj->client = client; | |||
| if ((bitdepth != 0) && (bitdepth != 8) && (bitdepth != 16) && (bitdepth != CELT_MODE)) | |||
| { | |||
| jack_info ("Invalid bitdepth: %d (8, 16 or 0 for float) !!!", bitdepth); | |||
| return NULL; | |||
| } | |||
| netj->bitdepth = bitdepth; | |||
| if (resample_factor_up == 0) | |||
| resample_factor_up = resample_factor; | |||
| netj->resample_factor = resample_factor; | |||
| netj->resample_factor_up = resample_factor_up; | |||
| return netj; | |||
| } | |||
| void netjack_release( netjack_driver_state_t *netj ) | |||
| { | |||
| close( netj->sockfd ); | |||
| close( netj->outsockfd ); | |||
| packet_cache_free( global_packcache ); | |||
| global_packcache = NULL; | |||
| } | |||
| int | |||
| netjack_startup( netjack_driver_state_t *netj ) | |||
| { | |||
| int first_pack_len; | |||
| struct sockaddr_in address; | |||
| // Now open the socket, and wait for the first packet to arrive... | |||
| netj->sockfd = socket (AF_INET, SOCK_DGRAM, 0); | |||
| #ifdef WIN32 | |||
| if (netj->sockfd == INVALID_SOCKET) | |||
| #else | |||
| if (netj->sockfd == -1) | |||
| #endif | |||
| { | |||
| jack_info ("socket error"); | |||
| return -1; | |||
| } | |||
| address.sin_family = AF_INET; | |||
| address.sin_port = htons(netj->listen_port); | |||
| address.sin_addr.s_addr = htonl(INADDR_ANY); | |||
| if (bind (netj->sockfd, (struct sockaddr *) &address, sizeof (address)) < 0) | |||
| { | |||
| jack_info("bind error"); | |||
| return -1; | |||
| } | |||
| netj->outsockfd = socket (AF_INET, SOCK_DGRAM, 0); | |||
| #ifdef WIN32 | |||
| if (netj->outsockfd == INVALID_SOCKET) | |||
| #else | |||
| if (netj->outsockfd == -1) | |||
| #endif | |||
| { | |||
| jack_info ("socket error"); | |||
| return -1; | |||
| } | |||
| netj->srcaddress_valid = 0; | |||
| if (netj->use_autoconfig) | |||
| { | |||
| jacknet_packet_header *first_packet = alloca (sizeof (jacknet_packet_header)); | |||
| #ifdef WIN32 | |||
| int address_size = sizeof( struct sockaddr_in ); | |||
| #else | |||
| socklen_t address_size = sizeof (struct sockaddr_in); | |||
| #endif | |||
| //jack_info ("Waiting for an incoming packet !!!"); | |||
| //jack_info ("*** IMPORTANT *** Dont connect a client to jackd until the driver is attached to a clock source !!!"); | |||
| while(1) { | |||
| first_pack_len = recvfrom (netj->sockfd, (char *)first_packet, sizeof (jacknet_packet_header), 0, (struct sockaddr*) & netj->syncsource_address, &address_size); | |||
| #ifdef WIN32 | |||
| if( first_pack_len == -1 ) { | |||
| first_pack_len = sizeof(jacknet_packet_header); | |||
| break; | |||
| } | |||
| #else | |||
| if (first_pack_len == sizeof (jacknet_packet_header)) | |||
| break; | |||
| #endif | |||
| } | |||
| netj->srcaddress_valid = 1; | |||
| if (first_pack_len == sizeof (jacknet_packet_header)) | |||
| { | |||
| packet_header_ntoh (first_packet); | |||
| jack_info ("AutoConfig Override !!!"); | |||
| if (netj->sample_rate != first_packet->sample_rate) | |||
| { | |||
| jack_info ("AutoConfig Override: Master JACK sample rate = %d", first_packet->sample_rate); | |||
| netj->sample_rate = first_packet->sample_rate; | |||
| } | |||
| if (netj->period_size != first_packet->period_size) | |||
| { | |||
| jack_info ("AutoConfig Override: Master JACK period size is %d", first_packet->period_size); | |||
| netj->period_size = first_packet->period_size; | |||
| } | |||
| if (netj->capture_channels_audio != first_packet->capture_channels_audio) | |||
| { | |||
| jack_info ("AutoConfig Override: capture_channels_audio = %d", first_packet->capture_channels_audio); | |||
| netj->capture_channels_audio = first_packet->capture_channels_audio; | |||
| } | |||
| if (netj->capture_channels_midi != first_packet->capture_channels_midi) | |||
| { | |||
| jack_info ("AutoConfig Override: capture_channels_midi = %d", first_packet->capture_channels_midi); | |||
| netj->capture_channels_midi = first_packet->capture_channels_midi; | |||
| } | |||
| if (netj->playback_channels_audio != first_packet->playback_channels_audio) | |||
| { | |||
| jack_info ("AutoConfig Override: playback_channels_audio = %d", first_packet->playback_channels_audio); | |||
| netj->playback_channels_audio = first_packet->playback_channels_audio; | |||
| } | |||
| if (netj->playback_channels_midi != first_packet->playback_channels_midi) | |||
| { | |||
| jack_info ("AutoConfig Override: playback_channels_midi = %d", first_packet->playback_channels_midi); | |||
| netj->playback_channels_midi = first_packet->playback_channels_midi; | |||
| } | |||
| netj->mtu = first_packet->mtu; | |||
| jack_info ("MTU is set to %d bytes", first_packet->mtu); | |||
| netj->latency = first_packet->latency; | |||
| } | |||
| } | |||
| netj->capture_channels = netj->capture_channels_audio + netj->capture_channels_midi; | |||
| netj->playback_channels = netj->playback_channels_audio + netj->playback_channels_midi; | |||
| // After possible Autoconfig: do all calculations... | |||
| netj->period_usecs = | |||
| (jack_time_t) floor ((((float) netj->period_size) / (float)netj->sample_rate) | |||
| * 1000000.0f); | |||
| if( netj->latency == 0 ) | |||
| netj->deadline_offset = 50*netj->period_usecs; | |||
| else | |||
| netj->deadline_offset = netj->period_usecs + 10*netj->latency*netj->period_usecs/100; | |||
| if( netj->bitdepth == CELT_MODE ) { | |||
| // celt mode. | |||
| // TODO: this is a hack. But i dont want to change the packet header. | |||
| netj->net_period_down = netj->resample_factor; | |||
| netj->net_period_up = netj->resample_factor_up; | |||
| } else { | |||
| netj->net_period_down = (float) netj->period_size / (float) netj->resample_factor; | |||
| netj->net_period_up = (float) netj->period_size / (float) netj->resample_factor_up; | |||
| } | |||
| netj->rx_bufsize = sizeof (jacknet_packet_header) + netj->net_period_down * netj->capture_channels * get_sample_size (netj->bitdepth); | |||
| netj->pkt_buf = malloc (netj->rx_bufsize); | |||
| global_packcache = packet_cache_new (netj->latency + 50, netj->rx_bufsize, netj->mtu); | |||
| netj->expected_framecnt_valid = 0; | |||
| netj->num_lost_packets = 0; | |||
| netj->next_deadline_valid = 0; | |||
| netj->deadline_goodness = 0; | |||
| netj->time_to_deadline = 0; | |||
| // Special handling for latency=0 | |||
| if( netj->latency == 0 ) | |||
| netj->resync_threshold = 0; | |||
| else | |||
| netj->resync_threshold = MIN( 15, netj->latency-1 ); | |||
| netj->running_free = 0; | |||
| return 0; | |||
| } | |||
| @@ -0,0 +1,145 @@ | |||
| /* | |||
| Copyright (C) 2003 Robert Ham <rah@bash.sh> | |||
| Copyright (C) 2005 Torben Hohn <torbenh@gmx.de> | |||
| 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. | |||
| */ | |||
| #ifndef __NETJACK_H__ | |||
| #define __NETJACK_H__ | |||
| #include <unistd.h> | |||
| #include <jack/types.h> | |||
| //#include <jack/driver.h> | |||
| #include <jack/jack.h> | |||
| #include <jack/transport.h> | |||
| #include "jack/jslist.h" | |||
| //#include <netinet/in.h> | |||
| #ifdef __cplusplus | |||
| extern "C" | |||
| { | |||
| #endif | |||
| typedef struct _netjack_driver_state netjack_driver_state_t; | |||
| struct _netjack_driver_state { | |||
| jack_nframes_t net_period_up; | |||
| jack_nframes_t net_period_down; | |||
| jack_nframes_t sample_rate; | |||
| jack_nframes_t bitdepth; | |||
| jack_nframes_t period_size; | |||
| jack_time_t period_usecs; | |||
| int dont_htonl_floats; | |||
| int always_deadline; | |||
| jack_nframes_t codec_latency; | |||
| unsigned int listen_port; | |||
| unsigned int capture_channels; | |||
| unsigned int playback_channels; | |||
| unsigned int capture_channels_audio; | |||
| unsigned int playback_channels_audio; | |||
| unsigned int capture_channels_midi; | |||
| unsigned int playback_channels_midi; | |||
| JSList *capture_ports; | |||
| JSList *playback_ports; | |||
| JSList *playback_srcs; | |||
| JSList *capture_srcs; | |||
| jack_client_t *client; | |||
| #ifdef WIN32 | |||
| SOCKET sockfd; | |||
| SOCKET outsockfd; | |||
| #else | |||
| int sockfd; | |||
| int outsockfd; | |||
| #endif | |||
| struct sockaddr_in syncsource_address; | |||
| int reply_port; | |||
| int srcaddress_valid; | |||
| int sync_state; | |||
| unsigned int handle_transport_sync; | |||
| unsigned int *rx_buf; | |||
| unsigned int *pkt_buf; | |||
| unsigned int rx_bufsize; | |||
| //unsigned int tx_bufsize; | |||
| unsigned int mtu; | |||
| unsigned int latency; | |||
| unsigned int redundancy; | |||
| jack_nframes_t expected_framecnt; | |||
| int expected_framecnt_valid; | |||
| unsigned int num_lost_packets; | |||
| jack_time_t next_deadline; | |||
| jack_time_t deadline_offset; | |||
| int next_deadline_valid; | |||
| int packet_data_valid; | |||
| int resync_threshold; | |||
| int running_free; | |||
| int deadline_goodness; | |||
| jack_time_t time_to_deadline; | |||
| unsigned int use_autoconfig; | |||
| unsigned int resample_factor; | |||
| unsigned int resample_factor_up; | |||
| }; | |||
| void netjack_wait( netjack_driver_state_t *netj ); | |||
| void netjack_send_silence( netjack_driver_state_t *netj, int syncstate ); | |||
| void netjack_read( netjack_driver_state_t *netj, jack_nframes_t nframes ) ; | |||
| void netjack_write( netjack_driver_state_t *netj, jack_nframes_t nframes, int syncstate ); | |||
| void netjack_attach( netjack_driver_state_t *netj ); | |||
| void netjack_detach( netjack_driver_state_t *netj ); | |||
| netjack_driver_state_t *netjack_init (netjack_driver_state_t *netj, | |||
| jack_client_t * client, | |||
| const char *name, | |||
| unsigned int capture_ports, | |||
| unsigned int playback_ports, | |||
| unsigned int capture_ports_midi, | |||
| unsigned int playback_ports_midi, | |||
| jack_nframes_t sample_rate, | |||
| jack_nframes_t period_size, | |||
| unsigned int listen_port, | |||
| unsigned int transport_sync, | |||
| unsigned int resample_factor, | |||
| unsigned int resample_factor_up, | |||
| unsigned int bitdepth, | |||
| unsigned int use_autoconfig, | |||
| unsigned int latency, | |||
| unsigned int redundancy, | |||
| int dont_htonl_floats, | |||
| int always_deadline); | |||
| void netjack_release( netjack_driver_state_t *netj ); | |||
| int netjack_startup( netjack_driver_state_t *netj ); | |||
| #ifdef __cplusplus | |||
| } | |||
| #endif | |||
| #endif | |||
| @@ -0,0 +1,165 @@ | |||
| /* | |||
| * NetJack - Packet Handling functions | |||
| * | |||
| * used by the driver and the jacknet_client | |||
| * | |||
| * Copyright (C) 2006 Torben Hohn <torbenh@gmx.de> | |||
| * | |||
| * 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. | |||
| * | |||
| * $Id: net_driver.c,v 1.16 2006/03/20 19:41:37 torbenh Exp $ | |||
| * | |||
| */ | |||
| #ifndef __JACK_NET_PACKET_H__ | |||
| #define __JACK_NET_PACKET_H__ | |||
| #ifdef __cplusplus | |||
| extern "C" | |||
| { | |||
| #endif | |||
| #include <jack/jack.h> | |||
| #include <jack/types.h> | |||
| //#include <jack/engine.h> | |||
| #include <jack/jslist.h> | |||
| #include <jack/midiport.h> | |||
| //#include <netinet/in.h> | |||
| // The Packet Header. | |||
| #define CELT_MODE 1000 // Magic bitdepth value that indicates CELT compression | |||
| #define MASTER_FREEWHEELS 0x80000000 | |||
| typedef struct _jacknet_packet_header jacknet_packet_header; | |||
| struct _jacknet_packet_header | |||
| { | |||
| // General AutoConf Data | |||
| jack_nframes_t capture_channels_audio; | |||
| jack_nframes_t playback_channels_audio; | |||
| jack_nframes_t capture_channels_midi; | |||
| jack_nframes_t playback_channels_midi; | |||
| jack_nframes_t period_size; | |||
| jack_nframes_t sample_rate; | |||
| // Transport Sync | |||
| jack_nframes_t sync_state; | |||
| jack_nframes_t transport_frame; | |||
| jack_nframes_t transport_state; | |||
| // Packet loss Detection, and latency reduction | |||
| jack_nframes_t framecnt; | |||
| jack_nframes_t latency; | |||
| jack_nframes_t reply_port; | |||
| jack_nframes_t mtu; | |||
| jack_nframes_t fragment_nr; | |||
| }; | |||
| typedef union _int_float int_float_t; | |||
| union _int_float | |||
| { | |||
| uint32_t i; | |||
| float f; | |||
| }; | |||
| // fragment reorder cache. | |||
| typedef struct _cache_packet cache_packet; | |||
| struct _cache_packet | |||
| { | |||
| int valid; | |||
| int num_fragments; | |||
| int packet_size; | |||
| int mtu; | |||
| jack_time_t recv_timestamp; | |||
| jack_nframes_t framecnt; | |||
| char * fragment_array; | |||
| char * packet_buf; | |||
| }; | |||
| typedef struct _packet_cache packet_cache; | |||
| struct _packet_cache | |||
| { | |||
| int size; | |||
| cache_packet *packets; | |||
| int mtu; | |||
| struct sockaddr_in master_address; | |||
| int master_address_valid; | |||
| jack_nframes_t last_framecnt_retreived; | |||
| int last_framecnt_retreived_valid; | |||
| }; | |||
| extern packet_cache *global_packcache; | |||
| // fragment cache function prototypes | |||
| // XXX: Some of these are private. | |||
| packet_cache *packet_cache_new(int num_packets, int pkt_size, int mtu); | |||
| void packet_cache_free(packet_cache *pkt_cache); | |||
| cache_packet *packet_cache_get_packet(packet_cache *pkt_cache, jack_nframes_t framecnt); | |||
| cache_packet *packet_cache_get_oldest_packet(packet_cache *pkt_cache); | |||
| cache_packet *packet_cache_get_free_packet(packet_cache *pkt_cache); | |||
| void cache_packet_reset(cache_packet *pack); | |||
| void cache_packet_set_framecnt(cache_packet *pack, jack_nframes_t framecnt); | |||
| void cache_packet_add_fragment(cache_packet *pack, char *packet_buf, int rcv_len); | |||
| int cache_packet_is_complete(cache_packet *pack); | |||
| void packet_cache_drain_socket( packet_cache *pcache, int sockfd ); | |||
| void packet_cache_reset_master_address( packet_cache *pcache ); | |||
| float packet_cache_get_fill( packet_cache *pcache, jack_nframes_t expected_framecnt ); | |||
| int packet_cache_retreive_packet_pointer( packet_cache *pcache, jack_nframes_t framecnt, char **packet_buf, int pkt_size, jack_time_t *timestamp ); | |||
| int packet_cache_release_packet( packet_cache *pcache, jack_nframes_t framecnt ); | |||
| int packet_cache_get_next_available_framecnt( packet_cache *pcache, jack_nframes_t expected_framecnt, jack_nframes_t *framecnt ); | |||
| int packet_cache_get_highest_available_framecnt( packet_cache *pcache, jack_nframes_t *framecnt ); | |||
| int packet_cache_find_latency( packet_cache *pcache, jack_nframes_t expected_framecnt, jack_nframes_t *framecnt ); | |||
| // Function Prototypes | |||
| int netjack_poll_deadline (int sockfd, jack_time_t deadline); | |||
| void netjack_sendto(int sockfd, char *packet_buf, int pkt_size, int flags, struct sockaddr *addr, int addr_size, int mtu); | |||
| int get_sample_size(int bitdepth); | |||
| void packet_header_hton(jacknet_packet_header *pkthdr); | |||
| void packet_header_ntoh(jacknet_packet_header *pkthdr); | |||
| void render_payload_to_jack_ports(int bitdepth, void *packet_payload, jack_nframes_t net_period_down, JSList *capture_ports, JSList *capture_srcs, jack_nframes_t nframes, int dont_htonl_floats ); | |||
| void render_jack_ports_to_payload(int bitdepth, JSList *playback_ports, JSList *playback_srcs, jack_nframes_t nframes, void *packet_payload, jack_nframes_t net_period_up, int dont_htonl_floats ); | |||
| // XXX: This is sort of deprecated: | |||
| // This one waits forever. an is not using ppoll | |||
| int netjack_poll(int sockfd, int timeout); | |||
| // TODO: these are deprecated. | |||
| //int netjack_recvfrom(int sockfd, char *packet_buf, int pkt_size, int flags, struct sockaddr *addr, socklen_t *addr_size, int mtu); | |||
| //int netjack_recv(int sockfd, char *packet_buf, int pkt_size, int flags, int mtu); | |||
| void decode_midi_buffer (uint32_t *buffer_uint32, unsigned int buffer_size_uint32, jack_default_audio_sample_t* buf); | |||
| void encode_midi_buffer (uint32_t *buffer_uint32, unsigned int buffer_size_uint32, jack_default_audio_sample_t* buf); | |||
| #ifdef __cplusplus | |||
| } | |||
| #endif | |||
| #endif | |||
| @@ -0,0 +1,757 @@ | |||
| /** @file simple_client.c | |||
| * | |||
| * @brief This simple client demonstrates the basic features of JACK | |||
| * as they would be used by many applications. | |||
| */ | |||
| #include <stdio.h> | |||
| #include <errno.h> | |||
| #include <unistd.h> | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #include <signal.h> | |||
| #include <alloca.h> | |||
| #include <math.h> | |||
| #include <jack/jack.h> | |||
| #include <jack/jslist.h> | |||
| #include <memops.h> | |||
| #include "alsa/asoundlib.h" | |||
| #include <samplerate.h> | |||
| // Here are the lists of the jack ports... | |||
| JSList *capture_ports = NULL; | |||
| JSList *capture_srcs = NULL; | |||
| JSList *playback_ports = NULL; | |||
| JSList *playback_srcs = NULL; | |||
| jack_client_t *client; | |||
| snd_pcm_t *alsa_handle; | |||
| int jack_sample_rate; | |||
| int jack_buffer_size; | |||
| int quit = 0; | |||
| double resample_mean = 1.0; | |||
| double static_resample_factor = 1.0; | |||
| double *offset_array; | |||
| double *window_array; | |||
| int offset_differential_index = 0; | |||
| double offset_integral = 0; | |||
| // ------------------------------------------------------ commandline parameters | |||
| int sample_rate = 0; /* stream rate */ | |||
| int num_channels = 2; /* count of channels */ | |||
| int period_size = 1024; | |||
| int num_periods = 2; | |||
| int target_delay = 0; /* the delay which the program should try to approach. */ | |||
| int max_diff = 0; /* the diff value, when a hard readpointer skip should occur */ | |||
| int catch_factor = 100000; | |||
| int catch_factor2 = 10000; | |||
| double pclamp = 15.0; | |||
| double controlquant = 10000.0; | |||
| int smooth_size = 256; | |||
| int good_window=0; | |||
| int verbose = 0; | |||
| int instrument = 0; | |||
| int samplerate_quality = 2; | |||
| // Debug stuff: | |||
| volatile float output_resampling_factor = 1.0; | |||
| volatile int output_new_delay = 0; | |||
| volatile float output_offset = 0.0; | |||
| volatile float output_integral = 0.0; | |||
| volatile float output_diff = 0.0; | |||
| snd_pcm_uframes_t real_buffer_size; | |||
| snd_pcm_uframes_t real_period_size; | |||
| // format selection, and corresponding functions from memops in a nice set of structs. | |||
| typedef struct alsa_format { | |||
| snd_pcm_format_t format_id; | |||
| size_t sample_size; | |||
| void (*jack_to_soundcard) (char *dst, jack_default_audio_sample_t *src, unsigned long nsamples, unsigned long dst_skip, dither_state_t *state); | |||
| void (*soundcard_to_jack) (jack_default_audio_sample_t *dst, char *src, unsigned long nsamples, unsigned long src_skip); | |||
| const char *name; | |||
| } alsa_format_t; | |||
| alsa_format_t formats[] = { | |||
| { SND_PCM_FORMAT_S24_3LE, 3, sample_move_d24_sS, sample_move_dS_s24, "24bit - real" }, | |||
| { SND_PCM_FORMAT_FLOAT_LE, 4, sample_move_dS_floatLE, sample_move_floatLE_sSs, "float" }, | |||
| { SND_PCM_FORMAT_S32, 4, sample_move_d32u24_sS, sample_move_dS_s32u24, "32bit" }, | |||
| { SND_PCM_FORMAT_S24, 4, sample_move_d24_sS, sample_move_dS_s24, "24bit" }, | |||
| { SND_PCM_FORMAT_S16, 2, sample_move_d16_sS, sample_move_dS_s16, "16bit" } | |||
| }; | |||
| #define NUMFORMATS (sizeof(formats)/sizeof(formats[0])) | |||
| int format=0; | |||
| // Alsa stuff... i dont want to touch this bullshit in the next years.... please... | |||
| static int xrun_recovery(snd_pcm_t *handle, int err) { | |||
| // printf( "xrun !!!.... %d\n", err ); | |||
| if (err == -EPIPE) { /* under-run */ | |||
| err = snd_pcm_prepare(handle); | |||
| if (err < 0) | |||
| printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err)); | |||
| return 0; | |||
| } else if (err == -EAGAIN) { | |||
| while ((err = snd_pcm_resume(handle)) == -EAGAIN) | |||
| usleep(100); /* wait until the suspend flag is released */ | |||
| if (err < 0) { | |||
| err = snd_pcm_prepare(handle); | |||
| if (err < 0) | |||
| printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err)); | |||
| } | |||
| return 0; | |||
| } | |||
| return err; | |||
| } | |||
| static int set_hwformat( snd_pcm_t *handle, snd_pcm_hw_params_t *params ) | |||
| { | |||
| int i; | |||
| int err; | |||
| for( i=0; i<NUMFORMATS; i++ ) { | |||
| /* set the sample format */ | |||
| err = snd_pcm_hw_params_set_format(handle, params, formats[i].format_id); | |||
| if (err == 0) { | |||
| format = i; | |||
| return 0; | |||
| } | |||
| } | |||
| return err; | |||
| } | |||
| static int set_hwparams(snd_pcm_t *handle, snd_pcm_hw_params_t *params, snd_pcm_access_t access, int rate, int channels, int period, int nperiods ) { | |||
| int err, dir=0; | |||
| unsigned int buffer_time; | |||
| unsigned int period_time; | |||
| unsigned int rrate; | |||
| unsigned int rchannels; | |||
| /* choose all parameters */ | |||
| err = snd_pcm_hw_params_any(handle, params); | |||
| if (err < 0) { | |||
| printf("Broken configuration for playback: no configurations available: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* set the interleaved read/write format */ | |||
| err = snd_pcm_hw_params_set_access(handle, params, access); | |||
| if (err < 0) { | |||
| printf("Access type not available for playback: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* set the sample format */ | |||
| err = set_hwformat(handle, params); | |||
| if (err < 0) { | |||
| printf("Sample format not available for playback: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* set the count of channels */ | |||
| rchannels = channels; | |||
| err = snd_pcm_hw_params_set_channels_near(handle, params, &rchannels); | |||
| if (err < 0) { | |||
| printf("Channels count (%i) not available for record: %s\n", channels, snd_strerror(err)); | |||
| return err; | |||
| } | |||
| if (rchannels != channels) { | |||
| printf("WARNING: chennel count does not match (requested %d got %d)\n", channels, rchannels); | |||
| num_channels = rchannels; | |||
| } | |||
| /* set the stream rate */ | |||
| rrate = rate; | |||
| err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0); | |||
| if (err < 0) { | |||
| printf("Rate %iHz not available for playback: %s\n", rate, snd_strerror(err)); | |||
| return err; | |||
| } | |||
| if (rrate != rate) { | |||
| printf("WARNING: Rate doesn't match (requested %iHz, get %iHz)\n", rate, rrate); | |||
| sample_rate = rrate; | |||
| } | |||
| /* set the buffer time */ | |||
| buffer_time = 1000000*period*nperiods/rate; | |||
| err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, &dir); | |||
| if (err < 0) { | |||
| printf("Unable to set buffer time %i for playback: %s\n", 1000000*period*nperiods/rate, snd_strerror(err)); | |||
| return err; | |||
| } | |||
| err = snd_pcm_hw_params_get_buffer_size( params, &real_buffer_size ); | |||
| if (err < 0) { | |||
| printf("Unable to get buffer size back: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| if( real_buffer_size != nperiods * period ) { | |||
| printf( "WARNING: buffer size does not match: (requested %d, got %d)\n", nperiods * period, (int) real_buffer_size ); | |||
| } | |||
| /* set the period time */ | |||
| period_time = 1000000*period/rate; | |||
| err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, &dir); | |||
| if (err < 0) { | |||
| printf("Unable to set period time %i for playback: %s\n", 1000000*period/rate, snd_strerror(err)); | |||
| return err; | |||
| } | |||
| err = snd_pcm_hw_params_get_period_size(params, &real_period_size, NULL ); | |||
| if (err < 0) { | |||
| printf("Unable to get period size back: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| if( real_period_size != period ) { | |||
| printf( "WARNING: period size does not match: (requested %i, got %i)\n", period, (int)real_period_size ); | |||
| } | |||
| /* write the parameters to device */ | |||
| err = snd_pcm_hw_params(handle, params); | |||
| if (err < 0) { | |||
| printf("Unable to set hw params for playback: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| return 0; | |||
| } | |||
| static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams, int period) { | |||
| int err; | |||
| /* get the current swparams */ | |||
| err = snd_pcm_sw_params_current(handle, swparams); | |||
| if (err < 0) { | |||
| printf("Unable to determine current swparams for capture: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* start the transfer when the buffer is full */ | |||
| err = snd_pcm_sw_params_set_start_threshold(handle, swparams, period ); | |||
| if (err < 0) { | |||
| printf("Unable to set start threshold mode for capture: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, -1 ); | |||
| if (err < 0) { | |||
| printf("Unable to set start threshold mode for capture: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* allow the transfer when at least period_size samples can be processed */ | |||
| err = snd_pcm_sw_params_set_avail_min(handle, swparams, 2*period ); | |||
| if (err < 0) { | |||
| printf("Unable to set avail min for capture: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* align all transfers to 1 sample */ | |||
| err = snd_pcm_sw_params_set_xfer_align(handle, swparams, 1); | |||
| if (err < 0) { | |||
| printf("Unable to set transfer align for capture: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* write the parameters to the playback device */ | |||
| err = snd_pcm_sw_params(handle, swparams); | |||
| if (err < 0) { | |||
| printf("Unable to set sw params for capture: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| return 0; | |||
| } | |||
| // ok... i only need this function to communicate with the alsa bloat api... | |||
| static snd_pcm_t *open_audiofd( char *device_name, int capture, int rate, int channels, int period, int nperiods ) { | |||
| int err; | |||
| snd_pcm_t *handle; | |||
| snd_pcm_hw_params_t *hwparams; | |||
| snd_pcm_sw_params_t *swparams; | |||
| snd_pcm_hw_params_alloca(&hwparams); | |||
| snd_pcm_sw_params_alloca(&swparams); | |||
| if ((err = snd_pcm_open(&(handle), device_name, capture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK )) < 0) { | |||
| printf("Capture open error: %s\n", snd_strerror(err)); | |||
| return NULL; | |||
| } | |||
| if ((err = set_hwparams(handle, hwparams,SND_PCM_ACCESS_RW_INTERLEAVED, rate, channels, period, nperiods )) < 0) { | |||
| printf("Setting of hwparams failed: %s\n", snd_strerror(err)); | |||
| return NULL; | |||
| } | |||
| if ((err = set_swparams(handle, swparams, period)) < 0) { | |||
| printf("Setting of swparams failed: %s\n", snd_strerror(err)); | |||
| return NULL; | |||
| } | |||
| snd_pcm_start( handle ); | |||
| snd_pcm_wait( handle, 200 ); | |||
| return handle; | |||
| } | |||
| double hann( double x ) | |||
| { | |||
| return 0.5 * (1.0 - cos( 2*M_PI * x ) ); | |||
| } | |||
| /** | |||
| * The process callback for this JACK application. | |||
| * It is called by JACK at the appropriate times. | |||
| */ | |||
| int process (jack_nframes_t nframes, void *arg) { | |||
| char *outbuf; | |||
| float *resampbuf; | |||
| int rlen; | |||
| int err; | |||
| snd_pcm_sframes_t delay = target_delay; | |||
| int put_back_samples=0; | |||
| int i; | |||
| snd_pcm_delay( alsa_handle, &delay ); | |||
| //delay -= jack_frames_since_cycle_start( client ); | |||
| // Do it the hard way. | |||
| // this is for compensating xruns etc... | |||
| if( delay > (target_delay+max_diff) ) { | |||
| char *tmp = alloca( (delay-target_delay) * formats[format].sample_size * num_channels ); | |||
| snd_pcm_readi( alsa_handle, tmp, delay-target_delay ); | |||
| output_new_delay = (int) delay; | |||
| delay = target_delay; | |||
| // Set the resample_rate... we need to adjust the offset integral, to do this. | |||
| // first look at the PI controller, this code is just a special case, which should never execute once | |||
| // everything is swung in. | |||
| offset_integral = - (resample_mean - static_resample_factor) * catch_factor * catch_factor2; | |||
| // Also clear the array. we are beginning a new control cycle. | |||
| for( i=0; i<smooth_size; i++ ) | |||
| offset_array[i] = 0.0; | |||
| } | |||
| if( delay < (target_delay-max_diff) ) { | |||
| snd_pcm_rewind( alsa_handle, target_delay - delay ); | |||
| output_new_delay = (int) delay; | |||
| delay = target_delay; | |||
| // Set the resample_rate... we need to adjust the offset integral, to do this. | |||
| offset_integral = - (resample_mean - static_resample_factor) * catch_factor * catch_factor2; | |||
| // Also clear the array. we are beginning a new control cycle. | |||
| for( i=0; i<smooth_size; i++ ) | |||
| offset_array[i] = 0.0; | |||
| } | |||
| /* ok... now we should have target_delay +- max_diff on the alsa side. | |||
| * | |||
| * calculate the number of frames, we want to get. | |||
| */ | |||
| double offset = delay - target_delay; | |||
| // Save offset. | |||
| offset_array[(offset_differential_index++)% smooth_size ] = offset; | |||
| // Build the mean of the windowed offset array | |||
| // basically fir lowpassing. | |||
| double smooth_offset = 0.0; | |||
| for( i=0; i<smooth_size; i++ ) | |||
| smooth_offset += | |||
| offset_array[ (i + offset_differential_index-1) % smooth_size] * window_array[i]; | |||
| smooth_offset /= (double) smooth_size; | |||
| // this is the integral of the smoothed_offset | |||
| offset_integral += smooth_offset; | |||
| // Clamp offset. | |||
| // the smooth offset still contains unwanted noise | |||
| // which would go straigth onto the resample coeff. | |||
| // it only used in the P component and the I component is used for the fine tuning anyways. | |||
| if( fabs( smooth_offset ) < pclamp ) | |||
| smooth_offset = 0.0; | |||
| // ok. now this is the PI controller. | |||
| // u(t) = K * ( e(t) + 1/T \int e(t') dt' ) | |||
| // K = 1/catch_factor and T = catch_factor2 | |||
| double current_resample_factor = static_resample_factor - smooth_offset / (double) catch_factor - offset_integral / (double) catch_factor / (double)catch_factor2; | |||
| // now quantize this value around resample_mean, so that the noise which is in the integral component doesnt hurt. | |||
| current_resample_factor = floor( (current_resample_factor - resample_mean) * controlquant + 0.5 ) / controlquant + resample_mean; | |||
| // Output "instrumentatio" gonna change that to real instrumentation in a few. | |||
| output_resampling_factor = (float) current_resample_factor; | |||
| output_diff = (float) smooth_offset; | |||
| output_integral = (float) offset_integral; | |||
| output_offset = (float) offset; | |||
| // Clamp a bit. | |||
| if( current_resample_factor < 0.25 ) current_resample_factor = 0.25; | |||
| if( current_resample_factor > 4 ) current_resample_factor = 4; | |||
| // Now Calculate how many samples we need. | |||
| rlen = ceil( ((double)nframes) / current_resample_factor )+2; | |||
| assert( rlen > 2 ); | |||
| // Calculate resample_mean so we can init ourselves to saner values. | |||
| resample_mean = 0.9999 * resample_mean + 0.0001 * current_resample_factor; | |||
| /* | |||
| * now this should do it... | |||
| */ | |||
| outbuf = alloca( rlen * formats[format].sample_size * num_channels ); | |||
| resampbuf = alloca( rlen * sizeof( float ) ); | |||
| // get the data... | |||
| again: | |||
| err = snd_pcm_readi(alsa_handle, outbuf, rlen); | |||
| if( err < 0 ) { | |||
| printf( "err = %d\n", err ); | |||
| if (xrun_recovery(alsa_handle, err) < 0) { | |||
| //printf("Write error: %s\n", snd_strerror(err)); | |||
| //exit(EXIT_FAILURE); | |||
| } | |||
| goto again; | |||
| } | |||
| if( err != rlen ) { | |||
| //printf( "read = %d\n", rlen ); | |||
| } | |||
| /* | |||
| * render jack ports to the outbuf... | |||
| */ | |||
| int chn = 0; | |||
| JSList *node = capture_ports; | |||
| JSList *src_node = capture_srcs; | |||
| SRC_DATA src; | |||
| while ( node != NULL) | |||
| { | |||
| jack_port_t *port = (jack_port_t *) node->data; | |||
| float *buf = jack_port_get_buffer (port, nframes); | |||
| SRC_STATE *src_state = src_node->data; | |||
| formats[format].soundcard_to_jack( resampbuf, outbuf + format[formats].sample_size * chn, rlen, num_channels*format[formats].sample_size ); | |||
| src.data_in = resampbuf; | |||
| src.input_frames = rlen; | |||
| src.data_out = buf; | |||
| src.output_frames = nframes; | |||
| src.end_of_input = 0; | |||
| src.src_ratio = current_resample_factor; | |||
| src_process( src_state, &src ); | |||
| put_back_samples = rlen-src.input_frames_used; | |||
| src_node = jack_slist_next (src_node); | |||
| node = jack_slist_next (node); | |||
| chn++; | |||
| } | |||
| // Put back the samples libsamplerate did not consume. | |||
| //printf( "putback = %d\n", put_back_samples ); | |||
| snd_pcm_rewind( alsa_handle, put_back_samples ); | |||
| return 0; | |||
| } | |||
| /** | |||
| * Allocate the necessary jack ports... | |||
| */ | |||
| void alloc_ports( int n_capture, int n_playback ) { | |||
| int port_flags = JackPortIsOutput; | |||
| int chn; | |||
| jack_port_t *port; | |||
| char buf[32]; | |||
| capture_ports = NULL; | |||
| for (chn = 0; chn < n_capture; chn++) | |||
| { | |||
| snprintf (buf, sizeof(buf) - 1, "capture_%u", chn+1); | |||
| port = jack_port_register (client, buf, | |||
| JACK_DEFAULT_AUDIO_TYPE, | |||
| port_flags, 0); | |||
| if (!port) | |||
| { | |||
| printf( "jacknet_client: cannot register port for %s", buf); | |||
| break; | |||
| } | |||
| capture_srcs = jack_slist_append( capture_srcs, src_new( 4-samplerate_quality, 1, NULL ) ); | |||
| capture_ports = jack_slist_append (capture_ports, port); | |||
| } | |||
| port_flags = JackPortIsInput; | |||
| playback_ports = NULL; | |||
| for (chn = 0; chn < n_playback; chn++) | |||
| { | |||
| snprintf (buf, sizeof(buf) - 1, "playback_%u", chn+1); | |||
| port = jack_port_register (client, buf, | |||
| JACK_DEFAULT_AUDIO_TYPE, | |||
| port_flags, 0); | |||
| if (!port) | |||
| { | |||
| printf( "jacknet_client: cannot register port for %s", buf); | |||
| break; | |||
| } | |||
| playback_srcs = jack_slist_append( playback_srcs, src_new( 4-samplerate_quality, 1, NULL ) ); | |||
| playback_ports = jack_slist_append (playback_ports, port); | |||
| } | |||
| } | |||
| /** | |||
| * This is the shutdown callback for this JACK application. | |||
| * It is called by JACK if the server ever shuts down or | |||
| * decides to disconnect the client. | |||
| */ | |||
| void jack_shutdown (void *arg) { | |||
| exit (1); | |||
| } | |||
| /** | |||
| * be user friendly. | |||
| * be user friendly. | |||
| * be user friendly. | |||
| */ | |||
| void printUsage() { | |||
| fprintf(stderr, "usage: alsa_out [options]\n" | |||
| "\n" | |||
| " -j <jack name> - client name\n" | |||
| " -d <alsa_device> \n" | |||
| " -c <channels> \n" | |||
| " -p <period_size> \n" | |||
| " -n <num_period> \n" | |||
| " -r <sample_rate> \n" | |||
| " -q <sample_rate quality [0..4]\n" | |||
| " -m <max_diff> \n" | |||
| " -t <target_delay> \n" | |||
| " -i turns on instrumentation\n" | |||
| " -v turns on printouts\n" | |||
| "\n"); | |||
| } | |||
| /** | |||
| * the main function.... | |||
| */ | |||
| void | |||
| sigterm_handler( int signal ) | |||
| { | |||
| quit = 1; | |||
| } | |||
| int main (int argc, char *argv[]) { | |||
| char jack_name[30] = "alsa_in"; | |||
| char alsa_device[30] = "hw:0"; | |||
| extern char *optarg; | |||
| extern int optind, optopt; | |||
| int errflg=0; | |||
| int c; | |||
| while ((c = getopt(argc, argv, "ivj:r:c:p:n:d:q:m:t:f:F:C:Q:s:")) != -1) { | |||
| switch(c) { | |||
| case 'j': | |||
| strcpy(jack_name,optarg); | |||
| break; | |||
| case 'r': | |||
| sample_rate = atoi(optarg); | |||
| break; | |||
| case 'c': | |||
| num_channels = atoi(optarg); | |||
| break; | |||
| case 'p': | |||
| period_size = atoi(optarg); | |||
| break; | |||
| case 'n': | |||
| num_periods = atoi(optarg); | |||
| break; | |||
| case 'd': | |||
| strcpy(alsa_device,optarg); | |||
| break; | |||
| case 't': | |||
| target_delay = atoi(optarg); | |||
| break; | |||
| case 'q': | |||
| samplerate_quality = atoi(optarg); | |||
| break; | |||
| case 'm': | |||
| max_diff = atoi(optarg); | |||
| break; | |||
| case 'f': | |||
| catch_factor = atoi(optarg); | |||
| break; | |||
| case 'F': | |||
| catch_factor2 = atoi(optarg); | |||
| break; | |||
| case 'C': | |||
| pclamp = (double) atoi(optarg); | |||
| break; | |||
| case 'Q': | |||
| controlquant = (double) atoi(optarg); | |||
| break; | |||
| case 'v': | |||
| verbose = 1; | |||
| break; | |||
| case 'i': | |||
| instrument = 1; | |||
| break; | |||
| case 's': | |||
| smooth_size = atoi(optarg); | |||
| break; | |||
| case ':': | |||
| fprintf(stderr, | |||
| "Option -%c requires an operand\n", optopt); | |||
| errflg++; | |||
| break; | |||
| case '?': | |||
| fprintf(stderr, | |||
| "Unrecognized option: -%c\n", optopt); | |||
| errflg++; | |||
| } | |||
| } | |||
| if (errflg) { | |||
| printUsage(); | |||
| exit(2); | |||
| } | |||
| if( (samplerate_quality < 0) || (samplerate_quality > 4) ) { | |||
| fprintf (stderr, "invalid samplerate quality\n"); | |||
| return 1; | |||
| } | |||
| if ((client = jack_client_open (jack_name, 0, NULL)) == 0) { | |||
| fprintf (stderr, "jack server not running?\n"); | |||
| return 1; | |||
| } | |||
| /* tell the JACK server to call `process()' whenever | |||
| there is work to be done. | |||
| */ | |||
| jack_set_process_callback (client, process, 0); | |||
| /* tell the JACK server to call `jack_shutdown()' if | |||
| it ever shuts down, either entirely, or if it | |||
| just decides to stop calling us. | |||
| */ | |||
| jack_on_shutdown (client, jack_shutdown, 0); | |||
| // get jack sample_rate | |||
| jack_sample_rate = jack_get_sample_rate( client ); | |||
| if( !sample_rate ) | |||
| sample_rate = jack_sample_rate; | |||
| // now open the alsa fd... | |||
| alsa_handle = open_audiofd( alsa_device, 1, sample_rate, num_channels, period_size, num_periods); | |||
| if( alsa_handle == 0 ) | |||
| exit(20); | |||
| printf( "selected sample format: %s\n", formats[format].name ); | |||
| static_resample_factor = (double) jack_sample_rate / (double) sample_rate; | |||
| resample_mean = static_resample_factor; | |||
| offset_array = malloc( sizeof(double) * smooth_size ); | |||
| if( offset_array == NULL ) { | |||
| fprintf( stderr, "no memory for offset_array !!!\n" ); | |||
| exit(20); | |||
| } | |||
| window_array = malloc( sizeof(double) * smooth_size ); | |||
| if( window_array == NULL ) { | |||
| fprintf( stderr, "no memory for window_array !!!\n" ); | |||
| exit(20); | |||
| } | |||
| int i; | |||
| for( i=0; i<smooth_size; i++ ) { | |||
| offset_array[i] = 0.0; | |||
| window_array[i] = hann( (double) i / ((double) smooth_size - 1.0) ); | |||
| } | |||
| jack_buffer_size = jack_get_buffer_size( client ); | |||
| // Setup target delay and max_diff for the normal user, who does not play with them... | |||
| if( !target_delay ) | |||
| target_delay = (num_periods*period_size / 2) - jack_buffer_size/2; | |||
| if( !max_diff ) | |||
| max_diff = target_delay; | |||
| if( max_diff > target_delay ) { | |||
| fprintf( stderr, "target_delay (%d) cant be smaller than max_diff(%d)\n", target_delay, max_diff ); | |||
| exit(20); | |||
| } | |||
| if( (target_delay+max_diff) > (num_periods*period_size) ) { | |||
| fprintf( stderr, "target_delay+max_diff (%d) cant be bigger than buffersize(%d)\n", target_delay+max_diff, num_periods*period_size ); | |||
| exit(20); | |||
| } | |||
| // alloc input ports, which are blasted out to alsa... | |||
| alloc_ports( num_channels, 0 ); | |||
| /* tell the JACK server that we are ready to roll */ | |||
| if (jack_activate (client)) { | |||
| fprintf (stderr, "cannot activate client"); | |||
| return 1; | |||
| } | |||
| signal( SIGTERM, sigterm_handler ); | |||
| signal( SIGINT, sigterm_handler ); | |||
| if( verbose ) { | |||
| while(!quit) { | |||
| usleep(500000); | |||
| if( output_new_delay ) { | |||
| printf( "delay = %d\n", output_new_delay ); | |||
| output_new_delay = 0; | |||
| } | |||
| printf( "res: %f, \tdiff = %f, \toffset = %f \n", output_resampling_factor, output_diff, output_offset ); | |||
| } | |||
| } else if( instrument ) { | |||
| printf( "# n\tresamp\tdiff\toffseti\tintegral\n"); | |||
| int n=0; | |||
| while(!quit) { | |||
| usleep(1000); | |||
| printf( "%d\t%f\t%f\t%f\t%f\n", n++, output_resampling_factor, output_diff, output_offset, output_integral ); | |||
| } | |||
| } else { | |||
| while(!quit) | |||
| { | |||
| usleep(500000); | |||
| if( output_new_delay ) { | |||
| printf( "delay = %d\n", output_new_delay ); | |||
| output_new_delay = 0; | |||
| } | |||
| } | |||
| } | |||
| jack_deactivate( client ); | |||
| jack_client_close (client); | |||
| exit (0); | |||
| } | |||
| @@ -0,0 +1,755 @@ | |||
| /** @file simple_client.c | |||
| * | |||
| * @brief This simple client demonstrates the basic features of JACK | |||
| * as they would be used by many applications. | |||
| */ | |||
| #include <stdio.h> | |||
| #include <errno.h> | |||
| #include <unistd.h> | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #include <signal.h> | |||
| #include <alloca.h> | |||
| #include <math.h> | |||
| #include <jack/jack.h> | |||
| #include <jack/jslist.h> | |||
| #include <memops.h> | |||
| #include "alsa/asoundlib.h" | |||
| #include <samplerate.h> | |||
| // Here are the lists of the jack ports... | |||
| JSList *capture_ports = NULL; | |||
| JSList *capture_srcs = NULL; | |||
| JSList *playback_ports = NULL; | |||
| JSList *playback_srcs = NULL; | |||
| jack_client_t *client; | |||
| snd_pcm_t *alsa_handle; | |||
| int jack_sample_rate; | |||
| int jack_buffer_size; | |||
| double resample_mean = 1.0; | |||
| double static_resample_factor = 1.0; | |||
| double *offset_array; | |||
| double *window_array; | |||
| int offset_differential_index = 0; | |||
| double offset_integral = 0; | |||
| int quit = 0; | |||
| // ------------------------------------------------------ commandline parameters | |||
| int sample_rate = 0; /* stream rate */ | |||
| int num_channels = 2; /* count of channels */ | |||
| int period_size = 1024; | |||
| int num_periods = 2; | |||
| int target_delay = 0; /* the delay which the program should try to approach. */ | |||
| int max_diff = 0; /* the diff value, when a hard readpointer skip should occur */ | |||
| int catch_factor = 100000; | |||
| int catch_factor2 = 10000; | |||
| double pclamp = 15.0; | |||
| double controlquant = 10000.0; | |||
| int smooth_size = 256; | |||
| int good_window=0; | |||
| int verbose = 0; | |||
| int instrument = 0; | |||
| int samplerate_quality = 2; | |||
| // Debug stuff: | |||
| volatile float output_resampling_factor = 1.0; | |||
| volatile int output_new_delay = 0; | |||
| volatile float output_offset = 0.0; | |||
| volatile float output_integral = 0.0; | |||
| volatile float output_diff = 0.0; | |||
| snd_pcm_uframes_t real_buffer_size; | |||
| snd_pcm_uframes_t real_period_size; | |||
| // format selection, and corresponding functions from memops in a nice set of structs. | |||
| typedef struct alsa_format { | |||
| snd_pcm_format_t format_id; | |||
| size_t sample_size; | |||
| void (*jack_to_soundcard) (char *dst, jack_default_audio_sample_t *src, unsigned long nsamples, unsigned long dst_skip, dither_state_t *state); | |||
| void (*soundcard_to_jack) (jack_default_audio_sample_t *dst, char *src, unsigned long nsamples, unsigned long src_skip); | |||
| const char *name; | |||
| } alsa_format_t; | |||
| alsa_format_t formats[] = { | |||
| { SND_PCM_FORMAT_FLOAT_LE, 4, sample_move_dS_floatLE, sample_move_floatLE_sSs, "float" }, | |||
| { SND_PCM_FORMAT_S32, 4, sample_move_d32u24_sS, sample_move_dS_s32u24, "32bit" }, | |||
| { SND_PCM_FORMAT_S24, 4, sample_move_d24_sS, sample_move_dS_s24, "24bit" }, | |||
| { SND_PCM_FORMAT_S16, 2, sample_move_d16_sS, sample_move_dS_s16, "16bit" } | |||
| }; | |||
| #define NUMFORMATS (sizeof(formats)/sizeof(formats[0])) | |||
| int format=0; | |||
| // Alsa stuff... i dont want to touch this bullshit in the next years.... please... | |||
| static int xrun_recovery(snd_pcm_t *handle, int err) { | |||
| // printf( "xrun !!!.... %d\n", err ); | |||
| if (err == -EPIPE) { /* under-run */ | |||
| err = snd_pcm_prepare(handle); | |||
| if (err < 0) | |||
| printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err)); | |||
| return 0; | |||
| } else if (err == -EAGAIN) { | |||
| while ((err = snd_pcm_resume(handle)) == -EAGAIN) | |||
| usleep(100); /* wait until the suspend flag is released */ | |||
| if (err < 0) { | |||
| err = snd_pcm_prepare(handle); | |||
| if (err < 0) | |||
| printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err)); | |||
| } | |||
| return 0; | |||
| } | |||
| return err; | |||
| } | |||
| static int set_hwformat( snd_pcm_t *handle, snd_pcm_hw_params_t *params ) | |||
| { | |||
| int i; | |||
| int err; | |||
| for( i=0; i<NUMFORMATS; i++ ) { | |||
| /* set the sample format */ | |||
| err = snd_pcm_hw_params_set_format(handle, params, formats[i].format_id); | |||
| if (err == 0) { | |||
| format = i; | |||
| return 0; | |||
| } | |||
| } | |||
| return err; | |||
| } | |||
| static int set_hwparams(snd_pcm_t *handle, snd_pcm_hw_params_t *params, snd_pcm_access_t access, int rate, int channels, int period, int nperiods ) { | |||
| int err, dir=0; | |||
| unsigned int buffer_time; | |||
| unsigned int period_time; | |||
| unsigned int rrate; | |||
| unsigned int rchannels; | |||
| /* choose all parameters */ | |||
| err = snd_pcm_hw_params_any(handle, params); | |||
| if (err < 0) { | |||
| printf("Broken configuration for playback: no configurations available: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* set the interleaved read/write format */ | |||
| err = snd_pcm_hw_params_set_access(handle, params, access); | |||
| if (err < 0) { | |||
| printf("Access type not available for playback: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* set the sample format */ | |||
| err = set_hwformat(handle, params); | |||
| if (err < 0) { | |||
| printf("Sample format not available for playback: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* set the count of channels */ | |||
| rchannels = channels; | |||
| err = snd_pcm_hw_params_set_channels_near(handle, params, &rchannels); | |||
| if (err < 0) { | |||
| printf("Channels count (%i) not available for record: %s\n", channels, snd_strerror(err)); | |||
| return err; | |||
| } | |||
| if (rchannels != channels) { | |||
| printf("WARNING: chennel count does not match (requested %d got %d)\n", channels, rchannels); | |||
| num_channels = rchannels; | |||
| } | |||
| /* set the stream rate */ | |||
| rrate = rate; | |||
| err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0); | |||
| if (err < 0) { | |||
| printf("Rate %iHz not available for playback: %s\n", rate, snd_strerror(err)); | |||
| return err; | |||
| } | |||
| if (rrate != rate) { | |||
| printf("Rate doesn't match (requested %iHz, get %iHz)\n", rate, rrate); | |||
| return -EINVAL; | |||
| } | |||
| /* set the buffer time */ | |||
| buffer_time = 1000000*period*nperiods/rate; | |||
| err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, &dir); | |||
| if (err < 0) { | |||
| printf("Unable to set buffer time %i for playback: %s\n", 1000000*period*nperiods/rate, snd_strerror(err)); | |||
| return err; | |||
| } | |||
| err = snd_pcm_hw_params_get_buffer_size( params, &real_buffer_size ); | |||
| if (err < 0) { | |||
| printf("Unable to get buffer size back: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| if( real_buffer_size != nperiods * period ) { | |||
| printf( "WARNING: buffer size does not match: (requested %d, got %d)\n", nperiods * period, (int) real_buffer_size ); | |||
| } | |||
| /* set the period time */ | |||
| period_time = 1000000*period/rate; | |||
| err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, &dir); | |||
| if (err < 0) { | |||
| printf("Unable to set period time %i for playback: %s\n", 1000000*period/rate, snd_strerror(err)); | |||
| return err; | |||
| } | |||
| err = snd_pcm_hw_params_get_period_size(params, &real_period_size, NULL ); | |||
| if (err < 0) { | |||
| printf("Unable to get period size back: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| if( real_period_size != period ) { | |||
| printf( "WARNING: period size does not match: (requested %i, got %i)\n", period, (int)real_period_size ); | |||
| } | |||
| /* write the parameters to device */ | |||
| err = snd_pcm_hw_params(handle, params); | |||
| if (err < 0) { | |||
| printf("Unable to set hw params for playback: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| return 0; | |||
| } | |||
| static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams, int period, int nperiods) { | |||
| int err; | |||
| /* get the current swparams */ | |||
| err = snd_pcm_sw_params_current(handle, swparams); | |||
| if (err < 0) { | |||
| printf("Unable to determine current swparams for capture: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* start the transfer when the buffer is full */ | |||
| err = snd_pcm_sw_params_set_start_threshold(handle, swparams, period ); | |||
| if (err < 0) { | |||
| printf("Unable to set start threshold mode for capture: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, -1 ); | |||
| if (err < 0) { | |||
| printf("Unable to set start threshold mode for capture: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* allow the transfer when at least period_size samples can be processed */ | |||
| err = snd_pcm_sw_params_set_avail_min(handle, swparams, 1 ); | |||
| if (err < 0) { | |||
| printf("Unable to set avail min for capture: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* align all transfers to 1 sample */ | |||
| err = snd_pcm_sw_params_set_xfer_align(handle, swparams, 1); | |||
| if (err < 0) { | |||
| printf("Unable to set transfer align for capture: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| /* write the parameters to the playback device */ | |||
| err = snd_pcm_sw_params(handle, swparams); | |||
| if (err < 0) { | |||
| printf("Unable to set sw params for capture: %s\n", snd_strerror(err)); | |||
| return err; | |||
| } | |||
| return 0; | |||
| } | |||
| // ok... i only need this function to communicate with the alsa bloat api... | |||
| static snd_pcm_t *open_audiofd( char *device_name, int capture, int rate, int channels, int period, int nperiods ) { | |||
| int err; | |||
| snd_pcm_t *handle; | |||
| snd_pcm_hw_params_t *hwparams; | |||
| snd_pcm_sw_params_t *swparams; | |||
| snd_pcm_hw_params_alloca(&hwparams); | |||
| snd_pcm_sw_params_alloca(&swparams); | |||
| if ((err = snd_pcm_open(&(handle), device_name, capture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK )) < 0) { | |||
| printf("Capture open error: %s\n", snd_strerror(err)); | |||
| return NULL; | |||
| } | |||
| if ((err = set_hwparams(handle, hwparams,SND_PCM_ACCESS_RW_INTERLEAVED, rate, channels, period, nperiods )) < 0) { | |||
| printf("Setting of hwparams failed: %s\n", snd_strerror(err)); | |||
| return NULL; | |||
| } | |||
| if ((err = set_swparams(handle, swparams, period, nperiods)) < 0) { | |||
| printf("Setting of swparams failed: %s\n", snd_strerror(err)); | |||
| return NULL; | |||
| } | |||
| //snd_pcm_start( handle ); | |||
| //snd_pcm_wait( handle, 200 ); | |||
| int num_null_samples = nperiods * period * channels; | |||
| char *tmp = alloca( num_null_samples * formats[format].sample_size ); | |||
| memset( tmp, 0, num_null_samples * formats[format].sample_size ); | |||
| snd_pcm_writei( handle, tmp, num_null_samples ); | |||
| return handle; | |||
| } | |||
| double hann( double x ) | |||
| { | |||
| return 0.5 * (1.0 - cos( 2*M_PI * x ) ); | |||
| } | |||
| /** | |||
| * The process callback for this JACK application. | |||
| * It is called by JACK at the appropriate times. | |||
| */ | |||
| int process (jack_nframes_t nframes, void *arg) { | |||
| char *outbuf; | |||
| float *resampbuf; | |||
| int rlen; | |||
| int err; | |||
| snd_pcm_sframes_t delay = target_delay; | |||
| int i; | |||
| snd_pcm_delay( alsa_handle, &delay ); | |||
| delay -= jack_frames_since_cycle_start( client ); | |||
| delay += jack_get_buffer_size( client ) / 2; | |||
| // Do it the hard way. | |||
| // this is for compensating xruns etc... | |||
| if( delay > (target_delay+max_diff) ) { | |||
| snd_pcm_rewind( alsa_handle, delay - target_delay ); | |||
| output_new_delay = (int) delay; | |||
| delay = target_delay; | |||
| // Set the resample_rate... we need to adjust the offset integral, to do this. | |||
| // first look at the PI controller, this code is just a special case, which should never execute once | |||
| // everything is swung in. | |||
| offset_integral = - (resample_mean - static_resample_factor) * catch_factor * catch_factor2; | |||
| // Also clear the array. we are beginning a new control cycle. | |||
| for( i=0; i<smooth_size; i++ ) | |||
| offset_array[i] = 0.0; | |||
| } | |||
| if( delay < (target_delay-max_diff) ) { | |||
| char *tmp = alloca( (target_delay-delay) * formats[format].sample_size * num_channels ); | |||
| memset( tmp, 0, formats[format].sample_size * num_channels * (target_delay-delay) ); | |||
| snd_pcm_writei( alsa_handle, tmp, target_delay-delay ); | |||
| output_new_delay = (int) delay; | |||
| delay = target_delay; | |||
| // Set the resample_rate... we need to adjust the offset integral, to do this. | |||
| offset_integral = - (resample_mean - static_resample_factor) * catch_factor * catch_factor2; | |||
| // Also clear the array. we are beginning a new control cycle. | |||
| for( i=0; i<smooth_size; i++ ) | |||
| offset_array[i] = 0.0; | |||
| } | |||
| /* ok... now we should have target_delay +- max_diff on the alsa side. | |||
| * | |||
| * calculate the number of frames, we want to get. | |||
| */ | |||
| double offset = delay - target_delay; | |||
| // Save offset. | |||
| offset_array[(offset_differential_index++)% smooth_size ] = offset; | |||
| // Build the mean of the windowed offset array | |||
| // basically fir lowpassing. | |||
| double smooth_offset = 0.0; | |||
| for( i=0; i<smooth_size; i++ ) | |||
| smooth_offset += | |||
| offset_array[ (i + offset_differential_index-1) % smooth_size] * window_array[i]; | |||
| smooth_offset /= (double) smooth_size; | |||
| // this is the integral of the smoothed_offset | |||
| offset_integral += smooth_offset; | |||
| // Clamp offset. | |||
| // the smooth offset still contains unwanted noise | |||
| // which would go straigth onto the resample coeff. | |||
| // it only used in the P component and the I component is used for the fine tuning anyways. | |||
| if( fabs( smooth_offset ) < pclamp ) | |||
| smooth_offset = 0.0; | |||
| // ok. now this is the PI controller. | |||
| // u(t) = K * ( e(t) + 1/T \int e(t') dt' ) | |||
| // K = 1/catch_factor and T = catch_factor2 | |||
| double current_resample_factor = static_resample_factor - smooth_offset / (double) catch_factor - offset_integral / (double) catch_factor / (double)catch_factor2; | |||
| // now quantize this value around resample_mean, so that the noise which is in the integral component doesnt hurt. | |||
| current_resample_factor = floor( (current_resample_factor - resample_mean) * controlquant + 0.5 ) / controlquant + resample_mean; | |||
| // Output "instrumentatio" gonna change that to real instrumentation in a few. | |||
| output_resampling_factor = (float) current_resample_factor; | |||
| output_diff = (float) smooth_offset; | |||
| output_integral = (float) offset_integral; | |||
| output_offset = (float) offset; | |||
| // Clamp a bit. | |||
| if( current_resample_factor < 0.25 ) current_resample_factor = 0.25; | |||
| if( current_resample_factor > 4 ) current_resample_factor = 4; | |||
| // Now Calculate how many samples we need. | |||
| rlen = ceil( ((double)nframes) * current_resample_factor )+2; | |||
| assert( rlen > 2 ); | |||
| // Calculate resample_mean so we can init ourselves to saner values. | |||
| resample_mean = 0.9999 * resample_mean + 0.0001 * current_resample_factor; | |||
| /* | |||
| * now this should do it... | |||
| */ | |||
| outbuf = alloca( rlen * formats[format].sample_size * num_channels ); | |||
| resampbuf = alloca( rlen * sizeof( float ) ); | |||
| /* | |||
| * render jack ports to the outbuf... | |||
| */ | |||
| int chn = 0; | |||
| JSList *node = playback_ports; | |||
| JSList *src_node = playback_srcs; | |||
| SRC_DATA src; | |||
| while ( node != NULL) | |||
| { | |||
| jack_port_t *port = (jack_port_t *) node->data; | |||
| float *buf = jack_port_get_buffer (port, nframes); | |||
| SRC_STATE *src_state = src_node->data; | |||
| src.data_in = buf; | |||
| src.input_frames = nframes; | |||
| src.data_out = resampbuf; | |||
| src.output_frames = rlen; | |||
| src.end_of_input = 0; | |||
| src.src_ratio = current_resample_factor; | |||
| src_process( src_state, &src ); | |||
| formats[format].jack_to_soundcard( outbuf + format[formats].sample_size * chn, resampbuf, src.output_frames_gen, num_channels*format[formats].sample_size, NULL); | |||
| src_node = jack_slist_next (src_node); | |||
| node = jack_slist_next (node); | |||
| chn++; | |||
| } | |||
| // now write the output... | |||
| again: | |||
| err = snd_pcm_writei(alsa_handle, outbuf, src.output_frames_gen); | |||
| //err = snd_pcm_writei(alsa_handle, outbuf, src.output_frames_gen); | |||
| if( err < 0 ) { | |||
| printf( "err = %d\n", err ); | |||
| if (xrun_recovery(alsa_handle, err) < 0) { | |||
| printf("Write error: %s\n", snd_strerror(err)); | |||
| exit(EXIT_FAILURE); | |||
| } | |||
| goto again; | |||
| } | |||
| return 0; | |||
| } | |||
| /** | |||
| * Allocate the necessary jack ports... | |||
| */ | |||
| void alloc_ports( int n_capture, int n_playback ) { | |||
| int port_flags = JackPortIsOutput | JackPortIsPhysical | JackPortIsTerminal; | |||
| int chn; | |||
| jack_port_t *port; | |||
| char buf[32]; | |||
| capture_ports = NULL; | |||
| for (chn = 0; chn < n_capture; chn++) | |||
| { | |||
| snprintf (buf, sizeof(buf) - 1, "capture_%u", chn+1); | |||
| port = jack_port_register (client, buf, | |||
| JACK_DEFAULT_AUDIO_TYPE, | |||
| port_flags, 0); | |||
| if (!port) | |||
| { | |||
| printf( "jacknet_client: cannot register port for %s", buf); | |||
| break; | |||
| } | |||
| capture_srcs = jack_slist_append( capture_srcs, src_new( 4-samplerate_quality, 1, NULL ) ); | |||
| capture_ports = jack_slist_append (capture_ports, port); | |||
| } | |||
| port_flags = JackPortIsInput; | |||
| playback_ports = NULL; | |||
| for (chn = 0; chn < n_playback; chn++) | |||
| { | |||
| snprintf (buf, sizeof(buf) - 1, "playback_%u", chn+1); | |||
| port = jack_port_register (client, buf, | |||
| JACK_DEFAULT_AUDIO_TYPE, | |||
| port_flags, 0); | |||
| if (!port) | |||
| { | |||
| printf( "jacknet_client: cannot register port for %s", buf); | |||
| break; | |||
| } | |||
| playback_srcs = jack_slist_append( playback_srcs, src_new( 4-samplerate_quality, 1, NULL ) ); | |||
| playback_ports = jack_slist_append (playback_ports, port); | |||
| } | |||
| } | |||
| /** | |||
| * This is the shutdown callback for this JACK application. | |||
| * It is called by JACK if the server ever shuts down or | |||
| * decides to disconnect the client. | |||
| */ | |||
| void jack_shutdown (void *arg) { | |||
| exit (1); | |||
| } | |||
| /** | |||
| * be user friendly. | |||
| * be user friendly. | |||
| * be user friendly. | |||
| */ | |||
| void printUsage() { | |||
| fprintf(stderr, "usage: alsa_out [options]\n" | |||
| "\n" | |||
| " -j <jack name> - client name\n" | |||
| " -d <alsa_device> \n" | |||
| " -c <channels> \n" | |||
| " -p <period_size> \n" | |||
| " -n <num_period> \n" | |||
| " -r <sample_rate> \n" | |||
| " -q <sample_rate quality [0..4]\n" | |||
| " -m <max_diff> \n" | |||
| " -t <target_delay> \n" | |||
| " -i turns on instrumentation\n" | |||
| " -v turns on printouts\n" | |||
| "\n"); | |||
| } | |||
| /** | |||
| * the main function.... | |||
| */ | |||
| void | |||
| sigterm_handler( int signal ) | |||
| { | |||
| quit = 1; | |||
| } | |||
| int main (int argc, char *argv[]) { | |||
| char jack_name[30] = "alsa_out"; | |||
| char alsa_device[30] = "hw:0"; | |||
| extern char *optarg; | |||
| extern int optind, optopt; | |||
| int errflg=0; | |||
| int c; | |||
| while ((c = getopt(argc, argv, "ivj:r:c:p:n:d:q:m:t:f:F:C:Q:s:")) != -1) { | |||
| switch(c) { | |||
| case 'j': | |||
| strcpy(jack_name,optarg); | |||
| break; | |||
| case 'r': | |||
| sample_rate = atoi(optarg); | |||
| break; | |||
| case 'c': | |||
| num_channels = atoi(optarg); | |||
| break; | |||
| case 'p': | |||
| period_size = atoi(optarg); | |||
| break; | |||
| case 'n': | |||
| num_periods = atoi(optarg); | |||
| break; | |||
| case 'd': | |||
| strcpy(alsa_device,optarg); | |||
| break; | |||
| case 't': | |||
| target_delay = atoi(optarg); | |||
| break; | |||
| case 'q': | |||
| samplerate_quality = atoi(optarg); | |||
| break; | |||
| case 'm': | |||
| max_diff = atoi(optarg); | |||
| break; | |||
| case 'f': | |||
| catch_factor = atoi(optarg); | |||
| break; | |||
| case 'F': | |||
| catch_factor2 = atoi(optarg); | |||
| break; | |||
| case 'C': | |||
| pclamp = (double) atoi(optarg); | |||
| break; | |||
| case 'Q': | |||
| controlquant = (double) atoi(optarg); | |||
| break; | |||
| case 'v': | |||
| verbose = 1; | |||
| break; | |||
| case 'i': | |||
| instrument = 1; | |||
| break; | |||
| case 's': | |||
| smooth_size = atoi(optarg); | |||
| break; | |||
| case ':': | |||
| fprintf(stderr, | |||
| "Option -%c requires an operand\n", optopt); | |||
| errflg++; | |||
| break; | |||
| case '?': | |||
| fprintf(stderr, | |||
| "Unrecognized option: -%c\n", optopt); | |||
| errflg++; | |||
| } | |||
| } | |||
| if (errflg) { | |||
| printUsage(); | |||
| exit(2); | |||
| } | |||
| if( (samplerate_quality < 0) || (samplerate_quality > 4) ) { | |||
| fprintf (stderr, "invalid samplerate quality\n"); | |||
| return 1; | |||
| } | |||
| if ((client = jack_client_open (jack_name, 0, NULL)) == 0) { | |||
| fprintf (stderr, "jack server not running?\n"); | |||
| return 1; | |||
| } | |||
| /* tell the JACK server to call `process()' whenever | |||
| there is work to be done. | |||
| */ | |||
| jack_set_process_callback (client, process, 0); | |||
| /* tell the JACK server to call `jack_shutdown()' if | |||
| it ever shuts down, either entirely, or if it | |||
| just decides to stop calling us. | |||
| */ | |||
| jack_on_shutdown (client, jack_shutdown, 0); | |||
| // get jack sample_rate | |||
| jack_sample_rate = jack_get_sample_rate( client ); | |||
| if( !sample_rate ) | |||
| sample_rate = jack_sample_rate; | |||
| static_resample_factor = (double) sample_rate / (double) jack_sample_rate; | |||
| resample_mean = static_resample_factor; | |||
| offset_array = malloc( sizeof(double) * smooth_size ); | |||
| if( offset_array == NULL ) { | |||
| fprintf( stderr, "no memory for offset_array !!!\n" ); | |||
| exit(20); | |||
| } | |||
| window_array = malloc( sizeof(double) * smooth_size ); | |||
| if( window_array == NULL ) { | |||
| fprintf( stderr, "no memory for window_array !!!\n" ); | |||
| exit(20); | |||
| } | |||
| int i; | |||
| for( i=0; i<smooth_size; i++ ) { | |||
| offset_array[i] = 0.0; | |||
| window_array[i] = hann( (double) i / ((double) smooth_size - 1.0) ); | |||
| } | |||
| jack_buffer_size = jack_get_buffer_size( client ); | |||
| // Setup target delay and max_diff for the normal user, who does not play with them... | |||
| if( !target_delay ) | |||
| target_delay = (num_periods*period_size / 2) - jack_buffer_size/2; | |||
| if( !max_diff ) | |||
| max_diff = target_delay; | |||
| if( max_diff > target_delay ) { | |||
| fprintf( stderr, "target_delay (%d) cant be smaller than max_diff(%d)\n", target_delay, max_diff ); | |||
| exit(20); | |||
| } | |||
| if( (target_delay+max_diff) > (num_periods*period_size) ) { | |||
| fprintf( stderr, "target_delay+max_diff (%d) cant be bigger than buffersize(%d)\n", target_delay+max_diff, num_periods*period_size ); | |||
| exit(20); | |||
| } | |||
| // now open the alsa fd... | |||
| alsa_handle = open_audiofd( alsa_device, 0, sample_rate, num_channels, period_size, num_periods); | |||
| if( alsa_handle == 0 ) | |||
| exit(20); | |||
| printf( "selected sample format: %s\n", formats[format].name ); | |||
| // alloc input ports, which are blasted out to alsa... | |||
| alloc_ports( 0, num_channels ); | |||
| /* tell the JACK server that we are ready to roll */ | |||
| if (jack_activate (client)) { | |||
| fprintf (stderr, "cannot activate client"); | |||
| return 1; | |||
| } | |||
| signal( SIGTERM, sigterm_handler ); | |||
| signal( SIGINT, sigterm_handler ); | |||
| if( verbose ) { | |||
| while(!quit) { | |||
| usleep(500000); | |||
| if( output_new_delay ) { | |||
| printf( "delay = %d\n", output_new_delay ); | |||
| output_new_delay = 0; | |||
| } | |||
| printf( "res: %f, \tdiff = %f, \toffset = %f \n", output_resampling_factor, output_diff, output_offset ); | |||
| } | |||
| } else if( instrument ) { | |||
| printf( "# n\tresamp\tdiff\toffseti\tintegral\n"); | |||
| int n=0; | |||
| while(!quit) { | |||
| usleep(1000); | |||
| printf( "%d\t%f\t%f\t%f\t%f\n", n++, output_resampling_factor, output_diff, output_offset, output_integral ); | |||
| } | |||
| } else { | |||
| while(!quit) | |||
| { | |||
| usleep(500000); | |||
| if( output_new_delay ) { | |||
| printf( "delay = %d\n", output_new_delay ); | |||
| output_new_delay = 0; | |||
| } | |||
| } | |||
| } | |||
| jack_deactivate( client ); | |||
| jack_client_close (client); | |||
| exit (0); | |||
| } | |||
| @@ -0,0 +1,758 @@ | |||
| /* | |||
| NetJack Client | |||
| Copyright (C) 2008 Marc-Olivier Barre <marco@marcochapeau.org> | |||
| Copyright (C) 2008 Pieter Palmers <pieterpalmers@users.sourceforge.net> | |||
| Copyright (C) 2006 Torben Hohn <torbenh@gmx.de> | |||
| 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. | |||
| */ | |||
| /** @file netsource.c | |||
| * | |||
| * @brief This client connects a remote slave JACK to a local JACK server assumed to be the master | |||
| */ | |||
| #include <stdio.h> | |||
| #include <errno.h> | |||
| #include <unistd.h> | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #ifdef WIN32 | |||
| #include <winsock2.h> | |||
| #include <malloc.h> | |||
| #else | |||
| #include <netinet/in.h> | |||
| #include <netdb.h> | |||
| #include <sys/socket.h> | |||
| #endif | |||
| /* These two required by FreeBSD. */ | |||
| #include <sys/types.h> | |||
| #if HAVE_ALLOCA_H | |||
| #include <alloca.h> | |||
| #endif | |||
| #include <jack/jack.h> | |||
| //#include <net_driver.h> | |||
| #include <netjack_packet.h> | |||
| #if HAVE_SAMPLERATE | |||
| #include <samplerate.h> | |||
| #endif | |||
| #if HAVE_CELT | |||
| #include <celt/celt.h> | |||
| #endif | |||
| #include <math.h> | |||
| JSList *capture_ports = NULL; | |||
| JSList *capture_srcs = NULL; | |||
| int capture_channels = 0; | |||
| int capture_channels_audio = 2; | |||
| int capture_channels_midi = 1; | |||
| JSList *playback_ports = NULL; | |||
| JSList *playback_srcs = NULL; | |||
| int playback_channels = 0; | |||
| int playback_channels_audio = 2; | |||
| int playback_channels_midi = 1; | |||
| int dont_htonl_floats = 0; | |||
| int latency = 5; | |||
| jack_nframes_t factor = 1; | |||
| int bitdepth = 0; | |||
| int mtu = 1400; | |||
| int reply_port = 0; | |||
| int redundancy = 1; | |||
| jack_client_t *client; | |||
| int state_connected = 0; | |||
| int state_latency = 0; | |||
| int state_netxruns = 0; | |||
| int state_currentframe = 0; | |||
| int state_recv_packet_queue_time = 0; | |||
| int outsockfd; | |||
| int insockfd; | |||
| #ifdef WIN32 | |||
| struct sockaddr_in destaddr; | |||
| struct sockaddr_in bindaddr; | |||
| #else | |||
| struct sockaddr destaddr; | |||
| struct sockaddr bindaddr; | |||
| #endif | |||
| int sync_state; | |||
| jack_transport_state_t last_transport_state; | |||
| int framecnt = 0; | |||
| int cont_miss = 0; | |||
| int freewheeling = 0; | |||
| /** | |||
| * This Function allocates all the I/O Ports which are added the lists. | |||
| */ | |||
| void | |||
| alloc_ports (int n_capture_audio, int n_playback_audio, int n_capture_midi, int n_playback_midi) | |||
| { | |||
| int port_flags = JackPortIsOutput; | |||
| int chn; | |||
| jack_port_t *port; | |||
| char buf[32]; | |||
| capture_ports = NULL; | |||
| /* Allocate audio capture channels */ | |||
| for (chn = 0; chn < n_capture_audio; chn++) | |||
| { | |||
| snprintf (buf, sizeof (buf) - 1, "capture_%u", chn + 1); | |||
| port = jack_port_register (client, buf, JACK_DEFAULT_AUDIO_TYPE, port_flags, 0); | |||
| if (!port) | |||
| { | |||
| printf( "jack_netsource: cannot register %s port\n", buf); | |||
| break; | |||
| } | |||
| if( bitdepth == 1000 ) { | |||
| #if HAVE_CELT | |||
| #if HAVE_CELT_API_0_7 | |||
| CELTMode *celt_mode = celt_mode_create( jack_get_sample_rate( client ), jack_get_buffer_size(client), NULL ); | |||
| capture_srcs = jack_slist_append(capture_srcs, celt_decoder_create( celt_mode, 1, NULL ) ); | |||
| #else | |||
| CELTMode *celt_mode = celt_mode_create( jack_get_sample_rate( client ), 1, jack_get_buffer_size(client), NULL ); | |||
| capture_srcs = jack_slist_append(capture_srcs, celt_decoder_create( celt_mode ) ); | |||
| #endif | |||
| #endif | |||
| } else { | |||
| #if HAVE_SAMPLERATE | |||
| capture_srcs = jack_slist_append (capture_srcs, src_new (SRC_LINEAR, 1, NULL)); | |||
| #endif | |||
| } | |||
| capture_ports = jack_slist_append (capture_ports, port); | |||
| } | |||
| /* Allocate midi capture channels */ | |||
| for (chn = n_capture_audio; chn < n_capture_midi + n_capture_audio; chn++) | |||
| { | |||
| snprintf (buf, sizeof (buf) - 1, "capture_%u", chn + 1); | |||
| port = jack_port_register (client, buf, JACK_DEFAULT_MIDI_TYPE, port_flags, 0); | |||
| if (!port) | |||
| { | |||
| printf ("jack_netsource: cannot register %s port\n", buf); | |||
| break; | |||
| } | |||
| capture_ports = jack_slist_append(capture_ports, port); | |||
| } | |||
| /* Allocate audio playback channels */ | |||
| port_flags = JackPortIsInput; | |||
| playback_ports = NULL; | |||
| for (chn = 0; chn < n_playback_audio; chn++) | |||
| { | |||
| snprintf (buf, sizeof (buf) - 1, "playback_%u", chn + 1); | |||
| port = jack_port_register (client, buf, JACK_DEFAULT_AUDIO_TYPE, port_flags, 0); | |||
| if (!port) | |||
| { | |||
| printf ("jack_netsource: cannot register %s port\n", buf); | |||
| break; | |||
| } | |||
| if( bitdepth == 1000 ) { | |||
| #if HAVE_CELT | |||
| #if HAVE_CELT_API_0_7 | |||
| CELTMode *celt_mode = celt_mode_create( jack_get_sample_rate (client), jack_get_buffer_size(client), NULL ); | |||
| playback_srcs = jack_slist_append(playback_srcs, celt_encoder_create( celt_mode, 1, NULL ) ); | |||
| #else | |||
| CELTMode *celt_mode = celt_mode_create( jack_get_sample_rate (client), 1, jack_get_buffer_size(client), NULL ); | |||
| playback_srcs = jack_slist_append(playback_srcs, celt_encoder_create( celt_mode ) ); | |||
| #endif | |||
| #endif | |||
| } else { | |||
| #if HAVE_SAMPLERATE | |||
| playback_srcs = jack_slist_append (playback_srcs, src_new (SRC_LINEAR, 1, NULL)); | |||
| #endif | |||
| } | |||
| playback_ports = jack_slist_append (playback_ports, port); | |||
| } | |||
| /* Allocate midi playback channels */ | |||
| for (chn = n_playback_audio; chn < n_playback_midi + n_playback_audio; chn++) | |||
| { | |||
| snprintf (buf, sizeof (buf) - 1, "playback_%u", chn + 1); | |||
| port = jack_port_register (client, buf, JACK_DEFAULT_MIDI_TYPE, port_flags, 0); | |||
| if (!port) | |||
| { | |||
| printf ("jack_netsource: cannot register %s port\n", buf); | |||
| break; | |||
| } | |||
| playback_ports = jack_slist_append (playback_ports, port); | |||
| } | |||
| } | |||
| /** | |||
| * The Sync callback... sync state is set elsewhere... | |||
| * we will see if this is working correctly. | |||
| * i dont really believe in it yet. | |||
| */ | |||
| int | |||
| sync_cb (jack_transport_state_t state, jack_position_t *pos, void *arg) | |||
| { | |||
| static int latency_count = 0; | |||
| int retval = sync_state; | |||
| if (latency_count) { | |||
| latency_count--; | |||
| retval = 0; | |||
| } | |||
| else if (state == JackTransportStarting && last_transport_state != JackTransportStarting) | |||
| { | |||
| retval = 0; | |||
| latency_count = latency - 1; | |||
| } | |||
| last_transport_state = state; | |||
| return retval; | |||
| } | |||
| void | |||
| freewheel_cb (int starting, void *arg) | |||
| { | |||
| freewheeling = starting; | |||
| } | |||
| int deadline_goodness=0; | |||
| /** | |||
| * The process callback for this JACK application. | |||
| * It is called by JACK at the appropriate times. | |||
| */ | |||
| int | |||
| process (jack_nframes_t nframes, void *arg) | |||
| { | |||
| jack_nframes_t net_period; | |||
| int rx_bufsize, tx_bufsize; | |||
| jack_default_audio_sample_t *buf; | |||
| jack_port_t *port; | |||
| JSList *node; | |||
| int chn; | |||
| int size, i; | |||
| const char *porttype; | |||
| int input_fd; | |||
| jack_position_t local_trans_pos; | |||
| uint32_t *packet_buf, *packet_bufX; | |||
| uint32_t *rx_packet_ptr; | |||
| jack_time_t packet_recv_timestamp; | |||
| if( bitdepth == 1000 ) | |||
| net_period = factor; | |||
| else | |||
| net_period = (float) nframes / (float) factor; | |||
| rx_bufsize = get_sample_size (bitdepth) * capture_channels * net_period + sizeof (jacknet_packet_header); | |||
| tx_bufsize = get_sample_size (bitdepth) * playback_channels * net_period + sizeof (jacknet_packet_header); | |||
| /* Allocate a buffer where both In and Out Buffer will fit */ | |||
| packet_buf = alloca ((rx_bufsize > tx_bufsize) ? rx_bufsize : tx_bufsize); | |||
| jacknet_packet_header *pkthdr = (jacknet_packet_header *) packet_buf; | |||
| /* | |||
| * for latency==0 we need to send out the packet before we wait on the reply. | |||
| * but this introduces a cycle of latency, when netsource is connected to itself. | |||
| * so we send out before read only in zero latency mode. | |||
| * | |||
| */ | |||
| if( latency == 0 ) { | |||
| /* reset packet_bufX... */ | |||
| packet_bufX = packet_buf + sizeof (jacknet_packet_header) / sizeof (jack_default_audio_sample_t); | |||
| /* ---------- Send ---------- */ | |||
| render_jack_ports_to_payload (bitdepth, playback_ports, playback_srcs, nframes, | |||
| packet_bufX, net_period, dont_htonl_floats); | |||
| /* fill in packet hdr */ | |||
| pkthdr->transport_state = jack_transport_query (client, &local_trans_pos); | |||
| pkthdr->transport_frame = local_trans_pos.frame; | |||
| pkthdr->framecnt = framecnt; | |||
| pkthdr->latency = latency; | |||
| pkthdr->reply_port = reply_port; | |||
| pkthdr->sample_rate = jack_get_sample_rate (client); | |||
| pkthdr->period_size = nframes; | |||
| /* playback for us is capture on the other side */ | |||
| pkthdr->capture_channels_audio = playback_channels_audio; | |||
| pkthdr->playback_channels_audio = capture_channels_audio; | |||
| pkthdr->capture_channels_midi = playback_channels_midi; | |||
| pkthdr->playback_channels_midi = capture_channels_midi; | |||
| pkthdr->mtu = mtu; | |||
| if( freewheeling!= 0 ) | |||
| pkthdr->sync_state = (jack_nframes_t)MASTER_FREEWHEELS; | |||
| else | |||
| pkthdr->sync_state = (jack_nframes_t)deadline_goodness; | |||
| //printf("goodness=%d\n", deadline_goodness ); | |||
| packet_header_hton (pkthdr); | |||
| if (cont_miss < 3*latency+5) { | |||
| int r; | |||
| for( r=0; r<redundancy; r++ ) | |||
| netjack_sendto (outsockfd, (char *) packet_buf, tx_bufsize, 0, &destaddr, sizeof (destaddr), mtu); | |||
| } | |||
| else if (cont_miss > 50+5*latency) | |||
| { | |||
| state_connected = 0; | |||
| packet_cache_reset_master_address( global_packcache ); | |||
| //printf ("Frame %d \tRealy too many packets missed (%d). Let's reset the counter\n", framecnt, cont_miss); | |||
| cont_miss = 0; | |||
| } | |||
| } | |||
| /* | |||
| * ok... now the RECEIVE code. | |||
| * | |||
| */ | |||
| /* reset packet_bufX... */ | |||
| packet_bufX = packet_buf + sizeof (jacknet_packet_header) / sizeof (jack_default_audio_sample_t); | |||
| if( reply_port ) | |||
| input_fd = insockfd; | |||
| else | |||
| input_fd = outsockfd; | |||
| // for latency == 0 we can poll. | |||
| if( (latency == 0) || (freewheeling!=0) ) { | |||
| jack_time_t deadline = jack_get_time() + 1000000 * jack_get_buffer_size(client)/jack_get_sample_rate(client); | |||
| // Now loop until we get the right packet. | |||
| while(1) { | |||
| jack_nframes_t got_frame; | |||
| if ( ! netjack_poll_deadline( input_fd, deadline ) ) | |||
| break; | |||
| packet_cache_drain_socket(global_packcache, input_fd); | |||
| if (packet_cache_get_next_available_framecnt( global_packcache, framecnt - latency, &got_frame )) | |||
| if( got_frame == (framecnt - latency) ) | |||
| break; | |||
| } | |||
| } else { | |||
| // normally: | |||
| // only drain socket. | |||
| packet_cache_drain_socket(global_packcache, input_fd); | |||
| } | |||
| size = packet_cache_retreive_packet_pointer( global_packcache, framecnt - latency, (char**)&rx_packet_ptr, rx_bufsize, &packet_recv_timestamp ); | |||
| /* First alternative : we received what we expected. Render the data | |||
| * to the JACK ports so it can be played. */ | |||
| if (size == rx_bufsize) | |||
| { | |||
| packet_buf = rx_packet_ptr; | |||
| pkthdr = (jacknet_packet_header *) packet_buf; | |||
| packet_bufX = packet_buf + sizeof (jacknet_packet_header) / sizeof (jack_default_audio_sample_t); | |||
| // calculate how much time there would have been, if this packet was sent at the deadline. | |||
| int recv_time_offset = (int) (jack_get_time() - packet_recv_timestamp); | |||
| packet_header_ntoh (pkthdr); | |||
| deadline_goodness = recv_time_offset - (int)pkthdr->latency; | |||
| //printf( "deadline goodness = %d ---> off: %d\n", deadline_goodness, recv_time_offset ); | |||
| if (cont_miss) | |||
| { | |||
| //printf("Frame %d \tRecovered from dropouts\n", framecnt); | |||
| cont_miss = 0; | |||
| } | |||
| render_payload_to_jack_ports (bitdepth, packet_bufX, net_period, | |||
| capture_ports, capture_srcs, nframes, dont_htonl_floats); | |||
| state_currentframe = framecnt; | |||
| state_recv_packet_queue_time = recv_time_offset; | |||
| state_connected = 1; | |||
| sync_state = pkthdr->sync_state; | |||
| packet_cache_release_packet( global_packcache, framecnt - latency ); | |||
| } | |||
| /* Second alternative : we've received something that's not | |||
| * as big as expected or we missed a packet. We render silence | |||
| * to the ouput ports */ | |||
| else | |||
| { | |||
| jack_nframes_t latency_estimate; | |||
| if( packet_cache_find_latency( global_packcache, framecnt, &latency_estimate ) ) | |||
| //if( (state_latency == 0) || (latency_estimate < state_latency) ) | |||
| state_latency = latency_estimate; | |||
| // Set the counters up. | |||
| state_currentframe = framecnt; | |||
| //state_latency = framecnt - pkthdr->framecnt; | |||
| state_netxruns += 1; | |||
| //printf ("Frame %d \tPacket missed or incomplete (expected: %d bytes, got: %d bytes)\n", framecnt, rx_bufsize, size); | |||
| //printf ("Frame %d \tPacket missed or incomplete\n", framecnt); | |||
| cont_miss += 1; | |||
| chn = 0; | |||
| node = capture_ports; | |||
| while (node != NULL) | |||
| { | |||
| port = (jack_port_t *) node->data; | |||
| buf = jack_port_get_buffer (port, nframes); | |||
| porttype = jack_port_type (port); | |||
| if (strncmp (porttype, JACK_DEFAULT_AUDIO_TYPE, jack_port_type_size ()) == 0) | |||
| for (i = 0; i < nframes; i++) | |||
| buf[i] = 0.0; | |||
| else if (strncmp (porttype, JACK_DEFAULT_MIDI_TYPE, jack_port_type_size ()) == 0) | |||
| jack_midi_clear_buffer (buf); | |||
| node = jack_slist_next (node); | |||
| chn++; | |||
| } | |||
| } | |||
| if( latency != 0 ) { | |||
| /* reset packet_bufX... */ | |||
| packet_bufX = packet_buf + sizeof (jacknet_packet_header) / sizeof (jack_default_audio_sample_t); | |||
| /* ---------- Send ---------- */ | |||
| render_jack_ports_to_payload (bitdepth, playback_ports, playback_srcs, nframes, | |||
| packet_bufX, net_period, dont_htonl_floats); | |||
| /* fill in packet hdr */ | |||
| pkthdr->transport_state = jack_transport_query (client, &local_trans_pos); | |||
| pkthdr->transport_frame = local_trans_pos.frame; | |||
| pkthdr->framecnt = framecnt; | |||
| pkthdr->latency = latency; | |||
| pkthdr->reply_port = reply_port; | |||
| pkthdr->sample_rate = jack_get_sample_rate (client); | |||
| pkthdr->period_size = nframes; | |||
| /* playback for us is capture on the other side */ | |||
| pkthdr->capture_channels_audio = playback_channels_audio; | |||
| pkthdr->playback_channels_audio = capture_channels_audio; | |||
| pkthdr->capture_channels_midi = playback_channels_midi; | |||
| pkthdr->playback_channels_midi = capture_channels_midi; | |||
| pkthdr->mtu = mtu; | |||
| if( freewheeling!= 0 ) | |||
| pkthdr->sync_state = (jack_nframes_t)MASTER_FREEWHEELS; | |||
| else | |||
| pkthdr->sync_state = (jack_nframes_t)deadline_goodness; | |||
| //printf("goodness=%d\n", deadline_goodness ); | |||
| packet_header_hton (pkthdr); | |||
| if (cont_miss < 3*latency+5) { | |||
| int r; | |||
| for( r=0; r<redundancy; r++ ) | |||
| netjack_sendto (outsockfd, (char *) packet_buf, tx_bufsize, 0, &destaddr, sizeof (destaddr), mtu); | |||
| } | |||
| else if (cont_miss > 50+5*latency) | |||
| { | |||
| state_connected = 0; | |||
| packet_cache_reset_master_address( global_packcache ); | |||
| //printf ("Frame %d \tRealy too many packets missed (%d). Let's reset the counter\n", framecnt, cont_miss); | |||
| cont_miss = 0; | |||
| } | |||
| } | |||
| framecnt++; | |||
| return 0; | |||
| } | |||
| /** | |||
| * This is the shutdown callback for this JACK application. | |||
| * It is called by JACK if the server ever shuts down or | |||
| * decides to disconnect the client. | |||
| */ | |||
| void | |||
| jack_shutdown (void *arg) | |||
| { | |||
| exit (1); | |||
| } | |||
| void | |||
| init_sockaddr_in (struct sockaddr_in *name , const char *hostname , uint16_t port) | |||
| { | |||
| printf( "still here... \n" ); | |||
| fflush( stdout ); | |||
| name->sin_family = AF_INET ; | |||
| name->sin_port = htons (port); | |||
| if (hostname) | |||
| { | |||
| struct hostent *hostinfo = gethostbyname (hostname); | |||
| if (hostinfo == NULL) { | |||
| fprintf (stderr, "init_sockaddr_in: unknown host: %s.\n", hostname); | |||
| fflush( stderr ); | |||
| } | |||
| #ifdef WIN32 | |||
| name->sin_addr.s_addr = inet_addr( hostname ); | |||
| #else | |||
| name->sin_addr = *(struct in_addr *) hostinfo->h_addr ; | |||
| #endif | |||
| } | |||
| else | |||
| name->sin_addr.s_addr = htonl (INADDR_ANY) ; | |||
| } | |||
| void | |||
| printUsage () | |||
| { | |||
| fprintf (stderr, "usage: jack_netsource -h <host peer> [options]\n" | |||
| "\n" | |||
| " -n <jack name> - Reports a different name to jack\n" | |||
| " -s <server name> - The name of the local jack server\n" | |||
| " -h <host_peer> - Host name of the slave JACK\n" | |||
| " -p <port> - UDP port used by the slave JACK\n" | |||
| " -P <num channels> - Number of audio playback channels\n" | |||
| " -C <num channels> - Number of audio capture channels\n" | |||
| " -o <num channels> - Number of midi playback channels\n" | |||
| " -i <num channels> - Number of midi capture channels\n" | |||
| " -l <latency> - Network latency in number of NetJack frames\n" | |||
| " -r <reply port> - Local UDP port to use\n" | |||
| " -f <downsample ratio> - Downsample data in the wire by this factor\n" | |||
| " -b <bitdepth> - Set transport to use 16bit or 8bit\n" | |||
| " -m <mtu> - Assume this mtu for the link\n" | |||
| " -c <bytes> - Use Celt and encode <bytes> per channel and packet.\n" | |||
| " -R <N> - Send out packets N times.\n" | |||
| "\n"); | |||
| } | |||
| int | |||
| main (int argc, char *argv[]) | |||
| { | |||
| /* Some startup related basics */ | |||
| char *client_name, *server_name = NULL, *peer_ip; | |||
| int peer_port = 3000; | |||
| jack_options_t options = JackNullOption; | |||
| jack_status_t status; | |||
| #ifdef WIN32 | |||
| WSADATA wsa; | |||
| int rc = WSAStartup(MAKEWORD(2,0),&wsa); | |||
| #endif | |||
| /* Torben's famous state variables, aka "the reporting API" ! */ | |||
| /* heh ? these are only the copies of them ;) */ | |||
| int statecopy_connected, statecopy_latency, statecopy_netxruns; | |||
| jack_nframes_t net_period; | |||
| /* Argument parsing stuff */ | |||
| extern char *optarg; | |||
| extern int optind, optopt; | |||
| int errflg=0, c; | |||
| if (argc < 3) | |||
| { | |||
| printUsage (); | |||
| return 1; | |||
| } | |||
| client_name = (char *) malloc (sizeof (char) * 10); | |||
| peer_ip = (char *) malloc (sizeof (char) * 10); | |||
| sprintf(client_name, "netsource"); | |||
| sprintf(peer_ip, "localhost"); | |||
| while ((c = getopt (argc, argv, ":H:R:n:s:h:p:C:P:i:o:l:r:f:b:m:c:")) != -1) | |||
| { | |||
| switch (c) | |||
| { | |||
| case 'n': | |||
| free(client_name); | |||
| client_name = (char *) malloc (sizeof (char) * strlen (optarg)+1); | |||
| strcpy (client_name, optarg); | |||
| break; | |||
| case 's': | |||
| server_name = (char *) malloc (sizeof (char) * strlen (optarg)+1); | |||
| strcpy (server_name, optarg); | |||
| options |= JackServerName; | |||
| break; | |||
| case 'h': | |||
| free(peer_ip); | |||
| peer_ip = (char *) malloc (sizeof (char) * strlen (optarg)+1); | |||
| strcpy (peer_ip, optarg); | |||
| break; | |||
| case 'p': | |||
| peer_port = atoi (optarg); | |||
| break; | |||
| case 'P': | |||
| playback_channels_audio = atoi (optarg); | |||
| break; | |||
| case 'C': | |||
| capture_channels_audio = atoi (optarg); | |||
| break; | |||
| case 'o': | |||
| playback_channels_midi = atoi (optarg); | |||
| break; | |||
| case 'i': | |||
| capture_channels_midi = atoi (optarg); | |||
| break; | |||
| case 'l': | |||
| latency = atoi (optarg); | |||
| break; | |||
| case 'r': | |||
| reply_port = atoi (optarg); | |||
| break; | |||
| case 'f': | |||
| factor = atoi (optarg); | |||
| break; | |||
| case 'b': | |||
| bitdepth = atoi (optarg); | |||
| break; | |||
| case 'c': | |||
| #if HAVE_CELT | |||
| bitdepth = 1000; | |||
| factor = atoi (optarg); | |||
| #else | |||
| printf( "not built with celt supprt\n" ); | |||
| exit(10); | |||
| #endif | |||
| break; | |||
| case 'm': | |||
| mtu = atoi (optarg); | |||
| break; | |||
| case 'R': | |||
| redundancy = atoi (optarg); | |||
| break; | |||
| case 'H': | |||
| dont_htonl_floats = atoi (optarg); | |||
| break; | |||
| case ':': | |||
| fprintf (stderr, "Option -%c requires an operand\n", optopt); | |||
| errflg++; | |||
| break; | |||
| case '?': | |||
| fprintf (stderr, "Unrecognized option: -%c\n", optopt); | |||
| errflg++; | |||
| } | |||
| } | |||
| if (errflg) | |||
| { | |||
| printUsage (); | |||
| exit (2); | |||
| } | |||
| capture_channels = capture_channels_audio + capture_channels_midi; | |||
| playback_channels = playback_channels_audio + playback_channels_midi; | |||
| outsockfd = socket (AF_INET, SOCK_DGRAM, 0); | |||
| insockfd = socket (AF_INET, SOCK_DGRAM, 0); | |||
| if( (outsockfd == -1) || (insockfd == -1) ) { | |||
| fprintf (stderr, "cant open sockets\n" ); | |||
| return 1; | |||
| } | |||
| init_sockaddr_in ((struct sockaddr_in *) &destaddr, peer_ip, peer_port); | |||
| if(reply_port) | |||
| { | |||
| init_sockaddr_in ((struct sockaddr_in *) &bindaddr, NULL, reply_port); | |||
| if( bind (insockfd, &bindaddr, sizeof (bindaddr)) ) { | |||
| fprintf (stderr, "bind failure\n" ); | |||
| } | |||
| } | |||
| /* try to become a client of the JACK server */ | |||
| client = jack_client_open (client_name, options, &status, server_name); | |||
| if (client == NULL) | |||
| { | |||
| fprintf (stderr, "jack_client_open() failed, status = 0x%2.0x\n" | |||
| "Is the JACK server running ?\n", status); | |||
| return 1; | |||
| } | |||
| /* Set up jack callbacks */ | |||
| jack_set_process_callback (client, process, 0); | |||
| jack_set_sync_callback (client, sync_cb, 0); | |||
| jack_set_freewheel_callback (client, freewheel_cb, 0); | |||
| jack_on_shutdown (client, jack_shutdown, 0); | |||
| alloc_ports (capture_channels_audio, playback_channels_audio, capture_channels_midi, playback_channels_midi); | |||
| if( bitdepth == 1000 ) | |||
| net_period = factor; | |||
| else | |||
| net_period = ceilf((float) jack_get_buffer_size (client) / (float) factor); | |||
| int rx_bufsize = get_sample_size (bitdepth) * capture_channels * net_period + sizeof (jacknet_packet_header); | |||
| global_packcache = packet_cache_new (latency + 50, rx_bufsize, mtu); | |||
| /* tell the JACK server that we are ready to roll */ | |||
| if (jack_activate (client)) | |||
| { | |||
| fprintf (stderr, "Cannot activate client"); | |||
| return 1; | |||
| } | |||
| /* Now sleep forever... and evaluate the state_ vars */ | |||
| statecopy_connected = 2; // make it report unconnected on start. | |||
| statecopy_latency = state_latency; | |||
| statecopy_netxruns = state_netxruns; | |||
| while (1) | |||
| { | |||
| #ifdef WIN32 | |||
| Sleep (1000); | |||
| #else | |||
| sleep(1); | |||
| #endif | |||
| if (statecopy_connected != state_connected) | |||
| { | |||
| statecopy_connected = state_connected; | |||
| if (statecopy_connected) | |||
| { | |||
| state_netxruns = 1; // We want to reset the netxrun count on each new connection | |||
| printf ("Connected :-)\n"); | |||
| } | |||
| else | |||
| printf ("Not Connected\n"); | |||
| fflush(stdout); | |||
| } | |||
| if (statecopy_connected) | |||
| { | |||
| if (statecopy_netxruns != state_netxruns) { | |||
| statecopy_netxruns = state_netxruns; | |||
| printf ("%s: at frame %06d -> total netxruns %d (%d%%) queue time= %d\n", | |||
| client_name, | |||
| state_currentframe, | |||
| statecopy_netxruns, | |||
| 100*statecopy_netxruns/state_currentframe, | |||
| state_recv_packet_queue_time); | |||
| fflush(stdout); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| if (statecopy_latency != state_latency) | |||
| { | |||
| statecopy_latency = state_latency; | |||
| if (statecopy_latency > 1) | |||
| printf ("current latency %d\n", statecopy_latency); | |||
| fflush(stdout); | |||
| } | |||
| } | |||
| } | |||
| /* Never reached. Well we will be a GtkApp someday... */ | |||
| packet_cache_free (global_packcache); | |||
| jack_client_close (client); | |||
| exit (0); | |||
| } | |||
| @@ -30,9 +30,16 @@ example_libs = { | |||
| def configure(conf): | |||
| e = conf.check_cc(header_name='sndfile.h', define_name="HAVE_SNDFILE") | |||
| conf.check_cc(header_name='samplerate.h', define_name="HAVE_SAMPLERATE") | |||
| if conf.is_defined('HAVE_SAMPLERATE'): | |||
| conf.env['LIB_SAMPLERATE'] = ['samplerate'] | |||
| if conf.is_defined('HAVE_SNDFILE'): | |||
| conf.env['LIB_SNDFILE'] = ['sndfile'] | |||
| conf.check_cfg(package='celt', atleast_version='0.5.0', args='--cflags --libs') | |||
| e = conf.check_cc(header_name='ncurses.h', define_name="HAVE_NCURSES") | |||
| if conf.is_defined('HAVE_NCURSES'): | |||
| @@ -105,6 +112,37 @@ def build(bld): | |||
| prog.uselib_local = 'clientlib' | |||
| prog.target = 'jack_rec' | |||
| prog = bld.new_task_gen('cc', 'program') | |||
| prog.includes = os_incdir + ['../common/jack', '../common'] | |||
| prog.source = ['netsource.c', '../common/netjack_packet.c'] | |||
| prog.env.append_value("CCFLAGS", "-DNO_JACK_ERROR") | |||
| if bld.env['HAVE_CELT']: | |||
| if bld.env['HAVE_CELT_API_0_5']: | |||
| prog.defines = ['HAVE_CELT', 'HAVE_CELT_API_0_5'] | |||
| elif bld.env['HAVE_CELT_API_0_7']: | |||
| prog.defines = ['HAVE_CELT', 'HAVE_CELT_API_0_7'] | |||
| prog.uselib = 'CELT SAMPLERATE' | |||
| prog.uselib_local = 'clientlib' | |||
| prog.target = 'jack_netsource' | |||
| if bld.env['IS_LINUX']: | |||
| prog = bld.new_task_gen('cc', 'program') | |||
| prog.includes = os_incdir + ['../common/jack', '../common'] | |||
| prog.source = ['alsa_in.c', '../common/memops.c'] | |||
| prog.env.append_value("CCFLAGS", "-DNO_JACK_ERROR") | |||
| prog.uselib = 'ALSA SAMPLERATE' | |||
| prog.uselib_local = 'clientlib' | |||
| prog.target = 'alsa_in' | |||
| prog = bld.new_task_gen('cc', 'program') | |||
| prog.includes = os_incdir + ['../common/jack', '../common'] | |||
| prog.source = ['alsa_out.c', '../common/memops.c'] | |||
| prog.env.append_value("CCFLAGS", "-DNO_JACK_ERROR") | |||
| prog.uselib = 'ALSA SAMPLERATE' | |||
| prog.uselib_local = 'clientlib' | |||
| prog.target = 'alsa_out' | |||
| for example_lib, example_lib_source in example_libs.items(): | |||
| lib = bld.new_task_gen('cc', 'shlib') | |||
| lib.env['shlib_PATTERN'] = '%s.so' | |||
| @@ -15,7 +15,12 @@ def create_jack_driver_obj(bld, target, sources, uselib = None): | |||
| driver = bld.new_task_gen('cxx', 'shlib') | |||
| driver.features.append('cc') | |||
| driver.env['shlib_PATTERN'] = 'jack_%s.so' | |||
| driver.defines = ['HAVE_CONFIG_H','SERVER_SIDE'] | |||
| driver.defines = ['HAVE_CONFIG_H','SERVER_SIDE', 'HAVE_PPOLL'] | |||
| if bld.env['HAVE_CELT']: | |||
| if bld.env['HAVE_CELT_API_0_5']: | |||
| driver.defines += ['HAVE_CELT', 'HAVE_CELT_API_0_5'] | |||
| elif bld.env['HAVE_CELT_API_0_7']: | |||
| driver.defines += ['HAVE_CELT', 'HAVE_CELT_API_0_7'] | |||
| driver.includes = ['.', '../linux', '../posix', '../common', '../common/jack', '../dbus'] | |||
| driver.target = target | |||
| driver.source = sources | |||
| @@ -64,3 +69,8 @@ def build(bld): | |||
| create_jack_driver_obj(bld, 'net', '../common/JackNetDriver.cpp') | |||
| create_jack_driver_obj(bld, 'loopback', '../common/JackLoopbackDriver.cpp') | |||
| create_jack_driver_obj(bld, 'netone', [ '../common/JackNetOneDriver.cpp', | |||
| '../common/netjack.c', | |||
| '../common/netjack_packet.c' ], "SAMPLERATE CELT" ) | |||
| @@ -6,6 +6,11 @@ def create_jack_driver_obj(bld, target, sources, uselib = None): | |||
| driver.features.append('cc') | |||
| driver.env['shlib_PATTERN'] = 'jack_%s.so' | |||
| driver.defines = ['HAVE_CONFIG_H','SERVER_SIDE'] | |||
| if bld.env['HAVE_CELT']: | |||
| if bld.env['HAVE_CELT_API_0_5']: | |||
| driver.defines += ['HAVE_CELT', 'HAVE_CELT_API_0_5'] | |||
| elif bld.env['HAVE_CELT_API_0_7']: | |||
| driver.defines += ['HAVE_CELT', 'HAVE_CELT_API_0_7'] | |||
| driver.includes = ['.', '../macosx', '../posix', '../common', '../common/jack'] | |||
| driver.target = target | |||
| driver.source = sources | |||
| @@ -73,3 +78,6 @@ def build(bld): | |||
| create_jack_driver_obj(bld, 'net', '../common/JackNetDriver.cpp') | |||
| create_jack_driver_obj(bld, 'netone', [ '../common/JackNetOneDriver.cpp', | |||
| '../common/netjack.c', | |||
| '../common/netjack_packet.c' ], "SAMPLERATE CELT" ) | |||
| @@ -224,6 +224,15 @@ def configure(conf): | |||
| else: | |||
| conf.env['LIBDIR'] = conf.env['PREFIX'] + '/lib32' | |||
| if conf.check_cfg(package='celt', atleast_version='0.7.0', args='--cflags --libs'): | |||
| conf.env['HAVE_CELT'] = True | |||
| conf.env['HAVE_CELT_API_0_7'] = True | |||
| elif conf.check_cfg(package='celt', atleast_version='0.5.0', args='--cflags --libs', required=True): | |||
| conf.env['HAVE_CELT'] = True | |||
| conf.env['HAVE_CELT_API_0_5'] = True | |||
| else: | |||
| conf.env['HAVE_CELT'] = False | |||
| def build(bld): | |||
| print ("make[1]: Entering directory `" + os.getcwd() + "/" + blddir + "'" ) | |||
| if not os.access('svnversion.h', os.R_OK): | |||