blob: 6ae95ee27a1d00db12ec0a5fc714e786f58f3595 [file] [log] [blame]
// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <getopt.h>
#include <signal.h>
#include <X11/Xlib.h>
#include <iostream>
#include <string>
#include "media_v4l2_device.h"
#define CHECK(a) assert(a)
class V4L2DeviceX11 : public V4L2Device {
public:
V4L2DeviceX11(const char* dev_name,
IOMethod io,
uint32_t buffers)
: V4L2Device(dev_name, io, buffers),
xdisplay_(NULL),
xwindow_(0),
xrunning_(false) {
}
virtual bool OpenDevice() {
if (!InitX11())
return false;
return V4L2Device::OpenDevice();
}
virtual void CloseDevice() {
UninitX11(); // Ignore error.
return V4L2Device::CloseDevice();
}
virtual bool InitDevice(uint32_t width,
uint32_t height,
uint32_t pixfmt,
uint32_t fps) {
// Only after InitDevice, we know the actual format of capture.
if (V4L2Device::InitDevice(width, height, pixfmt, fps)) {
// Resize the window to fit that of the video.
XResizeWindow(xdisplay_, xwindow_,
GetActualWidth(), GetActualHeight());
// Initialize the XImage to store the output of YUV -> RGBA conversion.
int size = GetActualWidth() * GetActualHeight() * 4;
ximage_ = XCreateImage(xdisplay_,
DefaultVisual(xdisplay_, DefaultScreen(xdisplay_)),
DefaultDepth(xdisplay_, DefaultScreen(xdisplay_)),
ZPixmap,
0,
static_cast<char*>(malloc(size)),
GetActualWidth(),
GetActualHeight(),
32,
GetActualWidth() * 4);
return ximage_ ? true : false;
}
return false;
}
private:
bool InitX11() {
xdisplay_ = XOpenDisplay(NULL);
if (!xdisplay_) {
std::cout << "Error - cannot open display" << std::endl;
return false;
}
// Get properties of the screen.
int32_t screen = DefaultScreen(xdisplay_);
int32_t root_window = RootWindow(xdisplay_, screen);
// Creates the window.
xwindow_ = XCreateSimpleWindow(xdisplay_, root_window, 1, 1, 100, 50, 0,
BlackPixel(xdisplay_, screen),
BlackPixel(xdisplay_, screen));
XStoreName(xdisplay_, xwindow_, "X11 Media Player");
XSelectInput(xdisplay_, xwindow_, ExposureMask | ButtonPressMask);
XMapWindow(xdisplay_, xwindow_);
return true;
}
bool UninitX11() {
XDestroyWindow(xdisplay_, xwindow_);
XCloseDisplay(xdisplay_);
return true;
}
virtual void ProcessImage(const void* p) {
while (XPending(xdisplay_)) {
// Pump out all pending events.
XEvent e;
XNextEvent(xdisplay_, &e);
if (e.type == ButtonPress)
Stop();
}
if (!ximage_->data)
return;
ConvertYUVToRGB32(reinterpret_cast<uint8_t*>(const_cast<void*>(p)),
reinterpret_cast<uint8_t*>(ximage_->data),
GetActualPixelFormat().fmt.pix.pixelformat,
GetActualWidth(), GetActualHeight(),
GetActualPixelFormat().fmt.pix.bytesperline,
ximage_->bytes_per_line);
GC gc = XCreateGC(xdisplay_, xwindow_, 0, NULL);
XPutImage(xdisplay_, xwindow_, gc, ximage_,
0, 0, 0, 0, GetActualWidth(), GetActualHeight());
XFlush(xdisplay_);
XFreeGC(xdisplay_, gc);
}
inline int32_t clip(int32_t value, int32_t min, int32_t max) {
return (value > max ? max : value < min ? min : value);
}
void ConvertYUVToRGB32(uint8_t* in, uint8_t* out, int32_t ifmt,
int32_t width, int32_t height,
int32_t istride, int32_t ostride) {
if ((ifmt == v4l2_fourcc('Y', 'U', 'Y', 'V')) ||
(ifmt == v4l2_fourcc('Y', 'V', 'Y', 'U')) ||
(ifmt == v4l2_fourcc('U', 'Y', 'V', 'Y')) ||
(ifmt == v4l2_fourcc('V', 'Y', 'U', 'Y'))) {
int y0_offset;
int y1_offset;
int u_offset;
int v_offset;
if (ifmt == v4l2_fourcc('Y', 'U', 'Y', 'V')) {
y0_offset = 0;
y1_offset = 2;
u_offset = 1;
v_offset = 3;
} else if (ifmt == v4l2_fourcc('Y', 'V', 'Y', 'U')) {
y0_offset = 0;
y1_offset = 2;
u_offset = 3;
v_offset = 1;
} else if (ifmt == v4l2_fourcc('U', 'Y', 'V', 'Y')) {
y0_offset = 1;
y1_offset = 3;
u_offset = 0;
v_offset = 2;
} else if (ifmt == v4l2_fourcc('V', 'Y', 'U', 'Y')) {
y0_offset = 1;
y1_offset = 3;
u_offset = 2;
v_offset = 0;
} else {
CHECK(0);
}
for (int32_t i = 0; i < height; ++i) {
for (int32_t j = 0; j < width * 2; j += 4) {
int32_t y0 = in[j + y0_offset];
int32_t y1 = in[j + y1_offset];
int32_t u = in[j + u_offset] - 128;
int32_t v = in[j + v_offset] - 128;
int32_t r = (298 * y0 + 409 * v + 128) >> 8;
int32_t g = (298 * y0 - 100 * u - 208 * v + 128) >> 8;
int32_t b = (298 * y0 + 516 * u + 128) >> 8;
out[j * 2 + 0] = clip(b, 0, 255);
out[j * 2 + 1] = clip(g, 0, 255);
out[j * 2 + 2] = clip(r, 0, 255);
out[j * 2 + 3] = 255;
r = (298 * y1 + 409 * v + 128) >> 8;
g = (298 * y1 - 100 * u - 208 * v + 128) >> 8;
b = (298 * y1 + 516 * u + 128) >> 8;
out[j * 2 + 4] = clip(b, 0, 255);
out[j * 2 + 5] = clip(g, 0, 255);
out[j * 2 + 6] = clip(r, 0, 255);
out[j * 2 + 7] = 255;
}
in += istride;
out += ostride;
}
} else if ((ifmt == v4l2_fourcc('Y', 'U', '1', '2')) ||
(ifmt == v4l2_fourcc('Y', 'V', '1', '2'))) {
// Can't use bytes_per_line for this. While bytes_per_line is width*1.5,
// the rest of this part of code is using line stride as the
// y-plane's line stride, which should just be the width of the image.
istride = width;
uint8_t* y_plane = in;
uint8_t* u_plane = in + height * istride;
// assumption. stride for uv is half of the y stride.
uint8_t* v_plane = u_plane + height * istride / 4;
// YU12 is identical to YV12 except that the U and V planes are swapped.
if (ifmt == v4l2_fourcc('Y', 'V', '1', '2')) {
uint8_t* temp = u_plane;
u_plane = v_plane;
v_plane = temp;
}
for (int32_t i = 0; i < height; ++i) {
for (int32_t j = 0; j < width; ++j) {
int32_t y = y_plane[j];
int32_t u = u_plane[j >> 1] - 128;
int32_t v = v_plane[j >> 1] - 128;
int32_t r = (298 * y + 409 * v + 128) >> 8;
int32_t g = (298 * y - 100 * u - 208 * v + 128) >> 8;
int32_t b = (298 * y + 516 * u + 128) >> 8;
out[j * 4 + 0] = clip(b, 0, 255);
out[j * 4 + 1] = clip(g, 0, 255);
out[j * 4 + 2] = clip(r, 0, 255);
out[j * 4 + 3] = 255;
}
y_plane += istride;
if (i & 1) {
u_plane += istride >> 1;
v_plane += istride >> 1;
}
out += ostride;
}
} else {
CHECK(0);
}
}
private:
Display* xdisplay_;
Window xwindow_;
XImage* ximage_;
bool xrunning_;
};
static void PrintUsage(int argc, char** argv) {
printf("Usage: %s [options]\n\n"
"Options:\n"
"--device=DEVICE_NAME Video device name [/dev/video]\n"
"--help Print usage\n"
"--mmap Use memory mapped buffers\n"
"--read Use read() calls\n"
"--userp Use application allocated buffers\n"
"--buffers=[NUM] Minimum buffers required\n"
"--frames=[NUM] Maximum frame to capture\n"
"--width=[NUM] Picture width to capture\n"
"--height=[NUM] Picture height to capture\n"
"--pixel-format=[fourcc] Picture format fourcc code\n"
"--fps=[NUM] Frame rate for capture\n"
"--display Launch X11 window to preview\n"
"--time=[NUM] Time to capture in seconds\n",
argv[0]);
}
static const char short_options[] = "d:?mrun:f:w:h:t:x:kz:";
static const struct option
long_options[] = {
{ "device", required_argument, NULL, 'd' },
{ "help", no_argument, NULL, '?' },
{ "mmap", no_argument, NULL, 'm' },
{ "read", no_argument, NULL, 'r' },
{ "userp", no_argument, NULL, 'u' },
{ "buffers", required_argument, NULL, 'n' },
{ "frames", required_argument, NULL, 'f' },
{ "width", required_argument, NULL, 'w' },
{ "height", required_argument, NULL, 'h' },
{ "pixel-format", required_argument, NULL, 't' },
{ "fps", required_argument, NULL, 'x' },
{ "display", no_argument, NULL, 'k' },
{ "time", required_argument, NULL, 'z' },
{ 0, 0, 0, 0 }
};
int main(int argc, char** argv) {
std::string dev_name = "/dev/video";
V4L2Device::IOMethod io = V4L2Device::IO_METHOD_MMAP;
uint32_t buffers = 4;
uint32_t frames = 100;
uint32_t width = 640;
uint32_t height = 480;
uint32_t pixfmt = V4L2_PIX_FMT_YUYV;
uint32_t fps = 0;
bool display = false;
uint32_t time_to_capture = 0;
for (;;) {
int32_t index;
int32_t c = getopt_long(argc, argv, short_options, long_options, &index);
if (-1 == c)
break;
switch (c) {
case 0: // getopt_long() flag.
break;
case 'd':
// Initialize default v4l2 device name.
dev_name = strdup(optarg);
break;
case '?':
PrintUsage(argc, argv);
exit (EXIT_SUCCESS);
case 'm':
io = V4L2Device::IO_METHOD_MMAP;
break;
case 'r':
io = V4L2Device::IO_METHOD_READ;
break;
case 'u':
io = V4L2Device::IO_METHOD_USERPTR;
break;
case 'n':
buffers = atoi(optarg);
break;
case 'f':
frames = atoi(optarg);
break;
case 'w':
width = atoi(optarg);
break;
case 'h':
height = atoi(optarg);
break;
case 't': {
std::string fourcc = optarg;
if (fourcc.length() != 4) {
PrintUsage(argc, argv);
exit (EXIT_FAILURE);
}
pixfmt = V4L2Device::MapFourCC(fourcc.c_str());
break;
}
case 'x':
fps = atoi(optarg);
break;
case 'k':
display = true;
break;
case 'z':
time_to_capture = atoi(optarg);
break;
default:
PrintUsage(argc, argv);
exit(EXIT_FAILURE);
}
}
if (time_to_capture) {
printf("capture %dx%d %c%c%c%c picture for %d seconds at %d fps\n",
width, height, (pixfmt >> 0) & 0xff, (pixfmt >> 8) & 0xff,
(pixfmt >> 16) & 0xff, (pixfmt >> 24) & 0xff, time_to_capture, fps);
} else {
printf("capture %dx%d %c%c%c%c picture for %d frames at %d fps\n",
width, height, (pixfmt >> 0) & 0xff, (pixfmt >> 8) & 0xff,
(pixfmt >> 16) & 0xff, (pixfmt >> 24) & 0xff, frames, fps);
}
V4L2Device* device = NULL;
if (display) {
device = new V4L2DeviceX11(dev_name.c_str(), io, buffers);
} else {
device = new V4L2Device(dev_name.c_str(), io, buffers);
}
int32_t retcode = 0;
if (!device->OpenDevice())
retcode = 1;
if (!retcode && !device->InitDevice(width, height, pixfmt, fps))
retcode = 2;
if (!retcode && !device->StartCapture())
retcode = 3;
if (!retcode && !device->Run(frames, time_to_capture))
retcode = 4;
if (!retcode && !device->StopCapture())
retcode = 5;
if (!retcode && !device->UninitDevice())
retcode = 6;
device->CloseDevice();
if (device)
delete device;
return retcode;
}