Allows for removal of letterboxes from common video formats.
The Qt renderer has been rewritten to increase performance required
when applying cropping. No longer uses memcpy.
Signed-off-by: Bård Eirik Winther <bwinther@cisco.com>
[hans.verkuil@cisco.com: fix crop calculation when cropcap is unsupported]
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
glClear(GL_COLOR_BUFFER_BIT);
}
+void CaptureWinGLEngine::paintFrame()
+{
+ float crop = (float)CaptureWin::cropHeight(m_frameWidth, m_frameHeight) / m_frameHeight;
+
+ glBegin(GL_QUADS);
+ glTexCoord2f(0.0f, crop); glVertex2f(0.0, 0);
+ glTexCoord2f(1.0f, crop); glVertex2f(m_frameWidth, 0);
+ glTexCoord2f(1.0f, 1.0f - crop); glVertex2f(m_frameWidth, m_frameHeight);
+ glTexCoord2f(0.0f, 1.0f - crop); glVertex2f(0, m_frameHeight);
+ glEnd();
+}
+
void CaptureWinGLEngine::paintGL()
{
if (m_frameWidth < 1 || m_frameHeight < 1) {
changeShader();
if (m_frameData == NULL) {
- glBegin(GL_QUADS);
- glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0, 0);
- glTexCoord2f(1.0f, 0.0f); glVertex2f(m_frameWidth, 0);
- glTexCoord2f(1.0f, 1.0f); glVertex2f(m_frameWidth, m_frameHeight);
- glTexCoord2f(0.0f, 1.0f); glVertex2f(0, m_frameHeight);
- glEnd();
+ paintFrame();
return;
}
checkError("Default paint");
break;
}
-
- glBegin(GL_QUADS);
- glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0, 0);
- glTexCoord2f(1.0f, 0.0f); glVertex2f(m_frameWidth, 0);
- glTexCoord2f(1.0f, 1.0f); glVertex2f(m_frameWidth, m_frameHeight);
- glTexCoord2f(0.0f, 1.0f); glVertex2f(0, m_frameHeight);
- glEnd();
+ paintFrame();
}
void CaptureWinGLEngine::configureTexture(size_t idx)
void clearShader();
void changeShader();
+ void paintFrame();
void configureTexture(size_t idx);
void checkError(const char *msg);
#include "capture-win-qt.h"
CaptureWinQt::CaptureWinQt() :
- m_frame(new QImage(0, 0, QImage::Format_Invalid))
+ m_frame(new QImage(0, 0, QImage::Format_Invalid)),
+ m_data(NULL),
+ m_supportedFormat(false),
+ m_filled(false)
{
CaptureWin::buildWindow(&m_videoSurface);
m_scaledSize.setWidth(0);
m_scaledSize.setHeight(0);
+ m_crop.crop = 0;
+ m_crop.height = 0;
+ m_crop.offset = 0;
+ m_crop.bytes = 0;
}
CaptureWinQt::~CaptureWinQt()
delete m_frame;
}
-void CaptureWinQt::resizeEvent(QResizeEvent *event)
+void CaptureWinQt::resizeScaleCrop()
{
- if (m_frame->bits() == NULL)
- return;
-
- QPixmap img = QPixmap::fromImage(*m_frame);
m_scaledSize = scaleFrameSize(QSize(m_videoSurface.width(), m_videoSurface.height()),
- QSize(m_frame->width(), m_frame->height()));
- img = img.scaled(m_scaledSize.width(), m_scaledSize.height(), Qt::IgnoreAspectRatio);
- m_videoSurface.setPixmap(img);
- QWidget::resizeEvent(event);
+ QSize(m_frame->width(), m_frame->height()));
+
+ if (!m_crop.bytes || m_crop.crop != cropHeight(m_frame->width(), m_frame->height())) {
+ m_crop.crop = cropHeight(m_frame->width(), m_frame->height());
+ m_crop.height = m_frame->height() - (m_crop.crop * 2);
+ m_crop.offset = m_crop.crop * (m_frame->depth() / 8) * m_frame->width();
+
+ // Even though the values above can be valid, it might be that there is no
+ // data at all. This makes sure that it is.
+ m_crop.bytes = m_crop.height * m_frame->width() * (m_frame->depth() / 8);
+ }
+}
+
+void CaptureWinQt::resizeEvent(QResizeEvent *event)
+{
+ resizeScaleCrop();
+ paintFrame();
}
void CaptureWinQt::setFrame(int width, int height, __u32 format, unsigned char *data, const QString &info)
{
+ m_data = data;
+
QImage::Format dstFmt;
- bool supported = findNativeFormat(format, dstFmt);
- if (!supported)
+ m_supportedFormat = findNativeFormat(format, dstFmt);
+ if (!m_supportedFormat)
dstFmt = QImage::Format_RGB888;
- if (m_frame->width() != width || m_frame->height() != height || m_frame->format() != dstFmt) {
+ if (m_frame->width() != width
+ || m_frame->height() != height
+ || m_frame->format() != dstFmt) {
delete m_frame;
m_frame = new QImage(width, height, dstFmt);
- m_scaledSize = scaleFrameSize(QSize(m_videoSurface.width(), m_videoSurface.height()),
- QSize(m_frame->width(), m_frame->height()));
+
+ resizeScaleCrop();
}
- if (data == NULL || !supported)
- m_frame->fill(0);
+ m_information.setText(info);
+ paintFrame();
+}
+
+void CaptureWinQt::paintFrame()
+{
+ if (!m_supportedFormat || !m_crop.bytes) {
+ if (!m_filled) {
+ m_filled = true;
+ m_frame->fill(0);
+ QPixmap img = QPixmap::fromImage(*m_frame);
+ m_videoSurface.setPixmap(img);
+ }
+ return;
+ }
+ m_filled = false;
+
+ unsigned char *data;
+
+ if (m_data == NULL)
+ data = m_frame->bits();
else
- memcpy(m_frame->bits(), data, m_frame->numBytes());
+ data = m_data;
- m_information.setText(info);
+ QImage displayFrame(&data[m_crop.offset], m_frame->width(), m_crop.height,
+ m_frame->numBytes()/m_frame->height(), m_frame->format());
+
+ QPixmap img = QPixmap::fromImage(displayFrame);
- QPixmap img = QPixmap::fromImage(*m_frame);
+ // No scaling is performed by scaled() if the scaled size is equal to original size
img = img.scaled(m_scaledSize.width(), m_scaledSize.height(), Qt::IgnoreAspectRatio);
m_videoSurface.setPixmap(img);
}
+void CaptureWinQt::stop()
+{
+ if (m_data != NULL)
+ memcpy(m_frame->bits(), m_data, m_frame->numBytes());
+ m_data = NULL;
+}
+
bool CaptureWinQt::hasNativeFormat(__u32 format)
{
QImage::Format fmt;
#include <QImage>
#include <QResizeEvent>
+struct CropInfo {
+ int crop;
+ int height;
+ int offset;
+ int bytes;
+};
+
class CaptureWinQt : public CaptureWin
{
public:
void setFrame(int width, int height, __u32 format,
unsigned char *data, const QString &info);
- void stop(){}
+ void stop();
bool hasNativeFormat(__u32 format);
static bool isSupported() { return true; }
private:
bool findNativeFormat(__u32 format, QImage::Format &dstFmt);
+ void paintFrame();
+ void resizeScaleCrop();
QImage *m_frame;
+ struct CropInfo m_crop;
+ unsigned char *m_data;
QLabel m_videoSurface;
QSize m_scaledSize;
+ bool m_supportedFormat;
+ bool m_filled;
};
#endif
bool CaptureWin::m_enableScaling = true;
double CaptureWin::m_pixelAspectRatio = 1.0;
+CropMethod CaptureWin::m_cropMethod = QV4L2_CROP_NONE;
CaptureWin::CaptureWin() :
m_curWidth(-1),
resize(w, h);
}
+int CaptureWin::cropHeight(int width, int height)
+{
+ int validHeight;
+
+ switch (m_cropMethod) {
+ case QV4L2_CROP_W149:
+ validHeight = (int)(width / 1.57);
+ break;
+ case QV4L2_CROP_W169:
+ validHeight = (int)(width / 1.78);
+ break;
+ case QV4L2_CROP_C185:
+ validHeight = (int)(width / 1.85);
+ break;
+ case QV4L2_CROP_C239:
+ validHeight = (int)(width / 2.39);
+ break;
+ case QV4L2_CROP_TB:
+ validHeight = height - 2;
+ break;
+ default:
+ return 0;
+ }
+
+ if (validHeight < MIN_WIN_SIZE_HEIGHT || validHeight >= height)
+ return 0;
+
+ return (height - validHeight) / 2;
+}
+
+
+void CaptureWin::setCropMethod(CropMethod crop)
+{
+ m_cropMethod = crop;
+ QResizeEvent event (QSize(width(), height()), QSize(width(), height()));
+ QCoreApplication::sendEvent(this, &event);
+}
+
int CaptureWin::actualFrameWidth(int width)
{
if (m_enableScaling)
- return (int)((double)width * m_pixelAspectRatio);
+ return width * m_pixelAspectRatio;
return width;
}
QWidget::setMinimumSize(MIN_WIN_SIZE_WIDTH, MIN_WIN_SIZE_HEIGHT);
}
m_enableScaling = enable;
- QResizeEvent *event = new QResizeEvent(QSize(width(), height()), QSize(width(), height()));
- QCoreApplication::sendEvent(this, event);
- delete event;
+ QResizeEvent event (QSize(width(), height()), QSize(width(), height()));
+ QCoreApplication::sendEvent(this, &event);
}
void CaptureWin::resize(int width, int height)
m_curHeight = height;
QSize margins = getMargins();
- width = actualFrameWidth(width) + margins.width();
- height += margins.height();
+ height = height + margins.height() - cropHeight(width, height) * 2;
+ width = margins.width() + actualFrameWidth(width);
QDesktopWidget *screen = QApplication::desktop();
QRect resolution = screen->screenGeometry();
QSize CaptureWin::scaleFrameSize(QSize window, QSize frame)
{
int actualWidth;
- int actualHeight = frame.height();
+ int actualHeight = frame.height() - cropHeight(frame.width(), frame.height()) * 2;
if (!m_enableScaling) {
window.setWidth(frame.width());
void CaptureWin::setPixelAspectRatio(double ratio)
{
m_pixelAspectRatio = ratio;
- QResizeEvent *event = new QResizeEvent(QSize(width(), height()), QSize(width(), height()));
- QCoreApplication::sendEvent(this, event);
- delete event;
+ QResizeEvent event(QSize(width(), height()), QSize(width(), height()));
+ QCoreApplication::sendEvent(this, &event);
}
void CaptureWin::closeEvent(QCloseEvent *event)
#include <QShortcut>
#include <QLabel>
+enum CropMethod {
+ QV4L2_CROP_NONE,
+ QV4L2_CROP_W149,
+ QV4L2_CROP_W169,
+ QV4L2_CROP_C185,
+ QV4L2_CROP_C239,
+ QV4L2_CROP_TB
+};
+
class CaptureWin : public QWidget
{
Q_OBJECT
~CaptureWin();
void resize(int minw, int minh);
+ void enableScaling(bool enable);
+ void setPixelAspectRatio(double ratio);
+ void setCropMethod(CropMethod crop);
/**
* @brief Set a frame into the capture window.
*/
static bool isSupported() { return false; }
- void enableScaling(bool enable);
- void setPixelAspectRatio(double ratio);
+ /**
+ * @brief Return the scaled size.
+ *
+ * Scales a frame to fit inside a given window. Preseves aspect ratio.
+ *
+ * @param window The window the frame shall scale into
+ * @param frame The frame to scale
+ * @return The scaledsizes to use for the frame
+ */
static QSize scaleFrameSize(QSize window, QSize frame);
+ /**
+ * @brief Get the number of pixels to crop.
+ *
+ * When cropping is applied this gives the number of pixels to
+ * remove from top and bottom. To get total multiply the return
+ * value by 2.
+ *
+ * @param width Frame width
+ * @param height Frame height
+ * @return The pixels to remove when cropping height
+ *
+ * @note The width and height must be original frame size
+ * to ensure that the cropping is done correctly.
+ */
+ static int cropHeight(int width, int height);
+
+ /**
+ * @brief Get the frame width when aspect ratio is applied.
+ *
+ * @param width The original frame width.
+ * @return The width with aspect ratio correctio (scaling must be enabled).
+ */
+ static int actualFrameWidth(int width);
+
public slots:
void resetSize();
protected:
void closeEvent(QCloseEvent *event);
- void buildWindow(QWidget *videoSurface);
- static int actualFrameWidth(int width);
+
+ /**
+ * @brief Get the amout of space outside the video frame.
+ *
+ * The margins are that of the window that encloses the displaying
+ * video frame. The sizes are total in both width and height.
+ *
+ * @return The margins around the video frame.
+ */
QSize getMargins();
/**
+ * @brief Creates the content of the window.
+ *
+ * Construct the new window layout and adds the video display surface.
+ * @param videoSurface The widget that contains the renderer.
+ */
+ void buildWindow(QWidget *videoSurface);
+
+ /**
* @brief A label that can is used to display capture information.
*
* @note This must be set in the derived class' setFrame() function.
*/
static bool m_enableScaling;
- /**
- * @note Aspect ratio it taken care of by scaling, frame size is for square pixels only!
- */
- static double m_pixelAspectRatio;
-
signals:
void close();
private:
+ static double m_pixelAspectRatio;
+ static CropMethod m_cropMethod;
QShortcut *m_hotkeyClose;
QShortcut *m_hotkeyScaleReset;
int m_curWidth;
m_qryStandard(NULL),
m_videoTimings(NULL),
m_pixelAspectRatio(NULL),
+ m_crop(NULL),
m_qryTimings(NULL),
m_freq(NULL),
m_vidCapFormats(NULL),
setAudioDeviceBufferSize((fract.numerator * 2000) / fract.denominator);
}
} else {
- fprintf(stderr, "BANNA\n");
delete m_audioInDevice;
delete m_audioOutDevice;
m_audioInDevice = NULL;
addLabel("Pixel Aspect Ratio");
addWidget(m_pixelAspectRatio);
connect(m_pixelAspectRatio, SIGNAL(activated(int)), SLOT(changePixelAspectRatio()));
+
+ m_crop = new QComboBox(parent);
+ m_crop->addItem("None");
+ m_crop->addItem("Top and Bottom Line");
+ m_crop->addItem("Widescreen 14:9");
+ m_crop->addItem("Widescreen 16:9");
+ m_crop->addItem("Cinema 1.85:1");
+ m_crop->addItem("Cinema 2.39:1");
+
+ addLabel("Cropping");
+ addWidget(m_crop);
+ connect(m_crop, SIGNAL(activated(int)), SIGNAL(cropChanged()));
}
if (m_tuner.capability) {
vs.framelines);
m_tvStandard->setWhatsThis(what);
updateVidCapFormat();
+ changePixelAspectRatio();
}
void GeneralTab::qryStdClicked()
updateFrameInterval();
}
+CropMethod GeneralTab::getCropMethod()
+{
+ switch (m_crop->currentIndex()) {
+ case 1:
+ return QV4L2_CROP_TB;
+ case 2:
+ return QV4L2_CROP_W149;
+ case 3:
+ return QV4L2_CROP_W169;
+ case 4:
+ return QV4L2_CROP_C185;
+ case 5:
+ return QV4L2_CROP_C239;
+ default:
+ return QV4L2_CROP_NONE;
+ }
+}
+
void GeneralTab::changePixelAspectRatio()
{
// Update hints by calling a get
double GeneralTab::getPixelAspectRatio()
{
+ v4l2_fract ratio = { 1, 1 };
+
switch (m_pixelAspectRatio->currentIndex()) {
case 0:
- v4l2_cropcap ratio;
- ratio.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- if (ioctl(VIDIOC_CROPCAP, &ratio) < 0) {
- m_pixelAspectRatio->setStatusTip("Pixel Aspect Ratio 1:1");
- m_pixelAspectRatio->setWhatsThis("Pixel Aspect Ratio 1:1");
- return 1.0;
- }
-
- m_pixelAspectRatio->setStatusTip(QString("Pixel Aspect Ratio %1:%2")
- .arg(ratio.pixelaspect.denominator)
- .arg(ratio.pixelaspect.numerator));
- m_pixelAspectRatio->setWhatsThis(QString("Pixel Aspect Ratio %1:%2")
- .arg(ratio.pixelaspect.denominator)
- .arg(ratio.pixelaspect.numerator));
- return (double)ratio.pixelaspect.denominator / ratio.pixelaspect.numerator;
+ ratio = g_pixel_aspect();
+ break;
case 2:
- m_pixelAspectRatio->setStatusTip("Pixel Aspect Ratio 10:11");
- m_pixelAspectRatio->setWhatsThis("Pixel Aspect Ratio 10:11");
- return 10.0 / 11.0;
+ ratio.numerator = 11;
+ ratio.denominator = 10;
+ break;
case 3:
- m_pixelAspectRatio->setStatusTip("Pixel Aspect Ratio 40:33");
- m_pixelAspectRatio->setWhatsThis("Pixel Aspect Ratio 40:33");
- return 40.0 / 33.0;
+ ratio.numerator = 33;
+ ratio.denominator = 40;
+ break;
case 4:
- m_pixelAspectRatio->setStatusTip("Pixel Aspect Ratio 12:11");
- m_pixelAspectRatio->setWhatsThis("Pixel Aspect Ratio 12:11");
- return 12.0 / 11.0;
+ ratio.numerator = 11;
+ ratio.denominator = 12;
+ break;
case 5:
- m_pixelAspectRatio->setStatusTip("Pixel Aspect Ratio 16:11");
- m_pixelAspectRatio->setWhatsThis("Pixel Aspect Ratio 16:11");
- return 16.0 / 11.0;
+ ratio.numerator = 11;
+ ratio.denominator = 16;
+ break;
default:
- m_pixelAspectRatio->setStatusTip("Pixel Aspect Ratio 1:1");
- m_pixelAspectRatio->setWhatsThis("Pixel Aspect Ratio 1:1");
- return 1.0;
+ break;
}
+
+ m_pixelAspectRatio->setWhatsThis(QString("Pixel Aspect Ratio %1:%2")
+ .arg(ratio.denominator).arg(ratio.numerator));
+ m_pixelAspectRatio->setStatusTip(m_pixelAspectRatio->whatsThis());
+ // Note: ratio is y / x, whereas we want x / y, so we return
+ // denominator / numerator.
+ return (double)ratio.denominator / ratio.numerator;
}
void GeneralTab::updateFrameInterval()
#include <sys/time.h>
#include <linux/videodev2.h>
#include <map>
+
#include "qv4l2.h"
#include "v4l2-api.h"
+#include "capture-win.h"
#ifdef HAVE_ALSA
extern "C" {
int getAudioDeviceBufferSize();
bool hasAlsaAudio();
double getPixelAspectRatio();
+ CropMethod getCropMethod();
bool get_interval(struct v4l2_fract &interval);
int width() const { return m_width; }
int height() const { return m_height; }
signals:
void audioDeviceChanged();
void pixelAspectRatioChanged();
+ void cropChanged();
private slots:
void inputChanged(int);
QPushButton *m_qryStandard;
QComboBox *m_videoTimings;
QComboBox *m_pixelAspectRatio;
+ QComboBox *m_crop;
QPushButton *m_qryTimings;
QLineEdit *m_freq;
QComboBox *m_freqTable;
}
#endif
connect(m_genTab, SIGNAL(pixelAspectRatioChanged()), this, SLOT(updatePixelAspectRatio()));
+ connect(m_genTab, SIGNAL(cropChanged()), this, SLOT(updateCropping()));
m_tabs->addTab(w, "General");
addTabs();
if (caps() & (V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_CAPTURE)) {
m_capture->setPixelAspectRatio(m_genTab->getPixelAspectRatio());
}
+void ApplicationWindow::updateCropping()
+{
+ if (m_capture != NULL)
+ m_capture->setCropMethod(m_genTab->getCropMethod());
+}
+
void ApplicationWindow::startAudio()
{
#ifdef HAVE_ALSA
void setAudioBufferSize();
void enableScaling(bool enable);
void updatePixelAspectRatio();
+ void updateCropping();
void about();
return false;
}
+
+v4l2_fract v4l2::g_pixel_aspect()
+{
+ v4l2_cropcap ratio;
+ v4l2_std_id std;
+ static const v4l2_fract square = { 1, 1 };
+ static const v4l2_fract hz50 = { 11, 12 };
+ static const v4l2_fract hz60 = { 11, 10 };
+
+ ratio.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ if (ioctl(VIDIOC_CROPCAP, &ratio) < 0) {
+ if (!g_std(std))
+ return square;
+ if (std & V4L2_STD_525_60)
+ return hz60;
+ if (std & V4L2_STD_625_50)
+ return hz50;
+ return square;
+ }
+ if (!ratio.pixelaspect.numerator || !ratio.pixelaspect.denominator)
+ return square;
+ return ratio.pixelaspect;
+}
bool qbuf_user(int index, __u32 buftype, void *ptr, int length);
bool streamon(__u32 buftype);
bool streamoff(__u32 buftype);
+ v4l2_fract g_pixel_aspect();
inline bool reqbufs_mmap_cap(v4l2_requestbuffers &reqbuf, int count = 0) {
return reqbufs_mmap(reqbuf, V4L2_BUF_TYPE_VIDEO_CAPTURE, count);