summaryrefslogtreecommitdiff
path: root/samples/BasicTutorial8.cs
blob: 02acfbddb796e78f6dc75b5c05c74a0166c59fe4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
// Authors
//   Copyright (C) 2014 Stephan Sundermann <stephansundermann@gmail.com>

using System;
using Gst;
using System.Runtime.InteropServices;

namespace GstreamerSharp
{
	class Playback
	{
		const int ChunkSize = 1024;
		const int SampleRate = 44100;

		static Gst.App.AppSink AppSink;
		static Gst.App.AppSrc AppSource;
		static Element Pipeline, Tee, AudioQueue, AudioConvert1, AudioResample, AudioSink;
		static Element VideoQueue, AudioConvert2, Visual, VideoConvert, VideoSink;
		static Element AppQueue;

		static long NumSamples;   // Number of samples generated so far (for timestamp generation)
		static float a, b, c, d;     // For waveform generation

		static uint Sourceid;        // To control the GSource

		static GLib.MainLoop MainLoop;  // GLib's Main Loop

		// This method is called by the idle GSource in the mainloop, to feed CHUNK_SIZE bytes into appsrc.
		// The idle 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 bool PushData () {
			var numSamples = ChunkSize / 2; // Because each sample is 16 bits
			MapInfo map;

			// Create a new empty buffer
			var buffer = new Gst.Buffer (null, ChunkSize, AllocationParams.Zero);

			// Set its timestamp and duration
			buffer.Pts = Util.Uint64Scale ((ulong)NumSamples, (ulong)Constants.SECOND, (ulong)SampleRate);
			buffer.Dts = Util.Uint64Scale ((ulong)NumSamples, (ulong)Constants.SECOND, (ulong)SampleRate);
			buffer.Duration = Util.Uint64Scale ((ulong)NumSamples, (ulong)Constants.SECOND, (ulong)SampleRate);

			// Generate some psychodelic waveforms
			buffer.Map (out map, MapFlags.Write);
			c += d;
			d -= c / 1000f;
			var freq = 1100f + 1000f * d;
			short[] data = new short[numSamples];
			for (int i = 0; i < numSamples; i++) {
				a += b;
				b -= a / freq;
				data[i] = (short)(500f * a);
			}
			// convert the short[] to a byte[] by marshalling
			var native = Marshal.AllocHGlobal (data.Length * sizeof(short));
			Marshal.Copy (data, 0, native, data.Length);
			byte[] bytedata = new byte[2 * data.Length];
			Marshal.Copy (native, bytedata, 0, data.Length * sizeof(short));

			map.Data = bytedata;
			buffer.Unmap (map);
			NumSamples += numSamples;

			// Push the buffer into the appsrc
			var ret = AppSource.PushBuffer (buffer);

			// Free the buffer now that we are done with it
			buffer.Dispose ();

			if (ret != FlowReturn.Ok) {
				// We got some error, stop sending data
				return false;
			}
			return true;
		}

		// This signal callback triggers when appsrc needs  Here, we add an idle handler
		// to the mainloop to start pushing data into the appsrc
		static void StartFeed (object sender, Gst.App.NeedDataArgs args) {
			if (Sourceid == 0) {
				Console.WriteLine ("Start feeding");
				Sourceid = GLib.Idle.Add (PushData);
			}
		}

		// This callback triggers when appsrc has enough data and we can stop sending.
		// We remove the idle handler from the mainloop
		static void StopFeed (object sender, EventArgs args) {
			if (Sourceid != 0) {
				Console.WriteLine ("Stop feeding");
				GLib.Source.Remove (Sourceid);
				Sourceid = 0;
			}
		}

		// The appsink has received a buffer
		static void NewSample (object sender, GLib.SignalArgs args) {
			var sink = (Gst.App.AppSink)sender;

			// Retrieve the buffer
			var sample = sink.PullSample ();
			if (sample != null) {
				// The only thing we do in this example is print a * to indicate a received buffer
				Console.Write ("*");
				sample.Dispose ();
			}
		}

		// This function is called when an error message is posted on the bus
		static void HandleError (object sender, GLib.SignalArgs args) {
			GLib.GException err;
			string debug;
			var msg = (Message) args.Args[0];

			// Print error details on the screen
			msg.ParseError (out err, out debug);
			Console.WriteLine ("Error received from element {0}: {1}", msg.Src.Name, err.Message);
			Console.WriteLine ("Debugging information: {0}", debug != null ? debug : "none");

			MainLoop.Quit ();
		}

