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

8
#include "Gem/State.h"
9
#include "Gem/Exception.h"
10

11
#include "RTE/MessageCallbacks.h"
12
13
14
#include "plugins/PluginFactory.h"

#include <map>
15

16
CPPEXTERN_NEW_WITH_GIMME(pix_record);
cclepper's avatar
cclepper committed
17

zmoelnig's avatar
zmoelnig committed
18
19
class pix_record :: PIMPL {
public:
20
21
22
23
  PIMPL(void) {};
  ~PIMPL(void) {};

  struct codechandle {
24
    codechandle(gem::plugins::record*h, const std::string c):handle(h), codec(c) {}
zmoelnig's avatar
zmoelnig committed
25
    
26
    gem::plugins::record*handle;
zmoelnig's avatar
zmoelnig committed
27
    std::string codec;
28
  };
zmoelnig's avatar
zmoelnig committed
29
  std::map<std::string, std::vector<codechandle> >m_codechandle;
30
31
  std::vector<std::string>m_codecs;

32
  void addCodecHandle(gem::plugins::record*handle, const std::string codec) {
33
34
35
#ifdef __GNUC__
# warning better handling of duplicate codecs
#endif
zmoelnig's avatar
zmoelnig committed
36
37
    /* FIXME: we should generate a unique codec-ID, e.g. "<handlename>:<codec>" */
    m_codechandle[codec].push_back(codechandle(handle, codec));
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
89
90
91
92
    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;
    }
  }
  
};

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

116

117
118
  gem::PluginFactory<gem::plugins::record>::loadPlugins("record");
  std::vector<std::string>ids=gem::PluginFactory<gem::plugins::record>::getIDs();
119
120
121
  addHandle(ids, "QT");
  addHandle(ids, "QT4L");
  addHandle(ids);
122

123

zmoelnig's avatar
zmoelnig committed
124
  if(m_allhandles.size()>0) {
125
126
  } else {
    error("no video backends found!");
127
  }
128

zmoelnig's avatar
zmoelnig committed
129
130
  m_handles=m_allhandles;

131
  getCodecList();
cclepper's avatar
cclepper committed
132
133
134
135
136
137
138
139
}

/////////////////////////////////////////////////////////
// Destructor
//
/////////////////////////////////////////////////////////
pix_record :: ~pix_record()
{
140
  if(m_handle)delete m_handle;
141
142
  outlet_free(m_outNumFrames);
  outlet_free(m_outInfo);
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
174
175
176

  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!
177
      gem::plugins::record         *handle=NULL;
zmoelnig's avatar
zmoelnig committed
178
      startpost("backend #%d='%s'\t", m_allhandles.size(), key.c_str());
179
      try {
180
        handle=gem::PluginFactory<gem::plugins::record>::getInstance(key); 
181
      } catch (GemException ex) {
182
183
        startpost("(%s) ", ex.what());
        handle=NULL;
184
185
      }
      if(NULL==handle) { 
186
187
        post("<--- DISABLED");
        break;
188
189
190
191
      }
      endpost();

      m_ids.push_back(key);
zmoelnig's avatar
zmoelnig committed
192
      m_allhandles.push_back(handle);
193
      count++;
zmoelnig's avatar
zmoelnig committed
194
      verbose(2, "added backend#%d '%s' @ 0x%x", m_allhandles.size()-1, key.c_str(), handle);
195
196
197
198
199
200
201
202
203
204
205
    }
  }

  return (count>0);
}

