Browse Source

linux-v4l2: Support for Motion-JPEG codec

Morten Bøgeskov 5 years ago
parent
commit
1c2aea4f89

+ 6 - 0
plugins/linux-v4l2/CMakeLists.txt

@@ -29,9 +29,13 @@ else()
 	endif()
 endif()
 
+find_package(FFmpeg REQUIRED
+        COMPONENTS avcodec avutil avformat)
+
 include_directories(
 	SYSTEM "${CMAKE_SOURCE_DIR}/libobs"
 	${LIBV4L2_INCLUDE_DIRS}
+	${FFMPEG_INCLUDE_DIRS}
 )
 
 set(linux-v4l2_SOURCES
@@ -40,6 +44,7 @@ set(linux-v4l2_SOURCES
 	v4l2-input.c
 	v4l2-helpers.c
 	v4l2-output.c
+	v4l2-mjpeg.c
 	${linux-v4l2-udev_SOURCES}
 )
 
@@ -50,6 +55,7 @@ target_link_libraries(linux-v4l2
 	libobs
 	${LIBV4L2_LIBRARIES}
 	${UDEV_LIBRARIES}
+	${FFMPEG_LIBRARIES}
 )
 set_target_properties(linux-v4l2 PROPERTIES FOLDER "plugins")
 

+ 2 - 0
plugins/linux-v4l2/v4l2-helpers.h

@@ -79,6 +79,8 @@ static inline enum video_format v4l2_to_obs_video_format(uint_fast32_t format)
 #endif
 	case V4L2_PIX_FMT_BGR24:
 		return VIDEO_FORMAT_BGR3;
+	case V4L2_PIX_FMT_MJPEG:
+		return VIDEO_FORMAT_I422;
 	default:
 		return VIDEO_FORMAT_NONE;
 	}

+ 29 - 2
plugins/linux-v4l2/v4l2-input.c

@@ -37,6 +37,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #include "v4l2-controls.h"
 #include "v4l2-helpers.h"
+#include "v4l2-mjpeg.h"
 
 #if HAVE_UDEV
 #include "v4l2-udev.h"
