gstreamer/ext/schroedinger/gstschroenc.c
2012-05-28 16:42:08 +02:00

656 lines
20 KiB
C

/* Schrodinger
* Copyright (C) 2006 David Schleef <ds@schleef.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include <gst/video/video.h>
#include <gst/video/gstvideoencoder.h>
#include <gst/video/gstvideoutils.h>
#include <string.h>
#include <schroedinger/schro.h>
#include <schroedinger/schrobitstream.h>
#include <schroedinger/schrovirtframe.h>
#include <math.h>
#include "gstschroutils.h"
GST_DEBUG_CATEGORY_EXTERN (schro_debug);
#define GST_CAT_DEFAULT schro_debug
#define GST_TYPE_SCHRO_ENC \
(gst_schro_enc_get_type())
#define GST_SCHRO_ENC(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SCHRO_ENC,GstSchroEnc))
#define GST_SCHRO_ENC_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SCHRO_ENC,GstSchroEncClass))
#define GST_IS_SCHRO_ENC(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SCHRO_ENC))
#define GST_IS_SCHRO_ENC_CLASS(obj) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SCHRO_ENC))
typedef struct _GstSchroEnc GstSchroEnc;
typedef struct _GstSchroEncClass GstSchroEncClass;
struct _GstSchroEnc
{
GstVideoEncoder base_encoder;
GstPad *sinkpad;
GstPad *srcpad;
/* state */
SchroEncoder *encoder;
SchroVideoFormat *video_format;
guint64 last_granulepos;
guint64 granule_offset;
GstVideoCodecState *input_state;
};
struct _GstSchroEncClass
{
GstVideoEncoderClass parent_class;
};
GType gst_schro_enc_get_type (void);
enum
{
LAST_SIGNAL
};
enum
{
ARG_0
};
static void gst_schro_enc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_schro_enc_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static GstFlowReturn gst_schro_enc_process (GstSchroEnc * schro_enc);
static gboolean gst_schro_enc_set_format (GstVideoEncoder *
base_video_encoder, GstVideoCodecState * state);
static gboolean gst_schro_enc_start (GstVideoEncoder * base_video_encoder);
static gboolean gst_schro_enc_stop (GstVideoEncoder * base_video_encoder);
static GstFlowReturn gst_schro_enc_finish (GstVideoEncoder *
base_video_encoder);
static GstFlowReturn gst_schro_enc_handle_frame (GstVideoEncoder *
base_video_encoder, GstVideoCodecFrame * frame);
static GstFlowReturn gst_schro_enc_pre_push (GstVideoEncoder *
base_video_encoder, GstVideoCodecFrame * frame);
static void gst_schro_enc_finalize (GObject * object);
static GstStaticPadTemplate gst_schro_enc_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (GST_SCHRO_YUV_LIST))
);
static GstStaticPadTemplate gst_schro_enc_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-dirac;video/x-qt-part;video/x-mp4-part")
);
#define parent_class gst_schro_enc_parent_class
G_DEFINE_TYPE (GstSchroEnc, gst_schro_enc, GST_TYPE_VIDEO_ENCODER);
static GType
register_enum_list (const SchroEncoderSetting * setting)
{
GType type;
static GEnumValue *enumtypes;
int n;
char *typename;
int i;
n = setting->max + 1;
enumtypes = g_malloc0 ((n + 1) * sizeof (GEnumValue));
for (i = 0; i < n; i++) {
enumtypes[i].value = i;
enumtypes[i].value_name = setting->enum_list[i];
enumtypes[i].value_nick = setting->enum_list[i];
}
typename = g_strdup_printf ("SchroEncoderSettingEnum_%s", setting->name);
type = g_enum_register_static (typename, enumtypes);
g_free (typename);
return type;
}
static void
gst_schro_enc_class_init (GstSchroEncClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *element_class;
GstVideoEncoderClass *basevideocoder_class;
int i;
gobject_class = G_OBJECT_CLASS (klass);
element_class = GST_ELEMENT_CLASS (klass);
basevideocoder_class = GST_VIDEO_ENCODER_CLASS (klass);
gobject_class->set_property = gst_schro_enc_set_property;
gobject_class->get_property = gst_schro_enc_get_property;
gobject_class->finalize = gst_schro_enc_finalize;
for (i = 0; i < schro_encoder_get_n_settings (); i++) {
const SchroEncoderSetting *setting;
setting = schro_encoder_get_setting_info (i);
switch (setting->type) {
case SCHRO_ENCODER_SETTING_TYPE_BOOLEAN:
g_object_class_install_property (gobject_class, i + 1,
g_param_spec_boolean (setting->name, setting->name, setting->name,
setting->default_value, G_PARAM_READWRITE));
break;
case SCHRO_ENCODER_SETTING_TYPE_INT:
g_object_class_install_property (gobject_class, i + 1,
g_param_spec_int (setting->name, setting->name, setting->name,
setting->min, setting->max, setting->default_value,
G_PARAM_READWRITE));
break;
case SCHRO_ENCODER_SETTING_TYPE_ENUM:
g_object_class_install_property (gobject_class, i + 1,
g_param_spec_enum (setting->name, setting->name, setting->name,
register_enum_list (setting), setting->default_value,
G_PARAM_READWRITE));
break;
case SCHRO_ENCODER_SETTING_TYPE_DOUBLE:
g_object_class_install_property (gobject_class, i + 1,
g_param_spec_double (setting->name, setting->name, setting->name,
setting->min, setting->max, setting->default_value,
G_PARAM_READWRITE));
break;
default:
break;
}
}
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&gst_schro_enc_src_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&gst_schro_enc_sink_template));
gst_element_class_set_details_simple (element_class, "Dirac Encoder",
"Codec/Encoder/Video",
"Encode raw video into Dirac stream", "David Schleef <ds@schleef.org>");
basevideocoder_class->set_format =
GST_DEBUG_FUNCPTR (gst_schro_enc_set_format);
basevideocoder_class->start = GST_DEBUG_FUNCPTR (gst_schro_enc_start);
basevideocoder_class->stop = GST_DEBUG_FUNCPTR (gst_schro_enc_stop);
basevideocoder_class->finish = GST_DEBUG_FUNCPTR (gst_schro_enc_finish);
basevideocoder_class->handle_frame =
GST_DEBUG_FUNCPTR (gst_schro_enc_handle_frame);
basevideocoder_class->pre_push = GST_DEBUG_FUNCPTR (gst_schro_enc_pre_push);
}
static void
gst_schro_enc_init (GstSchroEnc * schro_enc)
{
GST_DEBUG ("gst_schro_enc_init");
/* Normally, we'd create the encoder in ->start(), but we use the
* encoder to store object properties. So it needs to be created
* here. */
schro_enc->encoder = schro_encoder_new ();
schro_encoder_set_packet_assembly (schro_enc->encoder, TRUE);
schro_enc->video_format = schro_encoder_get_video_format (schro_enc->encoder);
}
static void
gst_schro_enc_finalize (GObject * object)
{
GstSchroEnc *schro_enc = GST_SCHRO_ENC (object);
if (schro_enc->encoder) {
schro_encoder_free (schro_enc->encoder);
schro_enc->encoder = NULL;
}
if (schro_enc->video_format) {
g_free (schro_enc->video_format);
schro_enc->video_format = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gst_schro_enc_set_format (GstVideoEncoder * base_video_encoder,
GstVideoCodecState * state)
{
GstSchroEnc *schro_enc = GST_SCHRO_ENC (base_video_encoder);
GstBuffer *seq_header_buffer;
GstVideoInfo *info = &state->info;
GstVideoCodecState *output_state;
GstClockTime latency;
GST_DEBUG ("set_output_caps");
schro_video_format_set_std_video_format (schro_enc->video_format,
SCHRO_VIDEO_FORMAT_CUSTOM);
switch (GST_VIDEO_INFO_FORMAT (info)) {
case GST_VIDEO_FORMAT_I420:
case GST_VIDEO_FORMAT_YV12:
#if SCHRO_CHECK_VERSION(1,0,11)
case GST_VIDEO_FORMAT_Y42B:
#endif
schro_enc->video_format->chroma_format = SCHRO_CHROMA_420;
break;
case GST_VIDEO_FORMAT_YUY2:
case GST_VIDEO_FORMAT_UYVY:
#if SCHRO_CHECK_VERSION(1,0,11)
case GST_VIDEO_FORMAT_v216:
case GST_VIDEO_FORMAT_v210:
#endif
schro_enc->video_format->chroma_format = SCHRO_CHROMA_422;
break;
case GST_VIDEO_FORMAT_AYUV:
#if SCHRO_CHECK_VERSION(1,0,12)
case GST_VIDEO_FORMAT_ARGB:
#endif
#if SCHRO_CHECK_VERSION(1,0,11)
case GST_VIDEO_FORMAT_Y444:
case GST_VIDEO_FORMAT_AYUV64:
#endif
schro_enc->video_format->chroma_format = SCHRO_CHROMA_444;
break;
default:
g_assert_not_reached ();
}
schro_enc->video_format->frame_rate_numerator = GST_VIDEO_INFO_FPS_N (info);
schro_enc->video_format->frame_rate_denominator = GST_VIDEO_INFO_FPS_D (info);
schro_enc->video_format->width = GST_VIDEO_INFO_WIDTH (info);
schro_enc->video_format->height = GST_VIDEO_INFO_HEIGHT (info);
schro_enc->video_format->clean_width = GST_VIDEO_INFO_WIDTH (info);
schro_enc->video_format->clean_height = GST_VIDEO_INFO_HEIGHT (info);
schro_enc->video_format->left_offset = 0;
schro_enc->video_format->top_offset = 0;
schro_enc->video_format->aspect_ratio_numerator = GST_VIDEO_INFO_PAR_N (info);
schro_enc->video_format->aspect_ratio_denominator =
GST_VIDEO_INFO_PAR_D (info);
switch (GST_VIDEO_INFO_FORMAT (&state->info)) {
default:
schro_video_format_set_std_signal_range (schro_enc->video_format,
SCHRO_SIGNAL_RANGE_8BIT_VIDEO);
break;
#if SCHRO_CHECK_VERSION(1,0,11)
case GST_VIDEO_FORMAT_v210:
schro_video_format_set_std_signal_range (schro_enc->video_format,
SCHRO_SIGNAL_RANGE_10BIT_VIDEO);
break;
case GST_VIDEO_FORMAT_v216:
case GST_VIDEO_FORMAT_AYUV64:
schro_enc->video_format->luma_offset = 64 << 8;
schro_enc->video_format->luma_excursion = 219 << 8;
schro_enc->video_format->chroma_offset = 128 << 8;
schro_enc->video_format->chroma_excursion = 224 << 8;
break;
#endif
#if SCHRO_CHECK_VERSION(1,0,12)
case GST_VIDEO_FORMAT_ARGB:
schro_enc->video_format->luma_offset = 256;
schro_enc->video_format->luma_excursion = 511;
schro_enc->video_format->chroma_offset = 256;
schro_enc->video_format->chroma_excursion = 511;
break;
#endif
}
/* Finally set latency */
latency = gst_util_uint64_scale (GST_SECOND,
GST_VIDEO_INFO_FPS_D (info) *
(int) schro_encoder_setting_get_double (schro_enc->encoder,
"queue_depth"), GST_VIDEO_INFO_FPS_N (info));
gst_video_encoder_set_latency (base_video_encoder, latency, latency);
schro_video_format_set_std_colour_spec (schro_enc->video_format,
SCHRO_COLOUR_SPEC_HDTV);
schro_encoder_set_video_format (schro_enc->encoder, schro_enc->video_format);
schro_encoder_start (schro_enc->encoder);
seq_header_buffer =
gst_schro_wrap_schro_buffer (schro_encoder_encode_sequence_header
(schro_enc->encoder));
schro_enc->granule_offset = ~0;
output_state =
gst_video_encoder_set_output_state (base_video_encoder,
gst_caps_new_empty_simple ("video/x-dirac"), state);
GST_BUFFER_FLAG_SET (seq_header_buffer, GST_BUFFER_FLAG_HEADER);
{
GValue array = { 0 };
GValue value = { 0 };
guint8 *outdata;
GstBuffer *buf;
GstMemory *seq_header_memory, *extra_header;
gsize size;
g_value_init (&array, GST_TYPE_ARRAY);
g_value_init (&value, GST_TYPE_BUFFER);
buf = gst_buffer_new ();
/* Add the sequence header */
seq_header_memory = gst_buffer_get_memory (seq_header_buffer, 0);
gst_buffer_append_memory (buf, seq_header_memory);
size = gst_buffer_get_size (buf) + SCHRO_PARSE_HEADER_SIZE;
outdata = g_malloc0 (SCHRO_PARSE_HEADER_SIZE);
GST_WRITE_UINT32_BE (outdata, 0x42424344);
GST_WRITE_UINT8 (outdata + 4, SCHRO_PARSE_CODE_END_OF_SEQUENCE);
GST_WRITE_UINT32_BE (outdata + 5, 0);
GST_WRITE_UINT32_BE (outdata + 9, size);
extra_header = gst_memory_new_wrapped (0, outdata, SCHRO_PARSE_HEADER_SIZE,
0, SCHRO_PARSE_HEADER_SIZE, outdata, g_free);
gst_buffer_append_memory (buf, extra_header);
/* ogg(mux) expects the header buffers to have 0 timestamps -
set OFFSET and OFFSET_END accordingly */
GST_BUFFER_OFFSET (buf) = 0;
GST_BUFFER_OFFSET_END (buf) = 0;
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);
gst_value_set_buffer (&value, buf);
gst_buffer_unref (buf);
gst_value_array_append_value (&array, &value);
gst_structure_set_value (gst_caps_get_structure (output_state->caps, 0),
"streamheader", &array);
g_value_unset (&value);
g_value_unset (&array);
}
gst_buffer_unref (seq_header_buffer);
gst_video_codec_state_unref (output_state);
/* And save the input state for later use */
if (schro_enc->input_state)
gst_video_codec_state_unref (schro_enc->input_state);
schro_enc->input_state = gst_video_codec_state_ref (state);
return TRUE;
}
static void
gst_schro_enc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstSchroEnc *src;
g_return_if_fail (GST_IS_SCHRO_ENC (object));
src = GST_SCHRO_ENC (object);
GST_DEBUG ("gst_schro_enc_set_property");
if (prop_id >= 1) {
const SchroEncoderSetting *setting;
setting = schro_encoder_get_setting_info (prop_id - 1);
switch (G_VALUE_TYPE (value)) {
case G_TYPE_DOUBLE:
schro_encoder_setting_set_double (src->encoder, setting->name,
g_value_get_double (value));
break;
case G_TYPE_INT:
schro_encoder_setting_set_double (src->encoder, setting->name,
g_value_get_int (value));
break;
case G_TYPE_BOOLEAN:
schro_encoder_setting_set_double (src->encoder, setting->name,
g_value_get_boolean (value));
break;
default:
schro_encoder_setting_set_double (src->encoder, setting->name,
g_value_get_enum (value));
break;
}
}
}
static void
gst_schro_enc_get_property (GObject * object, guint prop_id, GValue * value,
GParamSpec * pspec)
{
GstSchroEnc *src;
g_return_if_fail (GST_IS_SCHRO_ENC (object));
src = GST_SCHRO_ENC (object);
if (prop_id >= 1) {
const SchroEncoderSetting *setting;
setting = schro_encoder_get_setting_info (prop_id - 1);
switch (G_VALUE_TYPE (value)) {
case G_TYPE_DOUBLE:
g_value_set_double (value,
schro_encoder_setting_get_double (src->encoder, setting->name));
break;
case G_TYPE_INT:
g_value_set_int (value,
schro_encoder_setting_get_double (src->encoder, setting->name));
break;
case G_TYPE_BOOLEAN:
g_value_set_boolean (value,
schro_encoder_setting_get_double (src->encoder, setting->name));
break;
default:
/* it's an enum */
g_value_set_enum (value,
schro_encoder_setting_get_double (src->encoder, setting->name));
break;
}
}
}
static gboolean
gst_schro_enc_start (GstVideoEncoder * base_video_encoder)
{
return TRUE;
}
static gboolean
gst_schro_enc_stop (GstVideoEncoder * base_video_encoder)
{
return TRUE;
}
static GstFlowReturn
gst_schro_enc_finish (GstVideoEncoder * base_video_encoder)
{
GstSchroEnc *schro_enc = GST_SCHRO_ENC (base_video_encoder);
GST_DEBUG ("finish");
schro_encoder_end_of_stream (schro_enc->encoder);
gst_schro_enc_process (schro_enc);
return GST_FLOW_OK;
}
static GstFlowReturn
gst_schro_enc_handle_frame (GstVideoEncoder * base_video_encoder,
GstVideoCodecFrame * frame)
{
GstSchroEnc *schro_enc = GST_SCHRO_ENC (base_video_encoder);
SchroFrame *schro_frame;
GstFlowReturn ret;
GstVideoInfo *info = &schro_enc->input_state->info;
if (schro_enc->granule_offset == ~0LL) {
schro_enc->granule_offset =
gst_util_uint64_scale (frame->pts, 2 * GST_VIDEO_INFO_FPS_N (info),
GST_SECOND * GST_VIDEO_INFO_FPS_D (info));
GST_DEBUG ("granule offset %" G_GINT64_FORMAT, schro_enc->granule_offset);
}
/* FIXME : We could make that method just take GstVideoInfo ... */
schro_frame = gst_schro_buffer_wrap (gst_buffer_ref (frame->input_buffer),
GST_VIDEO_INFO_FORMAT (info),
GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info));
GST_DEBUG ("pushing frame %p", frame);
schro_encoder_push_frame_full (schro_enc->encoder, schro_frame, frame);
ret = gst_schro_enc_process (schro_enc);
return ret;
}
static GstFlowReturn
gst_schro_enc_pre_push (GstVideoEncoder * base_video_encoder,
GstVideoCodecFrame * frame)
{
GstSchroEnc *schro_enc;
int delay;
int dist;
int pt;
int dt;
guint64 granulepos_hi;
guint64 granulepos_low;
GstBuffer *buf = frame->output_buffer;
schro_enc = GST_SCHRO_ENC (base_video_encoder);
pt = frame->presentation_frame_number * 2 + schro_enc->granule_offset;
dt = frame->decode_frame_number * 2 + schro_enc->granule_offset;
delay = pt - dt;
dist = frame->distance_from_sync;
GST_DEBUG ("sys %d dpn %d pt %d dt %d delay %d dist %d",
(int) frame->system_frame_number,
(int) frame->decode_frame_number, pt, dt, delay, dist);
granulepos_hi = (((guint64) pt - delay) << 9) | ((dist >> 8));
granulepos_low = (delay << 9) | (dist & 0xff);
GST_DEBUG ("granulepos %" G_GINT64_FORMAT ":%" G_GINT64_FORMAT, granulepos_hi,
granulepos_low);
#if 0
if (frame->is_eos) {
GST_BUFFER_OFFSET_END (buf) = schro_enc->last_granulepos;
} else {
#endif
schro_enc->last_granulepos = (granulepos_hi << 22) | (granulepos_low);
GST_BUFFER_OFFSET_END (buf) = schro_enc->last_granulepos;
#if 0
}
#endif
return GST_FLOW_OK;
}
static GstFlowReturn
gst_schro_enc_process (GstSchroEnc * schro_enc)
{
SchroBuffer *encoded_buffer;
GstVideoCodecFrame *frame;
GstFlowReturn ret;
int presentation_frame;
void *voidptr;
GstVideoEncoder *base_video_encoder = GST_VIDEO_ENCODER (schro_enc);
GST_DEBUG ("process");
while (1) {
switch (schro_encoder_wait (schro_enc->encoder)) {
case SCHRO_STATE_NEED_FRAME:
return GST_FLOW_OK;
case SCHRO_STATE_END_OF_STREAM:
GST_DEBUG ("EOS");
return GST_FLOW_OK;
case SCHRO_STATE_HAVE_BUFFER:
voidptr = NULL;
encoded_buffer = schro_encoder_pull_full (schro_enc->encoder,
&presentation_frame, &voidptr);
frame = voidptr;
if (encoded_buffer == NULL) {
GST_DEBUG ("encoder_pull returned NULL");
/* FIXME This shouldn't happen */
return GST_FLOW_ERROR;
}
#if SCHRO_CHECK_VERSION (1, 0, 9)
{
GstMessage *message;
GstStructure *structure;
GstBuffer *buf;
gpointer data;
data = g_malloc (sizeof (double) * 21);
schro_encoder_get_frame_stats (schro_enc->encoder,
(double *) data, 21);
buf = gst_buffer_new_wrapped (data, sizeof (double) * 21);
structure = gst_structure_new ("GstSchroEnc",
"frame-stats", GST_TYPE_BUFFER, buf, NULL);
gst_buffer_unref (buf);
message = gst_message_new_element (GST_OBJECT (schro_enc), structure);
gst_element_post_message (GST_ELEMENT (schro_enc), message);
}
#endif
if (voidptr == NULL) {
GST_DEBUG ("got eos");
//frame = schro_enc->eos_frame;
frame = NULL;
schro_buffer_unref (encoded_buffer);
}
/* FIXME: Get the frame from somewhere somehow... */
if (frame) {
if (SCHRO_PARSE_CODE_IS_SEQ_HEADER (encoded_buffer->data[4])) {
GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
}
frame->output_buffer = gst_schro_wrap_schro_buffer (encoded_buffer);
ret = gst_video_encoder_finish_frame (base_video_encoder, frame);
if (ret != GST_FLOW_OK) {
GST_DEBUG ("pad_push returned %d", ret);
return ret;
}
}
break;
case SCHRO_STATE_AGAIN:
break;
}
}
return GST_FLOW_OK;
}