pix_record.cpp 11.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
12
13
#include "plugins/PluginFactory.h"

#include <map>
14
#include <algorithm>
15

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

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

  struct codechandle {
25
26
    codechandle(gem::plugins::record*h, const std::string&c):handle(h),
      codec(c) {}
27

28
    gem::plugins::record*handle;
zmoelnig's avatar
zmoelnig committed
29
    std::string codec;
30
  };
zmoelnig's avatar
zmoelnig committed
31
  std::map<std::string, std::vector<codechandle> >m_codechandle;
32
33
  std::vector<std::string>m_codecs;

34
35
  void addCodecHandle(gem::plugins::record*handle, const std::string&codec)
  {
36
37
38
#ifdef __GNUC__
# warning better handling of duplicate codecs
#endif
zmoelnig's avatar
zmoelnig committed
39
40
    /* FIXME: we should generate a unique codec-ID, e.g. "<handlename>:<codec>" */
    m_codechandle[codec].push_back(codechandle(handle, codec));
41
42
    m_codecs.push_back(codec);
  }
43
44
  void clearCodecHandle(void)
  {
45
46
47
48
    m_codecs.clear();
    m_codechandle.clear();
  }

49
50
  static gem::any atom2any(t_atom*ap)
  {
51
52
53
54
    gem::any result;
    if(ap) {
      switch(ap->a_type) {
      case A_FLOAT:
55
56
        result=atom_getfloat(ap);
        break;
57
      case A_SYMBOL:
58
59
        result=atom_getsymbol(ap)->s_name;
        break;
60
      default:
61
        result=ap->a_w.w_gpointer;
62
63
64
65
66
67
      }
    }
    return result;
  }
  static void addProperties(gem::Properties&props, int argc, t_atom*argv)
  {
68
69
70
    if(!argc) {
      return;
    }
71

72
73
74
75
76
77
    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;
78
79
    argc--;
    argv++;
80
81
82
83
84
85
86
87
88
89
    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;
90
91
92
93
94
    case 0: {
      gem::any dummy;
      props.set(key, dummy);
    }
    break;
95
96
    }
  }
97

98
99
};

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

123

IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
124
  m_handle=gem::plugins::record::getInstance();
125
  getCodecList();
cclepper's avatar
cclepper committed
126
127
128
129
130
131
132
133
}

/////////////////////////////////////////////////////////
// Destructor
//
/////////////////////////////////////////////////////////
pix_record :: ~pix_record()
{
134
135
136
  if(m_handle) {
    delete m_handle;
  }
137
138
  outlet_free(m_outNumFrames);
  outlet_free(m_outInfo);
139

140
141
142
  if(m_pimpl) {
    delete m_pimpl;
  }
143
144
145
146
147
148
149
}


