You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

865 lines
22 KiB

/*****************************************************************************
* rtp-rawvid.c: RTP raw video decoder
*****************************************************************************
* Copyright (C) 2022 Rémi Denis-Courmont
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <assert.h>
#include <errno.h>
#include <stdbit.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_codec.h>
#define PLANES_3(bits) \
uint##bits##_t *restrict p0 = planes[0]; \
uint##bits##_t *restrict p1 = planes[1]; \
uint##bits##_t *restrict p2 = planes[2]; \
(void)0
#define PLANES_4(bits) \
uint##bits##_t *restrict p0 = planes[0]; \
uint##bits##_t *restrict p1 = planes[1]; \
uint##bits##_t *restrict p2 = planes[2]; \
uint##bits##_t *restrict p3 = planes[3]; \
(void)0
/* Read one 8-bit sample from one octet */
#define READ_8(a) \
uint_fast8_t a; \
\
do { \
a = *(in++); \
len--; \
} while (0)
/* Read four 10-bit samples from five octets. */
#define READ_10(a, b, c, d) \
uint_fast16_t a, b, c, d; \
\
do { \
uint_fast8_t b0_ = in[0]; \
uint_fast8_t b1_ = in[1]; \
uint_fast8_t b2_ = in[2]; \
uint_fast8_t b3_ = in[3]; \
uint_fast8_t b4_ = in[4]; \
\
a = (b0_ << 2) | (b1_ >> 6); \
b = ((b1_ << 4) | (b2_ >> 4)) & 0x3ff; \
c = ((b2_ << 6) | (b3_ >> 2)) & 0x3ff; \
d = ((b3_ << 8) | (b4_ >> 0)) & 0x3ff; \
in += 5; \
len -= 5; \
} while (0)
/* Read two 12-bit samples from three octets. */
#define READ_12(a, b) \
uint_fast16_t a, b; \
\
do { \
uint_fast8_t b0_ = in[0]; \
uint_fast8_t b1_ = in[1]; \
uint_fast8_t b2_ = in[2]; \
\
a = (b0_ << 4) | (b1_ >> 4); \
b = ((b1_ << 4) | (b2_ >> 0)) & 0xfff; \
in += 3; \
len -= 3; \
} while (0)
/* Read one 16-bit sample from two octets */
#define READ_16(a) \
uint_fast16_t a; \
\
do { \
a = GetWBE(in); \
in += 2; \
len -= 2; \
} while (0)
#define WRITE_RGB(n) \
do { \
*(p0++) = g##n; \
*(p1++) = b##n; \
*(p2++) = r##n; \
} while (0)
#define WRITE_RGBA(n) \
do { \
*(p0++) = g##n; \
*(p1++) = b##n; \
*(p2++) = r##n; \
*(p3++) = a##n; \
} while (0)
#define WRITE_YUV444(n) \
do { \
*(p0++) = y##n; \
*(p1++) = u##n; \
*(p2++) = v##n; \
} while (0)
#define WRITE_YUV422(n) \
do { \
*(p0++) = y0##n; \
*(p0++) = y1##n; \
*(p1++) = u##n; \
*(p2++) = v##n; \
} while (0)
#define WRITE_YUV420(n) \
do { \
*(p0++) = y00##n; \
*(p0++) = y01##n; \
*(p3++) = y10##n; \
*(p3++) = y11##n; \
*(p1++) = u##n; \
*(p2++) = v##n; \
} while (0)
#define WRITE_YUV411(n) \
do { \
*(p0++) = y0##n; \
*(p0++) = y1##n; \
*(p0++) = y2##n; \
*(p0++) = y3##n; \
*(p1++) = u##n; \
*(p2++) = v##n; \
} while (0)
static void decode_rgb_8(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(8);
while (len > 0) {
READ_8(r0);
READ_8(g0);
READ_8(b0);
WRITE_RGB(0);
}
}
static void decode_rgb_10(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(16);
while (len > 0) {
READ_10(r0, g0, b0, r1);
READ_10(g1, b1, r2, g2);
READ_10(b2, r3, b3, g3);
WRITE_RGB(0);
WRITE_RGB(1);
WRITE_RGB(2);
WRITE_RGB(3);
}
}
static void decode_rgb_12(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(16);
while (len > 0) {
READ_12(r0, g0);
READ_12(b0, r1);
READ_12(g1, b1);
WRITE_RGB(0);
WRITE_RGB(1);
}
}
static void decode_rgb_16(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(16);
while (len > 0) {
READ_16(r0);
READ_16(g0);
READ_16(b0);
WRITE_RGB(0);
}
}
static void decode_rgba_8(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_4(8);
while (len > 0) {
READ_8(r0);
READ_8(g0);
READ_8(b0);
READ_8(a0);
WRITE_RGBA(0);
}
}
static void decode_rgba_10(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_4(16);
while (len > 0) {
READ_10(r0, g0, b0, a0);
WRITE_RGBA(0);
}
}
static void decode_rgba_12(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_4(16);
while (len > 0) {
READ_12(r0, g0);
READ_12(b0, a0);
WRITE_RGBA(0);
}
}
static void decode_rgba_16(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_4(16);
while (len > 0) {
READ_16(r0);
READ_16(g0);
READ_16(b0);
READ_16(a0);
WRITE_RGBA(0);
}
}
static void decode_bgr_8(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(8);
while (len > 0) {
READ_8(b0);
READ_8(g0);
READ_8(r0);
WRITE_RGB(0);
}
}
static void decode_bgr_10(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(16);
while (len > 0) {
READ_10(b0, g0, r0, b1);
READ_10(g1, r1, b2, g2);
READ_10(r2, b3, g3, r3);
WRITE_RGB(0);
WRITE_RGB(1);
WRITE_RGB(2);
WRITE_RGB(3);
}
}
static void decode_bgr_12(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(16);
while (len > 0) {
READ_12(b0, g0);
READ_12(r0, b1);
READ_12(g1, r1);
WRITE_RGB(0);
WRITE_RGB(1);
}
}
static void decode_bgr_16(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(16);
while (len > 0) {
READ_16(b0);
READ_16(g0);
READ_16(r0);
WRITE_RGB(0);
}
}
static void decode_bgra_8(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_4(8);
while (len > 0) {
READ_8(b0);
READ_8(g0);
READ_8(r0);
READ_8(a0);
WRITE_RGBA(0);
}
}
static void decode_bgra_10(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_4(16);
while (len > 0) {
READ_10(b0, g0, r0, a0);
WRITE_RGBA(0);
}
}
static void decode_bgra_12(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_4(16);
while (len > 0) {
READ_12(b0, g0);
READ_12(r0, a0);
WRITE_RGBA(0);
}
}
static void decode_bgra_16(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_4(16);
while (len > 0) {
READ_16(b0);
READ_16(g0);
READ_16(r0);
READ_16(a0);
WRITE_RGBA(0);
}
}
static void decode_yuv444_8(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(8);
while (len > 0) {
READ_8(u0);
READ_8(y0);
READ_8(v0);
WRITE_YUV444(0);
}
}
static void decode_yuv444_10(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(16);
while (len > 0) {
READ_10(u0, y0, v0, u1);
READ_10(y1, v1, u2, y2);
READ_10(v2, u3, y3, v3);
WRITE_YUV444(0);
WRITE_YUV444(1);
WRITE_YUV444(2);
WRITE_YUV444(3);
}
}
static void decode_yuv444_12(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(16);
while (len > 0) {
READ_12(u0, y0);
READ_12(v0, u1);
READ_12(y1, v1);
WRITE_YUV444(0);
WRITE_YUV444(1);
}
}
static void decode_yuv444_16(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(16);
while (len > 0) {
READ_16(u0);
READ_16(y0);
READ_16(v0);
WRITE_YUV444(0);
}
}
static void decode_yuv422_8(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(8);
while (len > 0) {
READ_8(u0);
READ_8(y00);
READ_8(v0);
READ_8(y10);
WRITE_YUV422(0);
}
}
static void decode_yuv422_10(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(16);
while (len > 0) {
READ_10(u0, y00, v0, y10);
WRITE_YUV422(0);
}
}
static void decode_yuv422_12(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(16);
while (len > 0) {
READ_12(u0, y00);
READ_12(v0, y10);
WRITE_YUV422(0);
}
}
static void decode_yuv422_16(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(16);
while (len > 0) {
READ_16(u0);
READ_16(y00);
READ_16(v0);
READ_16(y10);
WRITE_YUV422(0);
}
}
static void decode_yuv420_8(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_4(8);
while (len > 0) {
READ_8(y000);
READ_8(y010);
READ_8(y100);
READ_8(y110);
READ_8(u0);
READ_8(v0);
WRITE_YUV420(0);
}
}
static void decode_yuv420_10(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_4(16);
while (len > 0) {
READ_10(y000, y010, y100, y110);
READ_10(u0, v0, y001, y011);
READ_10(y101, y111, u1, v1);
WRITE_YUV420(0);
WRITE_YUV420(1);
}
}
static void decode_yuv420_12(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_4(16);
while (len > 0) {
READ_12(y000, y010);
READ_12(y100, y110);
READ_12(u0, v0);
WRITE_YUV420(0);
}
}
static void decode_yuv420_16(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_4(16);
while (len > 0) {
READ_16(y000);
READ_16(y010);
READ_16(y100);
READ_16(y110);
READ_16(u0);
READ_16(v0);
WRITE_YUV420(0);
}
}
static void decode_yuv411_8(void *restrict *restrict planes,
const unsigned char *restrict in, size_t len)
{
PLANES_3(8);
while (len > 0) {
READ_8(u0);
READ_8(y00);
READ_8(y10);
READ_8(v0);
READ_8(y20);
READ_8(y30);
WRITE_YUV411(0);
}
}
typedef void (*vlc_rtp_video_raw_cb)(void *restrict * restrict,
const unsigned char * restrict, size_t);
struct vlc_rtp_video_raw_dec {
unsigned int pgroup;
bool half_height_uv;
vlc_rtp_video_raw_cb decode_line;
picture_t *pic;
};
static int Decode(decoder_t *dec, vlc_frame_t *block)
{
struct vlc_rtp_video_raw_dec *sys = dec->p_sys;
picture_t *pic = sys->pic;
bool continuation;
if (block == NULL) {
/* Draining */
if (pic != NULL) /* Send incomplete picture */
decoder_QueueVideo(dec, pic);
sys->pic = NULL;
return VLCDEC_SUCCESS;
}
if ((block->i_flags & VLC_FRAME_FLAG_DISCONTINUITY)
&& pic != NULL && pic->date != block->i_pts) {
/* Ideally, the EOS is set on the last block for a picture.
* This manual check is necessary to deal with packet loss. */
decoder_QueueVideo(dec, pic);
pic = sys->pic = NULL;
}
if (pic == NULL) {
pic = decoder_NewPicture(dec);
if (pic == NULL) {
block_Release(block);
return VLCDEC_SUCCESS;
}
pic->date = block->i_pts;
pic->b_progressive = true; /* TODO: interlacing */
sys->pic = pic;
}
const unsigned char *in = block->p_buffer;
size_t inlen = block->i_buffer;
const unsigned int width = dec->fmt_out.video.i_width;
const unsigned int height = dec->fmt_out.video.i_height;
do {
if (unlikely(inlen < 6)) {
corrupt: msg_Err(dec, "corrupt packet, %zu bytes remaining", inlen);
break;
}
uint_fast16_t length = GetWBE(in);
uint_fast16_t lineno = GetWBE(in + 2);
uint_fast16_t offset = GetWBE(in + 4);
lineno &= 0x7fff; /* TODO: interlacing */
continuation = (offset & 0x8000) != 0;
offset &= 0x7fff;
in += 6;
inlen -= 6;
div_t d = div(length, sys->pgroup);
if (inlen < length /* input buffer underflow */
|| d.rem != 0 /* length must be a multiple of pgroup size */
|| offset + d.quot >= width /* output scanline overflow */
|| lineno >= height /* output picture overflow */)
goto corrupt;
void *restrict planes[4];
if (sys->half_height_uv) {
/* For I420, treat the odd Y lines as the 4th plane */
if (unlikely(lineno & 1))
goto corrupt; /* line number must always be even */
assert(pic->i_planes <= 3);
planes[0] = pic->p[0].p_pixels + lineno * pic->p[0].i_pitch
+ offset * pic->p[0].i_pixel_pitch;
planes[3] = ((unsigned char *)planes[0]) + pic->p[0].i_pitch;
lineno /= 2;
for (int i = 1; i < pic->i_planes; i++) {
plane_t *p = &pic->p[i];
planes[i] = p->p_pixels + lineno * p->i_pitch
+ offset * p->i_pixel_pitch;
}
} else {
for (int i = 0; i < pic->i_planes; i++) {
plane_t *p = &pic->p[i];
planes[i] = p->p_pixels + lineno * p->i_pitch
+ offset * p->i_pixel_pitch;
}
}
sys->decode_line(planes, in, length);
} while (continuation);
if (block->i_flags & VLC_FRAME_FLAG_END_OF_SEQUENCE) {
decoder_QueueVideo(dec, pic);
sys->pic = NULL;
}
block_Release(block);
return VLCDEC_SUCCESS;
}
static void Close(vlc_object_t *obj)
{
decoder_t *dec = (decoder_t *)obj;
struct vlc_rtp_video_raw_dec *sys = dec->p_sys;
if (sys->pic != NULL)
picture_Release(sys->pic);
}
struct vlc_rtp_video_raw_format {
vlc_fourcc_t fourcc;
vlc_rtp_video_raw_cb line_cb;
};
/**
* RTP video/raw sampling
*
* This defines the list of all supported (per component) bit depths.
*/
struct vlc_rtp_video_raw_sampling {
struct vlc_rtp_video_raw_format depth8;
struct vlc_rtp_video_raw_format depth10;
struct vlc_rtp_video_raw_format depth12;
struct vlc_rtp_video_raw_format depth16;
};
/**
* RTP video/raw samplings
*
* This type defines the list of all support RTP video/raw samplings.
* \note This is purposedly a structure rather than an array so that CPU
* optimisations can readily cherry-pick which samplings to optimise.
*/
struct vlc_rtp_video_raw_samplings {
struct vlc_rtp_video_raw_sampling rgb;
struct vlc_rtp_video_raw_sampling rgba;
struct vlc_rtp_video_raw_sampling bgr;
struct vlc_rtp_video_raw_sampling bgra;
struct vlc_rtp_video_raw_sampling yuv444;
struct vlc_rtp_video_raw_sampling yuv422;
struct vlc_rtp_video_raw_sampling yuv420;
struct vlc_rtp_video_raw_sampling yuv411;
};
#ifdef WORDS_BIGENDIAN
#define NE(x) x##B
#else
#define NE(x) x##L
#endif
static const struct vlc_rtp_video_raw_samplings samplings = {
.rgb = {
{ VLC_CODEC_GBR_PLANAR, decode_rgb_8, },
{ NE(VLC_CODEC_GBR_PLANAR_10), decode_rgb_10, },
{ NE(VLC_CODEC_GBR_PLANAR_12), decode_rgb_12, },
{ NE(VLC_CODEC_GBR_PLANAR_16), decode_rgb_16, },
},
.rgba = {
{ VLC_CODEC_GBR_PLANAR, decode_rgba_8, },
{ NE(VLC_CODEC_GBR_PLANAR_10), decode_rgba_10, },
{ NE(VLC_CODEC_GBR_PLANAR_12), decode_rgba_12, },
{ NE(VLC_CODEC_GBR_PLANAR_16), decode_rgba_16, },
},
.bgr = {
{ VLC_CODEC_GBR_PLANAR, decode_bgr_8, },
{ NE(VLC_CODEC_GBR_PLANAR_10), decode_bgr_10, },
{ NE(VLC_CODEC_GBR_PLANAR_12), decode_bgr_12, },
{ NE(VLC_CODEC_GBR_PLANAR_16), decode_bgr_16, },
},
.bgra = {
{ VLC_CODEC_GBR_PLANAR, decode_bgra_8, },
{ NE(VLC_CODEC_GBR_PLANAR_10), decode_bgra_10, },
{ NE(VLC_CODEC_GBR_PLANAR_12), decode_bgra_12, },
{ NE(VLC_CODEC_GBR_PLANAR_16), decode_bgra_16, },
},
.yuv444 = {
{ VLC_CODEC_I444, decode_yuv444_8, },
{ NE(VLC_CODEC_I444_10), decode_yuv444_10, },
{ NE(VLC_CODEC_I444_12), decode_yuv444_12, },
{ NE(VLC_CODEC_I444_16), decode_yuv444_16, },
},
.yuv422 = {
{ VLC_CODEC_I422, decode_yuv422_8, },
{ NE(VLC_CODEC_I422_10), decode_yuv422_10, },
{ NE(VLC_CODEC_I422_12), decode_yuv422_12, },
{ NE(VLC_CODEC_I422_16), decode_yuv422_16, },
},
.yuv420 = {
{ VLC_CODEC_I420, decode_yuv420_8, },
{ NE(VLC_CODEC_I420_10), decode_yuv420_10, },
{ NE(VLC_CODEC_I420_12), decode_yuv420_12, },
{ NE(VLC_CODEC_I420_16), decode_yuv420_16, },
},
.yuv411 = {
{ VLC_CODEC_I411, decode_yuv411_8, },
/* High-definition 4:1:1 not supported */
{ 0, NULL }, { 0, NULL }, { 0, NULL }
},
};
static int Open(vlc_object_t *obj)
{
decoder_t *dec = (decoder_t *)obj;
const char *sname = dec->fmt_in->p_extra;
if (dec->fmt_in->i_codec != VLC_CODEC_RTP_VIDEO_RAW)
return VLC_ENOTSUP;
if (dec->fmt_in->i_extra <= 0 || sname[dec->fmt_in->i_extra - 1] != '\0')
return VLC_EINVAL;
/* Sampling is supplied as extra data, bit depth as level */
unsigned int depth = dec->fmt_in->i_level;
const struct vlc_rtp_video_raw_sampling *sampling;
unsigned int spmp; /* samples per macropixel */
bool half_height_uv = false;
if (strcmp(sname, "RGB") == 0) {
sampling = &samplings.rgb;
spmp = 3;
} else if (strcmp(sname, "RGBA") == 0) {
sampling = &samplings.rgba;
spmp = 4;
} else if (strcmp(sname, "BGR") == 0) {
sampling = &samplings.bgr;
spmp = 3;
} else if (strcmp(sname, "BGRA") == 0) {
sampling = &samplings.bgra;
spmp = 4;
} else if (strcmp(sname, "YCbCr-4:4:4") == 0) {
sampling = &samplings.yuv444;
spmp = 3;
} else if (strcmp(sname, "YCbCr-4:2:2") == 0) {
sampling = &samplings.yuv422;
spmp = 4;
} else if (strcmp(sname, "YCbCr-4:2:0") == 0) {
sampling = &samplings.yuv420;
spmp = 6;
half_height_uv = true;
} else if (strcmp(sname, "YCbCr-4:1:1") == 0) {
sampling = &samplings.yuv411;
spmp = 6;
} else {
msg_Err(obj, "unknown RTP video sampling %s", sname);
return VLC_ENOTSUP;
}
const struct vlc_rtp_video_raw_format *format;
switch (depth) {
case 8:
format = &sampling->depth8;
break;
case 10:
format = &sampling->depth10;
break;
case 12:
format = &sampling->depth12;
break;
case 16:
format = &sampling->depth16;
break;
default:
msg_Err(obj, "unsupported RTP video bit depth %u", depth);
return VLC_ENOTSUP;
}
if (format->fourcc == 0) {
msg_Err(obj, "unimplemented RTP video format %u-bit %s",
depth, sname);
return VLC_ENOTSUP;
}
struct vlc_rtp_video_raw_dec *sys = vlc_obj_malloc(obj, sizeof (*sys));
if (unlikely(sys == NULL))
return VLC_ENOMEM;
es_format_Copy(&dec->fmt_out, dec->fmt_in);
dec->fmt_out.i_codec = format->fourcc;
dec->fmt_out.video.i_chroma = format->fourcc;
int ret = decoder_UpdateVideoFormat(dec);
if (ret != VLC_SUCCESS)
return ret;
unsigned int bpmp = depth * spmp;
/* pixel group size equals LCM(depth * spmp, 8) bits */
sys->pgroup = bpmp >> ((bpmp % 8) ? stdc_trailing_zeros(bpmp) : 3);
sys->half_height_uv = half_height_uv;
sys->decode_line = format->line_cb;
sys->pic = NULL;
dec->p_sys = sys;
dec->pf_decode = Decode;
return VLC_SUCCESS;
}
vlc_module_begin()
set_description(N_("RTP raw video decoder"))
set_capability("video decoder", 50)
set_subcategory(SUBCAT_INPUT_VCODEC)
set_callbacks(Open, Close)
vlc_module_end()