pix_record.cpp 13.8 KB
Newer Older
cclepper's avatar
cclepper committed
1
2
3
4
/*
 *  pix_record.cpp
 *
 */
5
#include "Base/GemConfig.h"
cclepper's avatar
cclepper committed
6
7
#include "pix_record.h"

8
#include "Base/GemState.h"
9
#include "Base/GemException.h"
10

cclepper's avatar
cclepper committed
11
12
CPPEXTERN_NEW_WITH_GIMME(pix_record)

13
14
15
16
17
18
struct pix_record :: PIMPL {
  PIMPL(void) {};
  ~PIMPL(void) {};

  struct codechandle {
    codechandle(gem::record*h, const std::string c):handle(h), codec(c) {}
zmoelnig's avatar
zmoelnig committed
19
    
20
    gem::record*handle;
zmoelnig's avatar
zmoelnig committed
21
    std::string codec;
22
  };
zmoelnig's avatar
zmoelnig committed
23
  std::map<std::string, std::vector<codechandle> >m_codechandle;
24
25
26
  std::vector<std::string>m_codecs;

  void addCodecHandle(gem::record*handle, const std::string codec) {
zmoelnig's avatar
zmoelnig committed
27
28
29
#warning better handling of duplicate codecs
    /* FIXME: we should generate a unique codec-ID, e.g. "<handlename>:<codec>" */
    m_codechandle[codec].push_back(codechandle(handle, codec));
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
    m_codecs.push_back(codec);
  }
  void clearCodecHandle(void) {
    m_codecs.clear();
    m_codechandle.clear();
  }

  static gem::any atom2any(t_atom*ap) {
    gem::any result;
    if(ap) {
      switch(ap->a_type) {
      case A_FLOAT:
	result=atom_getfloat(ap);
	break;
      case A_SYMBOL:
	result=atom_getsymbol(ap)->s_name;
	break;
      default:
	result=ap->a_w.w_gpointer;
      }
    }
    return result;
  }
  static void addProperties(gem::Properties&props, int argc, t_atom*argv)
  {
    if(!argc)return;
    
    if(argv->a_type != A_SYMBOL) {
      ::error("no key given...");
      return;
    }
    std::string key=std::string(atom_getsymbol(argv)->s_name);
    std::vector<gem::any> values;
    argc--; argv++;
    while(argc-->0) {
      values.push_back(atom2any(argv++));
    }
    switch(values.size()) {
    default:
      props.set(key, values);
      break;
    case 1:
      props.set(key, values[0]);
      break;
    case 0: 
      {
	gem::any dummy;
	props.set(key, dummy);
      }
      break;
    }
  }
  
};

85
/////////////////////////////////////////////////////////
cclepper's avatar
cclepper committed
86
87
88
89
90
91
92
//
// pix_record
//
/////////////////////////////////////////////////////////
// Constructor
//
/////////////////////////////////////////////////////////
zmoelnig's avatar
zmoelnig committed
93
pix_record :: pix_record(int argc, t_atom *argv):
94
95
  m_banged(false), m_automatic(true),
  m_outNumFrames(NULL), m_outInfo(NULL),
zmoelnig's avatar
zmoelnig committed
96
  m_currentFrame(-1),
zmoelnig's avatar
zmoelnig committed
97
  m_maxFrames(0),
98
99
100
  m_recording(false),
  m_handle(NULL),
  m_pimpl(new PIMPL())
cclepper's avatar
cclepper committed
101
{
102
103
  if (argc != 0){
    error("ignoring arugments");
cclepper's avatar
cclepper committed
104
  }
cclepper's avatar
cclepper committed
105
  m_outNumFrames = outlet_new(this->x_obj, 0);
106
  m_outInfo      = outlet_new(this->x_obj, 0);
cclepper's avatar
cclepper committed
107

108

109
110
111
112
113
  gem::PluginFactory<gem::record>::loadPlugins("record");
  std::vector<std::string>ids=gem::PluginFactory<gem::record>::getIDs();
  addHandle(ids, "QT");
  addHandle(ids, "QT4L");
  addHandle(ids);
114

115

zmoelnig's avatar
zmoelnig committed
116
  if(m_allhandles.size()>0) {
117
118
  } else {
    error("no video backends found!");
119
  }
120

zmoelnig's avatar
zmoelnig committed
121
122
  m_handles=m_allhandles;

123
  getCodecList();
cclepper's avatar
cclepper committed
124
125
126
127
128
129
130
131
}