/////////////////////////////////////////////////////////
// add backends
//
/////////////////////////////////////////////////////////
150
151
bool pix_record :: addHandle( std::vector<std::string>available,
                              std::string ID)
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
{
  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!
176
      gem::plugins::record         *handle=NULL;
zmoelnig's avatar
zmoelnig committed
177
      startpost("backend #%d='%s'\t", m_allhandles.size(), key.c_str());
178
      try {
179
        handle=gem::PluginFactory<gem::plugins::record>::getInstance(key);
180
      } catch (GemException&ex) {
181
182
        startpost("(%s) ", ex.what());
        handle=NULL;
183
      }
184
      if(NULL==handle) {
185
186
        post("<--- DISABLED");
        break;
187
188
189
190
      }
      endpost();

      m_ids.push_back(key);
zmoelnig's avatar
zmoelnig committed
191
      m_allhandles.push_back(handle);
192
      count++;
193
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()
{
206
207
208
  if(!m_handle) {
    return;
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
209

zmoelnig's avatar
zmoelnig committed
210
211
212
213
214
  if(m_filename.empty()) {
    error("start recording requested with no prior open");
    return;
  }

215
216
  // find a handle for the current settings (filename, codec, props)
  const std::string codec=m_codec;
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
217
218
219
  stopRecording();


zmoelnig's avatar
zmoelnig committed
220
  m_currentFrame = 0;
221
  unsigned int i=0;
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
222
223
  // do not re-set the codec, if there is no need...
  /* m_handle->setCodec(codec); */
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
224
  if(m_handle->start(m_filename, m_props)) {
225
226
    m_filename=std::string("");
    m_recording=true;
zmoelnig's avatar
zmoelnig committed
227
228
  } else {
    post("unable to open '%s'", m_filename.c_str());
229
  }
cclepper's avatar
cclepper committed
230
231
232
}

//
233
// stops recording into the movie
cclepper's avatar
cclepper committed
234
235
236
//
void pix_record :: stopRecording()
{
237
238
239
  if(!m_handle) {
    return;
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
240

241
  if(m_recording) {
242
    m_handle->stop();
243
    m_currentFrame = 0;
244
    outlet_float(m_outNumFrames,m_currentFrame);
245
    verbose(1, "movie written");
246
  }
cclepper's avatar
cclepper committed
247

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


/////////////////////////////////////////////////////////
// render
//
/////////////////////////////////////////////////////////
void pix_record :: render(GemState *state)
{
258
259
260
  if(!m_handle || !m_recording) {
    return;
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
261

262
  //check if state exists
263
264
265
  if(!state) {
    return;
  }
zmoelnig's avatar
zmoelnig committed
266
  pixBlock*img=NULL;
267
  state->get(GemState::_PIX, img);
zmoelnig's avatar
zmoelnig committed
268

269
  if(!img || !img->image.data) {
zmoelnig's avatar
zmoelnig committed
270
271
    return;
  }
272

273
  if(m_banged||m_automatic) {
274
    //      if(m_maxFrames != 0 && m_currentFrame >= m_maxFrames) m_recordStop = 1;
zmoelnig's avatar
zmoelnig committed
275
    bool success=m_handle->write(&img->image);
276
277
278
279
    m_banged=false;

    if(success) {
      m_currentFrame++;
zmoelnig's avatar
zmoelnig committed
280
      outlet_float(m_outNumFrames,m_currentFrame);
281
282
    } else {
      stopRecording();
zmoelnig's avatar
zmoelnig committed
283
284
    }
  }
cclepper's avatar
cclepper committed
285
286
287
}

/////////////////////////////////////////////////////////
288
// Properties
cclepper's avatar
cclepper committed
289
290
//
/////////////////////////////////////////////////////////
291
void pix_record :: enumPropertiesMess()
cclepper's avatar
cclepper committed
292
{
293
294
295
  if(!m_handle) {
    return;
  }
296

IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
297
  gem::Properties props;
298
  if(!m_handle->enumProperties(props)) {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
299
    return;
300
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
301
302
303
304
305
306
307
308
309

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

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

  unsigned int i=0;
  for(i=0; i<keys.size(); i++) {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
310
    int ac=2;
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
311
312
313
314
315
316
317
318
319
320
321
    std::string key=keys[i];
    SETSYMBOL(ap+0, gensym(key.c_str()));
    switch(props.type(key)) {
    case gem::Properties::NONE:
      SETSYMBOL(ap+1, gensym("Bang"));
      break;
    case gem::Properties::DOUBLE: {
      double d=-1;
      SETSYMBOL(ap+1, gensym("Float"));
      /* LATER: get and show ranges */
      if(props.get(key, d)) {
322
323
        ac=3;
        SETFLOAT(ap+2, d);
324
      }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
325
    }
326
    break;
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
327
328
329
330
    case gem::Properties::STRING: {
      SETSYMBOL(ap+1, gensym("Symbol"));
      std::string s;
      if(props.get(key, s)) {
331
332
        ac=3;
        SETSYMBOL(ap+2, gensym(s.c_str()));
333
      }
334
    }
335
    break;
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
336
337
338
339
340
    default:
      SETSYMBOL(ap+1, gensym("unknown"));
      break;
    }
    outlet_anything(m_outInfo, gensym("property"), ac, ap);
341
  }
cclepper's avatar
cclepper committed
342
}
343
void pix_record :: setPropertiesMess(t_symbol*s, int argc, t_atom*argv)
344
345
346
{
  PIMPL::addProperties(m_props, argc, argv);
}
cclepper's avatar
cclepper committed
347

348
void pix_record :: clearPropertiesMess()
cclepper's avatar
cclepper committed
349
{
350
  m_props.clear();
cclepper's avatar
cclepper committed
351
352
}

353
354


355
356
357
358
359
360
/////////////////////////////////////////////////////////
// dialogMess
//
/////////////////////////////////////////////////////////
void pix_record :: dialogMess()
{
361
362
363
  if(!m_handle) {
    return;
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
364

365
  if(!m_handle->dialog()) {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
366
    error("unable to open settings dialog");
367
368
  }
}
369
/////////////////////////////////////////////////////////
370
// recordMess
371
372
373
374
375
//
/////////////////////////////////////////////////////////
void pix_record :: recordMess(bool on)
{
  if (on) {
376
    startRecording();
377
  } else {
378
    stopRecording();
379
380
  }
}
381
382
383
384
385
386
387

/////////////////////////////////////////////////////////
// spits out a list of installed codecs and stores them
//
/////////////////////////////////////////////////////////
void pix_record :: getCodecList()
{
388
389
390
  if(!m_handle) {
    return;
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
391
392
393
394
395
396
397
398
399

  std::vector<std::string>codecs=m_handle->getCodecs();

  unsigned int i;
  for(i=0; i<codecs.size(); i++) {
    const std::string codecname=codecs[i];
    const std::string descr=m_handle->getCodecDescription(codecname);
    t_atom ap[3];

400
401
    verbose(2, "codec%d: '%s': %s", i, codecname.c_str(),
            (descr.empty()?"":descr.c_str()));
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
402
403
404
405
    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
417
418
  if(!m_handle) {
    return;
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
419

420
#ifdef __GNUC__
zmoelnig's avatar
zmoelnig committed
421
#warning codecMess is a mess
422
#endif
zmoelnig's avatar
zmoelnig committed
423
424
425
426
427
428
429
430
431
432
433
434
435
  /*
   * 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
   */

436
437
438
439
  std::string sid;

  if (A_SYMBOL==argv->a_type) {
    sid=std::string(atom_getsymbol(argv)->s_name);
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
440
  } else if (A_FLOAT==argv->a_type) {
441
    int id=atom_getint(argv);
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
442
    std::vector<std::string>codecs=m_handle->getCodecs();
443
    if(id>0 && ((unsigned int)id)<codecs.size()) {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
444
      sid=codecs[id];
445
    } else {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
446
447
      error("invalid codec# %d (0..%d)", id, codecs.size());
      return;
448
449
    }
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
450
  if(m_handle->setCodec(sid)) {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
451
    m_codec=sid;
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
452
    verbose(1, "successfully set codec '%s'", sid.c_str());
453
  } else {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
454
455
    error("couldn't find a valid backend for codec '%s'", sid.c_str());
    return;
456
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
457
  enumPropertiesMess();
458
459
}

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

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

479
480
  CPPEXTERN_MSG1(classPtr, "auto", autoMess, bool);
  CPPEXTERN_MSG0(classPtr, "bang", bangMess);
481
482
483
  CPPEXTERN_MSG1(classPtr, "record", recordMess, bool);
  CPPEXTERN_MSG0(classPtr, "dialog", dialogMess);
  CPPEXTERN_MSG0(classPtr, "codeclist", getCodecList);
484
485
  class_addmethod(classPtr,
                  reinterpret_cast<t_method>(&pix_record::codecMessCallback),
486
                  gensym("codec"), A_GIMME, A_NULL);
487

488
  CPPEXTERN_MSG0(classPtr, "proplist", enumPropertiesMess);
489
  CPPEXTERN_MSG0(classPtr, "enumProps", enumPropertiesMess);
490
  CPPEXTERN_MSG (classPtr, "set", setPropertiesMess);
cclepper's avatar
cclepper committed
491

492
  CPPEXTERN_MSG0(classPtr, "clearProps", clearPropertiesMess);
493
  CPPEXTERN_MSG0(classPtr, "clearprops", clearPropertiesMess);
cclepper's avatar
cclepper committed
494
495
}

496
void pix_record :: bangMess(void)
497
{
498
  m_banged=true;
499
}
500
void pix_record :: autoMess(bool on)
501
{
502
  m_automatic=on;
503
504
}

505
506
void pix_record :: codecMessCallback(void *data, t_symbol *s, int argc,
                                     t_atom *argv)
507
{
508
  if(argc) {
509
    GetMyClass(data)->codecMess(argv);
510
  }
511
}