Skip to content

Commit

Permalink
(decode) h264 and mjpeg in uvc capture
Browse files Browse the repository at this point in the history
This adds support for the missing two major formats for camera capture.
The weights for automatic format selection now favours h264 over mjpeg
over various raw ones. The next step here is extending shmif for video
passthrough. Even though this normally makes little sense for
afsrv_decode as its job is to be the [compressed formats] sink, the
situation is a bit more nuanced when it is combined with arcan-net.

For that scenario it is a substantial gain being able to maintain
compression from source, network transport to final sink and thus a good
test case for these kinds of setups.
  • Loading branch information
letoram committed May 14, 2023
1 parent 8080da8 commit 8af8646
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -37,6 +37,7 @@

## Decode
* defer REGISTER until proto argument has been parsed, let text register as TUI
* libuvc path now uses FFMPEG for h264 and mjpeg

## Package / Build
* console: added binding for shutdown
Expand Down
102 changes: 99 additions & 3 deletions src/frameserver/decode/default/uvc_support.c
Expand Up @@ -27,6 +27,8 @@
#include <arcan_shmif.h>
#include <libuvc/libuvc.h>
#include <libswscale/swscale.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <math.h>

static int video_buffer_count = 1;
Expand Down Expand Up @@ -112,6 +114,94 @@ static void run_swscale(
arcan_shmif_signal(dst, SHMIF_SIGVID);
}

static void frame_ffmpeg(
uvc_frame_t* uvc, struct arcan_shmif_cont* cont, int fmt, const char* fmtstr)
{
static const AVCodec* codec;
static AVCodecContext* decode;
static AVCodecParserContext* parser;
static AVFrame* frame;
static AVPacket* packet;

/* missing:
* - test shmif for the ability to tunnel raw h264 (important for a12)
*/

/* this is the same code used in a12/a12_decode.c */
if (!codec){
codec = avcodec_find_decoder(fmt);
if (!codec){
LOG("status=error:fatal:kind=missing:message=no %s support", fmtstr);
arcan_shmif_last_words(cont, "ffmpeg no matching codec");
arcan_shmif_drop(cont);
exit(EXIT_FAILURE);
}
decode = avcodec_alloc_context3(codec);
parser = av_parser_init(codec->id);
frame = av_frame_alloc();
packet = av_packet_alloc();
if (avcodec_open2(decode, codec, NULL) < 0){
LOG("status=error:fatal:kind=codec_fail:message=codec failed open");
arcan_shmif_drop(cont);
exit(EXIT_FAILURE);
}
}

int ofs = 0;
while (uvc->data_bytes - ofs > 0){
int ret = av_parser_parse2(parser, decode,
&packet->data, &packet->size,
&((uint8_t*)uvc->data)[ofs],
uvc->data_bytes - ofs,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0
);

if (ret < 0){
LOG("status=error:fatal:kind=ebad:message=%s parser failed", fmtstr);
arcan_shmif_last_words(cont, "ffmpeg parser error");
arcan_shmif_drop(cont);
exit(EXIT_FAILURE);
}
ofs += ret;

if (packet->data){
ret = avcodec_send_packet(decode, packet);
if (ret < 0)
goto decode_fail;

while (ret >= 0){
ret = avcodec_receive_frame(decode, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR(EOF)){
break;
}
else if (ret != 0)
goto decode_fail;

struct SwsContext* scaler =
sws_getContext(
frame->width, frame->height, AV_PIX_FMT_YUV420P,
cont->w, cont->h, AV_PIX_FMT_BGRA, SWS_BILINEAR, NULL, NULL, NULL);

uint8_t* const dst[] = {cont->vidb};
int dst_stride[] = {cont->stride};
sws_scale(scaler, (const uint8_t* const*) frame->data,
frame->linesize, 0, frame->height, dst, dst_stride);

sws_freeContext(scaler);
arcan_shmif_signal(cont, SHMIF_SIGVID);
}
}
}

return;

decode_fail:
LOG("status=error:fatal:kind=ebad:message=%s decoder failed", fmtstr);
arcan_shmif_last_words(cont, "ffmpeg decoder error");
arcan_shmif_drop(cont);
exit(EXIT_FAILURE);
}

static void frame_rgb(uvc_frame_t* frame, struct arcan_shmif_cont* dst)
{
uint8_t* buf = frame->data;
Expand Down Expand Up @@ -155,6 +245,12 @@ static void callback(uvc_frame_t* frame, void* tag)
case UVC_FRAME_FORMAT_RGB:
frame_rgb(frame, cont);
break;
case UVC_FRAME_FORMAT_H264:
frame_ffmpeg(frame, cont, AV_CODEC_ID_H264, "h264");
break;
case UVC_FRAME_FORMAT_MJPEG:
frame_ffmpeg(frame, cont, AV_CODEC_ID_MJPEG, "mjpeg");
break;
/* h264 and mjpeg should map into ffmpeg as well */
default:
LOG("unhandled frame format: %d\n", (int)frame->frame_format);
Expand Down Expand Up @@ -198,12 +294,12 @@ static int fmt_score(const uint8_t fourcc[static 4], int* out)
},
{
.enumv = UVC_FRAME_FORMAT_MJPEG,
.score = -1,
.score = 4,
.fourcc = {'M', 'J', 'P', 'G'}
},
{
.enumv = UVC_FRAME_FORMAT_MJPEG,
.score = -1,
.enumv = UVC_FRAME_FORMAT_H264,
.score = 5,
.fourcc = {'H', '2', '6', '4'}
},
};
Expand Down

0 comments on commit 8af8646

Please sign in to comment.