		public static void Main (string[] args)
		{
			b = 1;
			d = 1;
			Gst.Audio.AudioInfo info = Gst.Audio.AudioInfo.Zero;

			// Initialize Gstreamer
			Gst.Application.Init(ref args);

			// Create the elements
			AppSource = new Gst.App.AppSrc ("app_src");
			Tee = ElementFactory.Make ("tee", "tee");
			AudioQueue = ElementFactory.Make ("queue", "audio_queue");
			AudioConvert1 = ElementFactory.Make ("audioconvert", "audio_convert1");
			AudioResample = ElementFactory.Make ("audioresample", "audio_resample");
			AudioSink = ElementFactory.Make ("autoaudiosink", "audio_sink");
			VideoQueue = ElementFactory.Make ("queue", "video_queue");
			AudioConvert2 = ElementFactory.Make ("audioconvert", "audio_convert2");
			Visual = ElementFactory.Make ("wavescope", "visual");
			VideoConvert = ElementFactory.Make ("videoconvert", "video_convert");
			VideoSink = ElementFactory.Make ("autovideosink", "video_sink");
			AppQueue = ElementFactory.Make ("queue", "app_queue");
			AppSink = new Gst.App.AppSink ("app_sink");

			// Create the empty pipeline
			var pipeline = new Pipeline ("test-pipeline");

			if (AppSource == null || Tee == null || AudioQueue == null || AudioConvert1 == null || AudioResample == null || 
				AudioSink == null || VideoQueue == null || AudioConvert2 == null || Visual == null || VideoConvert == null || 
				AppQueue == null || AppSink == null ||pipeline == null) {
				Console.WriteLine ("Not all elements could be created.");
				return;
			}

			// Configure wavescope
			Visual ["shader"] = 0;
			Visual ["style"] = 0;

			// Configure appsrc
			info.SetFormat (Gst.Audio.AudioFormat.S16, SampleRate, 1, (Gst.Audio.AudioChannelPosition) 0);
			var audioCaps = info.ToCaps ();
			AppSource ["caps"] = audioCaps;
			AppSource ["format"] = Format.Time;

			AppSource.NeedData += StartFeed;
			AppSource.EnoughData += StopFeed;

			// Configure appsink
			AppSink ["emit-signals"] = true;
			AppSink ["caps"] = audioCaps;
			AppSink.NewSample += NewSample;

			// Link all elements that can be automatically linked because they have "Always" pads
			pipeline.Add (AppSource, Tee, AudioQueue, AudioConvert1, AudioResample, 
				AudioSink, VideoQueue, AudioConvert2, Visual, VideoConvert, VideoSink, AppQueue, AppSink);
			if (!Element.Link (AppSource, Tee) ||
				!Element.Link (AudioQueue, AudioConvert1, AudioResample, AudioSink) ||
				!Element.Link (VideoQueue, AudioConvert2, Visual, VideoConvert, VideoSink) ||
				!Element.Link (AppQueue, AppSink)) {
				Console.WriteLine ("Elements could not be linked.");
				return;
			}

			// Manually link the Tee, which has "Request" pads
			var teeSrcPadTemplate = Tee.GetPadTemplate ("src_%u");
			var teeAudioPad = Tee.RequestPad (teeSrcPadTemplate);
			Console.WriteLine ("Obtained request pad {0} for audio branch.", teeAudioPad.Name);
			var queueAudioPad = AudioQueue.GetStaticPad ("sink");
			var teeVideoPad = Tee.RequestPad (teeSrcPadTemplate);
			Console.WriteLine ("Obtained request pad {0} for video branch.", teeVideoPad.Name);
			var queueVideoPad = VideoQueue.GetStaticPad ("sink");
			var teeAppPad = Tee.RequestPad (teeSrcPadTemplate);
			Console.WriteLine ("Obtained request pad {0} for app branch.", teeAppPad.Name);
			var queueAppPad = AppQueue.GetStaticPad ("sink");
			if (teeAudioPad.Link (queueAudioPad) != PadLinkReturn.Ok ||
				teeVideoPad.Link (queueVideoPad) != PadLinkReturn.Ok ||
				teeAppPad.Link (queueAppPad) != PadLinkReturn.Ok) {
				Console.WriteLine ("Tee could not be linked");
				return;
			}

			// Instruct the bus to emit signals for each received message, and connect to the interesting signals
			var bus = pipeline.Bus;
			bus.AddSignalWatch ();
			bus.Connect ("message::error", HandleError);

			// Start playing the pipeline
			pipeline.SetState (State.Playing);

			// Create a GLib Main Loop and set it to run
			MainLoop = new GLib.MainLoop ();
			MainLoop.Run ();

			// Release the request pads from the Tee, and unref them
			Tee.ReleaseRequestPad(teeAudioPad);
			Tee.ReleaseRequestPad(teeVideoPad);
			Tee.ReleaseRequestPad(teeAppPad);

			// Free resources
			pipeline.SetState (State.Playing);
		}
	}
}