/////////////////////////////////////////////////////////
// Destructor
//
/////////////////////////////////////////////////////////
pix_record :: ~pix_record()
{
132
  if(m_handle)delete m_handle;
133
134
  outlet_free(m_outNumFrames);
  outlet_free(m_outInfo);
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169

  if(m_pimpl)delete m_pimpl;
}


/////////////////////////////////////////////////////////
// add backends
//
/////////////////////////////////////////////////////////
bool pix_record :: addHandle( std::vector<std::string>available, std::string ID)
{
  unsigned int i=0;
  int count=0;

  std::vector<std::string>id;
  if(!ID.empty()) {
    // if requested 'cid' is in 'available' add it to the list of 'id's
    if(std::find(available.begin(), available.end(), ID)!=available.end()) {
      id.push_back(ID);
    } else {
      // request for an unavailable ID
      verbose(2, "backend '%s' unavailable", ID.c_str());
      return false;
    }
  } else {
    // no 'ID' given: add all available IDs
    id=available;
  }

  for(i=0; i<id.size(); i++) {
    std::string key=id[i];
    verbose(2, "trying to add '%s' as backend", key.c_str());
    if(std::find(m_ids.begin(), m_ids.end(), key)==m_ids.end()) {
      // not yet added, do so now!
      gem::record         *handle=NULL;
zmoelnig's avatar
zmoelnig committed
170
      startpost("backend #%d='%s'\t", m_allhandles.size(), key.c_str());
171
172
173
174
175
176
177
178
179
180
181
      try {
	handle=gem::PluginFactory<gem::record>::getInstance(key); 
      } catch (GemException ex) {
      }
      if(NULL==handle) { 
	post("<--- DISABLED");
	break;
      }
      endpost();

      m_ids.push_back(key);
zmoelnig's avatar
zmoelnig committed
182
      m_allhandles.push_back(handle);
183
      count++;
zmoelnig's avatar
zmoelnig committed
184
      verbose(2, "added backend#%d '%s' @ 0x%x", m_allhandles.size()-1, key.c_str(), handle);
185
186
187
188
189
190
191
192
193
194
195
    }
  }

  return (count>0);
}

//
// stops recording into the movie
//
void pix_record :: startRecording()
{
zmoelnig's avatar
zmoelnig committed
196
197
198
199
200
  if(m_filename.empty()) {
    error("start recording requested with no prior open");
    return;
  }

201
202
203
204
  // find a handle for the current settings (filename, codec, props)
  const std::string codec=m_codec;
  if(m_handle) {
    stopRecording();
205
    m_handle=NULL;
206
  }
zmoelnig's avatar
zmoelnig committed
207
208
209
210
  m_currentFrame = 0;
  int i=0;
  for(i=0; i<m_handles.size(); i++) {
    // check whether the handle supports the requested codec
211
    gem::record *handle=m_handles[i];
212
    if(!codec.empty() && !handle->setCodec(codec))
zmoelnig's avatar
zmoelnig committed
213
      continue;
214
    if(handle->start(m_filename, m_props)) {
215
      m_handle=handle;
216
      break;
zmoelnig's avatar
zmoelnig committed
217
    }
218
219
220
221
  }
  if(m_handle) {
    m_filename=std::string("");
    m_recording=true;
zmoelnig's avatar
zmoelnig committed
222
223
  } else {
    post("unable to open '%s'", m_filename.c_str());
224
  }
cclepper's avatar
cclepper committed
225
226
227
}

//
228
// stops recording into the movie
cclepper's avatar
cclepper committed
229
230
231
//
void pix_record :: stopRecording()
{
232
  if(m_recording) {
233
    m_handle->stop();
234
    m_currentFrame = 0;
235
    outlet_float(m_outNumFrames,m_currentFrame);
236
    verbose(1, "movie written");
237
  }
cclepper's avatar
cclepper committed
238

239
  m_recording=false;
cclepper's avatar
cclepper committed
240
241
242
243
244
245
246
247
248
}


