mirror of
https://github.com/albfan/miraclecast.git
synced 2025-03-09 23:38:56 +00:00
migrate more gstreamer code to res/gstencoder
Change-Id: Id5098e37fb1eabb2906505f26969e6d8cedad519
This commit is contained in:
parent
b16f1e5a51
commit
9aa6ea14c2
2 changed files with 361 additions and 129 deletions
|
@ -1,169 +1,400 @@
|
|||
public enum GstEncoderConfig
|
||||
public enum DispdEncoderConfig
|
||||
{
|
||||
DISPLAY_SYSTEM, /* string */
|
||||
DISPLAY_NAME, /* string */
|
||||
MONITOR, /* uint32 */
|
||||
TOP, /* uint32 */
|
||||
LEFT, /* uint32 */
|
||||
WIDTH, /* uint32 */
|
||||
HEIGHT, /* uint32 */
|
||||
WINDOW_ID, /* uint32 */
|
||||
FRAMERATE, /* uint32 */
|
||||
SCALE_WIDTH, /* uint32 */
|
||||
SCALE_HEIGHT, /* uint32 */
|
||||
RTP_PORT1, /* uint32 */
|
||||
RTP_PORT2, /* uint32 */
|
||||
RTCP_PORT, /* uint32 */
|
||||
H264_PROFILE,
|
||||
H264_LEVEL,
|
||||
DEBUG_LEVEL,
|
||||
DISPLAY_TYPE, /* string */
|
||||
DISPLAY_NAME, /* string */
|
||||
MONITOR_NUM, /* uint32 */
|
||||
X, /* uint32 */
|
||||
Y, /* uint32 */
|
||||
WIDTH, /* uint32 */
|
||||
HEIGHT, /* uint32 */
|
||||
WINDOW_ID, /* uint32 */
|
||||
FRAMERATE, /* uint32 */
|
||||
SCALE_WIDTH, /* uint32 */
|
||||
SCALE_HEIGHT, /* uint32 */
|
||||
AUDIO_TYPE, /* string */
|
||||
AUDIO_DEV, /* string */
|
||||
PEER_ADDRESS, /* string */
|
||||
RTP_PORT0, /* uint32 */
|
||||
RTP_PORT1, /* uint32 */
|
||||
PEER_RTCP_PORT, /* uint32 */
|
||||
LOCAL_ADDRESS, /* uint32 */
|
||||
LOCAL_RTCP_PORT, /* uint32 */
|
||||
H264_PROFILE,
|
||||
H264_LEVEL,
|
||||
DEBUG_LEVEL,
|
||||
}
|
||||
|
||||
[DBus (name = "org.freedesktop.miracle.encoder.error")]
|
||||
public errordomain GstEncoderError
|
||||
public errordomain DispdEncoderError
|
||||
{
|
||||
UNEXPECTED_EOS,
|
||||
ENCODER_ERROR,
|
||||
INVALID_STATE,
|
||||
CANT_RETURN_UNIQUE_NAME,
|
||||
UNEXPECTED_EOS,
|
||||
ENCODER_ERROR,
|
||||
INVALID_STATE,
|
||||
}
|
||||
|
||||
[DBus(name = "org.freedesktop.miracle.encoder.state")]
|
||||
public enum DispdEncoderState
|
||||
{
|
||||
NULL,
|
||||
CONFIGURED,
|
||||
READY,
|
||||
STARTED,
|
||||
PAUSED,
|
||||
}
|
||||
|
||||
[DBus(name = "org.freedesktop.miracle.encoder")]
|
||||
public interface GstEncoder : GLib.Object
|
||||
public interface DispdEncoder : GLib.Object
|
||||
{
|
||||
public enum State
|
||||
{
|
||||
NULL,
|
||||
CONFIGURED,
|
||||
READY,
|
||||
STARTED,
|
||||
PAUSED,
|
||||
}
|
||||
public const string OBJECT_PATH = "/org/freedesktop/miracle/encoder";
|
||||
|
||||
public const string OBJECT_PATH = "/org/freedesktop/miracle/encoder";
|
||||
public abstract DispdEncoderState state { get; protected set; }
|
||||
public abstract signal void error(string reason);
|
||||
|
||||
public abstract State state { get; protected set; default = State.NULL; }
|
||||
|
||||
public abstract void configure(HashTable<GstEncoderConfig, Variant> configs) throws GstEncoderError;
|
||||
public abstract void start() throws GstEncoderError;
|
||||
public abstract void pause() throws GstEncoderError;
|
||||
public abstract void stop() throws GstEncoderError;
|
||||
public abstract void configure(HashTable<DispdEncoderConfig, Variant> configs) throws DispdEncoderError;
|
||||
public abstract void start() throws DispdEncoderError;
|
||||
public abstract void pause() throws DispdEncoderError;
|
||||
public abstract void stop() throws DispdEncoderError;
|
||||
}
|
||||
|
||||
internal class EncoderImpl : GstEncoder, GLib.Object
|
||||
internal class GstEncoder : DispdEncoder, GLib.Object
|
||||
{
|
||||
private DBusConnection conn;
|
||||
private HashTable<GstEncoderConfig, Variant> configs;
|
||||
private Gst.Element pipeline;
|
||||
private Gst.State pipeline_state = Gst.State.NULL;
|
||||
private DBusConnection conn;
|
||||
private HashTable<DispdEncoderConfig, Variant> configs;
|
||||
private Gst.Element pipeline;
|
||||
private Gst.State pipeline_state = Gst.State.NULL;
|
||||
private DispdEncoderState _state = DispdEncoderState.NULL;
|
||||
|
||||
public GstEncoder.State state { get; private set; }
|
||||
public DispdEncoderState state {
|
||||
get { return _state; }
|
||||
protected set {
|
||||
if(_state == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
public void configure(HashTable<GstEncoderConfig, Variant> configs) throws GstEncoderError
|
||||
{
|
||||
if(GstEncoder.State.NULL != state) {
|
||||
throw new GstEncoderError.INVALID_STATE("already configured");
|
||||
}
|
||||
_state = value;
|
||||
notify_state_changed();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
pipeline = Gst.parse_launch("""videotestsrc ! autovideosink""");
|
||||
}
|
||||
catch(Error e) {
|
||||
throw new GstEncoderError.ENCODER_ERROR("%s", e.message);
|
||||
}
|
||||
private void notify_state_changed()
|
||||
{
|
||||
var builder = new VariantBuilder(VariantType.ARRAY);
|
||||
var invalid_builder = new VariantBuilder(new VariantType ("as"));
|
||||
|
||||
pipeline.set_state(Gst.State.READY);
|
||||
var bus = pipeline.get_bus();
|
||||
bus.add_signal_watch();
|
||||
bus.message.connect(on_pipeline_message);
|
||||
Variant s = state;
|
||||
builder.add ("{sv}", "State", s);
|
||||
|
||||
this.configs = configs;
|
||||
state = GstEncoder.State.CONFIGURED;
|
||||
}
|
||||
try {
|
||||
conn.emit_signal(null,
|
||||
"/org/freedesktop/miracle/encoder",
|
||||
"org.freedesktop.DBus.Properties",
|
||||
"PropertiesChanged",
|
||||
new Variant ("(sa{sv}as)",
|
||||
"org.freedesktop.miracle.encoder",
|
||||
builder,
|
||||
invalid_builder));
|
||||
}
|
||||
catch (Error e) {
|
||||
warning("failed to emit signal: %s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public void start() throws GstEncoderError
|
||||
{
|
||||
check_configs();
|
||||
public void configure(HashTable<DispdEncoderConfig, Variant> configs) throws DispdEncoderError
|
||||
{
|
||||
//if(DispdEncoderState.NULL != state) {
|
||||
//throw new DispdEncoderError.INVALID_STATE("already configured");
|
||||
//}
|
||||
|
||||
pipeline.set_state(Gst.State.PLAYING);
|
||||
}
|
||||
uint32 framerate = configs.contains(DispdEncoderConfig.FRAMERATE)
|
||||
? configs.get(DispdEncoderConfig.FRAMERATE).get_uint32()
|
||||
: 30;
|
||||
StringBuilder desc = new StringBuilder();
|
||||
desc.append_printf("""
|
||||
ximagesrc name=vsrc use-damage=false show-pointer=false
|
||||
startx=%u starty=%u endx=%u endy=%u
|
||||
! video/x-raw, framerate=%u/1
|
||||
! videoscale method=0
|
||||
! video/x-raw, width=640, height=480
|
||||
! videoconvert dither=0
|
||||
! video/x-raw, format=YV12
|
||||
! x264enc pass=4 b-adapt=false key-int-max=%u speed-preset=4 tune=4
|
||||
! h264parse
|
||||
! video/x-h264, alignment=nal, stream-format=byte-stream
|
||||
%s
|
||||
! mpegtsmux name=muxer
|
||||
! rtpmp2tpay
|
||||
! .send_rtp_sink_0 rtpbin name=session do-retransmission=true
|
||||
do-sync-event=true do-lost=true ntp-time-source=3
|
||||
buffer-mode=0 latency=20 max-misorder-time=30
|
||||
! application/x-rtp
|
||||
! udpsink sync=false async=false host="%s" port=%u """,
|
||||
configs.contains(DispdEncoderConfig.X)
|
||||
? configs.get(DispdEncoderConfig.X).get_uint32()
|
||||
: 0,
|
||||
configs.contains(DispdEncoderConfig.Y)
|
||||
? configs.get(DispdEncoderConfig.Y).get_uint32()
|
||||
: 0,
|
||||
configs.contains(DispdEncoderConfig.WIDTH)
|
||||
? configs.get(DispdEncoderConfig.WIDTH).get_uint32() - 1
|
||||
: 640,
|
||||
configs.contains(DispdEncoderConfig.HEIGHT)
|
||||
? configs.get(DispdEncoderConfig.HEIGHT).get_uint32() - 1
|
||||
: 480,
|
||||
framerate,
|
||||
framerate,
|
||||
configs.contains(DispdEncoderConfig.AUDIO_TYPE)
|
||||
? "! queue max-size-buffers=0 max-size-bytes=0"
|
||||
: "",
|
||||
configs.contains(DispdEncoderConfig.PEER_ADDRESS)
|
||||
? configs.get(DispdEncoderConfig.PEER_ADDRESS).get_string()
|
||||
: "",
|
||||
configs.contains(DispdEncoderConfig.RTP_PORT0)
|
||||
? configs.get(DispdEncoderConfig.RTP_PORT0).get_uint32()
|
||||
: 16384);
|
||||
if(configs.contains(DispdEncoderConfig.LOCAL_RTCP_PORT)) {
|
||||
desc.append_printf("""udpsrc address="%s" "port=%u reuse=true
|
||||
! session.recv_rtcp_sink_0
|
||||
session.send_rtcp_src_0
|
||||
! udpsink host="%s" port=%u sync=false async=false """,
|
||||
configs.contains(DispdEncoderConfig.LOCAL_ADDRESS)
|
||||
? configs.get(DispdEncoderConfig.LOCAL_ADDRESS).get_string()
|
||||
: "",
|
||||
configs.contains(DispdEncoderConfig.LOCAL_RTCP_PORT)
|
||||
? configs.get(DispdEncoderConfig.LOCAL_RTCP_PORT).get_uint32()
|
||||
: 16385,
|
||||
configs.contains(DispdEncoderConfig.PEER_ADDRESS)
|
||||
? configs.get(DispdEncoderConfig.PEER_ADDRESS).get_string()
|
||||
: "",
|
||||
configs.contains(DispdEncoderConfig.PEER_RTCP_PORT)
|
||||
? configs.get(DispdEncoderConfig.PEER_RTCP_PORT).get_uint32()
|
||||
: 16385);
|
||||
}
|
||||
|
||||
public void pause() throws GstEncoderError
|
||||
{
|
||||
check_configs();
|
||||
this.configs = configs;
|
||||
|
||||
pipeline.set_state(Gst.State.PAUSED);
|
||||
}
|
||||
try {
|
||||
pipeline = Gst.parse_launch(desc.str);
|
||||
}
|
||||
catch(Error e) {
|
||||
throw new DispdEncoderError.ENCODER_ERROR("%s", e.message);
|
||||
}
|
||||
|
||||
public void stop() throws GstEncoderError
|
||||
{
|
||||
if(null == pipeline) {
|
||||
return;
|
||||
}
|
||||
var bus = pipeline.get_bus();
|
||||
bus.add_signal_watch();
|
||||
bus.message.connect(on_pipeline_message);
|
||||
|
||||
pipeline.set_state(Gst.State.NULL);
|
||||
pipeline = null;
|
||||
}
|
||||
pipeline.set_state(Gst.State.READY);
|
||||
}
|
||||
|
||||
public async void prepare() throws Error
|
||||
{
|
||||
conn = yield Bus.get(BusType.SESSION);
|
||||
conn.register_object(GstEncoder.OBJECT_PATH, this as GstEncoder);
|
||||
// if(*os->audio_dev) {
|
||||
// for(tmp = pipeline_desc; *tmp; ++tmp);
|
||||
// *tmp ++ = "pulsesrc";
|
||||
// *tmp ++ = "do-timestamp=true";
|
||||
// *tmp ++ = "client-name=miraclecast";
|
||||
// *tmp ++ = "device=";
|
||||
// *tmp ++ = quote_str(os->audio_dev, audio_dev, sizeof(audio_dev));
|
||||
// *tmp ++ = "!";
|
||||
// *tmp ++ = "voaacenc";
|
||||
// *tmp ++ = "mark-granule=true";
|
||||
// *tmp ++ = "hard-resync=true";
|
||||
// *tmp ++ = "tolerance=40";
|
||||
// *tmp ++ = "!";
|
||||
// *tmp ++ = "audio/mpeg,";
|
||||
// *tmp ++ = "rate=48000,";
|
||||
// *tmp ++ = "channels=2,";
|
||||
// *tmp ++ = "stream-format=adts,";
|
||||
// *tmp ++ = "base-profile=lc";
|
||||
// *tmp ++ = "!";
|
||||
// *tmp ++ = "queue";
|
||||
// *tmp ++ = "max-size-buffers=0";
|
||||
// *tmp ++ = "max-size-bytes=0";
|
||||
// *tmp ++ = "max-size-time=0";
|
||||
// *tmp ++ = "!";
|
||||
// *tmp ++ = "muxer.";
|
||||
// *tmp ++ = NULL;
|
||||
// }
|
||||
//
|
||||
// /* bad pratice, but since we are in the same process,
|
||||
// I think this is the only way to do it */
|
||||
// if(WFD_DISPLAY_TYPE_X == os->display_type) {
|
||||
// r = setenv("XAUTHORITY", os->authority, 1);
|
||||
// if(0 > r) {
|
||||
// return r;
|
||||
// }
|
||||
//
|
||||
// r = setenv("DISPLAY", os->display_name, 1);
|
||||
// if(0 > r) {
|
||||
// return r;
|
||||
// }
|
||||
//
|
||||
// if(!os->display_param_name) {
|
||||
// snprintf(vsrc_param1, sizeof(vsrc_param1), "startx=%hu", os->x);
|
||||
// snprintf(vsrc_param2, sizeof(vsrc_param2), "starty=%hu", os->y);
|
||||
// snprintf(vsrc_param3, sizeof(vsrc_param3), "endx=%d", os->x + os->width - 1);
|
||||
// snprintf(vsrc_param4, sizeof(vsrc_param4), "endy=%d", os->y + os->height - 1);
|
||||
// }
|
||||
// else if(!strcmp("xid", os->display_param_name) ||
|
||||
// !strcmp("xname", os->display_param_name)) {
|
||||
// snprintf(vsrc_param1, sizeof(vsrc_param1),
|
||||
// "%s=\"%s\"",
|
||||
// os->display_param_name,
|
||||
// os->display_param_value);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// pipeline = gst_parse_launchv(pipeline_desc, &error);
|
||||
// if(!pipeline) {
|
||||
// if(error) {
|
||||
// log_error("failed to create pipeline: %s", error->message);
|
||||
// g_error_free(error);
|
||||
// }
|
||||
// return -1;
|
||||
// }
|
||||
//
|
||||
// vsrc = gst_bin_get_by_name(GST_BIN(pipeline), "vsrc");
|
||||
// gst_base_src_set_live(GST_BASE_SRC(vsrc), true);
|
||||
// g_object_unref(vsrc);
|
||||
// vsrc = NULL;
|
||||
//
|
||||
// r = gst_element_set_state(pipeline, GST_STATE_PAUSED);
|
||||
// if(GST_STATE_CHANGE_FAILURE == r) {
|
||||
// g_object_unref(pipeline);
|
||||
// return -1;
|
||||
// }
|
||||
//
|
||||
// bus = gst_element_get_bus(pipeline);
|
||||
// gst_bus_add_watch(bus, wfd_out_session_handle_gst_message, s);
|
||||
//
|
||||
// os->pipeline = pipeline;
|
||||
// os->bus = bus;
|
||||
//
|
||||
// return 0;
|
||||
//}
|
||||
|
||||
/* we are ready, tell parent how to communicate with us */
|
||||
stderr.printf("\nunique-name: %s\n", conn.unique_name);
|
||||
stderr.flush();
|
||||
}
|
||||
public void start() throws DispdEncoderError
|
||||
{
|
||||
check_configs();
|
||||
|
||||
private void check_configs() throws GstEncoderError
|
||||
{
|
||||
if(null == configs || null == pipeline) {
|
||||
throw new GstEncoderError.INVALID_STATE("not configure yet");
|
||||
}
|
||||
}
|
||||
pipeline.set_state(Gst.State.PLAYING);
|
||||
}
|
||||
|
||||
private void on_pipeline_message(Gst.Message m)
|
||||
{
|
||||
if(m.src != pipeline) {
|
||||
return;
|
||||
}
|
||||
public void pause() throws DispdEncoderError
|
||||
{
|
||||
check_configs();
|
||||
|
||||
switch(m.type) {
|
||||
case Gst.MessageType.EOS:
|
||||
break;
|
||||
case Gst.MessageType.STATE_CHANGED:
|
||||
Gst.State oldstate;
|
||||
m.parse_state_changed(out oldstate, out pipeline_state, null);
|
||||
debug("state chagned from %s to %s",
|
||||
oldstate.to_string(),
|
||||
pipeline_state.to_string());
|
||||
break;
|
||||
default:
|
||||
debug("unhandled message: %s", m.type.to_string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
pipeline.set_state(Gst.State.PAUSED);
|
||||
}
|
||||
|
||||
public void stop() throws DispdEncoderError
|
||||
{
|
||||
if(null == pipeline) {
|
||||
return;
|
||||
}
|
||||
|
||||
pipeline.set_state(Gst.State.NULL);
|
||||
pipeline = null;
|
||||
}
|
||||
|
||||
public async void prepare() throws Error
|
||||
{
|
||||
conn = yield Bus.get(BusType.SYSTEM);
|
||||
conn.register_object(DispdEncoder.OBJECT_PATH, this as DispdEncoder);
|
||||
|
||||
/* we are ready, tell parent how to communicate with us */
|
||||
unowned string unique_name = conn.unique_name;
|
||||
ssize_t r = Posix.write(3,
|
||||
(void *) unique_name.data,
|
||||
unique_name.length);
|
||||
if(0 > r) {
|
||||
throw new DispdEncoderError.CANT_RETURN_UNIQUE_NAME("%s",
|
||||
Posix.strerror(Posix.errno));
|
||||
}
|
||||
Posix.fsync(3);
|
||||
}
|
||||
|
||||
private void check_configs() throws DispdEncoderError
|
||||
{
|
||||
if(null == configs || null == pipeline) {
|
||||
throw new DispdEncoderError.INVALID_STATE("not configure yet");
|
||||
}
|
||||
}
|
||||
|
||||
private void on_pipeline_message(Gst.Message m)
|
||||
{
|
||||
Error e;
|
||||
string d;
|
||||
|
||||
if(m.src != pipeline) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch(m.type) {
|
||||
case Gst.MessageType.EOS:
|
||||
error("unexpected EOS");
|
||||
break;
|
||||
case Gst.MessageType.ERROR:
|
||||
m.parse_error(out e, out d);
|
||||
error("unexpected error: %s\n%s".printf(e.message, d));
|
||||
break;
|
||||
case Gst.MessageType.STATE_CHANGED:
|
||||
Gst.State oldstate;
|
||||
m.parse_state_changed(out oldstate, out pipeline_state, null);
|
||||
info("pipeline state chagned from %s to %s",
|
||||
oldstate.to_string(),
|
||||
pipeline_state.to_string());
|
||||
switch(pipeline_state) {
|
||||
case Gst.State.READY:
|
||||
state = DispdEncoderState.CONFIGURED;
|
||||
break;
|
||||
case Gst.State.PLAYING:
|
||||
state = DispdEncoderState.STARTED;
|
||||
break;
|
||||
case Gst.State.PAUSED:
|
||||
if(Gst.State.PLAYING == oldstate) {
|
||||
state = DispdEncoderState.PAUSED;
|
||||
}
|
||||
break;
|
||||
case Gst.State.NULL:
|
||||
state = DispdEncoderState.NULL;
|
||||
Timeout.add(100, () => {
|
||||
loop.quit();
|
||||
return false;
|
||||
});
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
debug("unhandled message: %s", m.type.to_string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MainLoop loop;
|
||||
|
||||
int main(string[] argv)
|
||||
{
|
||||
Gst.init(ref argv);
|
||||
Gst.init(ref argv);
|
||||
|
||||
var encoder = new EncoderImpl();
|
||||
encoder.prepare.begin((o, r) => {
|
||||
try {
|
||||
encoder.prepare.end(r);
|
||||
}
|
||||
catch(Error e) {
|
||||
error("%s", e.message);
|
||||
}
|
||||
});
|
||||
var encoder = new GstEncoder();
|
||||
encoder.prepare.begin((o, r) => {
|
||||
try {
|
||||
encoder.prepare.end(r);
|
||||
}
|
||||
catch(Error e) {
|
||||
error("%s", e.message);
|
||||
}
|
||||
});
|
||||
|
||||
loop = new MainLoop();
|
||||
loop.run();
|
||||
loop = new MainLoop();
|
||||
loop.run();
|
||||
|
||||
Gst.deinit();
|
||||
Posix.close(3);
|
||||
|
||||
return 0;
|
||||
Gst.deinit();
|
||||
|
||||
info("bye");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -49,4 +49,5 @@ gst1 = dependency('gstreamer-1.0')
|
|||
gst1_base = dependency('gstreamer-base-1.0')
|
||||
executable('gstencoder', 'gstencoder.vala',
|
||||
dependencies: [gst1, gst1_base, gio2],
|
||||
install: true)
|
||||
install: true,
|
||||
vala_args: ['--pkg=posix'])
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue