From b7875862838fa8808fd9e55607eb080564f8862b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Canek=20Pel=C3=A1ez=20Vald=C3=A9s?= Date: Sun, 12 Apr 2020 22:50:30 -0500 Subject: [PATCH] Flushing to h264 filesink fails. --- meson.build | 4 +- src/application-window.vala | 23 ++- src/application.vala | 12 +- src/camera.vala | 317 ++++++++++++++++++++++-------------- src/devices.vala | 8 +- src/framerate.vala | 49 ++++++ src/video-device.vala | 82 ++++------ src/video-setup.vala | 91 +++++++++++ 8 files changed, 393 insertions(+), 193 deletions(-) create mode 100644 src/framerate.vala create mode 100644 src/video-setup.vala diff --git a/meson.build b/meson.build index a210713..816046e 100644 --- a/meson.build +++ b/meson.build @@ -45,9 +45,11 @@ c920_studio_vala_sources = [ 'src/application-window.vala', 'src/camera.vala', 'src/devices.vala', + 'src/framerate.vala', 'src/studio-event.vala', 'src/timestamp.vala', - 'src/video-device.vala' + 'src/video-device.vala', + 'src/video-setup.vala' ] executable('c920-studio', c920_studio_vala_sources, diff --git a/src/application-window.vala b/src/application-window.vala index 1944095..49e060f 100644 --- a/src/application-window.vala +++ b/src/application-window.vala @@ -56,6 +56,20 @@ namespace C920Studio { app.event_ocurred.connect(event_ocurred); } + /** + * Adds the camera widget to the window. + * @param widget the camera widget. + * @param width the widget width. + * @param height the height width. + */ + public void add_camera_widget(Gtk.Widget widget, + int width, int height) { + this.add(widget); + widget.width_request = width; + widget.height_request = height; + widget.visible = true; + } + /* The on close activated event handler. */ [GtkCallback] private bool on_close_activated() { @@ -88,14 +102,5 @@ namespace C920Studio { header.subtitle = "Recording"; } } - - /* Adds the camera widget to the window. */ - public void add_camera_widget(Gtk.Widget widget, - int width, int height) { - this.add(widget); - widget.width_request = width; - widget.height_request = height; - widget.visible = true; - } } } diff --git a/src/application.vala b/src/application.vala index 89b7c28..ebf8f14 100644 --- a/src/application.vala +++ b/src/application.vala @@ -42,6 +42,7 @@ namespace C920Studio { /* The camera. */ private Camera camera; + /* The devices. */ private Devices devices; /* The application window. */ private ApplicationWindow window; @@ -87,9 +88,14 @@ namespace C920Studio { /* Adds the camera, presents the window and starts the camera. */ private void present_and_start() { - window.add_camera_widget(camera.widget, - devices.video_best.width, - devices.video_best.height); + var setup = devices.video_best.setups.first(); + var w = setup.width; + var h = setup.height; + while (w > 1024) { + w /= 2; + h /= 2; + } + window.add_camera_widget(camera.widget, w, h); window.present(); camera.start(); } diff --git a/src/camera.vala b/src/camera.vala index c2399e2..b066ee3 100644 --- a/src/camera.vala +++ b/src/camera.vala @@ -37,6 +37,9 @@ namespace C920Studio { */ public bool saving { public get; private set; } + private VideoDevice videodev; + private VideoSetup setup; + /* Pipeline and elements. */ private Gst.Pipeline pipeline; private Gst.Bus bus; @@ -48,6 +51,7 @@ namespace C920Studio { private Gst.Element vq1; private Gst.Element vq2; private Gst.Element vq3; + private Gst.Element vq4; private Gst.Element tee; private Gst.Element dec; private Gst.Element vc; @@ -68,13 +72,70 @@ namespace C920Studio { * Constructs a camera object for a real video/x-raw device. */ public Camera(VideoDevice videodev) { + this.videodev = videodev; + setup = videodev.setups.first(); + if (videodev.is_test) + set_videotest_pipeline(); + else if (setup.media_type == VideoSetup.RAW) + set_v4l2raw_pipeline(); + else if (setup.media_type == VideoSetup.H264) + set_v4l2h264_pipeline(); + + var fr = setup.framerates.first(); + var caps = new Gst.Caps.empty_simple(setup.media_type); + caps.set_simple("framerate", typeof(Gst.Fraction), + fr.numerator, fr.denominator, + "width", typeof(int), setup.width, + "height", typeof(int), setup.height); + filter.set("caps", caps); + + blockpad = vq2.get_static_pad("src"); + + Gtk.Widget w; + gtksink.get("widget", out w); + widget = w; + + bus = pipeline.get_bus(); + bus.add_signal_watch(); + } + + /* Sets the elements for the video test source. */ + private void set_videotest_pipeline() { + pipeline = new Gst.Pipeline("fromage"); + vsrc = Gst.ElementFactory.make("videotestsrc", "vsrc"); + vsrc.set("pattern", 0); + vsrc.set("is-live", 1); + filter = Gst.ElementFactory.make("capsfilter", "filter"); + tee = Gst.ElementFactory.make("tee", "tee"); + vq1 = Gst.ElementFactory.make("queue", "vq1"); + vq1.set("leaky", 1); + vc = Gst.ElementFactory.make("videoconvert", "vc"); + text = Gst.ElementFactory.make("textoverlay", "text"); + text.set("font-desc", "sans 14"); + text.set("halignment", 2); + text.set("valignment", 2); + gtksink = Gst.ElementFactory.make("gtksink", "gtksink"); + vq2 = Gst.ElementFactory.make("queue", "vq2"); + fakesink = Gst.ElementFactory.make("fakesink", "fakesink"); + fakesink.set("async", false); + + pipeline.add_many(vsrc, filter, tee, + vq1, vc, text, gtksink, + vq2, fakesink); + vsrc.link_many(filter, tee); + tee.link_many(vq1, vc, text, gtksink); + tee.link_many(vq2, fakesink); + } + + /* Sets the elements for the V4L2 source. */ + private void set_v4l2raw_pipeline() { pipeline = new Gst.Pipeline("fromage"); vsrc = videodev.device.create_element("vsrc"); filter = Gst.ElementFactory.make("capsfilter", "filter"); vq0 = Gst.ElementFactory.make("queue", "vq0"); - // vq0.set("max-size-buffers", 0); - // vq0.set("max-size-bytes", 0); - // vq0.set("max-size-time", 1000000000); + vq0.set("max-size-buffers", 0); + vq0.set("max-size-bytes", 0); + vq0.set("max-size-time", 1000000000); tee = Gst.ElementFactory.make("tee", "tee"); vq1 = Gst.ElementFactory.make("queue", "vq1"); vq1.set("leaky", 1); @@ -91,70 +152,43 @@ namespace C920Studio { pipeline.add_many(vsrc, filter, vq0, tee, vq1, vc, text, gtksink, vq2, fakesink); - if (!vsrc.link_many(filter, vq0, tee)) - GLib.warning("raw 1"); - if (!tee.link_many(vq1, vc, text, gtksink)) - GLib.warning("raw 2"); - if (!tee.link_many(vq2, fakesink)) - GLib.warning("raw 3"); - - var caps = new Gst.Caps.empty_simple(videodev.name); - caps.set_simple("framerate", typeof(Gst.Fraction), - videodev.numerator, videodev.denominator, - "width", typeof(int), videodev.width, - "height", typeof(int), videodev.height); - filter.set("caps", caps); - - blockpad = vq2.get_static_pad("src"); + vsrc.link_many(filter, vq0, tee); + tee.link_many(vq1, vc, text, gtksink); + tee.link_many(vq2, fakesink); + } - Gtk.Widget w; - gtksink.get("widget", out w); - widget = w; + /* Sets the elements for the V4L2 source. */ + private void set_v4l2h264_pipeline() { + pipeline = new Gst.Pipeline("fromage"); + vsrc = videodev.device.create_element("vsrc"); + filter = Gst.ElementFactory.make("capsfilter", "filter"); + vq0 = Gst.ElementFactory.make("queue", "vq0"); + vq0.set("max-size-buffers", 0); + vq0.set("max-size-bytes", 0); + vq0.set("max-size-time", 1000000000); + tee = Gst.ElementFactory.make("tee", "tee"); + vq1 = Gst.ElementFactory.make("queue", "vq1"); + vq1.set("leaky", 1); + parse = Gst.ElementFactory.make("h264parse", "parse"); + dec = Gst.ElementFactory.make("avdec_h264", "dec"); + vc = Gst.ElementFactory.make("videoconvert", "vc"); + text = Gst.ElementFactory.make("textoverlay", "text"); + text.set("font-desc", "sans 14"); + text.set("halignment", 2); + text.set("valignment", 2); + gtksink = Gst.ElementFactory.make("gtksink", "gtksink"); + vq2 = Gst.ElementFactory.make("queue", "vq2"); + fakesink = Gst.ElementFactory.make("fakesink", "fakesink"); + fakesink.set("async", false); - bus = pipeline.get_bus(); - bus.add_signal_watch(); + pipeline.add_many(vsrc, filter, vq0, tee, + vq1, parse, dec, vc, text, gtksink, + vq2, fakesink); + vsrc.link_many(filter, vq0, tee); + tee.link_many(vq1, parse, dec, vc, text, gtksink); + tee.link_many(vq2, fakesink); } - // /** - // * Constructs a camera object. - // * @param testsrc whether to use a video test source. - // */ - // public Camera() { - // pipeline = new Gst.Pipeline("fromage"); - // filter = Gst.ElementFactory.make("capsfilter", "filter"); - // tee = Gst.ElementFactory.make("tee", "tee"); - // vq1 = Gst.ElementFactory.make("queue", "vq1"); - // vq2 = Gst.ElementFactory.make("queue", "vq2"); - // vc = Gst.ElementFactory.make("videoconvert", "vc"); - // text = Gst.ElementFactory.make("textoverlay", "text"); - // text.set("font-desc", "sans 14"); - // text.set("halignment", 2); - // text.set("valignment", 2); - // gtksink = Gst.ElementFactory.make("gtksink", "gtksink"); - // fakesink = Gst.ElementFactory.make("fakesink", "fakesink"); - // fakesink.set("async", false); - - // if (testsrc) - // set_testsrc_elements(); - // else - // set_v4l2src_elements(); - - // var caps = new Gst.Caps.empty_simple(format); - // caps.set_simple("framerate", typeof(Gst.Fraction), 30, 1, - // "width", typeof(int), 1920, - // "height", typeof(int), 1080); - // filter.set("caps", caps); - - // blockpad = vq2.get_static_pad("src"); - - // Gtk.Widget w; - // gtksink.get("widget", out w); - // widget = w; - - // bus = pipeline.get_bus(); - // bus.add_signal_watch(); - // } - /** * Starts the camera. */ @@ -180,28 +214,45 @@ namespace C920Studio { start_ts = last_ts = Timestamp.get_timestamp(); text.set("text", "•REC 00:00:00"); GLib.Idle.add(update_counter); - // blockpad.add_probe(Gst.PadProbeType.BLOCK_DOWNSTREAM, - // update_pipeline); + blockpad.add_probe(Gst.PadProbeType.BLOCK_DOWNSTREAM, + update_pipeline); } else { text.set("text", ""); - // blockpad.add_probe(Gst.PadProbeType.BLOCK_DOWNSTREAM, - // update_pipeline); + blockpad.add_probe(Gst.PadProbeType.BLOCK_DOWNSTREAM, + update_pipeline); } } private Gst.PadProbeReturn update_pipeline(Gst.Pad pad, Gst.PadProbeInfo info) { pad.remove_probe(info.id); - return saving ? set_filesink() : set_fakesink(); + if (saving) { + fakesink.set_state(Gst.State.NULL); + pipeline.remove(fakesink); + + if (videodev.is_test) + return set_videotest_filesink(); + if (setup.media_type == VideoSetup.RAW) + return set_v4l2raw_filesink(); + if (setup.media_type == VideoSetup.H264) + return set_v4l2h264_filesink(); + } else { + if (videodev.is_test) + return set_videotest_fakesink(); + if (setup.media_type == VideoSetup.RAW) + return set_v4l2raw_fakesink(); + if (setup.media_type == VideoSetup.H264) + return set_v4l2h264_fakesink(); + } + + return Gst.PadProbeReturn.OK; } - private Gst.PadProbeReturn set_fakesink() { + private Gst.PadProbeReturn set_videotest_fakesink() { var srcpad = vc264.get_static_pad("src"); - if (srcpad == null) - GLib.warning("EMPTY PAD"); srcpad.add_probe(Gst.PadProbeType.BLOCK | Gst.PadProbeType.EVENT_DOWNSTREAM, - flush_filesink); + flush_videotest_filesink); var sinkpad = vc264.get_static_pad("sink"); sinkpad.send_event(new Gst.Event.eos()); @@ -209,19 +260,33 @@ namespace C920Studio { return Gst.PadProbeReturn.OK; } - private Gst.PadProbeReturn set_filesink() { - fakesink.set_state(Gst.State.NULL); - pipeline.remove(fakesink); + private Gst.PadProbeReturn set_v4l2raw_fakesink() { + return Gst.PadProbeReturn.OK; + } + private Gst.PadProbeReturn set_v4l2h264_fakesink() { + stderr.printf("set_v4l2h264_fakesink\n"); + var srcpad = vq3.get_static_pad("src"); + srcpad.add_probe(Gst.PadProbeType.BLOCK | + Gst.PadProbeType.EVENT_DOWNSTREAM, + flush_v4l2h264_filesink); + + var sinkpad = vq3.get_static_pad("sink"); + sinkpad.send_event(new Gst.Event.eos()); + + return Gst.PadProbeReturn.OK; + } + + private Gst.PadProbeReturn set_videotest_filesink() { vc264 = Gst.ElementFactory.make("videoconvert", "vc264"); x264enc = Gst.ElementFactory.make("x264enc", "x264enc"); x264enc.set("speed-preset", 6); mp4mux = Gst.ElementFactory.make("mpegtsmux", "mp4mux"); - filesink = Gst.ElementFactory.make("filesink", "filesink"); vq3 = Gst.ElementFactory.make("queue", "vq3"); vq3.set("max-size-buffers", 0); vq3.set("max-size-bytes", 0); vq3.set("max-size-time", 1000000000); + filesink = Gst.ElementFactory.make("filesink", "filesink"); filesink.set("location", "/home/canek/test.mp4"); pipeline.add_many(vc264, x264enc, mp4mux, vq3, filesink); @@ -231,14 +296,41 @@ namespace C920Studio { return Gst.PadProbeReturn.DROP; } - private Gst.PadProbeReturn flush_filesink(Gst.Pad pad, - Gst.PadProbeInfo info) { + private Gst.PadProbeReturn set_v4l2raw_filesink() { + return Gst.PadProbeReturn.DROP; + } + + private Gst.PadProbeReturn set_v4l2h264_filesink() { + stderr.printf("set_v4l2h264_filesink\n"); + vq3 = Gst.ElementFactory.make("queue", "vq3"); + vq3.set("max-size-buffers", 0); + vq3.set("max-size-bytes", 0); + vq3.set("max-size-time", 1000000000); + mp4mux = Gst.ElementFactory.make("mpegtsmux", "mp4mux"); + vq4 = Gst.ElementFactory.make("queue", "vq4"); + vq4.set("max-size-buffers", 0); + vq4.set("max-size-bytes", 0); + vq4.set("max-size-time", 1000000000); + filesink = Gst.ElementFactory.make("filesink", "filesink"); + filesink.set("location", "/home/canek/test.mp4"); + + pipeline.add_many(vq3, mp4mux, vq4, filesink); + vq2.link_many(vq3, mp4mux, vq4, filesink); + vq3.set_state(Gst.State.PLAYING); + + return Gst.PadProbeReturn.DROP; + } + + private Gst.PadProbeReturn + flush_videotest_filesink(Gst.Pad pad, Gst.PadProbeInfo info) { var event = info.get_event(); if (event == null || event.type != Gst.EventType.EOS) return Gst.PadProbeReturn.PASS; + pad.remove_probe(info.id); vc264.set_state(Gst.State.NULL); pipeline.remove_many(vc264, x264enc, mp4mux, vq3, filesink); + fakesink = Gst.ElementFactory.make("fakesink", "fakesink"); fakesink.set("async", false); pipeline.add(fakesink); @@ -247,50 +339,25 @@ namespace C920Studio { return Gst.PadProbeReturn.DROP; } - // /* Sets the elements for the video test source. */ - // private void set_testsrc_elements() { - // vsrc = Gst.ElementFactory.make("videotestsrc", "vsrc"); - // vsrc.set("pattern", 0); - // vsrc.set("is-live", 1); - - // pipeline.add_many(vsrc, filter, tee, - // vq1, vc, text, gtksink, - // vq2, fakesink); - // if (!vsrc.link_many(filter, tee)) - // GLib.warning("1"); - // if (!tee.link_many(vq1, vc, text, gtksink)) - // GLib.warning("2"); - // if (!tee.link_many(vq2, fakesink)) - // GLib.warning("3"); - - // format = "video/x-raw"; - // } - - // /* Sets the elements for the V4L2 source. */ - // private void set_v4l2src_elements() { - // vsrc = Gst.ElementFactory.make("v4l2src", "vsrc"); - // vsrc.set("num-buffers", 1800); - // vsrc.set("device", "/dev/video0"); - // vq0 = Gst.ElementFactory.make("queue", "vq0"); - // vq0.set("max-size-buffers", 0); - // vq0.set("max-size-bytes", 0); - // vq0.set("max-size-time", 1000000000); - // vq1.set("leaky", 1); - // parse = Gst.ElementFactory.make("h264parse", "parse"); - // dec = Gst.ElementFactory.make("avdec_h264", "dec"); - - // pipeline.add_many(vsrc, filter, parse, vq0, tee, - // vq1, dec, vc, text, gtksink, - // vq2, fakesink); - // if (!vsrc.link_many(filter, parse, vq0, tee)) - // GLib.warning("1"); - // if (!tee.link_many(vq1, dec, vc, text, gtksink)) - // GLib.warning("2"); - // if (!tee.link_many(vq2, fakesink)) - // GLib.warning("3"); - - // format = "video/x-h264"; - // } + private Gst.PadProbeReturn + flush_v4l2h264_filesink(Gst.Pad pad, Gst.PadProbeInfo info) { + stderr.printf("flush_v4l2h264_filesink\n"); + var event = info.get_event(); + if (event == null || event.type != Gst.EventType.EOS) + return Gst.PadProbeReturn.PASS; + + pad.remove_probe(info.id); + vq3.set_state(Gst.State.NULL); + pipeline.remove_many(vq3, mp4mux, vq4, filesink); + + fakesink = Gst.ElementFactory.make("fakesink", "fakesink"); + fakesink.set("async", false); + pipeline.add(fakesink); + vq2.link(fakesink); + fakesink.set_state(Gst.State.PLAYING); + + return Gst.PadProbeReturn.DROP; + } /* Starts the camera asynchronously. */ private bool start_camera() { @@ -346,9 +413,9 @@ namespace C920Studio { return; case Gst.MessageType.ERROR: GLib.Error error; - message.parse_error(out error, null); - if (message.src == vsrc) - GLib.warning(error.message); + string debug; + message.parse_error(out error, out debug); + GLib.warning(error.message + "\n" + debug); return; } } diff --git a/src/devices.vala b/src/devices.vala index 96967fc..c2a627f 100644 --- a/src/devices.vala +++ b/src/devices.vala @@ -26,7 +26,7 @@ namespace C920Studio { public VideoDevice video_best { public get; private set; } private Gee.TreeSet video_devices; - //private Gee.TreeSet audio_devices; + //private Gee.TreeSet audio_devices; private Gst.DeviceMonitor monitor; private Gst.Bus bus; @@ -41,9 +41,9 @@ namespace C920Studio { monitor.add_filter("Audio/Source", new Gst.Caps.empty_simple("audio/x-raw")); monitor.add_filter("Video/Source", - new Gst.Caps.empty_simple("video/x-raw")); + new Gst.Caps.empty_simple(VideoSetup.RAW)); monitor.add_filter("Video/Source", - new Gst.Caps.empty_simple("video/x-h264")); + new Gst.Caps.empty_simple(VideoSetup.H264)); monitor.start(); @@ -52,7 +52,7 @@ namespace C920Studio { while (t != null) { switch (t.data.get_device_class()) { case "Audio/Source": - //audio_devices.add(new Device(t.data)); + //audio_devices.add(new AudioDevice(t.data)); break; case "Video/Source": video_devices.add(new VideoDevice(t.data)); diff --git a/src/framerate.vala b/src/framerate.vala new file mode 100644 index 0000000..f335e99 --- /dev/null +++ b/src/framerate.vala @@ -0,0 +1,49 @@ +/* + * This file is part of c920-studio. + * + * Copyright © 2020 Canek Peláez Valdés + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * This program 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 General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * along with this program. If not, see . + * + * Author: + * Canek Peláez Valdés + */ + +namespace C920Studio { + + public class Framerate : GLib.Object, Gee.Comparable { + + public int numerator { public get; private set; } + public int denominator { public get; private set; } + public double value { + public get { + return (double)numerator / (double)denominator; + } + } + + public Framerate(int numerator, int denominator) + requires (numerator > 0 && denominator > 0) { + this.numerator = numerator; + this.denominator = denominator; + } + + public int compare_to(Framerate fr) { + if (value > fr.value) + return -1; + if (value < fr.value) + return 1; + return 0; + } + } +} diff --git a/src/video-device.vala b/src/video-device.vala index 529c6ab..ee1eef8 100644 --- a/src/video-device.vala +++ b/src/video-device.vala @@ -26,14 +26,10 @@ namespace C920Studio { public Gst.Device device { public get; private set; } public bool is_test { public get; private set; } - public string name { public get; private set; } - public string format { public get; private set; } - public int width { public get; private set; } - public int height { public get; private set; } - public int numerator { public get; private set; } - public int denominator { public get; private set; } + public Gee.TreeSet setups { public get; private set; } public VideoDevice(Gst.Device device) { + setups = new Gee.TreeSet(); this.device = device; var caps = device.get_caps(); if (caps == null) @@ -43,13 +39,11 @@ namespace C920Studio { } public VideoDevice.test() { + setups = new Gee.TreeSet(); is_test = true; - name ="video/x-raw"; - format = "I420"; - width = 320; - height = 240; - numerator = 5; - denominator = 1; + var frs = new Gee.TreeSet(); + frs.add(new Framerate(30, 1)); + setups.add(new VideoSetup(VideoSetup.RAW, "I420", 640, 480, frs)); } private void parse_structure(Gst.Structure structure) { @@ -57,64 +51,50 @@ namespace C920Studio { string f = ""; int w = -1; int h = -1; - GLib.Value? v = null; + int num = -1; + int den = -1; n = structure.get_name(); - if (structure.has_field_typed("format", typeof(string))) - f = structure.get_string("format"); + if (n != VideoSetup.H264 && n != VideoSetup.RAW) + return; + f = structure.get_string("format"); if (structure.has_field_typed("width", typeof(int))) structure.get_int("width", out w); if (structure.has_field_typed("height", typeof(int))) structure.get_int("height", out h); - v = structure.get_value("framerate"); - if (f == "" || w == -1 || h == -1 || - w < h || w*h < width * height) + GLib.Value? v = structure.get_value("framerate"); + if (w < 1 || h < 1 || w < h || v == null) return; - name = n; - format = f; - width = w; - height = h; - if (v != null && v.type() == typeof(Gst.ValueList)) { + var frs = new Gee.TreeSet(); + if (v.type() == typeof(Gst.ValueList)) { for (int i = 0; i < Gst.ValueList.get_size(v); i ++) { var vf = Gst.ValueList.get_value(v, i); if (vf.type() != typeof(Gst.Fraction)) continue; - int fn = Gst.Value.get_fraction_numerator(vf); - int fd = Gst.Value.get_fraction_denominator(vf); - if (fraction_value(numerator, denominator) < - fraction_value(fn, fd)) { - denominator = fd; - numerator = fn; - } + parse_fraction(vf, out num, out den); + if (num > 0 && den > 0) + frs.add(new Framerate(num, den)); } + } else if (v.type() == typeof(Gst.Fraction)) { + parse_fraction(v, out num, out den); + if (num > 0 && den > 0) + frs.add(new Framerate(num, den)); } + if (!frs.is_empty) + setups.add(new VideoSetup(n, f, w, h, frs)); } - private double fraction_value(int numerator, int denominator) { - if (denominator == 0) - return 0.0; - double n = numerator; - double d = denominator; - return n / d; + private void parse_fraction(GLib.Value frac, + out int num, out int den) { + num = Gst.Value.get_fraction_numerator(frac); + den = Gst.Value.get_fraction_denominator(frac); } public int compare_to(VideoDevice device) { if (!is_test && device.is_test) return -1; - if (is_test && !device.is_test) - return 1; - if (name == "video/x-h264" && device.name != "video/x-h264") - return 1; - if (name != "video/x-h264" && device.name == "video/x-h264") - return 1; - if (width * height < device.width * device.height) - return 1; - if (width * height > device.width * device.height) - return -1; - if (width < height && device.width > device.height) - return 1; - if (width > height && device.width < device.height) - return -1; - return 0; + var ts = this.setups.first(); + var ds = device.setups.first(); + return ts.compare_to(ds); } } } diff --git a/src/video-setup.vala b/src/video-setup.vala new file mode 100644 index 0000000..177a9ac --- /dev/null +++ b/src/video-setup.vala @@ -0,0 +1,91 @@ +/* + * This file is part of c920-studio. + * + * Copyright © 2020 Canek Peláez Valdés + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * This program 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 General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * along with this program. If not, see . + * + * Author: + * Canek Peláez Valdés + */ + +namespace C920Studio { + + public class VideoSetup : GLib.Object, Gee.Comparable { + + public const string RAW = "video/x-raw"; + public const string H264 = "video/x-h264"; + + public string media_type { public get; private set; } + public string format { public get; private set; } + public int width { public get; private set; } + public int height { public get; private set; } + public Gee.TreeSet framerates { public get; private set; } + + public int area { public get { return width * height; } } + public bool is_landscape { public get { return width > height; } } + + public VideoSetup(string media_type, string? format, + int width, int height, + Gee.TreeSet framerates) { + this.media_type = media_type; + this.format = format; + this.width = width; + this.height = height; + this.framerates = framerates; + } + + public string to_string() { + string f = (format == null) ? "" : + "format=(string)%s, ".printf(format); + string sfr = ""; + if (framerates.size == 1) { + var fr = framerates.first(); + sfr = "framerate=(fraction)%d/%d".printf(fr.numerator, + fr.denominator); + } else { + foreach (var fr in framerates) { + if (sfr == "") { + sfr = "framerate=(fraction){ %d/%d".printf( + fr.numerator,fr.denominator); + } else { + sfr += ", %d/%d".printf(fr.numerator, fr.denominator); + } + } + sfr += " }"; + } + + return "%s, %swidth=(int)%d, height=(int)%d, %s".printf( + media_type, f, width, height, sfr); + } + + public int compare_to(VideoSetup vs) { + if (this.media_type == H264 && vs.media_type != H264) + return -1; + if (this.media_type != H264 && vs.media_type == H264) + return 1; + var tfr = this.framerates.first(); + var vfr = vs.framerates.first(); + if (tfr.compare_to(vfr) < 0) + return -1; + if (tfr.compare_to(vfr) > 0) + return 1; + if (this.is_landscape && !vs.is_landscape) + return -1; + if (!this.is_landscape && vs.is_landscape) + return 1; + return vs.area - this.area; + } + } +} -- GitLab