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