OpenShot Library | libopenshot 1.1.1
Loading...
Searching...
No Matches
Frame.cpp
Go to the documentation of this file.
1
9// Copyright (c) 2008-2019 OpenShot Studios, LLC
10//
11// SPDX-License-Identifier: LGPL-3.0-or-later
12
13#include <thread> // for std::this_thread::sleep_for
14#include <chrono> // for std::chrono::milliseconds
15#include <iomanip>
16#include <limits>
17
18#include "Frame.h"
19#include "AudioBufferSource.h"
20#include "AudioResampler.h"
21#include "QtUtilities.h"
22
23#include <AppConfig.h>
24#include <juce_audio_basics/juce_audio_basics.h>
25#include <juce_audio_devices/juce_audio_devices.h>
26
27#include <QApplication>
28#include <QImage>
29#include <QPixmap>
30#include <QBitmap>
31#include <QColor>
32#include <QString>
33#include <QVector>
34#include <QPainter>
35#include <QHBoxLayout>
36#include <QWidget>
37#include <QLabel>
38#include <QPointF>
39#include <QWidget>
40
41using namespace std;
42using namespace openshot;
43
44// Constructor - image & audio
45Frame::Frame(int64_t number, int width, int height, std::string color, int samples, int channels)
46 : audio(std::make_shared<juce::AudioBuffer<float>>(channels, samples)),
47 number(number), capture_timestamp(std::numeric_limits<double>::quiet_NaN()), width(width), height(height),
48 pixel_ratio(1,1), color(color),
49 channels(channels), channel_layout(LAYOUT_STEREO),
50 sample_rate(44100),
51 has_audio_data(false), has_image_data(false),
52 max_audio_sample(0), audio_is_increasing(true)
53{
54 // zero (fill with silence) the audio buffer
55 audio->clear();
56}
57
58// Delegating Constructor - blank frame
59Frame::Frame() : Frame::Frame(1, 1, 1, "#000000", 0, 2) {}
60
61// Delegating Constructor - image only
62Frame::Frame(int64_t number, int width, int height, std::string color)
63 : Frame::Frame(number, width, height, color, 0, 2) {}
64
65// Delegating Constructor - audio only
66Frame::Frame(int64_t number, int samples, int channels)
67 : Frame::Frame(number, 1, 1, "#000000", samples, channels) {}
68
69
70// Copy constructor
71Frame::Frame ( const Frame &other )
72{
73 // copy pointers and data
74 DeepCopy(other);
75}
76
77// Assignment operator
79{
80 // copy pointers and data
81 DeepCopy(other);
82
83 return *this;
84}
85
86// Copy data and pointers from another Frame instance
87void Frame::DeepCopy(const Frame& other)
88{
89 number = other.number;
91 channels = other.channels;
92 width = other.width;
93 height = other.height;
94 channel_layout = other.channel_layout;
97 sample_rate = other.sample_rate;
98 pixel_ratio = Fraction(other.pixel_ratio.num, other.pixel_ratio.den);
99 color = other.color;
100 max_audio_sample = other.max_audio_sample;
101 audio_is_increasing = other.audio_is_increasing;
102
103 if (other.image)
104 image = std::make_shared<QImage>(*(other.image));
105 if (other.audio)
106 audio = std::make_shared<juce::AudioBuffer<float>>(*(other.audio));
107 if (other.wave_image)
108 wave_image = std::make_shared<QImage>(*(other.wave_image));
109}
110
111// Destructor
113 // Clear all pointers
114 image.reset();
115 audio.reset();
116 #ifdef USE_OPENCV
117 imagecv.release();
118 #endif
119}
120
121// Display the frame image to the screen (primarily used for debugging reasons)
123{
124 if (!QApplication::instance()) {
125 // Only create the QApplication once
126 static int argc = 1;
127 static char* argv[1] = {NULL};
128 previewApp = std::make_shared<QApplication>(argc, argv);
129 }
130
131 // Get preview image
132 std::shared_ptr<QImage> previewImage = GetImage();
133
134 // Update the image to reflect the correct pixel aspect ration (i.e. to fix non-square pixels)
135 if (pixel_ratio.num != 1 || pixel_ratio.den != 1)
136 {
137 // Resize to fix DAR
138 previewImage = std::make_shared<QImage>(previewImage->scaled(
139 previewImage->size().width(), previewImage->size().height() * pixel_ratio.Reciprocal().ToDouble(),
140 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
141 }
142
143 // Create window
144 QWidget previewWindow;
145 previewWindow.setStyleSheet("background-color: #000000;");
146 QHBoxLayout layout;
147
148 // Create label with current frame's image
149 QLabel previewLabel;
150 previewLabel.setPixmap(QPixmap::fromImage(*previewImage));
151 previewLabel.setMask(QPixmap::fromImage(*previewImage).mask());
152 layout.addWidget(&previewLabel);
153
154 // Show the window
155 previewWindow.setLayout(&layout);
156 previewWindow.show();
157 previewApp->exec();
158}
159
160// Get an audio waveform image
161std::shared_ptr<QImage> Frame::GetWaveform(int width, int height, int Red, int Green, int Blue, int Alpha)
162{
163 // Clear any existing waveform image
165
166 // Init a list of lines
167 QVector<QPointF> lines;
168 QVector<QPointF> labels;
169
170 // Calculate width of an image based on the # of samples
171 int total_samples = GetAudioSamplesCount();
172 if (total_samples > 0)
173 {
174 // If samples are present...
175 int new_height = 200 * audio->getNumChannels();
176 int height_padding = 20 * (audio->getNumChannels() - 1);
177 int total_height = new_height + height_padding;
178 int total_width = 0;
179 float zero_height = 1.0; // Used to clamp near-zero vales to this value to prevent gaps
180
181 // Loop through each audio channel
182 float Y = 100.0;
183 for (int channel = 0; channel < audio->getNumChannels(); channel++)
184 {
185 float X = 0.0;
186
187 // Get audio for this channel
188 const float *samples = audio->getReadPointer(channel);
189
190 for (int sample = 0; sample < GetAudioSamplesCount(); sample++, X++)
191 {
192 // Sample value (scaled to -100 to 100)
193 float value = samples[sample] * 100.0;
194
195 // Set threshold near zero (so we don't allow near-zero values)
196 // This prevents empty gaps from appearing in the waveform
197 if (value > -zero_height && value < 0.0) {
198 value = -zero_height;
199 } else if (value > 0.0 && value < zero_height) {
200 value = zero_height;
201 }
202
203 // Append a line segment for each sample
204 lines.push_back(QPointF(X, Y));
205 lines.push_back(QPointF(X, Y - value));
206 }
207
208 // Add Channel Label Coordinate
209 labels.push_back(QPointF(5.0, Y - 5.0));
210
211 // Increment Y
212 Y += (200 + height_padding);
213 total_width = X;
214 }
215
216 // Create blank image
217 wave_image = std::make_shared<QImage>(
218 total_width, total_height, QImage::Format_RGBA8888_Premultiplied);
219 wave_image->fill(QColor(0,0,0,0));
220
221 // Load QPainter with wave_image device
222 QPainter painter(wave_image.get());
223
224 // Set pen color
225 QPen pen;
226 pen.setColor(QColor(Red, Green, Blue, Alpha));
227 pen.setWidthF(1.0);
228 pen.setStyle(Qt::SolidLine);
229 painter.setPen(pen);
230
231 // Draw the waveform
232 painter.drawLines(lines);
233 painter.end();
234 }
235 else
236 {
237 // No audio samples present
238 wave_image = std::make_shared<QImage>(width, height, QImage::Format_RGBA8888_Premultiplied);
239 wave_image->fill(QColor(QString::fromStdString("#000000")));
240 }
241
242 // Resize Image (if needed)
243 if (wave_image->width() != width || wave_image->height() != height) {
244 QImage scaled_wave_image = wave_image->scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
245 wave_image = std::make_shared<QImage>(scaled_wave_image);
246 }
247
248 // Return new image
249 return wave_image;
250}
251
252// Clear the waveform image (and deallocate its memory)
254{
255 if (wave_image)
256 wave_image.reset();
257}
258
259// Get an audio waveform image pixels
260const unsigned char* Frame::GetWaveformPixels(int width, int height, int Red, int Green, int Blue, int Alpha)
261{
262 // Get audio wave form image
263 wave_image = GetWaveform(width, height, Red, Green, Blue, Alpha);
264
265 // Return array of pixel packets
266 return wave_image->constBits();
267}
268
269// Display the wave form
271{
272 // Get audio wave form image
273 GetWaveform(720, 480, 0, 123, 255, 255);
274
275 if (!QApplication::instance()) {
276 // Only create the QApplication once
277 static int argc = 1;
278 static char* argv[1] = {NULL};
279 previewApp = std::make_shared<QApplication>(argc, argv);
280 }
281
282 // Create window
283 QWidget previewWindow;
284 previewWindow.setStyleSheet("background-color: #000000;");
285 QHBoxLayout layout;
286
287 // Create label with current frame's waveform image
288 QLabel previewLabel;
289 previewLabel.setPixmap(QPixmap::fromImage(*wave_image));
290 previewLabel.setMask(QPixmap::fromImage(*wave_image).mask());
291 layout.addWidget(&previewLabel);
292
293 // Show the window
294 previewWindow.setLayout(&layout);
295 previewWindow.show();
296 previewApp->exec();
297
298 // Deallocate waveform image
300}
301
302// Get magnitude of range of samples (if channel is -1, return average of all channels for that sample)
303float Frame::GetAudioSample(int channel, int sample, int magnitude_range)
304{
305 if (channel > 0) {
306 // return average magnitude for a specific channel/sample range
307 return audio->getMagnitude(channel, sample, magnitude_range);
308
309 } else {
310 // Return average magnitude for all channels
311 return audio->getMagnitude(sample, magnitude_range);
312 }
313}
314
315// Get an array of sample data (and optional reverse the sample values)
316float* Frame::GetAudioSamples(int channel) {
317
318 // Copy audio data
319 juce::AudioBuffer<float> *buffer(audio.get());
320
321 // return JUCE audio data for this channel
322 return buffer->getWritePointer(channel);
323}
324
325// Get an array of sample data (all channels interleaved together), using any sample rate
326float* Frame::GetInterleavedAudioSamples(int* sample_count)
327{
328 // Copy audio data
329 juce::AudioBuffer<float> *buffer(audio.get());
330
331 float *output = NULL;
332 int num_of_channels = audio->getNumChannels();
333 int num_of_samples = GetAudioSamplesCount();
334
335 // INTERLEAVE all samples together (channel 1 + channel 2 + channel 1 + channel 2, etc...)
336 output = new float[num_of_channels * num_of_samples];
337 int position = 0;
338
339 // Loop through samples in each channel (combining them)
340 for (int sample = 0; sample < num_of_samples; sample++)
341 {
342 for (int channel = 0; channel < num_of_channels; channel++)
343 {
344 // Add sample to output array
345 output[position] = buffer->getReadPointer(channel)[sample];
346
347 // increment position
348 position++;
349 }
350 }
351
352 // Update sample count (since it might have changed due to resampling)
353 *sample_count = num_of_samples;
354
355 // return combined array
356 return output;
357}
358
359// Get number of audio channels
361{
362 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
363 if (audio)
364 return audio->getNumChannels();
365 else
366 return 0;
367}
368
369// Get number of audio samples
371{
372 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
373 return max_audio_sample;
374}
375
380
381// Get the size in bytes of this frame (rough estimate)
383{
384 int64_t total_bytes = 0;
385 if (image) {
386 total_bytes += static_cast<int64_t>(
387 width * height * sizeof(char) * 4);
388 }
389 if (audio) {
390 // approximate audio size (sample rate / 24 fps)
391 total_bytes += (sample_rate / 24.0) * sizeof(float);
392 }
393
394 // return size of this frame
395 return total_bytes;
396}
397
398// Get pixel data (as packets)
399const unsigned char* Frame::GetPixels()
400{
401 // Check for blank image
402 if (!image)
403 // Fill with black
404 AddColor(width, height, color);
405
406 // Return array of pixel packets
407 return image->constBits();
408}
409
410// Get pixel data (for only a single scan-line)
411const unsigned char* Frame::GetPixels(int row)
412{
413 // Check for blank image
414 if (!image)
415 // Fill with black
416 AddColor(width, height, color);
417
418 // Return array of pixel packets
419 return image->constScanLine(row);
420}
421
422// Check a specific pixel color value (returns True/False)
423bool Frame::CheckPixel(int row, int col, int red, int green, int blue, int alpha, int threshold) {
424 int col_pos = col * 4; // Find column array position
425 if (!image || row < 0 || row >= (height - 1) ||
426 col_pos < 0 || col_pos >= (width - 1) ) {
427 // invalid row / col
428 return false;
429 }
430 // Check pixel color
431 const unsigned char* pixels = GetPixels(row);
432 if (pixels[col_pos + 0] >= (red - threshold) && pixels[col_pos + 0] <= (red + threshold) &&
433 pixels[col_pos + 1] >= (green - threshold) && pixels[col_pos + 1] <= (green + threshold) &&
434 pixels[col_pos + 2] >= (blue - threshold) && pixels[col_pos + 2] <= (blue + threshold) &&
435 pixels[col_pos + 3] >= (alpha - threshold) && pixels[col_pos + 3] <= (alpha + threshold)) {
436 // Pixel color matches successfully
437 return true;
438 } else {
439 // Pixel color does not match
440 return false;
441 }
442}
443
444// Set Pixel Aspect Ratio
445void Frame::SetPixelRatio(int num, int den)
446{
447 pixel_ratio.num = num;
448 pixel_ratio.den = den;
449}
450
451// Set frame number
452void Frame::SetFrameNumber(int64_t new_number)
453{
454 number = new_number;
455}
456
457// Calculate the # of samples per video frame (for a specific frame number and frame rate)
458int Frame::GetSamplesPerFrame(int64_t number, Fraction fps, int sample_rate, int channels)
459{
460 // Directly return 0 for invalid audio/frame-rate parameters
461 // so that we do not need to deal with NaNs later
462 if (channels <= 0 || sample_rate <= 0 || fps.num <= 0 || fps.den <= 0) return 0;
463
464 // Get the total # of samples for the previous frame, and the current frame (rounded)
465 double fps_rate = fps.Reciprocal().ToDouble();
466
467 // Determine previous samples total, and make sure it's evenly divisible by the # of channels
468 double previous_samples = (sample_rate * fps_rate) * (number - 1);
469 double previous_samples_remainder = fmod(previous_samples, (double)channels); // subtract the remainder to the total (to make it evenly divisible)
470 previous_samples -= previous_samples_remainder;
471
472 // Determine the current samples total, and make sure it's evenly divisible by the # of channels
473 double total_samples = (sample_rate * fps_rate) * number;
474 double total_samples_remainder = fmod(total_samples, (double)channels); // subtract the remainder to the total (to make it evenly divisible)
475 total_samples -= total_samples_remainder;
476
477 // Subtract the previous frame's total samples with this frame's total samples. Not all sample rates can
478 // be evenly divided into frames, so each frame can have have different # of samples.
479 int samples_per_frame = round(total_samples - previous_samples);
480 if (samples_per_frame < 0)
481 samples_per_frame = 0;
482 return samples_per_frame;
483}
484
485// Calculate the # of samples per video frame (for the current frame number)
486int Frame::GetSamplesPerFrame(Fraction fps, int sample_rate, int channels)
487{
488 return GetSamplesPerFrame(number, fps, sample_rate, channels);
489}
490
491// Get height of image
493{
494 return height;
495}
496
497// Get height of image
499{
500 return width;
501}
502
503// Get the original sample rate of this frame's audio data
505{
506 return sample_rate;
507}
508
509// Get the original sample rate of this frame's audio data
511{
512 return channel_layout;
513}
514
515
516// Save the frame image to the specified path. The image format is determined from the extension (i.e. image.PNG, image.JPEG)
517void Frame::Save(std::string path, float scale, std::string format, int quality)
518{
519 // Get preview image
520 std::shared_ptr<QImage> previewImage = GetImage();
521
522 // Update the image to reflect the correct pixel aspect ration (i.e. to fix non-square pixels)
523 if (pixel_ratio.num != 1 || pixel_ratio.den != 1)
524 {
525 // Resize to fix DAR
526 previewImage = std::make_shared<QImage>(previewImage->scaled(
527 previewImage->size().width(), previewImage->size().height() * pixel_ratio.Reciprocal().ToDouble(),
528 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
529 }
530
531 // scale image if needed
532 if (fabs(scale) > 1.001 || fabs(scale) < 0.999)
533 {
534 // Resize image
535 previewImage = std::make_shared<QImage>(previewImage->scaled(
536 previewImage->size().width() * scale, previewImage->size().height() * scale,
537 Qt::KeepAspectRatio, Qt::SmoothTransformation));
538 }
539
540 // Save image
541 previewImage->save(QString::fromStdString(path), format.c_str(), quality);
542}
543
544// Thumbnail the frame image to the specified path. The image format is determined from the extension (i.e. image.PNG, image.JPEG)
545void Frame::Thumbnail(std::string path, int new_width, int new_height, std::string mask_path, std::string overlay_path,
546 std::string background_color, bool ignore_aspect, std::string format, int quality, float rotate, ScaleType scale_mode) {
547
548 // Create blank thumbnail image & fill background color
549 auto thumbnail = std::make_shared<QImage>(
550 new_width, new_height, QImage::Format_RGBA8888_Premultiplied);
551 thumbnail->fill(QColor(QString::fromStdString(background_color)));
552
553 // Create painter
554 QPainter painter(thumbnail.get());
555 painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing, true);
556
557 // Get preview image
558 std::shared_ptr<QImage> previewImage = GetImage();
559
560 // Update the image to reflect the correct pixel aspect ration (i.e. to fix non-squar pixels)
561 if (pixel_ratio.num != 1 || pixel_ratio.den != 1)
562 {
563 // Calculate correct DAR (display aspect ratio)
564 int aspect_width = previewImage->size().width();
565 int aspect_height = previewImage->size().height() * pixel_ratio.Reciprocal().ToDouble();
566
567 // Resize to fix DAR
568 previewImage = std::make_shared<QImage>(previewImage->scaled(
569 aspect_width, aspect_height,
570 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
571 }
572
573 // Resize frame image
574 Qt::AspectRatioMode aspect_ratio_mode = Qt::KeepAspectRatio;
575 if (ignore_aspect) {
576 aspect_ratio_mode = Qt::IgnoreAspectRatio;
577 } else {
578 switch (scale_mode) {
579 case SCALE_CROP:
580 aspect_ratio_mode = Qt::KeepAspectRatioByExpanding;
581 break;
582 case SCALE_STRETCH:
583 aspect_ratio_mode = Qt::IgnoreAspectRatio;
584 break;
585 case SCALE_FIT:
586 case SCALE_NONE:
587 default:
588 aspect_ratio_mode = Qt::KeepAspectRatio;
589 break;
590 }
591 }
592
593 previewImage = std::make_shared<QImage>(previewImage->scaled(
594 new_width, new_height,
595 aspect_ratio_mode, Qt::SmoothTransformation));
596
597 // Composite frame image onto background (centered)
598 int x = (new_width - previewImage->size().width()) / 2.0; // center
599 int y = (new_height - previewImage->size().height()) / 2.0; // center
600 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
601
602
603 // Create transform and rotate (if needed)
604 QTransform transform;
605 float origin_x = previewImage->width() / 2.0;
606 float origin_y = previewImage->height() / 2.0;
607 transform.translate(origin_x, origin_y);
608 transform.rotate(rotate);
609 transform.translate(-origin_x,-origin_y);
610 painter.setTransform(transform);
611
612 // Draw image onto QImage
613 painter.drawImage(x, y, *previewImage);
614
615
616 // Overlay Image (if any)
617 if (overlay_path != "") {
618 // Open overlay
619 auto overlay = std::make_shared<QImage>();
620 overlay->load(QString::fromStdString(overlay_path));
621
622 // Set pixel format
623 overlay = std::make_shared<QImage>(
624 overlay->convertToFormat(QImage::Format_RGBA8888_Premultiplied));
625
626 // Resize to fit
627 overlay = std::make_shared<QImage>(overlay->scaled(
628 new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
629
630 // Composite onto thumbnail
631 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
632 painter.drawImage(0, 0, *overlay);
633 }
634
635
636 // Mask Image (if any)
637 if (mask_path != "") {
638 // Open mask
639 auto mask = std::make_shared<QImage>();
640 mask->load(QString::fromStdString(mask_path));
641
642 // Set pixel format
643 mask = std::make_shared<QImage>(
644 mask->convertToFormat(QImage::Format_RGBA8888_Premultiplied));
645
646 // Resize to fit
647 mask = std::make_shared<QImage>(mask->scaled(
648 new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
649
650 // Negate mask
651 mask->invertPixels();
652
653 // Get pixels
654 unsigned char *pixels = static_cast<unsigned char *>(thumbnail->bits());
655 const unsigned char *mask_pixels = static_cast<const unsigned char *>(mask->constBits());
656
657 // Convert the mask image to grayscale
658 // Loop through pixels
659 for (int pixel = 0, byte_index=0; pixel < new_width * new_height; pixel++, byte_index+=4)
660 {
661 // Get the RGB values from the pixel
662 int gray_value = qGray(mask_pixels[byte_index], mask_pixels[byte_index] + 1, mask_pixels[byte_index] + 2);
663 int Frame_Alpha = pixels[byte_index + 3];
664 int Mask_Value = constrain(Frame_Alpha - gray_value);
665
666 // Set all alpha pixels to gray value
667 pixels[byte_index + 3] = Mask_Value;
668 }
669 }
670
671
672 // End painter
673 painter.end();
674
675 // Save image
676 thumbnail->save(QString::fromStdString(path), format.c_str(), quality);
677}
678
679// Constrain a color value from 0 to 255
680int Frame::constrain(int color_value)
681{
682 // Constrain new color from 0 to 255
683 if (color_value < 0)
684 color_value = 0;
685 else if (color_value > 255)
686 color_value = 255;
687
688 return color_value;
689}
690
691void Frame::AddColor(int new_width, int new_height, std::string new_color)
692{
693 const std::lock_guard<std::recursive_mutex> lock(addingImageMutex);
694 // Update parameters
695 width = new_width;
696 height = new_height;
697 color = new_color;
698 AddColor(QColor(QString::fromStdString(new_color)));
699}
700
701// Add (or replace) pixel data to the frame (based on a solid color)
702void Frame::AddColor(const QColor& new_color)
703{
704 // Create new image object, and fill with pixel data
705 const std::lock_guard<std::recursive_mutex> lock(addingImageMutex);
706 image = std::make_shared<QImage>(width, height, QImage::Format_RGBA8888_Premultiplied);
707
708 // Fill with solid color
709 image->fill(new_color);
710 has_image_data = true;
711}
712
713// Add (or replace) pixel data to the frame
715 int new_width, int new_height, int bytes_per_pixel,
716 QImage::Format type, const unsigned char *pixels_)
717{
718 if (has_image_data) {
719 // Delete the previous QImage
720 image.reset();
721 }
722
723 // Create new image object from pixel data
724 auto new_image = std::make_shared<QImage>(
725 pixels_,
726 new_width, new_height,
727 new_width * bytes_per_pixel,
728 type,
729 (QImageCleanupFunction) &openshot::cleanUpBuffer,
730 (void*) pixels_
731 );
732 AddImage(new_image);
733}
734
735// Add (or replace) pixel data to the frame
736void Frame::AddImage(std::shared_ptr<QImage> new_image)
737{
738 // Ignore blank images
739 if (!new_image)
740 return;
741
742 // assign image data
743 const std::lock_guard<std::recursive_mutex> lock(addingImageMutex);
744 image = new_image;
745
746 // Always convert to Format_RGBA8888_Premultiplied (if different)
747 if (image->format() != QImage::Format_RGBA8888_Premultiplied)
748 *image = image->convertToFormat(QImage::Format_RGBA8888_Premultiplied);
749
750 // Update height and width
751 width = image->width();
752 height = image->height();
753 has_image_data = true;
754}
755
756// Add (or replace) pixel data to the frame (for only the odd or even lines)
757void Frame::AddImage(std::shared_ptr<QImage> new_image, bool only_odd_lines)
758{
759 // Ignore blank new_image
760 if (!new_image)
761 return;
762
763 // Check for blank source image
764 if (!image) {
765 // Replace the blank source image
766 AddImage(new_image);
767
768 } else {
769 // Ignore image of different sizes or formats
770 bool ret=false;
771 if (image == new_image || image->size() != new_image->size()) {
772 ret = true;
773 }
774 else if (new_image->format() != QImage::Format_RGBA8888_Premultiplied) {
775 new_image = std::make_shared<QImage>(
776 new_image->convertToFormat(QImage::Format_RGBA8888_Premultiplied));
777 }
778 if (ret) {
779 return;
780 }
781
782 // Get the frame's image
783 const std::lock_guard<std::recursive_mutex> lock(addingImageMutex);
784 unsigned char *pixels = image->bits();
785 const unsigned char *new_pixels = new_image->constBits();
786
787 // Loop through the scanlines of the image (even or odd)
788 int start = 0;
789 if (only_odd_lines)
790 start = 1;
791
792 for (int row = start; row < image->height(); row += 2) {
793 int offset = row * image->bytesPerLine();
794 memcpy(pixels + offset, new_pixels + offset, image->bytesPerLine());
795 }
796
797 // Update height and width
798 height = image->height();
799 width = image->width();
800 has_image_data = true;
801 }
802}
803
804
805// Resize audio container to hold more (or less) samples and channels
806void Frame::ResizeAudio(int channels, int length, int rate, ChannelLayout layout)
807{
808 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
809
810 // Resize JUCE audio buffer
811 audio->setSize(channels, length, true, true, false);
812 channel_layout = layout;
813 sample_rate = rate;
814
815 // Calculate max audio sample added
816 max_audio_sample = length;
817}
818
820void Frame::SetAudioDirection(bool is_increasing) {
821 if (audio && !audio_is_increasing && is_increasing) {
822 // Forward audio buffer
823 audio->reverse(0, audio->getNumSamples());
824 } else if (audio && audio_is_increasing && !is_increasing) {
825 // Reverse audio buffer
826 audio->reverse(0, audio->getNumSamples());
827 }
828 audio_is_increasing = is_increasing;
829}
830
831// Add audio samples to a specific channel
832void Frame::AddAudio(bool replaceSamples, int destChannel, int destStartSample, const float* source, int numSamples, float gainToApplyToSource = 1.0f) {
833 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
834
835 // Clamp starting sample to 0
836 int destStartSampleAdjusted = max(destStartSample, 0);
837
838 // Extend audio container to hold more (or less) samples and channels.. if needed
839 int new_length = destStartSampleAdjusted + numSamples;
840 int new_channel_length = audio->getNumChannels();
841 if (destChannel >= new_channel_length)
842 new_channel_length = destChannel + 1;
843 if (new_length > audio->getNumSamples() || new_channel_length > audio->getNumChannels())
844 audio->setSize(new_channel_length, new_length, true, true, false);
845
846 // Clear the range of samples first (if needed)
847 if (replaceSamples)
848 audio->clear(destChannel, destStartSampleAdjusted, numSamples);
849
850 // Add samples to frame's audio buffer
851 audio->addFrom(destChannel, destStartSampleAdjusted, source, numSamples, gainToApplyToSource);
852 has_audio_data = true;
853
854 // Calculate max audio sample added
855 if (new_length > max_audio_sample)
856 max_audio_sample = new_length;
857
858 // Reset audio direction
859 audio_is_increasing = true;
860}
861
862// Apply gain ramp (i.e. fading volume)
863void Frame::ApplyGainRamp(int destChannel, int destStartSample, int numSamples, float initial_gain = 0.0f, float final_gain = 1.0f)
864{
865 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
866
867 // Apply gain ramp
868 audio->applyGainRamp(destChannel, destStartSample, numSamples, initial_gain, final_gain);
869}
870
871// Get pointer to Magick++ image object
872std::shared_ptr<QImage> Frame::GetImage()
873{
874 // Check for blank image
875 if (!image)
876 // Fill with black
877 AddColor(width, height, color);
878
879 return image;
880}
881
882#ifdef USE_OPENCV
883
884// Convert Qimage to Mat
885cv::Mat Frame::Qimage2mat( std::shared_ptr<QImage>& qimage) {
886
887 cv::Mat mat = cv::Mat(qimage->height(), qimage->width(), CV_8UC4, (uchar*)qimage->constBits(), qimage->bytesPerLine()).clone();
888 cv::Mat mat2 = cv::Mat(mat.rows, mat.cols, CV_8UC3 );
889 int from_to[] = { 0,0, 1,1, 2,2 };
890 cv::mixChannels( &mat, 1, &mat2, 1, from_to, 3 );
891 cv::cvtColor(mat2, mat2, cv::COLOR_RGB2BGR);
892 return mat2;
893}
894
895// Get pointer to OpenCV image object
897{
898 // Check for blank image
899 if (!image)
900 // Fill with black
901 AddColor(width, height, color);
902
903 // if (imagecv.empty())
904 // Convert Qimage to Mat
905 imagecv = Qimage2mat(image);
906
907 return imagecv;
908}
909
910std::shared_ptr<QImage> Frame::Mat2Qimage(cv::Mat img){
911 cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
912 QImage qimg((uchar*) img.data, img.cols, img.rows, img.step, QImage::Format_RGB888);
913
914 std::shared_ptr<QImage> imgIn = std::make_shared<QImage>(qimg.copy());
915
916 // Always convert to RGBA8888 (if different)
917 if (imgIn->format() != QImage::Format_RGBA8888_Premultiplied)
918 *imgIn = imgIn->convertToFormat(QImage::Format_RGBA8888_Premultiplied);
919
920 return imgIn;
921}
922
923// Set pointer to OpenCV image object
924void Frame::SetImageCV(cv::Mat _image)
925{
926 imagecv = _image;
927 image = Mat2Qimage(_image);
928}
929#endif
930
931// Play audio samples for this frame
933{
934 // Check if samples are present
936 return;
937
938 juce::AudioDeviceManager deviceManager;
939 juce::String error = deviceManager.initialise (
940 0, /* number of input channels */
941 2, /* number of output channels */
942 0, /* no XML settings.. */
943 true /* select default device on failure */);
944
945 // Output error (if any)
946 if (error.isNotEmpty()) {
947 cout << "Error on initialise(): " << error << endl;
948 }
949
950 juce::AudioSourcePlayer audioSourcePlayer;
951 deviceManager.addAudioCallback (&audioSourcePlayer);
952
953 std::unique_ptr<AudioBufferSource> my_source;
954 my_source.reset (new AudioBufferSource (audio.get()));
955
956 // Create TimeSliceThread for audio buffering
957 juce::TimeSliceThread my_thread("Audio buffer thread");
958
959 // Start thread
960 my_thread.startThread();
961
962 juce::AudioTransportSource transport1;
963 transport1.setSource (my_source.get(),
964 5000, // tells it to buffer this many samples ahead
965 &my_thread,
966 (double) sample_rate,
967 audio->getNumChannels()); // sample rate of source
968 transport1.setPosition (0);
969 transport1.setGain(1.0);
970
971
972 // Create MIXER
973 juce::MixerAudioSource mixer;
974 mixer.addInputSource(&transport1, false);
975 audioSourcePlayer.setSource (&mixer);
976
977 // Start transports
978 transport1.start();
979
980 while (transport1.isPlaying())
981 {
982 cout << "playing" << endl;
983 std::this_thread::sleep_for(std::chrono::seconds(1));
984 }
985
986 cout << "DONE!!!" << endl;
987
988 transport1.stop();
989 transport1.setSource (0);
990 audioSourcePlayer.setSource (0);
991 my_thread.stopThread(500);
992 deviceManager.removeAudioCallback (&audioSourcePlayer);
993 deviceManager.closeAudioDevice();
994 deviceManager.removeAllChangeListeners();
995 deviceManager.dispatchPendingMessages();
996
997 cout << "End of Play()" << endl;
998
999
1000}
1001
1002// Add audio silence
1003void Frame::AddAudioSilence(int numSamples)
1004{
1005 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
1006
1007 // Resize audio container
1008 audio->setSize(channels, numSamples, false, true, false);
1009 audio->clear();
1010 has_audio_data = true;
1011
1012 // Calculate max audio sample added
1013 max_audio_sample = numSamples;
1014
1015 // Reset audio direction
1016 audio_is_increasing = true;
1017}
Header file for AudioBufferSource class.
Header file for AudioResampler class.
Header file for Frame class.
Header file for QtUtilities (compatibiity overlay)
This class is used to expose an AudioBuffer<float> as an AudioSource in JUCE.
This class represents a fraction.
Definition Fraction.h:30
int num
Numerator for the fraction.
Definition Fraction.h:32
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
Definition Fraction.cpp:40
Fraction Reciprocal() const
Return the reciprocal as a Fraction.
Definition Fraction.cpp:78
int den
Denominator for the fraction.
Definition Fraction.h:33
This class represents a single frame of video (i.e. image & audio data)
Definition Frame.h:90
Frame & operator=(const Frame &other)
Assignment operator.
Definition Frame.cpp:78
std::shared_ptr< juce::AudioBuffer< float > > audio
Definition Frame.h:116
std::shared_ptr< QImage > Mat2Qimage(cv::Mat img)
Convert OpenCV Mat to QImage.
Definition Frame.cpp:910
void AddColor(int new_width, int new_height, std::string new_color)
Add (or replace) pixel data to the frame (based on a solid color)
Definition Frame.cpp:691
const unsigned char * GetPixels()
Get pixel data (as packets)
Definition Frame.cpp:399
void SetAudioDirection(bool is_increasing)
Set the direction of the audio buffer of this frame.
Definition Frame.cpp:820
std::shared_ptr< QImage > GetWaveform(int width, int height, int Red, int Green, int Blue, int Alpha)
Get an audio waveform image.
Definition Frame.cpp:161
void Save(std::string path, float scale, std::string format="PNG", int quality=100)
Save the frame image to the specified path. The image format can be BMP, JPG, JPEG,...
Definition Frame.cpp:517
void Display()
Display the frame image to the screen (primarily used for debugging reasons)
Definition Frame.cpp:122
int GetAudioChannelsCount()
Get number of audio channels.
Definition Frame.cpp:360
cv::Mat Qimage2mat(std::shared_ptr< QImage > &qimage)
Convert Qimage to Mat.
Definition Frame.cpp:885
bool has_image_data
This frame has been loaded with pixel data.
Definition Frame.h:120
int GetWidth()
Get height of image.
Definition Frame.cpp:498
int SampleRate()
Get the original sample rate of this frame's audio data.
Definition Frame.cpp:504
void ResizeAudio(int channels, int length, int sample_rate, openshot::ChannelLayout channel_layout)
Resize audio container to hold more (or less) samples and channels.
Definition Frame.cpp:806
void DeepCopy(const Frame &other)
Copy data and pointers from another Frame instance.
Definition Frame.cpp:87
cv::Mat GetImageCV()
Get pointer to OpenCV Mat image object.
Definition Frame.cpp:896
void ClearWaveform()
Clear the waveform image (and deallocate its memory)
Definition Frame.cpp:253
void Play()
Play audio samples for this frame.
Definition Frame.cpp:932
void Thumbnail(std::string path, int new_width, int new_height, std::string mask_path, std::string overlay_path, std::string background_color, bool ignore_aspect, std::string format="png", int quality=100, float rotate=0.0, ScaleType scale_mode=SCALE_FIT)
Definition Frame.cpp:545
float * GetInterleavedAudioSamples(int *sample_count)
Get an array of sample data (all channels interleaved together), using any sample rate.
Definition Frame.cpp:326
bool has_audio_data
This frame has been loaded with audio data.
Definition Frame.h:119
int64_t GetBytes()
Get the size in bytes of this frame (rough estimate)
Definition Frame.cpp:382
openshot::ChannelLayout ChannelsLayout()
Definition Frame.cpp:510
void AddAudioSilence(int numSamples)
Add audio silence.
Definition Frame.cpp:1003
void DisplayWaveform()
Display the wave form.
Definition Frame.cpp:270
bool CheckPixel(int row, int col, int red, int green, int blue, int alpha, int threshold)
Check a specific pixel color value (returns True/False)
Definition Frame.cpp:423
float * GetAudioSamples(int channel)
Get an array of sample data (and optional reverse the sample values)
Definition Frame.cpp:316
float GetAudioSample(int channel, int sample, int magnitude_range)
Get magnitude of range of samples (if channel is -1, return average of all channels for that sample)
Definition Frame.cpp:303
virtual ~Frame()
Destructor.
Definition Frame.cpp:112
int GetSamplesPerFrame(openshot::Fraction fps, int sample_rate, int channels)
Calculate the # of samples per video frame (for the current frame number)
Definition Frame.cpp:486
std::shared_ptr< QImage > GetImage()
Get pointer to Qt QImage image object.
Definition Frame.cpp:872
double capture_timestamp
Optional source capture timestamp in seconds for live capture frames.
Definition Frame.h:118
Frame()
Constructor - blank frame.
Definition Frame.cpp:59
void AddImage(int new_width, int new_height, int bytes_per_pixel, QImage::Format type, const unsigned char *pixels_)
Add (or replace) pixel data to the frame.
Definition Frame.cpp:714
void ApplyGainRamp(int destChannel, int destStartSample, int numSamples, float initial_gain, float final_gain)
Apply gain ramp (i.e. fading volume)
Definition Frame.cpp:863
int GetAudioSamplesCount()
Get number of audio samples.
Definition Frame.cpp:370
void AddAudio(bool replaceSamples, int destChannel, int destStartSample, const float *source, int numSamples, float gainToApplyToSource)
Add audio samples to a specific channel.
Definition Frame.cpp:832
const unsigned char * GetWaveformPixels(int width, int height, int Red, int Green, int Blue, int Alpha)
Get an audio waveform image pixels.
Definition Frame.cpp:260
int GetHeight()
Get height of image.
Definition Frame.cpp:492
void SetPixelRatio(int num, int den)
Set Pixel Aspect Ratio.
Definition Frame.cpp:445
void SetFrameNumber(int64_t number)
Set frame number.
Definition Frame.cpp:452
juce::AudioBuffer< float > * GetAudioSampleBuffer()
Definition Frame.cpp:376
int64_t number
This is the frame number (starting at 1)
Definition Frame.h:117
void SetImageCV(cv::Mat _image)
Set pointer to OpenCV image object.
Definition Frame.cpp:924
This namespace is the default namespace for all code in the openshot library.
ScaleType
This enumeration determines how clips are scaled to fit their parent container.
Definition Enums.h:36
@ SCALE_FIT
Scale the clip until either height or width fills the canvas (with no cropping)
Definition Enums.h:38
@ SCALE_STRETCH
Scale the clip until both height and width fill the canvas (distort to fit)
Definition Enums.h:39
@ SCALE_CROP
Scale the clip until both height and width fill the canvas (cropping the overlap)
Definition Enums.h:37
@ SCALE_NONE
Do not scale the clip.
Definition Enums.h:40
ChannelLayout
This enumeration determines the audio channel layout (such as stereo, mono, 5 point surround,...