Index: /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpSlaveStreamProcessor.h =================================================================== --- /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpSlaveStreamProcessor.h (revision 705) +++ /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpSlaveStreamProcessor.h (revision 712) @@ -28,5 +28,6 @@ * This class implements IEC61883-6 / AM824 / AMDTP based streaming */ -#include "AmdtpStreamProcessor.h" +#include "AmdtpReceiveStreamProcessor.h" +#include "AmdtpTransmitStreamProcessor.h" #include "../util/cip.h" Index: /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpTransmitStreamProcessor.cpp =================================================================== --- /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpTransmitStreamProcessor.cpp (revision 712) +++ /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpTransmitStreamProcessor.cpp (revision 712) @@ -0,0 +1,912 @@ +/* + * Copyright (C) 2005-2007 by Pieter Palmers + * + * This file is part of FFADO + * FFADO = Free Firewire (pro-)audio drivers for linux + * + * FFADO is based upon FreeBoB. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation; + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "AmdtpTransmitStreamProcessor.h" +#include "AmdtpPort.h" + +#include "../util/cycletimer.h" + +#include +#include + +// in ticks +// as per AMDTP2.1: +// 354.17us + 125us @ 24.576ticks/usec = 11776.08192 ticks +#define DEFAULT_TRANSFER_DELAY (11776U) + +#define TRANSMIT_TRANSFER_DELAY DEFAULT_TRANSFER_DELAY + +namespace Streaming { + +/* transmit */ +AmdtpTransmitStreamProcessor::AmdtpTransmitStreamProcessor(int port, int framerate, int dimension) + : TransmitStreamProcessor(port, framerate), m_dimension(dimension) + , m_last_timestamp(0), m_dbc(0), m_ringbuffer_size_frames(0) +{} + +/** + * @return + */ +bool AmdtpTransmitStreamProcessor::init() { + + debugOutput( DEBUG_LEVEL_VERBOSE, "Initializing (%p)...\n"); + // call the parent init + // this has to be done before allocating the buffers, + // because this sets the buffersizes from the processormanager + if(!TransmitStreamProcessor::init()) { + debugFatal("Could not do base class init (%p)\n",this); + return false; + } + return true; +} + +enum raw1394_iso_disposition +AmdtpTransmitStreamProcessor::getPacket(unsigned char *data, unsigned int *length, + unsigned char *tag, unsigned char *sy, + int cycle, unsigned int dropped, unsigned int max_length) { + struct iec61883_packet *packet = (struct iec61883_packet *) data; + + if (cycle<0) { + debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE,"Xmit handler for cycle %d, (running=%d)\n", + cycle, m_running); + *tag = 0; + *sy = 0; + *length=0; + return RAW1394_ISO_OK; + } + + debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE,"Xmit handler for cycle %d, (running=%d)\n", + cycle, m_running); + + if (addCycles(m_last_cycle, 1) != cycle) { + debugWarning("(%p) Dropped %d packets on cycle %d\n", diffCycles(cycle,m_last_cycle)-1, cycle); + } + + m_last_cycle=cycle; + +#ifdef DEBUG + if(dropped>0) { + debugWarning("Dropped %d packets on cycle %d\n",dropped, cycle); + } +#endif + + // calculate & preset common values + + /* Our node ID can change after a bus reset, so it is best to fetch + * our node ID for each packet. */ + packet->sid = getNodeId() & 0x3f; + + packet->dbs = m_dimension; + packet->fn = 0; + packet->qpc = 0; + packet->sph = 0; + packet->reserved = 0; + packet->dbc = m_dbc; + packet->eoh1 = 2; + packet->fmt = IEC61883_FMT_AMDTP; + + *tag = IEC61883_TAG_WITH_CIP; + *sy = 0; + + // determine if we want to send a packet or not + // note that we can't use getCycleTimer directly here, + // because packets are queued in advance. This means that + // we the packet we are constructing will be sent out + // on 'cycle', not 'now'. + unsigned int ctr=m_handler->getCycleTimer(); + int now_cycles = (int)CYCLE_TIMER_GET_CYCLES(ctr); + + // the difference between the cycle this + // packet is intended for and 'now' + int cycle_diff = diffCycles(cycle, now_cycles); + +#ifdef DEBUG + if(m_running && (cycle_diff < 0)) { + debugWarning("Requesting packet for cycle %04d which is in the past (now=%04dcy)\n", + cycle, now_cycles); + } + + // keep track of the lag + m_PacketStat.mark(cycle_diff); +#endif + + // as long as the cycle parameter is not in sync with + // the current time, the stream is considered not + // to be 'running' + // NOTE: this works only at startup + if (!m_running && cycle_diff >= 0 && cycle >= 0) { + debugOutput(DEBUG_LEVEL_VERBOSE, "Xmit StreamProcessor %p started running at cycle %d\n",this, cycle); + m_running=true; + } + + signed int fc; + uint64_t presentation_time; + unsigned int presentation_cycle; + int cycles_until_presentation; + + uint64_t transmit_at_time; + unsigned int transmit_at_cycle; + int cycles_until_transmit; + + // FIXME: should become a define + // the absolute minimum number of cycles we want to transmit + // a packet ahead of the presentation time. The nominal time + // the packet is transmitted ahead of the presentation time is + // given by TRANSMIT_TRANSFER_DELAY (in ticks), but in case we + // are too late for that, this constant defines how late we can + // be. + const int min_cycles_before_presentation = 1; + // FIXME: should become a define + // the absolute maximum number of cycles we want to transmit + // a packet ahead of the ideal transmit time. The nominal time + // the packet is transmitted ahead of the presentation time is + // given by TRANSMIT_TRANSFER_DELAY (in ticks), but we can send + // packets early if we want to. (not completely according to spec) + const int max_cycles_to_transmit_early = 5; + + if( !m_running || !m_data_buffer->isEnabled() ) { + debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, + "Not running (%d) or buffer not enabled (enabled=%d)\n", + m_running, m_data_buffer->isEnabled()); + + // not running or not enabled + goto send_empty_packet; + } + +try_block_of_frames: + debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, "Try for cycle %d\n", cycle); + // check whether the packet buffer has packets for us to send. + // the base timestamp is the one of the next sample in the buffer + ffado_timestamp_t ts_head_tmp; + m_data_buffer->getBufferHeadTimestamp(&ts_head_tmp, &fc); // thread safe + + // the timestamp gives us the time at which we want the sample block + // to be output by the device + presentation_time=(uint64_t)ts_head_tmp; + + // now we calculate the time when we have to transmit the sample block + transmit_at_time = substractTicks(presentation_time, TRANSMIT_TRANSFER_DELAY); + + // calculate the cycle this block should be presented in + // (this is just a virtual calculation since at that time it should + // already be in the device's buffer) + presentation_cycle = (unsigned int)(TICKS_TO_CYCLES( presentation_time )); + + // calculate the cycle this block should be transmitted in + transmit_at_cycle = (unsigned int)(TICKS_TO_CYCLES( transmit_at_time )); + + // we can check whether this cycle is within the 'window' we have + // to send this packet. + // first calculate the number of cycles left before presentation time + cycles_until_presentation = diffCycles( presentation_cycle, cycle ); + + // we can check whether this cycle is within the 'window' we have + // to send this packet. + // first calculate the number of cycles left before presentation time + cycles_until_transmit = diffCycles( transmit_at_cycle, cycle ); + + // two different options: + // 1) there are not enough frames for one packet + // => determine wether this is a problem, since we might still + // have some time to send it + // 2) there are enough packets + // => determine whether we have to send them in this packet + if (fc < (signed int)m_syt_interval) { + m_PacketStat.signal(0); + // not enough frames in the buffer, + debugOutput(DEBUG_LEVEL_VERBOSE, + "Insufficient frames: N=%02d, CY=%04u, TC=%04u, CUT=%04d\n", + fc, cycle, transmit_at_cycle, cycles_until_transmit); + // we can still postpone the queueing of the packets + // if we are far enough ahead of the presentation time + if( cycles_until_presentation <= min_cycles_before_presentation ) { + m_PacketStat.signal(1); + // we are too late + // meaning that we in some sort of xrun state + // signal xrun situation ??HERE?? + m_xruns++; + // we send an empty packet on this cycle + goto send_empty_packet; // UGLY but effective + } else { + m_PacketStat.signal(2); + // there is still time left to send the packet + // we want the system to give this packet another go +// goto try_packet_again; // UGLY but effective + // unfortunatly the try_again doesn't work very well, + // so we'll have to either usleep(one cycle) and goto try_block_of_frames + + // or just fill this with an empty packet + // if we have to do this too often, the presentation time will + // get too close and we're in trouble + goto send_empty_packet; // UGLY but effective + } + } else { + m_PacketStat.signal(3); + // there are enough frames, so check the time they are intended for + // all frames have a certain 'time window' in which they can be sent + // this corresponds to the range of the timestamp mechanism: + // we can send a packet 15 cycles in advance of the 'presentation time' + // in theory we can send the packet up till one cycle before the presentation time, + // however this is not very smart. + + // There are 3 options: + // 1) the frame block is too early + // => send an empty packet + // 2) the frame block is within the window + // => send it + // 3) the frame block is too late + // => discard (and raise xrun?) + // get next block of frames and repeat + + if (cycles_until_transmit <= max_cycles_to_transmit_early) { + m_PacketStat.signal(4); + // it's time send the packet + goto send_packet; // UGLY but effective + } else if (cycles_until_transmit < 0) { + // we are too late + debugOutput(DEBUG_LEVEL_VERBOSE, + "Too late: CY=%04u, TC=%04u, CUT=%04d, TSP=%011llu (%04u)\n", + cycle, + transmit_at_cycle, cycles_until_transmit, + presentation_time, (unsigned int)TICKS_TO_CYCLES(presentation_time)); + + // however, if we can send this sufficiently before the presentation + // time, it could be harmless. + // NOTE: dangerous since the device has no way of reporting that it didn't get + // this packet on time. + if ( cycles_until_presentation <= min_cycles_before_presentation ) { + m_PacketStat.signal(5); + // we are not that late and can still try to transmit the packet + goto send_packet; // UGLY but effective + } else { // definitely too late + m_PacketStat.signal(6); + // remove the samples + m_data_buffer->dropFrames(m_syt_interval); + // signal some xrun situation ??HERE?? + m_xruns++; + // try a new block of frames + goto try_block_of_frames; // UGLY but effective + } + } else { + m_PacketStat.signal(7); + debugOutput(DEBUG_LEVEL_VERY_VERBOSE, + "Too early: CY=%04u, TC=%04u, CUT=%04d, TST=%011llu (%04u), TSP=%011llu (%04u)\n", + cycle, + transmit_at_cycle, cycles_until_transmit, + transmit_at_time, (unsigned int)TICKS_TO_CYCLES(transmit_at_time), + presentation_time, (unsigned int)TICKS_TO_CYCLES(presentation_time)); + #ifdef DEBUG + if (cycles_until_transmit > max_cycles_to_transmit_early + 1) { + debugOutput(DEBUG_LEVEL_VERBOSE, + "Way too early: CY=%04u, TC=%04u, CUT=%04d, TST=%011llu (%04u), TSP=%011llu (%04u)\n", + cycle, + transmit_at_cycle, cycles_until_transmit, + transmit_at_time, (unsigned int)TICKS_TO_CYCLES(transmit_at_time), + presentation_time, (unsigned int)TICKS_TO_CYCLES(presentation_time)); + } + #endif + // we are too early, send only an empty packet + goto send_empty_packet; // UGLY but effective + } + } + + debugFatal("Should never reach this code!\n"); + return RAW1394_ISO_ERROR; + +send_empty_packet: + debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "XMIT NONE: CY=%04u, TSP=%011llu (%04u)\n", + cycle, + presentation_time, (unsigned int)TICKS_TO_CYCLES(presentation_time)); + + m_dbc += fillNoDataPacketHeader(packet, length); + return RAW1394_ISO_DEFER; + +send_packet: + if (m_data_buffer->readFrames(m_syt_interval, (char *)(data + 8))) { + m_dbc += fillDataPacketHeader(packet, length, presentation_time); + + // process all ports that should be handled on a per-packet base + // this is MIDI for AMDTP (due to the need of DBC) + if (!encodePacketPorts((quadlet_t *)(data+8), m_syt_interval, packet->dbc)) { + debugWarning("Problem encoding Packet Ports\n"); + } + + debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "XMIT DATA: CY=%04u, TST=%011llu (%04u), TSP=%011llu (%04u)\n", + cycle, + transmit_at_time, (unsigned int)TICKS_TO_CYCLES(transmit_at_time), + presentation_time, (unsigned int)TICKS_TO_CYCLES(presentation_time)); + + return RAW1394_ISO_OK; + } + +// the ISO AGAIN does not work very well... +// try_packet_again: +// +// debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "XMIT RETRY: CY=%04u, TSP=%011llu (%04u)\n", +// cycle, +// presentation_time, (unsigned int)TICKS_TO_CYCLES(presentation_time)); +// return RAW1394_ISO_AGAIN; + + // else: + debugFatal("This is impossible, since we checked the buffer size before!\n"); + return RAW1394_ISO_ERROR; +} + +unsigned int AmdtpTransmitStreamProcessor::fillDataPacketHeader( + struct iec61883_packet *packet, unsigned int* length, + uint32_t ts) { + + packet->fdf = m_fdf; + + // convert the timestamp to SYT format + uint16_t timestamp_SYT = TICKS_TO_SYT(ts); + packet->syt = ntohs(timestamp_SYT); + + *length = m_syt_interval*sizeof(quadlet_t)*m_dimension + 8; + + return m_syt_interval; +} + +unsigned int AmdtpTransmitStreamProcessor::fillNoDataPacketHeader( + struct iec61883_packet *packet, unsigned int* length) { + + // no-data packets have syt=0xFFFF + // and have the usual amount of events as dummy data (?) + packet->fdf = IEC61883_FDF_NODATA; + packet->syt = 0xffff; + + // FIXME: either make this a setting or choose + bool send_payload=true; + if(send_payload) { + // this means no-data packets with payload (DICE doesn't like that) + *length = 2*sizeof(quadlet_t) + m_syt_interval * m_dimension * sizeof(quadlet_t); + return m_syt_interval; + } else { + // dbc is not incremented + // this means no-data packets without payload + *length = 2*sizeof(quadlet_t); + return 0; + } +} + +bool AmdtpTransmitStreamProcessor::prefill() { + + debugOutput( DEBUG_LEVEL_VERBOSE, "Prefill transmit buffers...\n"); + + if(!transferSilence(m_ringbuffer_size_frames)) { + debugFatal("Could not prefill transmit stream\n"); + return false; + } + + return true; +} + +bool AmdtpTransmitStreamProcessor::reset() { + + debugOutput( DEBUG_LEVEL_VERBOSE, "Resetting...\n"); + + // reset the statistics + m_PeriodStat.reset(); + m_PacketStat.reset(); + m_WakeupStat.reset(); + + m_data_buffer->setTickOffset(0); + + // reset all non-device specific stuff + // i.e. the iso stream and the associated ports + if(!TransmitStreamProcessor::reset()) { + debugFatal("Could not do base class reset\n"); + return false; + } + + // we should prefill the event buffer + if (!prefill()) { + debugFatal("Could not prefill buffers\n"); + return false; + } + + return true; +} + +bool AmdtpTransmitStreamProcessor::prepare() { + m_PeriodStat.setName("XMT PERIOD"); + m_PacketStat.setName("XMT PACKET"); + m_WakeupStat.setName("XMT WAKEUP"); + + debugOutput( DEBUG_LEVEL_VERBOSE, "Preparing (%p)...\n", this); + + // prepare all non-device specific stuff + // i.e. the iso stream and the associated ports + if(!TransmitStreamProcessor::prepare()) { + debugFatal("Could not prepare base class\n"); + return false; + } + + switch (m_framerate) { + case 32000: + m_syt_interval = 8; + m_fdf = IEC61883_FDF_SFC_32KHZ; + break; + case 44100: + m_syt_interval = 8; + m_fdf = IEC61883_FDF_SFC_44K1HZ; + break; + default: + case 48000: + m_syt_interval = 8; + m_fdf = IEC61883_FDF_SFC_48KHZ; + break; + case 88200: + m_syt_interval = 16; + m_fdf = IEC61883_FDF_SFC_88K2HZ; + break; + case 96000: + m_syt_interval = 16; + m_fdf = IEC61883_FDF_SFC_96KHZ; + break; + case 176400: + m_syt_interval = 32; + m_fdf = IEC61883_FDF_SFC_176K4HZ; + break; + case 192000: + m_syt_interval = 32; + m_fdf = IEC61883_FDF_SFC_192KHZ; + break; + } + + iec61883_cip_init ( + &m_cip_status, + IEC61883_FMT_AMDTP, + m_fdf, + m_framerate, + m_dimension, + m_syt_interval); + + // prepare the framerate estimate + float ticks_per_frame = (TICKS_PER_SECOND*1.0) / ((float)m_framerate); + m_ticks_per_frame=ticks_per_frame; + + // initialize internal buffer + m_ringbuffer_size_frames=m_nb_buffers * m_period; + + assert(m_data_buffer); + m_data_buffer->setBufferSize(m_ringbuffer_size_frames * 2); + m_data_buffer->setEventSize(sizeof(quadlet_t)); + m_data_buffer->setEventsPerFrame(m_dimension); + + m_data_buffer->setUpdatePeriod(m_period); + m_data_buffer->setNominalRate(ticks_per_frame); + + m_data_buffer->setWrapValue(128L*TICKS_PER_SECOND); + + m_data_buffer->prepare(); + + // set the parameters of ports we can: + // we want the audio ports to be period buffered, + // and the midi ports to be packet buffered + for ( PortVectorIterator it = m_Ports.begin(); + it != m_Ports.end(); + ++it ) + { + debugOutput(DEBUG_LEVEL_VERBOSE, "Setting up port %s\n",(*it)->getName().c_str()); + if(!(*it)->setBufferSize(m_period)) { + debugFatal("Could not set buffer size to %d\n",m_period); + return false; + } + + + switch ((*it)->getPortType()) { + case Port::E_Audio: + if(!(*it)->setSignalType(Port::E_PeriodSignalled)) { + debugFatal("Could not set signal type to PeriodSignalling"); + return false; + } + debugWarning("---------------- ! Doing hardcoded test setup ! --------------\n"); + // buffertype and datatype are dependant on the API + if(!(*it)->setBufferType(Port::E_PointerBuffer)) { + debugFatal("Could not set buffer type"); + return false; + } + if(!(*it)->useExternalBuffer(true)) { + debugFatal("Could not set external buffer usage"); + return false; + } + + if(!(*it)->setDataType(Port::E_Float)) { + debugFatal("Could not set data type"); + return false; + } + + + break; + case Port::E_Midi: + if(!(*it)->setSignalType(Port::E_PacketSignalled)) { + debugFatal("Could not set signal type to PeriodSignalling"); + return false; + } + + // we use a timing unit of 10ns + // this makes sure that for the max syt interval + // we don't have rounding, and keeps the numbers low + // we have 1 slot every 8 events + // we have syt_interval events per packet + // => syt_interval/8 slots per packet + // packet rate is 8000pkt/sec => interval=125us + // so the slot interval is (1/8000)/(syt_interval/8) + // or: 1/(1000 * syt_interval) sec + // which is 1e9/(1000*syt_interval) nsec + // or 100000/syt_interval 'units' + // the event interval is fixed to 320us = 32000 'units' + if(!(*it)->useRateControl(true,(100000/m_syt_interval),32000, false)) { + debugFatal("Could not set signal type to PeriodSignalling"); + return false; + } + + // buffertype and datatype are dependant on the API + debugWarning("---------------- ! Doing hardcoded test setup ! --------------\n"); + // buffertype and datatype are dependant on the API + 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; + } + break; + default: + debugWarning("Unsupported port type specified\n"); + break; + } + } + + // the API specific settings of the ports should already be set, + // as this is called from the processorManager->prepare() + // so we can init the ports + if(!initPorts()) { + debugFatal("Could not initialize ports!\n"); + return false; + } + + if(!preparePorts()) { + debugFatal("Could not initialize ports!\n"); + return false; + } + + debugOutput( DEBUG_LEVEL_VERBOSE, "Prepared for:\n"); + debugOutput( DEBUG_LEVEL_VERBOSE, " Samplerate: %d, FDF: %d, DBS: %d, SYT: %d\n", + m_framerate,m_fdf,m_dimension,m_syt_interval); + debugOutput( DEBUG_LEVEL_VERBOSE, " PeriodSize: %d, NbBuffers: %d\n", + m_period,m_nb_buffers); + debugOutput( DEBUG_LEVEL_VERBOSE, " Port: %d, Channel: %d\n", + m_port,m_channel); + + return true; + +} + +bool AmdtpTransmitStreamProcessor::prepareForStart() { + return true; +} + +bool AmdtpTransmitStreamProcessor::prepareForStop() { + return true; +} + +bool AmdtpTransmitStreamProcessor::prepareForEnable(uint64_t time_to_enable_at) { + + if (!StreamProcessor::prepareForEnable(time_to_enable_at)) { + debugError("StreamProcessor::prepareForEnable failed\n"); + return false; + } + + return true; +} + +bool AmdtpTransmitStreamProcessor::transferSilence(unsigned int nframes) { + bool retval; + signed int fc; + ffado_timestamp_t ts_tail_tmp; + uint64_t ts_tail; + + // prepare a buffer of silence + char *dummybuffer=(char *)calloc(sizeof(quadlet_t),nframes*m_dimension); + transmitSilenceBlock(dummybuffer, nframes, 0); + + + m_data_buffer->getBufferTailTimestamp(&ts_tail_tmp, &fc); + if (fc != 0) { + debugWarning("Prefilling a buffer that already contains %d frames\n", fc); + } + + ts_tail = (uint64_t)ts_tail_tmp; + // modify the timestamp such that it makes sense + ts_tail = addTicks(ts_tail, (uint64_t)(nframes * getTicksPerFrame())); + // add the silence data to the ringbuffer + if(m_data_buffer->writeFrames(nframes, dummybuffer, ts_tail)) { + retval=true; + } else { + debugWarning("Could not write to event buffer\n"); + retval=false; + } + + free(dummybuffer); + + return retval; +} + +bool AmdtpTransmitStreamProcessor::putFrames(unsigned int nbframes, int64_t ts) { + m_PeriodStat.mark(m_data_buffer->getBufferFill()); + debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, "AmdtpTransmitStreamProcessor::putFrames(%d, %llu)\n", nbframes, ts); + + // transfer the data + m_data_buffer->blockProcessWriteFrames(nbframes, ts); + + debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, " New timestamp: %llu\n", ts); + + return true; // FIXME: what about failure? +} + +bool AmdtpTransmitStreamProcessor::putFramesDry(unsigned int nbframes, int64_t ts) { + m_PeriodStat.mark(m_data_buffer->getBufferFill()); + debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, "AmdtpTransmitStreamProcessor::putFramesDry(%d, %llu)\n", nbframes, ts); + + bool retval; + char dummybuffer[sizeof(quadlet_t)*nbframes*m_dimension]; + + transmitSilenceBlock(dummybuffer, nbframes, 0); + // add the silence data to the ringbuffer + if(m_data_buffer->writeFrames(nbframes, dummybuffer, ts)) { + retval=true; + } else { + debugWarning("Could not write %u events to event buffer\n", nbframes); + retval=false; + } + + debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, " New timestamp: %llu\n", ts); + return retval; +} + +/* + * write received events to the stream ringbuffers. + */ + +bool AmdtpTransmitStreamProcessor::processWriteBlock(char *data, + unsigned int nevents, unsigned int offset) +{ + bool no_problem=true; + + for ( PortVectorIterator it = m_PeriodPorts.begin(); + it != m_PeriodPorts.end(); + ++it ) + { + + if((*it)->isDisabled()) {continue;}; + + //FIXME: make this into a static_cast when not DEBUG? + + AmdtpPortInfo *pinfo=dynamic_cast(*it); + assert(pinfo); // this should not fail!! + + switch(pinfo->getFormat()) { + case AmdtpPortInfo::E_MBLA: + if(encodePortToMBLAEvents(static_cast(*it), (quadlet_t *)data, offset, nevents)) { + debugWarning("Could not encode port %s to MBLA events",(*it)->getName().c_str()); + no_problem=false; + } + break; + case AmdtpPortInfo::E_SPDIF: // still unimplemented + break; + default: // ignore + break; + } + } + return no_problem; + +} + +int AmdtpTransmitStreamProcessor::transmitSilenceBlock(char *data, + unsigned int nevents, unsigned int offset) +{ + int problem=0; + + for ( PortVectorIterator it = m_PeriodPorts.begin(); + it != m_PeriodPorts.end(); + ++it ) + { + + //FIXME: make this into a static_cast when not DEBUG? + + AmdtpPortInfo *pinfo=dynamic_cast(*it); + assert(pinfo); // this should not fail!! + + switch(pinfo->getFormat()) { + case AmdtpPortInfo::E_MBLA: + if(encodeSilencePortToMBLAEvents(static_cast(*it), (quadlet_t *)data, offset, nevents)) { + debugWarning("Could not encode port %s to MBLA events",(*it)->getName().c_str()); + problem=1; + } + break; + case AmdtpPortInfo::E_SPDIF: // still unimplemented + break; + default: // ignore + break; + } + } + return problem; + +} + +/** + * @brief decode a packet for the packet-based ports + * + * @param data Packet data + * @param nevents number of events in data (including events of other ports & port types) + * @param dbc DataBlockCount value for this packet + * @return true if all successfull + */ +bool AmdtpTransmitStreamProcessor::encodePacketPorts(quadlet_t *data, unsigned int nevents, unsigned int dbc) +{ + bool ok=true; + quadlet_t byte; + + quadlet_t *target_event=NULL; + unsigned int j; + + for ( PortVectorIterator it = m_PacketPorts.begin(); + it != m_PacketPorts.end(); + ++it ) + { + +#ifdef DEBUG + AmdtpPortInfo *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()==AmdtpPortInfo::E_Midi); +#endif + + AmdtpMidiPort *mp=static_cast(*it); + + // we encode this directly (no function call) due to the high frequency + /* idea: + spec says: current_midi_port=(dbc+j)%8; + => if we start at (dbc+stream->location-1)%8, + we'll start at the right event for the midi port. + => if we increment j with 8, we stay at the right event. + */ + // FIXME: as we know in advance how big a packet is (syt_interval) we can + // predict how much loops will be present here + // first prefill the buffer with NO_DATA's on all time muxed channels + + for(j = (dbc & 0x07)+mp->getLocation(); j < nevents; j += 8) { + + quadlet_t tmpval; + + target_event=(quadlet_t *)(data + ((j * m_dimension) + mp->getPosition())); + + if(mp->canRead()) { // we can send a byte + mp->readEvent(&byte); + byte &= 0xFF; + tmpval=htonl( + IEC61883_AM824_SET_LABEL((byte)<<16, + IEC61883_AM824_LABEL_MIDI_1X)); + + debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, "MIDI port %s, pos=%d, loc=%d, dbc=%d, nevents=%d, dim=%d\n", + mp->getName().c_str(), mp->getPosition(), mp->getLocation(), dbc, nevents, m_dimension); + debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, "base=%p, target=%p, value=%08X\n", + data, target_event, tmpval); + + } else { + // can't send a byte, either because there is no byte, + // or because this would exceed the maximum rate + tmpval=htonl( + IEC61883_AM824_SET_LABEL(0,IEC61883_AM824_LABEL_MIDI_NO_DATA)); + } + + *target_event=tmpval; + } + + } + + return ok; +} + + +int AmdtpTransmitStreamProcessor::encodePortToMBLAEvents(AmdtpAudioPort *p, quadlet_t *data, + unsigned int offset, unsigned int nevents) +{ + unsigned int j=0; + + quadlet_t *target_event; + + target_event=(quadlet_t *)(data + p->getPosition()); + + switch(p->getDataType()) { + default: + case Port::E_Int24: + { + quadlet_t *buffer=(quadlet_t *)(p->getBufferAddress()); + + assert(nevents + offset <= p->getBufferSize()); + + buffer+=offset; + + for(j = 0; j < nevents; j += 1) { // decode max nsamples + *target_event = htonl((*(buffer) & 0x00FFFFFF) | 0x40000000); + buffer++; + target_event += m_dimension; + } + } + break; + case Port::E_Float: + { + const float multiplier = (float)(0x7FFFFF00); + float *buffer=(float *)(p->getBufferAddress()); + + assert(nevents + offset <= p->getBufferSize()); + + buffer+=offset; + + for(j = 0; j < nevents; j += 1) { // decode max nsamples + + // don't care for overflow + float v = *buffer * multiplier; // v: -231 .. 231 + unsigned int tmp = ((int)v); + *target_event = htonl((tmp >> 8) | 0x40000000); + + buffer++; + target_event += m_dimension; + } + } + break; + } + + return 0; +} +int AmdtpTransmitStreamProcessor::encodeSilencePortToMBLAEvents(AmdtpAudioPort *p, quadlet_t *data, + unsigned int offset, unsigned int nevents) +{ + unsigned int j=0; + + quadlet_t *target_event; + + target_event=(quadlet_t *)(data + p->getPosition()); + + switch(p->getDataType()) { + default: + case Port::E_Int24: + case Port::E_Float: + { + for(j = 0; j < nevents; j += 1) { // decode max nsamples + *target_event = htonl(0x40000000); + target_event += m_dimension; + } + } + break; + } + + return 0; +} + +} // end of namespace Streaming Index: /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpReceiveStreamProcessor.cpp =================================================================== --- /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpReceiveStreamProcessor.cpp (revision 712) +++ /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpReceiveStreamProcessor.cpp (revision 712) @@ -0,0 +1,635 @@ +/* + * Copyright (C) 2005-2007 by Pieter Palmers + * + * This file is part of FFADO + * FFADO = Free Firewire (pro-)audio drivers for linux + * + * FFADO is based upon FreeBoB. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation; + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "AmdtpReceiveStreamProcessor.h" +#include "AmdtpPort.h" + +#include "../util/cycletimer.h" + +#include +#include + +// in ticks +// as per AMDTP2.1: +// 354.17us + 125us @ 24.576ticks/usec = 11776.08192 ticks +#define DEFAULT_TRANSFER_DELAY (11776U) + +#define TRANSMIT_TRANSFER_DELAY DEFAULT_TRANSFER_DELAY + +namespace Streaming { + +/* --------------------- RECEIVE ----------------------- */ + +AmdtpReceiveStreamProcessor::AmdtpReceiveStreamProcessor(int port, int framerate, int dimension) + : ReceiveStreamProcessor(port, framerate) + , m_dimension(dimension) + , m_last_timestamp(0) + , m_last_timestamp2(0) + , m_dropped(0) +{} + +bool AmdtpReceiveStreamProcessor::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; +} + +enum raw1394_iso_disposition +AmdtpReceiveStreamProcessor::putPacket(unsigned char *data, unsigned int length, + unsigned char channel, unsigned char tag, unsigned char sy, + unsigned int cycle, unsigned int dropped) { + + enum raw1394_iso_disposition retval=RAW1394_ISO_OK; + + int dropped_cycles=diffCycles(cycle, m_last_cycle) - 1; + if (dropped_cycles < 0) debugWarning("(%p) dropped < 1 (%d)\n", this, dropped_cycles); + else m_dropped += dropped_cycles; + if (dropped_cycles > 0) debugWarning("(%p) dropped %d packets on cycle %u\n", this, dropped_cycles, cycle); + + m_last_cycle=cycle; + + struct iec61883_packet *packet = (struct iec61883_packet *) data; + assert(packet); + +#ifdef DEBUG + if(dropped>0) { + debugWarning("(%p) Dropped %d packets on cycle %d\n", this, dropped, cycle); + } + + debugOutput(DEBUG_LEVEL_VERY_VERBOSE,"ch%2u: CY=%4u, SYT=%08X (%4ucy + %04uticks) (running=%d)\n", + channel, cycle, ntohs(packet->syt), + CYCLE_TIMER_GET_CYCLES(ntohs(packet->syt)), CYCLE_TIMER_GET_OFFSET(ntohs(packet->syt)), + m_running); + + debugOutput(DEBUG_LEVEL_VERY_VERBOSE, + "RCV: CH = %d, FDF = %X. SYT = %6d, DBS = %3d, DBC = %3d, FMT = %3d, LEN = %4d\n", + channel, packet->fdf, + packet->syt, + packet->dbs, + packet->dbc, + packet->fmt, + length); + +#endif + + // check if this is a valid packet + if((packet->syt != 0xFFFF) + && (packet->fdf != 0xFF) + && (packet->fmt == 0x10) + && (packet->dbs>0) + && (length>=2*sizeof(quadlet_t))) { + + unsigned int nevents=((length / sizeof (quadlet_t)) - 2)/packet->dbs; + + //=> store the previous timestamp + m_last_timestamp2=m_last_timestamp; + + uint64_t nowX = m_handler->getCycleTimer(); + //=> convert the SYT to a full timestamp in ticks + m_last_timestamp=sytRecvToFullTicks((uint32_t)ntohs(packet->syt), + cycle, nowX); + + int64_t diffx = diffTicks(m_last_timestamp, m_last_timestamp2); + if (abs(diffx) > m_syt_interval * m_data_buffer->getRate() * 1.1) { + uint32_t now=m_handler->getCycleTimer(); + uint32_t syt = (uint32_t)ntohs(packet->syt); + uint32_t now_ticks=CYCLE_TIMER_TO_TICKS(now); + + debugOutput(DEBUG_LEVEL_VERBOSE, "diff=%06lld TS=%011llu TS2=%011llu\n", + diffx, m_last_timestamp, m_last_timestamp2); + debugOutput(DEBUG_LEVEL_VERBOSE, "[1] cy=%04d dropped=%05llu syt=%04llX NOW=%08llX => TS=%011llu\n", + m_last_good_cycle, m_last_dropped, m_last_syt, m_last_now, m_last_timestamp2); + debugOutput(DEBUG_LEVEL_VERBOSE, "[2] cy=%04d dropped=%05d syt=%04X NOW=%08llX => TS=%011llu\n", + cycle, dropped_cycles, ntohs(packet->syt), nowX, m_last_timestamp); + + uint32_t test_ts=sytRecvToFullTicks(syt, cycle, now); + + debugOutput(DEBUG_LEVEL_VERBOSE, "R %04d: SYT=%08X, CY=%04d OFF=%04d\n", + cycle, syt, CYCLE_TIMER_GET_CYCLES(syt), CYCLE_TIMER_GET_OFFSET(syt) + ); + debugOutput(DEBUG_LEVEL_VERBOSE, "R %04d: NOW=%011lu, SEC=%03u CY=%04u OFF=%04u\n", + cycle, now_ticks, CYCLE_TIMER_GET_SECS(now), CYCLE_TIMER_GET_CYCLES(now), CYCLE_TIMER_GET_OFFSET(now) + ); + debugOutput(DEBUG_LEVEL_VERBOSE, "R %04d: TSS=%011lu, SEC=%03u CY=%04u OFF=%04u\n", + cycle, test_ts, TICKS_TO_SECS(test_ts), TICKS_TO_CYCLES(test_ts), TICKS_TO_OFFSET(test_ts) + ); + + int64_t diff_ts = diffTicks(now_ticks, test_ts); + debugOutput(DEBUG_LEVEL_VERBOSE, "DIFF : TCK=%011lld, SEC=%03llu CY=%04llu OFF=%04llu\n", + diff_ts, + TICKS_TO_SECS((uint64_t)diff_ts), + TICKS_TO_CYCLES((uint64_t)diff_ts), + TICKS_TO_OFFSET((uint64_t)diff_ts) + ); + } + m_last_syt = ntohs(packet->syt); + m_last_now = nowX; + m_last_good_cycle = cycle; + m_last_dropped = dropped_cycles; + + debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "RECV: CY=%04u TS=%011llu\n", + cycle, m_last_timestamp); + + // we have to keep in mind that there are also + // some packets buffered by the ISO layer, + // at most x=m_handler->getWakeupInterval() + // these contain at most x*syt_interval + // frames, meaning that we might receive + // this packet x*syt_interval*ticks_per_frame + // later than expected (the real receive time) + debugOutput(DEBUG_LEVEL_VERY_VERBOSE,"STMP: %lluticks | buff=%d, syt_interval=%d, tpf=%f\n", + m_last_timestamp, m_handler->getWakeupInterval(),m_syt_interval,getTicksPerFrame()); + + //=> signal that we're running (if we are) + if(!m_running && nevents && m_last_timestamp2 && m_last_timestamp) { + debugOutput(DEBUG_LEVEL_VERBOSE,"Receive StreamProcessor %p started running at %d\n", this, cycle); + m_running=true; + m_data_buffer->setBufferTailTimestamp(m_last_timestamp); + // we don't want this first sample to be written + return RAW1394_ISO_OK; + } + + // if we are not running yet, there is nothing more to do + if(!m_running) { + return RAW1394_ISO_OK; + } + #ifdef DEBUG_OFF + if((cycle % 1000) == 0) { + uint32_t now=m_handler->getCycleTimer(); + uint32_t syt = (uint32_t)ntohs(packet->syt); + uint32_t now_ticks=CYCLE_TIMER_TO_TICKS(now); + + uint32_t test_ts=sytRecvToFullTicks(syt, cycle, now); + + debugOutput(DEBUG_LEVEL_VERBOSE, "R %04d: SYT=%08X, CY=%02d OFF=%04d\n", + cycle, syt, CYCLE_TIMER_GET_CYCLES(syt), CYCLE_TIMER_GET_OFFSET(syt) + ); + debugOutput(DEBUG_LEVEL_VERBOSE, "R %04d: NOW=%011lu, SEC=%03u CY=%02u OFF=%04u\n", + cycle, now_ticks, CYCLE_TIMER_GET_SECS(now), CYCLE_TIMER_GET_CYCLES(now), CYCLE_TIMER_GET_OFFSET(now) + ); + debugOutput(DEBUG_LEVEL_VERBOSE, "R %04d: TSS=%011lu, SEC=%03u CY=%02u OFF=%04u\n", + cycle, test_ts, TICKS_TO_SECS(test_ts), TICKS_TO_CYCLES(test_ts), TICKS_TO_OFFSET(test_ts) + ); + } + #endif + + #ifdef DEBUG + // keep track of the lag + uint32_t now=m_handler->getCycleTimer(); + int32_t diff = diffCycles( cycle, ((int)CYCLE_TIMER_GET_CYCLES(now)) ); + m_PacketStat.mark(diff); + #endif + + //=> process the packet + // add the data payload to the ringbuffer + + if(dropped_cycles) { + debugWarning("(%p) Correcting timestamp for dropped cycles, discarding packet...\n", this); + m_data_buffer->setBufferTailTimestamp(m_last_timestamp); + // we don't want this first sample to be written + return RAW1394_ISO_OK; + } + + if(m_data_buffer->writeFrames(nevents, (char *)(data+8), m_last_timestamp)) { + retval=RAW1394_ISO_OK; + + // process all ports that should be handled on a per-packet base + // this is MIDI for AMDTP (due to the need of DBC) + if (!decodePacketPorts((quadlet_t *)(data+8), nevents, packet->dbc)) { + debugWarning("Problem decoding Packet Ports\n"); + retval=RAW1394_ISO_DEFER; + } + + } else { + +// debugWarning("Receive buffer overrun (cycle %d, FC=%d, PC=%d)\n", +// cycle, m_data_buffer->getFrameCounter(), m_handler->getPacketCount()); + + m_xruns++; + + retval=RAW1394_ISO_DEFER; + } + } + + return retval; +} + +void AmdtpReceiveStreamProcessor::dumpInfo() { + StreamProcessor::dumpInfo(); +} + +bool AmdtpReceiveStreamProcessor::reset() { + + debugOutput( DEBUG_LEVEL_VERBOSE, "Resetting...\n"); + + m_PeriodStat.reset(); + m_PacketStat.reset(); + m_WakeupStat.reset(); + + m_data_buffer->setTickOffset(0); + + // reset all non-device specific stuff + // i.e. the iso stream and the associated ports + if(!ReceiveStreamProcessor::reset()) { + debugFatal("Could not do base class reset\n"); + return false; + } + return true; +} + +bool AmdtpReceiveStreamProcessor::prepare() { + + m_PeriodStat.setName("RCV PERIOD"); + m_PacketStat.setName("RCV PACKET"); + m_WakeupStat.setName("RCV WAKEUP"); + + debugOutput( DEBUG_LEVEL_VERBOSE, "Preparing (%p)...\n", this); + + // prepare all non-device specific stuff + // i.e. the iso stream and the associated ports + if(!ReceiveStreamProcessor::prepare()) { + debugFatal("Could not prepare base class\n"); + return false; + } + + switch (m_framerate) { + case 32000: + m_syt_interval = 8; + break; + case 44100: + m_syt_interval = 8; + break; + default: + case 48000: + m_syt_interval = 8; + break; + case 88200: + m_syt_interval = 16; + break; + case 96000: + m_syt_interval = 16; + break; + case 176400: + m_syt_interval = 32; + break; + case 192000: + m_syt_interval = 32; + break; + } + + // prepare the framerate estimate + float ticks_per_frame = (TICKS_PER_SECOND*1.0) / ((float)m_framerate); + m_ticks_per_frame=ticks_per_frame; + + debugOutput(DEBUG_LEVEL_VERBOSE,"Initializing remote ticks/frame to %f\n",ticks_per_frame); + + // initialize internal buffer + unsigned int ringbuffer_size_frames=m_nb_buffers * m_period; + + assert(m_data_buffer); + m_data_buffer->setBufferSize(ringbuffer_size_frames * 2); + m_data_buffer->setEventSize(sizeof(quadlet_t)); + m_data_buffer->setEventsPerFrame(m_dimension); + + // the buffer is written every syt_interval + m_data_buffer->setUpdatePeriod(m_syt_interval); + m_data_buffer->setNominalRate(ticks_per_frame); + + m_data_buffer->setWrapValue(128L*TICKS_PER_SECOND); + + m_data_buffer->prepare(); + + // set the parameters of ports we can: + // we want the audio ports to be period buffered, + // and the midi ports to be packet buffered + for ( PortVectorIterator it = m_Ports.begin(); + it != m_Ports.end(); + ++it ) + { + debugOutput(DEBUG_LEVEL_VERBOSE, "Setting up port %s\n",(*it)->getName().c_str()); + if(!(*it)->setBufferSize(m_period)) { + debugFatal("Could not set buffer size to %d\n",m_period); + return false; + } + + switch ((*it)->getPortType()) { + case Port::E_Audio: + if(!(*it)->setSignalType(Port::E_PeriodSignalled)) { + debugFatal("Could not set signal type to PeriodSignalling"); + return false; + } + // buffertype and datatype are dependant on the API + debugWarning("---------------- ! Doing hardcoded dummy setup ! --------------\n"); + // buffertype and datatype are dependant on the API + if(!(*it)->setBufferType(Port::E_PointerBuffer)) { + debugFatal("Could not set buffer type"); + return false; + } + if(!(*it)->useExternalBuffer(true)) { + debugFatal("Could not set external buffer usage"); + return false; + } + if(!(*it)->setDataType(Port::E_Float)) { + debugFatal("Could not set data type"); + return false; + } + break; + case Port::E_Midi: + if(!(*it)->setSignalType(Port::E_PacketSignalled)) { + debugFatal("Could not set signal type to PacketSignalling"); + return false; + } + // buffertype and datatype are dependant on the API + // buffertype and datatype are dependant on the API + debugWarning("---------------- ! Doing hardcoded test setup ! --------------\n"); + // buffertype and datatype are dependant on the API + 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; + } + break; + default: + debugWarning("Unsupported port type specified\n"); + break; + } + } + + // the API specific settings of the ports should already be set, + // as this is called from the processorManager->prepare() + // so we can init the ports + if(!initPorts()) { + debugFatal("Could not initialize ports!\n"); + return false; + } + + if(!preparePorts()) { + debugFatal("Could not initialize ports!\n"); + return false; + } + + debugOutput( DEBUG_LEVEL_VERBOSE, "Prepared for:\n"); + debugOutput( DEBUG_LEVEL_VERBOSE, " Samplerate: %d, DBS: %d, SYT: %d\n", + m_framerate,m_dimension,m_syt_interval); + debugOutput( DEBUG_LEVEL_VERBOSE, " PeriodSize: %d, NbBuffers: %d\n", + m_period,m_nb_buffers); + debugOutput( DEBUG_LEVEL_VERBOSE, " Port: %d, Channel: %d\n", + m_port,m_channel); + + return true; + +} + +bool AmdtpReceiveStreamProcessor::prepareForStart() { + disable(); + return true; +} + +bool AmdtpReceiveStreamProcessor::prepareForStop() { + disable(); + return true; +} + +bool AmdtpReceiveStreamProcessor::getFrames(unsigned int nbframes, int64_t ts) { + m_PeriodStat.mark(m_data_buffer->getBufferFill()); + +#ifdef DEBUG + uint64_t ts_head; + signed int fc; + int32_t lag_ticks; + float lag_frames; + + // in order to sync up multiple received streams, we should + // use the ts parameter. It specifies the time of the block's + // first sample. + + ffado_timestamp_t ts_head_tmp; + m_data_buffer->getBufferHeadTimestamp(&ts_head_tmp, &fc); + ts_head=(uint64_t)ts_head_tmp; + lag_ticks=diffTicks(ts, ts_head); + float rate=m_data_buffer->getRate(); + + assert(rate!=0.0); + + lag_frames=(((float)lag_ticks)/rate); + + debugOutput( DEBUG_LEVEL_VERY_VERBOSE, "stream (%p): drifts %6d ticks = %10.5f frames (rate=%10.5f), %lld, %llu, %d\n", + this, lag_ticks, lag_frames,rate, ts, ts_head, fc); + + if (lag_frames>=1.0) { + // the stream lags + debugWarning( "stream (%p): lags with %6d ticks = %10.5f frames (rate=%10.5f), %lld, %llu, %d\n", + this, lag_ticks, lag_frames,rate, ts, ts_head, fc); + } else if (lag_frames<=-1.0) { + // the stream leads + debugWarning( "stream (%p): leads with %6d ticks = %10.5f frames (rate=%10.5f), %lld, %llu, %d\n", + this, lag_ticks, lag_frames,rate, ts, ts_head, fc); + } +#endif + // ask the buffer to process nbframes of frames + // using it's registered client's processReadBlock(), + // which should be ours + m_data_buffer->blockProcessReadFrames(nbframes); + + return true; +} + +bool AmdtpReceiveStreamProcessor::getFramesDry(unsigned int nbframes, int64_t ts) { + m_PeriodStat.mark(m_data_buffer->getBufferFill()); + int frames_to_ditch=(int)(nbframes); + debugOutput( DEBUG_LEVEL_VERY_VERBOSE, "stream (%p): dry run %d frames (@ ts=%lld)\n", + this, frames_to_ditch, ts); + char dummy[m_data_buffer->getBytesPerFrame()]; // one frame of garbage + + while (frames_to_ditch--) { + m_data_buffer->readFrames(1, dummy); + } + return true; +} + +/** + * \brief write received events to the stream ringbuffers. + */ +bool AmdtpReceiveStreamProcessor::processReadBlock(char *data, + unsigned int nevents, unsigned int offset) +{ + debugOutput( DEBUG_LEVEL_VERY_VERBOSE, "(%p)->processReadBlock(%u, %u)\n",this,nevents,offset); + + bool no_problem=true; + + for ( PortVectorIterator it = m_PeriodPorts.begin(); + it != m_PeriodPorts.end(); + ++it ) + { + + if((*it)->isDisabled()) {continue;}; + + //FIXME: make this into a static_cast when not DEBUG? + + AmdtpPortInfo *pinfo=dynamic_cast(*it); + assert(pinfo); // this should not fail!! + + switch(pinfo->getFormat()) { + case AmdtpPortInfo::E_MBLA: + if(decodeMBLAEventsToPort(static_cast(*it), (quadlet_t *)data, offset, nevents)) { + debugWarning("Could not decode packet MBLA to port %s",(*it)->getName().c_str()); + no_problem=false; + } + break; + case AmdtpPortInfo::E_SPDIF: // still unimplemented + break; + /* for this processor, midi is a packet based port + case AmdtpPortInfo::E_Midi: + break;*/ + default: // ignore + break; + } + } + return no_problem; + +} + +/** + * @brief decode a packet for the packet-based ports + * + * @param data Packet data + * @param nevents number of events in data (including events of other ports & port types) + * @param dbc DataBlockCount value for this packet + * @return true if all successfull + */ +bool AmdtpReceiveStreamProcessor::decodePacketPorts(quadlet_t *data, unsigned int nevents, unsigned int dbc) +{ + bool ok=true; + + quadlet_t *target_event=NULL; + unsigned int j; + + for ( PortVectorIterator it = m_PacketPorts.begin(); + it != m_PacketPorts.end(); + ++it ) + { + +#ifdef DEBUG + AmdtpPortInfo *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()==AmdtpPortInfo::E_Midi); +#endif + AmdtpMidiPort *mp=static_cast(*it); + + // we decode this directly (no function call) due to the high frequency + /* idea: + spec says: current_midi_port=(dbc+j)%8; + => if we start at (dbc+stream->location-1)%8, + we'll start at the right event for the midi port. + => if we increment j with 8, we stay at the right event. + */ + // FIXME: as we know in advance how big a packet is (syt_interval) we can + // predict how much loops will be present here + for(j = (dbc & 0x07)+mp->getLocation(); j < nevents; j += 8) { + target_event=(quadlet_t *)(data + ((j * m_dimension) + mp->getPosition())); + quadlet_t sample_int=ntohl(*target_event); + // FIXME: this assumes that 2X and 3X speed isn't used, + // because only the 1X slot is put into the ringbuffer + if(IEC61883_AM824_GET_LABEL(sample_int) != IEC61883_AM824_LABEL_MIDI_NO_DATA) { + sample_int=(sample_int >> 16) & 0x000000FF; + if(!mp->writeEvent(&sample_int)) { + debugWarning("Packet port events lost\n"); + ok=false; + } + } + } + + } + + return ok; +} + +int AmdtpReceiveStreamProcessor::decodeMBLAEventsToPort(AmdtpAudioPort *p, quadlet_t *data, + unsigned int offset, unsigned int nevents) +{ + unsigned int j=0; + +// printf("****************\n"); +// hexDumpQuadlets(data,m_dimension*4); +// printf("****************\n"); + + quadlet_t *target_event; + + target_event=(quadlet_t *)(data + p->getPosition()); + + switch(p->getDataType()) { + default: + case Port::E_Int24: + { + quadlet_t *buffer=(quadlet_t *)(p->getBufferAddress()); + + assert(nevents + offset <= p->getBufferSize()); + + buffer+=offset; + + for(j = 0; j < nevents; j += 1) { // decode max nsamples + *(buffer)=(ntohl((*target_event) ) & 0x00FFFFFF); + buffer++; + target_event+=m_dimension; + } + } + break; + case Port::E_Float: + { + const float multiplier = 1.0f / (float)(0x7FFFFF); + float *buffer=(float *)(p->getBufferAddress()); + + assert(nevents + offset <= p->getBufferSize()); + + buffer+=offset; + + for(j = 0; j < nevents; j += 1) { // decode max nsamples + + unsigned int v = ntohl(*target_event) & 0x00FFFFFF; + // sign-extend highest bit of 24-bit int + int tmp = (int)(v << 8) / 256; + + *buffer = tmp * multiplier; + + buffer++; + target_event+=m_dimension; + } + } + break; + } + + return 0; +} + +} // end of namespace Streaming Index: /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpTransmitStreamProcessor.h =================================================================== --- /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpTransmitStreamProcessor.h (revision 712) +++ /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpTransmitStreamProcessor.h (revision 712) @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2005-2007 by Pieter Palmers + * + * This file is part of FFADO + * FFADO = Free Firewire (pro-)audio drivers for linux + * + * FFADO is based upon FreeBoB. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation; + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#ifndef __FFADO_AMDTPTRANSMITSTREAMPROCESSOR__ +#define __FFADO_AMDTPTRANSMITSTREAMPROCESSOR__ + +/** + * This class implements IEC61883-6 / AM824 / AMDTP based streaming + */ + +#include "debugmodule/debugmodule.h" + +#include "../generic/StreamProcessor.h" +#include "../util/cip.h" + +#include +#include + +#define AMDTP_MAX_PACKET_SIZE 2048 + +#define IEC61883_STREAM_TYPE_MIDI 0x0D +#define IEC61883_STREAM_TYPE_SPDIF 0x00 +#define IEC61883_STREAM_TYPE_MBLA 0x06 + +#define IEC61883_AM824_LABEL_MASK 0xFF000000 +#define IEC61883_AM824_GET_LABEL(x) (((x) & 0xFF000000) >> 24) +#define IEC61883_AM824_SET_LABEL(x,y) ((x) | ((y)<<24)) + +#define IEC61883_AM824_LABEL_MIDI_NO_DATA 0x80 +#define IEC61883_AM824_LABEL_MIDI_1X 0x81 +#define IEC61883_AM824_LABEL_MIDI_2X 0x82 +#define IEC61883_AM824_LABEL_MIDI_3X 0x83 + +namespace Streaming { + +class Port; +class AmdtpAudioPort; +class AmdtpMidiPort; + +/*! +\brief The Base Class for an AMDTP transmit stream processor + + This class implements a TransmitStreamProcessor that multiplexes Ports + into AMDTP streams. + +*/ +class AmdtpTransmitStreamProcessor + : public TransmitStreamProcessor +{ + +public: + /** + * Create a AMDTP transmit StreamProcessor + * @param port 1394 port + * @param framerate frame rate + * @param dimension number of substreams in the ISO stream + * (midi-muxed is only one stream) + */ + AmdtpTransmitStreamProcessor(int port, int framerate, int dimension); + virtual ~AmdtpTransmitStreamProcessor() {}; + + enum raw1394_iso_disposition + getPacket(unsigned char *data, unsigned int *length, + unsigned char *tag, unsigned char *sy, + int cycle, unsigned int dropped, unsigned int max_length); + + bool init(); + bool reset(); + bool prepare(); + + bool prepareForStop(); + bool prepareForStart(); + + bool prepareForEnable(uint64_t time_to_enable_at); + + bool putFrames(unsigned int nbframes, int64_t ts); ///< transfer the buffer contents from the client + bool putFramesDry(unsigned int nbframes, int64_t ts); + + // We have 1 period of samples = m_period + // this period takes m_period/m_framerate seconds of time + // during this time, 8000 packets are sent +// unsigned int getPacketsPerPeriod() {return (m_period*8000)/m_framerate;}; + + // however, if we only count the number of used packets + // it is m_period / m_syt_interval + unsigned int getPacketsPerPeriod() {return (m_period)/m_syt_interval;}; + + unsigned int getMaxPacketSize() {return 4 * (2 + m_syt_interval * m_dimension);}; + +protected: + bool processWriteBlock(char *data, unsigned int nevents, unsigned int offset); + + struct iec61883_cip m_cip_status; + + int m_dimension; + unsigned int m_syt_interval; + + int m_fdf; + + bool prefill(); + + unsigned int fillNoDataPacketHeader(struct iec61883_packet *packet, unsigned int* length); + unsigned int fillDataPacketHeader(struct iec61883_packet *packet, unsigned int* length, uint32_t ts); + + + bool transferSilence(unsigned int size); + + int transmitBlock(char *data, unsigned int nevents, + unsigned int offset); + + bool encodePacketPorts(quadlet_t *data, unsigned int nevents, unsigned int dbc); + int encodePortToMBLAEvents(AmdtpAudioPort *, quadlet_t *data, + unsigned int offset, unsigned int nevents); + + int transmitSilenceBlock(char *data, unsigned int nevents, + unsigned int offset); + int encodeSilencePortToMBLAEvents(AmdtpAudioPort *, quadlet_t *data, + unsigned int offset, unsigned int nevents); + void updatePreparedState(); + + unsigned long m_last_timestamp; + + unsigned int m_dbc; + + unsigned int m_ringbuffer_size_frames; +}; + +} // end of namespace Streaming + +#endif /* __FFADO_AMDTPTRANSMITSTREAMPROCESSOR__ */ + Index: /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpReceiveStreamProcessor.h =================================================================== --- /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpReceiveStreamProcessor.h (revision 712) +++ /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpReceiveStreamProcessor.h (revision 712) @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2005-2007 by Pieter Palmers + * + * This file is part of FFADO + * FFADO = Free Firewire (pro-)audio drivers for linux + * + * FFADO is based upon FreeBoB. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation; + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#ifndef __FFADO_AMDTPRECEIVESTREAMPROCESSOR__ +#define __FFADO_AMDTPRECEIVESTREAMPROCESSOR__ + +/** + * This class implements IEC61883-6 / AM824 / AMDTP based streaming + */ + +#include "debugmodule/debugmodule.h" + +#include "../generic/StreamProcessor.h" +#include "../util/cip.h" + +#include +#include + +#define AMDTP_MAX_PACKET_SIZE 2048 + +#define IEC61883_STREAM_TYPE_MIDI 0x0D +#define IEC61883_STREAM_TYPE_SPDIF 0x00 +#define IEC61883_STREAM_TYPE_MBLA 0x06 + +#define IEC61883_AM824_LABEL_MASK 0xFF000000 +#define IEC61883_AM824_GET_LABEL(x) (((x) & 0xFF000000) >> 24) +#define IEC61883_AM824_SET_LABEL(x,y) ((x) | ((y)<<24)) + +#define IEC61883_AM824_LABEL_MIDI_NO_DATA 0x80 +#define IEC61883_AM824_LABEL_MIDI_1X 0x81 +#define IEC61883_AM824_LABEL_MIDI_2X 0x82 +#define IEC61883_AM824_LABEL_MIDI_3X 0x83 + +namespace Streaming { + +class Port; +class AmdtpAudioPort; +class AmdtpMidiPort; +/*! +\brief The Base Class for an AMDTP receive stream processor + + This class implements a ReceiveStreamProcessor that demultiplexes + AMDTP streams into Ports. + +*/ +class AmdtpReceiveStreamProcessor + : public ReceiveStreamProcessor +{ + +public: + /** + * Create a AMDTP receive StreamProcessor + * @param port 1394 port + * @param framerate frame rate + * @param dimension number of substreams in the ISO stream + * (midi-muxed is only one stream) + */ + AmdtpReceiveStreamProcessor(int port, int framerate, int dimension); + virtual ~AmdtpReceiveStreamProcessor() {}; + + enum raw1394_iso_disposition putPacket(unsigned char *data, unsigned int length, + unsigned char channel, unsigned char tag, unsigned char sy, + unsigned int cycle, unsigned int dropped); + + + bool init(); + bool reset(); + bool prepare(); + + bool prepareForStop(); + bool prepareForStart(); + + bool getFrames(unsigned int nbframes, int64_t ts); ///< transfer the buffer contents to the client + bool getFramesDry(unsigned int nbframes, int64_t ts); + + // We have 1 period of samples = m_period + // this period takes m_period/m_framerate seconds of time + // during this time, 8000 packets are sent +// unsigned int getPacketsPerPeriod() {return (m_period*8000)/m_framerate;}; + + // however, if we only count the number of used packets + // it is m_period / m_syt_interval + unsigned int getPacketsPerPeriod() {return (m_period)/m_syt_interval;}; + + unsigned int getMaxPacketSize() {return 4 * (2 + m_syt_interval * m_dimension);}; + + void dumpInfo(); +protected: + + bool processReadBlock(char *data, unsigned int nevents, unsigned int offset); + + bool decodePacketPorts(quadlet_t *data, unsigned int nevents, unsigned int dbc); + + int decodeMBLAEventsToPort(AmdtpAudioPort *, quadlet_t *data, unsigned int offset, unsigned int nevents); + void updatePreparedState(); + + int m_dimension; + unsigned int m_syt_interval; + + uint64_t m_dropped; /// FIXME:debug + uint64_t m_last_dropped; /// FIXME:debug + uint64_t m_last_syt; /// FIXME:debug + uint64_t m_last_now; /// FIXME:debug + int m_last_good_cycle; /// FIXME:debug + uint64_t m_last_timestamp; /// last timestamp (in ticks) + uint64_t m_last_timestamp2; /// last timestamp (in ticks) + uint64_t m_last_timestamp_at_period_ticks; +}; + + +} // end of namespace Streaming + +#endif /* __FFADO_AMDTPRECEIVESTREAMPROCESSOR__ */ + Index: /anches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpStreamProcessor.cpp =================================================================== --- /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpStreamProcessor.cpp (revision 709) +++ (revision ) @@ -1,1393 +1,0 @@ -/* - * Copyright (C) 2005-2007 by Pieter Palmers - * - * This file is part of FFADO - * FFADO = Free Firewire (pro-)audio drivers for linux - * - * FFADO is based upon FreeBoB. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License version 2.1, as published by the Free Software Foundation; - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include "AmdtpStreamProcessor.h" -#include "AmdtpPort.h" - -#include "../util/cycletimer.h" - -#include -#include - -// in ticks -// as per AMDTP2.1: -// 354.17us + 125us @ 24.576ticks/usec = 11776.08192 ticks -#define DEFAULT_TRANSFER_DELAY (11776U) - -#define TRANSMIT_TRANSFER_DELAY DEFAULT_TRANSFER_DELAY - -namespace Streaming { - -/* transmit */ -AmdtpTransmitStreamProcessor::AmdtpTransmitStreamProcessor(int port, int framerate, int dimension) - : TransmitStreamProcessor(port, framerate), m_dimension(dimension) - , m_last_timestamp(0), m_dbc(0), m_ringbuffer_size_frames(0) -{} - -/** - * @return - */ -bool AmdtpTransmitStreamProcessor::init() { - - debugOutput( DEBUG_LEVEL_VERBOSE, "Initializing (%p)...\n"); - // call the parent init - // this has to be done before allocating the buffers, - // because this sets the buffersizes from the processormanager - if(!TransmitStreamProcessor::init()) { - debugFatal("Could not do base class init (%p)\n",this); - return false; - } - return true; -} - -enum raw1394_iso_disposition -AmdtpTransmitStreamProcessor::getPacket(unsigned char *data, unsigned int *length, - unsigned char *tag, unsigned char *sy, - int cycle, unsigned int dropped, unsigned int max_length) { - struct iec61883_packet *packet = (struct iec61883_packet *) data; - - if (cycle<0) { - debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE,"Xmit handler for cycle %d, (running=%d)\n", - cycle, m_running); - *tag = 0; - *sy = 0; - *length=0; - return RAW1394_ISO_OK; - } - - debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE,"Xmit handler for cycle %d, (running=%d)\n", - cycle, m_running); - - m_last_cycle=cycle; - -#ifdef DEBUG - if(dropped>0) { - debugWarning("Dropped %d packets on cycle %d\n",dropped, cycle); - } -#endif - - // calculate & preset common values - - /* Our node ID can change after a bus reset, so it is best to fetch - * our node ID for each packet. */ - packet->sid = getNodeId() & 0x3f; - - packet->dbs = m_dimension; - packet->fn = 0; - packet->qpc = 0; - packet->sph = 0; - packet->reserved = 0; - packet->dbc = m_dbc; - packet->eoh1 = 2; - packet->fmt = IEC61883_FMT_AMDTP; - - *tag = IEC61883_TAG_WITH_CIP; - *sy = 0; - - // determine if we want to send a packet or not - // note that we can't use getCycleTimer directly here, - // because packets are queued in advance. This means that - // we the packet we are constructing will be sent out - // on 'cycle', not 'now'. - unsigned int ctr=m_handler->getCycleTimer(); - int now_cycles = (int)CYCLE_TIMER_GET_CYCLES(ctr); - - // the difference between the cycle this - // packet is intended for and 'now' - int cycle_diff = diffCycles(cycle, now_cycles); - -#ifdef DEBUG - if(m_running && (cycle_diff < 0)) { - debugWarning("Requesting packet for cycle %04d which is in the past (now=%04dcy)\n", - cycle, now_cycles); - } - - // keep track of the lag - m_PacketStat.mark(cycle_diff); -#endif - - // as long as the cycle parameter is not in sync with - // the current time, the stream is considered not - // to be 'running' - // NOTE: this works only at startup - if (!m_running && cycle_diff >= 0 && cycle >= 0) { - debugOutput(DEBUG_LEVEL_VERBOSE, "Xmit StreamProcessor %p started running at cycle %d\n",this, cycle); - m_running=true; - } - - signed int fc; - uint64_t presentation_time; - unsigned int presentation_cycle; - int cycles_until_presentation; - - uint64_t transmit_at_time; - unsigned int transmit_at_cycle; - int cycles_until_transmit; - - // FIXME: should become a define - // the absolute minimum number of cycles we want to transmit - // a packet ahead of the presentation time. The nominal time - // the packet is transmitted ahead of the presentation time is - // given by TRANSMIT_TRANSFER_DELAY (in ticks), but in case we - // are too late for that, this constant defines how late we can - // be. - const int min_cycles_before_presentation = 1; - // FIXME: should become a define - // the absolute maximum number of cycles we want to transmit - // a packet ahead of the ideal transmit time. The nominal time - // the packet is transmitted ahead of the presentation time is - // given by TRANSMIT_TRANSFER_DELAY (in ticks), but we can send - // packets early if we want to. (not completely according to spec) - const int max_cycles_to_transmit_early = 1; - - if( !m_running || !m_data_buffer->isEnabled() ) { - debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, - "Not running (%d) or buffer not enabled (enabled=%d)\n", - m_running, m_data_buffer->isEnabled()); - - // not running or not enabled - goto send_empty_packet; - } - -try_block_of_frames: - debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, "Try for cycle %d\n", cycle); - // check whether the packet buffer has packets for us to send. - // the base timestamp is the one of the next sample in the buffer - ffado_timestamp_t ts_head_tmp; - m_data_buffer->getBufferHeadTimestamp(&ts_head_tmp, &fc); // thread safe - - // the timestamp gives us the time at which we want the sample block - // to be output by the device - presentation_time=(uint64_t)ts_head_tmp; - - // now we calculate the time when we have to transmit the sample block - transmit_at_time = substractTicks(presentation_time, TRANSMIT_TRANSFER_DELAY); - - // calculate the cycle this block should be presented in - // (this is just a virtual calculation since at that time it should - // already be in the device's buffer) - presentation_cycle = (unsigned int)(TICKS_TO_CYCLES( presentation_time )); - - // calculate the cycle this block should be transmitted in - transmit_at_cycle = (unsigned int)(TICKS_TO_CYCLES( transmit_at_time )); - - // we can check whether this cycle is within the 'window' we have - // to send this packet. - // first calculate the number of cycles left before presentation time - cycles_until_presentation = diffCycles( presentation_cycle, cycle ); - - // we can check whether this cycle is within the 'window' we have - // to send this packet. - // first calculate the number of cycles left before presentation time - cycles_until_transmit = diffCycles( transmit_at_cycle, cycle ); - - // two different options: - // 1) there are not enough frames for one packet - // => determine wether this is a problem, since we might still - // have some time to send it - // 2) there are enough packets - // => determine whether we have to send them in this packet - if (fc < (signed int)m_syt_interval) { - m_PacketStat.signal(0); - // not enough frames in the buffer, - debugOutput(DEBUG_LEVEL_VERBOSE, - "Insufficient frames: N=%02d, CY=%04u, TC=%04u, CUT=%04d\n", - fc, cycle, transmit_at_cycle, cycles_until_transmit); - // we can still postpone the queueing of the packets - // if we are far enough ahead of the presentation time - if( cycles_until_presentation <= min_cycles_before_presentation ) { - m_PacketStat.signal(1); - // we are too late - // meaning that we in some sort of xrun state - // signal xrun situation ??HERE?? - m_xruns++; - // we send an empty packet on this cycle - goto send_empty_packet; // UGLY but effective - } else { - m_PacketStat.signal(2); - // there is still time left to send the packet - // we want the system to give this packet another go -// goto try_packet_again; // UGLY but effective - // unfortunatly the try_again doesn't work very well, - // so we'll have to either usleep(one cycle) and goto try_block_of_frames - - // or just fill this with an empty packet - // if we have to do this too often, the presentation time will - // get too close and we're in trouble - goto send_empty_packet; // UGLY but effective - } - } else { - m_PacketStat.signal(3); - // there are enough frames, so check the time they are intended for - // all frames have a certain 'time window' in which they can be sent - // this corresponds to the range of the timestamp mechanism: - // we can send a packet 15 cycles in advance of the 'presentation time' - // in theory we can send the packet up till one cycle before the presentation time, - // however this is not very smart. - - // There are 3 options: - // 1) the frame block is too early - // => send an empty packet - // 2) the frame block is within the window - // => send it - // 3) the frame block is too late - // => discard (and raise xrun?) - // get next block of frames and repeat - - if (cycles_until_transmit <= max_cycles_to_transmit_early) { - m_PacketStat.signal(4); - // it's time send the packet - goto send_packet; // UGLY but effective - } else if (cycles_until_transmit < 0) { - // we are too late - debugOutput(DEBUG_LEVEL_VERBOSE, - "Too late: CY=%04u, TC=%04u, CUT=%04d, TSP=%011llu (%04u)\n", - cycle, - transmit_at_cycle, cycles_until_transmit, - presentation_time, (unsigned int)TICKS_TO_CYCLES(presentation_time)); - - // however, if we can send this sufficiently before the presentation - // time, it could be harmless. - // NOTE: dangerous since the device has no way of reporting that it didn't get - // this packet on time. - if ( cycles_until_presentation <= min_cycles_before_presentation ) { - m_PacketStat.signal(5); - // we are not that late and can still try to transmit the packet - goto send_packet; // UGLY but effective - } else { // definitely too late - m_PacketStat.signal(6); - // remove the samples - m_data_buffer->dropFrames(m_syt_interval); - // signal some xrun situation ??HERE?? - m_xruns++; - // try a new block of frames - goto try_block_of_frames; // UGLY but effective - } - } else { - m_PacketStat.signal(7); - debugOutput(DEBUG_LEVEL_VERY_VERBOSE, - "Too early: CY=%04u, TC=%04u, CUT=%04d, TST=%011llu (%04u), TSP=%011llu (%04u)\n", - cycle, - transmit_at_cycle, cycles_until_transmit, - transmit_at_time, (unsigned int)TICKS_TO_CYCLES(transmit_at_time), - presentation_time, (unsigned int)TICKS_TO_CYCLES(presentation_time)); - #ifdef DEBUG - if (cycles_until_transmit > max_cycles_to_transmit_early + 1) { - debugOutput(DEBUG_LEVEL_VERBOSE, - "Way too early: CY=%04u, TC=%04u, CUT=%04d, TST=%011llu (%04u), TSP=%011llu (%04u)\n", - cycle, - transmit_at_cycle, cycles_until_transmit, - transmit_at_time, (unsigned int)TICKS_TO_CYCLES(transmit_at_time), - presentation_time, (unsigned int)TICKS_TO_CYCLES(presentation_time)); - } - #endif - // we are too early, send only an empty packet - goto send_empty_packet; // UGLY but effective - } - } - - debugFatal("Should never reach this code!\n"); - return RAW1394_ISO_ERROR; - -send_empty_packet: - debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "XMIT NONE: CY=%04u, TSP=%011llu (%04u)\n", - cycle, - presentation_time, (unsigned int)TICKS_TO_CYCLES(presentation_time)); - - m_dbc += fillNoDataPacketHeader(packet, length); - return RAW1394_ISO_DEFER; - -send_packet: - if (m_data_buffer->readFrames(m_syt_interval, (char *)(data + 8))) { - m_dbc += fillDataPacketHeader(packet, length, presentation_time); - - // process all ports that should be handled on a per-packet base - // this is MIDI for AMDTP (due to the need of DBC) - if (!encodePacketPorts((quadlet_t *)(data+8), m_syt_interval, packet->dbc)) { - debugWarning("Problem encoding Packet Ports\n"); - } - - debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "XMIT DATA: CY=%04u, TST=%011llu (%04u), TSP=%011llu (%04u)\n", - cycle, - transmit_at_time, (unsigned int)TICKS_TO_CYCLES(transmit_at_time), - presentation_time, (unsigned int)TICKS_TO_CYCLES(presentation_time)); - - return RAW1394_ISO_OK; - } - -// the ISO AGAIN does not work very well... -// try_packet_again: -// -// debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "XMIT RETRY: CY=%04u, TSP=%011llu (%04u)\n", -// cycle, -// presentation_time, (unsigned int)TICKS_TO_CYCLES(presentation_time)); -// return RAW1394_ISO_AGAIN; - - // else: - debugFatal("This is impossible, since we checked the buffer size before!\n"); - return RAW1394_ISO_ERROR; -} - -unsigned int AmdtpTransmitStreamProcessor::fillDataPacketHeader( - struct iec61883_packet *packet, unsigned int* length, - uint32_t ts) { - - packet->fdf = m_fdf; - - // convert the timestamp to SYT format - uint16_t timestamp_SYT = TICKS_TO_SYT(ts); - packet->syt = ntohs(timestamp_SYT); - - *length = m_syt_interval*sizeof(quadlet_t)*m_dimension + 8; - - return m_syt_interval; -} - -unsigned int AmdtpTransmitStreamProcessor::fillNoDataPacketHeader( - struct iec61883_packet *packet, unsigned int* length) { - - // no-data packets have syt=0xFFFF - // and have the usual amount of events as dummy data (?) - packet->fdf = IEC61883_FDF_NODATA; - packet->syt = 0xffff; - - // FIXME: either make this a setting or choose - bool send_payload=true; - if(send_payload) { - // this means no-data packets with payload (DICE doesn't like that) - *length = 2*sizeof(quadlet_t) + m_syt_interval * m_dimension * sizeof(quadlet_t); - return m_syt_interval; - } else { - // dbc is not incremented - // this means no-data packets without payload - *length = 2*sizeof(quadlet_t); - return 0; - } -} - -bool AmdtpTransmitStreamProcessor::prefill() { - - debugOutput( DEBUG_LEVEL_VERBOSE, "Prefill transmit buffers...\n"); - - if(!transferSilence(m_ringbuffer_size_frames)) { - debugFatal("Could not prefill transmit stream\n"); - return false; - } - - return true; -} - -bool AmdtpTransmitStreamProcessor::reset() { - - debugOutput( DEBUG_LEVEL_VERBOSE, "Resetting...\n"); - - // reset the statistics - m_PeriodStat.reset(); - m_PacketStat.reset(); - m_WakeupStat.reset(); - - m_data_buffer->setTickOffset(0); - - // reset all non-device specific stuff - // i.e. the iso stream and the associated ports - if(!TransmitStreamProcessor::reset()) { - debugFatal("Could not do base class reset\n"); - return false; - } - - // we should prefill the event buffer - if (!prefill()) { - debugFatal("Could not prefill buffers\n"); - return false; - } - - return true; -} - -bool AmdtpTransmitStreamProcessor::prepare() { - m_PeriodStat.setName("XMT PERIOD"); - m_PacketStat.setName("XMT PACKET"); - m_WakeupStat.setName("XMT WAKEUP"); - - debugOutput( DEBUG_LEVEL_VERBOSE, "Preparing (%p)...\n", this); - - // prepare all non-device specific stuff - // i.e. the iso stream and the associated ports - if(!TransmitStreamProcessor::prepare()) { - debugFatal("Could not prepare base class\n"); - return false; - } - - switch (m_framerate) { - case 32000: - m_syt_interval = 8; - m_fdf = IEC61883_FDF_SFC_32KHZ; - break; - case 44100: - m_syt_interval = 8; - m_fdf = IEC61883_FDF_SFC_44K1HZ; - break; - default: - case 48000: - m_syt_interval = 8; - m_fdf = IEC61883_FDF_SFC_48KHZ; - break; - case 88200: - m_syt_interval = 16; - m_fdf = IEC61883_FDF_SFC_88K2HZ; - break; - case 96000: - m_syt_interval = 16; - m_fdf = IEC61883_FDF_SFC_96KHZ; - break; - case 176400: - m_syt_interval = 32; - m_fdf = IEC61883_FDF_SFC_176K4HZ; - break; - case 192000: - m_syt_interval = 32; - m_fdf = IEC61883_FDF_SFC_192KHZ; - break; - } - - iec61883_cip_init ( - &m_cip_status, - IEC61883_FMT_AMDTP, - m_fdf, - m_framerate, - m_dimension, - m_syt_interval); - - // prepare the framerate estimate - float ticks_per_frame = (TICKS_PER_SECOND*1.0) / ((float)m_framerate); - m_ticks_per_frame=ticks_per_frame; - - // initialize internal buffer - m_ringbuffer_size_frames=m_nb_buffers * m_period; - - assert(m_data_buffer); - m_data_buffer->setBufferSize(m_ringbuffer_size_frames); - m_data_buffer->setEventSize(sizeof(quadlet_t)); - m_data_buffer->setEventsPerFrame(m_dimension); - - m_data_buffer->setUpdatePeriod(m_period); - m_data_buffer->setNominalRate(ticks_per_frame); - - m_data_buffer->setWrapValue(128L*TICKS_PER_SECOND); - - m_data_buffer->prepare(); - - // set the parameters of ports we can: - // we want the audio ports to be period buffered, - // and the midi ports to be packet buffered - for ( PortVectorIterator it = m_Ports.begin(); - it != m_Ports.end(); - ++it ) - { - debugOutput(DEBUG_LEVEL_VERBOSE, "Setting up port %s\n",(*it)->getName().c_str()); - if(!(*it)->setBufferSize(m_period)) { - debugFatal("Could not set buffer size to %d\n",m_period); - return false; - } - - - switch ((*it)->getPortType()) { - case Port::E_Audio: - if(!(*it)->setSignalType(Port::E_PeriodSignalled)) { - debugFatal("Could not set signal type to PeriodSignalling"); - return false; - } - debugWarning("---------------- ! Doing hardcoded test setup ! --------------\n"); - // buffertype and datatype are dependant on the API - if(!(*it)->setBufferType(Port::E_PointerBuffer)) { - debugFatal("Could not set buffer type"); - return false; - } - if(!(*it)->useExternalBuffer(true)) { - debugFatal("Could not set external buffer usage"); - return false; - } - - if(!(*it)->setDataType(Port::E_Float)) { - debugFatal("Could not set data type"); - return false; - } - - - break; - case Port::E_Midi: - if(!(*it)->setSignalType(Port::E_PacketSignalled)) { - debugFatal("Could not set signal type to PeriodSignalling"); - return false; - } - - // we use a timing unit of 10ns - // this makes sure that for the max syt interval - // we don't have rounding, and keeps the numbers low - // we have 1 slot every 8 events - // we have syt_interval events per packet - // => syt_interval/8 slots per packet - // packet rate is 8000pkt/sec => interval=125us - // so the slot interval is (1/8000)/(syt_interval/8) - // or: 1/(1000 * syt_interval) sec - // which is 1e9/(1000*syt_interval) nsec - // or 100000/syt_interval 'units' - // the event interval is fixed to 320us = 32000 'units' - if(!(*it)->useRateControl(true,(100000/m_syt_interval),32000, false)) { - debugFatal("Could not set signal type to PeriodSignalling"); - return false; - } - - // buffertype and datatype are dependant on the API - debugWarning("---------------- ! Doing hardcoded test setup ! --------------\n"); - // buffertype and datatype are dependant on the API - 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; - } - break; - default: - debugWarning("Unsupported port type specified\n"); - break; - } - } - - // the API specific settings of the ports should already be set, - // as this is called from the processorManager->prepare() - // so we can init the ports - if(!initPorts()) { - debugFatal("Could not initialize ports!\n"); - return false; - } - - if(!preparePorts()) { - debugFatal("Could not initialize ports!\n"); - return false; - } - - debugOutput( DEBUG_LEVEL_VERBOSE, "Prepared for:\n"); - debugOutput( DEBUG_LEVEL_VERBOSE, " Samplerate: %d, FDF: %d, DBS: %d, SYT: %d\n", - m_framerate,m_fdf,m_dimension,m_syt_interval); - debugOutput( DEBUG_LEVEL_VERBOSE, " PeriodSize: %d, NbBuffers: %d\n", - m_period,m_nb_buffers); - debugOutput( DEBUG_LEVEL_VERBOSE, " Port: %d, Channel: %d\n", - m_port,m_channel); - - return true; - -} - -bool AmdtpTransmitStreamProcessor::prepareForStart() { - return true; -} - -bool AmdtpTransmitStreamProcessor::prepareForStop() { - return true; -} - -bool AmdtpTransmitStreamProcessor::prepareForEnable(uint64_t time_to_enable_at) { - - if (!StreamProcessor::prepareForEnable(time_to_enable_at)) { - debugError("StreamProcessor::prepareForEnable failed\n"); - return false; - } - - return true; -} - -bool AmdtpTransmitStreamProcessor::transferSilence(unsigned int nframes) { - bool retval; - - char *dummybuffer=(char *)calloc(sizeof(quadlet_t),nframes*m_dimension); - - transmitSilenceBlock(dummybuffer, nframes, 0); - - // add the silence data to the ringbuffer - if(m_data_buffer->writeFrames(nframes, dummybuffer, 0)) { - retval=true; - } else { - debugWarning("Could not write to event buffer\n"); - retval=false; - } - - free(dummybuffer); - - return retval; -} - -bool AmdtpTransmitStreamProcessor::putFrames(unsigned int nbframes, int64_t ts) { - m_PeriodStat.mark(m_data_buffer->getBufferFill()); - debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, "AmdtpTransmitStreamProcessor::putFrames(%d, %llu)\n", nbframes, ts); - - // transfer the data - m_data_buffer->blockProcessWriteFrames(nbframes, ts); - - debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, " New timestamp: %llu\n", ts); - - return true; // FIXME: what about failure? -} - -bool AmdtpTransmitStreamProcessor::putFramesDry(unsigned int nbframes, int64_t ts) { - m_PeriodStat.mark(m_data_buffer->getBufferFill()); - debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, "AmdtpTransmitStreamProcessor::putFramesDry(%d, %llu)\n", nbframes, ts); - - bool retval; - char dummybuffer[sizeof(quadlet_t)*nbframes*m_dimension]; - - transmitSilenceBlock(dummybuffer, nbframes, 0); - // add the silence data to the ringbuffer - if(m_data_buffer->writeFrames(nbframes, dummybuffer, ts)) { - retval=true; - } else { - debugWarning("Could not write to event buffer\n"); - retval=false; - } - - debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, " New timestamp: %llu\n", ts); - return retval; -} - -/* - * write received events to the stream ringbuffers. - */ - -bool AmdtpTransmitStreamProcessor::processWriteBlock(char *data, - unsigned int nevents, unsigned int offset) -{ - bool no_problem=true; - - for ( PortVectorIterator it = m_PeriodPorts.begin(); - it != m_PeriodPorts.end(); - ++it ) - { - - if((*it)->isDisabled()) {continue;}; - - //FIXME: make this into a static_cast when not DEBUG? - - AmdtpPortInfo *pinfo=dynamic_cast(*it); - assert(pinfo); // this should not fail!! - - switch(pinfo->getFormat()) { - case AmdtpPortInfo::E_MBLA: - if(encodePortToMBLAEvents(static_cast(*it), (quadlet_t *)data, offset, nevents)) { - debugWarning("Could not encode port %s to MBLA events",(*it)->getName().c_str()); - no_problem=false; - } - break; - case AmdtpPortInfo::E_SPDIF: // still unimplemented - break; - default: // ignore - break; - } - } - return no_problem; - -} - -int AmdtpTransmitStreamProcessor::transmitSilenceBlock(char *data, - unsigned int nevents, unsigned int offset) -{ - int problem=0; - - for ( PortVectorIterator it = m_PeriodPorts.begin(); - it != m_PeriodPorts.end(); - ++it ) - { - - //FIXME: make this into a static_cast when not DEBUG? - - AmdtpPortInfo *pinfo=dynamic_cast(*it); - assert(pinfo); // this should not fail!! - - switch(pinfo->getFormat()) { - case AmdtpPortInfo::E_MBLA: - if(encodeSilencePortToMBLAEvents(static_cast(*it), (quadlet_t *)data, offset, nevents)) { - debugWarning("Could not encode port %s to MBLA events",(*it)->getName().c_str()); - problem=1; - } - break; - case AmdtpPortInfo::E_SPDIF: // still unimplemented - break; - default: // ignore - break; - } - } - return problem; - -} - -/** - * @brief decode a packet for the packet-based ports - * - * @param data Packet data - * @param nevents number of events in data (including events of other ports & port types) - * @param dbc DataBlockCount value for this packet - * @return true if all successfull - */ -bool AmdtpTransmitStreamProcessor::encodePacketPorts(quadlet_t *data, unsigned int nevents, unsigned int dbc) -{ - bool ok=true; - quadlet_t byte; - - quadlet_t *target_event=NULL; - unsigned int j; - - for ( PortVectorIterator it = m_PacketPorts.begin(); - it != m_PacketPorts.end(); - ++it ) - { - -#ifdef DEBUG - AmdtpPortInfo *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()==AmdtpPortInfo::E_Midi); -#endif - - AmdtpMidiPort *mp=static_cast(*it); - - // we encode this directly (no function call) due to the high frequency - /* idea: - spec says: current_midi_port=(dbc+j)%8; - => if we start at (dbc+stream->location-1)%8, - we'll start at the right event for the midi port. - => if we increment j with 8, we stay at the right event. - */ - // FIXME: as we know in advance how big a packet is (syt_interval) we can - // predict how much loops will be present here - // first prefill the buffer with NO_DATA's on all time muxed channels - - for(j = (dbc & 0x07)+mp->getLocation(); j < nevents; j += 8) { - - quadlet_t tmpval; - - target_event=(quadlet_t *)(data + ((j * m_dimension) + mp->getPosition())); - - if(mp->canRead()) { // we can send a byte - mp->readEvent(&byte); - byte &= 0xFF; - tmpval=htonl( - IEC61883_AM824_SET_LABEL((byte)<<16, - IEC61883_AM824_LABEL_MIDI_1X)); - - debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, "MIDI port %s, pos=%d, loc=%d, dbc=%d, nevents=%d, dim=%d\n", - mp->getName().c_str(), mp->getPosition(), mp->getLocation(), dbc, nevents, m_dimension); - debugOutput(DEBUG_LEVEL_ULTRA_VERBOSE, "base=%p, target=%p, value=%08X\n", - data, target_event, tmpval); - - } else { - // can't send a byte, either because there is no byte, - // or because this would exceed the maximum rate - tmpval=htonl( - IEC61883_AM824_SET_LABEL(0,IEC61883_AM824_LABEL_MIDI_NO_DATA)); - } - - *target_event=tmpval; - } - - } - - return ok; -} - - -int AmdtpTransmitStreamProcessor::encodePortToMBLAEvents(AmdtpAudioPort *p, quadlet_t *data, - unsigned int offset, unsigned int nevents) -{ - unsigned int j=0; - - quadlet_t *target_event; - - target_event=(quadlet_t *)(data + p->getPosition()); - - switch(p->getDataType()) { - default: - case Port::E_Int24: - { - quadlet_t *buffer=(quadlet_t *)(p->getBufferAddress()); - - assert(nevents + offset <= p->getBufferSize()); - - buffer+=offset; - - for(j = 0; j < nevents; j += 1) { // decode max nsamples - *target_event = htonl((*(buffer) & 0x00FFFFFF) | 0x40000000); - buffer++; - target_event += m_dimension; - } - } - break; - case Port::E_Float: - { - const float multiplier = (float)(0x7FFFFF00); - float *buffer=(float *)(p->getBufferAddress()); - - assert(nevents + offset <= p->getBufferSize()); - - buffer+=offset; - - for(j = 0; j < nevents; j += 1) { // decode max nsamples - - // don't care for overflow - float v = *buffer * multiplier; // v: -231 .. 231 - unsigned int tmp = ((int)v); - *target_event = htonl((tmp >> 8) | 0x40000000); - - buffer++; - target_event += m_dimension; - } - } - break; - } - - return 0; -} -int AmdtpTransmitStreamProcessor::encodeSilencePortToMBLAEvents(AmdtpAudioPort *p, quadlet_t *data, - unsigned int offset, unsigned int nevents) -{ - unsigned int j=0; - - quadlet_t *target_event; - - target_event=(quadlet_t *)(data + p->getPosition()); - - switch(p->getDataType()) { - default: - case Port::E_Int24: - case Port::E_Float: - { - for(j = 0; j < nevents; j += 1) { // decode max nsamples - *target_event = htonl(0x40000000); - target_event += m_dimension; - } - } - break; - } - - return 0; -} - -/* --------------------- RECEIVE ----------------------- */ - -AmdtpReceiveStreamProcessor::AmdtpReceiveStreamProcessor(int port, int framerate, int dimension) - : ReceiveStreamProcessor(port, framerate), m_dimension(dimension), m_last_timestamp(0), m_last_timestamp2(0) -{} - -bool AmdtpReceiveStreamProcessor::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; -} - -enum raw1394_iso_disposition -AmdtpReceiveStreamProcessor::putPacket(unsigned char *data, unsigned int length, - unsigned char channel, unsigned char tag, unsigned char sy, - unsigned int cycle, unsigned int dropped) { - - enum raw1394_iso_disposition retval=RAW1394_ISO_OK; - m_last_cycle=cycle; - - struct iec61883_packet *packet = (struct iec61883_packet *) data; - assert(packet); - -#ifdef DEBUG - if(dropped>0) { - debugWarning("Dropped %d packets on cycle %d\n",dropped, cycle); - } - - debugOutput(DEBUG_LEVEL_VERY_VERBOSE,"ch%2u: CY=%4u, SYT=%08X (%4ucy + %04uticks) (running=%d)\n", - channel, cycle, ntohs(packet->syt), - CYCLE_TIMER_GET_CYCLES(ntohs(packet->syt)), CYCLE_TIMER_GET_OFFSET(ntohs(packet->syt)), - m_running); - - debugOutput(DEBUG_LEVEL_VERY_VERBOSE, - "RCV: CH = %d, FDF = %X. SYT = %6d, DBS = %3d, DBC = %3d, FMT = %3d, LEN = %4d\n", - channel, packet->fdf, - packet->syt, - packet->dbs, - packet->dbc, - packet->fmt, - length); - -#endif - - // check if this is a valid packet - if((packet->syt != 0xFFFF) - && (packet->fdf != 0xFF) - && (packet->fmt == 0x10) - && (packet->dbs>0) - && (length>=2*sizeof(quadlet_t))) { - - unsigned int nevents=((length / sizeof (quadlet_t)) - 2)/packet->dbs; - - //=> store the previous timestamp - m_last_timestamp2=m_last_timestamp; - - //=> convert the SYT to a full timestamp in ticks - m_last_timestamp=sytRecvToFullTicks((uint32_t)ntohs(packet->syt), - cycle, m_handler->getCycleTimer()); - - debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "RECV: CY=%04u TS=%011llu\n", - cycle, m_last_timestamp); - - // we have to keep in mind that there are also - // some packets buffered by the ISO layer, - // at most x=m_handler->getWakeupInterval() - // these contain at most x*syt_interval - // frames, meaning that we might receive - // this packet x*syt_interval*ticks_per_frame - // later than expected (the real receive time) - debugOutput(DEBUG_LEVEL_VERY_VERBOSE,"STMP: %lluticks | buff=%d, syt_interval=%d, tpf=%f\n", - m_last_timestamp, m_handler->getWakeupInterval(),m_syt_interval,getTicksPerFrame()); - - //=> signal that we're running (if we are) - if(!m_running && nevents && m_last_timestamp2 && m_last_timestamp) { - debugOutput(DEBUG_LEVEL_VERBOSE,"Receive StreamProcessor %p started running at %d\n", this, cycle); - m_running=true; - } - - #ifdef DEBUG_OFF - if((cycle % 1000) == 0) { - uint32_t now=m_handler->getCycleTimer(); - uint32_t syt = (uint32_t)ntohs(packet->syt); - uint32_t now_ticks=CYCLE_TIMER_TO_TICKS(now); - - uint32_t test_ts=sytRecvToFullTicks(syt, cycle, now); - - debugOutput(DEBUG_LEVEL_VERBOSE, "R %04d: SYT=%08X, CY=%02d OFF=%04d\n", - cycle, syt, CYCLE_TIMER_GET_CYCLES(syt), CYCLE_TIMER_GET_OFFSET(syt) - ); - debugOutput(DEBUG_LEVEL_VERBOSE, "R %04d: NOW=%011lu, SEC=%03u CY=%02u OFF=%04u\n", - cycle, now_ticks, CYCLE_TIMER_GET_SECS(now), CYCLE_TIMER_GET_CYCLES(now), CYCLE_TIMER_GET_OFFSET(now) - ); - debugOutput(DEBUG_LEVEL_VERBOSE, "R %04d: TSS=%011lu, SEC=%03u CY=%02u OFF=%04u\n", - cycle, test_ts, TICKS_TO_SECS(test_ts), TICKS_TO_CYCLES(test_ts), TICKS_TO_OFFSET(test_ts) - ); - } - #endif - - #ifdef DEBUG - // keep track of the lag - uint32_t now=m_handler->getCycleTimer(); - int32_t diff = diffCycles( cycle, ((int)CYCLE_TIMER_GET_CYCLES(now)) ); - m_PacketStat.mark(diff); - #endif - - //=> process the packet - // add the data payload to the ringbuffer - if(m_data_buffer->writeFrames(nevents, (char *)(data+8), m_last_timestamp)) { - retval=RAW1394_ISO_OK; - - // process all ports that should be handled on a per-packet base - // this is MIDI for AMDTP (due to the need of DBC) - if (!decodePacketPorts((quadlet_t *)(data+8), nevents, packet->dbc)) { - debugWarning("Problem decoding Packet Ports\n"); - retval=RAW1394_ISO_DEFER; - } - - } else { - -// debugWarning("Receive buffer overrun (cycle %d, FC=%d, PC=%d)\n", -// cycle, m_data_buffer->getFrameCounter(), m_handler->getPacketCount()); - - m_xruns++; - - retval=RAW1394_ISO_DEFER; - } - } - - return retval; -} - -void AmdtpReceiveStreamProcessor::dumpInfo() { - StreamProcessor::dumpInfo(); -} - -bool AmdtpReceiveStreamProcessor::reset() { - - debugOutput( DEBUG_LEVEL_VERBOSE, "Resetting...\n"); - - m_PeriodStat.reset(); - m_PacketStat.reset(); - m_WakeupStat.reset(); - - m_data_buffer->setTickOffset(0); - - // reset all non-device specific stuff - // i.e. the iso stream and the associated ports - if(!ReceiveStreamProcessor::reset()) { - debugFatal("Could not do base class reset\n"); - return false; - } - return true; -} - -bool AmdtpReceiveStreamProcessor::prepare() { - - m_PeriodStat.setName("RCV PERIOD"); - m_PacketStat.setName("RCV PACKET"); - m_WakeupStat.setName("RCV WAKEUP"); - - debugOutput( DEBUG_LEVEL_VERBOSE, "Preparing (%p)...\n", this); - - // prepare all non-device specific stuff - // i.e. the iso stream and the associated ports - if(!ReceiveStreamProcessor::prepare()) { - debugFatal("Could not prepare base class\n"); - return false; - } - - switch (m_framerate) { - case 32000: - m_syt_interval = 8; - break; - case 44100: - m_syt_interval = 8; - break; - default: - case 48000: - m_syt_interval = 8; - break; - case 88200: - m_syt_interval = 16; - break; - case 96000: - m_syt_interval = 16; - break; - case 176400: - m_syt_interval = 32; - break; - case 192000: - m_syt_interval = 32; - break; - } - - // prepare the framerate estimate - float ticks_per_frame = (TICKS_PER_SECOND*1.0) / ((float)m_framerate); - m_ticks_per_frame=ticks_per_frame; - - debugOutput(DEBUG_LEVEL_VERBOSE,"Initializing remote ticks/frame to %f\n",ticks_per_frame); - - // initialize internal buffer - unsigned int ringbuffer_size_frames=m_nb_buffers * m_period; - - assert(m_data_buffer); - m_data_buffer->setBufferSize(ringbuffer_size_frames); - m_data_buffer->setEventSize(sizeof(quadlet_t)); - m_data_buffer->setEventsPerFrame(m_dimension); - - // the buffer is written every syt_interval - m_data_buffer->setUpdatePeriod(m_syt_interval); - m_data_buffer->setNominalRate(ticks_per_frame); - - m_data_buffer->setWrapValue(128L*TICKS_PER_SECOND); - - m_data_buffer->prepare(); - - // set the parameters of ports we can: - // we want the audio ports to be period buffered, - // and the midi ports to be packet buffered - for ( PortVectorIterator it = m_Ports.begin(); - it != m_Ports.end(); - ++it ) - { - debugOutput(DEBUG_LEVEL_VERBOSE, "Setting up port %s\n",(*it)->getName().c_str()); - if(!(*it)->setBufferSize(m_period)) { - debugFatal("Could not set buffer size to %d\n",m_period); - return false; - } - - switch ((*it)->getPortType()) { - case Port::E_Audio: - if(!(*it)->setSignalType(Port::E_PeriodSignalled)) { - debugFatal("Could not set signal type to PeriodSignalling"); - return false; - } - // buffertype and datatype are dependant on the API - debugWarning("---------------- ! Doing hardcoded dummy setup ! --------------\n"); - // buffertype and datatype are dependant on the API - if(!(*it)->setBufferType(Port::E_PointerBuffer)) { - debugFatal("Could not set buffer type"); - return false; - } - if(!(*it)->useExternalBuffer(true)) { - debugFatal("Could not set external buffer usage"); - return false; - } - if(!(*it)->setDataType(Port::E_Float)) { - debugFatal("Could not set data type"); - return false; - } - break; - case Port::E_Midi: - if(!(*it)->setSignalType(Port::E_PacketSignalled)) { - debugFatal("Could not set signal type to PacketSignalling"); - return false; - } - // buffertype and datatype are dependant on the API - // buffertype and datatype are dependant on the API - debugWarning("---------------- ! Doing hardcoded test setup ! --------------\n"); - // buffertype and datatype are dependant on the API - 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; - } - break; - default: - debugWarning("Unsupported port type specified\n"); - break; - } - } - - // the API specific settings of the ports should already be set, - // as this is called from the processorManager->prepare() - // so we can init the ports - if(!initPorts()) { - debugFatal("Could not initialize ports!\n"); - return false; - } - - if(!preparePorts()) { - debugFatal("Could not initialize ports!\n"); - return false; - } - - debugOutput( DEBUG_LEVEL_VERBOSE, "Prepared for:\n"); - debugOutput( DEBUG_LEVEL_VERBOSE, " Samplerate: %d, DBS: %d, SYT: %d\n", - m_framerate,m_dimension,m_syt_interval); - debugOutput( DEBUG_LEVEL_VERBOSE, " PeriodSize: %d, NbBuffers: %d\n", - m_period,m_nb_buffers); - debugOutput( DEBUG_LEVEL_VERBOSE, " Port: %d, Channel: %d\n", - m_port,m_channel); - - return true; - -} - -bool AmdtpReceiveStreamProcessor::prepareForStart() { - disable(); - return true; -} - -bool AmdtpReceiveStreamProcessor::prepareForStop() { - disable(); - return true; -} - -bool AmdtpReceiveStreamProcessor::getFrames(unsigned int nbframes, int64_t ts) { - m_PeriodStat.mark(m_data_buffer->getBufferFill()); - - // ask the buffer to process nbframes of frames - // using it's registered client's processReadBlock(), - // which should be ours - m_data_buffer->blockProcessReadFrames(nbframes); - - return true; -} - -bool AmdtpReceiveStreamProcessor::getFramesDry(unsigned int nbframes, int64_t ts) { - m_PeriodStat.mark(m_data_buffer->getBufferFill()); - int frames_to_ditch=(int)(nbframes); - debugOutput( DEBUG_LEVEL_VERY_VERBOSE, "stream (%p): dry run %d frames (@ ts=%lld)\n", - this, frames_to_ditch, ts); - char dummy[m_data_buffer->getBytesPerFrame()]; // one frame of garbage - - while (frames_to_ditch--) { - m_data_buffer->readFrames(1, dummy); - } - return true; -} - -/** - * \brief write received events to the stream ringbuffers. - */ -bool AmdtpReceiveStreamProcessor::processReadBlock(char *data, - unsigned int nevents, unsigned int offset) -{ - debugOutput( DEBUG_LEVEL_VERY_VERBOSE, "(%p)->processReadBlock(%u, %u)\n",this,nevents,offset); - - bool no_problem=true; - - for ( PortVectorIterator it = m_PeriodPorts.begin(); - it != m_PeriodPorts.end(); - ++it ) - { - - if((*it)->isDisabled()) {continue;}; - - //FIXME: make this into a static_cast when not DEBUG? - - AmdtpPortInfo *pinfo=dynamic_cast(*it); - assert(pinfo); // this should not fail!! - - switch(pinfo->getFormat()) { - case AmdtpPortInfo::E_MBLA: - if(decodeMBLAEventsToPort(static_cast(*it), (quadlet_t *)data, offset, nevents)) { - debugWarning("Could not decode packet MBLA to port %s",(*it)->getName().c_str()); - no_problem=false; - } - break; - case AmdtpPortInfo::E_SPDIF: // still unimplemented - break; - /* for this processor, midi is a packet based port - case AmdtpPortInfo::E_Midi: - break;*/ - default: // ignore - break; - } - } - return no_problem; - -} - -/** - * @brief decode a packet for the packet-based ports - * - * @param data Packet data - * @param nevents number of events in data (including events of other ports & port types) - * @param dbc DataBlockCount value for this packet - * @return true if all successfull - */ -bool AmdtpReceiveStreamProcessor::decodePacketPorts(quadlet_t *data, unsigned int nevents, unsigned int dbc) -{ - bool ok=true; - - quadlet_t *target_event=NULL; - unsigned int j; - - for ( PortVectorIterator it = m_PacketPorts.begin(); - it != m_PacketPorts.end(); - ++it ) - { - -#ifdef DEBUG - AmdtpPortInfo *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()==AmdtpPortInfo::E_Midi); -#endif - AmdtpMidiPort *mp=static_cast(*it); - - // we decode this directly (no function call) due to the high frequency - /* idea: - spec says: current_midi_port=(dbc+j)%8; - => if we start at (dbc+stream->location-1)%8, - we'll start at the right event for the midi port. - => if we increment j with 8, we stay at the right event. - */ - // FIXME: as we know in advance how big a packet is (syt_interval) we can - // predict how much loops will be present here - for(j = (dbc & 0x07)+mp->getLocation(); j < nevents; j += 8) { - target_event=(quadlet_t *)(data + ((j * m_dimension) + mp->getPosition())); - quadlet_t sample_int=ntohl(*target_event); - // FIXME: this assumes that 2X and 3X speed isn't used, - // because only the 1X slot is put into the ringbuffer - if(IEC61883_AM824_GET_LABEL(sample_int) != IEC61883_AM824_LABEL_MIDI_NO_DATA) { - sample_int=(sample_int >> 16) & 0x000000FF; - if(!mp->writeEvent(&sample_int)) { - debugWarning("Packet port events lost\n"); - ok=false; - } - } - } - - } - - return ok; -} - -int AmdtpReceiveStreamProcessor::decodeMBLAEventsToPort(AmdtpAudioPort *p, quadlet_t *data, - unsigned int offset, unsigned int nevents) -{ - unsigned int j=0; - -// printf("****************\n"); -// hexDumpQuadlets(data,m_dimension*4); -// printf("****************\n"); - - quadlet_t *target_event; - - target_event=(quadlet_t *)(data + p->getPosition()); - - switch(p->getDataType()) { - default: - case Port::E_Int24: - { - quadlet_t *buffer=(quadlet_t *)(p->getBufferAddress()); - - assert(nevents + offset <= p->getBufferSize()); - - buffer+=offset; - - for(j = 0; j < nevents; j += 1) { // decode max nsamples - *(buffer)=(ntohl((*target_event) ) & 0x00FFFFFF); - buffer++; - target_event+=m_dimension; - } - } - break; - case Port::E_Float: - { - const float multiplier = 1.0f / (float)(0x7FFFFF); - float *buffer=(float *)(p->getBufferAddress()); - - assert(nevents + offset <= p->getBufferSize()); - - buffer+=offset; - - for(j = 0; j < nevents; j += 1) { // decode max nsamples - - unsigned int v = ntohl(*target_event) & 0x00FFFFFF; - // sign-extend highest bit of 24-bit int - int tmp = (int)(v << 8) / 256; - - *buffer = tmp * multiplier; - - buffer++; - target_event+=m_dimension; - } - } - break; - } - - return 0; -} - -} // end of namespace Streaming Index: /anches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpStreamProcessor.h =================================================================== --- /branches/ppalmers-streaming/src/libstreaming/amdtp/AmdtpStreamProcessor.h (revision 709) +++ (revision ) @@ -1,218 +1,0 @@ -/* - * Copyright (C) 2005-2007 by Pieter Palmers - * - * This file is part of FFADO - * FFADO = Free Firewire (pro-)audio drivers for linux - * - * FFADO is based upon FreeBoB. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License version 2.1, as published by the Free Software Foundation; - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#ifndef __FFADO_AMDTPSTREAMPROCESSOR__ -#define __FFADO_AMDTPSTREAMPROCESSOR__ - -/** - * This class implements IEC61883-6 / AM824 / AMDTP based streaming - */ - -#include "debugmodule/debugmodule.h" - -#include "../generic/StreamProcessor.h" -#include "../util/cip.h" - -#include -#include - -#define AMDTP_MAX_PACKET_SIZE 2048 - -#define IEC61883_STREAM_TYPE_MIDI 0x0D -#define IEC61883_STREAM_TYPE_SPDIF 0x00 -#define IEC61883_STREAM_TYPE_MBLA 0x06 - -#define IEC61883_AM824_LABEL_MASK 0xFF000000 -#define IEC61883_AM824_GET_LABEL(x) (((x) & 0xFF000000) >> 24) -#define IEC61883_AM824_SET_LABEL(x,y) ((x) | ((y)<<24)) - -#define IEC61883_AM824_LABEL_MIDI_NO_DATA 0x80 -#define IEC61883_AM824_LABEL_MIDI_1X 0x81 -#define IEC61883_AM824_LABEL_MIDI_2X 0x82 -#define IEC61883_AM824_LABEL_MIDI_3X 0x83 - -namespace Streaming { - -class Port; -class AmdtpAudioPort; -class AmdtpMidiPort; -class AmdtpReceiveStreamProcessor; - -/*! -\brief The Base Class for an AMDTP transmit stream processor - - This class implements a TransmitStreamProcessor that multiplexes Ports - into AMDTP streams. - -*/ -class AmdtpTransmitStreamProcessor - : public TransmitStreamProcessor -{ - -public: - /** - * Create a AMDTP transmit StreamProcessor - * @param port 1394 port - * @param framerate frame rate - * @param dimension number of substreams in the ISO stream - * (midi-muxed is only one stream) - */ - AmdtpTransmitStreamProcessor(int port, int framerate, int dimension); - virtual ~AmdtpTransmitStreamProcessor() {}; - - enum raw1394_iso_disposition - getPacket(unsigned char *data, unsigned int *length, - unsigned char *tag, unsigned char *sy, - int cycle, unsigned int dropped, unsigned int max_length); - - bool init(); - bool reset(); - bool prepare(); - - bool prepareForStop(); - bool prepareForStart(); - - bool prepareForEnable(uint64_t time_to_enable_at); - - bool putFrames(unsigned int nbframes, int64_t ts); ///< transfer the buffer contents from the client - bool putFramesDry(unsigned int nbframes, int64_t ts); - - // We have 1 period of samples = m_period - // this period takes m_period/m_framerate seconds of time - // during this time, 8000 packets are sent -// unsigned int getPacketsPerPeriod() {return (m_period*8000)/m_framerate;}; - - // however, if we only count the number of used packets - // it is m_period / m_syt_interval - unsigned int getPacketsPerPeriod() {return (m_period)/m_syt_interval;}; - - unsigned int getMaxPacketSize() {return 4 * (2 + m_syt_interval * m_dimension);}; - -protected: - bool processWriteBlock(char *data, unsigned int nevents, unsigned int offset); - - struct iec61883_cip m_cip_status; - - int m_dimension; - unsigned int m_syt_interval; - - int m_fdf; - - bool prefill(); - - unsigned int fillNoDataPacketHeader(struct iec61883_packet *packet, unsigned int* length); - unsigned int fillDataPacketHeader(struct iec61883_packet *packet, unsigned int* length, uint32_t ts); - - - bool transferSilence(unsigned int size); - - int transmitBlock(char *data, unsigned int nevents, - unsigned int offset); - - bool encodePacketPorts(quadlet_t *data, unsigned int nevents, unsigned int dbc); - int encodePortToMBLAEvents(AmdtpAudioPort *, quadlet_t *data, - unsigned int offset, unsigned int nevents); - - int transmitSilenceBlock(char *data, unsigned int nevents, - unsigned int offset); - int encodeSilencePortToMBLAEvents(AmdtpAudioPort *, quadlet_t *data, - unsigned int offset, unsigned int nevents); - void updatePreparedState(); - - unsigned long m_last_timestamp; - - unsigned int m_dbc; - - unsigned int m_ringbuffer_size_frames; -}; -/*! -\brief The Base Class for an AMDTP receive stream processor - - This class implements a ReceiveStreamProcessor that demultiplexes - AMDTP streams into Ports. - -*/ -class AmdtpReceiveStreamProcessor - : public ReceiveStreamProcessor -{ - -public: - /** - * Create a AMDTP receive StreamProcessor - * @param port 1394 port - * @param framerate frame rate - * @param dimension number of substreams in the ISO stream - * (midi-muxed is only one stream) - */ - AmdtpReceiveStreamProcessor(int port, int framerate, int dimension); - virtual ~AmdtpReceiveStreamProcessor() {}; - - enum raw1394_iso_disposition putPacket(unsigned char *data, unsigned int length, - unsigned char channel, unsigned char tag, unsigned char sy, - unsigned int cycle, unsigned int dropped); - - - bool init(); - bool reset(); - bool prepare(); - - bool prepareForStop(); - bool prepareForStart(); - - bool getFrames(unsigned int nbframes, int64_t ts); ///< transfer the buffer contents to the client - bool getFramesDry(unsigned int nbframes, int64_t ts); - - // We have 1 period of samples = m_period - // this period takes m_period/m_framerate seconds of time - // during this time, 8000 packets are sent -// unsigned int getPacketsPerPeriod() {return (m_period*8000)/m_framerate;}; - - // however, if we only count the number of used packets - // it is m_period / m_syt_interval - unsigned int getPacketsPerPeriod() {return (m_period)/m_syt_interval;}; - - unsigned int getMaxPacketSize() {return 4 * (2 + m_syt_interval * m_dimension);}; - - void dumpInfo(); -protected: - - bool processReadBlock(char *data, unsigned int nevents, unsigned int offset); - - bool decodePacketPorts(quadlet_t *data, unsigned int nevents, unsigned int dbc); - - int decodeMBLAEventsToPort(AmdtpAudioPort *, quadlet_t *data, unsigned int offset, unsigned int nevents); - void updatePreparedState(); - - int m_dimension; - unsigned int m_syt_interval; - - uint64_t m_last_timestamp; /// last timestamp (in ticks) - uint64_t m_last_timestamp2; /// last timestamp (in ticks) - uint64_t m_last_timestamp_at_period_ticks; -}; - - -} // end of namespace Streaming - -#endif /* __FFADO_AMDTPSTREAMPROCESSOR__ */ - Index: /branches/ppalmers-streaming/src/libstreaming/StreamProcessorManager.cpp =================================================================== --- /branches/ppalmers-streaming/src/libstreaming/StreamProcessorManager.cpp (revision 709) +++ /branches/ppalmers-streaming/src/libstreaming/StreamProcessorManager.cpp (revision 712) @@ -341,5 +341,7 @@ m_SyncSource->setSyncDelay(max_of_min_delay); - + debugOutput( DEBUG_LEVEL_VERBOSE, "Waiting for device to indicate clock sync lock...\n"); + //sleep(2); // FIXME: be smarter here + debugOutput( DEBUG_LEVEL_VERBOSE, "Resetting StreamProcessors...\n"); // now we reset the frame counters @@ -365,12 +367,13 @@ // m_SyncSource->m_data_buffer->setTransparent(false); // debugShowBackLog(); + +// m_SyncSource->setVerboseLevel(DEBUG_LEVEL_ULTRA_VERBOSE); debugOutput( DEBUG_LEVEL_VERBOSE, "Waiting for sync...\n"); // in order to obtain that, we wait for the first periods to be // received. - int nb_sync_runs=10; + int nb_sync_runs=20; while(nb_sync_runs--) { // or while not sync-ed? waitForPeriod(); - // drop the frames for all receive SP's dryRun(StreamProcessor::E_Receive); @@ -378,5 +381,14 @@ // we don't have to dryrun for the xmit SP's since they // are not sending data yet. - } + + // sync the xmit SP's buffer head timestamps + for ( StreamProcessorVectorIterator it = m_TransmitProcessors.begin(); + it != m_TransmitProcessors.end(); + ++it ) { + // FIXME: encapsulate + (*it)->m_data_buffer->setBufferHeadTimestamp(m_time_of_transfer); + } + } +// m_SyncSource->setVerboseLevel(DEBUG_LEVEL_VERBOSE); debugOutput( DEBUG_LEVEL_VERBOSE, " sync at TS=%011llu (%03us %04uc %04ut)...\n", @@ -433,5 +445,5 @@ debugOutput( DEBUG_LEVEL_VERBOSE, "Running dry for a while...\n"); - #define MAX_DRYRUN_CYCLES 20 + #define MAX_DRYRUN_CYCLES 40 #define MIN_SUCCESSFUL_DRYRUN_CYCLES 4 // run some cycles 'dry' such that everything can stabilize @@ -442,4 +454,64 @@ waitForPeriod(); + + if (dryRun()) { + nb_succesful_cycles++; + } else { + debugOutput( DEBUG_LEVEL_VERBOSE, " This dry-run was not xrun free...\n" ); + resetXrunCounters(); + // reset the transmit SP's such that there is no issue with accumulating buffers + // FIXME: what about receive SP's + for ( StreamProcessorVectorIterator it = m_TransmitProcessors.begin(); + it != m_TransmitProcessors.end(); + ++it ) { + // FIXME: encapsulate + (*it)->reset(); //CHECK!!! + (*it)->m_data_buffer->setBufferHeadTimestamp(m_time_of_transfer); + } + + nb_succesful_cycles = 0; + // FIXME: xruns can screw up the framecounter accounting. do something more sane here + } + nb_dryrun_cycles_left--; + } + + if(nb_dryrun_cycles_left == 0) { + debugOutput( DEBUG_LEVEL_VERBOSE, " max # dry-run cycles achieved without steady-state...\n" ); + return false; + } + debugOutput( DEBUG_LEVEL_VERBOSE, " dry-run resulted in steady-state...\n" ); + + // now we should clear the xrun flags + resetXrunCounters(); + +/* debugOutput( DEBUG_LEVEL_VERBOSE, "Aligning streams...\n"); + // run some cycles 'dry' such that everything can stabilize + nb_dryrun_cycles_left = MAX_DRYRUN_CYCLES; + nb_succesful_cycles = 0; + while(nb_dryrun_cycles_left > 0 && + nb_succesful_cycles < MIN_SUCCESSFUL_DRYRUN_CYCLES ) { + + waitForPeriod(); + + // align the received streams + int64_t sp_lag; + for ( StreamProcessorVectorIterator it = m_ReceiveProcessors.begin(); + it != m_ReceiveProcessors.end(); + ++it ) { + uint64_t ts_sp=(*it)->getTimeAtPeriod(); + uint64_t ts_sync=m_SyncSource->getTimeAtPeriod(); + + sp_lag = diffTicks(ts_sp, ts_sync); + debugOutput( DEBUG_LEVEL_VERBOSE, " SP(%p) TS=%011llu - TS=%011llu = %04lld\n", + (*it), ts_sp, ts_sync, sp_lag); + // sync the other receive SP's to the sync source +// if((*it) != m_SyncSource) { +// if(!(*it)->m_data_buffer->syncCorrectLag(sp_lag)) { +// debugOutput(DEBUG_LEVEL_VERBOSE,"could not syncCorrectLag(%11lld) for stream processor (%p)\n", +// sp_lag, *it); +// } +// } + } + if (dryRun()) { @@ -455,12 +527,11 @@ if(nb_dryrun_cycles_left == 0) { - debugOutput( DEBUG_LEVEL_VERBOSE, " max # dry-run cycles achieved without steady-state...\n" ); - return false; - } - debugOutput( DEBUG_LEVEL_VERBOSE, " dry-run resulted in steady-state...\n" ); - + debugOutput( DEBUG_LEVEL_VERBOSE, " max # dry-run cycles achieved without aligned steady-state...\n" ); + return false; + } + debugOutput( DEBUG_LEVEL_VERBOSE, " dry-run resulted in aligned steady-state...\n" );*/ + // now we should clear the xrun flags resetXrunCounters(); - // and off we go return true; @@ -913,5 +984,5 @@ // NOTE: before waitForPeriod() is called again, both the transmit // and the receive processors should have done their transfer. - m_time_of_transfer=m_SyncSource->getTimeAtPeriod(); + m_time_of_transfer = m_SyncSource->getTimeAtPeriod(); debugOutput( DEBUG_LEVEL_VERY_VERBOSE, "transfer at %llu ticks...\n", m_time_of_transfer); Index: /branches/ppalmers-streaming/src/libstreaming/generic/StreamProcessor.h =================================================================== --- /branches/ppalmers-streaming/src/libstreaming/generic/StreamProcessor.h (revision 709) +++ /branches/ppalmers-streaming/src/libstreaming/generic/StreamProcessor.h (revision 712) @@ -107,5 +107,4 @@ virtual bool prepareForStart() {return true;}; - public: Util::TimestampedBuffer *m_data_buffer; @@ -234,4 +233,5 @@ int getLastCycle() {return m_last_cycle;}; + int getFrameRate() {return m_framerate;}; int getBufferFill(); Index: /branches/ppalmers-streaming/src/libstreaming/generic/StreamProcessor.cpp =================================================================== --- /branches/ppalmers-streaming/src/libstreaming/generic/StreamProcessor.cpp (revision 709) +++ /branches/ppalmers-streaming/src/libstreaming/generic/StreamProcessor.cpp (revision 712) @@ -300,4 +300,5 @@ IsoStream::setVerboseLevel(l); PortManager::setVerboseLevel(l); + m_data_buffer->setVerboseLevel(l); } Index: /branches/ppalmers-streaming/src/libstreaming/util/cycletimer.h =================================================================== --- /branches/ppalmers-streaming/src/libstreaming/util/cycletimer.h (revision 707) +++ /branches/ppalmers-streaming/src/libstreaming/util/cycletimer.h (revision 712) @@ -156,4 +156,32 @@ return x; +} + +/** + * @brief Computes the sum of two cycle values + * + * This function computes a sum between cycles + * such that it respects wrapping (at 8000 cycles). + * + * The passed arguments are assumed to be valid cycle numbers, + * i.e. they should be wrapped at 8000 cycles + * + * See addTicks + * + * @param x First cycle value + * @param y Second cycle value + * @return the sum x+y, wrapped + */ +static inline unsigned int addCycles(unsigned int x, unsigned int y) { + unsigned int sum = x + y; +#ifdef DEBUG + if (x >= CYCLES_PER_SECOND || y >= CYCLES_PER_SECOND ) { + debugWarning("At least one argument not wrapped correctly: x=%u, y=%u\n",x,y); + } +#endif + + // since both x and y are < CYCLES_PER_SECOND this should be enough to unwrap + if (sum > CYCLES_PER_SECOND) sum -= CYCLES_PER_SECOND; + return sum; } Index: /branches/ppalmers-streaming/src/SConscript =================================================================== --- /branches/ppalmers-streaming/src/SConscript (revision 705) +++ /branches/ppalmers-streaming/src/SConscript (revision 712) @@ -153,5 +153,6 @@ libstreaming/amdtp/AmdtpPort.cpp \ libstreaming/amdtp/AmdtpPortInfo.cpp \ - libstreaming/amdtp/AmdtpStreamProcessor.cpp \ + libstreaming/amdtp/AmdtpReceiveStreamProcessor.cpp \ + libstreaming/amdtp/AmdtpTransmitStreamProcessor.cpp \ ' ) Index: /branches/ppalmers-streaming/src/dice/dice_avdevice.h =================================================================== --- /branches/ppalmers-streaming/src/dice/dice_avdevice.h (revision 705) +++ /branches/ppalmers-streaming/src/dice/dice_avdevice.h (revision 712) @@ -30,5 +30,6 @@ #include "libavc/avc_definitions.h" -#include "libstreaming/amdtp/AmdtpStreamProcessor.h" +#include "libstreaming/amdtp/AmdtpReceiveStreamProcessor.h" +#include "libstreaming/amdtp/AmdtpTransmitStreamProcessor.h" #include "libstreaming/amdtp/AmdtpPort.h" #include "libieee1394/ARMHandler.h" Index: /branches/ppalmers-streaming/src/dice/dice_avdevice.cpp =================================================================== --- /branches/ppalmers-streaming/src/dice/dice_avdevice.cpp (revision 705) +++ /branches/ppalmers-streaming/src/dice/dice_avdevice.cpp (revision 712) @@ -28,6 +28,4 @@ #include "libieee1394/ieee1394service.h" -#include "libstreaming/amdtp/AmdtpStreamProcessor.h" - #include "debugmodule/debugmodule.h" @@ -51,4 +49,5 @@ // vendor id, model id, vendor name, model name {FW_VENDORID_TCAT, 0x00000002, "TCAT", "DiceII EVM"}, + {FW_VENDORID_TCAT, 0x00000004, "TCAT", "DiceII EVM (vxx)"}, }; Index: /branches/ppalmers-streaming/src/libutil/TimestampedBuffer.cpp =================================================================== --- /branches/ppalmers-streaming/src/libutil/TimestampedBuffer.cpp (revision 707) +++ /branches/ppalmers-streaming/src/libutil/TimestampedBuffer.cpp (revision 712) @@ -152,5 +152,5 @@ debugWarning("(%p) rate (%10.5f) more that 10%% off nominal (rate=%10.5f, diff="TIMESTAMP_FORMAT_SPEC", update_period=%d)\n", this, rate,m_nominal_rate,diff, m_update_period); - dumpInfo(); + //dumpInfo(); return m_nominal_rate; } else { @@ -403,6 +403,8 @@ } else { // add the data payload to the ringbuffer - if (ffado_ringbuffer_write(m_event_buffer,data,write_size) < write_size) + size_t written = ffado_ringbuffer_write(m_event_buffer, data, write_size); + if (written < write_size) { + debugWarning("ringbuffer full, %u, %u\n", write_size, written); return false; } @@ -710,5 +712,5 @@ EXIT_CRITICAL_SECTION; - debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "for (%p) to " + debugOutput(DEBUG_LEVEL_VERBOSE, "for (%p) to " TIMESTAMP_FORMAT_SPEC" => "TIMESTAMP_FORMAT_SPEC", NTS=" TIMESTAMP_FORMAT_SPEC", DLL2=%f, RATE=%f\n", @@ -761,4 +763,178 @@ this, new_timestamp, ts, m_buffer_next_tail_timestamp, m_dll_e2, getRate()); +} + +/** + * @brief Synchronize the buffer head to a specified timestamp + * + * Try to synchronize the buffer head to a specific timestamp. This + * can mean adding or removing samples to/from the buffer such that + * the buffer head aligns with the specified timestamp. The alignment + * is within ts +/- Tsample/2 + * + * @param target the timestamp to align to + * @return true if alignment succeeded, false if not + */ +bool +TimestampedBuffer::syncBufferHeadToTimestamp(ffado_timestamp_t target) +{ + uint64_t ts_head; + uint64_t ts_target=(uint64_t)target; + signed int fc; + int32_t lag_ticks; + float lag_frames; + + ffado_timestamp_t ts_head_tmp; + getBufferHeadTimestamp(&ts_head_tmp, &fc); + ts_head=(uint64_t)ts_head_tmp; + // if target > ts_head then the wanted buffer head timestamp + // is later than the actual. This means that we (might) have to drop + // some frames. + lag_ticks=diffTicks(ts_target, ts_head); + float rate=getRate(); + + assert(rate!=0.0); + + lag_frames=(((float)lag_ticks)/rate); + + debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): HEAD=%llu, TS=%llu, diff=%ld = %10.5f frames (rate=%10.5f)\n", + this, ts_head, ts_target, lag_ticks, lag_frames, rate); + + if (lag_frames>=1.0) { + // the buffer head is too early + // ditch frames until the buffer head is on time + char dummy[getBytesPerFrame()]; // one frame of garbage + int frames_to_ditch=(int)roundf(lag_frames); + debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): ditching %d frames (@ ts=%lld)\n",this,frames_to_ditch,ts_target); + + while (frames_to_ditch--) { + readFrames(1, dummy); + } + + } else if (lag_frames<=-1.0) { + // the buffer head is too late + // add some padding frames + int frames_to_add=(int)roundf(lag_frames); + debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): adding %d frames (@ ts=%lld)\n",this,-frames_to_add,ts_target); + + while (frames_to_add++) { + writeDummyFrame(); + } + } + getBufferHeadTimestamp(&ts_head_tmp, &fc); + ts_head=(uint64_t)ts_head_tmp; + debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): new HEAD=%llu, fc=%d, target=%llu, new diff=%lld\n", + this, ts_head, fc, ts_target, diffTicks(ts_target, ts_head)); + // FIXME: of course this doesn't always succeed + return true; +} + +/** + * @brief Synchronize the buffer tail to a specified timestamp + * + * Try to synchronize the buffer tail to a specific timestamp. This + * can mean adding or removing samples to/from the buffer such that + * the buffer tail aligns with the specified timestamp. The alignment + * is within ts +/- Tsample/2 + * + * @param target the timestamp to align to + * @return true if alignment succeeded, false if not + */ +bool +TimestampedBuffer::syncBufferTailToTimestamp(ffado_timestamp_t target) +{ + uint64_t ts_tail; + uint64_t ts_target=(uint64_t)target; + signed int fc; + int32_t lag_ticks; + float lag_frames; + + debugWarning("Untested\n"); + + ffado_timestamp_t ts_tail_tmp; + getBufferTailTimestamp(&ts_tail_tmp, &fc); + ts_tail=(uint64_t)ts_tail_tmp; + // if target < ts_tail then the wanted buffer head timestamp + // is later than the actual. This means that we (might) have to drop + // some frames. + lag_ticks=diffTicks(ts_tail, ts_target); + float rate=getRate(); + + assert(rate!=0.0); + + lag_frames=(((float)lag_ticks)/rate); + + debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): HEAD=%llu, TS=%llu, diff=%ld = %10.5f frames (rate=%10.5f)\n", + this, ts_tail, ts_target, lag_ticks, lag_frames, rate); + + if (lag_frames>=1.0) { + // the buffer head is too early + // ditch frames until the buffer head is on time + char dummy[getBytesPerFrame()]; // one frame of garbage + int frames_to_ditch=(int)roundf(lag_frames); + debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): ditching %d frames (@ ts=%lld)\n",this,frames_to_ditch,ts_target); + + while (frames_to_ditch--) { + readFrames(1, dummy); + } + + } else if (lag_frames<=-1.0) { + // the buffer head is too late + // add some padding frames + int frames_to_add=(int)roundf(lag_frames); + debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): adding %d frames (@ ts=%lld)\n",this,-frames_to_add,ts_target); + + while (frames_to_add++) { + writeDummyFrame(); + } + } + getBufferHeadTimestamp(&ts_tail_tmp, &fc); + ts_tail=(uint64_t)ts_tail_tmp; + debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): new HEAD=%llu, fc=%d, target=%llu, new diff=%lld\n", + this, ts_tail, fc, ts_target, diffTicks(ts_target, ts_tail)); + // FIXME: of course this doesn't always succeed + return true; +} + +/** + * @brief correct lag + * + * Try to synchronize the buffer tail to a specific timestamp. This + * can mean adding or removing samples to/from the buffer such that + * the buffer tail aligns with the specified timestamp. The alignment + * is within ts +/- Tsample/2 + * + * @param target the timestamp to align to + * @return true if alignment succeeded, false if not + */ +bool +TimestampedBuffer::syncCorrectLag(int64_t lag_ticks) +{ + float lag_frames; + float rate=getRate(); + assert(rate!=0.0); + + lag_frames=(((float)lag_ticks)/rate); + if (lag_frames >= 1.0) { + // the buffer head is too late + // add some padding frames + int frames_to_add=(int)roundf(lag_frames); + debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): adding %d frames\n",this,frames_to_add); + + while (frames_to_add++) { + writeDummyFrame(); + } + } else if (lag_frames <= -1.0) { + // the buffer head is too early + // ditch frames until the buffer head is on time + char dummy[getBytesPerFrame()]; // one frame of garbage + int frames_to_ditch=(int)roundf(lag_frames); + debugOutput( DEBUG_LEVEL_VERBOSE, "(%p): ditching %d frames\n",this,-frames_to_ditch); + + while (frames_to_ditch--) { + readFrames(1, dummy); + } + } + return true; } @@ -920,49 +1096,47 @@ // return; // } - - // update the DLL - ENTER_CRITICAL_SECTION; - diff = ts-m_buffer_next_tail_timestamp; - EXIT_CRITICAL_SECTION; + ffado_timestamp_t pred_buffer_next_tail_timestamp; + if(nbframes == m_update_period) { + pred_buffer_next_tail_timestamp = m_buffer_next_tail_timestamp; + } else { + debugOutput( DEBUG_LEVEL_VERBOSE, + "Number of frames (%u) != update period (%u)\n", + nbframes, m_update_period ); + // calculate the predicted timestamp for nframes (instead of m_update_period) + // after the previous update. + float rel_step = ((float)nbframes)/((float)m_update_period); + ENTER_CRITICAL_SECTION; // FIXME: do we need these? + ffado_timestamp_t corrected_step = (m_buffer_next_tail_timestamp - m_buffer_tail_timestamp) * rel_step; + pred_buffer_next_tail_timestamp = m_buffer_tail_timestamp + corrected_step; + EXIT_CRITICAL_SECTION; + + debugOutput( DEBUG_LEVEL_VERBOSE, + "Updated ("TIMESTAMP_FORMAT_SPEC","TIMESTAMP_FORMAT_SPEC") to ("TIMESTAMP_FORMAT_SPEC","TIMESTAMP_FORMAT_SPEC")\n", + m_buffer_tail_timestamp, m_buffer_next_tail_timestamp, + m_buffer_tail_timestamp, pred_buffer_next_tail_timestamp); + + } + + // the difference between the given TS and the one predicted for this time instant + // this is the error for the DLL + diff = ts - pred_buffer_next_tail_timestamp; // check whether the update is within the allowed bounds - const float max_deviation = (100.0/100.0); // maximal relative difference considered normal - ffado_timestamp_t expected_difference=m_update_period * getRate(); - ffado_timestamp_t max_abs_diff = expected_difference * max_deviation; + const float max_deviation = (50.0/100.0); // maximal relative difference considered normal + ffado_timestamp_t one_update_step = nbframes * getRate(); + ffado_timestamp_t max_abs_diff = one_update_step * (1.0 + max_deviation); if (diff > max_abs_diff) { - debugWarning("(%p) difference rather large (+): diff="TIMESTAMP_FORMAT_SPEC", expected="TIMESTAMP_FORMAT_SPEC", "TIMESTAMP_FORMAT_SPEC", "TIMESTAMP_FORMAT_SPEC"\n", - this, diff, expected_difference, ts, m_buffer_next_tail_timestamp); + debugWarning("(%p) difference rather large (+): diff="TIMESTAMP_FORMAT_SPEC", max="TIMESTAMP_FORMAT_SPEC", "TIMESTAMP_FORMAT_SPEC", "TIMESTAMP_FORMAT_SPEC"\n", + this, diff, max_abs_diff, ts, pred_buffer_next_tail_timestamp); // debugShowBackLogLines(40); - - // we can limit the difference - // we can't discard it because that would prevent us from tracking the samplerate - diff = max_abs_diff; - } else if (diff < -max_abs_diff) { - debugWarning("(%p) difference rather large (-): diff="TIMESTAMP_FORMAT_SPEC", expected="TIMESTAMP_FORMAT_SPEC", "TIMESTAMP_FORMAT_SPEC", "TIMESTAMP_FORMAT_SPEC"\n", - this, diff, expected_difference, ts, m_buffer_next_tail_timestamp); + debugWarning("(%p) difference rather large (-): diff="TIMESTAMP_FORMAT_SPEC", max="TIMESTAMP_FORMAT_SPEC", "TIMESTAMP_FORMAT_SPEC", "TIMESTAMP_FORMAT_SPEC"\n", + this, diff, -max_abs_diff, ts, pred_buffer_next_tail_timestamp); // debugShowBackLogLines(40); - - // we can limit the difference - // we can't discard it because that would prevent us from tracking the samplerate - diff = -max_abs_diff; - } - - // idea to implement it for nbframes values that differ from m_update_period: - // diff = diff * nbframes/m_update_period - // m_buffer_next_tail_timestamp = m_buffer_tail_timestamp + diff + } debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "(%p): diff="TIMESTAMP_FORMAT_SPEC" ", this, diff); - - // the maximal difference we can allow (64secs) -// const ffado_timestamp_t max=m_wrap_at/2; -// -// if(diff > max) { -// diff -= m_wrap_at; -// } else if (diff < -max) { -// diff += m_wrap_at; -// } double err=diff; @@ -971,11 +1145,11 @@ diff, err); debugOutput(DEBUG_LEVEL_VERY_VERBOSE, "B: FC=%10u, TS="TIMESTAMP_FORMAT_SPEC", NTS="TIMESTAMP_FORMAT_SPEC"\n", - m_framecounter, m_buffer_tail_timestamp, m_buffer_next_tail_timestamp); + m_framecounter, m_buffer_tail_timestamp, pred_buffer_next_tail_timestamp); ENTER_CRITICAL_SECTION; m_framecounter += nbframes; - m_buffer_tail_timestamp=m_buffer_next_tail_timestamp; - m_buffer_next_tail_timestamp += (ffado_timestamp_t)(m_dll_b * err + m_dll_e2); + m_buffer_tail_timestamp = pred_buffer_next_tail_timestamp; + m_buffer_next_tail_timestamp = pred_buffer_next_tail_timestamp + (ffado_timestamp_t)(m_dll_b * err + m_dll_e2); // m_buffer_tail_timestamp=ts; // m_buffer_next_tail_timestamp += (ffado_timestamp_t)(m_dll_b * err + m_dll_e2); Index: /branches/ppalmers-streaming/src/libutil/TimestampedBuffer.h =================================================================== --- /branches/ppalmers-streaming/src/libutil/TimestampedBuffer.h (revision 705) +++ /branches/ppalmers-streaming/src/libutil/TimestampedBuffer.h (revision 712) @@ -133,4 +133,9 @@ void setBufferHeadTimestamp(ffado_timestamp_t new_timestamp); + // sync related, also drops or add frames when necessary + bool syncBufferHeadToTimestamp(ffado_timestamp_t ts); + bool syncBufferTailToTimestamp(ffado_timestamp_t ts); + bool syncCorrectLag(int64_t ts); + ffado_timestamp_t getTimestampFromTail(int nframes); ffado_timestamp_t getTimestampFromHead(int nframes); Index: /branches/ppalmers-streaming/src/genericavc/avc_avdevice.h =================================================================== --- /branches/ppalmers-streaming/src/genericavc/avc_avdevice.h (revision 705) +++ /branches/ppalmers-streaming/src/genericavc/avc_avdevice.h (revision 712) @@ -34,5 +34,6 @@ #include "libavc/general/avc_plug.h" -#include "libstreaming/amdtp/AmdtpStreamProcessor.h" +#include "libstreaming/amdtp/AmdtpReceiveStreamProcessor.h" +#include "libstreaming/amdtp/AmdtpTransmitStreamProcessor.h" #include "libstreaming/amdtp/AmdtpPort.h" #include "libstreaming/amdtp/AmdtpPortInfo.h" Index: /branches/ppalmers-streaming/src/bebob/bebob_avdevice.h =================================================================== --- /branches/ppalmers-streaming/src/bebob/bebob_avdevice.h (revision 705) +++ /branches/ppalmers-streaming/src/bebob/bebob_avdevice.h (revision 712) @@ -42,5 +42,6 @@ #include "bebob/bebob_mixer.h" -#include "libstreaming/amdtp/AmdtpStreamProcessor.h" +#include "libstreaming/amdtp/AmdtpReceiveStreamProcessor.h" +#include "libstreaming/amdtp/AmdtpTransmitStreamProcessor.h" #include "libstreaming/amdtp/AmdtpPort.h" #include "libstreaming/amdtp/AmdtpPortInfo.h" Index: /branches/ppalmers-streaming/src/bounce/bounce_avdevice.h =================================================================== --- /branches/ppalmers-streaming/src/bounce/bounce_avdevice.h (revision 705) +++ /branches/ppalmers-streaming/src/bounce/bounce_avdevice.h (revision 712) @@ -30,5 +30,6 @@ #include "libavc/general/avc_extended_cmd_generic.h" -#include "libstreaming/amdtp/AmdtpStreamProcessor.h" +#include "libstreaming/amdtp/AmdtpReceiveStreamProcessor.h" +#include "libstreaming/amdtp/AmdtpTransmitStreamProcessor.h" #include "libstreaming/amdtp/AmdtpPort.h" #include "libstreaming/amdtp/AmdtpPortInfo.h" Index: /branches/ppalmers-streaming/src/maudio/maudio_avdevice.h =================================================================== --- /branches/ppalmers-streaming/src/maudio/maudio_avdevice.h (revision 705) +++ /branches/ppalmers-streaming/src/maudio/maudio_avdevice.h (revision 712) @@ -32,5 +32,6 @@ #include "bebob/bebob_avdevice.h" -#include "libstreaming/amdtp/AmdtpStreamProcessor.h" +#include "libstreaming/amdtp/AmdtpReceiveStreamProcessor.h" +#include "libstreaming/amdtp/AmdtpTransmitStreamProcessor.h" #include "libstreaming/amdtp/AmdtpPort.h" #include "libstreaming/amdtp/AmdtpPortInfo.h"