summaryrefslogtreecommitdiff
path: root/ext/vp8
diff options
context:
space:
mode:
authorSebastian Dröge <sebastian.droege@collabora.co.uk>2010-06-12 09:02:29 +0200
committerSebastian Dröge <sebastian.droege@collabora.co.uk>2010-06-15 11:51:41 +0200
commit351bb1bbb675d855ee735f2f63da0c0f14866777 (patch)
tree4b1cdaa126e38fa0f74d9a2a8455ea420635d0b6 /ext/vp8
parenta60af008d5871e5d65333e7f991e3fc708b852a1 (diff)
vp8enc: Implement multipass encoding
Fixes bug #621348.
Diffstat (limited to 'ext/vp8')
-rw-r--r--ext/vp8/gstvp8enc.c229
1 files changed, 189 insertions, 40 deletions
diff --git a/ext/vp8/gstvp8enc.c b/ext/vp8/gstvp8enc.c
index 07318c482..42af0d0a6 100644
--- a/ext/vp8/gstvp8enc.c
+++ b/ext/vp8/gstvp8enc.c
@@ -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 (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 (l == hook->invisible && frame->is_sync_point) {
+ 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;
}