Create an Open h.264 encoder
parent
1113b6b12a
commit
15c14d7d4b
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Andri Yngvason
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "h264-encoder.h"
|
||||
#include "rfb-proto.h"
|
||||
#include "enc-util.h"
|
||||
#include "vec.h"
|
||||
#include "fb.h"
|
||||
#include "rcbuf.h"
|
||||
#include "encoder.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef void (*open_h264_ready_fn)(void*);
|
||||
|
||||
struct open_h264_header {
|
||||
uint32_t length;
|
||||
uint32_t flags;
|
||||
} RFB_PACKED;
|
||||
|
||||
struct open_h264 {
|
||||
struct encoder parent;
|
||||
|
||||
struct h264_encoder* encoder;
|
||||
|
||||
struct vec pending;
|
||||
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t format;
|
||||
|
||||
bool needs_reset;
|
||||
};
|
||||
|
||||
enum open_h264_flags {
|
||||
OPEN_H264_FLAG_RESET_CONTEXT = 0,
|
||||
OPEN_H264_FLAG_RESET_ALL_CONTEXTS = 1,
|
||||
};
|
||||
|
||||
struct encoder* open_h264_new(void);
|
||||
|
||||
struct encoder_impl encoder_impl_open_h264;
|
||||
|
||||
static inline struct open_h264* open_h264(struct encoder* enc)
|
||||
{
|
||||
return (struct open_h264*)enc;
|
||||
}
|
||||
|
||||
static void open_h264_handle_packet(const void* data, size_t size,
|
||||
void* userdata)
|
||||
{
|
||||
struct open_h264* self = userdata;
|
||||
|
||||
// Let's not deplete the RAM if the client isn't pulling
|
||||
if (self->pending.len > 100000000) {
|
||||
// TODO: Drop buffer and request a keyframe?
|
||||
log_debug("Pending buffer grew too large. Dropping packet...\n");
|
||||
return;
|
||||
}
|
||||
|
||||
vec_append(&self->pending, data, size);
|
||||
|
||||
if (self->parent.on_done)
|
||||
self->parent.on_done(&self->parent, NULL);
|
||||
}
|
||||
|
||||
static int open_h264_init_pending(struct open_h264* self)
|
||||
{
|
||||
if (vec_init(&self->pending, 4096) < 0)
|
||||
return -1;
|
||||
|
||||
vec_append_zero(&self->pending, sizeof(struct rfb_server_fb_rect));
|
||||
vec_append_zero(&self->pending, sizeof(struct open_h264_header));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct encoder* open_h264_new(void)
|
||||
{
|
||||
struct open_h264* self = calloc(1, sizeof(*self));
|
||||
if (!self)
|
||||
return NULL;
|
||||
|
||||
self->parent.impl = &encoder_impl_open_h264;
|
||||
|
||||
if (open_h264_init_pending(self) < 0) {
|
||||
free(self);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (struct encoder*)self;
|
||||
}
|
||||
|
||||
static void open_h264_destroy(struct encoder* enc)
|
||||
{
|
||||
struct open_h264* self = open_h264(enc);
|
||||
|
||||
if (self->encoder)
|
||||
h264_encoder_destroy(self->encoder);
|
||||
vec_destroy(&self->pending);
|
||||
|
||||
free(self);
|
||||
}
|
||||
|
||||
static int open_h264_resize(struct open_h264* self, struct nvnc_fb* fb)
|
||||
{
|
||||
struct h264_encoder* encoder = h264_encoder_create(fb->width,
|
||||
fb->height, fb->fourcc_format);
|
||||
if (!encoder)
|
||||
return -1;
|
||||
|
||||
if (self->encoder)
|
||||
h264_encoder_destroy(self->encoder);
|
||||
|
||||
h264_encoder_set_userdata(encoder, self);
|
||||
h264_encoder_set_packet_handler_fn(encoder, open_h264_handle_packet);
|
||||
|
||||
self->encoder = encoder;
|
||||
|
||||
self->width = fb->width;
|
||||
self->height = fb->height;
|
||||
self->format = fb->fourcc_format;
|
||||
self->needs_reset = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int open_h264_push(struct encoder* enc, struct nvnc_fb* fb,
|
||||
struct pixman_region16* damage)
|
||||
{
|
||||
struct open_h264* self = open_h264(enc);
|
||||
(void)damage;
|
||||
|
||||
if (fb->width != self->width || fb->height != self->height ||
|
||||
fb->fourcc_format != self->format) {
|
||||
if (open_h264_resize(self, fb) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(self->width && self->height);
|
||||
|
||||
// TODO: encoder_feed should return an error code
|
||||
h264_encoder_feed(self->encoder, fb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct rcbuf* open_h264_pull(struct encoder* enc)
|
||||
{
|
||||
struct open_h264* self = open_h264(enc);
|
||||
|
||||
size_t payload_size = self->pending.len
|
||||
- sizeof(struct rfb_server_fb_rect)
|
||||
- sizeof(struct open_h264_header);
|
||||
if (payload_size == 0)
|
||||
return NULL;
|
||||
|
||||
uint32_t flags = self->needs_reset ? OPEN_H264_FLAG_RESET_CONTEXT : 0;
|
||||
self->needs_reset = false;
|
||||
|
||||
struct rfb_server_fb_rect* rect = self->pending.data;
|
||||
rect->encoding = htonl(RFB_ENCODING_OPEN_H264);
|
||||
rect->width = htons(self->width);
|
||||
rect->height = htons(self->height);
|
||||
rect->x = htons(self->parent.x_pos);
|
||||
rect->y = htons(self->parent.y_pos);
|
||||
|
||||
struct open_h264_header* header =
|
||||
(void*)(((uint8_t*)self->pending.data) + sizeof(*rect));
|
||||
header->length = htonl(payload_size);
|
||||
header->flags = htonl(flags);
|
||||
|
||||
enc->n_rects = 1;
|
||||
|
||||
struct rcbuf* payload = rcbuf_new(self->pending.data, self->pending.len);
|
||||
|
||||
open_h264_init_pending(self);
|
||||
return payload;
|
||||
}
|
||||
|
||||
static void open_h264_request_keyframe(struct encoder* enc)
|
||||
{
|
||||
struct open_h264* self = open_h264(enc);
|
||||
h264_encoder_request_keyframe(self->encoder);
|
||||
}
|
||||
|
||||
struct encoder_impl encoder_impl_open_h264 = {
|
||||
.destroy = open_h264_destroy,
|
||||
.push = open_h264_push,
|
||||
.pull = open_h264_pull,
|
||||
.request_key_frame = open_h264_request_keyframe,
|
||||
};
|
Loading…
Reference in New Issue