root/branches/libffado-2.0/src/bebob/bebob_avdevice.cpp

Revision 1198, 19.9 kB (checked in by wagi, 13 years ago)

Set clock source always to 'Device Controlled'. The FA-66 doesn't support
switching clock source by software.

It might be possible to read out the current setting though. This is
a open TODO.

Furthermore, re-enable device discovering. This fixes a previous checkin r1183.

Line 
1 /*
2  * Copyright (C) 2005-2008 by Daniel Wagner
3  *
4  * This file is part of FFADO
5  * FFADO = Free Firewire (pro-)audio drivers for linux
6  *
7  * FFADO is based upon FreeBoB
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 2 of the License, or
12  * (at your option) version 3 of the License.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  */
23
24 #include "config.h"
25
26 #include "bebob/bebob_avdevice.h"
27 #include "bebob/bebob_avdevice_subunit.h"
28 #include "bebob/bebob_mixer.h"
29
30 #include "bebob/focusrite/focusrite_saffire.h"
31 #include "bebob/focusrite/focusrite_saffirepro.h"
32 #include "bebob/terratec/terratec_device.h"
33 #include "bebob/edirol/edirol_fa101.h"
34 #include "bebob/edirol/edirol_fa66.h"
35 #include "bebob/esi/quatafire610.h"
36
37 #include "libieee1394/configrom.h"
38 #include "libieee1394/ieee1394service.h"
39
40 #include "genericavc/avc_vendormodel.h"
41
42 #include "libavc/general/avc_plug_info.h"
43 #include "libavc/general/avc_extended_plug_info.h"
44 #include "libavc/general/avc_subunit_info.h"
45 #include "libavc/streamformat/avc_extended_stream_format.h"
46 #include "libutil/cmd_serialize.h"
47 #include "libavc/avc_definitions.h"
48
49 #include "debugmodule/debugmodule.h"
50
51 #include <iostream>
52 #include <sstream>
53 #include <unistd.h>
54 #include <sys/stat.h>
55
56 using namespace AVC;
57
58 namespace BeBoB {
59
60 AvDevice::AvDevice( DeviceManager& d, std::auto_ptr< ConfigRom >( configRom ) )
61     : GenericAVC::AvDevice( d, configRom )
62     , m_Mixer ( 0 )
63 {
64     debugOutput( DEBUG_LEVEL_VERBOSE, "Created BeBoB::AvDevice (NodeID %d)\n",
65                  getConfigRom().getNodeId() );
66
67     // DM1500 based devices seem to upset the linux1394 stack when commands are
68     // sent too fast.
69     if (AVC::AVCCommand::getSleepAfterAVCCommand() < 200) {
70         AVC::AVCCommand::setSleepAfterAVCCommand( 200 );
71     }
72
73 }
74
75 AvDevice::~AvDevice()
76 {
77     destroyMixer();
78 }
79
80 bool
81 AvDevice::probe( ConfigRom& configRom )
82 {
83     unsigned int vendorId = configRom.getNodeVendorId();
84     unsigned int modelId = configRom.getModelId();
85
86     GenericAVC::VendorModel vendorModel( SHAREDIR "/ffado_driver_bebob.txt" );
87     if ( vendorModel.parse() ) {
88         return vendorModel.isPresent( vendorId, modelId );
89     }
90
91     return false;
92 }
93
94 FFADODevice *
95 AvDevice::createDevice(DeviceManager& d, std::auto_ptr<ConfigRom>( configRom ))
96 {
97     unsigned int vendorId = configRom->getNodeVendorId();
98     unsigned int modelId = configRom->getModelId();
99
100     switch (vendorId) {
101         case FW_VENDORID_EDIROL:
102             switch (modelId) {
103                 case 0x00010048:
104                     return new Edirol::EdirolFa101Device(d, configRom);
105                 case 0x00010049:
106                     return new Edirol::EdirolFa66Device(d, configRom);
107                 default:
108                     return new AvDevice(d, configRom);
109             }
110         case FW_VENDORID_ESI:
111             if (modelId == 0x00010064) {
112                 return new ESI::QuataFireDevice(d, configRom);
113             }
114             break;
115         case FW_VENDORID_TERRATEC:
116             switch(modelId) {
117                 case 0x00000003:
118                     return new Terratec::Phase88Device(d, configRom);
119                 default: // return a plain BeBoB device
120                     return new AvDevice(d, configRom);
121             }
122         case FW_VENDORID_FOCUSRITE:
123             switch(modelId) {
124                 case 0x00000003:
125                 case 0x00000006:
126                     return new Focusrite::SaffireProDevice(d, configRom);
127                 case 0x00000000:
128                     return new Focusrite::SaffireDevice(d, configRom);
129                 default: // return a plain BeBoB device
130                     return new AvDevice(d, configRom);
131            }
132         default:
133             return new AvDevice(d, configRom);
134     }
135     return NULL;
136 }
137
138 bool
139 AvDevice::discover()
140 {
141     unsigned int vendorId = getConfigRom().getNodeVendorId();
142     unsigned int modelId = getConfigRom().getModelId();
143
144     GenericAVC::VendorModel vendorModel( SHAREDIR "/ffado_driver_bebob.txt" );
145     if ( vendorModel.parse() ) {
146         m_model = vendorModel.find( vendorId, modelId );
147     }
148
149     if (GenericAVC::VendorModel::isValid(m_model)) {
150         debugOutput( DEBUG_LEVEL_VERBOSE, "found %s %s\n",
151                      m_model.vendor_name.c_str(),
152                      m_model.model_name.c_str());
153     } else return false;
154
155     if ( !Unit::discover() ) {
156         debugError( "Could not discover unit\n" );
157         return false;
158     }
159
160     if((getAudioSubunit( 0 ) == NULL)) {
161         debugError( "Unit doesn't have an Audio subunit.\n");
162         return false;
163     }
164     if((getMusicSubunit( 0 ) == NULL)) {
165         debugError( "Unit doesn't have a Music subunit.\n");
166         return false;
167     }
168
169     if(!buildMixer()) {
170         debugWarning("Could not build mixer\n");
171     }
172     return true;
173 }
174
175 bool
176 AvDevice::buildMixer()
177 {
178     debugOutput(DEBUG_LEVEL_VERBOSE, "Building a generic BeBoB mixer...\n");
179     // create a Mixer
180     // this removes the mixer if it already exists
181     // note: a mixer self-registers to it's parent
182     delete m_Mixer;
183
184     // create the mixer & register it
185     if(getAudioSubunit(0) == NULL) {
186         debugWarning("Could not find audio subunit, mixer not available.\n");
187         m_Mixer = NULL;
188     } else {
189         m_Mixer = new Mixer(*this);
190     }
191     if (m_Mixer) m_Mixer->setVerboseLevel(getDebugLevel());
192     return m_Mixer != NULL;
193 }
194
195 bool
196 AvDevice::destroyMixer()
197 {
198     delete m_Mixer;
199     return true;
200 }
201
202 bool
203 AvDevice::setSelectorFBValue(int id, int value) {
204     FunctionBlockCmd fbCmd( get1394Service(),
205                             FunctionBlockCmd::eFBT_Selector,
206                             id,
207                             FunctionBlockCmd::eCA_Current );
208     fbCmd.setNodeId( getNodeId() );
209     fbCmd.setSubunitId( 0x00 );
210     fbCmd.setCommandType( AVCCommand::eCT_Control );
211     fbCmd.m_pFBSelector->m_inputFbPlugNumber = (value & 0xFF);
212     fbCmd.setVerboseLevel( getDebugLevel() );
213
214     if ( !fbCmd.fire() ) {
215         debugError( "cmd failed\n" );
216         return false;
217     }
218
219 //     if ( getDebugLevel() >= DEBUG_LEVEL_NORMAL ) {
220 //         Util::CoutSerializer se;
221 //         fbCmd.serialize( se );
222 //     }
223 //     
224     if((fbCmd.getResponse() != AVCCommand::eR_Accepted)) {
225         debugWarning("fbCmd.getResponse() != AVCCommand::eR_Accepted\n");
226     }
227
228     return (fbCmd.getResponse() == AVCCommand::eR_Accepted);
229 }
230
231 int
232 AvDevice::getSelectorFBValue(int id) {
233
234     FunctionBlockCmd fbCmd( get1394Service(),
235                             FunctionBlockCmd::eFBT_Selector,
236                             id,
237                             FunctionBlockCmd::eCA_Current );
238     fbCmd.setNodeId( getNodeId() );
239     fbCmd.setSubunitId( 0x00 );
240     fbCmd.setCommandType( AVCCommand::eCT_Status );
241     fbCmd.m_pFBSelector->m_inputFbPlugNumber = 0xFF;
242     fbCmd.setVerboseLevel( getDebugLevel() );
243
244     if ( !fbCmd.fire() ) {
245         debugError( "cmd failed\n" );
246         return -1;
247     }
248    
249 //     if ( getDebugLevel() >= DEBUG_LEVEL_NORMAL ) {
250 //         Util::CoutSerializer se;
251 //         fbCmd.serialize( se );
252 //     }
253
254     if((fbCmd.getResponse() != AVCCommand::eR_Implemented)) {
255         debugWarning("fbCmd.getResponse() != AVCCommand::eR_Implemented\n");
256     }
257    
258     return fbCmd.m_pFBSelector->m_inputFbPlugNumber;
259 }
260
261 bool
262 AvDevice::setFeatureFBVolumeValue(int id, int channel, int v) {
263
264     FunctionBlockCmd fbCmd( get1394Service(),
265                             FunctionBlockCmd::eFBT_Feature,
266                             id,
267                             FunctionBlockCmd::eCA_Current );
268     fbCmd.setNodeId( getNodeId() );
269     fbCmd.setSubunitId( 0x00 );
270     fbCmd.setCommandType( AVCCommand::eCT_Control );
271     fbCmd.m_pFBFeature->m_audioChannelNumber = channel;
272     fbCmd.m_pFBFeature->m_controlSelector = FunctionBlockFeature::eCSE_Feature_Volume;
273     fbCmd.m_pFBFeature->m_pVolume->m_volume = v;
274     fbCmd.setVerboseLevel( getDebugLevel() );
275
276     if ( !fbCmd.fire() ) {
277         debugError( "cmd failed\n" );
278         return false;
279     }
280
281 //     if ( getDebugLevel() >= DEBUG_LEVEL_NORMAL ) {
282 //         Util::CoutSerializer se;
283 //         fbCmd.serialize( se );
284 //     }
285    
286     if((fbCmd.getResponse() != AVCCommand::eR_Accepted)) {
287         debugWarning("fbCmd.getResponse() != AVCCommand::eR_Accepted\n");
288     }
289
290     return (fbCmd.getResponse() == AVCCommand::eR_Accepted);
291 }
292
293 int
294 AvDevice::getFeatureFBVolumeValue(int id, int channel) {
295     FunctionBlockCmd fbCmd( get1394Service(),
296                             FunctionBlockCmd::eFBT_Feature,
297                             id,
298                             FunctionBlockCmd::eCA_Current );
299     fbCmd.setNodeId( getNodeId() );
300     fbCmd.setSubunitId( 0x00 );
301     fbCmd.setCommandType( AVCCommand::eCT_Status );
302     fbCmd.m_pFBFeature->m_audioChannelNumber = channel;
303     fbCmd.m_pFBFeature->m_controlSelector = FunctionBlockFeature::eCSE_Feature_Volume;
304     fbCmd.m_pFBFeature->m_pVolume->m_volume = 0;
305     fbCmd.setVerboseLevel( getDebugLevel() );
306
307     if ( !fbCmd.fire() ) {
308         debugError( "cmd failed\n" );
309         return 0;
310     }
311    
312 //     if ( getDebugLevel() >= DEBUG_LEVEL_NORMAL ) {
313 //         Util::CoutSerializer se;
314 //         fbCmd.serialize( se );
315 //     }
316
317     if((fbCmd.getResponse() != AVCCommand::eR_Implemented)) {
318         debugWarning("fbCmd.getResponse() != AVCCommand::eR_Implemented\n");
319     }
320    
321     int16_t volume=(int16_t)(fbCmd.m_pFBFeature->m_pVolume->m_volume);
322    
323     return volume;
324 }
325
326 void
327 AvDevice::showDevice()
328 {
329     debugOutput(DEBUG_LEVEL_NORMAL, "Device is a BeBoB device\n");
330     GenericAVC::AvDevice::showDevice();
331     flushDebugOutput();
332 }
333
334 void
335 AvDevice::setVerboseLevel(int l)
336 {
337     if (m_Mixer) m_Mixer->setVerboseLevel( l );
338     GenericAVC::AvDevice::setVerboseLevel( l );
339     debugOutput( DEBUG_LEVEL_VERBOSE, "Setting verbose level to %d...\n", l );
340 }
341
342 AVC::Subunit*
343 AvDevice::createSubunit(AVC::Unit& unit,
344                         AVC::ESubunitType type,
345                         AVC::subunit_t id )
346 {
347     AVC::Subunit* s=NULL;
348     switch (type) {
349         case eST_Audio:
350             s=new BeBoB::SubunitAudio(unit, id );
351             break;
352         case eST_Music:
353             s=new BeBoB::SubunitMusic(unit, id );
354             break;
355         default:
356             s=NULL;
357             break;
358     }
359     if(s) s->setVerboseLevel(getDebugLevel());
360     return s;
361 }
362
363
364 AVC::Plug *
365 AvDevice::createPlug( AVC::Unit* unit,
366                       AVC::Subunit* subunit,
367                       AVC::function_block_type_t functionBlockType,
368                       AVC::function_block_type_t functionBlockId,
369                       AVC::Plug::EPlugAddressType plugAddressType,
370                       AVC::Plug::EPlugDirection plugDirection,
371                       AVC::plug_id_t plugId )
372 {
373
374     Plug *p= new BeBoB::Plug( unit,
375                               subunit,
376                               functionBlockType,
377                               functionBlockId,
378                               plugAddressType,
379                               plugDirection,
380                               plugId );
381     if (p) p->setVerboseLevel(getDebugLevel());
382     return p;
383 }
384
385 bool
386 AvDevice::propagatePlugInfo() {
387     // we don't have to propagate since we discover things
388     // another way
389     debugOutput(DEBUG_LEVEL_VERBOSE, "Skip plug info propagation\n");
390     return true;
391 }
392
393
394 uint8_t
395 AvDevice::getConfigurationIdSampleRate()
396 {
397     ExtendedStreamFormatCmd extStreamFormatCmd( get1394Service() );
398     UnitPlugAddress unitPlugAddress( UnitPlugAddress::ePT_PCR, 0 );
399     extStreamFormatCmd.setPlugAddress( PlugAddress( PlugAddress::ePD_Input,
400                                                     PlugAddress::ePAM_Unit,
401                                                     unitPlugAddress ) );
402
403     extStreamFormatCmd.setNodeId( getNodeId() );
404     extStreamFormatCmd.setCommandType( AVCCommand::eCT_Status );
405     extStreamFormatCmd.setVerbose( getDebugLevel() );
406
407     if ( !extStreamFormatCmd.fire() ) {
408         debugError( "Stream format command failed\n" );
409         return false;
410     }
411
412     FormatInformation* formatInfo =
413         extStreamFormatCmd.getFormatInformation();
414     FormatInformationStreamsCompound* compoundStream
415         = dynamic_cast< FormatInformationStreamsCompound* > (
416             formatInfo->m_streams );
417     if ( compoundStream ) {
418         debugOutput(DEBUG_LEVEL_VERBOSE, "Sample rate 0x%02x\n",
419                     compoundStream->m_samplingFrequency );
420         return compoundStream->m_samplingFrequency;
421     }
422
423     debugError( "Could not retrieve sample rate\n" );
424     return 0;
425 }
426
427 uint8_t
428 AvDevice::getConfigurationIdNumberOfChannel( PlugAddress::EPlugDirection ePlugDirection )
429 {
430     ExtendedPlugInfoCmd extPlugInfoCmd( get1394Service() );
431     UnitPlugAddress unitPlugAddress( UnitPlugAddress::ePT_PCR,
432                                      getNodeId() );
433     extPlugInfoCmd.setPlugAddress( PlugAddress( ePlugDirection,
434                                                 PlugAddress::ePAM_Unit,
435                                                 unitPlugAddress ) );
436     extPlugInfoCmd.setNodeId( getNodeId() );
437     extPlugInfoCmd.setCommandType( AVCCommand::eCT_Status );
438     extPlugInfoCmd.setVerbose( getDebugLevel() );
439     ExtendedPlugInfoInfoType extendedPlugInfoInfoType(
440         ExtendedPlugInfoInfoType::eIT_NoOfChannels );
441     extendedPlugInfoInfoType.initialize();
442     extPlugInfoCmd.setInfoType( extendedPlugInfoInfoType );
443
444     if ( !extPlugInfoCmd.fire() ) {
445         debugError( "Number of channels command failed\n" );
446         return false;
447     }
448
449     ExtendedPlugInfoInfoType* infoType = extPlugInfoCmd.getInfoType();
450     if ( infoType
451          && infoType->m_plugNrOfChns )
452     {
453         debugOutput(DEBUG_LEVEL_VERBOSE, "Number of channels 0x%02x\n",
454                     infoType->m_plugNrOfChns->m_nrOfChannels );
455         return infoType->m_plugNrOfChns->m_nrOfChannels;
456     }
457
458     debugError( "Could not retrieve number of channels\n" );
459     return 0;
460 }
461
462 uint8_t
463 AvDevice::getConfigurationIdSyncMode()
464 {
465     SignalSourceCmd signalSourceCmd( get1394Service() );
466     SignalUnitAddress signalUnitAddr;
467     signalUnitAddr.m_plugId = 0x01;
468     signalSourceCmd.setSignalDestination( signalUnitAddr );
469     signalSourceCmd.setNodeId( getNodeId() );
470     signalSourceCmd.setSubunitType( eST_Unit  );
471     signalSourceCmd.setSubunitId( 0xff );
472     signalSourceCmd.setVerbose( getDebugLevel() );
473
474     signalSourceCmd.setCommandType( AVCCommand::eCT_Status );
475
476     if ( !signalSourceCmd.fire() ) {
477         debugError( "Signal source command failed\n" );
478         return false;
479     }
480
481     SignalAddress* pSyncPlugSignalAddress = signalSourceCmd.getSignalSource();
482     SignalSubunitAddress* pSyncPlugSubunitAddress
483         = dynamic_cast<SignalSubunitAddress*>( pSyncPlugSignalAddress );
484     if ( pSyncPlugSubunitAddress ) {
485         debugOutput(DEBUG_LEVEL_VERBOSE, "Sync mode 0x%02x\n",
486                     ( pSyncPlugSubunitAddress->m_subunitType << 3
487                       | pSyncPlugSubunitAddress->m_subunitId ) << 8
488                     | pSyncPlugSubunitAddress->m_plugId );
489
490         return ( pSyncPlugSubunitAddress->m_subunitType << 3
491                  | pSyncPlugSubunitAddress->m_subunitId ) << 8
492             | pSyncPlugSubunitAddress->m_plugId;
493     }
494
495     SignalUnitAddress* pSyncPlugUnitAddress
496       = dynamic_cast<SignalUnitAddress*>( pSyncPlugSignalAddress );
497     if ( pSyncPlugUnitAddress ) {
498         debugOutput(DEBUG_LEVEL_VERBOSE, "Sync mode 0x%02x\n",
499                       0xff << 8 | pSyncPlugUnitAddress->m_plugId );
500
501         return ( 0xff << 8 | pSyncPlugUnitAddress->m_plugId );
502     }
503
504     debugError( "Could not retrieve sync mode\n" );
505     return 0;
506 }
507
508 uint64_t
509 AvDevice::getConfigurationId()
510 {
511     // create a unique configuration id.
512     uint64_t id = 0;
513     id = getConfigurationIdSampleRate();
514     id |= ( getConfigurationIdNumberOfChannel( PlugAddress::ePD_Input )
515             + getConfigurationIdNumberOfChannel( PlugAddress::ePD_Output ) ) << 8;
516     id |= getConfigurationIdSyncMode() << 16;
517
518     return id;
519 }
520
521 bool
522 AvDevice::serialize( std::string basePath,
523                      Util::IOSerialize& ser ) const
524 {
525     bool result;
526     result  = GenericAVC::AvDevice::serialize( basePath, ser );
527     return result;
528 }
529
530 bool
531 AvDevice::deserialize( std::string basePath,
532                        Util::IODeserialize& deser )
533 {
534     bool result;
535     result  = GenericAVC::AvDevice::deserialize( basePath, deser );
536     return result;
537 }
538
539 std::string
540 AvDevice::getCachePath()
541 {
542     std::string cachePath;
543     char* pCachePath;
544
545     string path = CACHEDIR;
546     if ( path.size() && path[0] == '~' ) {
547         path.erase( 0, 1 ); // remove ~
548         path.insert( 0, getenv( "HOME" ) ); // prepend the home path
549     }
550
551     if ( asprintf( &pCachePath, "%s/cache/",  path.c_str() ) < 0 ) {
552         debugError( "Could not create path string for cache pool (trying '/var/cache/libffado' instead)\n" );
553         cachePath == "/var/cache/libffado/";
554     } else {
555         cachePath = pCachePath;
556         free( pCachePath );
557     }
558     return cachePath;
559 }
560
561 bool
562 AvDevice::loadFromCache()
563 {
564     std::string sDevicePath = getCachePath() + getConfigRom().getGuidString();
565
566     char* configId;
567     asprintf(&configId, "%016llx", getConfigurationId() );
568     if ( !configId ) {
569         debugError( "could not create id string\n" );
570         return false;
571     }
572
573     std::string sFileName = sDevicePath + "/" + configId + ".xml";
574     free( configId );
575     debugOutput( DEBUG_LEVEL_NORMAL, "filename %s\n", sFileName.c_str() );
576
577     struct stat buf;
578     if ( stat( sFileName.c_str(), &buf ) != 0 ) {
579         debugOutput( DEBUG_LEVEL_NORMAL,  "\"%s\" does not exist\n",  sFileName.c_str() );
580         return false;
581     } else {
582         if ( !S_ISREG( buf.st_mode ) ) {
583             debugOutput( DEBUG_LEVEL_NORMAL,  "\"%s\" is not a regular file\n",  sFileName.c_str() );
584             return false;
585         }
586     }
587
588     Util::XMLDeserialize deser( sFileName, getDebugLevel() );
589
590     if (!deser.isValid()) {
591         debugOutput( DEBUG_LEVEL_NORMAL, "cache not valid: %s\n",
592                      sFileName.c_str() );
593         return false;
594     }
595
596     bool result = deserialize( "", deser );
597     if ( result ) {
598         debugOutput( DEBUG_LEVEL_NORMAL, "could create valid bebob driver from %s\n",
599                      sFileName.c_str() );
600     }
601
602     if(result) {
603         buildMixer();
604     }
605
606     return result;
607 }
608
609 bool
610 AvDevice::saveCache()
611 {
612     // the path looks like this:
613     // PATH_TO_CACHE + GUID + CONFIGURATION_ID
614     string tmp_path = getCachePath() + getConfigRom().getGuidString();
615
616     // the following piece should do something like
617     // 'mkdir -p some/path/with/some/dirs/which/do/not/exist'
618     vector<string> tokens;
619     tokenize( tmp_path, tokens, "/" );
620     string path;
621     for ( vector<string>::const_iterator it = tokens.begin();
622           it != tokens.end();
623           ++it )
624     {
625         path +=  "/" + *it;
626
627         struct stat buf;
628         if ( stat( path.c_str(), &buf ) == 0 ) {
629             if ( !S_ISDIR( buf.st_mode ) ) {
630                 debugError( "\"%s\" is not a directory\n",  path.c_str() );
631                 return false;
632             }
633         } else {
634             if (  mkdir( path.c_str(), S_IRWXU | S_IRWXG ) != 0 ) {
635                 debugError( "Could not create \"%s\" directory\n", path.c_str() );
636                 return false;
637             }
638         }
639     }
640
641     // come up with an unique file name for the current settings
642     char* configId;
643     asprintf(&configId, "%016llx", BeBoB::AvDevice::getConfigurationId() );
644     if ( !configId ) {
645         debugError( "Could not create id string\n" );
646         return false;
647     }
648     string filename = path + "/" + configId + ".xml";
649     free( configId );
650     debugOutput( DEBUG_LEVEL_NORMAL, "filename %s\n", filename.c_str() );
651
652     Util::XMLSerialize ser( filename );
653     return serialize( "", ser );
654 }
655
656 } // end of namespace
Note: See TracBrowser for help on using the browser.