diff --git a/plugins/linux-v4l2/CMakeLists.txt b/plugins/linux-v4l2/CMakeLists.txt index 51047ec59..403a3318b 100644 --- a/plugins/linux-v4l2/CMakeLists.txt +++ b/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") diff --git a/plugins/linux-v4l2/v4l2-helpers.h b/plugins/linux-v4l2/v4l2-helpers.h index b14d0199f..506dfa29d 100644 --- a/plugins/linux-v4l2/v4l2-helpers.h +++ b/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; } diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c index 91d6a79b1..f57653ffe 100644 --- a/plugins/linux-v4l2/v4l2-input.c +++ b/plugins/linux-v4l2/v4l2-input.c @@ -37,6 +37,7 @@ along with this program. If not, see . #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; diff --git a/plugins/linux-v4l2/v4l2-mjpeg.c b/plugins/linux-v4l2/v4l2-mjpeg.c new file mode 100644 index 000000000..45b2b9193 --- /dev/null +++ b/plugins/linux-v4l2/v4l2-mjpeg.c @@ -0,0 +1,98 @@ +/* +Copyright (C) 2020 by Morten Bøgeskov + +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 . +*/ + +#include + +#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; +} diff --git a/plugins/linux-v4l2/v4l2-mjpeg.h b/plugins/linux-v4l2/v4l2-mjpeg.h new file mode 100644 index 000000000..04ff75c39 --- /dev/null +++ b/plugins/linux-v4l2/v4l2-mjpeg.h @@ -0,0 +1,68 @@ +/* +Copyright (C) 2020 by Morten Bøgeskov + +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 . +*/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/** + * 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