/////////////////////////////////////////////////////////
// render
//
/////////////////////////////////////////////////////////
void pix_record :: render(GemState *state)
{
249
  //check if state exists
zmoelnig's avatar
zmoelnig committed
250
251
252
253
254
  if(!state)return;
  pixBlock*img=NULL;
  state->get("pix", img);

  if(!img || !img->image.data){
zmoelnig's avatar
zmoelnig committed
255
256
    return;
  }
257
  if(!m_handle)return;
258
259
260
  
  if(m_banged||m_automatic){
    //      if(m_maxFrames != 0 && m_currentFrame >= m_maxFrames) m_recordStop = 1;
zmoelnig's avatar
zmoelnig committed
261
    bool success=m_handle->write(&img->image);
262
263
264
265
    m_banged=false;

    if(success) {
      m_currentFrame++;
zmoelnig's avatar
zmoelnig committed
266
      outlet_float(m_outNumFrames,m_currentFrame);
267
268
    } else {
      stopRecording();
zmoelnig's avatar
zmoelnig committed
269
270
    }
  }
cclepper's avatar
cclepper committed
271
272
273
}

/////////////////////////////////////////////////////////
274
// Properties
cclepper's avatar
cclepper committed
275
276
//
/////////////////////////////////////////////////////////
277
void pix_record :: enumPropertiesMess()
cclepper's avatar
cclepper committed
278
{
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
  if(m_handle) {
    gem::Properties props;
    if(!m_handle->enumProperties(props))
      return;

    int ac=0;
    t_atom ap[3];
    std::vector<std::string>keys=props.keys();

    SETFLOAT(ap+0, keys.size());
    outlet_anything(m_outInfo, gensym("numprops"), 1, ap);

    int i=0;
    for(i=0; i<keys.size(); i++) {
      ac=2;
      std::string key=keys[i];
      SETSYMBOL(ap+0, gensym(key.c_str()));
      switch(props.type(key)) {
      case gem::Properties::NONE:
zmoelnig's avatar
zmoelnig committed
298
        SETSYMBOL(ap+1, gensym("Bang"));
299
300
301
        break;
      case gem::Properties::DOUBLE: {
        double d=-1;
zmoelnig's avatar
zmoelnig committed
302
        SETSYMBOL(ap+1, gensym("Float"));
303
304
305
306
307
308
309
310
        /* LATER: get and show ranges */
        if(props.get(key, d)) {
          ac=3;
          SETFLOAT(ap+2, d);
        }
      }
        break;
      case gem::Properties::STRING: {
zmoelnig's avatar
zmoelnig committed
311
        SETSYMBOL(ap+1, gensym("Symbol"));
312
313
314
315
316
317
318
319
320
321
322
323
        std::string s;
        if(props.get(key, s)) {
          ac=3;
          SETSYMBOL(ap+2, gensym(s.c_str()));
        }
      }
        break;
      default:
        SETSYMBOL(ap+1, gensym("unknown"));
        break;
      }
      outlet_anything(m_outInfo, gensym("property"), ac, ap);
324
325
    }
  }
cclepper's avatar
cclepper committed
326
}
327
328
329
330
void pix_record :: setPropertiesMess(int argc, t_atom*argv)
{
  PIMPL::addProperties(m_props, argc, argv);
}
cclepper's avatar
cclepper committed
331

332
void pix_record :: clearPropertiesMess()
cclepper's avatar
cclepper committed
333
{
334
  m_props.clear();
cclepper's avatar
cclepper committed
335
336
}

337
338


339
340
341
342
343
344
/////////////////////////////////////////////////////////
// dialogMess
//
/////////////////////////////////////////////////////////
void pix_record :: dialogMess()
{
345
346
  if(m_handle){
    if(!m_handle->dialog()){
347
      error("unable to open settings dialog");
348
    }
349
350
  }
}
351
/////////////////////////////////////////////////////////
352
// recordMess
353
354
355
356
357
//
/////////////////////////////////////////////////////////
void pix_record :: recordMess(bool on)
{
  if (on) {
358
    startRecording();
359
  }else{
360
    stopRecording();
361
362
  }
}
363
364
365
366
367
368
369

