Import GstTranscoder

This commit is contained in:
Saunier Thibault 2015-12-03 12:32:05 +01:00 committed by Thibault Saunier
parent 87311d404e
commit 7a66b16d97
36 changed files with 4262 additions and 1142 deletions

22
data/meson.build Normal file
View file

@ -0,0 +1,22 @@
encoding_targetsdir = join_paths(get_option('datadir'),
'gstreamer-' + api_version, 'encoding-profiles')
encoding_targets = [
['file-extension', ['targets/file-extension/ogv.gep',
'targets/file-extension/oga.gep',
'targets/file-extension/mkv.gep',
'targets/file-extension/mp3.gep',
'targets/file-extension/webm.gep',
'targets/file-extension/flv.gep',
'targets/file-extension/mp4.gep',
'targets/file-extension/avi.gep',],
],
['online-services', ['targets/online-service/youtube.gep',]],
['device', ['targets/device/dvd.gep',]],
]
foreach path_targets : encoding_targets
dir = join_paths(encoding_targetsdir, path_targets.get(0))
etargets = path_targets.get(1)
install_data(sources: etargets, install_dir: dir)
endforeach

View file

@ -0,0 +1,24 @@
[GStreamer Encoding Target]
name=dvd
category=device
description=Encoding target suitable for DVDs
[profile-dvd]
name=dvd
type=container
description[c]=This is an encoding profile usable for DVDs
format=video/mpeg, mpegversion=(int)2, systemstream=(boolean)true
[streamprofile-dvd-0]
parent=dvd
type=video
format=video/mpeg, mpegversion=(int)2, systemstream=(boolean)false
presence=0
pass=0
variableframerate=false
[streamprofile-dvd-1]
parent=dvd
type=audio
format=audio/mpeg, mpegversion=(int)1, layer=(int)2
presence=0

View file

@ -0,0 +1,21 @@
[GStreamer Encoding Target]
name=avi
category=file-extension
description=Default target for files with a .avi extension
[profile-default]
name=default
type=container
description=Default profile for files with a .avi extension.
format=video/x-msvideo
[streamprofile-default-0]
parent=default
type=audio
format=audio/mpeg, mpegversion=(int)1, mpegaudioversion=(int)1, layer=(int)3
[streamprofile-default-1]
parent=default
type=video
format=video/mpeg, mpegversion=(int)4

View file

@ -0,0 +1,32 @@
[GStreamer Encoding Target]
name=flv
category=file-extension
description=Default target for files with a .flv extension
[profile-default]
name=default
type=container
description=Default profile for files with a .flv extension.
format=video/x-flv
[streamprofile-default-0]
parent=default
type=audio
format=audio/mpeg,mpegversion=4
[streamprofile-default-1]
parent=default
type=audio
format=audio/mpeg, mpegversion=(int)1, mpegaudioversion=(int)1, layer=(int)3
[streamprofile-default-2]
parent=default
type=video
format=video/x-h264
preset=Profile YouTube
pass=0
[streamprofile-default-3]
parent=default
type=video
format=video/x-h264

View file

@ -0,0 +1,28 @@
[GStreamer Encoding Target]
name=mkv;matroska;
category=file-extension
description=Default target for files with a .mkv extension
[profile-default]
name=default
description=Default profile for files with a .mkv extension. Audio stream can be either opus (default) or vorbis depending on what is available on the system. Video stream will be either in vp8 (default) or vp9.
type=container
format=video/x-matroska
[streamprofile-default-0]
parent=default
type=audio
format=audio/x-vorbis;audio/x-opus
[streamprofile-default-1]
parent=default
type=video
format=video/x-h264
preset=Quality Normal
pass=0
[streamprofile-default-2]
parent=default
type=video
format=video/x-h264
pass=0

View file

@ -0,0 +1,15 @@
[GStreamer Encoding Target]
name=mp3
category=file-extension
description=Default target for files with a .mp3 extension
[profile-default]
name=default
type=container
description=Default profile for files with a .mp3 extension.
format=application/x-id3
[streamprofile-default-0]
parent=default
type=audio
format=audio/mpeg, mpegversion=(int)1, mpegaudioversion=(int)1, layer=(int)3

View file

@ -0,0 +1,34 @@
[GStreamer Encoding Target]
name=mp4;mov
category=file-extension
description=Default target for files with a .mp4 and .mov extension
[profile-default]
name=default
type=container
description=Default profile for files with a .mp4 extension. Suitable for uploading to youtube.
format=video/quicktime
preset=Profile YouTube
[streamprofile-default-0]
parent=default
type=audio
format=audio/mpeg,mpegversion=4,base-profile=lc,rate={48000,96000},channels=2;audio/mpeg,mpegversion=4,base-profile=lc,rate={48000,96000}
restriction=audio/x-raw,channels=6,channel-mask=0x3f;audio/x-raw,channels=2
[streamprofile-default-1]
parent=default
type=video
format=video/x-h264
preset=Profile YouTube
pass=0
[streamprofile-default-2]
parent=default
type=audio
format=audio/mpeg,mpegversion=4
[streamprofile-default-3]
parent=default
type=video
format=video/x-h264

View file

@ -0,0 +1,15 @@
[GStreamer Encoding Target]
name=oga
category=file-extension
description=Default target for files with a .ogg and friends extension
[profile-default]
name=default
description=Default target for files with a .ogg and friends extension
type=container
format=audio/ogg
[streamprofile-default-0]
parent=default
type=audio
format=audio/x-vorbis;audio/x-opus

View file

@ -0,0 +1,25 @@
[GStreamer Encoding Target]
name=ogv;ogg
category=file-extension
description=Default target for files with a .ogg and friends extension
[profile-default]
name=default
description=Default target for files with a .ogg and friends extension
type=container
format=application/ogg
[streamprofile-default-0]
parent=default
type=audio
format=audio/x-vorbis;audio/x-opus
[streamprofile-default-1]
parent=default
type=video
format=video/x-theora
[streamprofile-default-2]
parent=default
type=video
format=video/x-vp8

View file

@ -0,0 +1,21 @@
[GStreamer Encoding Target]
name=webm
category=file-extension
description=Default target for files with a .webm extension
[profile-default]
name=default
description=Default profile for files with a .webm extension. Audio stream can be either vorbis (default) or opus depending on what is available on the system. Video stream will be either in vp8 (default) or vp9.
type=container
format=video/webm
[streamprofile-default-0]
parent=default
type=audio
format=audio/x-vorbis;audio/x-opus
[streamprofile-default-1]
parent=default
type=video
format=video/x-vp8;video/x-vp9
pass=0

View file

@ -0,0 +1,24 @@
[GStreamer Encoding Target]
name=youtube;yt
category=online-service
description=Recommended encoding settings for YouTube
[profile-default]
name=default
type=container
description=Youtube recommended profile with automatic audio setting
format=video/quicktime
preset=Profile YouTube
[streamprofile-default-0]
parent=default
type=audio
format=audio/mpeg,mpegversion=4,base-profile=lc
restriction=audio/x-raw,channels=6,rate={48000,96000};audio/x-raw,channels=2,rate={48000,96000}
[streamprofile-default-1]
parent=default
type=video
format=video/x-h264,profile=high
preset=Profile YouTube
pass=0

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
# GstTranscoder
High level API to transcode streams
This library should be linked to by getting cflags and libs from
gstreamer-transcoder-{{ gst_api_version.md }}
> NOTE: This library API is considered *unstable*

View file

@ -0,0 +1 @@
gi-index

View file

@ -103,6 +103,7 @@ if build_gir
{'name': 'adaptivedemux', 'lib': gstadaptivedemux_dep},
{'name': 'webrtc', 'gir': webrtc_gir, 'lib': gstwebrtc_dep, 'suffix': 'lib'},
{'name': 'audio', 'gir': audio_gir, 'lib': gstbadaudio_dep, 'prefix': 'bad-'},
{'name': 'transcoder', 'gir': transcoder_gir, 'lib': gst_transcoder_dep},
]
endif

View file

