]> git.sesse.net Git - kdenlive/blob - src/mltdevicecapture.cpp
Cleanup capture stuff
[kdenlive] / src / mltdevicecapture.cpp
1 /***************************************************************************
2                         mltdevicecapture.cpp  -  description
3                            -------------------
4    begin                : Sun May 21 2011
5    copyright            : (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org)
6
7 ***************************************************************************/
8
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17
18
19 #include "mltdevicecapture.h"
20 #include "kdenlivesettings.h"
21 #include "definitions.h"
22 //#include "recmonitor.h"
23 //#include "renderer.h"
24 #include "blackmagic/devices.h"
25
26 #include <mlt++/Mlt.h>
27
28 #include <KDebug>
29 #include <KStandardDirs>
30 #include <KMessageBox>
31 #include <KLocale>
32 #include <KTemporaryFile>
33
34 #include <QTimer>
35 #include <QDir>
36 #include <QString>
37 #include <QApplication>
38 #include <QThread>
39
40 #include <cstdlib>
41 #include <cstdarg>
42
43 #include <QDebug>
44
45
46
47 static void consumer_gl_frame_show(mlt_consumer, MltDeviceCapture * self, mlt_frame frame_ptr)
48 {
49     // detect if the producer has finished playing. Is there a better way to do it?
50     Mlt::Frame frame(frame_ptr);
51     self->showFrame(frame);
52 }
53
54 static void rec_consumer_frame_show(mlt_consumer, MltDeviceCapture * self, mlt_frame frame_ptr)
55 {
56     Mlt::Frame frame(frame_ptr);
57     if (!frame.is_valid()) return;
58     self->gotCapturedFrame(frame);
59 }
60
61 static void rec_consumer_frame_preview(mlt_consumer, MltDeviceCapture * self, mlt_frame frame_ptr)
62 {
63     Mlt::Frame frame(frame_ptr);
64     if (!frame.is_valid()) return;
65     if (self->sendFrameForAnalysis && frame_ptr->convert_image) {
66         self->emitFrameUpdated(frame);
67     }
68     if (self->doCapture > 0) {
69         self->doCapture --;
70         if (self->doCapture == 0) self->saveFrame(frame);
71     }
72
73 /*    if (self->analyseAudio) {
74         self->showAudio(frame);
75     }
76     if (frame.get_double("_speed") == 0.0) {
77         self->emitConsumerStopped();
78     } else if (frame.get_double("_speed") < 0.0 && mlt_frame_get_position(frame_ptr) <= 0) {
79         self->pause();
80         self->emitConsumerStopped();
81     }*/
82 }
83
84
85 MltDeviceCapture::MltDeviceCapture(QString profile, VideoPreviewContainer *surface, QWidget *parent) :
86     AbstractRender("capture", parent),
87     doCapture(0),
88     sendFrameForAnalysis(false),
89     m_mltConsumer(NULL),
90     m_mltProducer(NULL),
91     m_mltProfile(NULL),
92     m_droppedFrames(0),
93     m_livePreview(true),
94     m_captureDisplayWidget(surface),
95     m_winid((int) surface->winId()),
96     m_analyseAudio(KdenliveSettings::monitor_audio())
97 {
98     if (profile.isEmpty()) profile = KdenliveSettings::current_profile();
99     buildConsumer(profile);
100 }
101
102 MltDeviceCapture::~MltDeviceCapture()
103 {
104     if (m_mltConsumer) delete m_mltConsumer;
105     if (m_mltProducer) delete m_mltProducer;
106     if (m_mltProfile) delete m_mltProfile;
107 }
108
109 void MltDeviceCapture::buildConsumer(const QString &profileName)
110 {
111     if (!profileName.isEmpty()) m_activeProfile = profileName;
112
113     if (m_mltProfile) delete m_mltProfile;
114
115     char *tmp = qstrdup(m_activeProfile.toUtf8().constData());
116     setenv("MLT_PROFILE", tmp, 1);
117     m_mltProfile = new Mlt::Profile(tmp);
118     m_mltProfile->get_profile()->is_explicit = 1;
119     delete[] tmp;
120
121     QString videoDriver = KdenliveSettings::videodrivername();
122     if (!videoDriver.isEmpty()) {
123         if (videoDriver == "x11_noaccel") {
124             setenv("SDL_VIDEO_YUV_HWACCEL", "0", 1);
125             videoDriver = "x11";
126         } else {
127             unsetenv("SDL_VIDEO_YUV_HWACCEL");
128         }
129     }
130     setenv("SDL_VIDEO_ALLOW_SCREENSAVER", "1", 1);
131
132     if (m_winid == 0) {
133         // OpenGL monitor
134         m_mltConsumer = new Mlt::Consumer(*m_mltProfile, "sdl_audio");
135         m_mltConsumer->set("preview_off", 1);
136         m_mltConsumer->set("preview_format", mlt_image_rgb24a);
137         m_mltConsumer->listen("consumer-frame-show", this, (mlt_listener) consumer_gl_frame_show);
138     } else {
139         m_mltConsumer = new Mlt::Consumer(*m_mltProfile, "sdl_preview");
140         m_mltConsumer->set("window_id", m_winid);
141     }
142     m_mltConsumer->set("resize", 1);
143     //m_mltConsumer->set("terminate_on_pause", 1);
144     m_mltConsumer->set("window_background", KdenliveSettings::window_background().name().toUtf8().constData());
145     m_mltConsumer->set("rescale", "nearest");
146     
147     m_mltConsumer->listen("consumer-frame-show", this, (mlt_listener) rec_consumer_frame_preview);
148
149     QString audioDevice = KdenliveSettings::audiodevicename();
150     if (!audioDevice.isEmpty())
151         m_mltConsumer->set("audio_device", audioDevice.toUtf8().constData());
152
153     if (!videoDriver.isEmpty())
154         m_mltConsumer->set("video_driver", videoDriver.toUtf8().constData());
155
156     QString audioDriver = KdenliveSettings::audiodrivername();
157
158     if (!audioDriver.isEmpty())
159         m_mltConsumer->set("audio_driver", audioDriver.toUtf8().constData());
160
161     //m_mltConsumer->set("progressive", 1);
162     //m_mltConsumer->set("buffer", 1);
163     //m_mltConsumer->set("real_time", 0);
164 }
165
166 void MltDeviceCapture::stop()
167 {
168     bool isPlaylist = false;
169     m_captureDisplayWidget->stop();
170     if (m_mltConsumer) {
171         m_mltConsumer->set("refresh", 0);
172         m_mltConsumer->stop();
173         //if (!m_mltConsumer->is_stopped()) m_mltConsumer->stop();
174     }
175     if (m_mltProducer) {
176         QList <Mlt::Producer *> prods;
177         Mlt::Service service(m_mltProducer->parent().get_service());
178         mlt_service_lock(service.get_service());
179         if (service.type() == tractor_type) {
180             isPlaylist = true;
181             Mlt::Tractor tractor(service);
182             mlt_tractor_close(tractor.get_tractor());
183             Mlt::Field *field = tractor.field();
184             mlt_service nextservice = mlt_service_get_producer(service.get_service());
185             mlt_service nextservicetodisconnect;
186             mlt_properties properties = MLT_SERVICE_PROPERTIES(nextservice);
187             QString mlt_type = mlt_properties_get(properties, "mlt_type");
188             QString resource = mlt_properties_get(properties, "mlt_service");
189             // Delete all transitions
190             while (mlt_type == "transition") {
191                 nextservicetodisconnect = nextservice;
192                 nextservice = mlt_service_producer(nextservice);
193                 mlt_field_disconnect_service(field->get_field(), nextservicetodisconnect);
194                 if (nextservice == NULL) break;
195                 properties = MLT_SERVICE_PROPERTIES(nextservice);
196                 mlt_type = mlt_properties_get(properties, "mlt_type");
197                 resource = mlt_properties_get(properties, "mlt_service");
198             }
199             for (int trackNb = tractor.count() - 1; trackNb >= 0; --trackNb) {
200                 Mlt::Producer trackProducer(tractor.track(trackNb));
201                 Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
202                 if (trackPlaylist.type() == playlist_type) {
203                     for (int i = 0; i < trackPlaylist.count();i++) {
204                         // We need to manually decrease the ref count and close the producer, otherwise
205                         // the video4linux device stays open, seems like a bug in MLT that is not cleaning properly
206                         mlt_properties props = MLT_PRODUCER_PROPERTIES(trackPlaylist.get_clip(i)->get_parent());
207                         while (mlt_properties_ref_count(props) > 0) mlt_properties_dec_ref(props);
208                         if (trackPlaylist.get_clip(i)) mlt_producer_close(trackPlaylist.get_clip(i)->get_parent());
209                     }
210                     mlt_playlist_close(trackPlaylist.get_playlist());
211                 }
212             }
213             delete field;
214             field = NULL;
215         }
216         mlt_service_unlock(service.get_service());
217         delete m_mltProducer;
218         m_mltProducer = NULL;
219     }
220     // For some reason, the consumer seems to be deleted by previous stuff when in playlist mode
221     if (!isPlaylist && m_mltConsumer) delete m_mltConsumer;
222     m_mltConsumer = NULL;
223 }
224
225
226 void MltDeviceCapture::doRefresh()
227 {
228     if (m_mltConsumer) m_mltConsumer->set("refresh", 1);
229 }
230
231
232 void MltDeviceCapture::emitFrameUpdated(Mlt::Frame& frame)
233 {
234     mlt_image_format format = mlt_image_rgb24a;
235     int width = 0;
236     int height = 0;
237     const uchar* image = frame.get_image(format, width, height);
238     QImage qimage(width, height, QImage::Format_ARGB32);
239     memcpy(qimage.bits(), image, width * height * 4);
240     emit frameUpdated(qimage.rgbSwapped());
241 }
242
243 void MltDeviceCapture::showFrame(Mlt::Frame& frame)
244 {
245     mlt_image_format format = mlt_image_rgb24a;
246     int width = 0;
247     int height = 0;
248     const uchar* image = frame.get_image(format, width, height);
249     QImage qimage(width, height, QImage::Format_ARGB32_Premultiplied);
250     memcpy(qimage.scanLine(0), image, width * height * 4);
251     emit showImageSignal(qimage);
252     if (m_analyseAudio) showAudio(frame);
253     if (sendFrameForAnalysis && frame.get_frame()->convert_image) {
254         emit frameUpdated(qimage.rgbSwapped());
255     }
256 }
257
258 void MltDeviceCapture::showAudio(Mlt::Frame& frame)
259 {
260     if (!frame.is_valid() || frame.get_int("test_audio") != 0) {
261         return;
262     }
263     mlt_audio_format audio_format = mlt_audio_s16;
264     int freq = 0;
265     int num_channels = 0;
266     int samples = 0;
267     int16_t* data = (int16_t*)frame.get_audio(audio_format, freq, num_channels, samples);
268
269     if (!data) {
270         return;
271     }
272
273     // Data format: [ c00 c10 c01 c11 c02 c12 c03 c13 ... c0{samples-1} c1{samples-1} for 2 channels.
274     // So the vector is of size samples*channels.
275     QVector<int16_t> sampleVector(samples*num_channels);
276     memcpy(sampleVector.data(), data, samples*num_channels*sizeof(int16_t));
277
278     if (samples > 0) {
279         emit audioSamplesSignal(sampleVector, freq, num_channels, samples);
280     }
281 }
282
283 bool MltDeviceCapture::slotStartPreview(const QString &producer, bool xmlFormat)
284 {
285     if (m_mltConsumer == NULL) {
286         buildConsumer();
287     }
288     char *tmp = qstrdup(producer.toUtf8().constData());
289     if (xmlFormat) m_mltProducer = new Mlt::Producer(*m_mltProfile, "xml-string", tmp);
290     else m_mltProducer = new Mlt::Producer(*m_mltProfile, tmp);
291     delete[] tmp;
292
293     if (m_mltProducer == NULL || !m_mltProducer->is_valid()) {
294         if (m_mltProducer) {
295             delete m_mltProducer;
296             m_mltProducer = NULL;
297         }
298         kDebug()<<"//// ERROR CREATRING PROD";
299         return false;
300     }
301     m_mltConsumer->connect(*m_mltProducer);
302     if (m_mltConsumer->start() == -1) {
303         delete m_mltConsumer;
304         m_mltConsumer = NULL;
305         return 0;
306     }
307     return 1;
308 }
309
310 void MltDeviceCapture::gotCapturedFrame(Mlt::Frame& frame)
311 {
312     if (m_mltProducer) {
313         int dropped = m_mltProducer->get_int("dropped");
314         if (dropped != m_droppedFrames) {
315             m_droppedFrames = dropped;
316             emit droppedFrames(m_droppedFrames);
317         }
318     }
319     if (!m_livePreview) return;
320     mlt_image_format format = mlt_image_rgb24a;
321     int width = 0;
322     int height = 0;
323     const uchar* image = frame.get_image(format, width, height);
324     QImage qimage(width, height, QImage::Format_ARGB32);
325     memcpy(qimage.bits(), image, width * height * 4);
326     m_captureDisplayWidget->setImage(qimage.rgbSwapped());
327 }
328
329 void MltDeviceCapture::saveFrame(Mlt::Frame& frame)
330 {
331     mlt_image_format format = mlt_image_rgb24a;
332     int width = 0;
333     int height = 0;
334     const uchar* image = frame.get_image(format, width, height);
335     QImage qimage(width, height, QImage::Format_ARGB32);
336     memcpy(qimage.bits(), image, width * height * 4);
337
338     // Re-enable overlay
339     Mlt::Service service(m_mltProducer->parent().get_service());
340     Mlt::Tractor tractor(service);
341     Mlt::Producer trackProducer(tractor.track(0));
342     trackProducer.set("hide", 0);
343     
344     qimage.rgbSwapped().save(m_capturePath);
345     emit frameSaved(m_capturePath);
346     m_capturePath.clear();
347 }
348
349 void MltDeviceCapture::captureFrame(const QString &path)
350 {
351     if (m_mltProducer == NULL || !m_mltProducer->is_valid()) return;
352
353     // Hide overlay track before doing the capture
354     Mlt::Service service(m_mltProducer->parent().get_service());
355     Mlt::Tractor tractor(service);
356     Mlt::Producer trackProducer(tractor.track(0));
357     mlt_service_lock(service.get_service());
358     trackProducer.set("hide", 1);
359     m_mltConsumer->purge();
360     mlt_service_unlock(service.get_service());
361     m_capturePath = path;
362     // Wait for 5 frames before capture to make sure overlay is gone
363     doCapture = 5;
364 }
365
366 bool MltDeviceCapture::slotStartCapture(const QString &params, const QString &path, const QString &playlist, bool livePreview, bool xmlPlaylist)
367 {
368     stop();
369     m_livePreview = livePreview;
370     m_droppedFrames = 0;
371     if (m_mltProfile) delete m_mltProfile;
372     char *tmp = qstrdup(m_activeProfile.toUtf8().constData());
373     m_mltProfile = new Mlt::Profile(tmp);
374     delete[] tmp;
375     m_mltProfile->get_profile()->is_explicit = 1;
376     kDebug()<<"-- CREATING CAP: "<<params<<", PATH: "<<path;
377     tmp = qstrdup(QString("avformat:" + path).toUtf8().constData());
378     m_mltConsumer = new Mlt::Consumer(*m_mltProfile, tmp);
379     m_mltConsumer->set("terminate_on_pause", 1);
380     delete[] tmp;
381
382     QStringList paramList = params.split(" ", QString::SkipEmptyParts);
383     char *tmp2;
384     for (int i = 0; i < paramList.count(); i++) {
385         tmp = qstrdup(paramList.at(i).section("=", 0, 0).toUtf8().constData());
386         QString value = paramList.at(i).section("=", 1, 1);
387         if (value == "%threads") value = QString::number(QThread::idealThreadCount());
388         tmp2 = qstrdup(value.toUtf8().constData());
389         m_mltConsumer->set(tmp, tmp2);
390         delete[] tmp;
391         delete[] tmp2;
392     }
393     
394     if (m_mltConsumer == NULL || !m_mltConsumer->is_valid()) {
395         if (m_mltConsumer) {
396             delete m_mltConsumer;
397             m_mltConsumer = NULL;
398         }
399         return false;
400     }
401     
402     // FIXME: the event object returned by the listen gets leaked...
403     m_mltConsumer->listen("consumer-frame-show", this, (mlt_listener) rec_consumer_frame_show);
404     tmp = qstrdup(playlist.toUtf8().constData());
405     if (xmlPlaylist) {
406         // create an xml producer
407         m_mltProducer = new Mlt::Producer(*m_mltProfile, "xml-string", tmp);
408     }
409     else {
410         // create a producer based on mltproducer parameter
411         m_mltProducer = new Mlt::Producer(*m_mltProfile, tmp);
412     }
413     delete[] tmp;
414
415     if (m_mltProducer == NULL || !m_mltProducer->is_valid()) {
416         kDebug()<<"//// ERROR CREATRING PROD";
417         return false;
418     }
419
420     m_mltConsumer->connect(*m_mltProducer);
421     if (m_mltConsumer->start() == -1) {
422         delete m_mltConsumer;
423         m_mltConsumer = NULL;
424         return 0;
425     }
426     m_captureDisplayWidget->start();
427     return 1;
428 }
429
430
431 void MltDeviceCapture::setOverlay(const QString &path)
432 {
433     if (m_mltProducer == NULL || !m_mltProducer->is_valid()) return;
434     Mlt::Producer parentProd(m_mltProducer->parent());
435     if (parentProd.get_producer() == NULL) {
436         kDebug() << "PLAYLIST BROKEN, CANNOT INSERT CLIP //////";
437         return;
438     }
439
440     Mlt::Service service(parentProd.get_service());
441     if (service.type() != tractor_type) {
442         kWarning() << "// TRACTOR PROBLEM";
443         return;
444     }
445     Mlt::Tractor tractor(service);
446     if ( tractor.count() < 2) {
447         kWarning() << "// TRACTOR PROBLEM";
448         return;
449     }
450     mlt_service_lock(service.get_service());
451     Mlt::Producer trackProducer(tractor.track(0));
452     Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
453
454     trackPlaylist.remove(0);
455     if (path.isEmpty()) {
456         mlt_service_unlock(service.get_service());
457         return;
458     }
459
460     // Add overlay clip
461     char *tmp = qstrdup(path.toUtf8().constData());
462     Mlt::Producer *clip = new Mlt::Producer (*m_mltProfile, "loader", tmp);
463     delete[] tmp;
464     clip->set_in_and_out(0, 99999);
465     trackPlaylist.insert_at(0, clip, 1);
466
467     // Add transition
468     mlt_service serv = m_mltProducer->parent().get_service();
469     mlt_service nextservice = mlt_service_get_producer(serv);
470     mlt_properties properties = MLT_SERVICE_PROPERTIES(nextservice);
471     QString mlt_type = mlt_properties_get(properties, "mlt_type");
472     if (mlt_type != "transition") {
473         // transition does not exist, add it
474         Mlt::Field *field = tractor.field();
475         Mlt::Transition *transition = new Mlt::Transition(*m_mltProfile, "composite");
476         transition->set_in_and_out(0, 0);
477         transition->set("geometry", "0,0:100%x100%:70");
478         transition->set("fill", 1);
479         transition->set("operator", "and");
480         transition->set("a_track", 0);
481         transition->set("b_track", 1);
482         field->plant_transition(*transition, 0, 1);
483     }
484     mlt_service_unlock(service.get_service());
485     //delete clip;
486 }
487
488 void MltDeviceCapture::setOverlayEffect(const QString tag, QStringList parameters)
489 {
490     if (m_mltProducer == NULL || !m_mltProducer->is_valid()) return;
491     Mlt::Service service(m_mltProducer->parent().get_service());
492     Mlt::Tractor tractor(service);
493     Mlt::Producer trackProducer(tractor.track(0));
494     Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
495     Mlt::Service trackService(trackProducer.get_service());
496
497     mlt_service_lock(service.get_service());
498
499     // delete previous effects
500     Mlt::Filter *filter;
501     filter = trackService.filter(0);
502     if (filter && !tag.isEmpty()) {
503         QString currentService = filter->get("mlt_service");
504         if (currentService == tag) {
505             // Effect is already there
506             mlt_service_unlock(service.get_service());
507             return;
508         }
509     }
510     while (filter) {
511         trackService.detach(*filter);
512         delete filter;
513         filter = trackService.filter(0);
514     }
515     
516     if (tag.isEmpty()) {
517         mlt_service_unlock(service.get_service());
518         return;
519     }
520     
521     char *tmp = qstrdup(tag.toUtf8().constData());
522     filter = new Mlt::Filter(*m_mltProfile, tmp);
523     delete[] tmp;
524     if (filter && filter->is_valid()) {
525         for (int j = 0; j < parameters.count(); j++) {
526             filter->set(parameters.at(j).section("=", 0, 0).toUtf8().constData(), parameters.at(j).section("=", 1, 1).toUtf8().constData());
527         }
528         trackService.attach(*filter);
529     }
530     mlt_service_unlock(service.get_service());
531 }
532
533 void MltDeviceCapture::mirror(bool activate)
534 {
535     if (m_mltProducer == NULL || !m_mltProducer->is_valid()) return;
536     Mlt::Service service(m_mltProducer->parent().get_service());
537     Mlt::Tractor tractor(service);
538     Mlt::Producer trackProducer(tractor.track(1));
539     Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
540     Mlt::Service trackService(trackProducer.get_service());
541
542     mlt_service_lock(service.get_service());
543
544     // delete previous effects
545     Mlt::Filter *filter;
546     filter = trackService.filter(0);
547     while (filter) {
548         trackService.detach(*filter);
549         delete filter;
550         filter = trackService.filter(0);
551     }
552
553     if (!activate) {
554         mlt_service_unlock(service.get_service());
555         return;
556     }
557
558     filter = new Mlt::Filter(*m_mltProfile, "mirror");
559     if (filter && filter->is_valid()) {
560         filter->set("mirror", "flip");
561         trackService.attach(*filter);
562     }
563     mlt_service_unlock(service.get_service());
564 }
565