[MOVED FROM BAD 050/134] vp8enc: Implement multipass encoding

Fixes bug #621348.
This commit is contained in:
Sebastian Dröge 2010-06-12 09:02:29 +02:00
parent 7f3e1a1484
commit 83ffb384d0

View file

@ -79,6 +79,10 @@ struct _GstVP8Enc
int max_keyframe_distance;
int speed;
int threads;
enum vpx_enc_pass multipass_mode;
gchar *multipass_cache_file;
GByteArray *first_pass_cache_content;
vpx_fixed_buf_t last_pass_cache_content;
/* state */
@ -117,6 +121,8 @@ enum
#define DEFAULT_MAX_KEYFRAME_DISTANCE 60
#define DEFAULT_SPEED 0
#define DEFAULT_THREADS 1
#define DEFAULT_MULTIPASS_MODE VPX_RC_ONE_PASS
#define DEFAULT_MULTIPASS_CACHE_FILE NULL
enum
{
@ -128,7 +134,9 @@ enum
PROP_MAX_LATENCY,
PROP_MAX_KEYFRAME_DISTANCE,
PROP_SPEED,
PROP_THREADS
PROP_THREADS,
PROP_MULTIPASS_MODE,
PROP_MULTIPASS_CACHE_FILE
};
#define GST_VP8_ENC_MODE_TYPE (gst_vp8_enc_mode_get_type())
@ -153,6 +161,29 @@ gst_vp8_enc_mode_get_type (void)
return id;
}
#define GST_VP8_ENC_MULTIPASS_MODE_TYPE (gst_vp8_enc_multipass_mode_get_type())
static GType
gst_vp8_enc_multipass_mode_get_type (void)
{
static const GEnumValue values[] = {
{VPX_RC_ONE_PASS, "One pass encoding (default)", "one-pass"},
{VPX_RC_FIRST_PASS, "First pass of multipass encoding", "first-pass"},
{VPX_RC_LAST_PASS, "Last pass of multipass encoding", "last-pass"},
{0, NULL, NULL}
};
static volatile GType id = 0;
if (g_once_init_enter ((gsize *) & id)) {
GType _id;
_id = g_enum_register_static ("GstVP8EncMultipassMode", values);
g_once_init_leave ((gsize *) & id, _id);
}
return id;
}
static void gst_vp8_enc_finalize (GObject * object);
static void gst_vp8_enc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
@ -294,6 +325,18 @@ gst_vp8_enc_class_init (GstVP8EncClass * klass)
1, 64, DEFAULT_THREADS,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (gobject_class, PROP_MULTIPASS_MODE,
g_param_spec_enum ("multipass-mode", "Multipass Mode",
"Multipass encode mode",
GST_VP8_ENC_MULTIPASS_MODE_TYPE, DEFAULT_MULTIPASS_MODE,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (gobject_class, PROP_MULTIPASS_CACHE_FILE,
g_param_spec_string ("multipass-cache-file", "Multipass Cache File",
"Multipass cache file",
DEFAULT_MULTIPASS_CACHE_FILE,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
GST_DEBUG_CATEGORY_INIT (gst_vp8enc_debug, "vp8enc", 0, "VP8 Encoder");
}
@ -309,6 +352,8 @@ gst_vp8_enc_init (GstVP8Enc * gst_vp8_enc, GstVP8EncClass * klass)
gst_vp8_enc->error_resilient = DEFAULT_ERROR_RESILIENT;
gst_vp8_enc->max_latency = DEFAULT_MAX_LATENCY;
gst_vp8_enc->max_keyframe_distance = DEFAULT_MAX_KEYFRAME_DISTANCE;
gst_vp8_enc->multipass_mode = DEFAULT_MULTIPASS_MODE;
gst_vp8_enc->multipass_cache_file = DEFAULT_MULTIPASS_CACHE_FILE;
/* FIXME: Add sink/src event vmethods */
gst_vp8_enc->base_sink_event_func =
@ -327,6 +372,9 @@ gst_vp8_enc_finalize (GObject * object)
g_return_if_fail (GST_IS_GST_VP8_ENC (object));
gst_vp8_enc = GST_VP8_ENC (object);
g_free (gst_vp8_enc->multipass_cache_file);
gst_vp8_enc->multipass_cache_file = NULL;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
@ -366,6 +414,14 @@ gst_vp8_enc_set_property (GObject * object, guint prop_id,
case PROP_THREADS:
gst_vp8_enc->threads = g_value_get_int (value);
break;
case PROP_MULTIPASS_MODE:
gst_vp8_enc->multipass_mode = g_value_get_enum (value);
break;
case PROP_MULTIPASS_CACHE_FILE:
if (gst_vp8_enc->multipass_cache_file)
g_free (gst_vp8_enc->multipass_cache_file);
gst_vp8_enc->multipass_cache_file = g_value_dup_string (value);
break;
default:
break;
}
@ -405,6 +461,12 @@ gst_vp8_enc_get_property (GObject * object, guint prop_id, GValue * value,
case PROP_THREADS:
g_value_set_int (value, gst_vp8_enc->threads);
break;
case PROP_MULTIPASS_MODE:
g_value_set_enum (value, gst_vp8_enc->multipass_mode);
break;
case PROP_MULTIPASS_CACHE_FILE:
g_value_set_string (value, gst_vp8_enc->multipass_cache_file);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -437,6 +499,17 @@ gst_vp8_enc_stop (GstBaseVideoEncoder * base_video_encoder)
encoder->inited = FALSE;
}
if (encoder->first_pass_cache_content) {
g_byte_array_free (encoder->first_pass_cache_content, TRUE);
encoder->first_pass_cache_content = NULL;
}
if (encoder->last_pass_cache_content.buf) {
g_free (encoder->last_pass_cache_content.buf);
encoder->last_pass_cache_content.buf = NULL;
encoder->last_pass_cache_content.sz = 0;
}
gst_tag_setter_reset_tags (GST_TAG_SETTER (encoder));
return TRUE;
@ -564,7 +637,24 @@ gst_vp8_enc_finish (GstBaseVideoEncoder * base_video_encoder)
GST_DEBUG_OBJECT (encoder, "packet %u type %d", (guint) pkt->data.frame.sz,
pkt->kind);
if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) {
if (pkt->kind == VPX_CODEC_STATS_PKT
&& encoder->multipass_mode == VPX_RC_FIRST_PASS) {
GST_LOG_OBJECT (encoder, "handling STATS packet");
g_byte_array_append (encoder->first_pass_cache_content,
pkt->data.twopass_stats.buf, pkt->data.twopass_stats.sz);
frame = gst_base_video_encoder_get_oldest_frame (base_video_encoder);
if (frame != NULL) {
buffer = gst_buffer_new ();
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_PREROLL);
frame->src_buffer = buffer;
gst_base_video_encoder_finish_frame (base_video_encoder, frame);
}
pkt = vpx_codec_get_cx_data (&encoder->encoder, &iter);
continue;
} else if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) {
GST_LOG_OBJECT (encoder, "non frame pkt: %d", pkt->kind);
pkt = vpx_codec_get_cx_data (&encoder->encoder, &iter);
continue;
@ -596,6 +686,19 @@ gst_vp8_enc_finish (GstBaseVideoEncoder * base_video_encoder)
pkt = vpx_codec_get_cx_data (&encoder->encoder, &iter);
}
if (encoder->multipass_mode == VPX_RC_FIRST_PASS
&& encoder->multipass_cache_file) {
GError *err = NULL;
if (!g_file_set_contents (encoder->multipass_cache_file,
(const gchar *) encoder->first_pass_cache_content->data,
encoder->first_pass_cache_content->len, &err)) {
GST_ELEMENT_ERROR (encoder, RESOURCE, WRITE, (NULL),
("Failed to write multipass cache file: %s", err->message));
g_error_free (err);
}
}
return TRUE;
}
@ -683,7 +786,6 @@ gst_vp8_enc_handle_frame (GstBaseVideoEncoder * base_video_encoder,
cfg.g_timebase.den = base_video_encoder->state.fps_n;
cfg.g_error_resilient = encoder->error_resilient;
cfg.g_pass = VPX_RC_ONE_PASS;
cfg.g_lag_in_frames = encoder->max_latency;
cfg.g_threads = encoder->threads;
cfg.rc_end_usage = encoder->mode;
@ -699,6 +801,31 @@ gst_vp8_enc_handle_frame (GstBaseVideoEncoder * base_video_encoder,
cfg.kf_min_dist = 0;
cfg.kf_max_dist = encoder->max_keyframe_distance;
cfg.g_pass = encoder->multipass_mode;
if (encoder->multipass_mode == VPX_RC_FIRST_PASS) {
encoder->first_pass_cache_content = g_byte_array_sized_new (4096);
} else if (encoder->multipass_mode == VPX_RC_LAST_PASS) {
GError *err = NULL;
if (!encoder->multipass_cache_file) {
GST_ELEMENT_ERROR (encoder, RESOURCE, OPEN_READ,
("No multipass cache file provided"), (NULL));
return GST_FLOW_ERROR;
}
if (!g_file_get_contents (encoder->multipass_cache_file,
(gchar **) & encoder->last_pass_cache_content.buf,
&encoder->last_pass_cache_content.sz, &err)) {
GST_ELEMENT_ERROR (encoder, RESOURCE, OPEN_READ,
("Failed to read multipass cache file provided"), ("%s",
err->message));
g_error_free (err);
return GST_FLOW_ERROR;
}
cfg.rc_twopass_stats_in = encoder->last_pass_cache_content;
}
status = vpx_codec_enc_init (&encoder->encoder, &vpx_codec_vp8_cx_algo,
&cfg, 0);
if (status != VPX_CODEC_OK) {
@ -750,7 +877,24 @@ gst_vp8_enc_handle_frame (GstBaseVideoEncoder * base_video_encoder,
GST_DEBUG_OBJECT (encoder, "packet %u type %d", (guint) pkt->data.frame.sz,
pkt->kind);
if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) {
if (pkt->kind == VPX_CODEC_STATS_PKT
&& encoder->multipass_mode == VPX_RC_FIRST_PASS) {
GST_LOG_OBJECT (encoder, "handling STATS packet");
g_byte_array_append (encoder->first_pass_cache_content,
pkt->data.twopass_stats.buf, pkt->data.twopass_stats.sz);
frame = gst_base_video_encoder_get_oldest_frame (base_video_encoder);
if (frame != NULL) {
buffer = gst_buffer_new ();
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_PREROLL);
frame->src_buffer = buffer;
gst_base_video_encoder_finish_frame (base_video_encoder, frame);
}
pkt = vpx_codec_get_cx_data (&encoder->encoder, &iter);
continue;
} else if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) {
GST_LOG_OBJECT (encoder, "non frame pkt: %d", pkt->kind);
pkt = vpx_codec_get_cx_data (&encoder->encoder, &iter);
continue;
@ -813,45 +957,48 @@ gst_vp8_enc_shape_output (GstBaseVideoEncoder * base_video_encoder,
state = gst_base_video_encoder_get_state (base_video_encoder);
for (inv_count = 0, l = hook->invisible; l; inv_count++, l = l->next) {
buf = l->data;
buf = frame->src_buffer;
frame->src_buffer = NULL;
if (l == hook->invisible && frame->is_sync_point) {
if (hook) {
for (inv_count = 0, l = hook->invisible; l; inv_count++, l = l->next) {
buf = l->data;
if (l == hook->invisible && frame->is_sync_point) {
GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
encoder->keyframe_distance = 0;
} else {
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
encoder->keyframe_distance++;
}
GST_BUFFER_TIMESTAMP (buf) = gst_video_state_get_timestamp (state,
&base_video_encoder->segment, frame->presentation_frame_number);
GST_BUFFER_DURATION (buf) = 0;
GST_BUFFER_OFFSET_END (buf) =
_to_granulepos (frame->presentation_frame_number + 1,
inv_count, encoder->keyframe_distance);
GST_BUFFER_OFFSET (buf) =
gst_util_uint64_scale (frame->presentation_frame_number + 1,
GST_SECOND * state->fps_d, state->fps_n);
gst_buffer_set_caps (buf, base_video_encoder->caps);
ret =
gst_pad_push (GST_BASE_VIDEO_CODEC_SRC_PAD (base_video_encoder), buf);
if (ret != GST_FLOW_OK) {
GST_WARNING_OBJECT (encoder, "flow error %d", ret);
goto done;
}
}
if (!hook->invisible && frame->is_sync_point) {
GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
encoder->keyframe_distance = 0;
} else {
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
encoder->keyframe_distance++;
}
GST_BUFFER_TIMESTAMP (buf) = gst_video_state_get_timestamp (state,
&base_video_encoder->segment, frame->presentation_frame_number);
GST_BUFFER_DURATION (buf) = 0;
GST_BUFFER_OFFSET_END (buf) =
_to_granulepos (frame->presentation_frame_number + 1,
inv_count, encoder->keyframe_distance);
GST_BUFFER_OFFSET (buf) =
gst_util_uint64_scale (frame->presentation_frame_number + 1,
GST_SECOND * state->fps_d, state->fps_n);
gst_buffer_set_caps (buf, base_video_encoder->caps);
ret = gst_pad_push (GST_BASE_VIDEO_CODEC_SRC_PAD (base_video_encoder), buf);
if (ret != GST_FLOW_OK) {
GST_WARNING_OBJECT (encoder, "flow error %d", ret);
goto done;
}
}
buf = frame->src_buffer;
frame->src_buffer = NULL;
if (!hook->invisible && frame->is_sync_point) {
GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
encoder->keyframe_distance = 0;
} else {
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
encoder->keyframe_distance++;
}
GST_BUFFER_TIMESTAMP (buf) = gst_video_state_get_timestamp (state,
@ -874,10 +1021,12 @@ gst_vp8_enc_shape_output (GstBaseVideoEncoder * base_video_encoder,
}
done:
g_list_foreach (hook->invisible, (GFunc) gst_mini_object_unref, NULL);
g_list_free (hook->invisible);
g_slice_free (GstVP8EncCoderHook, hook);
frame->coder_hook = NULL;
if (hook) {
g_list_foreach (hook->invisible, (GFunc) gst_mini_object_unref, NULL);
g_list_free (hook->invisible);
g_slice_free (GstVP8EncCoderHook, hook);
frame->coder_hook = NULL;
}
return ret;
}