mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-04-07 16:36:27 +00:00
Initial video code and RTP library.
This commit is contained in:
@@ -3,6 +3,8 @@ package com.limelight;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
@@ -11,12 +13,21 @@ import com.limelight.nvstream.input.NvController;
|
||||
import com.limelight.nvstream.input.NvInputPacket;
|
||||
|
||||
import tv.ouya.console.api.OuyaController;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaExtractor;
|
||||
import android.media.MediaFormat;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.MediaController;
|
||||
import android.widget.VideoView;
|
||||
|
||||
@@ -35,11 +46,16 @@ public class Game extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
setContentView(R.layout.activity_game);
|
||||
|
||||
OuyaController.init(this);
|
||||
|
||||
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this);
|
||||
SurfaceView sv = (SurfaceView) findViewById(R.id.surfaceView);
|
||||
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this, sv.getHolder().getSurface());
|
||||
conn.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.Surface;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.limelight.Game;
|
||||
@@ -21,13 +22,15 @@ public class NvConnection {
|
||||
|
||||
private NvControl controlStream;
|
||||
private NvController inputStream;
|
||||
private Surface video;
|
||||
|
||||
private ThreadPoolExecutor threadPool;
|
||||
|
||||
public NvConnection(String host, Activity activity)
|
||||
public NvConnection(String host, Activity activity, Surface video)
|
||||
{
|
||||
this.host = host;
|
||||
this.activity = activity;
|
||||
this.video = video;
|
||||
this.threadPool = new ThreadPoolExecutor(1, 1, Long.MAX_VALUE, TimeUnit.DAYS, new LinkedBlockingQueue<Runnable>());
|
||||
}
|
||||
|
||||
@@ -47,14 +50,10 @@ public class NvConnection {
|
||||
try {
|
||||
startSteamBigPicture();
|
||||
performHandshake();
|
||||
startVideo(video);
|
||||
beginControlStream();
|
||||
startController();
|
||||
|
||||
//new NvAudioStream().start();
|
||||
new NvVideoStream().start(host);
|
||||
|
||||
controlStream.startJitterPackets();
|
||||
|
||||
startController();
|
||||
} catch (XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
displayToast(e.getMessage());
|
||||
@@ -66,6 +65,11 @@ public class NvConnection {
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void startVideo(Surface surface)
|
||||
{
|
||||
new NvVideoStream().startVideoStream(host, surface);
|
||||
}
|
||||
|
||||
public void sendControllerInput(final short buttonFlags,
|
||||
final byte leftTrigger, final byte rightTrigger,
|
||||
final short leftStickX, final short leftStickY,
|
||||
|
||||
@@ -2,44 +2,53 @@ package com.limelight.nvstream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class NvVideoStream {
|
||||
public static final int PORT = 47998;
|
||||
import jlibrtp.DataFrame;
|
||||
import jlibrtp.Participant;
|
||||
import jlibrtp.RTPAppIntf;
|
||||
import jlibrtp.RTPSession;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodec.BufferInfo;
|
||||
import android.media.MediaFormat;
|
||||
import android.view.Surface;
|
||||
|
||||
public class NvVideoStream implements RTPAppIntf {
|
||||
public static final int RTP_PORT = 47998;
|
||||
public static final int RTCP_PORT = 47999;
|
||||
public static final int FIRST_FRAME_PORT = 47996;
|
||||
|
||||
private MediaCodec codec;
|
||||
|
||||
private InputStream getFirstFrame(String host) throws UnknownHostException, IOException
|
||||
{
|
||||
Socket s = new Socket(host, FIRST_FRAME_PORT);
|
||||
return s.getInputStream();
|
||||
}
|
||||
|
||||
public void start(final String host)
|
||||
public void startVideoStream(final String host, final Surface surface)
|
||||
{
|
||||
new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
byte[] firstFrame = new byte[98];
|
||||
try {
|
||||
System.out.println("VID: Waiting for first frame");
|
||||
InputStream firstFrameStream = getFirstFrame(host);
|
||||
|
||||
System.out.println(firstFrameStream.available());
|
||||
int i;
|
||||
for (i = 0; i < 98; i++)
|
||||
int offset = 0;
|
||||
do
|
||||
{
|
||||
if (firstFrameStream.read() == -1)
|
||||
{
|
||||
System.out.println("EOF on FF");
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.out.println("VID: First frame read "+i);
|
||||
offset = firstFrameStream.read(firstFrame, offset, firstFrame.length-offset);
|
||||
} while (offset != firstFrame.length);
|
||||
System.out.println("VID: First frame read ");
|
||||
} catch (UnknownHostException e2) {
|
||||
// TODO Auto-generated catch block
|
||||
e2.printStackTrace();
|
||||
@@ -50,57 +59,97 @@ public class NvVideoStream {
|
||||
return;
|
||||
}
|
||||
|
||||
final DatagramSocket ds;
|
||||
final DatagramSocket rtp, rtcp;
|
||||
try {
|
||||
ds = new DatagramSocket(PORT);
|
||||
rtp = new DatagramSocket(RTP_PORT);
|
||||
rtcp = new DatagramSocket(RTCP_PORT);
|
||||
} catch (SocketException e1) {
|
||||
// TODO Auto-generated catch block
|
||||
e1.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ping thread
|
||||
/*new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
byte[] ping = new byte[]{0x50, 0x49, 0x4e, 0x47};
|
||||
for (;;)
|
||||
{
|
||||
DatagramPacket dgp = new DatagramPacket(ping, 0, ping.length);
|
||||
dgp.setSocketAddress(new InetSocketAddress(host, PORT));
|
||||
try {
|
||||
ds.send(dgp);
|
||||
} catch (IOException e1) {
|
||||
// TODO Auto-generated catch block
|
||||
e1.printStackTrace();
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();*/
|
||||
|
||||
codec = MediaCodec.createDecoderByType("video/avc");
|
||||
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 1280, 720);
|
||||
codec.configure(mediaFormat, surface, null, 0);
|
||||
codec.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
||||
codec.start();
|
||||
|
||||
|
||||
int inputIndex = codec.dequeueInputBuffer(-1);
|
||||
if (inputIndex >= 0)
|
||||
{
|
||||
ByteBuffer buf = codec.getInputBuffers()[inputIndex];
|
||||
|
||||
buf.clear();
|
||||
buf.put(firstFrame);
|
||||
|
||||
codec.queueInputBuffer(inputIndex,
|
||||
0, firstFrame.length,
|
||||
100, MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
|
||||
}
|
||||
|
||||
RTPSession session = new RTPSession(rtp, rtcp);
|
||||
session.addParticipant(new Participant(host, RTP_PORT, RTCP_PORT));
|
||||
session.RTPSessionRegister(NvVideoStream.this, null, null);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
DatagramPacket dp = new DatagramPacket(new byte[1500], 1500);
|
||||
|
||||
try {
|
||||
ds.receive(dp);
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
|
||||
System.out.println("Got UDP 47998: "+dp.getLength());
|
||||
BufferInfo info = new BufferInfo();
|
||||
int outIndex = codec.dequeueOutputBuffer(info, -1);
|
||||
switch (outIndex) {
|
||||
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
|
||||
System.out.println("Output buffers changed");
|
||||
break;
|
||||
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
|
||||
System.out.println("Output format changed");
|
||||
break;
|
||||
case MediaCodec.INFO_TRY_AGAIN_LATER:
|
||||
System.out.println("Try again later");
|
||||
break;
|
||||
default:
|
||||
if (outIndex >= 0)
|
||||
{
|
||||
codec.releaseOutputBuffer(outIndex, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveData(DataFrame frame, Participant participant) {
|
||||
|
||||
ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
|
||||
|
||||
int inputIndex = codec.dequeueInputBuffer(-1);
|
||||
if (inputIndex >= 0)
|
||||
{
|
||||
ByteBuffer buf = codecInputBuffers[inputIndex];
|
||||
|
||||
buf.clear();
|
||||
buf.put(frame.getConcatenatedData());
|
||||
|
||||
if (buf.position() != 1024)
|
||||
{
|
||||
System.out.println("Data length: "+buf.position());
|
||||
System.out.println(buf.get()+" "+buf.get()+" "+buf.get());
|
||||
}
|
||||
|
||||
codec.queueInputBuffer(inputIndex,
|
||||
0, buf.position(),
|
||||
10000000, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEvent(int type, Participant[] participant) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int frameSize(int payloadType) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user