@ -11,6 +11,7 @@ subdir('mpegts')
subdir('opencv')
subdir('player')
subdir('sctp')
subdir('transcoder')
subdir('vulkan')
subdir('wayland')
subdir('webrtc')

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,141 @@
#ifndef __GST_TRANSCODER_H
#define __GST_TRANSCODER_H
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include "transcoder-prelude.h"
G_BEGIN_DECLS
typedef struct _GstTranscoderSignalDispatcher GstTranscoderSignalDispatcher;
typedef struct _GstTranscoderSignalDispatcherInterface GstTranscoderSignalDispatcherInterface;
/*********** Error definitions ************/
#define GST_TRANSCODER_ERROR (gst_transcoder_error_quark ())
#define GST_TYPE_TRANSCODER_ERROR (gst_transcoder_error_get_type ())
/**
* GstTranscoderError:
* @GST_TRANSCODER_ERROR_FAILED: generic error.
*/
typedef enum {
GST_TRANSCODER_ERROR_FAILED = 0
} GstTranscoderError;
GST_TRANSCODER_API
GQuark gst_transcoder_error_quark (void);
GST_TRANSCODER_API
GType gst_transcoder_error_get_type (void);
GST_TRANSCODER_API
const gchar * gst_transcoder_error_get_name (GstTranscoderError error);
/*********** GstTranscoder definition ************/
#define GST_TYPE_TRANSCODER (gst_transcoder_get_type ())
#define GST_TRANSCODER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TRANSCODER, GstTranscoder))
#define GST_TRANSCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_TRANSCODER, GstTranscoderClass))
#define GST_IS_TRANSCODER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_TRANSCODER))
#define GST_IS_TRANSCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_TRANSCODER))
#define GST_TRANSCODER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_TRANSCODER, GstTranscoderClass))
typedef struct _GstTranscoder GstTranscoder;
typedef struct _GstTranscoderClass GstTranscoderClass;
typedef struct _GstTranscoderPrivate GstTranscoderPrivate;
GST_TRANSCODER_API
GType gst_transcoder_get_type (void);
GST_TRANSCODER_API
GstTranscoder * gst_transcoder_new (const gchar * source_uri,
const gchar * dest_uri,
const gchar * encoding_profile);
GST_TRANSCODER_API
GstTranscoder * gst_transcoder_new_full (const gchar * source_uri,
const gchar * dest_uri,
GstEncodingProfile *profile,
GstTranscoderSignalDispatcher *signal_dispatcher);
GST_TRANSCODER_API
gboolean gst_transcoder_run (GstTranscoder *self,
GError ** error);
GST_TRANSCODER_API
void gst_transcoder_set_cpu_usage (GstTranscoder *self,
gint cpu_usage);
GST_TRANSCODER_API
void gst_transcoder_run_async (GstTranscoder *self);
GST_TRANSCODER_API
void gst_transcoder_set_position_update_interval (GstTranscoder *self,
guint interval);
GST_TRANSCODER_API
gchar * gst_transcoder_get_source_uri (GstTranscoder * self);
GST_TRANSCODER_API
gchar * gst_transcoder_get_dest_uri (GstTranscoder * self);
GST_TRANSCODER_API
guint gst_transcoder_get_position_update_interval (GstTranscoder *self);
GST_TRANSCODER_API
GstClockTime gst_transcoder_get_position (GstTranscoder * self);
GST_TRANSCODER_API
GstClockTime gst_transcoder_get_duration (GstTranscoder * self);
GST_TRANSCODER_API
GstElement * gst_transcoder_get_pipeline (GstTranscoder * self);
GST_TRANSCODER_API
gboolean gst_transcoder_get_avoid_reencoding (GstTranscoder * self);
GST_TRANSCODER_API
void gst_transcoder_set_avoid_reencoding (GstTranscoder * self,
gboolean avoid_reencoding);
/****************** Signal dispatcher *******************************/
#define GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER (gst_transcoder_signal_dispatcher_get_type ())
#define GST_TRANSCODER_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER, GstTranscoderSignalDispatcher))
#define GST_IS_TRANSCODER_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER))
#define GST_TRANSCODER_SIGNAL_DISPATCHER_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_TRANSCODER_SIGNAL_DISPATCHER, GstTranscoderSignalDispatcherInterface))
struct _GstTranscoderSignalDispatcherInterface {
GTypeInterface parent_iface;
void (*dispatch) (GstTranscoderSignalDispatcher * self,
GstTranscoder * transcoder,
void (*emitter) (gpointer data),
gpointer data,
GDestroyNotify destroy);
};
typedef struct _GstTranscoderGMainContextSignalDispatcher GstTranscoderGMainContextSignalDispatcher;
typedef struct _GstTranscoderGMainContextSignalDispatcherClass GstTranscoderGMainContextSignalDispatcherClass;
GST_TRANSCODER_API
GType gst_transcoder_signal_dispatcher_get_type (void);
#define GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER (gst_transcoder_g_main_context_signal_dispatcher_get_type ())
#define GST_IS_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER))
#define GST_IS_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER))
#define GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstTranscoderGMainContextSignalDispatcherClass))
#define GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstTranscoderGMainContextSignalDispatcher))
#define GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER, GstTranscoderGMainContextSignalDispatcherClass))
#define GST_TRANSCODER_G_MAIN_CONTEXT_SIGNAL_DISPATCHER_CAST(obj) ((GstTranscoderGMainContextSignalDispatcher*)(obj))
GST_TRANSCODER_API
GType gst_transcoder_g_main_context_signal_dispatcher_get_type (void);
GST_TRANSCODER_API
GstTranscoderSignalDispatcher * gst_transcoder_g_main_context_signal_dispatcher_new (GMainContext * application_context);
G_END_DECLS
#endif

View file

@ -0,0 +1,33 @@
sources = files(['gsttranscoder.c'])
headers = files(['gsttranscoder.h', 'transcoder-prelude.h'])
install_headers(headers, subdir : 'gstreamer-' + api_version + '/gst/transcoder')
gst_transcoder = library('gsttranscoder-' + api_version,
sources,
install: true,
include_directories : [configinc, libsinc],
dependencies: [gst_dep, gstpbutils_dep],
c_args: gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API', '-DBUILDING_GST_TRANSCODER'],
soversion : soversion,
)
if build_gir
transcoder_gir = gnome.generate_gir(gst_transcoder,
sources : sources + headers,
nsversion : api_version,
namespace : 'GstTranscoder',
identifier_prefix : 'Gst',
symbol_prefix : 'gst_',
includes : ['GObject-2.0',
'Gst-' + api_version,
'GstPbutils-' + api_version],
dependencies: [gst_dep, gstpbutils_dep],
install : true,
extra_args : ['--add-init-section=extern gboolean gst_init(gint *argc, gchar **argv); gst_init(NULL,NULL);']
)
endif
gst_transcoder_dep = declare_dependency(link_with: gst_transcoder,
dependencies : [gst_dep, gstpbutils_dep],
include_directories : [libsinc]
)

View file

@ -0,0 +1,36 @@
/* GStreamer Transcoder Library
*
* Copyright (C) 2019 Thibault Saunier <tsaunier@igalia.com>
*
* transcoder-prelude.h: prelude include header for the gst-transcoder library
*
* 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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __GST_TRANSCODER_PRELUDE_H__
#define __GST_TRANSCODER_PRELUDE_H__
#include <gst/gst.h>
#ifndef GST_TRANSCODER_API
# ifdef BUILDING_GST_TRANSCODER
# define GST_TRANSCODER_API GST_API_EXPORT /* from config.h */
# else
# define GST_TRANSCODER_API GST_API_IMPORT
# endif
#endif
#endif /* __GST_TRANSCODER_PRELUDE_H__ */

View file

@ -9,7 +9,7 @@ foreach plugin : ['accurip', 'adpcmdec', 'adpcmenc', 'aiff', 'asfmux',
'midi', 'mpegdemux', 'mpegpsmux', 'mpegtsdemux', 'mpegtsmux',
'mxf', 'netsim', 'onvif', 'pcapparse', 'pnm', 'proxy',
'rawparse', 'removesilence', 'rist', 'rtp', 'sdp', 'segmentclip',
'siren', 'smooth', 'speed', 'subenc', 'timecode',
'siren', 'smooth', 'speed', 'subenc', 'timecode', 'transcode',
'videofilters', 'videoframe_audiolevel', 'videoparsers',
'videosignal', 'vmnc', 'y4m', 'yadif']
if not get_option(plugin).disabled()

View file

