#include #include #include #include #include extern "C" auto XvShmCreateImage(Display*, XvPortID, signed, char*, signed, signed, XShmSegmentInfo*) -> XvImage*; struct VideoXv : Video { ~VideoXv() { term(); } uint32_t* buffer = nullptr; uint8_t* ytable = nullptr; uint8_t* utable = nullptr; uint8_t* vtable = nullptr; enum XvFormat : unsigned { XvFormatRGB32, XvFormatRGB24, XvFormatRGB16, XvFormatRGB15, XvFormatYUY2, XvFormatUYVY, XvFormatUnknown, }; struct { Display* display = nullptr; GC gc = 0; Window window = 0; Colormap colormap = 0; XShmSegmentInfo shminfo; signed port = -1; signed depth = 0; signed visualid = 0; XvImage* image = nullptr; XvFormat format = XvFormatUnknown; uint32_t fourcc = 0; unsigned width = 0; unsigned height = 0; } device; struct { Window handle = 0; bool synchronize = false; unsigned width = 0; unsigned height = 0; } settings; auto cap(const string& name) -> bool { if(name == Video::Handle) return true; if(name == Video::Synchronize) { Display* display = XOpenDisplay(nullptr); bool result = XInternAtom(display, "XV_SYNC_TO_VBLANK", true) != None; XCloseDisplay(display); return result; } return false; } auto get(const string& name) -> any { if(name == Video::Handle) return settings.handle; if(name == Video::Synchronize) return settings.synchronize; return {}; } auto set(const string& name, const any& value) -> bool { if(name == Video::Handle && value.is()) { settings.handle = value.get(); return true; } if(name == Video::Synchronize && value.is()) { bool result = false; Display* display = XOpenDisplay(nullptr); Atom atom = XInternAtom(display, "XV_SYNC_TO_VBLANK", true); if(atom != None && device.port >= 0) { settings.synchronize = value.get(); XvSetPortAttribute(display, device.port, atom, settings.synchronize); result = true; } XCloseDisplay(display); return result; } return false; } auto resize(unsigned width, unsigned height) -> void { if(device.width >= width && device.height >= height) return; device.width = max(width, device.width); device.height = max(height, device.height); XShmDetach(device.display, &device.shminfo); shmdt(device.shminfo.shmaddr); shmctl(device.shminfo.shmid, IPC_RMID, nullptr); XFree(device.image); delete[] buffer; device.image = XvShmCreateImage(device.display, device.port, device.fourcc, 0, device.width, device.height, &device.shminfo); device.shminfo.shmid = shmget(IPC_PRIVATE, device.image->data_size, IPC_CREAT | 0777); device.shminfo.shmaddr = device.image->data = (char*)shmat(device.shminfo.shmid, 0, 0); device.shminfo.readOnly = false; XShmAttach(device.display, &device.shminfo); buffer = new uint32_t[device.width * device.height]; } auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool { if(width != settings.width || height != settings.height) { resize(settings.width = width, settings.height = height); } pitch = device.width * 4; return data = buffer; } auto unlock() -> void { } auto clear() -> void { memory::fill(buffer, device.width * device.height * sizeof(uint32_t)); //clear twice in case video is double buffered ... refresh(); refresh(); } auto refresh() -> void { unsigned width = settings.width; unsigned height = settings.height; XWindowAttributes target; XGetWindowAttributes(device.display, device.window, &target); //we must ensure that the child window is the same size as the parent window. //unfortunately, we cannot hook the parent window resize event notification, //as we did not create the parent window, nor have any knowledge of the toolkit used. //therefore, query each window size and resize as needed. XWindowAttributes parent; XGetWindowAttributes(device.display, settings.handle, &parent); if(target.width != parent.width || target.height != parent.height) { XResizeWindow(device.display, device.window, parent.width, parent.height); } //update target width and height attributes XGetWindowAttributes(device.display, device.window, &target); switch(device.format) { case XvFormatRGB32: renderRGB32(width, height); break; case XvFormatRGB24: renderRGB24(width, height); break; case XvFormatRGB16: renderRGB16(width, height); break; case XvFormatRGB15: renderRGB15(width, height); break; case XvFormatYUY2: renderYUY2 (width, height); break; case XvFormatUYVY: renderUYVY (width, height); break; } XvShmPutImage(device.display, device.port, device.window, device.gc, device.image, 0, 0, width, height, 0, 0, target.width, target.height, true); } auto init() -> bool { device.display = XOpenDisplay(nullptr); if(!XShmQueryExtension(device.display)) { fprintf(stderr, "VideoXv: XShm extension not found.\n"); return false; } //find an appropriate Xv port device.port = -1; XvAdaptorInfo* adaptor_info; unsigned adaptor_count; XvQueryAdaptors(device.display, DefaultRootWindow(device.display), &adaptor_count, &adaptor_info); for(unsigned i = 0; i < adaptor_count; i++) { //find adaptor that supports both input (memory->drawable) and image (drawable->screen) masks if(adaptor_info[i].num_formats < 1) continue; if(!(adaptor_info[i].type & XvInputMask)) continue; if(!(adaptor_info[i].type & XvImageMask)) continue; device.port = adaptor_info[i].base_id; device.depth = adaptor_info[i].formats->depth; device.visualid = adaptor_info[i].formats->visual_id; break; } XvFreeAdaptorInfo(adaptor_info); if(device.port < 0) { fprintf(stderr, "VideoXv: failed to find valid XvPort.\n"); return false; } //create child window to attach to parent window. //this is so that even if parent window visual depth doesn't match Xv visual //(common with composited windows), Xv can still render to child window. XWindowAttributes window_attributes; XGetWindowAttributes(device.display, settings.handle, &window_attributes); XVisualInfo visualtemplate; visualtemplate.visualid = device.visualid; visualtemplate.screen = DefaultScreen(device.display); visualtemplate.depth = device.depth; visualtemplate.visual = 0; signed visualmatches = 0; XVisualInfo* visualinfo = XGetVisualInfo(device.display, VisualIDMask | VisualScreenMask | VisualDepthMask, &visualtemplate, &visualmatches); if(visualmatches < 1 || !visualinfo->visual) { if(visualinfo) XFree(visualinfo); fprintf(stderr, "VideoXv: unable to find Xv-compatible visual.\n"); return false; } device.colormap = XCreateColormap(device.display, settings.handle, visualinfo->visual, AllocNone); XSetWindowAttributes attributes; attributes.colormap = device.colormap; attributes.border_pixel = 0; attributes.event_mask = StructureNotifyMask; device.window = XCreateWindow(device.display, /* parent = */ settings.handle, /* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height, /* border_width = */ 0, device.depth, InputOutput, visualinfo->visual, CWColormap | CWBorderPixel | CWEventMask, &attributes); XFree(visualinfo); XSetWindowBackground(device.display, device.window, /* color = */ 0); XMapWindow(device.display, device.window); device.gc = XCreateGC(device.display, device.window, 0, 0); //set colorkey to auto paint, so that Xv video output is always visible Atom atom = XInternAtom(device.display, "XV_AUTOPAINT_COLORKEY", true); if(atom != None) XvSetPortAttribute(device.display, device.port, atom, 1); //find optimal rendering format device.format = XvFormatUnknown; signed format_count; XvImageFormatValues* format = XvListImageFormats(device.display, device.port, &format_count); if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { if(format[i].type == XvRGB && format[i].bits_per_pixel == 32) { device.format = XvFormatRGB32; device.fourcc = format[i].id; break; } } if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { if(format[i].type == XvRGB && format[i].bits_per_pixel == 24) { device.format = XvFormatRGB24; device.fourcc = format[i].id; break; } } if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { if(format[i].type == XvRGB && format[i].bits_per_pixel <= 16 && format[i].red_mask == 0xf800) { device.format = XvFormatRGB16; device.fourcc = format[i].id; break; } } if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { if(format[i].type == XvRGB && format[i].bits_per_pixel <= 16 && format[i].red_mask == 0x7c00) { device.format = XvFormatRGB15; device.fourcc = format[i].id; break; } } if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { if(format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked) { if(format[i].component_order[0] == 'Y' && format[i].component_order[1] == 'U' && format[i].component_order[2] == 'Y' && format[i].component_order[3] == 'V' ) { device.format = XvFormatYUY2; device.fourcc = format[i].id; break; } } } if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { if(format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked) { if(format[i].component_order[0] == 'U' && format[i].component_order[1] == 'Y' && format[i].component_order[2] == 'V' && format[i].component_order[3] == 'Y' ) { device.format = XvFormatUYVY; device.fourcc = format[i].id; break; } } } free(format); if(device.format == XvFormatUnknown) { fprintf(stderr, "VideoXv: unable to find a supported image format.\n"); return false; } device.width = 256; device.height = 256; device.image = XvShmCreateImage(device.display, device.port, device.fourcc, 0, device.width, device.height, &device.shminfo); if(!device.image) { fprintf(stderr, "VideoXv: XShmCreateImage failed.\n"); return false; } device.shminfo.shmid = shmget(IPC_PRIVATE, device.image->data_size, IPC_CREAT | 0777); device.shminfo.shmaddr = device.image->data = (char*)shmat(device.shminfo.shmid, 0, 0); device.shminfo.readOnly = false; if(!XShmAttach(device.display, &device.shminfo)) { fprintf(stderr, "VideoXv: XShmAttach failed.\n"); return false; } buffer = new uint32_t[device.width * device.height]; settings.width = 256; settings.height = 256; initTables(); clear(); return true; } auto term() -> void { XShmDetach(device.display, &device.shminfo); shmdt(device.shminfo.shmaddr); shmctl(device.shminfo.shmid, IPC_RMID, nullptr); XFree(device.image); if(device.window) { XUnmapWindow(device.display, device.window); device.window = 0; } if(device.colormap) { XFreeColormap(device.display, device.colormap); device.colormap = 0; } if(device.display) { XCloseDisplay(device.display); device.display = nullptr; } if(buffer) { delete[] buffer; buffer = nullptr; } if(ytable) { delete[] ytable; ytable = nullptr; } if(utable) { delete[] utable; utable = nullptr; } if(vtable) { delete[] vtable; vtable = nullptr; } } private: auto renderRGB32(unsigned width, unsigned height) -> void { uint32_t* input = (uint32_t*)buffer; uint32_t* output = (uint32_t*)device.image->data; for(unsigned y = 0; y < height; y++) { memcpy(output, input, width * 4); input += device.width; output += device.width; } } auto renderRGB24(unsigned width, unsigned height) -> void { uint32_t* input = (uint32_t*)buffer; uint8_t* output = (uint8_t*)device.image->data; for(unsigned y = 0; y < height; y++) { for(unsigned x = 0; x < width; x++) { uint32_t p = *input++; *output++ = p; *output++ = p >> 8; *output++ = p >> 16; } input += (device.width - width); output += (device.width - width) * 3; } } auto renderRGB16(unsigned width, unsigned height) -> void { uint32_t* input = (uint32_t*)buffer; uint16_t* output = (uint16_t*)device.image->data; for(unsigned y = 0; y < height; y++) { for(unsigned x = 0; x < width; x++) { uint32_t p = *input++; *output++ = ((p >> 8) & 0xf800) | ((p >> 5) & 0x07e0) | ((p >> 3) & 0x001f); //RGB32->RGB16 } input += device.width - width; output += device.width - width; } } auto renderRGB15(unsigned width, unsigned height) -> void { uint32_t* input = (uint32_t*)buffer; uint16_t* output = (uint16_t*)device.image->data; for(unsigned y = 0; y < height; y++) { for(unsigned x = 0; x < width; x++) { uint32_t p = *input++; *output++ = ((p >> 9) & 0x7c00) | ((p >> 6) & 0x03e0) | ((p >> 3) & 0x001f); //RGB32->RGB15 } input += device.width - width; output += device.width - width; } } auto renderYUY2(unsigned width, unsigned height) -> void { uint32_t* input = (uint32_t*)buffer; uint16_t* output = (uint16_t*)device.image->data; for(unsigned y = 0; y < height; y++) { for(unsigned x = 0; x < width >> 1; x++) { uint32_t p0 = *input++; uint32_t p1 = *input++; p0 = ((p0 >> 8) & 0xf800) + ((p0 >> 5) & 0x07e0) + ((p0 >> 3) & 0x001f); //RGB32->RGB16 p1 = ((p1 >> 8) & 0xf800) + ((p1 >> 5) & 0x07e0) + ((p1 >> 3) & 0x001f); //RGB32->RGB16 uint8_t u = (utable[p0] + utable[p1]) >> 1; uint8_t v = (vtable[p0] + vtable[p1]) >> 1; *output++ = (u << 8) | ytable[p0]; *output++ = (v << 8) | ytable[p1]; } input += device.width - width; output += device.width - width; } } auto renderUYVY(unsigned width, unsigned height) -> void { uint32_t* input = (uint32_t*)buffer; uint16_t* output = (uint16_t*)device.image->data; for(unsigned y = 0; y < height; y++) { for(unsigned x = 0; x < width >> 1; x++) { uint32_t p0 = *input++; uint32_t p1 = *input++; p0 = ((p0 >> 8) & 0xf800) + ((p0 >> 5) & 0x07e0) + ((p0 >> 3) & 0x001f); p1 = ((p1 >> 8) & 0xf800) + ((p1 >> 5) & 0x07e0) + ((p1 >> 3) & 0x001f); uint8_t u = (utable[p0] + utable[p1]) >> 1; uint8_t v = (vtable[p0] + vtable[p1]) >> 1; *output++ = (ytable[p0] << 8) | u; *output++ = (ytable[p1] << 8) | v; } input += device.width - width; output += device.width - width; } } auto initTables() -> void { ytable = new uint8_t[65536]; utable = new uint8_t[65536]; vtable = new uint8_t[65536]; for(unsigned i = 0; i < 65536; i++) { //extract RGB565 color data from i uint8_t r = (i >> 11) & 31, g = (i >> 5) & 63, b = (i) & 31; r = (r << 3) | (r >> 2); //R5->R8 g = (g << 2) | (g >> 4); //G6->G8 b = (b << 3) | (b >> 2); //B5->B8 //ITU-R Recommendation BT.601 //double lr = 0.299, lg = 0.587, lb = 0.114; int y = int( +(double(r) * 0.257) + (double(g) * 0.504) + (double(b) * 0.098) + 16.0 ); int u = int( -(double(r) * 0.148) - (double(g) * 0.291) + (double(b) * 0.439) + 128.0 ); int v = int( +(double(r) * 0.439) - (double(g) * 0.368) - (double(b) * 0.071) + 128.0 ); //ITU-R Recommendation BT.709 //double lr = 0.2126, lg = 0.7152, lb = 0.0722; //int y = int( double(r) * lr + double(g) * lg + double(b) * lb ); //int u = int( (double(b) - y) / (2.0 - 2.0 * lb) + 128.0 ); //int v = int( (double(r) - y) / (2.0 - 2.0 * lr) + 128.0 ); ytable[i] = y < 0 ? 0 : y > 255 ? 255 : y; utable[i] = u < 0 ? 0 : u > 255 ? 255 : u; vtable[i] = v < 0 ? 0 : v > 255 ? 255 : v; } } };