pix_record.cpp 13.5 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

11
12
#include "RTE/MessageCallbacks.h"

cclepper's avatar
cclepper committed
13
14
CPPEXTERN_NEW_WITH_GIMME(pix_record)

15
16
17
18
19
20
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
21
    
22
    gem::record*handle;
zmoelnig's avatar
zmoelnig committed
23
    std::string codec;
24
  };
zmoelnig's avatar
zmoelnig committed
25
  std::map<std::string, std::vector<codechandle> >m_codechandle;
26
27
28
  std::vector<std::string>m_codecs;

  void addCodecHandle(gem::record*handle, const std::string codec) {
29
30
31
#ifdef __GNUC__
# warning better handling of duplicate codecs
#endif
zmoelnig's avatar
zmoelnig committed
32
33
    /* FIXME: we should generate a unique codec-ID, e.g. "<handlename>:<codec>" */
    m_codechandle[codec].push_back(codechandle(handle, codec));
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
85
86
87
88
    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;
    }
  }
  
};

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

112

113
114
115
116
117
  gem::PluginFactory<gem::record>::loadPlugins("record");
  std::vector<std::string>ids=gem::PluginFactory<gem::record>::getIDs();
  addHandle(ids, "QT");
  addHandle(ids, "QT4L");
  addHandle(ids);
118

119

zmoelnig's avatar
zmoelnig committed
120
  if(m_allhandles.size()>0) {
121
122
  } else {
    error("no video backends found!");
123
  }
124

zmoelnig's avatar
zmoelnig committed
125
126
  m_handles=m_allhandles;

127
  getCodecList();
cclepper's avatar
cclepper committed
128
129
130
131
132
133
134
135
}

/////////////////////////////////////////////////////////
// Destructor
//
/////////////////////////////////////////////////////////
pix_record :: ~pix_record()
{
136
  if(m_handle)delete m_handle;
137
138
  outlet_free(m_outNumFrames);
  outlet_free(m_outInfo);
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
170
171
172
173

  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
174
      startpost("backend #%d='%s'\t", m_allhandles.size(), key.c_str());
175
176
177
178
179
180
181
182
183
184
185
      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
186
      m_allhandles.push_back(handle);
187
      count++;
zmoelnig's avatar
zmoelnig committed
188
      verbose(2, "added backend#%d '%s' @ 0x%x", m_allhandles.size()-1, key.c_str(), handle);
189
190
191
192
193
194
195
196
197
198
199
    }
  }

  return (count>0);
}

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

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

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

243
  m_recording=false;
cclepper's avatar
cclepper committed
244
245
246
247
248
249
250
251
252
}


/////////////////////////////////////////////////////////
// render
//
/////////////////////////////////////////////////////////
void pix_record :: render(GemState *state)
{
253
  //check if state exists
zmoelnig's avatar
zmoelnig committed
254
255
  if(!state)return;
  pixBlock*img=NULL;
256
  state->get(GemState::_PIX, img);
zmoelnig's avatar
zmoelnig committed
257
258

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

    if(success) {
      m_currentFrame++;
zmoelnig's avatar
zmoelnig committed
270
      outlet_float(m_outNumFrames,m_currentFrame);
271
272
    } else {
      stopRecording();
zmoelnig's avatar
zmoelnig committed
273
274
    }
  }
cclepper's avatar
cclepper committed
275
276
277
}

/////////////////////////////////////////////////////////
278
// Properties
cclepper's avatar
cclepper committed
279
280
//
/////////////////////////////////////////////////////////
281
void pix_record :: enumPropertiesMess()
cclepper's avatar
cclepper committed
282
{
283
284
285
286
287
288
289
290
291
292
293
294
  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);

295
    unsigned int i=0;
296
297
298
299
300
301
    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
302
        SETSYMBOL(ap+1, gensym("Bang"));
