-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathStreaming.java
298 lines (256 loc) · 8.97 KB
/
Streaming.java
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
package processing.streaming;
/**
* ##library.name##
* ##library.sentence##
* ##library.url##
*
* Copyright ##copyright## ##author##
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*
* @author ##author##
* @modified ##date##
* @version ##library.prettyVersion## (##library.version##)
*
* HUGE Thanks to Andres Colubri and Gottfried Haider, without their help this library wouldn't be possible
*/
import java.io.File;
import processing.core.*;
import java.lang.reflect.*;
public class Streaming {
private static boolean loaded = false;
private static boolean error = false;
//Dimensions of the Processing screen
int height;
int width;
// capabilities passed to GStreamer
protected static String caps; //"video/x-raw,format=RGB,width=640,height=360,pixel-aspect-ratio=1/1";
private static String pipeline;
private long handle = 0;
private PApplet parent;
private Method movieEventMethod;
//Use this constructor when just playing a movie (no streaming)
public Streaming(PApplet parent, String fn, int width, int height) {
super();
this.parent = parent;
this.width = width;
this.height = height;
loadGstreamer(height, width);
// get absolute path for filename
if (fn.indexOf("://") != -1) {
// got URI, use as is
} else {
// first, check Processing's dataPath
File file = new File(parent.dataPath(fn));
if (file.exists() == false) {
// next, the current directory
file = new File(fn);
}
if (file.exists()) {
fn = file.getAbsolutePath();
}
}
/*
* This is a simple pipeline to launch a video
* Videoconvert handles conversion between many different common video types
* The format string will be filled in in the native code
* appsink must be named "sink"
*/
pipeline = "uridecodebin uri=%s ! videoconvert ! videoscale ! appsink name=sink caps=\"" + caps + "\"";
//false because this pipeline is not streaming and needs to be loaded from the local disk
handle = gstreamer_loadFile(fn, pipeline, false);
if (handle == 0) {
throw new RuntimeException("Could not load video");
}
}
//Simpler constructor when not loading a video (audio streaming or videostreaming, for example)
public Streaming(PApplet parent, int width, int height) {
super();
this.parent = parent;
this.width = width;
this.height = height;
loadGstreamer(height, width);
}
//Links against libstreamvideo.so, the JNI binding to the native c code in impl.c
private void loadGstreamer(int height, int width)
{
if (!loaded) {
System.out.println(System.getProperty("java.library.path"));
System.loadLibrary("streamvideo");
loaded = true;
if (gstreamer_init() == false) {
error = true;
}
//Give the dimensions to GStreamer, and tell it the format of the video
if(width > 0 && height > 0){
caps = "video/x-raw,format=RGB,"
+ "width=" + Integer.toString(width)
+ ",height=" + Integer.toString(height)
+ ",pixel-aspect-ratio=1/1";
}
}
if (error) {
throw new RuntimeException("Could not load gstreamer");
}
try {
movieEventMethod = parent.getClass().getMethod("movieEvent", int[].class);
} catch (Exception e) {
// no such method, or an error... which is fine, just ignore
}
}
public void videoReceive(int port)
{
// appsink must be named "sink"
pipeline = "udpsrc port=" + Integer.toString(port)
+ " caps=\"application/x-rtp, payload=127\" ! rtph264depay ! avdec_h264 ! videoconvert ! videoscale ! appsink name=sink caps=\""
+ caps + "\"";
//filename is set to null because we are streaming from network and don't need to load file
//streaming is set to true
handle = gstreamer_loadFile(null, pipeline, true);
if (handle == 0) {
throw new RuntimeException("Could not launch stream receive pipeline");
}
}
public void videoBroadcast(String ip, int port, String filename)
{
pipeline = "filesrc location=" + filename
+ " ! decodebin name=dec ! queue ! videoconvert ! x264enc pass=qual quantizer=20 tune=zerolatency "
+ "! rtph264pay config-interval=1 ! udpsink host=" + ip
+ " port=" + Integer.toString(port)
+ " sync=true dec. ! queue ! audioconvert ! audioresample ! autoaudiosink";
//System.out.println("Running: " + pipeline);
gstreamer_pipeline_launch(pipeline);
}
public void dispose() {
System.out.println("called dispose");
}
public void play() {
gstreamer_play(handle, true);
}
public void pause() {
gstreamer_play(handle, false);
}
public void stop() {
gstreamer_play(handle, false);
gstreamer_seek(handle, 0.0f);
}
public void loop() {
gstreamer_set_loop(handle, true);
gstreamer_play(handle, true);
}
public void noLoop() {
gstreamer_set_loop(handle, false);
}
public void jump(float sec) {
gstreamer_seek(handle, sec);
}
public float duration() {
return gstreamer_get_duration(handle);
}
public float time() {
return gstreamer_get_time(handle);
}
public PImage getFrame() {
byte[] buffer = gstreamer_get_frame(handle);
if (buffer == null) {
return null;
}
PImage frame = parent.createImage(width, height, PConstants.RGB);
// TODO: Colors are slightly off, need a better way to
// TODO: we also need to handle the audio somehow
int idx = 0;
frame.loadPixels();
for (int i = 0; i < buffer.length/3; i++) {
int ri = 3 * i + 0;
int gi = 3 * i + 1;
int bi = 3 * i + 2;
int r = buffer[ri] & 0xff;
int g = buffer[gi] & 0xff;
int b = buffer[bi] & 0xff;
frame.pixels[idx++] = 0xFF000000 | (r << 16) | (g << 8) | b;
}
frame.updatePixels();
/*
if (movieEventMethod != null) {
try {
movieEventMethod.invoke(parent, pixels);
} catch (Exception e) {
e.printStackTrace();
movieEventMethod = null;
}
}
*/
return frame;
}
//Statically launch a pipeline - just a wrapper for native GStreamer functions
public static void pipelineLaunch(String pipe)
{
if (!loaded) {
System.out.println(System.getProperty("java.library.path"));
System.loadLibrary("streamvideo");
loaded = true;
if (gstreamer_init() == false) {
error = true;
}
}
if (error) {
throw new RuntimeException("Could not load gstreamer");
}
gstreamer_pipeline_launch(pipe);
}
//Takes an audio file and streams it via local host and port 6969, no argument default
//Use the corresponding method receiveAudio() to easily receive the stream
public void streamAudio(String filepath)
{
streamAudio(filepath, 8008, "127.0.0.1");
}
//TODO: Threading!
public void streamAudio(String filepath, int port, String host)
{
//Validate Filepath
File f = new File(filepath);
if(f.exists())
{
String portt = Integer.toString(port);
String pipe = "filesrc location=" + filepath + " ! decodebin ! audioconvert ! rtpL16pay ! udpsink port=" + portt + " host=" + host;
gstreamer_pipeline_launch(pipe);
}
else
{
System.err.println("Cannot find file! Cannot stream");
}
}
//Assumes there is already data coming in from streamAudio(), receives from localhost and port 6969 (no argument default)
public void receiveAudio()
{
receiveAudio(8008);
}
public void receiveAudio(int port)
{
String portt = Integer.toString(port);
gstreamer_pipeline_launch("udpsrc port=" + portt + " caps=\"application/x-rtp, media=(string)audio, format=(string)S32LE, layout=(string)interleaved, clock-rate=(int)44100, channels=(int)2, payload=(int)0\" ! rtpL16depay ! playsink");
}
//Native methods are implemented in impl.c in the native folder
private static native boolean gstreamer_init();
private native long gstreamer_loadFile(String fn, String pipeline, boolean live);
private native void gstreamer_play(long handle, boolean play);
private native void gstreamer_seek(long handle, float sec);
private native void gstreamer_set_loop(long handle, boolean loop);
private native float gstreamer_get_duration(long handle);
private native float gstreamer_get_time(long handle);
private native byte[] gstreamer_get_frame(long handle);
private static native void gstreamer_pipeline_launch(String pipe);
}