1 /***************************************************************************
2 mltdevicecapture.cpp - description
4 begin : Sun May 21 2011
5 copyright : (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org)
7 ***************************************************************************/
9 /***************************************************************************
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. *
16 ***************************************************************************/
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"
26 #include <mlt++/Mlt.h>
29 #include <KStandardDirs>
30 #include <KMessageBox>
32 #include <KTemporaryFile>
37 #include <QApplication>
47 static void consumer_gl_frame_show(mlt_consumer, MltDeviceCapture * self, mlt_frame frame_ptr)
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);
54 static void rec_consumer_frame_show(mlt_consumer, MltDeviceCapture * self, mlt_frame frame_ptr)
56 Mlt::Frame frame(frame_ptr);
57 if (!frame.is_valid()) return;
58 self->gotCapturedFrame(frame);
61 static void rec_consumer_frame_preview(mlt_consumer, MltDeviceCapture * self, mlt_frame frame_ptr)
63 Mlt::Frame frame(frame_ptr);
64 if (!frame.is_valid()) return;
65 if (self->sendFrameForAnalysis && frame_ptr->convert_image) {
66 self->emitFrameUpdated(frame);
68 if (self->doCapture > 0) {
70 if (self->doCapture == 0) self->saveFrame(frame);
73 /* if (self->analyseAudio) {
74 self->showAudio(frame);
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) {
80 self->emitConsumerStopped();
85 MltDeviceCapture::MltDeviceCapture(QString profile, VideoPreviewContainer *surface, QWidget *parent) :
86 AbstractRender("capture", parent),
88 sendFrameForAnalysis(false),
94 m_captureDisplayWidget(surface),
95 m_winid((int) surface->winId()),
96 m_analyseAudio(KdenliveSettings::monitor_audio())
98 if (profile.isEmpty()) profile = KdenliveSettings::current_profile();
99 buildConsumer(profile);
102 MltDeviceCapture::~MltDeviceCapture()
104 if (m_mltConsumer) delete m_mltConsumer;
105 if (m_mltProducer) delete m_mltProducer;
106 if (m_mltProfile) delete m_mltProfile;
109 void MltDeviceCapture::buildConsumer(const QString &profileName)
111 if (!profileName.isEmpty()) m_activeProfile = profileName;
113 if (m_mltProfile) delete m_mltProfile;
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;
121 QString videoDriver = KdenliveSettings::videodrivername();
122 if (!videoDriver.isEmpty()) {
123 if (videoDriver == "x11_noaccel") {
124 setenv("SDL_VIDEO_YUV_HWACCEL", "0", 1);
127 unsetenv("SDL_VIDEO_YUV_HWACCEL");
130 setenv("SDL_VIDEO_ALLOW_SCREENSAVER", "1", 1);
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);
139 m_mltConsumer = new Mlt::Consumer(*m_mltProfile, "sdl_preview");
140 m_mltConsumer->set("window_id", m_winid);
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");
147 m_mltConsumer->listen("consumer-frame-show", this, (mlt_listener) rec_consumer_frame_preview);
149 QString audioDevice = KdenliveSettings::audiodevicename();
150 if (!audioDevice.isEmpty())
151 m_mltConsumer->set("audio_device", audioDevice.toUtf8().constData());
153 if (!videoDriver.isEmpty())
154 m_mltConsumer->set("video_driver", videoDriver.toUtf8().constData());
156 QString audioDriver = KdenliveSettings::audiodrivername();
158 if (!audioDriver.isEmpty())
159 m_mltConsumer->set("audio_driver", audioDriver.toUtf8().constData());
161 //m_mltConsumer->set("progressive", 1);
162 //m_mltConsumer->set("buffer", 1);
163 //m_mltConsumer->set("real_time", 0);
166 void MltDeviceCapture::stop()
168 bool isPlaylist = false;
169 m_captureDisplayWidget->stop();
171 m_mltConsumer->set("refresh", 0);
172 m_mltConsumer->stop();
173 //if (!m_mltConsumer->is_stopped()) m_mltConsumer->stop();
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) {
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");
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());
210 mlt_playlist_close(trackPlaylist.get_playlist());
216 mlt_service_unlock(service.get_service());
217 delete m_mltProducer;
218 m_mltProducer = NULL;
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;
226 void MltDeviceCapture::doRefresh()
228 if (m_mltConsumer) m_mltConsumer->set("refresh", 1);
232 void MltDeviceCapture::emitFrameUpdated(Mlt::Frame& frame)
234 mlt_image_format format = mlt_image_rgb24a;
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());
243 void MltDeviceCapture::showFrame(Mlt::Frame& frame)
245 mlt_image_format format = mlt_image_rgb24a;
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());
258 void MltDeviceCapture::showAudio(Mlt::Frame& frame)
260 if (!frame.is_valid() || frame.get_int("test_audio") != 0) {
263 mlt_audio_format audio_format = mlt_audio_s16;
265 int num_channels = 0;
267 int16_t* data = (int16_t*)frame.get_audio(audio_format, freq, num_channels, samples);
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));
279 emit audioSamplesSignal(sampleVector, freq, num_channels, samples);
283 bool MltDeviceCapture::slotStartPreview(const QString &producer, bool xmlFormat)
285 if (m_mltConsumer == NULL) {
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);
293 if (m_mltProducer == NULL || !m_mltProducer->is_valid()) {
295 delete m_mltProducer;
296 m_mltProducer = NULL;
298 kDebug()<<"//// ERROR CREATRING PROD";
301 m_mltConsumer->connect(*m_mltProducer);
302 if (m_mltConsumer->start() == -1) {
303 delete m_mltConsumer;
304 m_mltConsumer = NULL;
310 void MltDeviceCapture::gotCapturedFrame(Mlt::Frame& frame)
313 int dropped = m_mltProducer->get_int("dropped");
314 if (dropped != m_droppedFrames) {
315 m_droppedFrames = dropped;
316 emit droppedFrames(m_droppedFrames);
319 if (!m_livePreview) return;
320 mlt_image_format format = mlt_image_rgb24a;
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());
329 void MltDeviceCapture::saveFrame(Mlt::Frame& frame)
331 mlt_image_format format = mlt_image_rgb24a;
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);
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);
344 qimage.rgbSwapped().save(m_capturePath);
345 emit frameSaved(m_capturePath);
346 m_capturePath.clear();
349 void MltDeviceCapture::captureFrame(const QString &path)
351 if (m_mltProducer == NULL || !m_mltProducer->is_valid()) return;
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
366 bool MltDeviceCapture::slotStartCapture(const QString ¶ms, const QString &path, const QString &playlist, bool livePreview, bool xmlPlaylist)
369 m_livePreview = livePreview;
371 if (m_mltProfile) delete m_mltProfile;
372 char *tmp = qstrdup(m_activeProfile.toUtf8().constData());
373 m_mltProfile = new Mlt::Profile(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);
382 QStringList paramList = params.split(" ", QString::SkipEmptyParts);
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);
394 if (m_mltConsumer == NULL || !m_mltConsumer->is_valid()) {
396 delete m_mltConsumer;
397 m_mltConsumer = NULL;
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());
406 // create an xml producer
407 m_mltProducer = new Mlt::Producer(*m_mltProfile, "xml-string", tmp);
410 // create a producer based on mltproducer parameter
411 m_mltProducer = new Mlt::Producer(*m_mltProfile, tmp);
415 if (m_mltProducer == NULL || !m_mltProducer->is_valid()) {
416 kDebug()<<"//// ERROR CREATRING PROD";
420 m_mltConsumer->connect(*m_mltProducer);
421 if (m_mltConsumer->start() == -1) {
422 delete m_mltConsumer;
423 m_mltConsumer = NULL;
426 m_captureDisplayWidget->start();
431 void MltDeviceCapture::setOverlay(const QString &path)
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 //////";
440 Mlt::Service service(parentProd.get_service());
441 if (service.type() != tractor_type) {
442 kWarning() << "// TRACTOR PROBLEM";
445 Mlt::Tractor tractor(service);
446 if ( tractor.count() < 2) {
447 kWarning() << "// TRACTOR PROBLEM";
450 mlt_service_lock(service.get_service());
451 Mlt::Producer trackProducer(tractor.track(0));
452 Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service());
454 trackPlaylist.remove(0);
455 if (path.isEmpty()) {
456 mlt_service_unlock(service.get_service());
461 char *tmp = qstrdup(path.toUtf8().constData());
462 Mlt::Producer *clip = new Mlt::Producer (*m_mltProfile, "loader", tmp);
464 clip->set_in_and_out(0, 99999);
465 trackPlaylist.insert_at(0, clip, 1);
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);
484 mlt_service_unlock(service.get_service());
488 void MltDeviceCapture::setOverlayEffect(const QString tag, QStringList parameters)
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());
497 mlt_service_lock(service.get_service());
499 // delete previous effects
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());
511 trackService.detach(*filter);
513 filter = trackService.filter(0);
517 mlt_service_unlock(service.get_service());
521 char *tmp = qstrdup(tag.toUtf8().constData());
522 filter = new Mlt::Filter(*m_mltProfile, 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());
528 trackService.attach(*filter);
530 mlt_service_unlock(service.get_service());
533 void MltDeviceCapture::mirror(bool activate)
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());
542 mlt_service_lock(service.get_service());
544 // delete previous effects
546 filter = trackService.filter(0);
548 trackService.detach(*filter);
550 filter = trackService.filter(0);
554 mlt_service_unlock(service.get_service());
558 filter = new Mlt::Filter(*m_mltProfile, "mirror");
559 if (filter && filter->is_valid()) {
560 filter->set("mirror", "flip");
561 trackService.attach(*filter);
563 mlt_service_unlock(service.get_service());