303
304
305
        break;
      case gem::Properties::DOUBLE: {
        double d=-1;
zmoelnig's avatar
zmoelnig committed
306
        SETSYMBOL(ap+1, gensym("Float"));
307
308
309
310
311
312
313
314
        /* 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
315
        SETSYMBOL(ap+1, gensym("Symbol"));
316
317
318
319
320
321
322
323
324
325
326
327
        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);
328
329
    }
  }
cclepper's avatar
cclepper committed
330
}
331
332
333
334
void pix_record :: setPropertiesMess(int argc, t_atom*argv)
{
  PIMPL::addProperties(m_props, argc, argv);
}
cclepper's avatar
cclepper committed
335

336
void pix_record :: clearPropertiesMess()
cclepper's avatar
cclepper committed
337
{
338
  m_props.clear();
cclepper's avatar
cclepper committed
339
340
}

341
342


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

/////////////////////////////////////////////////////////
// spits out a list of installed codecs and stores them
//
/////////////////////////////////////////////////////////
void pix_record :: getCodecList()
{
374
  m_pimpl->clearCodecHandle();
375
  unsigned int i=0;
376
377
  for(i=0; i<m_handles.size(); i++) {
    std::vector<std::string>c=m_handles[i]->getCodecs();
378
    unsigned int j;
379
380
    for(j=0; j<c.size(); j++) {
      m_pimpl->addCodecHandle(m_handles[i], c[j]);
381
382
    }
  }
383
384
  for(i=0; i<m_pimpl->m_codecs.size(); i++) {
    const std::string id=m_pimpl->m_codecs[i];
zmoelnig's avatar
zmoelnig committed
385
    std::vector<PIMPL::codechandle>handles=m_pimpl->m_codechandle[id];
386
    unsigned int j=0;
zmoelnig's avatar
zmoelnig committed
387
388
389
390
391
392
393
394
395
396
397
398
399
    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);
    }
400
  }
401
402
403
404
405
406
407
}


/////////////////////////////////////////////////////////
// deals with the name of a codec
//
/////////////////////////////////////////////////////////
408
void pix_record :: codecMess(t_atom *argv)
409
{
410
#ifdef __GNUC__
zmoelnig's avatar
zmoelnig committed
411
#warning codecMess is a mess
412
#endif
zmoelnig's avatar
zmoelnig committed
413
414
415
416
417
418
419
420
421
422
423
424
425
  /*
   * 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
   */

426
  if(m_handle){
427
    m_handle->stop();
428
429
430
431
432
433
434
435
436
437
438
439
440
441
    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
442
443
444
  std::vector<PIMPL::codechandle>handles=m_pimpl->m_codechandle[sid];
  if(handles.size()>0) {
    m_handles.clear();
445
    unsigned int i=0;
zmoelnig's avatar
zmoelnig committed
446
447
448
449
    for(i=0; i<handles.size(); i++) {
      gem::record*handle=handles[i].handle;
      std::string codec=handles[i].codec;
      if(handle->setCodec(codec)) {
450
	m_codec=codec;
zmoelnig's avatar
zmoelnig committed
451
452
453
454
455
	m_handles.push_back(handle);
      } 
    }
    if(m_handles.size()>0) {
      m_handle=m_handles[0];
456
    } else {
zmoelnig's avatar
zmoelnig committed
457
      error("couldn't find a valid backend for codec '%s'", sid.c_str());
458
    }
zmoelnig's avatar
zmoelnig committed
459

460
  } else {
zmoelnig's avatar
zmoelnig committed
461
    error("unknown codec '%s", sid.c_str());
462
  }
zmoelnig's avatar
zmoelnig committed
463
464
465
466
467
468

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

zmoelnig's avatar
zmoelnig committed
469
470
  if(m_handle)
    enumPropertiesMess();
471
472
}

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

/////////////////////////////////////////////////////////
// static member functions
//
/////////////////////////////////////////////////////////
void pix_record :: obj_setupCallback(t_class *classPtr)
{
zmoelnig's avatar
zmoelnig committed
490
  class_addmethod(classPtr, reinterpret_cast<t_method>(&pix_record::fileMessCallback),
cclepper's avatar
cclepper committed
491
		  gensym("file"), A_GIMME, A_NULL);
zmoelnig's avatar
zmoelnig committed
492
  class_addmethod(classPtr, reinterpret_cast<t_method>(&pix_record::autoMessCallback),
cclepper's avatar
cclepper committed
493
		  gensym("auto"), A_FLOAT, A_NULL);
zmoelnig's avatar
zmoelnig committed
494
  class_addbang(classPtr, reinterpret_cast<t_method>(&pix_record::bangMessCallback));
cclepper's avatar
cclepper committed
495

496
  CPPEXTERN_MSG1(classPtr, "record", recordMess, bool);
497

498
499
  CPPEXTERN_MSG0(classPtr, "dialog", dialogMess);
  CPPEXTERN_MSG0(classPtr, "codeclist", getCodecList);
zmoelnig's avatar
zmoelnig committed
500
  class_addmethod(classPtr, reinterpret_cast<t_method>(&pix_record::codecMessCallback),
501
		  gensym("codec"), A_GIMME, A_NULL);
502

503
  CPPEXTERN_MSG0(classPtr, "proplist", enumPropertiesMess);
504
505
506
  class_addmethod(classPtr, 
		  reinterpret_cast<t_method>(&pix_record::setPropertiesMessCallback),
		  gensym("set"), A_GIMME, A_NULL);
507
  CPPEXTERN_MSG0(classPtr, "clearprops", clearPropertiesMess);
cclepper's avatar
cclepper committed
508
509
510
511
512
513
514
515
}

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)
{
516
517
  bool onb=static_cast<bool>(on);
  GetMyClass(data)->m_automatic=onb;
cclepper's avatar
cclepper committed
518
519
520
521
522
523
524
525
}
void pix_record :: bangMessCallback(void *data)
{
  GetMyClass(data)->m_banged=true;
}

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

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

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

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

545
546

void pix_record :: enumPropertiesMessCallback(void *data)
547
{
548
  GetMyClass(data)->enumPropertiesMess();
549
}
550
551
552
553
554
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)
555
{
556
  GetMyClass(data)->clearPropertiesMess();
557
}