How to render images to /dev/video0 using v4l2loopback? - c++

I've been trying to render images to /dev/video. I can get something to sort of display but it's somewhat scrambled.
I first started off trying to render a normal RGB24 image (based off this example https://stackoverflow.com/a/44648382/3818491), but the result (below) was a scrambled image.
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <iostream>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <CImg.h>
#define VIDEO_OUT "/dev/video0" // V4L2 Loopack
#define WIDTH 1280
#define HEIGHT 720
int main() {
using namespace cimg_library;
CImg<uint8_t> canvas(WIDTH, HEIGHT, 1, 3);
const uint8_t red[] = {255, 0, 0};
const uint8_t purple[] = {255, 0, 255};
int fd;
if ((fd = open(VIDEO_OUT, O_RDWR)) == -1) {
std::cerr << "Unable to open video output!\n";
return 1;
}
struct v4l2_format vid_format;
vid_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
if (ioctl(fd, VIDIOC_G_FMT, &vid_format) == -1) {
std::cerr << "Unable to get video format data. Errro: " << errno << '\n';
return 1;
}
size_t framesize = canvas.size();
int width = canvas.width(), height = canvas.height();
vid_format.fmt.pix.width = width;
vid_format.fmt.pix.height = height;
vid_format.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;
vid_format.fmt.pix.sizeimage = framesize;
vid_format.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(fd, VIDIOC_S_FMT, &vid_format) == -1) {
std::cerr << "Unable to set video format! Errno: " << errno << '\n';
return 1;
}
std::cout << "Stream running!\n";
while (true) {
canvas.draw_plasma();
canvas.draw_rectangle(
100, 100, 100 + 100, 100 + 100, red, 1);
canvas.draw_text(5,5, "Hello World!", purple);
canvas.draw_text(5, 20, "Image freshly rendered with the CImg Library!", red);
write(fd, canvas.data(), framesize);
}
}
So I checked what (I think) /dev/video expects which seems to be YUV420P.
v4l2-ctl --list-formats-ext 130 ↵
ioctl: VIDIOC_ENUM_FMT
Type: Video Capture
[0]: 'YU12' (Planar YUV 4:2:0)
Size: Discrete 1280x720
Interval: Discrete 0.033s (30.000 fps)
So I attempted to convert the frame that format (using this code to quickly test).
Adjusting the spec to:
vid_format.fmt.pix.width = width;
vid_format.fmt.pix.height = height;
vid_format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;
vid_format.fmt.pix.sizeimage = width*height*3/2; // size of yuv buffer
vid_format.fmt.pix.field = V4L2_FIELD_NONE;
That results in this (which seems to be from what I've gathered the structure of a yuv420 image but still rendered incorrectly).
What does /dev/video0 expect?

After a lot of hacking around, I've managed to generate a valid YUYV video/image to send to /dev/video0.
First I make a buffer to hold the frame:
// Allocate buffer for the YUUV frame
std::vector<uint8_t> buffer;
buffer.resize(vid_format.fmt.pix.sizeimage);
Then I write the current canvas to the buffer in YUYV format.
bool skip = true;
cimg_forXY(canvas, cx, cy) {
size_t row = cy * width * 2;
uint8_t r, g, b, y;
r = canvas(cx, cy, 0);
g = canvas(cx, cy, 1);
b = canvas(cx, cy, 2);
y = std::clamp<uint8_t>(r * .299000 + g * .587000 + b * .114000, 0, 255);
buffer[row + cx * 2] = y;
if (!skip) {
uint8_t u, v;
u = std::clamp<uint8_t>(r * -.168736 + g * -.331264 + b * .500000 + 128, 0, 255);
v = std::clamp<uint8_t>(r * .500000 + g * -.418688 + b * -.081312 + 128, 0, 255);
buffer[row + (cx - 1) * 2 + 1] = u;
buffer[row + (cx - 1) * 2 + 3] = v;
}
skip = !skip;
}
Note:
CImg has RGBtoYUV has an in-place RGB to YUV conversion, but for some reason calling this on a uint8_t canvas just zeros it.
It also has get_YUVtoRGB which (allocates and) returns a CImg<float> canvas, which I think you multiply each value by 255 to scale to a byte, however, whatever I tried that did not give the correct colour. Edit: I likely forgot the +128 bias (though I still prefer not reallocating for each frame)
My full code is here (if anyone wants to do something similar) https://gist.github.com/MacDue/36199c3f3ca04bd9fd40a1bc2067ef72

Related

Convert Pixels Buffer type from 1555 to 5551 (C++, OpenGL ES)

I'm having a problem while converting OpenGL video plugin to support GLES 3.0
So far everything went well, except glTexSubImage2D the original code uses GL_UNSIGNED_SHORT_1_5_5_5_REV as pixels type which is not supported in GLES 3.0
the type that worked is GL_UNSIGNED_SHORT_5_5_5_1 but colors and pixels are broken,
so I thought converting the pixels buffer would be fine..
but due to my limited understanding in GL and C++ I didn't succeed to do that.
Pixels process:
the pixels will be converted internally to 16 bit ABGR as in the Shader comments:
// Take a normalized color and convert it into a 16bit 1555 ABGR
// integer in the format used internally by the Playstation GPU.
uint rebuild_psx_color(vec4 color) {
uint a = uint(floor(color.a + 0.5));
uint r = uint(floor(color.r * 31. + 0.5));
uint g = uint(floor(color.g * 31. + 0.5));
uint b = uint(floor(color.b * 31. + 0.5));
return (a << 15) | (b << 10) | (g << 5) | r;
}
it will be received by this method after processing by vGPU:
static void Texture_set_sub_image_window(struct Texture *tex, uint16_t top_left[2], uint16_t resolution[2], size_t row_len, uint16_t* data)
{
uint16_t x = top_left[0];
uint16_t y = top_left[1];
/* TODO - Am I indexing data out of bounds? */
size_t index = ((size_t) y) * row_len + ((size_t) x);
uint16_t* sub_data = &( data[index] );
glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint) row_len);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glBindTexture(GL_TEXTURE_2D, tex->id);
glTexSubImage2D(GL_TEXTURE_2D, 0,
(GLint) top_left[0], (GLint) top_left[1],
(GLsizei) resolution[0], (GLsizei) resolution[1],
GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV /* Not supported in GLES */,
(void*)sub_data);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
as for row_len it's get the value from #define VRAM_WIDTH_PIXELS 1024
What I tried to do:
1st I replaced the type with another one:
glTexSubImage2D(GL_TEXTURE_2D, 0,
(GLint) top_left[0], (GLint) top_left[1],
(GLsizei) resolution[0], (GLsizei) resolution[1],
GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 /* <- Here new type */,
(void*)sub_data);
2nd converted sub_data using this method:
uint16_t* ABGRConversion(const uint16_t* pixels, int row_len, int x, int y, int width, int height) {
uint16_t *frameBuffer = (uint16_t*)malloc(width * row_len * height);
signed i, j;
for (j=0; j < height; j++)
{
for (i=0; i < width; i++)
{
int offset = j * row_len + i;
uint16_t pixel = pixels[offset];
frameBuffer[offset] = Convert1555To5551(pixel); //<- stuck here
}
}
return frameBuffer;
}
I have no idea what Convert1555To5551 should look like?
Note: Sorry if some descriptions is wrong, I don't really have full understanding for the whole process.
Performance is not major problem.. just need to know how to deal with the current pixel buffer.
Side note: I had to replace glFramebufferTexture with glFramebufferTexture2D so I hope it's not involved in the issue.
Thanks.
This should be what you're looking for.
uint16_t Convert1555To5551(uint16_t pixel)
{
// extract rgba from 1555 (1 bit alpha, 5 bits blue, 5 bits green, 5 bits red)
uint16_t a = pixel >> 15;
uint16_t b = (pixel >> 10) & 0x1f; // mask lowest five bits
uint16_t g = (pixel >> 5) & 0x1f;
uint16_t r = pixel & 0x1f;
// compress rgba into 5551 (5 bits red, 5 bits green, 5 bits blue, 1 bit alpha)
return (r << 11) | (g << 6) | (b << 1) | a;
}

