/** * Simple test program to test various ways to output an image via Xv * * to compile: * g++ -o xv_texturedvideo_test -lXv -lpng xv_texturedvideo_test.c */ #include #include #include #include #include //#include #include //#include #include #include #include #include Display* display = NULL; XvImage* xvimage = NULL; XvPortID xvport = None; GC xvgc; Window xvwindow; XShmSegmentInfo xvshminfo; #define _XV_COLORKEY_ 0x000102f3 /* ARGB (?) */ png_byte* png_image_buffer; int png_image_width = 0, png_image_height = 0, png_image_buffer_size = 0; #define FOURCC(a, b, c, d) (((uint32_t)(unsigned char)(a) << 0)|((uint32_t)(unsigned char)(b) << 8)|((uint32_t)(unsigned char)(c) << 16)|((uint32_t)(unsigned char)(d) << 24)) #define FOURCC_INVALID FOURCC(0, 0, 0, 0) #define FOURCC_RGBA32 FOURCC('R', 'G', 'B', 'A') #define FOURCC_RGB24 FOURCC('R', '-', '2', '4') #define FOURCC_RGBT16 FOURCC('R', 'G', 'B', 'T') #define FOURCC_RGB16 FOURCC('R', 'G', 'B', '2') #define FOURCC_RGB15 FOURCC('R', 'G', 'B', 'O') #define FOURCC_BGR15 FOURCC('B', 'G', 'R', 'O') #define FOURCC_UYVY FOURCC('U', 'Y', 'V', 'Y') int display_format = FOURCC_INVALID; char* window_title = NULL; void DestroyDisplay() { if(display) { if(xvport!=None) { XvUngrabPort(display, xvport, CurrentTime); xvport = None; } if(xvshminfo.shmaddr) { XShmDetach(display, &xvshminfo); XFree(xvimage); shmdt(xvshminfo.shmaddr); shmctl(xvshminfo.shmid, IPC_RMID, 0); } xvshminfo.shmaddr=0; // XCloseDisplay destroys all windows, resource IDs (Window, Font, Pixmap, Colormap, Cursor, and GContext), or other resources XCloseDisplay(display); } display = NULL; } bool CreateDisplay(int iResX, int iResY) { DestroyDisplay(); fprintf(stderr, "Opening X11 display\n"); display = XOpenDisplay(NULL); int screen = DefaultScreen(display); Window root = RootWindow(display, screen); // First we need to allocate some shared memory if(XShmQueryExtension(display)) { int major, minor, pixmaps; if(XShmQueryVersion(display, &major, &minor, &pixmaps)) { fprintf(stderr, "Shm version: %d.%d, pixmaps = %d\n", major, minor, pixmaps); // NOTE: We don't care about pixmaps (we don't use them) } } else { fprintf(stderr, "Shared memory not available!\n"); DestroyDisplay(); return false; } // Ask the server for all of the available adaptors unsigned int num_adaptors, a; XvAdaptorInfo *adaptor_info; XvQueryAdaptors(display, root, &num_adaptors, &adaptor_info); // Set some parameters depending on the requested display_format; int req_bpp = 0, req_depth = 0, req_type = 0; const char* req_component_order = "NONE"; switch(display_format) { case FOURCC_RGB15: req_bpp = 16; req_depth= 15; req_component_order = "RGB"; req_type = XvRGB; break; case FOURCC_BGR15: req_bpp = 16; req_depth= 15; req_component_order = "BGR"; req_type = XvRGB; break; case FOURCC_RGB16: req_bpp = 16; req_depth= 16; req_component_order = "RGB"; req_type = XvRGB; break; case FOURCC_RGBT16: req_bpp = 16; req_depth= 16; req_component_order = "ARGB"; req_type = XvRGB; break; case FOURCC_RGB24: req_bpp = 24; req_depth = 24; req_component_order = "RGB"; req_type = XvRGB; break; case FOURCC_RGBA32: req_bpp = 32; req_depth = 32; req_component_order = "ARGB"; req_type = XvRGB; break; case FOURCC_UYVY: req_bpp = 16; req_depth = 0; req_component_order = "UYVY"; req_type = XvYUV; } /** * Search the adaptors for an available port, and suitable image format */ xvport = None; int p; XvImageFormatValues format; bool format_found = false; for (a = 0; a < num_adaptors && xvport == None; a++) { printf ("Adapator \"%s\" has %lu ports\n", adaptor_info[a].name, adaptor_info[a].num_ports); for (p = 0; p < adaptor_info[a].num_ports && format_found == false; p++) { if (XvGrabPort (display, adaptor_info[a].base_id + p, CurrentTime) == Success) { xvport = adaptor_info[a].base_id + p; fprintf(stderr, "Opened port %d on adaptor \"%s\".\n", (int)xvport, adaptor_info[a].name); int nformats, f; XvImageFormatValues* formats = XvListImageFormats (display, xvport, &nformats); fprintf(stderr, "Available formats:\n"); for (f = 0; f < nformats; f++) { fprintf(stderr, "\ttype=%d, packed=%d, bpp=%d, depth=%d, nplanes=%d, scanline_order=%d, component_order=%s\n", formats[f].type, formats[f].format, formats[f].bits_per_pixel, formats[f].depth, formats[f].num_planes, formats[f].scanline_order, formats[f].component_order); /* * Check a bunch of port format data; most of this * is probably redundant, and there are probably * a few checks missing that are necessary */ if (formats[f].type != req_type) continue; if (formats[f].format != XvPacked) continue; if (formats[f].bits_per_pixel != req_bpp) continue; if (formats[f].depth != req_depth) continue; if (formats[f].num_planes != 1) continue; if (formats[f].scanline_order != XvTopToBottom) continue; if (strcmp (formats[f].component_order, req_component_order) != 0) continue; format = formats[f]; format_found = true; } XFree(formats); if(!format_found) { XvUngrabPort(display, xvport, CurrentTime); xvport = None; } } else fprintf(stderr, "Port %ld on adaptor \"%s\" unavailable.\n", adaptor_info[a].base_id + p, adaptor_info[a].name); } } if(format_found) { fprintf(stderr, "Picked from adaptor \"%s\", port %d:\n\ttype=%d, packed=%d, bpp=%d, depth=%d, nplanes=%d, scanline_order=%d, component_order=%s\n", adaptor_info[a-1].name, (int)xvport, format.type, format.format, format.bits_per_pixel, format.depth, format.num_planes, format.scanline_order, format.component_order); } XvFreeAdaptorInfo(adaptor_info); if(!format_found) { fprintf(stderr, "Could not find a suitable Xv image format!\n"); DestroyDisplay(); return false; } /** * Get Color Key. * This is not strictly necessary with textured video (in fact textured * video doesn't have a colorkey), but the code is there, leave it in. */ fprintf(stderr, "Getting the color key\n"); XvAttribute * attributes; int attrib_count,i; Atom xv_atom = None; attributes = XvQueryPortAttributes( display, xvport, &attrib_count ); if( attributes!=NULL ) { fprintf(stderr, "Checking %d attributes for \"XV_COLORKEY\"\n", attrib_count); for ( i = 0; i < attrib_count; ++i ) { fprintf(stderr, "\tattribute %d: %s\n", i, attributes[i].name); if ( strcmp(attributes[i].name, "XV_COLORKEY" ) == 0 ) { fprintf(stderr, "found XV_COLORKEY\n", attrib_count); xv_atom = XInternAtom( display, "XV_COLORKEY", False ); break; // found what we want, break out } } XFree( attributes ); } int xv_colorkey = _XV_COLORKEY_; if(xv_atom != None) { int xv_colorkey, colorkey_ret; int rez=XvGetPortAttribute(display, xvport, xv_atom, &colorkey_ret); if ( rez == Success ) { fprintf(stderr, "Succes, color key = 0x%08x\n", colorkey_ret); xv_colorkey = colorkey_ret; } } if(xv_colorkey == _XV_COLORKEY_) { /* FIXME: This is expected for Textured Video, * detect that and don't make a fuss about it */ fprintf(stderr, "Failed to get color key, falling back to 0x%08x\n", _XV_COLORKEY_); } // Create a window fprintf(stderr, "Setting up window\n"); XSetWindowAttributes wattr; wattr.event_mask = ExposureMask|KeyPressMask; wattr.border_pixel = 0; wattr.background_pixel = xv_colorkey; wattr.backing_store=NotUseful; wattr.override_redirect=False; // fixme: what does this do? wattr.save_under=False; wattr.do_not_propagate_mask=0; wattr.cursor=None; xvwindow = XCreateWindow (display, root, 100, 100, iResX, iResY, 0, DefaultDepth (display, screen), InputOutput, DefaultVisual (display, screen), CWEventMask|CWBackPixel | CWBorderPixel, &wattr); XSizeHints sizeHints; XWMHints wmHints; XTextProperty wm_name, icon_name; sizeHints.flags = 0; wmHints.flags = InputHint; wmHints.input = True; wm_name.value = (unsigned char*)window_title; wm_name.encoding = XA_STRING; wm_name.format = 8; wm_name.nitems = strlen ((char*)wm_name.value) + 1; icon_name = wm_name; XSetWMProperties (display, xvwindow, &wm_name, &icon_name, NULL, 0, &sizeHints, &wmHints, 0); XSelectInput(display, xvwindow, FocusChangeMask | ExposureMask | KeyPressMask | KeyReleaseMask); XMapRaised(display,xvwindow); XClearWindow(display,xvwindow); XEvent event; XWindowEvent(display,xvwindow,ExposureMask,&event); xvgc = XCreateGC (display, xvwindow, 0, NULL); XSetForeground(display, xvgc, 0xff408040); // ABGR XSetBackground(display, xvgc, 0xff000000); xvimage = XvShmCreateImage(display, xvport, format.id, NULL, png_image_width, png_image_height, &xvshminfo); if(xvimage->width != png_image_width || xvimage->height != png_image_height) { fprintf(stderr, "Error: image width/height differs: %ix%i != %ix%i\nDon't know what to do, aborting...\n", xvimage->width, xvimage->height, png_image_width, png_image_height); return false; } xvshminfo.shmaddr = 0; xvshminfo.shmid = shmget(IPC_PRIVATE, xvimage->data_size, IPC_CREAT | 0600); // FIXME: Not sse2 proof(?), see draw.c/DoBufferSwap if (xvshminfo.shmid != -1) { xvshminfo.shmaddr = (char*) shmat(xvshminfo.shmid, 0, 0); xvshminfo.readOnly = False; } else { fprintf(stderr, "Failed to get %d bytes of shared memory!\n", xvimage->data_size); DestroyDisplay(); return false; } if(!XShmAttach(display, &xvshminfo)) { fprintf(stderr, "Error on XShmAttach!\n"); DestroyDisplay(); return false; } xvimage->data = xvshminfo.shmaddr; fprintf(stderr, "CreateDisplay done\n"); return true; } bool LoadPNG(char* file_name) { FILE *fp = fopen(file_name, "rb"); if(!fp) { fprintf(stderr, "Error opening \"%s\" for reading!\n", file_name); return false; } png_byte header[8]; fread(header, 1, 8, fp); bool is_png = !png_sig_cmp(header, 0, 8); if(!is_png) { fprintf(stderr, "\"%s\" does not look like an PNG image\n", file_name); fclose(fp); return false; } png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { fprintf(stderr, "Error allocating png_read_struct"); return false; } png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { fprintf(stderr, "Error allocating png_info_struct"); png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); return false; } png_infop end_info = png_create_info_struct(png_ptr); if (!end_info) { fprintf(stderr, "Error allocating png_info_struct"); png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); return false; } png_init_io(png_ptr, fp); png_set_sig_bytes(png_ptr, 8); fprintf(stderr, "Loading image...\n"); // The next bit taken and modified from http://www.slackworks.com/~yotam/code/yimg/YImage.cpp // // Now we set up transforms. // 1. convert gray and paletted to rgb (this guarantees 8 or 16 bit depths (rgb must be 8 or 16)). // also expand the alpha color to an alpha channel and convert 16 bit depths to 8. // 2. if we don't have alpha, add an opaque channel. also swap BGR to RGB. // 3. ask libpng to deinterlace { png_read_info( png_ptr, info_ptr ) ; png_byte color_type = png_get_color_type( png_ptr, info_ptr ) ; png_byte depth = png_get_bit_depth( png_ptr, info_ptr ) ; // 1 if( color_type == PNG_COLOR_TYPE_PALETTE ) png_set_expand( png_ptr ) ; if( color_type == PNG_COLOR_TYPE_GRAY && depth < 8 ) png_set_expand( png_ptr ) ; if( png_get_valid( png_ptr, info_ptr, PNG_INFO_tRNS ) ) png_set_expand( png_ptr ) ; if( depth == 16 ) png_set_strip_16( png_ptr ) ; if( color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA ) png_set_gray_to_rgb( png_ptr ) ; // NOTE: This next step affects the layout of the channels in the pixel. // We turn the pixel into libpng default ARGB format. // 2 if( color_type != PNG_COLOR_TYPE_GRAY_ALPHA && color_type != PNG_COLOR_TYPE_RGB_ALPHA && !png_get_valid( png_ptr, info_ptr, PNG_INFO_tRNS ) ) png_set_filler( png_ptr, 0xFF, PNG_FILLER_AFTER); // 3 png_set_interlace_handling( png_ptr ) ; } { // Now we can create a rows[] pointing into our data* and read the png into our buffer. // 1. allocate row pointers array png_uint_32 width, height; int bit_depth, color_type; png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL); int stride = 4 * width; png_image_buffer = new png_byte[stride * height]; png_image_width = width; png_image_height = height; png_image_buffer_size = stride * height; if(!png_image_buffer) { fprintf(stderr, "Out of memory.\n"); return false; } png_bytep* row_pointers = (png_bytep*) png_malloc( png_ptr, height * sizeof(png_bytep) ) ; // 2. point row pointers into m_data for( int i = 0 ; i < height ; ++i ) row_pointers[i] = (png_bytep) (png_image_buffer + i*stride ) ; // 3. read the image data png_read_image( png_ptr, row_pointers ) ; // 4. free row pointers array png_free( png_ptr, row_pointers ) ; fprintf(stderr, "Image loaded: %dx%d, %dbpp color_type %d\n", width, height, bit_depth, color_type); } fclose(fp); png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return true; } void blit_RGBA32_to_ARGB1555(char* dst, png_byte* src, int width, int height) { for(int j = 0; j < height; ++j) { for(int i = 0; i < width; ++i) { char r = ((*src) >> 3) & 0x1f; ++src; char g = ((*src) >> 3) & 0x1f; ++src; char b = ((*src) >> 3) & 0x1f; ++src; char a = ((*src) >> 7) & 0x01; ++src; short co = 0; co |= ((a << 15) & 0x8000); co |= ((r << 10) & 0x7e00); co |= ((g << 5) & 0x03e0); co |= ((b << 0) & 0x001f); *(unsigned short*)dst = co; dst += 2; } } } void blit_RGBA32_to_RGB555(char* dst, png_byte* src, int width, int height) { for(int j = 0; j < height; ++j) { for(int i = 0; i < width; ++i) { char r = ((*src) >> 3) & 0x1f; ++src; char g = ((*src) >> 3) & 0x1f; ++src; char b = ((*src) >> 3) & 0x1f; ++src; /* no alpha channel */ ++src; short co = 0x8000; // start with full alpa co |= ((r << 10) & 0x7c00); co |= ((g << 5) & 0x03e0); co |= ((b << 0) & 0x001f); *(unsigned short*)dst = co; dst += 2; } } } void blit_RGBA32_to_BGR555(char* dst, png_byte* src, int width, int height) { for(int j = 0; j < height; ++j) { for(int i = 0; i < width; ++i) { char r = ((*src) >> 3) & 0x1f; ++src; char g = ((*src) >> 3) & 0x1f; ++src; char b = ((*src) >> 3) & 0x1f; ++src; /* no alpha channel */ ++src; short co = 0x8000; // start with full alpa co |= ((b << 10) & 0x7c00); co |= ((g << 5) & 0x03e0); co |= ((r << 0) & 0x001f); *(unsigned short*)dst = co; dst += 2; } } } void blit_RGBA32_to_RGB565(char* dst, png_byte* src, int width, int height) { for(int j = 0; j < height; ++j) { for(int i = 0; i < width; ++i) { char r = ((*src) >> 3) & 0x1f; ++src; char g = ((*src) >> 2) & 0x3f; ++src; char b = ((*src) >> 3) & 0x1f; ++src; /* no alpha channel */ ++src; short co = 0; co |= ((r << 11) & 0xf800); co |= ((g << 5) & 0x07e0); co |= ((b << 0) & 0x001f); *(unsigned short*)dst = co; dst += 2; } } } void blit_RGBA32_to_RGB24(char* dst, png_byte* src, int width, int height) { for(int j = 0; j < height; ++j) { for(int i = 0; i < width; ++i) { char r = *src; ++src; char g = *src; ++src; char b = *src; ++src; char a = *src; ++src; /* skip the alpha channel */ *dst = r; ++dst; *dst = g; ++dst; *dst = b; ++dst; } } } void blit_RGBA32_to_ARGB32(char* dst, png_byte* src, int width, int height) { for(int j = 0; j < height; ++j) { for(int i = 0; i < width; ++i) { char r = *src; ++src; char g = *src; ++src; char b = *src; ++src; char a = *src; ++src; // a r g b *dst = a; ++dst; *dst = r; ++dst; *dst = g; ++dst; *dst = b; ++dst; } } } void blit_RGBA32_to_UYVY(unsigned char* dst, png_byte* src, int width, int height) { fprintf(stderr, "width: %i", width); if(width&1) { // Because we need to pack two RGB pixels into one UYUV entry packed pixel fprintf(stderr, "Error: odd width not supported for UYVY\n"); return; } for(int j = 0; j < height; ++j) { for(int i = 0; i < width >> 1; ++i) { unsigned char r1 = *src; ++src; unsigned char g1 = *src; ++src; unsigned char b1 = *src; ++src; unsigned char a1 = *src; ++src; unsigned char r2 = *src; ++src; unsigned char g2 = *src; ++src; unsigned char b2 = *src; ++src; unsigned char a2 = *src; ++src; unsigned char y1 = ( ( 66 * r1 + 129 * g1 + 25 * b1 + 128) >> 8) + 16; unsigned char u1 = ( ( -38 * r1 - 74 * g1 + 112 * b1 + 128) >> 8) + 128; unsigned char v1 = ( ( 112 * r1 - 94 * g1 - 18 * b1 + 128) >> 8) + 128; unsigned char y2 = ( ( 66 * r2 + 129 * g2 + 25 * b2 + 128) >> 8) + 16; unsigned char u2 = ( ( -38 * r2 - 74 * g2 + 112 * b2 + 128) >> 8) + 128; unsigned char v2 = ( ( 112 * r2 - 94 * g2 - 18 * b2 + 128) >> 8) + 128; *dst = (u1+u2+1)>>1; ++dst; *dst = y1; ++dst; *dst = (v1+v2+1)>>1; ++dst; *dst = y2; ++dst; } } } void BlitImageBufferToXvImage() { fprintf(stderr, "Converting image to selected Xv format\n"); switch(display_format) { case FOURCC_RGB15: blit_RGBA32_to_RGB555(xvimage->data, png_image_buffer, xvimage->width, xvimage->height); break; case FOURCC_BGR15: blit_RGBA32_to_BGR555(xvimage->data, png_image_buffer, xvimage->width, xvimage->height); break; case FOURCC_RGBT16: blit_RGBA32_to_ARGB1555(xvimage->data, png_image_buffer, xvimage->width, xvimage->height); break; case FOURCC_UYVY: blit_RGBA32_to_UYVY((unsigned char*)xvimage->data, png_image_buffer, xvimage->width, xvimage->height); break; case FOURCC_RGB16: blit_RGBA32_to_RGB565(xvimage->data, png_image_buffer, xvimage->width, xvimage->height); break; case FOURCC_RGB24: blit_RGBA32_to_RGB24(xvimage->data, png_image_buffer, xvimage->width, xvimage->height); break; case FOURCC_RGBA32: blit_RGBA32_to_ARGB32(xvimage->data, png_image_buffer, xvimage->width, xvimage->height); break; default: fprintf(stderr, "ERROR: FOURCC %08x not supported!\n", display_format); } } void Repaint() { fprintf(stderr, "Repainting window\n"); XWindowAttributes attr; XGetWindowAttributes(display, xvwindow, &attr); XvShmPutImage(display, xvport, xvwindow, xvgc, xvimage, 0, 0, png_image_width, png_image_height, 0, 0, attr.width, attr.height, 0); } int main(int argc, char** argv) { if(argc < 2 || argc > 3) { fprintf(stderr, "Usage: xv_texturedvideo_test image.png [format]\n"); fprintf(stderr, "supported formats:\n"); fprintf(stderr, "\tRGBA32 (0x%08x)\n", FOURCC_RGBA32); fprintf(stderr, "\tRGB24 (0x%08x)\n", FOURCC_RGB24); fprintf(stderr, "\tRGB16 (0x%08x)\n", FOURCC_RGB16); fprintf(stderr, "\tRGBT16 (0x%08x)\n", FOURCC_RGBT16); fprintf(stderr, "\tRGB15 (0x%08x)\n", FOURCC_RGB15); fprintf(stderr, "\tBGR15 (0x%08x)\n", FOURCC_BGR15); fprintf(stderr, "\tUYVY (0x%08x)\n", FOURCC_UYVY); return 1; } display_format = FOURCC_RGBA32; if(argc == 3) { display_format = FOURCC_INVALID; if(strcmp(argv[2], "RGB15") == 0) display_format = FOURCC_RGB15; if(strcmp(argv[2], "BGR15") == 0) display_format = FOURCC_BGR15; if(strcmp(argv[2], "RGB16") == 0) display_format = FOURCC_RGB16; if(strcmp(argv[2], "RGBT16") == 0) display_format = FOURCC_RGBT16; if(strcmp(argv[2], "RGB24") == 0) display_format = FOURCC_RGB24; if(strcmp(argv[2], "RGBA32") == 0) display_format = FOURCC_RGBA32; if(strcmp(argv[2], "UYVY") == 0) display_format = FOURCC_UYVY; if(display_format == FOURCC_INVALID) { fprintf(stderr, "FOURCC not supported: %s\n", argv[2]); return 1; } } if(!LoadPNG(argv[1])) { return 1; } const char* fourcc = "RGBA32"; if(argc == 3) fourcc = argv[2]; window_title = new char[256]; snprintf(window_title, 256, "Xv PNG viewer v0.1 - %s - %s", argv[1], fourcc); if(!CreateDisplay(640, 480)) { return 1; } BlitImageBufferToXvImage(); XEvent event; Atom wmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", False); XSetWMProtocols(display, xvwindow, &wmDeleteMessage, 1); bool done = false; while(!done) { Repaint(); while(XPending(display)) { XNextEvent(display, &event); if(event.type == ClientMessage && event.xclient.data.l[0] == wmDeleteMessage) done = true; } if(!done) usleep(100000); } delete[] window_title; DestroyDisplay(); return 0; }