Index: /branches/libfreebob-2.0/src/libstreaming/MotuPort.h =================================================================== --- /branches/libfreebob-2.0/src/libstreaming/MotuPort.h (revision 267) +++ /branches/libfreebob-2.0/src/libstreaming/MotuPort.h (revision 312) @@ -79,5 +79,5 @@ int position) : MidiPort(name, direction), - MotuPortInfo(name, position, 2) // TODO: add more port information parameters here if nescessary + MotuPortInfo(name, position, 0) // TODO: add more port information parameters here if nescessary {}; Index: /branches/libfreebob-2.0/src/libstreaming/MotuStreamProcessor.h =================================================================== --- /branches/libfreebob-2.0/src/libstreaming/MotuStreamProcessor.h (revision 309) +++ /branches/libfreebob-2.0/src/libstreaming/MotuStreamProcessor.h (revision 312) @@ -76,4 +76,5 @@ virtual bool preparedForStop(); + virtual bool preparedForStart(); protected: @@ -165,4 +166,5 @@ virtual bool preparedForStop(); + virtual bool preparedForStart(); protected: Index: /branches/libfreebob-2.0/src/libstreaming/StreamProcessorManager.cpp =================================================================== --- /branches/libfreebob-2.0/src/libstreaming/StreamProcessorManager.cpp (revision 309) +++ /branches/libfreebob-2.0/src/libstreaming/StreamProcessorManager.cpp (revision 312) @@ -320,4 +320,8 @@ it != m_ReceiveProcessors.end(); ++it ) { + if (!(*it)->preparedForStart()) { + debugOutput(DEBUG_LEVEL_VERBOSE,"Receive stream processor (%p) failed to prepare for start\n", *it); + return false; + } if (!m_isoManager->registerStream(*it)) { debugOutput(DEBUG_LEVEL_VERBOSE,"Could not register receive stream processor (%p) with the Iso manager\n",*it); @@ -332,4 +336,8 @@ it != m_TransmitProcessors.end(); ++it ) { + if (!(*it)->preparedForStart()) { + debugOutput(DEBUG_LEVEL_VERBOSE,"Transmit stream processor (%p) failed to prepare for start\n", *it); + return false; + } if (!m_isoManager->registerStream(*it)) { debugOutput(DEBUG_LEVEL_VERBOSE,"Could not register transmit stream processor (%p) with the Iso manager\n",*it); Index: /branches/libfreebob-2.0/src/libstreaming/StreamProcessor.h =================================================================== --- /branches/libfreebob-2.0/src/libstreaming/StreamProcessor.h (revision 287) +++ /branches/libfreebob-2.0/src/libstreaming/StreamProcessor.h (revision 312) @@ -112,4 +112,5 @@ virtual bool preparedForStop() {return true;}; + virtual bool preparedForStart() {return true;}; protected: Index: /branches/libfreebob-2.0/src/libstreaming/MotuStreamProcessor.cpp =================================================================== --- /branches/libfreebob-2.0/src/libstreaming/MotuStreamProcessor.cpp (revision 310) +++ /branches/libfreebob-2.0/src/libstreaming/MotuStreamProcessor.cpp (revision 312) @@ -71,9 +71,4 @@ return false; } - m_next_cycle = -1; - m_closedown_count = -1; - m_streaming_active = 0; - m_cycle_count = -1; - m_cycle_ofs = 0.0; return true; @@ -115,5 +110,5 @@ // Similarly, initialise the "next cycle". This can be done // whenever iso data is seen - it doesn't have to wait until - // the stream is initialised. + // the stream is enabled. if (m_next_cycle < 0) m_next_cycle = cycle; @@ -138,8 +133,11 @@ // Detect a missed cycle and attempt to "catch up". - if (!m_disabled && m_next_cycle>=0 && cycle!=m_next_cycle) { + if (cycle != m_next_cycle) { + debugOutput(DEBUG_LEVEL_VERBOSE, "tx cycle miss: %d requested, %d expected\n",cycle,m_next_cycle); + } + // Attempt to catch up any missed cycles but only if we're enabled. + if (!m_disabled && cycle!=m_next_cycle) { float ftmp; signed int ccount = m_next_cycle; - debugOutput(DEBUG_LEVEL_VERBOSE, "tx cycle miss: %d requested, %d expected\n",cycle,m_next_cycle); while (ccount!=cycle) { @@ -175,9 +173,6 @@ } - if (!m_disabled) { - if (++m_next_cycle >= 8000) - m_next_cycle -= 8000; - } else - m_next_cycle = -1; + if ((m_next_cycle=cycle+1) >= 8000) + m_next_cycle -= 8000; // Deal cleanly with potential wrap-around cycle counter conditions @@ -250,4 +245,5 @@ } else { + retval=RAW1394_ISO_OK; *length += read_size; @@ -387,10 +383,4 @@ } - m_next_cycle = -1; - m_closedown_count = -1; - m_streaming_active = 0; - m_cycle_count = -1; - m_cycle_ofs = 0.0; - return true; } @@ -455,4 +445,14 @@ return false; } + if (!(*it)->setBufferType(Port::E_RingBuffer)) { + debugFatal("Could not set buffer type"); + return false; + } + if (!(*it)->setDataType(Port::E_MidiEvent)) { + debugFatal("Could not set data type"); + return false; + } + // FIXME: probably need rate control too. See + // Port::useRateControl() and AmdtpStreamProcessor. break; @@ -696,32 +696,57 @@ * @return true if all successfull */ -bool MotuTransmitStreamProcessor::encodePacketPorts(quadlet_t *data, unsigned int nevents, unsigned int dbc) -{ - bool ok=true; - char byte; - - quadlet_t *target_event=NULL; - int j; - - for ( PortVectorIterator it = m_PacketPorts.begin(); - it != m_PacketPorts.end(); - ++it ) - { +bool MotuTransmitStreamProcessor::encodePacketPorts(quadlet_t *data, unsigned int nevents, + unsigned int dbc) { + bool ok=true; + char byte; + + // Use char here since the target address won't necessarily be + // aligned; use of an unaligned quadlet_t may cause issues on + // certain architectures. Besides, the target for MIDI data going + // directly to the MOTU isn't structured in quadlets anyway; it is a + // sequence of 3 unaligned bytes. + unsigned char *target = NULL; + + for ( PortVectorIterator it = m_PacketPorts.begin(); + it != m_PacketPorts.end(); + ++it ) { #ifdef DEBUG - MotuPortInfo *pinfo=dynamic_cast(*it); - assert(pinfo); // this should not fail!! - - // the only packet type of events for AMDTP is MIDI in mbla -// assert(pinfo->getFormat()==MotuPortInfo::E_Midi); + //FIXME: make this into a static_cast when not DEBUG? + Port *port=dynamic_cast(*it); + assert(port); // this should not fail!! + + // Currently the only packet type of events for MOTU + // is MIDI in mbla. However in future control data + // might also be sent via "packet" events. + // assert(pinfo->getFormat()==MotuPortInfo::E_Midi); #endif - - MotuMidiPort *mp=static_cast(*it); - - // TODO: decode the midi (or other type) stuff here - - } - - return ok; + // FIXME: MIDI output is completely untested at present. + switch (port->getPortType()) { + case Port::E_Midi: { + MotuMidiPort *mp=static_cast(*it); + + // Send a byte if we can. MOTU MIDI data is + // sent using a 3-byte sequence starting at + // the port's position. For now we'll + // always send in the first event of a + // packet, but this might need refinement + // later. + if (mp->canRead()) { + mp->readEvent(&byte); + target = (unsigned char *)data + mp->getPosition(); + *(target++) = 0x01; + *(target++) = 0x00; + *(target++) = byte; + } + break; + } + default: + debugOutput(DEBUG_LEVEL_VERBOSE, "Unknown packet-type port type %d\n",port->getPortType()); + return ok; + } + } + + return ok; } @@ -871,4 +896,25 @@ } +bool MotuTransmitStreamProcessor::preparedForStart() { +// Reset some critical variables required so the stream starts cleanly. This +// method is called once on every stream restart, including those during +// xrun recovery. Initialisations which should be done once should be +// placed in the init() method instead. + m_running = 0; + m_next_cycle = -1; + m_closedown_count = -1; + m_streaming_active = 0; + m_cycle_count = -1; + m_cycle_ofs = 0.0; + + // At this point we'll also disable the stream processor here. + // At this stage stream processors are always explicitly re-enabled + // after being started, so by starting in the disabled state we + // ensure that every start will be exactly the same. + disable(); + + return true; +} + /* --------------------- RECEIVE ----------------------- */ @@ -890,13 +936,13 @@ bool MotuReceiveStreamProcessor::init() { - // call the parent init - // this has to be done before allocating the buffers, - // because this sets the buffersizes from the processormanager - if(!ReceiveStreamProcessor::init()) { - debugFatal("Could not do base class init (%d)\n",this); - return false; - } - - return true; + // call the parent init + // this has to be done before allocating the buffers, + // because this sets the buffersizes from the processormanager + if(!ReceiveStreamProcessor::init()) { + debugFatal("Could not do base class init (%d)\n",this); + return false; + } + + return true; } @@ -906,143 +952,150 @@ unsigned int cycle, unsigned int dropped) { - enum raw1394_iso_disposition retval=RAW1394_ISO_OK; - signed int have_lost_cycles = 0; - - // Detect missed receive cycles - // FIXME: it would be nice to advance the rx buffer by the amount of - // frames missed. However, since the MOTU transmits more frames per - // cycle than the average and "catches up" with periodic empty cycles - // it's not trivial to work out precisely how many frames were missed. - // Ultimately I think we need to do so if sync is to be maintained - // across a transient receive failure. - if (m_next_cycle < 0) - m_next_cycle = cycle; - if ((signed)cycle != m_next_cycle) { - debugOutput(DEBUG_LEVEL_VERBOSE, "lost rx cycles; received %d, expected %d\n", - cycle, m_next_cycle); - m_next_cycle = cycle; - have_lost_cycles = 1; - } - if (!m_disabled) { - if (++m_next_cycle >= 8000) - m_next_cycle -= 8000; - } else - m_next_cycle = -1; - - // If the packet length is 8 bytes (ie: just a CIP-like header) there is - // no isodata. - if (length > 8) { - // The iso data blocks from the MOTUs comprise a CIP-like header - // followed by a number of events (8 for 1x rates, 16 for 2x rates, - // 32 for 4x rates). - quadlet_t *quadlet = (quadlet_t *)data; - unsigned int dbs = get_bits(ntohl(quadlet[0]), 23, 8); // Size of one event in terms of fdf_size - unsigned int fdf_size = get_bits(ntohl(quadlet[1]), 23, 8) == 0x22 ? 32:0; // Event unit size in bits - unsigned int event_length = (fdf_size * dbs) / 8; // Event size in bytes - unsigned int n_events = (length-8) / event_length; - - // Don't even attempt to process a packet if it isn't what we expect - // from a MOTU - if (tag!=1 || fdf_size!=32) { - return RAW1394_ISO_OK; - } - - // Signal that we're running - if (n_events) m_running=true; - - /* Send actual ticks-per-frame values (as deduced by the incoming - * SPHs) to the DLL for averaging. Doing this here means the DLL - * should acquire a reasonable estimation of the ticks per frame - * even while the stream is formally disabled. This in turn means - * the transmit stream should have access to a very realistic - * estimate by the time it is enabled. The major disadvantage - * is a small increase in the overheads of this function compared - * to what would be the case if this was delayed by pushing it into - * the decode functions. - */ - unsigned int ev; - signed int sph_ofs; - - /* If this is the first block received or we have lost cycles, - * initialise the m_last_cycle_ofs to a value which won't cause the - * DLL to become polluted with an inappropriate ticks-per-frame - * estimate. - */ - if (m_last_cycle_ofs<0 || have_lost_cycles) { - sph_ofs = ntohl(*(quadlet_t *)(data+8)) & 0xfff; - m_last_cycle_ofs = sph_ofs-(int)(m_ticks_per_frame); - } - for (ev=0; ev= 8000) + m_next_cycle -= 8000; + + // If the packet length is 8 bytes (ie: just a CIP-like header) + // there is no isodata. + if (length > 8) { + // The iso data blocks from the MOTUs comprise a CIP-like + // header followed by a number of events (8 for 1x rates, 16 + // for 2x rates, 32 for 4x rates). + quadlet_t *quadlet = (quadlet_t *)data; + unsigned int dbs = get_bits(ntohl(quadlet[0]), 23, 8); // Size of one event in terms of fdf_size + unsigned int fdf_size = get_bits(ntohl(quadlet[1]), 23, 8) == 0x22 ? 32:0; // Event unit size in bits + unsigned int event_length = (fdf_size * dbs) / 8; // Event size in bytes + unsigned int n_events = (length-8) / event_length; + + // Don't even attempt to process a packet if it isn't what + // we expect from a MOTU. Yes, an FDF value of 32 bears + // little relationship to the actual data (24 bit integer) + // sent by the MOTU - it's one of those areas where MOTU + // have taken a curious detour around the standards. + if (tag!=1 || fdf_size!=32) { + return RAW1394_ISO_OK; + } + + // Signal that we're running + if (n_events) m_running=true; + + /* Send actual ticks-per-frame values (as deduced by the + * incoming SPHs) to the DLL for averaging. Doing this here + * means the DLL should acquire a reasonable estimation of + * the ticks per frame even while the stream is formally + * disabled. This in turn means the transmit stream should + * have access to a very realistic estimate by the time it + * is enabled. The major disadvantage is a small increase + * in the overheads of this function compared to what would + * be the case if this was delayed by pushing it into the + * decode functions. + */ + unsigned int ev; + signed int sph_ofs; + + /* If this is the first block received or we have lost + * cycles, initialise the m_last_cycle_ofs to a value which + * won't cause the DLL to become polluted with an + * inappropriate ticks-per-frame estimate. + */ + if (m_last_cycle_ofs<0 || have_lost_cycles) { + sph_ofs = ntohl(*(quadlet_t *)(data+8)) & 0xfff; + m_last_cycle_ofs = sph_ofs-(int)(m_ticks_per_frame); + } + for (ev=0; ev (signed int)m_period) + return RAW1394_ISO_DEFER; + return RAW1394_ISO_OK; + } + + debugOutput( DEBUG_LEVEL_VERY_VERBOSE, "put packet...\n"); + + // Add the data payload (events) to the ringbuffer. We'll + // just copy everything including the 4 byte timestamp at + // the start of each event (that is, everything except the + // CIP-like header). The demultiplexer can deal with the + // complexities such as the channel 24-bit data. + unsigned int write_size = length-8; + if (freebob_ringbuffer_write(m_event_buffer,(char *)(data+8),write_size) < write_size) { + debugWarning("Receive buffer overrun (cycle %d, FC=%d, PC=%d)\n", + cycle, m_framecounter, m_handler->getPacketCount()); + m_xruns++; + + retval=RAW1394_ISO_DEFER; + } else { + retval=RAW1394_ISO_OK; + // Process all ports that should be handled on a + // per-packet basis. This is MIDI for AMDTP (due to + // the need of DBC) + int dbc = get_bits(ntohl(quadlet[0]), 8, 8); // Low byte of CIP quadlet 0 + if (!decodePacketPorts((quadlet_t *)(data+8), n_events, dbc)) { + debugWarning("Problem decoding Packet Ports\n"); + retval=RAW1394_ISO_DEFER; + } + // time stamp processing can be done here + } + + // update the frame counter incrementFrameCounter(n_events); - if (m_framecounter > (signed int)m_period) - return RAW1394_ISO_DEFER; - return RAW1394_ISO_OK; - } - - debugOutput( DEBUG_LEVEL_VERY_VERBOSE, "put packet...\n"); - - // Add the data payload (events) to the ringbuffer. We'll just copy - // everything including the 4 byte timestamp at the start of each - // event (that is, everything except the CIP-like header). The - // demultiplexer can deal with the complexities such as the channel - // 24-bit data. - unsigned int write_size = length-8; - if (freebob_ringbuffer_write(m_event_buffer,(char *)(data+8),write_size) < write_size) { - debugWarning("Receive buffer overrun (cycle %d, FC=%d, PC=%d)\n", - cycle, m_framecounter, m_handler->getPacketCount()); - m_xruns++; - - retval=RAW1394_ISO_DEFER; - } else { - retval=RAW1394_ISO_OK; - // Process all ports that should be handled on a per-packet basis - // This is MIDI for AMDTP (due to the need of DBC) - int dbc = get_bits(ntohl(quadlet[0]), 8, 8); // Low byte of CIP quadlet 0 - if (!decodePacketPorts((quadlet_t *)(data+8), n_events, dbc)) { - debugWarning("Problem decoding Packet Ports\n"); + // keep this at the end, because otherwise the + // raw1394_loop_iterate functions inner loop keeps + // requesting packets without going to the xmit handler, + // leading to xmit starvation + if(m_framecounter>(signed int)m_period) { retval=RAW1394_ISO_DEFER; } - // time stamp processing can be done here - } - - // update the frame counter - incrementFrameCounter(n_events); - // keep this at the end, because otherwise the raw1394_loop_iterate functions inner loop - // keeps requesting packets without going to the xmit handler, leading to xmit starvation - if(m_framecounter>(signed int)m_period) { - retval=RAW1394_ISO_DEFER; - } - - } else { // no events in packet - // discard packet - // can be important for sync though - } + + } else { // no events in packet + // discard packet + // can be important for sync though + } - return retval; + return retval; } @@ -1149,4 +1202,14 @@ return false; } + if (!(*it)->setBufferType(Port::E_RingBuffer)) { + debugFatal("Could not set buffer type"); + return false; + } + if (!(*it)->setDataType(Port::E_MidiEvent)) { + debugFatal("Could not set data type"); + return false; + } + // FIXME: probably need rate control too. See + // Port::useRateControl() and AmdtpStreamProcessor. break; case Port::E_Control: @@ -1320,29 +1383,62 @@ * @return true if all successfull */ -bool MotuReceiveStreamProcessor::decodePacketPorts(quadlet_t *data, unsigned int nevents, unsigned int dbc) -{ +bool MotuReceiveStreamProcessor::decodePacketPorts(quadlet_t *data, unsigned int nevents, + unsigned int dbc) { bool ok=true; - - quadlet_t *target_event=NULL; - int j; - + + // Use char here since the source address won't necessarily be + // aligned; use of an unaligned quadlet_t may cause issues on + // certain architectures. Besides, the source for MIDI data going + // directly to the MOTU isn't structured in quadlets anyway; it is a + // sequence of 3 unaligned bytes. + unsigned char *src = NULL; + for ( PortVectorIterator it = m_PacketPorts.begin(); - it != m_PacketPorts.end(); - ++it ) { + it != m_PacketPorts.end(); + ++it ) { #ifdef DEBUG - MotuPortInfo *pinfo=dynamic_cast(*it); - assert(pinfo); // this should not fail!! - - // the only packet type of events for AMDTP is MIDI in mbla -// assert(pinfo->getFormat()==MotuPortInfo::E_Midi); + //FIXME: make these into a static_casts when not DEBUG? + Port *port=dynamic_cast(*it); + assert(port); // this should not fail!! + + // Currently the only packet type of events for MOTU + // is MIDI in mbla. However in future control data + // might also be sent via "packet" events, so allow + // for this possible expansion. #endif - MotuMidiPort *mp=static_cast(*it); - - - // do decoding here - - } - + // FIXME: MIDI input is completely untested at present. + switch (port->getPortType()) { + case Port::E_Midi: { + MotuMidiPort *mp=static_cast(*it); + signed int sample; + unsigned int j = 0; + // Get MIDI bytes if present anywhere in the + // packet. MOTU MIDI data is sent using a + // 3-byte sequence starting at the port's + // position. It's thought that there can never + // be more than one MIDI byte per packet, but + // for completeness we'll check the entire packet + // anyway. + src = (unsigned char *)data + mp->getPosition(); + while (j < nevents) { + if (*src==0x01 && *(src+1)==0x00) { + sample = *(src+2); + if (!mp->writeEvent(&sample)) { + debugWarning("MIDI packet port events lost\n"); + ok = false; + } + } + j++; + src += m_event_size; + } + break; + } + default: + debugOutput(DEBUG_LEVEL_VERBOSE, "Unknown packet-type port format %d\n",port->getPortType()); + return ok; + } + } + return ok; } @@ -1442,4 +1538,24 @@ return true; } + +bool MotuReceiveStreamProcessor::preparedForStart() { +// Reset some critical variables required so the stream starts cleanly. This +// method is called once on every stream restart, including those during +// xrun recovery. Initialisations which should be done once should be +// placed in the init() method instead. + m_running = 0; + m_next_cycle = -1; + m_closedown_active = 0; + m_last_cycle_ofs = -1; + + // At this point we'll also disable the stream processor here. + // At this stage stream processors are always explicitly re-enabled + // after being started, so by starting in the disabled state we + // ensure that every start will be exactly the same. + disable(); + + return true; +} + } // end of namespace FreebobStreaming Index: /branches/libfreebob-2.0/src/motu/motu_avdevice.cpp =================================================================== --- /branches/libfreebob-2.0/src/motu/motu_avdevice.cpp (revision 311) +++ /branches/libfreebob-2.0/src/motu/motu_avdevice.cpp (revision 312) @@ -404,5 +404,4 @@ char *buff; - unsigned int i; FreebobStreaming::Port *p=NULL; @@ -412,25 +411,23 @@ } - // example of adding an midi port: -// asprintf(&buff,"dev%d_cap_%s",m_id,"myportnamehere"); -// p=new FreebobStreaming::MotuMidiPort( -// buff, -// FreebobStreaming::Port::E_Capture, -// 0 // you can add all other port specific stuff you -// // need to pass by extending MotuXXXPort and MotuPortInfo -// ); -// free(buff); -// -// if (!p) { -// debugOutput(DEBUG_LEVEL_VERBOSE, "Skipped port %s\n",buff); -// } else { -// if (!m_receiveProcessor->addPort(p)) { -// debugWarning("Could not register port with stream processor\n"); -// return false; -// } else { -// debugOutput(DEBUG_LEVEL_VERBOSE, "Added port %s\n",buff); -// } -// } - + // Add MIDI port. The MOTU only has one MIDI input port, with each + // MIDI byte sent using a 3 byte sequence starting at byte 4 of the + // event data. + asprintf(&buff,"dev%d_cap_MIDI0",m_id); + p = new FreebobStreaming::MotuMidiPort(buff, + FreebobStreaming::Port::E_Capture, 4); + if (!p) { + debugOutput(DEBUG_LEVEL_VERBOSE, "Skipped port %s\n", buff); + } else { + if (!m_receiveProcessor->addPort(p)) { + debugWarning("Could not register port with stream processor\n"); + free(buff); + return false; + } else { + debugOutput(DEBUG_LEVEL_VERBOSE, "Added port %s\n", buff); + } + } + free(buff); + // example of adding an control port: // asprintf(&buff,"dev%d_cap_%s",m_id,"myportnamehere"); @@ -478,26 +475,23 @@ } -// // example of adding an midi port: -// asprintf(&buff,"dev%d_pbk_%s",m_id,"myportnamehere"); -// -// p=new FreebobStreaming::MotuMidiPort( -// buff, -// FreebobStreaming::Port::E_Playback, -// 0 // you can add all other port specific stuff you -// // need to pass by extending MotuXXXPort and MotuPortInfo -// ); -// free(buff); -// -// if (!p) { -// debugOutput(DEBUG_LEVEL_VERBOSE, "Skipped port %s\n",buff); -// } else { -// if (!m_transmitProcessor->addPort(p)) { -// debugWarning("Could not register port with stream processor\n"); -// return false; -// } else { -// debugOutput(DEBUG_LEVEL_VERBOSE, "Added port %s\n",buff); -// } -// } - + // Add MIDI port. The MOTU only has one output MIDI port, with each + // MIDI byte transmitted using a 3 byte sequence starting at byte 4 + // of the event data. + asprintf(&buff,"dev%d_pbk_MIDI0",m_id); + p = new FreebobStreaming::MotuMidiPort(buff, + FreebobStreaming::Port::E_Capture, 4); + if (!p) { + debugOutput(DEBUG_LEVEL_VERBOSE, "Skipped port %s\n", buff); + } else { + if (!m_receiveProcessor->addPort(p)) { + debugWarning("Could not register port with stream processor\n"); + free(buff); + return false; + } else { + debugOutput(DEBUG_LEVEL_VERBOSE, "Added port %s\n", buff); + } + } + free(buff); + // example of adding an control port: // asprintf(&buff,"dev%d_pbk_%s",m_id,"myportnamehere"); Index: /branches/libfreebob-2.0/support/jack/freebob_driver.c =================================================================== --- /branches/libfreebob-2.0/support/jack/freebob_driver.c (revision 302) +++ /branches/libfreebob-2.0/support/jack/freebob_driver.c (revision 312) @@ -207,6 +207,10 @@ for (node = driver->capture_ports; node; node = jack_slist_next (node)) { - jack_port_unregister (driver->client, - ((jack_port_t *) node->data)); + // Don't try to unregister NULL entries added for non-audio + // freebob ports by freebob_driver_attach(). + if (node->data != NULL) { + jack_port_unregister (driver->client, + ((jack_port_t *) node->data)); + } }