/////////////////////////////////////////////////////////
// spits out a list of installed codecs and stores them
//
/////////////////////////////////////////////////////////
void pix_record :: getCodecList()
{
370
371
372
373
374
375
376
  m_pimpl->clearCodecHandle();
  int i=0;
  for(i=0; i<m_handles.size(); i++) {
    std::vector<std::string>c=m_handles[i]->getCodecs();
    int j;
    for(j=0; j<c.size(); j++) {
      m_pimpl->addCodecHandle(m_handles[i], c[j]);
377
378
    }
  }
379
380
  for(i=0; i<m_pimpl->m_codecs.size(); i++) {
    const std::string id=m_pimpl->m_codecs[i];
zmoelnig's avatar
zmoelnig committed
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
    std::vector<PIMPL::codechandle>handles=m_pimpl->m_codechandle[id];
    int j=0;
    for(j=0; j<handles.size(); j++) {
      gem::record*handle=handles[j].handle;

      const std::string codecname=handles[j].codec;
      const std::string descr=handle->getCodecDescription(codecname);
      t_atom ap[3];

      verbose(2, "codec%d: '%s': %s", i, codecname.c_str(), (descr.empty()?"":descr.c_str()));
      SETFLOAT (ap+0, static_cast<t_float>(i));
      SETSYMBOL(ap+1, gensym(codecname.c_str()));
      SETSYMBOL(ap+2, gensym(descr.c_str()));
      outlet_anything(m_outInfo, gensym("codec"), 3, ap);
    }
396
  }
397
398
399
400
401
402
403
}


/////////////////////////////////////////////////////////
// deals with the name of a codec
//
/////////////////////////////////////////////////////////
404
void pix_record :: codecMess(t_atom *argv)
405
{
zmoelnig's avatar
zmoelnig committed
406
407
408
409
410
411
412
413
414
415
416
417
418
419
#warning codecMess is a mess
  /*
   * allow setting of codec without handle
   */

  /*
   * codecMess should do the following:
   *  find "valid" handles (those that support the given codec)
   *  copy all valid handles from m_allhandles to m_handles
   *  query all valid handles for settable properties
   *
   * if a special codec is given (e.g. none at all), all handles are valid
   */

420
  if(m_handle){
421
    m_handle->stop();
422
423
424
425
426
427
428
429
430
431
432
433
434
435
    m_handle=NULL;
  }
  
  std::string sid;

  if (A_SYMBOL==argv->a_type) {
    sid=std::string(atom_getsymbol(argv)->s_name);
  } else if(A_FLOAT==argv->a_type){
    /* maintain a list of all codecs and resolve using that */
    int id=atom_getint(argv);
    if((id>=0) && (id<m_pimpl->m_codecs.size())) {
      sid=m_pimpl->m_codecs[id];
    }
  }
zmoelnig's avatar
zmoelnig committed
436
437
438
439
440
441
442
443
  std::vector<PIMPL::codechandle>handles=m_pimpl->m_codechandle[sid];
  if(handles.size()>0) {
    m_handles.clear();
    int i=0;
    for(i=0; i<handles.size(); i++) {
      gem::record*handle=handles[i].handle;
      std::string codec=handles[i].codec;
      if(handle->setCodec(codec)) {
444
	m_codec=codec;
zmoelnig's avatar
zmoelnig committed
445
446
447
448
449
	m_handles.push_back(handle);
      } 
    }
    if(m_handles.size()>0) {
      m_handle=m_handles[0];
450
    } else {
zmoelnig's avatar
zmoelnig committed
451
      error("couldn't find a valid backend for codec '%s'", sid.c_str());
452
    }
zmoelnig's avatar
zmoelnig committed
453

454
  } else {
zmoelnig's avatar
zmoelnig committed
455
    error("unknown codec '%s", sid.c_str());
456
  }
zmoelnig's avatar
zmoelnig committed
457
458
459
460
461
462

  verbose(1, "successfully set codec '%s' and got %d handles: %p", 
	  sid.c_str(),
	  m_handles.size(),
	  m_handle);

zmoelnig's avatar
zmoelnig committed
463
464
  if(m_handle)
    enumPropertiesMess();
465
466
}

cclepper's avatar
cclepper committed
467
468
void pix_record :: fileMess(int argc, t_atom *argv)
{
zmoelnig's avatar
zmoelnig committed
469
  /* LATER let the record()-handles chose whether they accept an open request
470
471
472
   * and then try other handles (if available)
   * this would allow to use this object for streaming, virtual output devices,...
   */
473
474
  if(argc) {
    m_filename=std::string(atom_getsymbol(argv)->s_name);
cclepper's avatar
cclepper committed
475
476
477
478
479
480
481
482
483
  }
}