//
// stops recording into the movie
//
void pix_record :: startRecording()
{
zmoelnig's avatar
zmoelnig committed
206
207
208
209
210
  if(m_filename.empty()) {
    error("start recording requested with no prior open");
    return;
  }

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

//
238
// stops recording into the movie
cclepper's avatar
cclepper committed
239
240
241
//
void pix_record :: stopRecording()
{
242
  if(m_recording) {
243
    m_handle->stop();
244
    m_currentFrame = 0;
245
    outlet_float(m_outNumFrames,m_currentFrame);
246
    verbose(1, "movie written");
247
  }
cclepper's avatar
cclepper committed
248

249
  m_recording=false;
cclepper's avatar
cclepper committed
250
251
252
253
254
255
256
257
258
}


/////////////////////////////////////////////////////////
// render
//
/////////////////////////////////////////////////////////
void pix_record :: render(GemState *state)
{
259
  //check if state exists
zmoelnig's avatar
zmoelnig committed
260
261
  if(!state)return;
  pixBlock*img=NULL;
262
  state->get(GemState::_PIX, img);
zmoelnig's avatar
zmoelnig committed
263
264

  if(!img || !img->image.data){
zmoelnig's avatar
zmoelnig committed
265
266
    return;
  }
267
  if(!m_handle)return;
268
269
270
  
  if(m_banged||m_automatic){
    //      if(m_maxFrames != 0 && m_currentFrame >= m_maxFrames) m_recordStop = 1;
zmoelnig's avatar
zmoelnig committed
271
    bool success=m_handle->write(&img->image);
272
273
274
275
    m_banged=false;

    if(success) {
      m_currentFrame++;
zmoelnig's avatar
zmoelnig committed
276
      outlet_float(m_outNumFrames,m_currentFrame);
277
278
    } else {
      stopRecording();
zmoelnig's avatar
zmoelnig committed
279
280
    }
  }
cclepper's avatar
cclepper committed
281
282
283
}

/////////////////////////////////////////////////////////
284
// Properties
cclepper's avatar
cclepper committed
285
286
//
/////////////////////////////////////////////////////////
287
void pix_record :: enumPropertiesMess()
cclepper's avatar
cclepper committed
288
{
289
290
291
292
293
294
295
296
297
298
299
300
  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);

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

342
void pix_record :: clearPropertiesMess()
cclepper's avatar
cclepper committed
343
{
344
  m_props.clear();
cclepper's avatar
cclepper committed
345
346
}

347
348


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

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

      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);
    }
406
  }
407
408
409
410
411
412
413
}


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

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

466
  } else {
zmoelnig's avatar
zmoelnig committed
467
    error("unknown codec '%s", sid.c_str());
468
  }
zmoelnig's avatar
zmoelnig committed
469
470
471
472
473
474

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

zmoelnig's avatar
zmoelnig committed
475
476
  if(m_handle)
    enumPropertiesMess();
477
478
}

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

/////////////////////////////////////////////////////////
// static member functions
//
/////////////////////////////////////////////////////////
void pix_record :: obj_setupCallback(t_class *classPtr)
{
496
  CPPEXTERN_MSG (classPtr, "file", fileMess);
cclepper's avatar
cclepper committed
497

498
499
  CPPEXTERN_MSG1(classPtr, "auto", autoMess, bool);
  CPPEXTERN_MSG0(classPtr, "bang", bangMess);
500
501
502
  CPPEXTERN_MSG1(classPtr, "record", recordMess, bool);
  CPPEXTERN_MSG0(classPtr, "dialog", dialogMess);
  CPPEXTERN_MSG0(classPtr, "codeclist", getCodecList);
zmoelnig's avatar
zmoelnig committed
503
  class_addmethod(classPtr, reinterpret_cast<t_method>(&pix_record::codecMessCallback),
504
		  gensym("codec"), A_GIMME, A_NULL);
505

506
  CPPEXTERN_MSG0(classPtr, "proplist", enumPropertiesMess);
507
  CPPEXTERN_MSG (classPtr, "set", fileMess);
cclepper's avatar
cclepper committed
508

509
  CPPEXTERN_MSG0(classPtr, "clearprops", clearPropertiesMess);
cclepper's avatar
cclepper committed
510
511
}

512
void pix_record :: bangMess(void)
513
{
514
  m_banged=true;
515
}
516
void pix_record :: autoMess(bool on)
517
{
518
  m_automatic=on;
519
520
521
522
}

void pix_record :: codecMessCallback(void *data, t_symbol *s, int argc, t_atom *argv)
{
523
524
  if(argc)
    GetMyClass(data)->codecMess(argv);
525
}