@ -0,0 +1,220 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_GETRUSAGE
#include "gst-cpu-throttling-clock.h"
#include <unistd.h>
#include <sys/resource.h>
#include "gst-cpu-throttling-clock.h"
/**
* SECTION: gst-cpu-throttling-clock
* @title: GstCpuThrottlingClock
* @short_description: TODO
*
* TODO
*/
/* *INDENT-OFF* */
GST_DEBUG_CATEGORY_STATIC (gst_cpu_throttling_clock_debug);
#define GST_CAT_DEFAULT gst_cpu_throttling_clock_debug
struct _GstCpuThrottlingClockPrivate
{
guint wanted_cpu_usage;
GstClock *sclock;
GstClockTime current_wait_time;
GstPoll *timer;
struct rusage last_usage;
GstClockID evaluate_wait_time;
GstClockTime time_between_evals;
};
#define parent_class gst_cpu_throttling_clock_parent_class
G_DEFINE_TYPE_WITH_CODE (GstCpuThrottlingClock, gst_cpu_throttling_clock, GST_TYPE_CLOCK, G_ADD_PRIVATE(GstCpuThrottlingClock))
enum
{
PROP_FIRST,
PROP_CPU_USAGE,
PROP_LAST
};
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
/* *INDENT-ON* */
static void
gst_cpu_throttling_clock_get_property (GObject * object,
guint property_id, GValue * value, GParamSpec * pspec)
{
GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (object);
switch (property_id) {
case PROP_CPU_USAGE:
g_value_set_uint (value, self->priv->wanted_cpu_usage);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gst_cpu_throttling_clock_set_property (GObject * object,
guint property_id, const GValue * value, GParamSpec * pspec)
{
GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (object);
switch (property_id) {
case PROP_CPU_USAGE:
self->priv->wanted_cpu_usage = g_value_get_uint (value);
if (self->priv->wanted_cpu_usage == 0)
self->priv->wanted_cpu_usage = 100;
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static gboolean
gst_transcoder_adjust_wait_time (GstClock * sync_clock, GstClockTime time,
GstClockID id, GstCpuThrottlingClock * self)
{
struct rusage ru;
float delta_usage, usage, coef;
GstCpuThrottlingClockPrivate *priv = self->priv;
getrusage (RUSAGE_SELF, &ru);
delta_usage = GST_TIMEVAL_TO_TIME (ru.ru_utime) -
GST_TIMEVAL_TO_TIME (self->priv->last_usage.ru_utime);
usage =
((float) delta_usage / self->priv->time_between_evals * 100) /
g_get_num_processors ();
self->priv->last_usage = ru;
coef = GST_MSECOND / 10;
if (usage < (gfloat) priv->wanted_cpu_usage) {
coef = -coef;
}
priv->current_wait_time = CLAMP (0,
(GstClockTime) priv->current_wait_time + coef, GST_SECOND);
GST_DEBUG_OBJECT (self,
"Avg is %f (wanted %d) => %" GST_TIME_FORMAT, usage,
self->priv->wanted_cpu_usage, GST_TIME_ARGS (priv->current_wait_time));
return TRUE;
}
static GstClockReturn
_wait (GstClock * clock, GstClockEntry * entry, GstClockTimeDiff * jitter)
{
GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (clock);
if (!self->priv->evaluate_wait_time) {
if (!(self->priv->sclock)) {
GST_ERROR_OBJECT (clock, "Could not find any system clock"
" to start the wait time evaluation task");
} else {
self->priv->evaluate_wait_time =
gst_clock_new_periodic_id (self->priv->sclock,
gst_clock_get_time (self->priv->sclock),
self->priv->time_between_evals);
gst_clock_id_wait_async (self->priv->evaluate_wait_time,
(GstClockCallback) gst_transcoder_adjust_wait_time,
(gpointer) self, NULL);
}
}
if (G_UNLIKELY (GST_CLOCK_ENTRY_STATUS (entry) == GST_CLOCK_UNSCHEDULED))
return GST_CLOCK_UNSCHEDULED;
if (gst_poll_wait (self->priv->timer, self->priv->current_wait_time)) {
GST_INFO_OBJECT (self, "Something happened on the poll");
}
return GST_CLOCK_ENTRY_STATUS (entry);
}
static GstClockTime
_get_internal_time (GstClock * clock)
{
GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (clock);
return gst_clock_get_internal_time (self->priv->sclock);
}
static void
gst_cpu_throttling_clock_dispose (GObject * object)
{
GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (object);
if (self->priv->evaluate_wait_time) {
gst_clock_id_unschedule (self->priv->evaluate_wait_time);
gst_clock_id_unref (self->priv->evaluate_wait_time);
self->priv->evaluate_wait_time = 0;
}
}
static void
gst_cpu_throttling_clock_class_init (GstCpuThrottlingClockClass * klass)
{
GObjectClass *oclass = G_OBJECT_CLASS (klass);
GstClockClass *clock_klass = GST_CLOCK_CLASS (klass);
GST_DEBUG_CATEGORY_INIT (gst_cpu_throttling_clock_debug, "cpuclock", 0,
"UriTranscodebin element");
oclass->get_property = gst_cpu_throttling_clock_get_property;
oclass->set_property = gst_cpu_throttling_clock_set_property;
oclass->dispose = gst_cpu_throttling_clock_dispose;
/**
* GstCpuThrottlingClock:cpu-usage:
*
* Since: UNRELEASED
*/
param_specs[PROP_CPU_USAGE] = g_param_spec_uint ("cpu-usage", "cpu-usage",
"The percentage of CPU to try to use with the processus running the "
"pipeline driven by the clock", 0, 100,
100, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (oclass, PROP_LAST, param_specs);
clock_klass->wait = GST_DEBUG_FUNCPTR (_wait);
clock_klass->get_internal_time = _get_internal_time;
}
static void
gst_cpu_throttling_clock_init (GstCpuThrottlingClock * self)
{
self->priv = gst_cpu_throttling_clock_get_instance_private (self);
self->priv->current_wait_time = GST_MSECOND;
self->priv->wanted_cpu_usage = 100;
self->priv->timer = gst_poll_new_timer ();
self->priv->time_between_evals = GST_SECOND / 4;
self->priv->sclock = GST_CLOCK (gst_system_clock_obtain ());
getrusage (RUSAGE_SELF, &self->priv->last_usage);
}
GstCpuThrottlingClock *
gst_cpu_throttling_clock_new (guint cpu_usage)
{
return g_object_new (GST_TYPE_CPU_THROTTLING_CLOCK, "cpu-usage",
cpu_usage, NULL);
}
#endif

View file

@ -0,0 +1,60 @@
/*
* gst-cpu-throttling-clock.h
*
* Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
*
* 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 __GST_CPU_THROTTLING_CLOCK_H__
#define __GST_CPU_THROTTLING_CLOCK_H__
#include <glib-object.h>
#include <gst/gst.h>
G_BEGIN_DECLS
typedef struct _GstCpuThrottlingClock GstCpuThrottlingClock;
typedef struct _GstCpuThrottlingClockClass GstCpuThrottlingClockClass;
typedef struct _GstCpuThrottlingClockPrivate GstCpuThrottlingClockPrivate;
GType gst_cpu_throttling_clock_get_type (void) G_GNUC_CONST;
#define GST_TYPE_CPU_THROTTLING_CLOCK (gst_cpu_throttling_clock_get_type ())
#define GST_CPU_THROTTLING_CLOCK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_CPU_THROTTLING_CLOCK, GstCpuThrottlingClock))
#define GST_CPU_THROTTLING_CLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_CPU_THROTTLING_CLOCK, GstCpuThrottlingClockClass))
#define GST_IS_CPU_THROTTLING_CLOCK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_CPU_THROTTLING_CLOCK))
#define GST_IS_CPU_THROTTLING_CLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_CPU_THROTTLING_CLOCK))
#define GST_CPU_THROTTLING_CLOCK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CPU_THROTTLING_CLOCK, GstCpuThrottlingClockClass))
struct _GstCpuThrottlingClockClass
{
/*<private>*/
GstClockClass parent_class;
};
struct _GstCpuThrottlingClock
{
/*<private>*/
GstClock parent;
GstCpuThrottlingClockPrivate *priv;
};
GstCpuThrottlingClock * gst_cpu_throttling_clock_new (guint cpu_usage);
G_END_DECLS
#endif /* #ifndef __GST_CPU_THROTTLING_CLOCK_H__*/

View file

@ -0,0 +1,614 @@
/* GStreamer
* Copyright (C) 2019 Thibault Saunier <tsaunier@igalia.com>
*
* gsttranscodebin.c:
*
* 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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "gsttranscoding.h"
#include <gst/pbutils/pbutils.h>
#include <gst/pbutils/missing-plugins.h>
GST_DEBUG_CATEGORY_STATIC (gst_transcodebin_debug);
#define GST_CAT_DEFAULT gst_transcodebin_debug
static GstStaticPadTemplate transcode_bin_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate transcode_bin_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
typedef struct
{
GstBin parent;
GstElement *decodebin;
GstElement *encodebin;
GstEncodingProfile *profile;
gboolean avoid_reencoding;
GstPad *sinkpad;
GstPad *srcpad;
GstElement *audio_filter;
GstElement *video_filter;
} GstTranscodeBin;
typedef struct
{
GstBinClass parent;
} GstTranscodeBinClass;
/* *INDENT-OFF* */
#define GST_TYPE_TRANSCODE_BIN (gst_transcode_bin_get_type ())
#define GST_TRANSCODE_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TRANSCODE_BIN, GstTranscodeBin))
#define GST_TRANSCODE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TRANSCODE_BIN_TYPE, GstTranscodeBinClass))
#define GST_IS_TRANSCODE_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TRANSCODE_BIN_TYPE))
#define GST_IS_TRANSCODE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TRANSCODE_BIN_TYPE))
#define GST_TRANSCODE_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TRANSCODE_BIN_TYPE, GstTranscodeBinClass))
#define DEFAULT_AVOID_REENCODING FALSE
G_DEFINE_TYPE (GstTranscodeBin, gst_transcode_bin, GST_TYPE_BIN)
enum
{
PROP_0,
PROP_PROFILE,
PROP_AVOID_REENCODING,
PROP_VIDEO_FILTER,
PROP_AUDIO_FILTER,
LAST_PROP
};
static void
post_missing_plugin_error (GstElement * dec, const gchar * element_name)
{
GstMessage *msg;
msg = gst_missing_element_message_new (dec, element_name);
gst_element_post_message (dec, msg);
GST_ELEMENT_ERROR (dec, CORE, MISSING_PLUGIN,
("Missing element '%s' - check your GStreamer installation.",
element_name), (NULL));
}
/* *INDENT-ON* */
static GstPad *
_insert_filter (GstTranscodeBin * self, GstPad * sinkpad, GstPad * pad,
GstCaps * caps)
{
GstPad *filter_src = NULL, *filter_sink = NULL;
GstElement *filter = NULL;
GstObject *filter_parent;
if (self->video_filter &&
!g_strcmp0 (gst_structure_get_name (gst_caps_get_structure (caps, 0)),
"video/x-raw")) {
filter = self->video_filter;
} else if (self->audio_filter &&
!g_strcmp0 (gst_structure_get_name (gst_caps_get_structure (caps, 0)),
"audio/x-raw")) {
filter = self->audio_filter;
}
if (!filter)
return pad;
if ((filter_parent = gst_object_get_parent (GST_OBJECT (filter)))) {
GST_WARNING_OBJECT (self,
"Filter already in use (inside %" GST_PTR_FORMAT ").", filter_parent);
GST_FIXME_OBJECT (self,
"Handle transcoding several streams of a same kind.");
gst_object_unref (filter_parent);
return pad;
}
/* We are guaranteed filters only have 1 unique sinkpad and srcpad */
GST_OBJECT_LOCK (filter);
filter_sink = filter->sinkpads->data;
filter_src = filter->srcpads->data;
GST_OBJECT_UNLOCK (filter);
gst_bin_add (GST_BIN (self), gst_object_ref (filter));
if (G_UNLIKELY (gst_pad_link (pad, filter_sink) != GST_PAD_LINK_OK)) {
GstCaps *othercaps = gst_pad_get_current_caps (sinkpad);
caps = gst_pad_get_current_caps (pad);
GST_ELEMENT_ERROR (self, CORE, PAD,
(NULL),
("Couldn't link pads \n\n%" GST_PTR_FORMAT "\n\n and \n\n %"
GST_PTR_FORMAT "\n\n", caps, othercaps));
gst_caps_unref (caps);
gst_caps_unref (othercaps);
}
gst_element_sync_state_with_parent (filter);
return filter_src;
}
static void
pad_added_cb (GstElement * decodebin, GstPad * pad, GstTranscodeBin * self)
{
GstCaps *caps;
GstPad *sinkpad = NULL;
GstPadLinkReturn lret;
caps = gst_pad_query_caps (pad, NULL);
GST_DEBUG_OBJECT (decodebin, "Pad added, caps: %" GST_PTR_FORMAT, caps);
g_signal_emit_by_name (self->encodebin, "request-pad", caps, &sinkpad);
if (sinkpad == NULL) {
gchar *stream_id = gst_pad_get_stream_id (pad);
GST_ELEMENT_WARNING_WITH_DETAILS (self, STREAM, FORMAT,
(NULL), ("Stream with caps: %" GST_PTR_FORMAT " can not be"
" encoded in the defined encoding formats",
caps),
("can-t-encode-stream", G_TYPE_BOOLEAN, TRUE,
"stream-caps", GST_TYPE_CAPS, caps,
"stream-id", G_TYPE_STRING, stream_id, NULL));
g_free (stream_id);
return;
}
if (caps)
gst_caps_unref (caps);
pad = _insert_filter (self, sinkpad, pad, caps);
lret = gst_pad_link (pad, sinkpad);
switch (lret) {
case GST_PAD_LINK_OK:
break;
case GST_PAD_LINK_WAS_LINKED:
GST_FIXME_OBJECT (self, "Pad %" GST_PTR_FORMAT " was already linked",
sinkpad);
break;
default:
{
GstCaps *othercaps = gst_pad_query_caps (sinkpad, NULL);
caps = gst_pad_get_current_caps (pad);
GST_ELEMENT_ERROR_WITH_DETAILS (self, CORE, PAD,
(NULL),
("Couldn't link pads:\n %" GST_PTR_FORMAT ": %" GST_PTR_FORMAT
"\nand:\n"
" %" GST_PTR_FORMAT ": %" GST_PTR_FORMAT "\n\n",
pad, caps, sinkpad, othercaps),
("linking-error", GST_TYPE_PAD_LINK_RETURN, lret,
"source-pad", GST_TYPE_PAD, pad,
"source-caps", GST_TYPE_CAPS, caps,
"sink-pad", GST_TYPE_PAD, sinkpad,
"sink-caps", GST_TYPE_CAPS, othercaps, NULL));
gst_caps_unref (caps);
if (othercaps)
gst_caps_unref (othercaps);
}
}
gst_object_unref (sinkpad);
}
static gboolean
make_encodebin (GstTranscodeBin * self)
{
GstPad *pad;
GST_INFO_OBJECT (self, "making new encodebin");
if (!self->profile)
goto no_profile;
self->encodebin = gst_element_factory_make ("encodebin", NULL);
if (!self->encodebin)
goto no_encodebin;
gst_bin_add (GST_BIN (self), self->encodebin);
g_object_set (self->encodebin, "profile", self->profile, NULL);
pad = gst_element_get_static_pad (self->encodebin, "src");
if (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), pad)) {
gst_object_unref (pad);
GST_ERROR_OBJECT (self, "Could not ghost %" GST_PTR_FORMAT " srcpad",
self->encodebin);
return FALSE;
}
gst_object_unref (pad);
return gst_element_sync_state_with_parent (self->encodebin);
/* ERRORS */
no_encodebin:
{
post_missing_plugin_error (GST_ELEMENT_CAST (self), "encodebin");
GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL),
("No encodebin element, check your installation"));
return FALSE;
}
/* ERRORS */
no_profile:
{
GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL),
("No GstEncodingProfile set, can not run."));
return FALSE;
}
}
static gboolean
make_decodebin (GstTranscodeBin * self)
{
GstPad *pad;
GST_INFO_OBJECT (self, "making new decodebin");
self->decodebin = gst_element_factory_make ("decodebin", NULL);
if (!self->decodebin)
goto no_decodebin;
if (self->avoid_reencoding) {
GstCaps *decodecaps;
g_object_get (self->decodebin, "caps", &decodecaps, NULL);
if (GST_IS_ENCODING_CONTAINER_PROFILE (self->profile)) {
GList *tmp;
decodecaps = gst_caps_make_writable (decodecaps);
for (tmp = (GList *)
gst_encoding_container_profile_get_profiles
(GST_ENCODING_CONTAINER_PROFILE (self->profile)); tmp;
tmp = tmp->next) {
GstCaps *encodecaps = gst_encoding_profile_get_format (tmp->data);
GstCaps *restrictions =
gst_encoding_profile_get_restriction (tmp->data);
if (!restrictions)
gst_caps_append (decodecaps, encodecaps);
else
gst_caps_unref (restrictions);
}
}
g_object_set (self->decodebin, "caps", decodecaps, NULL);
gst_caps_unref (decodecaps);
}
g_signal_connect (self->decodebin, "pad-added", G_CALLBACK (pad_added_cb),
self);
gst_bin_add (GST_BIN (self), self->decodebin);
pad = gst_element_get_static_pad (self->decodebin, "sink");
if (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), pad)) {
gst_object_unref (pad);
GST_ERROR_OBJECT (self, "Could not ghost %" GST_PTR_FORMAT " sinkpad",
self->decodebin);
return FALSE;
}
gst_object_unref (pad);
return TRUE;
/* ERRORS */
no_decodebin:
{
post_missing_plugin_error (GST_ELEMENT_CAST (self), "decodebin");
GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL),
("No decodebin element, check your installation"));
return FALSE;
}
}
static void
remove_all_children (GstTranscodeBin * self)
{
if (self->encodebin) {
gst_element_set_state (self->encodebin, GST_STATE_NULL);
gst_bin_remove (GST_BIN (self), self->encodebin);
self->encodebin = NULL;
}
if (self->video_filter && GST_OBJECT_PARENT (self->video_filter)) {
gst_element_set_state (self->video_filter, GST_STATE_NULL);
gst_bin_remove (GST_BIN (self), self->video_filter);
}
if (self->audio_filter && GST_OBJECT_PARENT (self->audio_filter)) {
gst_element_set_state (self->audio_filter, GST_STATE_NULL);
gst_bin_remove (GST_BIN (self), self->audio_filter);
}
if (self->decodebin) {
gst_element_set_state (self->decodebin, GST_STATE_NULL);
gst_bin_remove (GST_BIN (self), self->decodebin);
self->decodebin = NULL;
}
}
static GstStateChangeReturn
gst_transcode_bin_change_state (GstElement * element, GstStateChange transition)
{
GstStateChangeReturn ret;
GstTranscodeBin *self = GST_TRANSCODE_BIN (element);
switch (transition) {
case GST_STATE_CHANGE_READY_TO_PAUSED:
if (!make_encodebin (self))
goto setup_failed;
if (!make_decodebin (self))
goto setup_failed;
break;
default:
break;
}
ret =
GST_ELEMENT_CLASS (gst_transcode_bin_parent_class)->change_state (element,
transition);
if (ret == GST_STATE_CHANGE_FAILURE)
goto beach;
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
remove_all_children (self);
break;
default:
break;
}
beach:
return ret;
setup_failed:
remove_all_children (self);
return GST_STATE_CHANGE_FAILURE;
}
static void
gst_transcode_bin_dispose (GObject * object)
{
GstTranscodeBin *self = (GstTranscodeBin *) object;
g_clear_object (&self->video_filter);
g_clear_object (&self->audio_filter);
G_OBJECT_CLASS (gst_transcode_bin_parent_class)->dispose (object);
}
static void
gst_transcode_bin_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec)
{
GstTranscodeBin *self = GST_TRANSCODE_BIN (object);
switch (prop_id) {
case PROP_PROFILE:
GST_OBJECT_LOCK (self);
g_value_set_object (value, self->profile);
GST_OBJECT_UNLOCK (self);
break;
case PROP_AVOID_REENCODING:
GST_OBJECT_LOCK (self);
g_value_set_boolean (value, self->avoid_reencoding);
GST_OBJECT_UNLOCK (self);
break;
case PROP_AUDIO_FILTER:
GST_OBJECT_LOCK (self);
g_value_set_object (value, self->audio_filter);
GST_OBJECT_UNLOCK (self);
break;
case PROP_VIDEO_FILTER:
GST_OBJECT_LOCK (self);
g_value_set_object (value, self->video_filter);
GST_OBJECT_UNLOCK (self);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
_set_filter (GstTranscodeBin * self, GstElement * filter, GstElement ** mfilter)
{
if (filter) {
GST_OBJECT_LOCK (filter);
if (filter->numsinkpads != 1) {
GST_ERROR_OBJECT (self, "Can not use %" GST_PTR_FORMAT
" as filter as it does not have "
" one and only one sinkpad", filter);
goto bail_out;
} else if (filter->numsrcpads != 1) {
GST_ERROR_OBJECT (self, "Can not use %" GST_PTR_FORMAT
" as filter as it does not have " " one and only one srcpad", filter);
goto bail_out;
}
GST_OBJECT_UNLOCK (filter);
}
GST_OBJECT_LOCK (self);
*mfilter = filter;
GST_OBJECT_UNLOCK (self);
return;
bail_out:
GST_OBJECT_UNLOCK (filter);
}
static void
gst_transcode_bin_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec)
{
GstTranscodeBin *self = GST_TRANSCODE_BIN (object);
switch (prop_id) {
case PROP_PROFILE:
GST_OBJECT_LOCK (self);
self->profile = g_value_dup_object (value);
GST_OBJECT_UNLOCK (self);
break;
case PROP_AVOID_REENCODING:
GST_OBJECT_LOCK (self);
self->avoid_reencoding = g_value_get_boolean (value);
GST_OBJECT_UNLOCK (self);
break;
case PROP_AUDIO_FILTER:
_set_filter (self, g_value_dup_object (value), &self->audio_filter);
break;
case PROP_VIDEO_FILTER:
_set_filter (self, g_value_dup_object (value), &self->video_filter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gst_transcode_bin_class_init (GstTranscodeBinClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *gstelement_klass;
object_class->dispose = gst_transcode_bin_dispose;
object_class->get_property = gst_transcode_bin_get_property;
object_class->set_property = gst_transcode_bin_set_property;
gstelement_klass = (GstElementClass *) klass;
gstelement_klass->change_state =
GST_DEBUG_FUNCPTR (gst_transcode_bin_change_state);
gst_element_class_add_pad_template (gstelement_klass,
gst_static_pad_template_get (&transcode_bin_sink_template));
gst_element_class_add_pad_template (gstelement_klass,
gst_static_pad_template_get (&transcode_bin_src_template));
gst_element_class_set_static_metadata (gstelement_klass,
"Transcode Bin", "Generic/Bin/Encoding",
"Autoplug and transcoder a stream",
"Thibault Saunier <tsaunier@igalia.com>");
/**
* GstTranscodeBin:profile:
*
* The #GstEncodingProfile to use. This property must be set before going
* to %GST_STATE_PAUSED or higher.
*/
g_object_class_install_property (object_class, PROP_PROFILE,
g_param_spec_object ("profile", "Profile",
"The GstEncodingProfile to use", GST_TYPE_ENCODING_PROFILE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstTranscodeBin:avoid-reencoding:
*
* See #encodebin:avoid-reencoding
*/
g_object_class_install_property (object_class, PROP_AVOID_REENCODING,
g_param_spec_boolean ("avoid-reencoding", "Avoid re-encoding",
"Whether to re-encode portions of compatible video streams that lay on segment boundaries",
DEFAULT_AVOID_REENCODING,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstTranscodeBin:video-filter:
*
* Set the video filter element/bin to use.
*/
g_object_class_install_property (object_class, PROP_VIDEO_FILTER,
g_param_spec_object ("video-filter", "Video filter",
"the video filter(s) to apply, if possible",
GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstTranscodeBin:audio-filter:
*
* Set the audio filter element/bin to use.
*/
g_object_class_install_property (object_class, PROP_AUDIO_FILTER,
g_param_spec_object ("audio-filter", "Audio filter",
"the audio filter(s) to apply, if possible",
GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
gst_transcode_bin_init (GstTranscodeBin * self)
{
GstPadTemplate *pad_tmpl;
pad_tmpl = gst_static_pad_template_get (&transcode_bin_sink_template);
self->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", pad_tmpl);
gst_pad_set_active (self->sinkpad, TRUE);
gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
gst_object_unref (pad_tmpl);
pad_tmpl = gst_static_pad_template_get (&transcode_bin_src_template);
self->srcpad = gst_ghost_pad_new_no_target_from_template ("src", pad_tmpl);
gst_pad_set_active (self->srcpad, TRUE);
gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
gst_object_unref (pad_tmpl);
}
static gboolean
plugin_init (GstPlugin * plugin)
{
gboolean res = TRUE;
gst_pb_utils_init ();
GST_DEBUG_CATEGORY_INIT (gst_transcodebin_debug, "transcodebin", 0,
"Transcodebin element");
res &= gst_element_register (plugin, "transcodebin", GST_RANK_NONE,
GST_TYPE_TRANSCODE_BIN);
res &= gst_element_register (plugin, "uritranscodebin", GST_RANK_NONE,
gst_uri_transcode_bin_get_type ());
return res;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
transcode,
"A plugin containing elements for transcoding", plugin_init, VERSION,
GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)

View file

@ -0,0 +1,31 @@
/* GStreamer
* Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
*
* gsttranscodebin.c:
*
* 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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __GST_TRANSCODING_H__
#define __GST_TRANSCODING_H__
#include <gst/gst.h>
GType gst_transcode_bin_get_type (void);
GType gst_uri_transcode_bin_get_type (void);
#endif /* __GST_TRANSCODING_H__ */

View file

@ -0,0 +1,562 @@
/* GStreamer
* Copyright (C) 2019 Thibault Saunier <tsaunier@igalia.com>
*
* gsturitranscodebin.c:
*
* 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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "gsttranscoding.h"
#if HAVE_GETRUSAGE
#include "gst-cpu-throttling-clock.h"
#endif
#include <gst/pbutils/pbutils.h>
#include <gst/pbutils/missing-plugins.h>
GST_DEBUG_CATEGORY_STATIC (gst_uri_transcodebin_debug);
#define GST_CAT_DEFAULT gst_uri_transcodebin_debug
typedef struct
{
GstPipeline parent;
GstElement *src;
gchar *source_uri;
GstElement *transcodebin;
GstElement *audio_filter;
GstElement *video_filter;
GstEncodingProfile *profile;
gboolean avoid_reencoding;
guint wanted_cpu_usage;
GstElement *sink;
gchar *dest_uri;
GstClock *cpu_clock;
} GstUriTranscodeBin;
typedef struct
{
GstPipelineClass parent;
} GstUriTranscodeBinClass;
/* *INDENT-OFF* */
#define parent_class gst_uri_transcode_bin_parent_class
#define GST_TYPE_URI_TRANSCODE_BIN (gst_uri_transcode_bin_get_type ())
#define GST_URI_TRANSCODE_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_URI_TRANSCODE_BIN, GstUriTranscodeBin))
#define GST_URI_TRANSCODE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_URI_TRANSCODE_BIN_TYPE, GstUriTranscodeBinClass))
#define GST_IS_TRANSCODE_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_URI_TRANSCODE_BIN_TYPE))
#define GST_IS_TRANSCODE_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_URI_TRANSCODE_BIN_TYPE))
#define GST_URI_TRANSCODE_BIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_URI_TRANSCODE_BIN_TYPE, GstUriTranscodeBinClass))
#define DEFAULT_AVOID_REENCODING FALSE
G_DEFINE_TYPE (GstUriTranscodeBin, gst_uri_transcode_bin, GST_TYPE_PIPELINE)
enum
{
PROP_0,
PROP_PROFILE,
PROP_SOURCE_URI,
PROP_DEST_URI,
PROP_AVOID_REENCODING,
PROP_SINK,
PROP_SRC,
PROP_CPU_USAGE,
PROP_VIDEO_FILTER,
PROP_AUDIO_FILTER,
LAST_PROP
};
static void
post_missing_plugin_error (GstElement * dec, const gchar * element_name)
{
GstMessage *msg;
msg = gst_missing_element_message_new (dec, element_name);
gst_element_post_message (dec, msg);
GST_ELEMENT_ERROR (dec, CORE, MISSING_PLUGIN,
("Missing element '%s' - check your GStreamer installation.",
element_name), (NULL));
}
/* *INDENT-ON* */
static gboolean
make_transcodebin (GstUriTranscodeBin * self)
{
GST_INFO_OBJECT (self, "making new transcodebin");
self->transcodebin = gst_element_factory_make ("transcodebin", NULL);
if (!self->transcodebin)
goto no_decodebin;
g_object_set (self->transcodebin, "profile", self->profile,
"video-filter", self->video_filter,
"audio-filter", self->audio_filter,
"avoid-reencoding", self->avoid_reencoding, NULL);
gst_bin_add (GST_BIN (self), self->transcodebin);
if (!gst_element_link (self->transcodebin, self->sink))
return FALSE;
return TRUE;
/* ERRORS */
no_decodebin:
{
post_missing_plugin_error (GST_ELEMENT_CAST (self), "transcodebin");
GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL),
("No transcodebin element, check your installation"));
return FALSE;
}
}
static gboolean
make_dest (GstUriTranscodeBin * self)
{
GError *err = NULL;
if (!gst_uri_is_valid (self->dest_uri))
goto invalid_uri;
self->sink = gst_element_make_from_uri (GST_URI_SINK, self->dest_uri,
"sink", &err);
if (!self->sink)
goto no_sink;
gst_bin_add (GST_BIN (self), self->sink);
g_object_set (self->sink, "sync", TRUE, "max-lateness", GST_CLOCK_TIME_NONE,
NULL);
return TRUE;
invalid_uri:
{
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("Invalid URI \"%s\".", self->dest_uri), (NULL));
g_clear_error (&err);
return FALSE;
}
no_sink:
{
/* whoops, could not create the source element, dig a little deeper to
* figure out what might be wrong. */
if (err != NULL && err->code == GST_URI_ERROR_UNSUPPORTED_PROTOCOL) {
gchar *prot;
prot = gst_uri_get_protocol (self->dest_uri);
if (prot == NULL)
goto invalid_uri;
gst_element_post_message (GST_ELEMENT_CAST (self),
gst_missing_uri_source_message_new (GST_ELEMENT (self), prot));
GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN,
("No URI handler implemented for \"%s\".", prot), (NULL));
g_free (prot);
} else {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("%s", (err) ? err->message : "URI was not accepted by any element"),
("No element accepted URI '%s'", self->dest_uri));
}
g_clear_error (&err);
return FALSE;
}
}
static gboolean
make_source (GstUriTranscodeBin * self)
{
GError *err = NULL;
if (!gst_uri_is_valid (self->source_uri))
goto invalid_uri;
self->src = gst_element_make_from_uri (GST_URI_SRC, self->source_uri,
"src", &err);
if (!self->src)
goto no_sink;
gst_bin_add (GST_BIN (self), self->src);
if (!gst_element_link (self->src, self->transcodebin))
return FALSE;
return TRUE;
invalid_uri:
{
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("Invalid URI \"%s\".", self->source_uri), (NULL));
g_clear_error (&err);
return FALSE;
}
no_sink:
{
/* whoops, could not create the source element, dig a little deeper to
* figure out what might be wrong. */
if (err != NULL && err->code == GST_URI_ERROR_UNSUPPORTED_PROTOCOL) {
gchar *prot;
prot = gst_uri_get_protocol (self->source_uri);
if (prot == NULL)
goto invalid_uri;
gst_element_post_message (GST_ELEMENT_CAST (self),
gst_missing_uri_source_message_new (GST_ELEMENT (self), prot));
GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN,
("No URI handler implemented for \"%s\".", prot), (NULL));
g_free (prot);
} else {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("%s", (err) ? err->message : "URI was not accepted by any element"),
("No element accepted URI '%s'", self->dest_uri));
}
g_clear_error (&err);
return FALSE;
}
}
static void
remove_all_children (GstUriTranscodeBin * self)
{
if (self->sink) {
gst_element_set_state (self->sink, GST_STATE_NULL);
gst_bin_remove (GST_BIN (self), self->sink);
self->sink = NULL;
}
if (self->transcodebin) {
gst_element_set_state (self->transcodebin, GST_STATE_NULL);
gst_bin_remove (GST_BIN (self), self->transcodebin);
self->transcodebin = NULL;
}
if (self->src) {
gst_element_set_state (self->src, GST_STATE_NULL);
gst_bin_remove (GST_BIN (self), self->src);
self->src = NULL;
}
}
static GstStateChangeReturn
gst_uri_transcode_bin_change_state (GstElement * element,
GstStateChange transition)
{
GstStateChangeReturn ret;
GstUriTranscodeBin *self = GST_URI_TRANSCODE_BIN (element);
switch (transition) {
case GST_STATE_CHANGE_READY_TO_PAUSED:
if (!make_dest (self))
goto setup_failed;
if (!make_transcodebin (self))
goto setup_failed;
if (!make_source (self))
goto setup_failed;
if (gst_element_set_state (self->sink,
GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
GST_ERROR_OBJECT (self,
"Could not set %" GST_PTR_FORMAT " state to PAUSED", self->sink);
goto setup_failed;
}
if (gst_element_set_state (self->transcodebin,
GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
GST_ERROR_OBJECT (self,
"Could not set %" GST_PTR_FORMAT " state to PAUSED",
self->transcodebin);
goto setup_failed;
}
if (gst_element_set_state (self->src,
GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
GST_ERROR_OBJECT (self,
"Could not set %" GST_PTR_FORMAT " state to PAUSED", self->src);
goto setup_failed;
}
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
if (ret == GST_STATE_CHANGE_FAILURE)
goto beach;
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
remove_all_children (self);
break;
default:
break;
}
beach:
return ret;
setup_failed:
remove_all_children (self);
return GST_STATE_CHANGE_FAILURE;
}
static void
gst_uri_transcode_bin_constructed (GObject * object)
{
#if HAVE_GETRUSAGE
GstUriTranscodeBin *self = GST_URI_TRANSCODE_BIN (object);
self->cpu_clock =
GST_CLOCK (gst_cpu_throttling_clock_new (self->wanted_cpu_usage));
gst_pipeline_use_clock (GST_PIPELINE (self), self->cpu_clock);
#endif
((GObjectClass *) parent_class)->constructed (object);
}
static void
gst_uri_transcode_bin_dispose (GObject * object)
{
GstUriTranscodeBin *self = (GstUriTranscodeBin *) object;
g_clear_object (&self->video_filter);
g_clear_object (&self->audio_filter);
g_clear_object (&self->cpu_clock);
G_OBJECT_CLASS (gst_uri_transcode_bin_parent_class)->dispose (object);
}
static void
gst_uri_transcode_bin_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec)
{
GstUriTranscodeBin *self = GST_URI_TRANSCODE_BIN (object);
switch (prop_id) {
case PROP_PROFILE:
GST_OBJECT_LOCK (self);
g_value_set_object (value, self->profile);
GST_OBJECT_UNLOCK (self);
break;
case PROP_DEST_URI:
GST_OBJECT_LOCK (self);
g_value_set_string (value, self->dest_uri);
GST_OBJECT_UNLOCK (self);
break;
case PROP_SOURCE_URI:
GST_OBJECT_LOCK (self);
g_value_set_string (value, self->source_uri);
GST_OBJECT_UNLOCK (self);
break;
case PROP_AVOID_REENCODING:
GST_OBJECT_LOCK (self);
g_value_set_boolean (value, self->avoid_reencoding);
GST_OBJECT_UNLOCK (self);
break;
case PROP_CPU_USAGE:
GST_OBJECT_LOCK (self);
g_value_set_uint (value, self->wanted_cpu_usage);
GST_OBJECT_UNLOCK (self);
break;
case PROP_VIDEO_FILTER:
GST_OBJECT_LOCK (self);
g_value_set_object (value, self->video_filter);
GST_OBJECT_UNLOCK (self);
break;
case PROP_AUDIO_FILTER:
GST_OBJECT_LOCK (self);
g_value_set_object (value, self->audio_filter);
GST_OBJECT_UNLOCK (self);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gst_uri_transcode_bin_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec)
{
GstUriTranscodeBin *self = GST_URI_TRANSCODE_BIN (object);
switch (prop_id) {
case PROP_PROFILE:
GST_OBJECT_LOCK (self);
self->profile = g_value_dup_object (value);
GST_OBJECT_UNLOCK (self);
break;
case PROP_DEST_URI:
GST_OBJECT_LOCK (self);
g_free (self->dest_uri);
self->dest_uri = g_value_dup_string (value);
GST_OBJECT_UNLOCK (self);
break;
case PROP_SOURCE_URI:
GST_OBJECT_LOCK (self);
g_free (self->source_uri);
self->source_uri = g_value_dup_string (value);
GST_OBJECT_UNLOCK (self);
break;
case PROP_AVOID_REENCODING:
GST_OBJECT_LOCK (self);
self->avoid_reencoding = g_value_get_boolean (value);
GST_OBJECT_UNLOCK (self);
break;
case PROP_CPU_USAGE:
#if HAVE_GETRUSAGE
GST_OBJECT_LOCK (self);
self->wanted_cpu_usage = g_value_get_uint (value);
g_object_set (self->cpu_clock, "cpu-usage", self->wanted_cpu_usage, NULL);
GST_OBJECT_UNLOCK (self);
#else
GST_ERROR_OBJECT (self,
"No CPU usage throttling support for that platform");
#endif
break;
case PROP_AUDIO_FILTER:
GST_OBJECT_LOCK (self);
self->audio_filter = g_value_dup_object (value);
GST_OBJECT_UNLOCK (self);
break;
case PROP_VIDEO_FILTER:
GST_OBJECT_LOCK (self);
self->video_filter = g_value_dup_object (value);
GST_OBJECT_UNLOCK (self);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gst_uri_transcode_bin_class_init (GstUriTranscodeBinClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *gstelement_klass;
object_class->get_property = gst_uri_transcode_bin_get_property;
object_class->set_property = gst_uri_transcode_bin_set_property;
object_class->constructed = gst_uri_transcode_bin_constructed;
object_class->dispose = gst_uri_transcode_bin_dispose;
gstelement_klass = (GstElementClass *) klass;
gstelement_klass->change_state =
GST_DEBUG_FUNCPTR (gst_uri_transcode_bin_change_state);
GST_DEBUG_CATEGORY_INIT (gst_uri_transcodebin_debug, "uritranscodebin", 0,
"UriTranscodebin element");
gst_element_class_set_static_metadata (gstelement_klass,
"URITranscode Bin", "Generic/Bin/Encoding",
"Autoplug and transcoder media from uris",
"Thibault Saunier <tsaunier@igalia.com>");
/**
* GstUriTranscodeBin:profile:
*
* The #GstEncodingProfile to use. This property must be set before going
* to %GST_STATE_PAUSED or higher.
*/
g_object_class_install_property (object_class, PROP_PROFILE,
g_param_spec_object ("profile", "Profile",
"The GstEncodingProfile to use", GST_TYPE_ENCODING_PROFILE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstUriTranscodeBin:source-uri:
*
* The URI of the stream to encode
*/
g_object_class_install_property (object_class, PROP_SOURCE_URI,
g_param_spec_string ("source-uri", "Source URI", "URI to decode",
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstUriTranscodeBin:dest-uri:
*
* The destination URI to which the stream should be encoded.
*/
g_object_class_install_property (object_class, PROP_DEST_URI,
g_param_spec_string ("dest-uri", "URI", "URI to put output stream",
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstUriTranscodeBin:avoid-reencoding:
*
* See #encodebin:avoid-reencoding
*/
g_object_class_install_property (object_class, PROP_AVOID_REENCODING,
g_param_spec_boolean ("avoid-reencoding", "Avoid re-encoding",
"Whether to re-encode portions of compatible video streams that lay on segment boundaries",
DEFAULT_AVOID_REENCODING,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_CPU_USAGE,
g_param_spec_uint ("cpu-usage", "cpu-usage",
"The percentage of CPU to try to use with the processus running the "
"pipeline driven by the clock", 0, 100,
100, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstUriTranscodeBin:video-filter:
*
* Set the video filter element/bin to use.
*/
g_object_class_install_property (object_class, PROP_VIDEO_FILTER,
g_param_spec_object ("video-filter", "Video filter",
"the video filter(s) to apply, if possible",
GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstUriTranscodeBin:audio-filter:
*
* Set the audio filter element/bin to use.
*/
g_object_class_install_property (object_class, PROP_AUDIO_FILTER,
g_param_spec_object ("audio-filter", "Audio filter",
"the audio filter(s) to apply, if possible",
GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
gst_uri_transcode_bin_init (GstUriTranscodeBin * self)
{
self->wanted_cpu_usage = 100;
}

13
gst/transcode/meson.build Normal file
View file

@ -0,0 +1,13 @@
gsttranscoder_plugin = library('gsttranscode',
'gsttranscodebin.c',
'gst-cpu-throttling-clock.c',
'gsturitranscodebin.c',
install : true,
dependencies : [gst_dep, gstpbutils_dep],
c_args : gst_plugins_bad_args,
include_directories : [configinc],
install_dir : plugins_install_dir,
)
pkgconfig.generate(gsttranscoder_plugin, install_dir : plugins_pkgconfig_install_dir)
plugins += [gsttranscoder_plugin]

View file

@ -147,10 +147,15 @@ check_functions = [
['HAVE_GMTIME_R', 'gmtime_r'],
['HAVE_MMAP', 'mmap'],
['HAVE_PIPE2', 'pipe2'],
['HAVE_GETRUSAGE', 'getrusage', '#include<sys/resource.h>'],
]
foreach f : check_functions
if cc.has_function(f.get(1))
prefix = ''
if f.length() == 3
prefix = f.get(2)
endif
if cc.has_function(f.get(1), prefix: prefix)
cdata.set(f.get(0), 1)
endif
endforeach
@ -427,6 +432,8 @@ subdir('gst')
subdir('sys')
subdir('ext')
subdir('tests')
subdir('data')
subdir('tools')
subdir('pkgconfig')
# xgettext is optional (on Windows for instance)

View file

@ -143,6 +143,7 @@ option('srt', type : 'feature', value : 'auto', description : 'Secure, Reliable,
option('srtp', type : 'feature', value : 'auto', description : 'Secure RTP codec plugin')
option('teletext', type : 'feature', value : 'auto', description : 'Teletext plugin')
option('tinyalsa', type : 'feature', value : 'auto', description : 'TinyALSA plugin')
option('transcode', type : 'feature', value : 'auto', description : 'Transcode plugin')
option('ttml', type : 'feature', value : 'auto', description : 'TTML subtitle parser and renderer plugin')
option('uvch264', type : 'feature', value : 'auto', description : 'UVC compliant H.264 camera source plugin')
option('voaacenc', type : 'feature', value : 'auto', description : 'AAC audio encoder plugin')

View file

@ -0,0 +1,12 @@
prefix=
exec_prefix=
libdir=@transcoderlibdir@
includedir=@abs_top_srcdir@/gst-libs
Name: GStreamer bad transcoder library, uninstalled
Description: High level API for transcoding using GStreamer, uninstalled
Version: @VERSION@
Requires: gstreamer-@GST_API_VERSION@ gstreamer-base-@GST_API_VERSION@
Libs: -L${libdir} -lgsttranscoder-@GST_API_VERSION@
Cflags: -I@abs_top_srcdir@/gst-libs -I@abs_top_builddir@/gst-libs

View file

@ -0,0 +1,13 @@
prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@/gstreamer-@GST_API_VERSION@
pluginsdir=@libdir@/gstreamer-@GST_API_VERSION@
Name: GStreamer bad transcoder library, uninstalled
Description: High level API for transcoding using GStreamer
Version: @VERSION@
Requires: gstreamer-@GST_API_VERSION@ gstreamer-base-@GST_API_VERSION@
Libs: -L${libdir} -lgsttranscoder-@GST_API_VERSION@
Cflags: -I${includedir}

View file

@ -11,6 +11,7 @@ pkgconf.set('VERSION', gst_version)
pkgconf.set('abs_top_builddir', join_paths(meson.current_build_dir(), '..'))
pkgconf.set('abs_top_srcdir', join_paths(meson.current_source_dir(), '..'))
pkgconf.set('audiolibdir', join_paths(meson.build_root(), gstbadaudio.outdir()))
pkgconf.set('transcoderlibdir', join_paths(meson.build_root(), gst_transcoder.outdir()))
pkgconf.set('codecparserslibdir', join_paths(meson.build_root(), gstcodecparsers.outdir()))
pkgconf.set('insertbinlibdir', join_paths(meson.build_root(), gstinsertbin.outdir()))
pkgconf.set('mpegtslibdir', join_paths(meson.build_root(), gstmpegts.outdir()))

401
tools/gst-transcoder.c Normal file
View file

@ -0,0 +1,401 @@
/* GStreamer
*
* Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <string.h>
#include "utils.h"
#include <gst/transcoder/gsttranscoder.h>
static const gchar *HELP_SUMMARY =
"gst-transcoder-1.0 transcodes a stream defined by its first <input-uri>\n"
"argument to the place defined by its second <output-uri> argument\n"
"into the format described in its third <encoding-format> argument,\n"
"or using the given <output-uri> file extension.\n"
"\n"
"The <encoding-format> argument:\n"
"===============================\n"
"\n"
"If the encoding format is not defined, it will be guessed with\n"
"the given <output-uri> file extension."
"\n"
"<encoding-format> describe the media format into which the\n"
"input stream is going to be transcoded. We have two different\n"
"ways of describing the format:\n"
"\n"
"GstEncodingProfile serialization format\n"
"---------------------------------------\n"
"\n"
"GStreamer encoding profiles can be descibed with a quite extensive\n"
"syntax which is descibed in the GstEncodingProfile documentation.\n"
"\n"
"The simple case looks like:\n"
"\n"
" muxer_source_caps:videoencoder_source_caps:audioencoder_source_caps\n"
"\n"
"Name and category of serialized GstEncodingTarget\n"
"-------------------------------------------------\n"
"\n"
"Encoding targets describe well known formats which\n"
"those are provided in '.gep' files. You can list\n"
"available ones using the `--list-targets` argument.\n";
typedef struct
{
gint cpu_usage, rate;
gboolean list;
GstEncodingProfile *profile;
gchar *src_uri, *dest_uri, *encoding_format, *size;
gchar *framerate;
} Settings;
static void
position_updated_cb (GstTranscoder * transcoder, GstClockTime pos)
{
GstClockTime dur = -1;
gchar status[64] = { 0, };
g_object_get (transcoder, "duration", &dur, NULL);
memset (status, ' ', sizeof (status) - 1);
if (pos != -1 && dur > 0 && dur != -1) {
gchar dstr[32], pstr[32];
/* FIXME: pretty print in nicer format */
g_snprintf (pstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (pos));
pstr[9] = '\0';
g_snprintf (dstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (dur));
dstr[9] = '\0';
g_print ("%s / %s %s\r", pstr, dstr, status);
}
}
static GList *
get_profiles_of_type (GstEncodingProfile * profile, GType profile_type)
{
GList *tmp, *profiles = NULL;
if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) {
for (tmp = (GList *)
gst_encoding_container_profile_get_profiles
(GST_ENCODING_CONTAINER_PROFILE (profile)); tmp; tmp = tmp->next) {
if (G_OBJECT_TYPE (tmp->data) == profile_type)
profiles = g_list_prepend (profiles, tmp->data);
}
} else if (GST_IS_ENCODING_VIDEO_PROFILE (profile)) {
profiles = g_list_prepend (profiles, profile);
}
return profiles;
}
static gboolean
set_video_settings (Settings * settings)
{
GList *video_profiles, *tmp;
gchar *p, *tmpstr, **vsize;
gint width = 0, height = 0;
GValue framerate = G_VALUE_INIT;
if (!settings->size && !settings->framerate)
return TRUE;
if (settings->size) {
p = tmpstr = g_strdup (settings->size);
for (; *p; ++p)
*p = g_ascii_tolower (*p);
vsize = g_strsplit (tmpstr, "x", -1);
g_free (tmpstr);
if (!vsize[1] || vsize[2]) {
g_strfreev (vsize);
error ("Video size should be in the form: WxH, got %s", settings->size);
return FALSE;
}
width = g_ascii_strtoull (vsize[0], NULL, 0);
height = g_ascii_strtoull (vsize[1], NULL, 10);
g_strfreev (vsize);
}
if (settings->framerate) {
g_value_init (&framerate, GST_TYPE_FRACTION);
if (!gst_value_deserialize (&framerate, settings->framerate)) {
error ("Video framerate should be either a fraction or an integer"
" not: %s", settings->framerate);
return FALSE;
}
}
video_profiles = get_profiles_of_type (settings->profile,
GST_TYPE_ENCODING_VIDEO_PROFILE);
for (tmp = video_profiles; tmp; tmp = tmp->next) {
GstCaps *rest = gst_encoding_profile_get_restriction (tmp->data);
if (!rest)
rest = gst_caps_new_empty_simple ("video/x-raw");
else
rest = gst_caps_copy (rest);
if (settings->size) {
gst_caps_set_simple (rest, "width", G_TYPE_INT, width,
"height", G_TYPE_INT, height, NULL);
}
if (settings->framerate)
gst_caps_set_value (rest, "framerate", &framerate);
gst_encoding_profile_set_restriction (tmp->data, rest);
}
return TRUE;
}
static gboolean
set_audio_settings (Settings * settings)
{
GList *audio_profiles, *tmp;
if (settings->rate < 0)
return TRUE;
audio_profiles =
get_profiles_of_type (settings->profile, GST_TYPE_ENCODING_AUDIO_PROFILE);
for (tmp = audio_profiles; tmp; tmp = tmp->next) {
GstCaps *rest = gst_encoding_profile_get_restriction (tmp->data);
if (!rest)
rest = gst_caps_new_empty_simple ("audio/x-raw");
else
rest = gst_caps_copy (rest);
gst_caps_set_simple (rest, "rate", G_TYPE_INT, settings->rate, NULL);
gst_encoding_profile_set_restriction (tmp->data, rest);
}
return TRUE;
}
static void
list_encoding_targets (void)
{
GList *tmp, *targets = gst_encoding_list_all_targets (NULL);
for (tmp = targets; tmp; tmp = tmp->next) {
GstEncodingTarget *target = tmp->data;
GList *usable_profiles = get_usable_profiles (target);
if (usable_profiles) {
GList *tmpprof;
g_print ("\n%s (%s): %s\n * Profiles:\n",
gst_encoding_target_get_name (target),
gst_encoding_target_get_category (target),
gst_encoding_target_get_description (target));
for (tmpprof = usable_profiles; tmpprof; tmpprof = tmpprof->next)
g_print (" - %s: %s",
gst_encoding_profile_get_name (tmpprof->data),
gst_encoding_profile_get_description (tmpprof->data));
g_print ("\n");
g_list_free (usable_profiles);
}
}
g_list_free_full (targets, (GDestroyNotify) g_object_unref);
}
static void
_error_cb (GstTranscoder * transcoder, GError * err, GstStructure * details)
{
if (g_error_matches (err, GST_CORE_ERROR, GST_CORE_ERROR_PAD)) {
GstPadLinkReturn lret;
GType type;
if (details && gst_structure_get (details, "linking-error",
GST_TYPE_PAD_LINK_RETURN, &lret,
"msg-source-type", G_TYPE_GTYPE, &type, NULL) &&
type == g_type_from_name ("GstTranscodeBin")) {
error ("\nCould not setup transcoding pipeline,"
" make sure that you transcoding format parametters"
" are compatible with the input stream.");
return;
}
}
error ("\nFAILURE: %s", err->message);
}
static void
_warning_cb (GstTranscoder * transcoder, GError * error, GstStructure * details)
{
gboolean cant_encode;
GstCaps *caps = NULL;
gchar *stream_id = NULL;
if (details && gst_structure_get (details, "can-t-encode-stream",
G_TYPE_BOOLEAN, &cant_encode, "stream-caps", GST_TYPE_CAPS,
&caps, "stream-id", G_TYPE_STRING, &stream_id, NULL)) {
gchar *source_uri = gst_transcoder_get_source_uri (transcoder);
warn ("WARNING: Input stream %s: WON'T BE ENCODED.\n"
"Make sure the encoding settings are valid and that"
" any preset you set actually exists.\n"
"For more information about that stream, you can inspect"
" the source stream with:\n\n"
" gst-discoverer-1.0 -v %s\n", stream_id, source_uri);
gst_caps_unref (caps);
g_free (stream_id);;
g_free (source_uri);;
return;
}
warn ("Got warning: %s", error->message);
}
int
main (int argc, char *argv[])
{
gint res = 0;
GError *err = NULL;
GstTranscoder *transcoder;
GOptionContext *ctx;
Settings settings = {
.cpu_usage = 100,
.rate = -1,
.encoding_format = NULL,
.size = NULL,
.framerate = NULL,
};
GOptionEntry options[] = {
{"cpu-usage", 'c', 0, G_OPTION_ARG_INT, &settings.cpu_usage,
"The CPU usage to target in the transcoding process", NULL},
{"list-targets", 'l', G_OPTION_ARG_NONE, 0, &settings.list,
"List all encoding targets", NULL},
{"size", 's', 0, G_OPTION_ARG_STRING, &settings.size,
"set frame size (WxH or abbreviation)", NULL},
{"audio-rate", 'r', 0, G_OPTION_ARG_INT, &settings.rate,
"set audio sampling rate (in Hz)", NULL},
{"framerate", 'f', 0, G_OPTION_ARG_STRING, &settings.framerate,
"set video framerate as a fraction (24/1 for 24fps)"
" or a single number (24 for 24fps))", NULL},
{"video-encoder", 'v', 0, G_OPTION_ARG_STRING, &settings.size,
"The video encoder to use.", NULL},
{NULL}
};
g_set_prgname ("gst-transcoder");
ctx = g_option_context_new ("<source uri> <destination uri> "
"[<encoding format>[/<encoding profile name>]]");
g_option_context_set_summary (ctx, HELP_SUMMARY);
g_option_context_add_main_entries (ctx, options, NULL);
g_option_context_add_group (ctx, gst_init_get_option_group ());
if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
g_print ("Error initializing: %s\n", GST_STR_NULL (err->message));
g_clear_error (&err);
g_option_context_free (ctx);
return 1;
}
gst_pb_utils_init ();
if (settings.list) {
list_encoding_targets ();
return 0;
}
if (argc < 3 || argc > 4) {
g_print ("%s", g_option_context_get_help (ctx, TRUE, NULL));
g_option_context_free (ctx);
return -1;
}
g_option_context_free (ctx);
settings.src_uri = ensure_uri (argv[1]);
settings.dest_uri = ensure_uri (argv[2]);
if (argc == 3) {
settings.encoding_format = get_file_extension (settings.dest_uri);
if (!settings.encoding_format)
goto no_extension;
} else {
settings.encoding_format = argv[3];
}
settings.profile = create_encoding_profile (settings.encoding_format);
if (!settings.profile) {
error ("Could not find any encoding format for %s\n",
settings.encoding_format);
warn ("You can list available targets using %s --list-targets", argv[0]);
res = 1;
goto done;
}
g_print ("Encoding to:\n\n");
describe_encoding_profile (settings.profile);
if (!set_video_settings (&settings)) {
res = -1;
goto done;
}
if (!set_audio_settings (&settings)) {
res = -1;
goto done;
}
transcoder = gst_transcoder_new_full (settings.src_uri, settings.dest_uri,
settings.profile, NULL);
gst_transcoder_set_avoid_reencoding (transcoder, TRUE);
gst_transcoder_set_cpu_usage (transcoder, settings.cpu_usage);
g_signal_connect (transcoder, "position-updated",
G_CALLBACK (position_updated_cb), NULL);
g_signal_connect (transcoder, "warning", G_CALLBACK (_warning_cb), NULL);
g_signal_connect (transcoder, "error", G_CALLBACK (_error_cb), NULL);
g_assert (transcoder);
ok ("Starting transcoding...");
gst_transcoder_run (transcoder, &err);
if (!err)
ok ("\nDONE.");
done:
g_free (settings.dest_uri);
g_free (settings.src_uri);
return res;
no_extension:
error ("No <encoding-format> specified and no extension"
" available in the output target: %s", settings.dest_uri);
res = 1;
goto done;
}

5
tools/meson.build Normal file
View file

@ -0,0 +1,5 @@
executable('gst-transcoder-' + api_version,
'gst-transcoder.c', 'utils.c',
install : true,
dependencies : [gst_dep, gstpbutils_dep, gst_transcoder_dep],
)

208
tools/utils.c Normal file
View file

@ -0,0 +1,208 @@
#include <string.h>
#include "utils.h"
#include <gst/pbutils/descriptions.h>
void
print (GstDebugColorFlags c, gboolean err, gboolean nline, const gchar * format,
va_list var_args)
{
GString *str = g_string_new (NULL);
GstDebugColorMode color_mode;
gchar *color = NULL;
const gchar *clear = NULL;
color_mode = gst_debug_get_color_mode ();
#ifdef G_OS_WIN32
if (color_mode == GST_DEBUG_COLOR_MODE_UNIX) {
#else
if (color_mode != GST_DEBUG_COLOR_MODE_OFF) {
#endif
clear = "\033[00m";
color = gst_debug_construct_term_color (c);
}
if (color) {
g_string_append (str, color);
g_free (color);
}
g_string_append_vprintf (str, format, var_args);
if (nline)
g_string_append_c (str, '\n');
if (clear)
g_string_append (str, clear);
if (err)
g_printerr ("%s", str->str);
else
g_print ("%s", str->str);
g_string_free (str, TRUE);
}
void
ok (const gchar * format, ...)
{
va_list var_args;
va_start (var_args, format);
print (GST_DEBUG_FG_GREEN, FALSE, TRUE, format, var_args);
va_end (var_args);
}
void
warn (const gchar * format, ...)
{
va_list var_args;
va_start (var_args, format);
print (GST_DEBUG_FG_YELLOW, TRUE, TRUE, format, var_args);
va_end (var_args);
}
void
error (const gchar * format, ...)
{
va_list var_args;
va_start (var_args, format);
print (GST_DEBUG_FG_RED, TRUE, TRUE, format, var_args);
va_end (var_args);
}
gchar *
ensure_uri (const gchar * location)
{
if (gst_uri_is_valid (location))
return g_strdup (location);
else
return gst_filename_to_uri (location, NULL);
}
gchar *
get_file_extension (gchar * uri)
{
size_t len;
gint find;
len = strlen (uri);
find = len - 1;
while (find >= 0) {
if (uri[find] == '.')
break;
find--;
}
if (find < 0)
return NULL;
return &uri[find + 1];
}
GList *
get_usable_profiles (GstEncodingTarget * target)
{
GList *tmpprof, *usable_profiles = NULL;
for (tmpprof = (GList *) gst_encoding_target_get_profiles (target);
tmpprof; tmpprof = tmpprof->next) {
GstEncodingProfile *profile = tmpprof->data;
GstElement *tmpencodebin = gst_element_factory_make ("encodebin", NULL);
gst_encoding_profile_set_presence (profile, 1);
if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) {
GList *tmpsubprof;
for (tmpsubprof = (GList *)
gst_encoding_container_profile_get_profiles
(GST_ENCODING_CONTAINER_PROFILE (profile)); tmpsubprof;
tmpsubprof = tmpsubprof->next)
gst_encoding_profile_set_presence (tmpsubprof->data, 1);
}
g_object_set (tmpencodebin, "profile", gst_object_ref (profile), NULL);
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (tmpencodebin),
GST_DEBUG_GRAPH_SHOW_ALL, gst_encoding_profile_get_name (profile));
/* The profile could be expended */
if (GST_BIN (tmpencodebin)->children)
usable_profiles = g_list_prepend (usable_profiles, profile);
gst_object_unref (tmpencodebin);
}
return usable_profiles;
}
GstEncodingProfile *
create_encoding_profile (const gchar * pname)
{
GstEncodingProfile *profile;
GValue value = G_VALUE_INIT;
g_value_init (&value, GST_TYPE_ENCODING_PROFILE);
if (!gst_value_deserialize (&value, pname)) {
g_value_reset (&value);
return NULL;
}
profile = g_value_dup_object (&value);
g_value_reset (&value);
return profile;
}
static const gchar *
get_profile_type (GstEncodingProfile * profile)
{
if (GST_IS_ENCODING_CONTAINER_PROFILE (profile))
return "Container";
else if (GST_IS_ENCODING_AUDIO_PROFILE (profile))
return "Audio";
else if (GST_IS_ENCODING_VIDEO_PROFILE (profile))
return "Video";
else
return "Unkonwn";
}
static void
print_profile (GstEncodingProfile * profile, const gchar * prefix)
{
const gchar *name = gst_encoding_profile_get_name (profile);
const gchar *desc = gst_encoding_profile_get_description (profile);
GstCaps *format = gst_encoding_profile_get_format (profile);
gchar *capsdesc;
if (gst_caps_is_fixed (format))
capsdesc = gst_pb_utils_get_codec_description (format);
else
capsdesc = gst_caps_to_string (format);
g_print ("%s%s: %s%s%s%s%s%s\n", prefix, get_profile_type (profile),
name ? name : capsdesc, desc ? ": " : "", desc ? desc : "",
name ? " (" : "", name ? capsdesc : "", name ? ")" : "");
g_free (capsdesc);
}
void
describe_encoding_profile (GstEncodingProfile * profile)
{
g_return_if_fail (GST_IS_ENCODING_PROFILE (profile));
print_profile (profile, " ");
if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) {
const GList *tmp;
for (tmp =
gst_encoding_container_profile_get_profiles
(GST_ENCODING_CONTAINER_PROFILE (profile)); tmp; tmp = tmp->next)
print_profile (tmp->data, " - ");
}
}

21
tools/utils.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef __GST_TRANSCODER_UTILS_H
#define __GST_TRANSCODER_UTILS_H
#include "utils.h"
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include <gst/pbutils/encoding-profile.h>
void print (GstDebugColorFlags c, gboolean err, gboolean nline, const gchar * format, va_list var_args);
void ok (const gchar * format, ...);
void warn (const gchar * format, ...);
void error (const gchar * format, ...);
gchar * ensure_uri (const gchar * location);
gchar * get_file_extension (gchar * uri);
GList * get_usable_profiles (GstEncodingTarget * target);
GstEncodingProfile * create_encoding_profile (const gchar * pname);
void describe_encoding_profile (GstEncodingProfile *profile);
#endif /*__GST_TRANSCODER_UTILS_H*/