/////////////////////////////////////////////////////////
// static member functions
//
/////////////////////////////////////////////////////////
void pix_record :: obj_setupCallback(t_class *classPtr)
{
zmoelnig's avatar
zmoelnig committed
484
  class_addmethod(classPtr, reinterpret_cast<t_method>(&pix_record::fileMessCallback),
cclepper's avatar
cclepper committed
485
		  gensym("file"), A_GIMME, A_NULL);
zmoelnig's avatar
zmoelnig committed
486
  class_addmethod(classPtr, reinterpret_cast<t_method>(&pix_record::autoMessCallback),
cclepper's avatar
cclepper committed
487
		  gensym("auto"), A_FLOAT, A_NULL);
zmoelnig's avatar
zmoelnig committed
488
  class_addbang(classPtr, reinterpret_cast<t_method>(&pix_record::bangMessCallback));
cclepper's avatar
cclepper committed
489

zmoelnig's avatar
zmoelnig committed
490
  class_addmethod(classPtr, reinterpret_cast<t_method>(&pix_record::recordMessCallback),
491
492
		  gensym("record"), A_FLOAT, A_NULL);

zmoelnig's avatar
zmoelnig committed
493
  class_addmethod(classPtr, reinterpret_cast<t_method>(&pix_record::dialogMessCallback),
494
		  gensym("dialog"),  A_NULL);
zmoelnig's avatar
zmoelnig committed
495
  class_addmethod(classPtr, reinterpret_cast<t_method>(&pix_record::codeclistMessCallback),
496
		  gensym("codeclist"),  A_NULL);
zmoelnig's avatar
zmoelnig committed
497
  class_addmethod(classPtr, reinterpret_cast<t_method>(&pix_record::codecMessCallback),
498
		  gensym("codec"), A_GIMME, A_NULL);
499
500
501
502
503
504
505
506
507
508
509


  class_addmethod(classPtr, 
		  reinterpret_cast<t_method>(&pix_record::enumPropertiesMessCallback),
		  gensym("proplist"), A_NULL);
  class_addmethod(classPtr, 
		  reinterpret_cast<t_method>(&pix_record::setPropertiesMessCallback),
		  gensym("set"), A_GIMME, A_NULL);
  class_addmethod(classPtr, 
		  reinterpret_cast<t_method>(&pix_record::clearPropertiesMessCallback),
		  gensym("clearprops"), A_NULL);
cclepper's avatar
cclepper committed
510
511
512
513
514
515
516
517
}

void pix_record :: fileMessCallback(void *data, t_symbol *s, int argc, t_atom *argv)
{
  GetMyClass(data)->fileMess(argc, argv);
}
void pix_record :: autoMessCallback(void *data, t_floatarg on)
{
518
519
  bool onb=static_cast<bool>(on);
  GetMyClass(data)->m_automatic=onb;
cclepper's avatar
cclepper committed
520
521
522
523
524
525
526
527
}
void pix_record :: bangMessCallback(void *data)
{
  GetMyClass(data)->m_banged=true;
}

void pix_record :: recordMessCallback(void *data, t_floatarg on)
{
528
  GetMyClass(data)->recordMess(!(!static_cast<int>(on)));
cclepper's avatar
cclepper committed
529
530
}

531
532
void pix_record :: dialogMessCallback(void *data)
{
533
  GetMyClass(data)->dialogMess();
534
535
536
537
}

void pix_record :: codeclistMessCallback(void *data)
{
538
  GetMyClass(data)->getCodecList();
539
540
541
542
}

void pix_record :: codecMessCallback(void *data, t_symbol *s, int argc, t_atom *argv)
{
543
544
  if(argc)
    GetMyClass(data)->codecMess(argv);
545
}
546

547
548

void pix_record :: enumPropertiesMessCallback(void *data)
549
{
550
  GetMyClass(data)->enumPropertiesMess();
551
}
552
553
554
555
556
void pix_record :: setPropertiesMessCallback(void *data, t_symbol *s, int argc, t_atom *argv)
{
  GetMyClass(data)->setPropertiesMess(argc, argv);
}
void pix_record :: clearPropertiesMessCallback(void *data)
557
{
558
  GetMyClass(data)->clearPropertiesMess();
559
}