@@ -80,6 +81,7 @@ struct v4l2_data {
 	obs_source_t *source;
 	pthread_t thread;
 	os_event_t *event;
+	struct v4l2_mjpeg_decoder mjpeg_decoder;
 
 	bool framerate_unchanged;
 	bool resolution_unchanged;
@@ -100,6 +102,8 @@ static void v4l2_update(void *vptr, obs_data_t *settings);
 
 /**
  * Prepare the output frame structure for obs and compute plane offsets
+ * For encoded formats (mjpeg) this clears the frame and plane offsets,
+ * which will be filled in after decoding.
  *
  * Basically all data apart from memory pointers and the timestamp is known
  * before the capture starts. This function prepares the obs_source_frame
@@ -108,6 +112,7 @@ static void v4l2_update(void *vptr, obs_data_t *settings);
  * v4l2 uses a continuous memory segment for all planes so we simply compute
  * offsets to add to the start address in order to give obs the correct data
  * pointers for the individual planes.
+ *
  */
 static void v4l2_prep_obs_frame(struct v4l2_data *data,
 				struct obs_source_frame *frame,
@@ -143,6 +148,13 @@ static void v4l2_prep_obs_frame(struct v4l2_data *data,
 		plane_offsets[1] = data->linesize * data->height;
 		plane_offsets[2] = data->linesize * data->height * 5 / 4;
 		break;
+	case V4L2_PIX_FMT_MJPEG:
+		frame->linesize[0] = 0;
+		frame->linesize[1] = 0;
+		frame->linesize[2] = 0;
+		plane_offsets[1] = 0;
+		plane_offsets[2] = 0;
+		break;
 	default:
 		frame->linesize[0] = data->linesize;
 		break;
@@ -257,8 +269,17 @@ static void *v4l2_thread(void *vptr)
 		out.timestamp -= first_ts;
 
 		start = (uint8_t *)data->buffers.info[buf.index].start;
-		for (uint_fast32_t i = 0; i < MAX_AV_PLANES; ++i)
-			out.data[i] = start + plane_offsets[i];
+
+		if (data->pixfmt == V4L2_PIX_FMT_MJPEG) {
+			if (v4l2_decode_mjpeg(&out, start, buf.bytesused,
+					      &data->mjpeg_decoder) < 0) {
+				blog(LOG_ERROR, "failed to unpack jpeg");
+				break;
+			}
+		} else {
+			for (uint_fast32_t i = 0; i < MAX_AV_PLANES; ++i)
+				out.data[i] = start + plane_offsets[i];
+		}
 		obs_source_output_video(data->source, &out);
 
 		if (v4l2_ioctl(data->dev, VIDIOC_QBUF, &buf) < 0) {
@@ -890,6 +911,7 @@ static void v4l2_terminate(struct v4l2_data *data)
 		data->thread = 0;
 	}
 
+	v4l2_destroy_mjpeg(&data->mjpeg_decoder);
 	v4l2_destroy_mmap(&data->buffers);
 
 	if (data->dev != -1) {
@@ -1003,6 +1025,11 @@ static void v4l2_init(struct v4l2_data *data)
 		goto fail;
 	}
 
+	if (v4l2_init_mjpeg(&data->mjpeg_decoder) < 0) {
+		blog(LOG_ERROR, "Failed to initialize mjpeg decoder");
+		goto fail;
+	}
+
 	/* start the capture thread */
 	if (os_event_init(&data->event, OS_EVENT_TYPE_MANUAL) != 0)
 		goto fail;

+ 98 - 0
plugins/linux-v4l2/v4l2-mjpeg.c

@@ -0,0 +1,98 @@
+/*
+Copyright (C) 2020 by Morten Bøgeskov <[email protected]>
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <obs-module.h>
+
+#include "v4l2-mjpeg.h"
+
+#define blog(level, msg, ...) \
+	blog(level, "v4l2-input: mjpeg: " msg, ##__VA_ARGS__)
+
+int v4l2_init_mjpeg(struct v4l2_mjpeg_decoder *decoder)
+{
+	decoder->codec = avcodec_find_decoder(AV_CODEC_ID_MJPEG);
+	if (!decoder->codec) {
+		return -1;
+	}
+
+	decoder->context = avcodec_alloc_context3(decoder->codec);
+	if (!decoder->context) {
+		return -1;
+	}
+
+	decoder->packet = av_packet_alloc();
+	if (!decoder->packet) {
+		return -1;
+	}
+
+	decoder->frame = av_frame_alloc();
+	if (!decoder->frame) {
+		return -1;
+	}
+
+	decoder->context->flags2 |= AV_CODEC_FLAG2_FAST;
+	decoder->context->pix_fmt = AV_PIX_FMT_YUVJ422P;
+
+	if (avcodec_open2(decoder->context, decoder->codec, NULL) < 0) {
+		blog(LOG_ERROR, "failed to open codec");
+		return -1;
+	}
+
+	blog(LOG_DEBUG, "initialized avcodec");
+
+	return 0;
+}
+
+void v4l2_destroy_mjpeg(struct v4l2_mjpeg_decoder *decoder)
+{
+	blog(LOG_DEBUG, "destroying avcodec");
+	if (decoder->frame) {
+		av_frame_free(&decoder->frame);
+	}
+
+	if (decoder->packet) {
+		av_packet_free(&decoder->packet);
+	}
+
+	if (decoder->context) {
+		avcodec_free_context(&decoder->context);
+	}
+}
+
+int v4l2_decode_mjpeg(struct obs_source_frame *out, uint8_t *data,
+		      size_t length, struct v4l2_mjpeg_decoder *decoder)
+{
+
+	decoder->packet->data = data;
+	decoder->packet->size = length;
+	if (avcodec_send_packet(decoder->context, decoder->packet) < 0) {
+		blog(LOG_ERROR, "failed to send jpeg to codec");
+		return -1;
+	}
+
+	if (avcodec_receive_frame(decoder->context, decoder->frame) < 0) {
+		blog(LOG_ERROR, "failed to recieve frame from codec");
+		return -1;
+	}
+
+	for (uint_fast32_t i = 0; i < MAX_AV_PLANES; ++i) {
+		out->data[i] = decoder->frame->data[i];
+		out->linesize[i] = decoder->frame->linesize[i];
+	}
+
+	return 0;
+}

+ 68 - 0
plugins/linux-v4l2/v4l2-mjpeg.h

@@ -0,0 +1,68 @@
+/*
+Copyright (C) 2020 by Morten Bøgeskov <[email protected]>
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavutil/pixfmt.h>
+
+/**
+ * Data structure for mjpeg decoding
+ */
+struct v4l2_mjpeg_decoder {
+	AVCodec *codec;
+	AVCodecContext *context;
+	AVPacket *packet;
+	AVFrame *frame;
+};
+
+/**
+ * Initialize the mjpeg decoder.
+ * The decoder must be destroyed on failure.
+ *
+ * @param props the decoder structure
+ * @return non-zero on failure
+ */
+int v4l2_init_mjpeg(struct v4l2_mjpeg_decoder *decoder);
+
+/**
+ * Free any data associated with the decoder.
+ *
+ * @param decoder the decoder structure
+ */
+void v4l2_destroy_mjpeg(struct v4l2_mjpeg_decoder *decoder);
+
+/**
+ * Decode a jpeg into an obs frame
+ *
+ * @param out the obs frame to decode into
+ * @param data the jpeg data
+ * @param length length of the data
+ * @param decoder the decoder as initialized by v4l2_init_mjpeg
+ * @return non-zero on failure
+ */
+int v4l2_decode_mjpeg(struct obs_source_frame *out, uint8_t *data,
+		      size_t length, struct v4l2_mjpeg_decoder *decoder);
+
+#ifdef __cplusplus
+}
+#endif