How to get image with transparent background from FLTK Fl__Image__Surface?

I want to draw string or char (offscreen) and use it as Fl_Image or Fl_RGB_Image.
Based on this link I can do that easily with Fl__Image__Surface. The problem with Fl__Image__Surface is that it does not support transparency when I convert its output to image (Fl_RGB_Image) using image() method. So is there any way I can achieve this? I can do that on Java Swing with BufferedImage, also in Android with Canvas by creating Bitmap with Bitmap.Config.ARGB_8888.
If you prefer to do it manually, you can try the following:
#include <FL/Enumerations.H>
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Device.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Image.H>
#include <FL/Fl_Image_Surface.H>
#include <FL/fl_draw.H>
#include <cassert>
#include <vector>
Fl_RGB_Image *get_image(int w, int h) {
// draw image on surface
auto img_surf = new Fl_Image_Surface(w, h);
Fl_Surface_Device::push_current(img_surf);
// We'll use white to mask 255, 255, 255, see the loop
fl_color(FL_WHITE);
fl_rectf(0, 0, w, h);
fl_color(FL_BLACK);
fl_font(FL_HELVETICA_BOLD, 20);
fl_draw("Hello", 100, 100);
auto image = img_surf->image();
delete img_surf;
Fl_Surface_Device::pop_current();
return image;
}
Fl_RGB_Image *get_transparent_image(const Fl_RGB_Image *image) {
assert(image);
// make image transparent
auto data = (const unsigned char*)(*image->data());
auto len = image->w() * image->h() * image->d(); // the depth is by default 3
std::vector<unsigned char> temp;
for (size_t i = 0; i < len; i++) {
if (i > 0 && i % 3 == 0) {
// check if the last 3 vals are the rgb values of white, add a 0 alpha
if (data[i] == 255 && data[i - 1] == 255 && data[i - 2] == 255)
temp.push_back(0);
else
// add a 255 alpha, making the black opaque
temp.push_back(255);
temp.push_back(data[i]);
} else {
temp.push_back(data[i]);
}
}
temp.push_back(0);
assert(temp.size() == image->w() * image->h() * 4);
auto new_image_data = new unsigned char[image->w() * image->h() * 4];
memcpy(new_image_data, temp.data(), image->w() * image->h() * 4);
auto new_image = new Fl_RGB_Image(new_image_data, image->w(), image->h(), 4); // account for alpha
return new_image;
}
int main() {
auto win = new Fl_Double_Window(400, 300);
auto box = new Fl_Box(0, 0, 400, 300);
win->end();
win->show();
auto image = get_image(box->w(), box->h());
auto transparent_image = get_transparent_image(image);
delete image;
box->image(transparent_image);
box->redraw();
return Fl::run();
}
The idea is that an Fl_Image_Surface gives an Fl_RGB_Image with 3 channels (r, g, b), no alpha. We manually add the alpha by creating a temporary vector, querying the data (can be optimized if you know the colors you're using by only checking data[i] == 255. The vector is an RAII type whose life ends at the end of the scope, so we just mempcy the data from the vector to a long-lived unsigned char array that we pass to an Fl_RGB_Image specifying the depth to be 4, accounting for alpha.
The other option is to use an external library like CImg (single header lib) to draw text into an image buffer and then pass that buffer to Fl_RGB_Image.

FreeImage wrong image color

I am trying to extract frames from a stream which I create with Gstreamer and trying to save them with FreeImage or QImage ( this one is for testing ).
GstMapInfo bufferInfo;
GstBuffer *sampleBuffer;
GstStructure *capsStruct;
GstSample *sample;
GstCaps *caps;
int width, height;
const int BitsPP = 32;
/* Retrieve the buffer */
g_signal_emit_by_name (sink, "pull-sample", &sample);
if (sample) {
sampleBuffer = gst_sample_get_buffer(sample);
gst_buffer_map(sampleBuffer,&bufferInfo,GST_MAP_READ);
if (!bufferInfo.data) {
g_printerr("Warning: could not map GStreamer buffer!\n");
throw;
}
caps = gst_sample_get_caps(sample);
capsStruct= gst_caps_get_structure(caps,0);
gst_structure_get_int(capsStruct,"width",&width);
gst_structure_get_int(capsStruct,"height",&height);
auto bitmap = FreeImage_Allocate(width, height, BitsPP,0,0,0);
memcpy( FreeImage_GetBits( bitmap ), bufferInfo.data, width * height * (BitsPP/8));
// int pitch = ((((BitsPP * width) + 31) / 32) * 4);
// auto bitmap = FreeImage_ConvertFromRawBits(bufferInfo.data,width,height,pitch,BitsPP,0, 0, 0);
FreeImage_FlipHorizontal(bitmap);
bitmap = FreeImage_RotateClassic(bitmap,180);
static int id = 0;
std::string name = "/home/stadmin/pic/sample" + std::to_string(id++) + ".png";
#ifdef FREE_SAVE
FreeImage_Save(FIF_PNG,bitmap,name.c_str());
#endif
#ifdef QT_SAVE
//Format_ARGB32
QImage image(bufferInfo.data,width,height,QImage::Format_ARGB32);
image.save(QString::fromStdString(name));
#endif
fibPipeline.push(bitmap);
gst_sample_unref(sample);
gst_buffer_unmap(sampleBuffer, &bufferInfo);
return GST_FLOW_OK;
The color output in FreeImage are totally wrong like when Qt - Format_ARGB32 [ greens like blue or blues like oranges etc.. ] but when I test with Qt - Format_RGBA8888 I can get correct output. I need to use FreeImage and I wish to learn how to correct this.
Since you say Qt succeeds using Format_RGBA8888, I can only guess: the gstreamer frame has bytes in RGBA order while FreeImage expects ARGB.
Quick fix:
//have a buffer the same length of the incoming bytes
size_t length = width * height * (BitsPP/8);
BYTE * bytes = (BYTE *) malloc(length);
//copy the incoming bytes to it, in the right order:
int index = 0;
while(index < length)
{
bytes[index] = bufferInfo.data[index + 2]; //B
bytes[index + 1] = bufferInfo.data[index + 1]; //G
bytes[index + 2] = bufferInfo.data[index]; //R
bytes[index + 3] = bufferInfo.data[index + 3]; //A
index += 4;
}
//fill the bitmap using the buffer
auto bitmap = FreeImage_Allocate(width, height, BitsPP,0,0,0);
memcpy( FreeImage_GetBits( bitmap ), bytes, length);
//don't forget to
free(bytes);

How to resize an image from an rgb buffer using c++

I have an (char*)RGB buffer that has the data of actual image. Let's say that the actual image resolution is 720x576. Now I want to resize it to a resolution , say 120x90.
How can I do this using https://code.google.com/p/jpeg-compressor/ or libjpeg ?
Note: can use any other library, but should work in linux.
Edited: Video decoder decodes a frame in YUV, which I convert it into RGB. All these happen in a buffer.
I need to resize the RGB buffer to make a thumbnail out of it with variable size.
Thanks for the help in advance
I did the following to achieve my goal:
#define TN_WIDTH 240
#define TN_HEIGHT 180
#include "jpegcompressor/jpge.h"
#include "jpegcompressor/jpgd.h"
#include <ippi.h>
bool createThumnailJpeg(const uint8* pSrc, int srcwidth, int srcheight)
{
int req_comps = 3;
jpge::params params;
params.m_quality = 50;
params.m_subsampling = jpge::H2V2;
params.m_two_pass_flag = false;
FILE *fpJPEGTN = fopen("Resource\\jpegcompressor.jpeg","wb");
int dstWidth = TN_WIDTH;
int dstHeight = TN_HEIGHT;
int uiDstBufferSize = dstWidth * dstHeight * 3;
uint8 *pDstRGBBuffer = new uint8[uiDstBufferSize];
uint8 *pJPEGTNBuffer = new uint8[uiDstBufferSize];
int uiSrcBufferSize = srcwidth * srcheight * 3;
IppiSize srcSize = {srcwidth , srcheight};
IppiRect srcROI = {0, 0, srcwidth, srcheight};
IppiSize dstROISize = {dstWidth, dstHeight};
double xfactor = (double) dstWidth / srcwidth;
double yfactor = (double) dstHeight / srcheight;
IppStatus status = ippiResize_8u_C3R(pSrc, srcSize, srcwidth*3, srcROI,
pDstRGBBuffer, dstWidth*3, dstROISize, xfactor, yfactor, 1);
if (!jpge::compress_image_to_jpeg_file_in_memory(pJPEGTNBuffer, uiDstBufferSize, dstWidth, dstHeight, req_comps, pDstRGBBuffer, params))
{
cout << "failed!";
delete[] pDstRGBBuffer;
delete [] pJPEGTNBuffer;
return false;
}
if (fpJPEGTN)
{
fwrite(pJPEGTNBuffer, uiDstBufferSize, 1, fpJPEGTN);
fclose(fpJPEGTN);
}
delete [] pDstRGBBuffer;
delete [] pJPEGTNBuffer;
return true;
}

Issue with writing YUV image frame in C/C++

I am trying to convert an RGB frame, which is taken from OpenGL glReadPixels(), to a YUV frame, and write the YUV frame to a file (.yuv). Later on I would like to write it to a named_pipe as an input for FFMPEG, but as for now I just want to write it to a file and view the image result using a YUV Image Viewer. So just disregard the "writing to pipe" for now.
After running my code, I encountered the following errors:
The number of frames shown in the YUV Image Viewer software is always 1/3 of the number of frames I declared in my program. When I declare fps as 10, I could only view 3 frames. When I declared fps as 30, I could only view 10 frames. However when I view the file in Text Editor, I could see that I have the correct amount of word "FRAME" printed in the file.
This is the example output that I got: http://www.bobdanani.net/image.yuv
I could not see the correct image, but just some distorted green, blue, yellow, and black pixels.
I read about YUV format from http://wiki.multimedia.cx/index.php?title=YUV4MPEG2 and http://www.fourcc.org/fccyvrgb.php#mikes_answer and http://kylecordes.com/2007/pipe-ffmpeg
Here is what I have tried so far. I know that this conversion approach is quite in-efficient, and I can optimize it later. Now I just want to get this naive approach to work and have the image shown properly.
int frameCounter = 1;
int windowWidth = 0, windowHeight = 0;
unsigned char *yuvBuffer;
unsigned long bufferLength = 0;
unsigned long frameLength = 0;
int fps = 10;
void display(void) {
/* clear the color buffers */
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* DRAW some OPENGL animation, i.e. cube, sphere, etc
.......
.......
*/
glutSwapBuffers();
if ((frameCounter % fps) == 1){
bufferLength = 0;
windowWidth = glutGet(GLUT_WINDOW_WIDTH);
windowHeight = glutGet (GLUT_WINDOW_HEIGHT);
frameLength = (long) (windowWidth * windowHeight * 1.5 * fps) + 100; // YUV 420 length (width*height*1.5) + header length
yuvBuffer = new unsigned char[frameLength];
write_yuv_frame_header();
}
write_yuv_frame();
frameCounter = (frameCounter % fps) + 1;
if ( (frameCounter % fps) == 1){
snprintf(filename, 100, "out/image-%d.yuv", seq_num);
ofstream out(filename, ios::out | ios::binary);
if(!out) {
cout << "Cannot open file.\n";
}
out.write (reinterpret_cast<char*> (yuvBuffer), bufferLength);
out.close();
bufferLength = 0;
delete[] yuvBuffer;
}
}
void write_yuv_frame_header (){
char *yuvHeader = new char[100];
sprintf (yuvHeader, "YUV4MPEG2 W%d H%d F%d:1 Ip A0:0 C420mpeg2 XYSCSS=420MPEG2\n", windowWidth, windowHeight, fps);
memcpy ((char*)yuvBuffer + bufferLength, yuvHeader, strlen(yuvHeader));
bufferLength += strlen (yuvHeader);
delete (yuvHeader);
}
void write_yuv_frame() {
int width = glutGet(GLUT_WINDOW_WIDTH);
int height = glutGet(GLUT_WINDOW_HEIGHT);
memcpy ((void*) (yuvBuffer+bufferLength), (void*) "FRAME\n", 6);
bufferLength +=6;
long length = windowWidth * windowHeight;
long yuv420FrameLength = (float)length * 1.5;
long lengthRGB = length * 3;
unsigned char *rgb = (unsigned char *) malloc(lengthRGB * sizeof(unsigned char));
unsigned char *yuvdest = (unsigned char *) malloc(yuv420FrameLength * sizeof(unsigned char));
glReadPixels(0, 0, windowWidth, windowHeight, GL_RGB, GL_UNSIGNED_BYTE, rgb);
int r, g, b, y, u, v, ypos, upos, vpos;
for (int j = 0; j < windowHeight; ++j){
for (int i = 0; i < windowWidth; ++i){
r = (int)rgb[(j * windowWidth + i) * 3 + 0];
g = (int)rgb[(j * windowWidth + i) * 3 + 1];
b = (int)rgb[(j * windowWidth + i) * 3 + 2];
y = (int)(r * 0.257 + g * 0.504 + b * 0.098) + 16;
u = (int)(r * 0.439 + g * -0.368 + b * -0.071) + 128;
v = (int)(r * -0.148 + g * -0.291 + b * 0.439 + 128);
ypos = j * windowWidth + i;
upos = (j/2) * (windowWidth/2) + i/2 + length;
vpos = (j/2) * (windowWidth/2) + i/2 + length + length/4;
yuvdest[ypos] = y;
yuvdest[upos] = u;
yuvdest[vpos] = v;
}
}
memcpy ((void*) (yuvBuffer + bufferLength), (void*)yuvdest, yuv420FrameLength);
bufferLength += yuv420FrameLength;
free (yuvdest);
free (rgb);
}
This is just the very basic approach, and I can optimize the conversion algorithm later.
Can anyone tell me what is wrong in my approach? My guess is that one of the issues is with the outstream.write() call, because I converted the unsigned char* data to char* data that it may lose data precision. But if I don't cast it to char* I will get a compile error. However this doesn't explain why the output frames are corrupted (only account to 1/3 of the number of total frames).
It looks to me like you have too many bytes per frame for 4:2:0 data. ACcording to the spec you linked to, the number of bytes for a 200x200 pixel 4:2:0 frame should be 200 * 200 * 3 / 2 = 60,000. But you have ~90,000 bytes. Looking at your code, I don't see where you are convert from 4:4:4 to 4:2:0. So you have 2 choices - either set the header to 4:4:4, or convert the YCbCr data to 4:2:0 before writing it out.
I compiled your code and surely there is a problem when computing upos and vpos values.
For me this worked (RGB to YUV NV12):
vpos = length + (windowWidth * (j/2)) + (i/2)*2;
upos = vpos + 1;