mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-18 15:51:11 +00:00
3bb8700577
On macOS, a Cocoa event loop is needed in the main thread to ensure things like opening a GL window work correctly. In the past, this was patched into glib via Cerbero, but that prevented us from updating it. This workaround simply runs an NSApplication and then calls the main function on a secondary thread, allowing GStreamer to correctly display windows and/or system permission prompts, for example. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3532>
186 lines
5.5 KiB
C
186 lines
5.5 KiB
C
#include <gst/gst.h>
|
|
#include <gst/audio/audio.h>
|
|
#include <string.h>
|
|
|
|
#ifdef __APPLE__
|
|
#include <TargetConditionals.h>
|
|
#endif
|
|
|
|
#define CHUNK_SIZE 1024 /* Amount of bytes we are sending in each buffer */
|
|
#define SAMPLE_RATE 44100 /* Samples per second we are sending */
|
|
|
|
/* Structure to contain all our information, so we can pass it to callbacks */
|
|
typedef struct _CustomData
|
|
{
|
|
GstElement *pipeline;
|
|
GstElement *app_source;
|
|
|
|
guint64 num_samples; /* Number of samples generated so far (for timestamp generation) */
|
|
gfloat a, b, c, d; /* For waveform generation */
|
|
|
|
guint sourceid; /* To control the GSource */
|
|
|
|
GMainLoop *main_loop; /* GLib's Main Loop */
|
|
} CustomData;
|
|
|
|
/* This method is called by the idle GSource in the mainloop, to feed CHUNK_SIZE bytes into appsrc.
|
|
* The ide handler is added to the mainloop when appsrc requests us to start sending data (need-data signal)
|
|
* and is removed when appsrc has enough data (enough-data signal).
|
|
*/
|
|
static gboolean
|
|
push_data (CustomData * data)
|
|
{
|
|
GstBuffer *buffer;
|
|
GstFlowReturn ret;
|
|
int i;
|
|
GstMapInfo map;
|
|
gint16 *raw;
|
|
gint num_samples = CHUNK_SIZE / 2; /* Because each sample is 16 bits */
|
|
gfloat freq;
|
|
|
|
/* Create a new empty buffer */
|
|
buffer = gst_buffer_new_and_alloc (CHUNK_SIZE);
|
|
|
|
/* Set its timestamp and duration */
|
|
GST_BUFFER_TIMESTAMP (buffer) =
|
|
gst_util_uint64_scale (data->num_samples, GST_SECOND, SAMPLE_RATE);
|
|
GST_BUFFER_DURATION (buffer) =
|
|
gst_util_uint64_scale (num_samples, GST_SECOND, SAMPLE_RATE);
|
|
|
|
/* Generate some psychodelic waveforms */
|
|
gst_buffer_map (buffer, &map, GST_MAP_WRITE);
|
|
raw = (gint16 *) map.data;
|
|
data->c += data->d;
|
|
data->d -= data->c / 1000;
|
|
freq = 1100 + 1000 * data->d;
|
|
for (i = 0; i < num_samples; i++) {
|
|
data->a += data->b;
|
|
data->b -= data->a / freq;
|
|
raw[i] = (gint16) (500 * data->a);
|
|
}
|
|
gst_buffer_unmap (buffer, &map);
|
|
data->num_samples += num_samples;
|
|
|
|
/* Push the buffer into the appsrc */
|
|
g_signal_emit_by_name (data->app_source, "push-buffer", buffer, &ret);
|
|
|
|
/* Free the buffer now that we are done with it */
|
|
gst_buffer_unref (buffer);
|
|
|
|
if (ret != GST_FLOW_OK) {
|
|
/* We got some error, stop sending data */
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* This signal callback triggers when appsrc needs data. Here, we add an idle handler
|
|
* to the mainloop to start pushing data into the appsrc */
|
|
static void
|
|
start_feed (GstElement * source, guint size, CustomData * data)
|
|
{
|
|
if (data->sourceid == 0) {
|
|
g_print ("Start feeding\n");
|
|
data->sourceid = g_idle_add ((GSourceFunc) push_data, data);
|
|
}
|
|
}
|
|
|
|
/* This callback triggers when appsrc has enough data and we can stop sending.
|
|
* We remove the idle handler from the mainloop */
|
|
static void
|
|
stop_feed (GstElement * source, CustomData * data)
|
|
{
|
|
if (data->sourceid != 0) {
|
|
g_print ("Stop feeding\n");
|
|
g_source_remove (data->sourceid);
|
|
data->sourceid = 0;
|
|
}
|
|
}
|
|
|
|
/* This function is called when an error message is posted on the bus */
|
|
static void
|
|
error_cb (GstBus * bus, GstMessage * msg, CustomData * data)
|
|
{
|
|
GError *err;
|
|
gchar *debug_info;
|
|
|
|
/* Print error details on the screen */
|
|
gst_message_parse_error (msg, &err, &debug_info);
|
|
g_printerr ("Error received from element %s: %s\n",
|
|
GST_OBJECT_NAME (msg->src), err->message);
|
|
g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
|
|
g_clear_error (&err);
|
|
g_free (debug_info);
|
|
|
|
g_main_loop_quit (data->main_loop);
|
|
}
|
|
|
|
/* This function is called when playbin has created the appsrc element, so we have
|
|
* a chance to configure it. */
|
|
static void
|
|
source_setup (GstElement * pipeline, GstElement * source, CustomData * data)
|
|
{
|
|
GstAudioInfo info;
|
|
GstCaps *audio_caps;
|
|
|
|
g_print ("Source has been created. Configuring.\n");
|
|
data->app_source = source;
|
|
|
|
/* Configure appsrc */
|
|
gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16, SAMPLE_RATE, 1, NULL);
|
|
audio_caps = gst_audio_info_to_caps (&info);
|
|
g_object_set (source, "caps", audio_caps, "format", GST_FORMAT_TIME, NULL);
|
|
g_signal_connect (source, "need-data", G_CALLBACK (start_feed), data);
|
|
g_signal_connect (source, "enough-data", G_CALLBACK (stop_feed), data);
|
|
gst_caps_unref (audio_caps);
|
|
}
|
|
|
|
int
|
|
tutorial_main (int argc, char *argv[])
|
|
{
|
|
CustomData data;
|
|
GstBus *bus;
|
|
|
|
/* Initialize cumstom data structure */
|
|
memset (&data, 0, sizeof (data));
|
|
data.b = 1; /* For waveform generation */
|
|
data.d = 1;
|
|
|
|
/* Initialize GStreamer */
|
|
gst_init (&argc, &argv);
|
|
|
|
/* Create the playbin element */
|
|
data.pipeline = gst_parse_launch ("playbin uri=appsrc://", NULL);
|
|
g_signal_connect (data.pipeline, "source-setup", G_CALLBACK (source_setup),
|
|
&data);
|
|
|
|
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
|
|
bus = gst_element_get_bus (data.pipeline);
|
|
gst_bus_add_signal_watch (bus);
|
|
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
|
|
&data);
|
|
gst_object_unref (bus);
|
|
|
|
/* Start playing the pipeline */
|
|
gst_element_set_state (data.pipeline, GST_STATE_PLAYING);
|
|
|
|
/* Create a GLib Main Loop and set it to run */
|
|
data.main_loop = g_main_loop_new (NULL, FALSE);
|
|
g_main_loop_run (data.main_loop);
|
|
|
|
/* Free resources */
|
|
gst_element_set_state (data.pipeline, GST_STATE_NULL);
|
|
gst_object_unref (data.pipeline);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
|
|
return gst_macos_main (tutorial_main, argc, argv, NULL);
|
|
#else
|
|
return tutorial_main (argc, argv);
|
|
#endif
|
|
}
|