mirror of
https://github.com/pikvm/ustreamer.git
synced 2025-12-24 03:00:01 +00:00
jpeg
This commit is contained in:
parent
c1d4356fd3
commit
65f61cdae9
2
Makefile
2
Makefile
@ -1,6 +1,6 @@
|
||||
CC = gcc
|
||||
CFLAGS = -c -Wall -Wextra
|
||||
LDFLAGS =
|
||||
LDFLAGS = -ljpeg
|
||||
SOURCES = $(shell ls src/*.c)
|
||||
OBJECTS = $(SOURCES:.c=.o)
|
||||
PROG = ustreamer
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include "tools.h"
|
||||
#include "device.h"
|
||||
#include "jpeg.h"
|
||||
|
||||
|
||||
static int _capture_init_loop(struct device *dev);
|
||||
@ -87,7 +88,8 @@ void capture_loop(struct device *dev) {
|
||||
}
|
||||
|
||||
LOG_DEBUG("Grabbed a new frame");
|
||||
usleep(100000); // TODO: process dev->run->buffers[buf.index].start, buf.bytesused
|
||||
jpeg_compress_buffer(dev, buf.index);
|
||||
//usleep(100000); // TODO: process dev->run->buffers[buf.index].start, buf.bytesused
|
||||
|
||||
pass_frame:
|
||||
|
||||
|
||||
48
src/device.c
48
src/device.c
@ -39,6 +39,7 @@ static int _device_open_check_cap(struct device *dev);
|
||||
static int _device_open_dv_timings(struct device *dev);
|
||||
static int _device_apply_dv_timings(struct device *dev);
|
||||
static int _device_open_format(struct device *dev);
|
||||
static int _device_open_alloc_picbufs(struct device *dev);
|
||||
static int _device_open_mmap(struct device *dev);
|
||||
static int _device_open_queue_buffers(struct device *dev);
|
||||
|
||||
@ -109,6 +110,9 @@ int device_open(struct device *dev) {
|
||||
if (_device_open_queue_buffers(dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (_device_open_alloc_picbufs(dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Device fd=%d initialized", dev->run->fd);
|
||||
return 0;
|
||||
@ -119,12 +123,21 @@ int device_open(struct device *dev) {
|
||||
}
|
||||
|
||||
void device_close(struct device *dev) {
|
||||
if (dev->run->pictures) {
|
||||
LOG_DEBUG("Releasing picture buffers ...");
|
||||
for (unsigned index = 0; index < dev->run->n_buffers && dev->run->pictures[index]; ++index) {
|
||||
free(dev->run->pictures[index]);
|
||||
}
|
||||
free(dev->run->pictures);
|
||||
dev->run->pictures = NULL;
|
||||
}
|
||||
|
||||
if (dev->run->buffers) {
|
||||
LOG_DEBUG("Unmapping buffers ...");
|
||||
LOG_DEBUG("Unmapping device buffers ...");
|
||||
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
||||
if (dev->run->buffers[index].start != MAP_FAILED) {
|
||||
if (munmap(dev->run->buffers[index].start, dev->run->buffers[index].length) < 0) {
|
||||
LOG_PERROR("Can't unmap buffer %d", index);
|
||||
LOG_PERROR("Can't unmap device buffer %d", index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -319,10 +332,11 @@ static int _device_open_mmap(struct device *dev) {
|
||||
LOG_INFO("Requested %d device buffers, got %d", dev->n_buffers, req.count);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Allocating buffers ...");
|
||||
LOG_DEBUG("Allocating device buffers ...");
|
||||
|
||||
dev->run->buffers = NULL;
|
||||
if ((dev->run->buffers = calloc(req.count, sizeof(*dev->run->buffers))) == NULL) {
|
||||
LOG_PERROR("Can't allocate buffers");
|
||||
LOG_PERROR("Can't allocate device buffers");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -334,17 +348,17 @@ static int _device_open_mmap(struct device *dev) {
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = dev->run->n_buffers;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for buffer %d ...", dev->run->n_buffers);
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for device buffer %d ...", dev->run->n_buffers);
|
||||
if (xioctl(dev->run->fd, VIDIOC_QUERYBUF, &buf) < 0) {
|
||||
LOG_PERROR("Can't VIDIOC_QUERYBUF");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Mapping buffer %d ...", dev->run->n_buffers);
|
||||
LOG_DEBUG("Mapping device buffer %d ...", dev->run->n_buffers);
|
||||
dev->run->buffers[dev->run->n_buffers].length = buf.length;
|
||||
dev->run->buffers[dev->run->n_buffers].start = mmap(NULL, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, dev->run->fd, buf.m.offset);
|
||||
if (dev->run->buffers[dev->run->n_buffers].start == MAP_FAILED) {
|
||||
LOG_PERROR("Can't map buffer %d", dev->run->n_buffers);
|
||||
LOG_PERROR("Can't map device buffer %d", dev->run->n_buffers);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@ -369,6 +383,26 @@ static int _device_open_queue_buffers(struct device *dev) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _device_open_alloc_picbufs(struct device *dev) {
|
||||
LOG_DEBUG("Allocating picture buffers ...");
|
||||
|
||||
dev->run->pictures = NULL;
|
||||
if ((dev->run->pictures = calloc(dev->run->n_buffers, sizeof(*dev->run->pictures))) == NULL) {
|
||||
LOG_PERROR("Can't allocate picture buffers");
|
||||
return -1;
|
||||
}
|
||||
|
||||
dev->run->picture_size = dev->run->width * dev->run->height << 1;
|
||||
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
||||
LOG_DEBUG("Allocating picture buffer %d ...", index);
|
||||
if ((dev->run->pictures[index] = malloc(dev->run->picture_size)) == NULL) {
|
||||
LOG_PERROR("Can't allocate picture buffer %d", index);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *_format_to_string_auto(char *buf, const size_t length, const unsigned format) {
|
||||
if (length < 8) {
|
||||
buf[0] = '\0';
|
||||
|
||||
@ -21,6 +21,8 @@ struct device_runtime {
|
||||
unsigned format;
|
||||
unsigned n_buffers;
|
||||
struct buffer *buffers;
|
||||
unsigned picture_size;
|
||||
unsigned char **pictures;
|
||||
bool capturing;
|
||||
};
|
||||
|
||||
|
||||
263
src/jpeg.c
Normal file
263
src/jpeg.c
Normal file
@ -0,0 +1,263 @@
|
||||
/*******************************************************************************
|
||||
# Based on source code of mjpg-streamer. #
|
||||
# #
|
||||
# Orginally Copyright (C) 2005 2006 Laurent Pinchart && Michel Xhaard #
|
||||
# Modifications Copyright (C) 2006 Gabriel A. Devenyi #
|
||||
# Modifications Copyright (C) 2007 Tom Stöveken #
|
||||
# Modifications Copyright (C) 2018 Maxim Devaev #
|
||||
# #
|
||||
# This program is free software; you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation; either version 2 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program; if not, write to the Free Software #
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
# #
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <jpeglib.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "device.h"
|
||||
#include "jpeg.h"
|
||||
|
||||
|
||||
#define JPEG_OUTPUT_BUFFER_SIZE 4096
|
||||
|
||||
|
||||
struct mjpg_destination_mgr {
|
||||
struct jpeg_destination_mgr mgr; // Default manager
|
||||
JOCTET *buffer; // Start of buffer
|
||||
unsigned char *outbuffer_cursor;
|
||||
int *written;
|
||||
};
|
||||
|
||||
|
||||
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, int *written);
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height);
|
||||
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height);
|
||||
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height);
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg);
|
||||
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
|
||||
static void _jpeg_term_destination(j_compress_ptr jpeg);
|
||||
|
||||
|
||||
int jpeg_compress_buffer(struct device *dev, int index) {
|
||||
// This function based on compress_image_to_jpeg() from mjpg-streamer
|
||||
|
||||
struct jpeg_compress_struct jpeg;
|
||||
struct jpeg_error_mgr jpeg_error;
|
||||
unsigned char *line_buffer;
|
||||
int written = -1;
|
||||
|
||||
if ((line_buffer = calloc(dev->run->width * 3, sizeof(unsigned char))) == NULL) {
|
||||
LOG_PERROR("Can't allocate memory for JPEG scanline");
|
||||
return written;
|
||||
}
|
||||
|
||||
jpeg.err = jpeg_std_error(&jpeg_error);
|
||||
jpeg_create_compress(&jpeg);
|
||||
|
||||
_jpeg_set_dest_picture(&jpeg, dev->run->pictures[index], &written);
|
||||
|
||||
jpeg.image_width = dev->run->width;
|
||||
jpeg.image_height = dev->run->height;
|
||||
jpeg.input_components = 3;
|
||||
jpeg.in_color_space = JCS_RGB;
|
||||
|
||||
jpeg_set_defaults(&jpeg);
|
||||
jpeg_set_quality(&jpeg, dev->jpeg_quality, TRUE);
|
||||
|
||||
jpeg_start_compress(&jpeg, TRUE);
|
||||
|
||||
# define WRITE_SCANLINES(_func) \
|
||||
_func(&jpeg, line_buffer, dev->run->buffers[index].start, dev->run->width, dev->run->height)
|
||||
|
||||
switch (dev->run->format) {
|
||||
case V4L2_PIX_FMT_YUYV: WRITE_SCANLINES(_jpeg_write_scanlines_yuyv); break;
|
||||
case V4L2_PIX_FMT_UYVY: WRITE_SCANLINES(_jpeg_write_scanlines_uyvy); break;
|
||||
case V4L2_PIX_FMT_RGB565: WRITE_SCANLINES(_jpeg_write_scanlines_rgb565); break;
|
||||
default:
|
||||
LOG_ERROR("Unsupported input format for JPEG compressor");
|
||||
written = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: process jpeg errors:
|
||||
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
|
||||
jpeg_finish_compress(&jpeg);
|
||||
jpeg_destroy_compress(&jpeg);
|
||||
free(line_buffer);
|
||||
return written;
|
||||
}
|
||||
|
||||
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, int *written) {
|
||||
struct mjpg_destination_mgr *dest;
|
||||
|
||||
if (jpeg->dest == NULL) {
|
||||
jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct mjpg_destination_mgr)
|
||||
);
|
||||
}
|
||||
|
||||
dest = (struct mjpg_destination_mgr *) jpeg->dest;
|
||||
dest->mgr.init_destination = _jpeg_init_destination;
|
||||
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
|
||||
dest->mgr.term_destination = _jpeg_term_destination;
|
||||
dest->outbuffer_cursor = picture;
|
||||
dest->written = written;
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height) {
|
||||
|
||||
JSAMPROW scanlines[1];
|
||||
unsigned z = 0;
|
||||
|
||||
while (jpeg->next_scanline < height) {
|
||||
unsigned char *ptr = line_buffer;
|
||||
|
||||
for (unsigned x = 0; x < width; x++) {
|
||||
int y = (!z ? data[0] << 8 : data[2] << 8);
|
||||
int u = data[1] - 128;
|
||||
int v = data[3] - 128;
|
||||
|
||||
int r = (y + (359 * v)) >> 8;
|
||||
int g = (y - (88 * u) - (183 * v)) >> 8;
|
||||
int b = (y + (454 * u)) >> 8;
|
||||
|
||||
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
|
||||
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
|
||||
*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
|
||||
|
||||
if (z++) {
|
||||
z = 0;
|
||||
data += 4;
|
||||
}
|
||||
}
|
||||
|
||||
scanlines[0] = line_buffer;
|
||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height) {
|
||||
|
||||
JSAMPROW scanlines[1];
|
||||
unsigned z = 0;
|
||||
|
||||
while(jpeg->next_scanline < height) {
|
||||
unsigned char *ptr = line_buffer;
|
||||
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
int y = (!z ? data[1] << 8 : data[3] << 8);
|
||||
int u = data[0] - 128;
|
||||
int v = data[2] - 128;
|
||||
|
||||
int r = (y + (359 * v)) >> 8;
|
||||
int g = (y - (88 * u) - (183 * v)) >> 8;
|
||||
int b = (y + (454 * u)) >> 8;
|
||||
|
||||
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
|
||||
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
|
||||
*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
|
||||
|
||||
if (z++) {
|
||||
z = 0;
|
||||
data += 4;
|
||||
}
|
||||
}
|
||||
|
||||
scanlines[0] = line_buffer;
|
||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg,
|
||||
unsigned char *line_buffer, const unsigned char *data,
|
||||
const unsigned width, const unsigned height) {
|
||||
|
||||
JSAMPROW scanlines[1];
|
||||
|
||||
while(jpeg->next_scanline < height) {
|
||||
unsigned char *ptr = line_buffer;
|
||||
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
unsigned int two_byte = (data[1] << 8) + data[0];
|
||||
|
||||
*(ptr++) = data[1] & 248;
|
||||
*(ptr++) = (unsigned char)((two_byte & 2016) >> 3);
|
||||
*(ptr++) = (data[0] & 31) * 8;
|
||||
|
||||
data += 2;
|
||||
}
|
||||
|
||||
scanlines[0] = line_buffer;
|
||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||
struct mjpg_destination_mgr *dest = (struct mjpg_destination_mgr *) jpeg->dest;
|
||||
|
||||
// Allocate the output buffer --- it will be released when done with image
|
||||
dest->buffer = (JOCTET *)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET)
|
||||
);
|
||||
|
||||
dest->mgr.next_output_byte = dest->buffer;
|
||||
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
|
||||
// Called whenever local jpeg buffer fills up
|
||||
|
||||
struct mjpg_destination_mgr *dest = (struct mjpg_destination_mgr *) jpeg->dest;
|
||||
|
||||
memcpy(dest->outbuffer_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
|
||||
dest->outbuffer_cursor += JPEG_OUTPUT_BUFFER_SIZE;
|
||||
*dest->written += JPEG_OUTPUT_BUFFER_SIZE;
|
||||
|
||||
dest->mgr.next_output_byte = dest->buffer;
|
||||
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void _jpeg_term_destination(j_compress_ptr jpeg) {
|
||||
// Called by jpeg_finish_compress after all data has been written.
|
||||
// Usually needs to flush buffer
|
||||
|
||||
struct mjpg_destination_mgr *dest = (struct mjpg_destination_mgr *) jpeg->dest;
|
||||
size_t data_count = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
|
||||
|
||||
// Write any data remaining in the buffer
|
||||
memcpy(dest->outbuffer_cursor, dest->buffer, data_count);
|
||||
dest->outbuffer_cursor += data_count;
|
||||
*dest->written += data_count;
|
||||
}
|
||||
8
src/jpeg.h
Normal file
8
src/jpeg.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include "device.h"
|
||||
|
||||
|
||||
int jpeg_compress_buffer(struct device *dev, int index);
|
||||
Loading…
x
Reference in New Issue
Block a user