mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2026-08-07 12:36:46 +00:00
server: Enable adding alternative MJPEG video encoders
This replaces the original mjpeg_encoder API with a VideoEncoder base class which can be reimplemented by other encoders. This also renames the members and enums from mjpeg_* to video_*. Signed-off-by: Francois Gouget <fgouget@codeweavers.com> Acked-by: Christophe Fergeau <cfergeau@redhat.com>
This commit is contained in:
parent
8a5ba34c01
commit
0e10629d70
@ -87,7 +87,6 @@ libserver_la_SOURCES = \
|
||||
main-channel.c \
|
||||
main-channel.h \
|
||||
mjpeg-encoder.c \
|
||||
mjpeg-encoder.h \
|
||||
red-channel.c \
|
||||
red-channel.h \
|
||||
red-common.h \
|
||||
@ -125,6 +124,7 @@ libserver_la_SOURCES = \
|
||||
sound.h \
|
||||
stat.h \
|
||||
spicevmc.c \
|
||||
video-encoder.h \
|
||||
zlib-encoder.c \
|
||||
zlib-encoder.h \
|
||||
image-cache.h \
|
||||
|
||||
@ -1659,7 +1659,7 @@ static int red_marshall_stream_data(RedChannelClient *rcc,
|
||||
Stream *stream = drawable->stream;
|
||||
SpiceImage *image;
|
||||
uint32_t frame_mm_time;
|
||||
int n;
|
||||
uint32_t n;
|
||||
int width, height;
|
||||
int ret;
|
||||
|
||||
@ -1693,7 +1693,7 @@ static int red_marshall_stream_data(RedChannelClient *rcc,
|
||||
uint64_t time_now = spice_get_monotonic_time_ns();
|
||||
size_t outbuf_size;
|
||||
|
||||
if (!dcc->use_mjpeg_encoder_rate_control) {
|
||||
if (!dcc->use_video_encoder_rate_control) {
|
||||
if (time_now - agent->last_send_time < (1000 * 1000 * 1000) / agent->fps) {
|
||||
agent->frames--;
|
||||
#ifdef STREAM_STATS
|
||||
@ -1708,25 +1708,26 @@ static int red_marshall_stream_data(RedChannelClient *rcc,
|
||||
drawable->red_drawable->mm_time :
|
||||
reds_get_mm_time();
|
||||
outbuf_size = dcc->send_data.stream_outbuf_size;
|
||||
ret = mjpeg_encoder_encode_frame(agent->mjpeg_encoder,
|
||||
&image->u.bitmap, width, height,
|
||||
&drawable->red_drawable->u.copy.src_area,
|
||||
stream->top_down, frame_mm_time,
|
||||
&dcc->send_data.stream_outbuf,
|
||||
&outbuf_size, &n);
|
||||
ret = agent->video_encoder->encode_frame(agent->video_encoder,
|
||||
frame_mm_time,
|
||||
&image->u.bitmap, width, height,
|
||||
&drawable->red_drawable->u.copy.src_area,
|
||||
stream->top_down,
|
||||
&dcc->send_data.stream_outbuf,
|
||||
&outbuf_size, &n);
|
||||
switch (ret) {
|
||||
case MJPEG_ENCODER_FRAME_DROP:
|
||||
spice_assert(dcc->use_mjpeg_encoder_rate_control);
|
||||
case VIDEO_ENCODER_FRAME_DROP:
|
||||
spice_assert(dcc->use_video_encoder_rate_control);
|
||||
#ifdef STREAM_STATS
|
||||
agent->stats.num_drops_fps++;
|
||||
#endif
|
||||
return TRUE;
|
||||
case MJPEG_ENCODER_FRAME_UNSUPPORTED:
|
||||
case VIDEO_ENCODER_FRAME_UNSUPPORTED:
|
||||
return FALSE;
|
||||
case MJPEG_ENCODER_FRAME_ENCODE_DONE:
|
||||
case VIDEO_ENCODER_FRAME_ENCODE_DONE:
|
||||
break;
|
||||
default:
|
||||
spice_error("bad return value (%d) from mjpeg_encoder_encode_frame", ret);
|
||||
spice_error("bad return value (%d) from VideoEncoder::encode_frame", ret);
|
||||
return FALSE;
|
||||
}
|
||||
dcc->send_data.stream_outbuf_size = outbuf_size;
|
||||
|
||||
24
server/dcc.c
24
server/dcc.c
@ -346,7 +346,7 @@ static void dcc_init_stream_agents(DisplayChannelClient *dcc)
|
||||
red_pipe_item_init(&agent->create_item, RED_PIPE_ITEM_TYPE_STREAM_CREATE);
|
||||
red_pipe_item_init(&agent->destroy_item, RED_PIPE_ITEM_TYPE_STREAM_DESTROY);
|
||||
}
|
||||
dcc->use_mjpeg_encoder_rate_control =
|
||||
dcc->use_video_encoder_rate_control =
|
||||
red_channel_client_test_remote_cap(RED_CHANNEL_CLIENT(dcc), SPICE_DISPLAY_CAP_STREAM_REPORT);
|
||||
}
|
||||
|
||||
@ -477,9 +477,9 @@ static void dcc_destroy_stream_agents(DisplayChannelClient *dcc)
|
||||
StreamAgent *agent = &dcc->stream_agents[i];
|
||||
region_destroy(&agent->vis_region);
|
||||
region_destroy(&agent->clip);
|
||||
if (agent->mjpeg_encoder) {
|
||||
mjpeg_encoder_destroy(agent->mjpeg_encoder);
|
||||
agent->mjpeg_encoder = NULL;
|
||||
if (agent->video_encoder) {
|
||||
agent->video_encoder->destroy(agent->video_encoder);
|
||||
agent->video_encoder = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1393,7 +1393,7 @@ static int dcc_handle_stream_report(DisplayChannelClient *dcc,
|
||||
}
|
||||
|
||||
agent = &dcc->stream_agents[report->stream_id];
|
||||
if (!agent->mjpeg_encoder) {
|
||||
if (!agent->video_encoder) {
|
||||
spice_info("stream_report: no encoder for stream id %u. "
|
||||
"The stream has probably been destroyed",
|
||||
report->stream_id);
|
||||
@ -1407,13 +1407,13 @@ static int dcc_handle_stream_report(DisplayChannelClient *dcc,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
mjpeg_encoder_client_stream_report(agent->mjpeg_encoder,
|
||||
report->num_frames,
|
||||
report->num_drops,
|
||||
report->start_frame_mm_time,
|
||||
report->end_frame_mm_time,
|
||||
report->last_frame_delay,
|
||||
report->audio_delay);
|
||||
agent->video_encoder->client_stream_report(agent->video_encoder,
|
||||
report->num_frames,
|
||||
report->num_drops,
|
||||
report->start_frame_mm_time,
|
||||
report->end_frame_mm_time,
|
||||
report->last_frame_delay,
|
||||
report->audio_delay);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
@ -109,7 +109,7 @@ struct DisplayChannelClient {
|
||||
QRegion surface_client_lossy_region[NUM_SURFACES];
|
||||
|
||||
StreamAgent stream_agents[NUM_STREAMS];
|
||||
int use_mjpeg_encoder_rate_control;
|
||||
int use_video_encoder_rate_control;
|
||||
uint32_t streams_max_latency;
|
||||
uint64_t streams_max_bit_rate;
|
||||
bool gl_draw_ongoing;
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
#endif
|
||||
|
||||
#include "red-common.h"
|
||||
#include "mjpeg-encoder.h"
|
||||
#include "video-encoder.h"
|
||||
#include "utils.h"
|
||||
#include <jerror.h>
|
||||
#include <jpeglib.h>
|
||||
@ -154,7 +154,8 @@ typedef struct MJpegEncoderRateControl {
|
||||
uint64_t warmup_start_time;
|
||||
} MJpegEncoderRateControl;
|
||||
|
||||
struct MJpegEncoder {
|
||||
typedef struct MJpegEncoder {
|
||||
VideoEncoder base;
|
||||
uint8_t *row;
|
||||
uint32_t row_size;
|
||||
int first_frame;
|
||||
@ -166,13 +167,13 @@ struct MJpegEncoder {
|
||||
void (*pixel_converter)(void *src, uint8_t *dest);
|
||||
|
||||
MJpegEncoderRateControl rate_control;
|
||||
MJpegEncoderRateControlCbs cbs;
|
||||
VideoEncoderRateControlCbs cbs;
|
||||
|
||||
/* stats */
|
||||
uint64_t starting_bit_rate;
|
||||
uint64_t avg_quality;
|
||||
uint32_t num_frames;
|
||||
};
|
||||
} MJpegEncoder;
|
||||
|
||||
static void mjpeg_encoder_process_server_drops(MJpegEncoder *encoder);
|
||||
static uint32_t get_min_required_playback_delay(uint64_t frame_enc_size,
|
||||
@ -184,8 +185,9 @@ static inline int rate_control_is_active(MJpegEncoder* encoder)
|
||||
return encoder->cbs.get_roundtrip_ms != NULL;
|
||||
}
|
||||
|
||||
void mjpeg_encoder_destroy(MJpegEncoder *encoder)
|
||||
static void mjpeg_encoder_destroy(VideoEncoder *video_encoder)
|
||||
{
|
||||
MJpegEncoder *encoder = (MJpegEncoder*)video_encoder;
|
||||
free(encoder->cinfo.dest);
|
||||
jpeg_destroy_compress(&encoder->cinfo);
|
||||
free(encoder->row);
|
||||
@ -724,7 +726,7 @@ static int mjpeg_encoder_start_frame(MJpegEncoder *encoder,
|
||||
interval = (now - rate_control->bit_rate_info.last_frame_time);
|
||||
|
||||
if (interval < NSEC_PER_SEC / rate_control->adjusted_fps) {
|
||||
return MJPEG_ENCODER_FRAME_DROP;
|
||||
return VIDEO_ENCODER_FRAME_DROP;
|
||||
}
|
||||
|
||||
mjpeg_encoder_adjust_params_to_bit_rate(encoder);
|
||||
@ -772,14 +774,14 @@ static int mjpeg_encoder_start_frame(MJpegEncoder *encoder,
|
||||
break;
|
||||
default:
|
||||
spice_debug("unsupported format %d", format);
|
||||
return MJPEG_ENCODER_FRAME_UNSUPPORTED;
|
||||
return VIDEO_ENCODER_FRAME_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (encoder->pixel_converter != NULL) {
|
||||
unsigned int stride = width * 3;
|
||||
/* check for integer overflow */
|
||||
if (stride < width) {
|
||||
return MJPEG_ENCODER_FRAME_UNSUPPORTED;
|
||||
return VIDEO_ENCODER_FRAME_UNSUPPORTED;
|
||||
}
|
||||
if (encoder->row_size < stride) {
|
||||
encoder->row = spice_realloc(encoder->row, stride);
|
||||
@ -799,7 +801,7 @@ static int mjpeg_encoder_start_frame(MJpegEncoder *encoder,
|
||||
|
||||
encoder->num_frames++;
|
||||
encoder->avg_quality += quality;
|
||||
return MJPEG_ENCODER_FRAME_ENCODE_DONE;
|
||||
return VIDEO_ENCODER_FRAME_ENCODE_DONE;
|
||||
}
|
||||
|
||||
static int mjpeg_encoder_encode_scanline(MJpegEncoder *encoder,
|
||||
@ -923,27 +925,31 @@ static int encode_frame(MJpegEncoder *encoder, const SpiceRect *src,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int mjpeg_encoder_encode_frame(MJpegEncoder *encoder,
|
||||
const SpiceBitmap *bitmap, int width, int height,
|
||||
const SpiceRect *src,
|
||||
int top_down, uint32_t frame_mm_time,
|
||||
uint8_t **outbuf, size_t *outbuf_size,
|
||||
int *data_size)
|
||||
static int mjpeg_encoder_encode_frame(VideoEncoder *video_encoder,
|
||||
uint32_t frame_mm_time,
|
||||
const SpiceBitmap *bitmap,
|
||||
int width, int height,
|
||||
const SpiceRect *src, int top_down,
|
||||
uint8_t **outbuf, size_t *outbuf_size,
|
||||
uint32_t *data_size)
|
||||
{
|
||||
MJpegEncoder *encoder = (MJpegEncoder*)video_encoder;
|
||||
|
||||
int ret = mjpeg_encoder_start_frame(encoder, bitmap->format,
|
||||
width, height, outbuf, outbuf_size,
|
||||
frame_mm_time);
|
||||
if (ret != MJPEG_ENCODER_FRAME_ENCODE_DONE) {
|
||||
width, height,
|
||||
outbuf, outbuf_size,
|
||||
frame_mm_time);
|
||||
if (ret != VIDEO_ENCODER_FRAME_ENCODE_DONE) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!encode_frame(encoder, src, bitmap, top_down)) {
|
||||
return MJPEG_ENCODER_FRAME_UNSUPPORTED;
|
||||
return VIDEO_ENCODER_FRAME_UNSUPPORTED;
|
||||
}
|
||||
|
||||
*data_size = mjpeg_encoder_end_frame(encoder);
|
||||
|
||||
return MJPEG_ENCODER_FRAME_ENCODE_DONE;
|
||||
return VIDEO_ENCODER_FRAME_ENCODE_DONE;
|
||||
}
|
||||
|
||||
|
||||
@ -1174,14 +1180,15 @@ static uint32_t get_min_required_playback_delay(uint64_t frame_enc_size,
|
||||
#define MJPEG_VIDEO_VS_AUDIO_LATENCY_FACTOR 1.25
|
||||
#define MJPEG_VIDEO_DELAY_TH -15
|
||||
|
||||
void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder,
|
||||
uint32_t num_frames,
|
||||
uint32_t num_drops,
|
||||
uint32_t start_frame_mm_time,
|
||||
uint32_t end_frame_mm_time,
|
||||
int32_t end_frame_delay,
|
||||
uint32_t audio_delay)
|
||||
static void mjpeg_encoder_client_stream_report(VideoEncoder *video_encoder,
|
||||
uint32_t num_frames,
|
||||
uint32_t num_drops,
|
||||
uint32_t start_frame_mm_time,
|
||||
uint32_t end_frame_mm_time,
|
||||
int32_t end_frame_delay,
|
||||
uint32_t audio_delay)
|
||||
{
|
||||
MJpegEncoder *encoder = (MJpegEncoder*)video_encoder;
|
||||
MJpegEncoderRateControl *rate_control = &encoder->rate_control;
|
||||
MJpegEncoderClientState *client_state = &rate_control->client_state;
|
||||
uint64_t avg_enc_size = 0;
|
||||
@ -1282,8 +1289,9 @@ void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder,
|
||||
}
|
||||
}
|
||||
|
||||
void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder)
|
||||
static void mjpeg_encoder_notify_server_frame_drop(VideoEncoder *video_encoder)
|
||||
{
|
||||
MJpegEncoder *encoder = (MJpegEncoder*)video_encoder;
|
||||
encoder->rate_control.server_state.num_frames_dropped++;
|
||||
mjpeg_encoder_process_server_drops(encoder);
|
||||
}
|
||||
@ -1320,24 +1328,33 @@ static void mjpeg_encoder_process_server_drops(MJpegEncoder *encoder)
|
||||
server_state->num_frames_dropped = 0;
|
||||
}
|
||||
|
||||
uint64_t mjpeg_encoder_get_bit_rate(MJpegEncoder *encoder)
|
||||
static uint64_t mjpeg_encoder_get_bit_rate(VideoEncoder *video_encoder)
|
||||
{
|
||||
MJpegEncoder *encoder = (MJpegEncoder*)video_encoder;
|
||||
return encoder->rate_control.byte_rate * 8;
|
||||
}
|
||||
|
||||
void mjpeg_encoder_get_stats(MJpegEncoder *encoder, MJpegEncoderStats *stats)
|
||||
static void mjpeg_encoder_get_stats(VideoEncoder *video_encoder,
|
||||
VideoEncoderStats *stats)
|
||||
{
|
||||
MJpegEncoder *encoder = (MJpegEncoder*)video_encoder;
|
||||
spice_assert(encoder != NULL && stats != NULL);
|
||||
stats->starting_bit_rate = encoder->starting_bit_rate;
|
||||
stats->cur_bit_rate = mjpeg_encoder_get_bit_rate(encoder);
|
||||
stats->cur_bit_rate = mjpeg_encoder_get_bit_rate(video_encoder);
|
||||
stats->avg_quality = (double)encoder->avg_quality / encoder->num_frames;
|
||||
}
|
||||
|
||||
MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate,
|
||||
MJpegEncoderRateControlCbs *cbs)
|
||||
VideoEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate,
|
||||
VideoEncoderRateControlCbs *cbs)
|
||||
{
|
||||
MJpegEncoder *encoder = spice_new0(MJpegEncoder, 1);
|
||||
|
||||
encoder->base.destroy = mjpeg_encoder_destroy;
|
||||
encoder->base.encode_frame = mjpeg_encoder_encode_frame;
|
||||
encoder->base.client_stream_report = mjpeg_encoder_client_stream_report;
|
||||
encoder->base.notify_server_frame_drop = mjpeg_encoder_notify_server_frame_drop;
|
||||
encoder->base.get_bit_rate = mjpeg_encoder_get_bit_rate;
|
||||
encoder->base.get_stats = mjpeg_encoder_get_stats;
|
||||
encoder->first_frame = TRUE;
|
||||
encoder->rate_control.byte_rate = starting_bit_rate / 8;
|
||||
encoder->starting_bit_rate = starting_bit_rate;
|
||||
@ -1357,5 +1374,5 @@ MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate,
|
||||
encoder->cinfo.err = jpeg_std_error(&encoder->jerr);
|
||||
jpeg_create_compress(&encoder->cinfo);
|
||||
|
||||
return encoder;
|
||||
return (VideoEncoder*)encoder;
|
||||
}
|
||||
|
||||
@ -1,102 +0,0 @@
|
||||
/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
/*
|
||||
Copyright (C) 2009 Red Hat, Inc.
|
||||
|
||||
This library 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 library 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 library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _H_MJPEG_ENCODER
|
||||
#define _H_MJPEG_ENCODER
|
||||
|
||||
#include "red-common.h"
|
||||
|
||||
enum {
|
||||
MJPEG_ENCODER_FRAME_UNSUPPORTED = -1,
|
||||
MJPEG_ENCODER_FRAME_DROP,
|
||||
MJPEG_ENCODER_FRAME_ENCODE_DONE,
|
||||
};
|
||||
|
||||
typedef struct MJpegEncoder MJpegEncoder;
|
||||
|
||||
/*
|
||||
* Callbacks required for controling and adjusting
|
||||
* the stream bit rate:
|
||||
* @opaque: a pointer to be passed to the rate control callbacks.
|
||||
* get_roundtrip_ms: roundtrip time in milliseconds
|
||||
* get_source_fps: the input frame rate (#frames per second), i.e.,
|
||||
* the rate of frames arriving from the guest to spice-server,
|
||||
* before any drops.
|
||||
*/
|
||||
typedef struct MJpegEncoderRateControlCbs {
|
||||
void *opaque;
|
||||
uint32_t (*get_roundtrip_ms)(void *opaque);
|
||||
uint32_t (*get_source_fps)(void *opaque);
|
||||
void (*update_client_playback_delay)(void *opaque, uint32_t delay_ms);
|
||||
} MJpegEncoderRateControlCbs;
|
||||
|
||||
typedef struct MJpegEncoderStats {
|
||||
uint64_t starting_bit_rate;
|
||||
uint64_t cur_bit_rate;
|
||||
double avg_quality;
|
||||
} MJpegEncoderStats;
|
||||
|
||||
MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate,
|
||||
MJpegEncoderRateControlCbs *cbs);
|
||||
void mjpeg_encoder_destroy(MJpegEncoder *encoder);
|
||||
|
||||
int mjpeg_encoder_encode_frame(MJpegEncoder *encoder,
|
||||
const SpiceBitmap *bitmap, int width, int height,
|
||||
const SpiceRect *src,
|
||||
int top_down, uint32_t frame_mm_time,
|
||||
uint8_t **outbuf, size_t *outbuf_size,
|
||||
int *data_size);
|
||||
|
||||
/*
|
||||
* bit rate control
|
||||
*/
|
||||
|
||||
/*
|
||||
* Data that should be periodically obtained from the client. The report contains:
|
||||
* num_frames : the number of frames that reached the client during the time
|
||||
* the report is referring to.
|
||||
* num_drops : the part of the above frames that was dropped by the client due to
|
||||
* late arrival time.
|
||||
* start_frame_mm_time: the mm_time of the first frame included in the report
|
||||
* end_frame_mm_time : the mm_time of the last_frame included in the report
|
||||
* end_frame_delay : (end_frame_mm_time - client_mm_time)
|
||||
* audio delay : the latency of the audio playback.
|
||||
* If there is no audio playback, set it to MAX_UINT.
|
||||
*
|
||||
*/
|
||||
void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder,
|
||||
uint32_t num_frames,
|
||||
uint32_t num_drops,
|
||||
uint32_t start_frame_mm_time,
|
||||
uint32_t end_frame_mm_time,
|
||||
int32_t end_frame_delay,
|
||||
uint32_t audio_delay);
|
||||
|
||||
/*
|
||||
* Notify the encoder each time a frame is dropped due to pipe
|
||||
* congestion.
|
||||
* We can deduce the client state by the frame dropping rate in the server.
|
||||
* Monitoring the frame drops can help in fine tuning the playback parameters
|
||||
* when the client reports are delayed.
|
||||
*/
|
||||
void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder);
|
||||
|
||||
uint64_t mjpeg_encoder_get_bit_rate(MJpegEncoder *encoder);
|
||||
void mjpeg_encoder_get_stats(MJpegEncoder *encoder, MJpegEncoderStats *stats);
|
||||
|
||||
#endif
|
||||
@ -32,10 +32,10 @@ void stream_agent_stats_print(StreamAgent *agent)
|
||||
#ifdef STREAM_STATS
|
||||
StreamStats *stats = &agent->stats;
|
||||
double passed_mm_time = (stats->end - stats->start) / 1000.0;
|
||||
MJpegEncoderStats encoder_stats = {0};
|
||||
VideoEncoderStats encoder_stats = {0};
|
||||
|
||||
if (agent->mjpeg_encoder) {
|
||||
mjpeg_encoder_get_stats(agent->mjpeg_encoder, &encoder_stats);
|
||||
if (agent->video_encoder) {
|
||||
agent->video_encoder->get_stats(agent->video_encoder, &encoder_stats);
|
||||
}
|
||||
|
||||
spice_debug("stream=%p dim=(%dx%d) #in-frames=%"PRIu64" #in-avg-fps=%.2f #out-frames=%"PRIu64" "
|
||||
@ -79,8 +79,8 @@ void stream_stop(DisplayChannel *display, Stream *stream)
|
||||
region_clear(&stream_agent->vis_region);
|
||||
region_clear(&stream_agent->clip);
|
||||
spice_assert(!red_pipe_item_is_linked(&stream_agent->destroy_item));
|
||||
if (stream_agent->mjpeg_encoder && dcc->use_mjpeg_encoder_rate_control) {
|
||||
uint64_t stream_bit_rate = mjpeg_encoder_get_bit_rate(stream_agent->mjpeg_encoder);
|
||||
if (stream_agent->video_encoder && dcc->use_video_encoder_rate_control) {
|
||||
uint64_t stream_bit_rate = stream_agent->video_encoder->get_bit_rate(stream_agent->video_encoder);
|
||||
|
||||
if (stream_bit_rate > dcc->streams_max_bit_rate) {
|
||||
spice_debug("old max-bit-rate=%.2f new=%.2f",
|
||||
@ -338,7 +338,7 @@ static void before_reattach_stream(DisplayChannel *display,
|
||||
dcc = dpi->dcc;
|
||||
agent = &dcc->stream_agents[index];
|
||||
|
||||
if (!dcc->use_mjpeg_encoder_rate_control &&
|
||||
if (!dcc->use_video_encoder_rate_control &&
|
||||
!dcc->common.is_low_bandwidth) {
|
||||
continue;
|
||||
}
|
||||
@ -347,8 +347,8 @@ static void before_reattach_stream(DisplayChannel *display,
|
||||
#ifdef STREAM_STATS
|
||||
agent->stats.num_drops_pipe++;
|
||||
#endif
|
||||
if (dcc->use_mjpeg_encoder_rate_control) {
|
||||
mjpeg_encoder_notify_server_frame_drop(agent->mjpeg_encoder);
|
||||
if (dcc->use_video_encoder_rate_control) {
|
||||
agent->video_encoder->notify_server_frame_drop(agent->video_encoder);
|
||||
} else {
|
||||
++agent->drops;
|
||||
}
|
||||
@ -361,7 +361,7 @@ static void before_reattach_stream(DisplayChannel *display,
|
||||
|
||||
agent = &dcc->stream_agents[index];
|
||||
|
||||
if (dcc->use_mjpeg_encoder_rate_control) {
|
||||
if (dcc->use_video_encoder_rate_control) {
|
||||
continue;
|
||||
}
|
||||
if (agent->frames / agent->fps < FPS_TEST_INTERVAL) {
|
||||
@ -594,7 +594,7 @@ static void dcc_update_streams_max_latency(DisplayChannelClient *dcc, StreamAgen
|
||||
}
|
||||
for (i = 0; i < NUM_STREAMS; i++) {
|
||||
StreamAgent *other_agent = &dcc->stream_agents[i];
|
||||
if (other_agent == remove_agent || !other_agent->mjpeg_encoder) {
|
||||
if (other_agent == remove_agent || !other_agent->video_encoder) {
|
||||
continue;
|
||||
}
|
||||
if (other_agent->client_required_latency > new_max_latency) {
|
||||
@ -714,19 +714,19 @@ void dcc_create_stream(DisplayChannelClient *dcc, Stream *stream)
|
||||
agent->fps = MAX_FPS;
|
||||
agent->dcc = dcc;
|
||||
|
||||
if (dcc->use_mjpeg_encoder_rate_control) {
|
||||
MJpegEncoderRateControlCbs mjpeg_cbs;
|
||||
if (dcc->use_video_encoder_rate_control) {
|
||||
VideoEncoderRateControlCbs video_cbs;
|
||||
uint64_t initial_bit_rate;
|
||||
|
||||
mjpeg_cbs.opaque = agent;
|
||||
mjpeg_cbs.get_roundtrip_ms = get_roundtrip_ms;
|
||||
mjpeg_cbs.get_source_fps = get_source_fps;
|
||||
mjpeg_cbs.update_client_playback_delay = update_client_playback_delay;
|
||||
video_cbs.opaque = agent;
|
||||
video_cbs.get_roundtrip_ms = get_roundtrip_ms;
|
||||
video_cbs.get_source_fps = get_source_fps;
|
||||
video_cbs.update_client_playback_delay = update_client_playback_delay;
|
||||
|
||||
initial_bit_rate = get_initial_bit_rate(dcc, stream);
|
||||
agent->mjpeg_encoder = mjpeg_encoder_new(initial_bit_rate, &mjpeg_cbs);
|
||||
agent->video_encoder = mjpeg_encoder_new(initial_bit_rate, &video_cbs);
|
||||
} else {
|
||||
agent->mjpeg_encoder = mjpeg_encoder_new(0, NULL);
|
||||
agent->video_encoder = mjpeg_encoder_new(0, NULL);
|
||||
}
|
||||
red_channel_client_pipe_add(RED_CHANNEL_CLIENT(dcc), &agent->create_item);
|
||||
|
||||
@ -752,9 +752,9 @@ void stream_agent_stop(StreamAgent *agent)
|
||||
DisplayChannelClient *dcc = agent->dcc;
|
||||
|
||||
dcc_update_streams_max_latency(dcc, agent);
|
||||
if (agent->mjpeg_encoder) {
|
||||
mjpeg_encoder_destroy(agent->mjpeg_encoder);
|
||||
agent->mjpeg_encoder = NULL;
|
||||
if (agent->video_encoder) {
|
||||
agent->video_encoder->destroy(agent->video_encoder);
|
||||
agent->video_encoder = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
|
||||
#include <glib.h>
|
||||
#include "utils.h"
|
||||
#include "mjpeg-encoder.h"
|
||||
#include "video-encoder.h"
|
||||
#include "common/region.h"
|
||||
#include "red-channel.h"
|
||||
#include "image-cache.h"
|
||||
@ -85,7 +85,7 @@ typedef struct StreamAgent {
|
||||
RedPipeItem destroy_item;
|
||||
Stream *stream;
|
||||
uint64_t last_send_time;
|
||||
MJpegEncoder *mjpeg_encoder;
|
||||
VideoEncoder *video_encoder;
|
||||
DisplayChannelClient *dcc;
|
||||
|
||||
int frames;
|
||||
|
||||
161
server/video-encoder.h
Normal file
161
server/video-encoder.h
Normal file
@ -0,0 +1,161 @@
|
||||
/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
/*
|
||||
Copyright (C) 2009 Red Hat, Inc.
|
||||
Copyright (C) 2015 Jeremy White
|
||||
Copyright (C) 2015 Francois Gouget
|
||||
|
||||
This library 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 library 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 library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _H_VIDEO_ENCODER
|
||||
#define _H_VIDEO_ENCODER
|
||||
|
||||
enum {
|
||||
VIDEO_ENCODER_FRAME_UNSUPPORTED = -1,
|
||||
VIDEO_ENCODER_FRAME_DROP,
|
||||
VIDEO_ENCODER_FRAME_ENCODE_DONE,
|
||||
};
|
||||
|
||||
typedef struct VideoEncoderStats {
|
||||
uint64_t starting_bit_rate;
|
||||
uint64_t cur_bit_rate;
|
||||
double avg_quality;
|
||||
} VideoEncoderStats;
|
||||
|
||||
typedef struct VideoEncoder VideoEncoder;
|
||||
struct VideoEncoder {
|
||||
/* Releases the video encoder's resources */
|
||||
void (*destroy)(VideoEncoder *encoder);
|
||||
|
||||
/* Compresses the specified src image area into the outbuf buffer.
|
||||
*
|
||||
* @encoder: The video encoder.
|
||||
* @frame_mm_time: The frame's mm-time timestamp in milliseconds.
|
||||
* @bitmap: The Spice screen.
|
||||
* @src: A rectangle specifying the area occupied by the video.
|
||||
* @top_down: If true the first video line is specified by src.top.
|
||||
* @outbuf: The buffer for the compressed frame. This must either
|
||||
* be NULL or point to a buffer allocated by malloc
|
||||
* since it may be reallocated, if its size is too small.
|
||||
* @outbuf_size: The size of the outbuf buffer.
|
||||
* @data_size: The size of the compressed frame.
|
||||
* @return:
|
||||
* VIDEO_ENCODER_FRAME_ENCODE_DONE if successful.
|
||||
* VIDEO_ENCODER_FRAME_UNSUPPORTED if the frame cannot be encoded.
|
||||
* VIDEO_ENCODER_FRAME_DROP if the frame was dropped. This value can
|
||||
* only happen if rate control is active.
|
||||
*/
|
||||
int (*encode_frame)(VideoEncoder *encoder, uint32_t frame_mm_time,
|
||||
const SpiceBitmap *bitmap, int width, int height,
|
||||
const SpiceRect *src, int top_down,
|
||||
uint8_t **outbuf, size_t *outbuf_size,
|
||||
uint32_t *data_size);
|
||||
|
||||
/*
|
||||
* Bit rate control methods.
|
||||
*/
|
||||
|
||||
/* When rate control is active statistics are periodically obtained from
|
||||
* the client and sent to the video encoder through this method.
|
||||
*
|
||||
* @encoder: The video encoder.
|
||||
* @num_frames: The number of frames that reached the client during
|
||||
* the time period the report is referring to.
|
||||
* @num_drops: The part of the above frames that was dropped by the
|
||||
* client due to late arrival time.
|
||||
* @start_frame_mm_time: The mm_time of the first frame included in the
|
||||
* report.
|
||||
* @end_frame_mm_time: The mm_time of the last frame included in the
|
||||
* report.
|
||||
* @end_frame_delay: This indicates how long in advance the client
|
||||
* received the last frame before having to display it.
|
||||
* @audio delay: The latency of the audio playback or MAX_UINT if it
|
||||
* is not tracked.
|
||||
*/
|
||||
void (*client_stream_report)(VideoEncoder *encoder,
|
||||
uint32_t num_frames, uint32_t num_drops,
|
||||
uint32_t start_frame_mm_time,
|
||||
uint32_t end_frame_mm_time,
|
||||
int32_t end_frame_delay, uint32_t audio_delay);
|
||||
|
||||
/* This notifies the video encoder each time a frame is dropped due to
|
||||
* pipe congestion.
|
||||
*
|
||||
* Note that frames are being dropped before they are encoded and that
|
||||
* there may be any number of encoded frames in the network queue.
|
||||
* The client reports provide richer and typically more reactive
|
||||
* information for fine tuning the playback parameters but this function
|
||||
* provides a fallback when client reports are getting delayed or are not
|
||||
* supported by the client.
|
||||
*
|
||||
* @encoder: The video encoder.
|
||||
*/
|
||||
void (*notify_server_frame_drop)(VideoEncoder *encoder);
|
||||
|
||||
/* This queries the video encoder's current bit rate.
|
||||
*
|
||||
* @encoder: The video encoder.
|
||||
* @return: The current bit rate in bits per second.
|
||||
*/
|
||||
uint64_t (*get_bit_rate)(VideoEncoder *encoder);
|
||||
|
||||
/* Collects video statistics.
|
||||
*
|
||||
* @encoder: The video encoder.
|
||||
* @stats: A VideoEncoderStats structure to fill with the collected
|
||||
* statistics.
|
||||
*/
|
||||
void (*get_stats)(VideoEncoder *encoder, VideoEncoderStats *stats);
|
||||
};
|
||||
|
||||
|
||||
/* When rate control is active the video encoder can use these callbacks to
|
||||
* figure out how to adjust the stream bit rate and adjust some stream
|
||||
* parameters.
|
||||
*/
|
||||
typedef struct VideoEncoderRateControlCbs {
|
||||
/* The opaque parameter for the callbacks */
|
||||
void *opaque;
|
||||
|
||||
/* Returns the stream's estimated roundtrip time in milliseconds. */
|
||||
uint32_t (*get_roundtrip_ms)(void *opaque);
|
||||
|
||||
/* Returns the estimated input frame rate.
|
||||
*
|
||||
* This is the number of frames per second arriving from the guest to
|
||||
* spice-server, before any drops.
|
||||
*/
|
||||
uint32_t (*get_source_fps)(void *opaque);
|
||||
|
||||
/* Informs the client of the minimum playback delay.
|
||||
*
|
||||
* @delay_ms: The minimum number of milliseconds required for the
|
||||
* frames to reach the client.
|
||||
*/
|
||||
void (*update_client_playback_delay)(void *opaque, uint32_t delay_ms);
|
||||
} VideoEncoderRateControlCbs;
|
||||
|
||||
|
||||
/* Instantiates the video encoder.
|
||||
*
|
||||
* @starting_bit_rate: An initial estimate of the available stream bit rate
|
||||
* or zero if the client does not support rate control.
|
||||
* @cbs: A set of callback methods to be used for rate control.
|
||||
* @return: A pointer to a structure implementing the VideoEncoder
|
||||
* methods.
|
||||
*/
|
||||
VideoEncoder* mjpeg_encoder_new(uint64_t starting_bit_rate,
|
||||
VideoEncoderRateControlCbs *cbs);
|
||||
|
||||
#endif
|
||||
Loading…
Reference in New Issue
Block a user