Android和C#实时视频传输Demo

说起去年的Demo。以今天的免费整齐优势。

原理很easy,虽然没有写android申请书。但,好了~ 高级语言是相通的。傲慢约。就这么简单研究了一下api后,找到相机对象有一个预览回调方法。

意识到发生了什么Camera.PreviewCallback接口。就能够得到一个每一帧画面的回调事件,那么思路就非常easy了。

拿到画面后,进行下简单的压缩,然后把图像用Socket传输到server上。server上绑定到一个窗体的picBox上就能够了。

当然,这里还牵扯到多线程的问题,由于一个SocketServer能够实现和多个client建立连接,而每个连接都须要独立的线程来实现监听。

安卓端代码:

package com.xwg.monitorclient;


import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.List;
import java.util.zip.DeflaterOutputStream;

import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.app.Activity;
import android.content.SharedPreferences;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends Activity implements SurfaceHolder.Callback,
Camera.PreviewCallback{
	private SurfaceView mSurfaceview = null; // SurfaceView对象:(视图组件)视频显示
    private SurfaceHolder mSurfaceHolder = null; // SurfaceHolder对象:(抽象接口)SurfaceView支持类
    private Camera mCamera = null; // Camera对象,相机预览 
    
    /**服务器地址*/
    private String pUsername="XZY";
    /**服务器地址*/
    private String serverUrl="192.168.0.3";
    /**服务器端口*/
    private int serverPort=9999;
    /**视频刷新间隔*/
    private int VideoPreRate=1;
    /**当前视频序号*/
    private int tempPreRate=0;
    /**视频质量*/
    private int VideoQuality=85;
    
    /**发送视频宽度比例*/
    private float VideoWidthRatio=1;
    /**发送视频高度比例*/
    private float VideoHeightRatio=1;
    
    /**发送视频宽度*/
    private int VideoWidth=320;
    /**发送视频高度*/
    private int VideoHeight=240;
    /**视频格式索引*/
    private int VideoFormatIndex=0;
    /**是否发送视频*/
    private boolean startSendVideo=false;
    /**是否连接主机*/
    private boolean connectedServer=false;
    
    private Button myBtn01, myBtn02;
    
    private EditText txtIP;
    
    @Override
    public void onStart()//又一次启动的时候
    {	
    	mSurfaceHolder = mSurfaceview.getHolder(); // 绑定SurfaceView。取得SurfaceHolder对象
    	mSurfaceHolder.addCallback(this); // SurfaceHolder增加回调接口       
    	mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);// 设置显示器类型,setType必须设置
	    //读取配置文件
        SharedPreferences preParas = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
        pUsername=preParas.getString("Username", "XZY");
        serverUrl=preParas.getString("ServerUrl", "192.168.0.3");
    	String tempStr=preParas.getString("ServerPort", "9999");
    	serverPort=Integer.parseInt(tempStr);
        tempStr=preParas.getString("VideoPreRate", "1");
        VideoPreRate=Integer.parseInt(tempStr);	            
        tempStr=preParas.getString("VideoQuality", "85");
        VideoQuality=Integer.parseInt(tempStr);
        tempStr=preParas.getString("VideoWidthRatio", "100");
        VideoWidthRatio=Integer.parseInt(tempStr);
        tempStr=preParas.getString("VideoHeightRatio", "100");
        VideoHeightRatio=Integer.parseInt(tempStr);
        VideoWidthRatio=VideoWidthRatio/100f;
        VideoHeightRatio=VideoHeightRatio/100f;
        
        super.onStart();
    }
    
    @Override
    protected void onResume() {
        super.onResume();        
        InitCamera();
    }
    
    /**初始化摄像头*/
    private void InitCamera(){
    	try{
    		
    		mCamera = Camera.open();
    		List<Size> list = mCamera.getParameters().getSupportedPreviewSizes();
    		for(Size s : list)
    		{
    			if(s.width<=640)
    			{
    				Camera.Parameters params = mCamera.getParameters();
    				params.setPreviewSize(s.width, s.height);
    				mCamera.setParameters(params);
    				break;
    			}
    		}
    	} catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    @Override
    protected void onPause() {
    	try{
	        if (mCamera != null) {
	        	mCamera.setPreviewCallback(null); // 。。这个必须在前。不然退出出错
	            mCamera.stopPreview();
	            mCamera.release();
	            mCamera = null;
	        } 
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        super.onPause();
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
      //禁止屏幕休眠
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 
        		WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
               
        mSurfaceview = (SurfaceView) findViewById(R.id.camera_preview);
        myBtn01=(Button)findViewById(R.id.button1);
        myBtn02=(Button)findViewById(R.id.button2);
        txtIP = (EditText)findViewById(R.id.editText1);
        //開始连接主机按钮
        myBtn01.setOnClickListener(new OnClickListener(){
			public void onClick(View v) {
				serverUrl = txtIP.getText().toString();
				if(connectedServer){//停止连接主机,同一时候断开传输
					startSendVideo=false;
					connectedServer=false;					
					myBtn02.setEnabled(false);
					myBtn01.setText("開始连接");
					myBtn02.setText("開始传输");
					
					//断开连接
					//Thread th = new MySendCommondThread("PHONEDISCONNECT|"+pUsername+"|");
			  	  	//th.start(); 
					
				}
				else//连接主机
				{
					//启用线程发送命令PHONECONNECT
			  	  	
					connectedServer=true;
					myBtn02.setEnabled(true);
					myBtn01.setText("停止连接");
				}
			}});
        
        myBtn02.setEnabled(false);
        myBtn02.setOnClickListener(new OnClickListener(){
			public void onClick(View v) {
				if(startSendVideo)//停止传输视频
				{
					startSendVideo=false;
					myBtn02.setText("開始传输");
				}
				else{ // 開始传输视频
					Thread th = new MyThread();
			  	  	th.start(); 
					startSendVideo=true;
					myBtn02.setText("停止传输");
				}
			}});
    }
    


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }


    @Override
	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
		// TODO Auto-generated method stub
		if (mCamera == null) {
            return;
        }
        mCamera.stopPreview();
        mCamera.setPreviewCallback(this);
        mCamera.setDisplayOrientation(90); //设置横行录制
        
        //获取摄像头參数
        Camera.Parameters parameters = mCamera.getParameters();
        
        Size size = parameters.getPreviewSize();
        VideoWidth=size.width;
        VideoHeight=size.height;

        VideoFormatIndex=parameters.getPreviewFormat();
        
        mCamera.startPreview();
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		// TODO Auto-generated method stub
		try {
            if (mCamera != null) {
                mCamera.setPreviewDisplay(mSurfaceHolder);
                mCamera.startPreview();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } 
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		// TODO Auto-generated method stub
		if (null != mCamera) {
            mCamera.setPreviewCallback(null); // !。这个必须在前,不然退出出错
            mCamera.stopPreview();
            mCamera = null;
        }
	}

	@Override
	public void onPreviewFrame(byte[] data, Camera camera) {
		// TODO Auto-generated method stub
		//假设没有指令传输视频,就先不传
		if(!startSendVideo)
			return;
//		if(tempPreRate<VideoPreRate){
//			tempPreRate++;
//			return;
//		}
//		tempPreRate=0;		
		try {
		      if(data!=null)
		      {
		        YuvImage image = new YuvImage(data,VideoFormatIndex, VideoWidth, VideoHeight,null);
		        if(image!=null)
		        {
		        	ByteArrayOutputStream outstream = new ByteArrayOutputStream();
		        	
		      	  	//在此设置图片的尺寸和质量 
		      	  	image.compressToJpeg(new Rect(0, 0, (int)(VideoWidthRatio*VideoWidth), 
		      	  		(int)(VideoHeightRatio*VideoHeight)), VideoQuality, outstream);  
		      	  	outstream.flush();
		      	  	
		      	   SendMessage(outstream.toByteArray());
		      	
		      	  	//启用线程将图像数据发送出去
		      	  	//Thread th = new MySendFileThread(outstream.toByteArray());
		      	  	//th.start();  
		        }
		      }
		  } catch (IOException e) {
		      e.printStackTrace();
		  }
	}
	
	Socket client = null;
   
	public void SendMessage(byte[] content)
	{
		if(client==null)
			return;
		OutputStream outsocket;
		try{
		outsocket = client.getOutputStream();
		
		//BufferedOutputStream dos = new BufferedOutputStream(outsocket);
		DataOutputStream dos = new DataOutputStream(outsocket);
		int len = content.length;
		byte[] header = Int2Byte(len);
		//outsocket.write(header);
		
		dos.write(header);
		//outsocket.flush();
		//outsocket.write(content);
//		byte byteBuffer[] = new byte[1024];
//		ByteArrayInputStream inputstream = new ByteArrayInputStream(content);
//		
//		PrintWriter pw = new PrintWriter(outsocket);
//		
//        int amount;
//        while ((amount = inputstream.read(byteBuffer)) != -1) {
//        	outsocket.write(byteBuffer, 0, amount);
//        }
		//outsocket.write(content);
		dos.write(content);
		//dos.flush();
		//dos.close();
		//outsocket.flush();
		
		}catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	public byte[] Int2Byte(int len)
	{
		byte[] rtn = new byte[5];
		rtn[0] = (byte)0;
		rtn[1] = (byte)(len&0xff);
		rtn[2] = (byte)((len>>8)&0xff);
		rtn[3] = (byte)((len>>16)&0xff);
		rtn[4] = (byte)(len>>>24);
		return rtn;
		
	}
	
	/**发送命令线程*/
    class MyThread extends Thread{
    	
    	public void run(){
    		//实例化Socket  
            try {
            	client=new Socket(serverUrl,serverPort);
    			
    		} catch (UnknownHostException e) {
    		} catch (IOException e) {
    		}  
    	}
    }
    
    /**发送文件线程*/
    class MySendFileThread extends Thread{	
    	byte[] content = null;
    	public MySendFileThread(byte[] content){
    		this.content = content;
    	}
    	
        public void run() {
            try{
            	SendMessage(this.content);
                //tempSocket.close();                   
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

</pre><p></p><p>安卓端代码通过一个全局Socket创建连接,然后通过onPreviewFrame事件,捕获图像帧。然后压缩,处理成byte[]然后发送啦。</p><p>只是java这里没有int和byte[]的转换,非常二有木有,歧视一下java。

还得自己写代码转换,这里直接生成了header[],因为没设计其它操作,所以第一位默认0,</p><p>后4位是图片byte[]长度。

</p><p>然后依次发送数据出去。</p><p>其它的相机设置的代码,来自baidu。

</p><p>话说java这里不熟,所以比較乱。没有详细封装什么的。

</p>C#代码<p>ClientInfo。一个用来保存连接的实体类,嘛~因为Demo吗,没细致处理,一下也是同样原因,没有详细优化过,只是測试过wifi条件下,传600p左右画质,开2~3个client还是能够的。

</p><p></p><pre code_snippet_id="419870" snippet_file_name="blog_20140707_3_9307192" name="code" class="csharp">using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Net.Sockets; namespace com.xwg.net { public class ClientInfo { public Thread ReceiveThread; public Socket Client; public string ip; public string name; } }


serverSocket管理类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using xwg.common;
using System.IO;

namespace com.xwg.net
{
    public class SocketServer
    {
        #region 变量定义
        List<ClientInfo> clientList = null;

        private Socket socketServer = null;

        /// <summary>
        /// 服务器IP
        /// </summary>
        private IPAddress serverIP;

        /// <summary>
        /// 监听端口号
        /// </summary>
        private int portNo = 15693;


        /// <summary>
        /// 完整终端地址包括端口
        /// </summary>
        private IPEndPoint serverFullAddr;

        // Server监听线程
        Thread accpetThread = null;

        #endregion

        #region 构造函数
        public SocketServer(string ServerIP)
        {
            this.serverIP = IPAddress.Parse(ServerIP);
        }

        public SocketServer(string ServerIP, int portNo)
        {
            this.serverIP = IPAddress.Parse(ServerIP);
            this.portNo = portNo;
        }
        #endregion

        #region Event

        // 客户端接入事件
        public event ClientAccepted OnClientAccepted;

        // 连接接收数据事件
        public event StreamReceived OnStreamReceived;

        public event ClientBreak OnClentBreak;

        #endregion

        public void StartListen()
        {
            //取得完整地址
            serverFullAddr = new IPEndPoint(serverIP, portNo);//取端口号
            try
            {
                // 实例化Server对象
                socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                // 绑定监听端口
                socketServer.Bind(serverFullAddr);

                // 启动监听。制定最大20挂起
                socketServer.Listen(20);

            }
            catch (Exception e)
            {
                Logger.Write("StartListen方法:"+e.Message);
            }

            clientList = new List<ClientInfo>();

            accpetThread = new Thread(new ThreadStart(AcceptSocket));
            accpetThread.IsBackground = true;
            accpetThread.Start();
        }

        private void AcceptSocket()
        {
            while (true)
            {
                // client Socket 获得客户端连接
                Socket acceptSock = socketServer.Accept();
                
                Logger.Write("接收到连接!");
                string ip = ((IPEndPoint)acceptSock.RemoteEndPoint).Address.ToString();
                Logger.Write("客户端IP:");
                ClientInfo info = new ClientInfo();
                info.Client = acceptSock;
                info.ip = ip;
                info.name = ip;

                Thread recThread = new Thread(new ParameterizedThreadStart(ReceiveMsg));
                recThread.IsBackground = true;
                info.ReceiveThread = recThread;

                clientList.Add(info);
                
                recThread.Start(info);

                // 客户端接入响应事件
                if (OnClientAccepted != null)
                    OnClientAccepted(this, info);
            }
        }

        private void ReceiveMsg(object obj)
        {
            ClientInfo info = (ClientInfo)obj;
            Socket clientSock = info.Client;
            try
            {
                while (true)
                {
                    // 推断连接状态
                    if (!clientSock.Connected)
                    {
                        clientList.Remove(info);
                        info.ReceiveThread.Abort();
                        clientSock.Close();

                    }

                    try
                    {
                        byte[] header = new byte[5];
                        int len = clientSock.Receive(header,SocketFlags.None);
                        if (len != 5)
                        {
                            // 错误 终端连接
                            Logger.Write("数据头接收错误,长度不足:" + len);
                            clientSock.Close();
                            info.ReceiveThread.Abort();
                            return;
                        }

                        int conLen = BitConverter.ToInt32(header, 1);

                        //byte[] content = new byte[conLen];
                        //len = clientSock.Receive(content, SocketFlags.None);
                        MemoryStream stream = new MemoryStream();
                        //byte [] buffer = new byte[1024];
                        //while ((len = clientSock.Receive(buffer)) > 0)
                        //{
                        //    stream.Write(buffer,0,len);
                        //}
                        //if (conLen != stream.Length)
                        //{
                        //    Logger.Write("长度错误:"+stream.Length+"/"+conLen);
                        //}
                        for (int i = 0; i < conLen; i++)
                        {
                            byte[] arr = new byte[1];
                            clientSock.Receive(arr,SocketFlags.None);
                            stream.Write(arr,0,1);
                        }

                        //stream.Write(content,0,content.Length);
                        stream.Flush();
                        
                        //len = clientSock.Receive(content, SocketFlags.None);

                        //if (len != conLen)
                        //{
                        //    // 错误 终端连接
                        //    Logger.Write("header:" + header[1] + "," + header[2] + "," + header[3] + "," + header[4]);
                        //    Logger.Write("Content接收错误。长度不足:" + len+"/"+conLen);
                        //    clientSock.Close();
                        //    return;
                        //}

                        // 接收事件
                        if (OnStreamReceived != null)
                        {
                            OnStreamReceived(info, stream);
                        }
                    }
                    catch (Exception ex)
                    {
                        if (OnClentBreak != null)
                        {
                            OnClentBreak(this, info);
                        }
                        Logger.Write("Receive数据:"+ex.Message);
                        clientSock.Close();
                        return;
                    }
                }
            }
            catch (Exception e)
            {
                if (OnClentBreak != null)
                {
                    OnClentBreak(this, info);
                }
                Logger.Write("ReceiveMsg:" + e.Message);
                clientSock.Close();
                return;
            }
        }
    }
    
    public delegate void ClientAccepted(object sender,ClientInfo info);

    public delegate void StreamReceived(object sender,MemoryStream stream);

    public delegate void ClientBreak(object sender,ClientInfo info);
}

这里面涉及了Socket和多线程处理的知识。

简单一说,Socket既能够做Server,也能够做Client,当然你用TCPListener也一样效果就是了。

这里因为是服务端,所以Socket被我Bind到了一个port上面,启用了Listen,监听方法。

然后启用了一个accpet线程。总用时实现port监听。

每当accpet到一个client的时候,会触发 OnClientAccepted 事件。accpet方法是会触发堵塞的,所以绝对不能够用主线程。否则就是程序无响应。

C#处理过程中,实现封装的最好方法就是使用事件机制了,这是我觉得比Java方便的多的设计。能够把逻辑的实现。全然的抛出封装对象。

然后就是AcceptSocket 这种方法了,这种方法其中,一旦接收到client连接,会创建一个ClientInfo对象。把一些相关属性设置上去保存。

同一时候新建一个线程,实现ReceiveMessage的监听。IsBackground是一个小技巧,表示主线程结束时,把这些线程同一时候结束掉。

比起手动结束方便多。

假设你应用关闭,发现程序还在后台跑,那么多数是因为创建的线程没有结束的原因。这时候这个属性会起到关键作用。

ReceiveMsg是接收数据的方法。这里简单定义了一个数据协议,数据发送时,分成两部分发送,先发送一个5byte的header。然后是实际内容content。

header第一位表示操作标示(由于demo简单,所以没有详细设计协议,这个能够自己定义的,也是通用的处理方法)。后4位标示content内容流的长度,这样取数据的时候,就不至于乱掉。

正常来说,Socket.Receive是有堵塞的啦,只是这里不知道为什么,接收的时候有问题,怀疑安卓socket流导致的。所以没办法直接定义buffer。一次性接受全部content。因为时间紧。没细致研究。反正总长度是一定不会变的,所以直接循环处理了...偷懒了有木有...

接受到信息后,通过事件OnStreamReceived 吧数据流返回出去。

服务端基本就这样了。由于不牵扯双向消息。所以没处理send啦。

然后就是界面:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using com.xwg.net;
using System.IO;
using xwg.common;


namespace MonitorServer
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        SocketServer server = null;
        List<ViewForm> list = new List<ViewForm>();
        //ViewForm vf = new ViewForm();

        private void button1_Click(object sender, EventArgs e)
        {
            
            server = new SocketServer(txtIP.Text, int.Parse(txtPort.Text));
            Logger.Write("server = new SocketServer(txtIP.Text, int.Parse(txtPort.Text));");
            server.OnClientAccepted += ClientAccepted;
            server.OnStreamReceived += StreamReceived;
            server.OnClentBreak += ClientBreak;
            server.StartListen();
            Logger.Write("StartListen");
            button1.Enabled = false;
        }

        public void AddClient(string ip)
        {
            try
            {
                this.Invoke((EventHandler)delegate
                {
                    listBox1.Items.Add(ip);
                });
            }
            catch (Exception e)
            {

            }
        }

        public void RemoveClient(string ip)
        {
            try
            {
                this.Invoke((EventHandler)delegate
                {
                    listBox1.Items.Remove(ip);
                });
            }
            catch (Exception e)
            {

            }
        }

        public void CloseViewForm(string ip)
        {
            try
            {
                ViewForm vf = GetViewByIP(ip);
                if (vf == null)
                    return;
                vf.Invoke((EventHandler)delegate
                {
                    vf.Close();
                });
            }
            catch (Exception e)
            {

            }
        }

        public void SetViewImage(ViewForm vf, MemoryStream stream)
        {
            try
            {
                vf.Invoke((EventHandler)delegate
                {
                    Image img = Image.FromStream(stream);
                    vf.SetImage(img);
                    stream.Close();
                });
            }
            catch (Exception e)
            {

            }
        }

        protected void ClientAccepted(object sender, ClientInfo info)
        {
            Logger.Write("ClientAccepted:"+info.ip);
            AddClient(info.ip);
            
            //ViewForm vf = new ViewForm();
            //list.Add(vf);
            //vf.SetTitle(info.ip);
            //vf.Show();
        }

        private ViewForm GetViewByIP(string ip)
        {
            foreach (ViewForm vf in list)
            {
                if (vf.Text == ip)
                    return vf;
            }
            return null;
        }

        protected void StreamReceived(object sender, MemoryStream stream)
        {
            ClientInfo info = (ClientInfo) sender;
            try
            {
                //Image img = Image.FromStream(stream);
                img.Save("a.jpg"); 
                ViewForm vf = GetViewByIP(info.ip);
                if (vf == null)
                {
                    stream.Close();
                    return;
                }
                SetViewImage(vf, stream);
                
                //vf.SetImage(img);
                //stream.Close();
            }
            catch (Exception e)
            {
                Logger.Write("StreamReceived:"+e.Message);
            }
        }

        protected void ClientBreak(object sender, ClientInfo info)
        {
            CloseViewForm(info.ip);
            list.Remove(GetViewByIP(info.ip));
            RemoveClient(info.ip);
        }

        private void listBox1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (listBox1.SelectedItem == null)
                return;
            string ip = listBox1.SelectedItem.ToString();
            ViewForm vf = new ViewForm();
            list.Add(vf);
            vf.SetTitle(ip);
            vf.Show();
        }
    }
}

界面实现了那几个事件,通过事件机制得到数据,进行UI设置。(界面嘛~就是UI层啦。一般仅仅关注UI,不要放逻辑代码)

然后用来展示的窗口

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace MonitorServer
{
    public partial class ViewForm : Form
    {
        public ViewForm()
        {
            InitializeComponent();
        }

        public void SetTitle(string title)
        {
            this.Text = title;
        }

        public void SetImage(Image img)
        {
            pictureBox1.Image = img;
        }
    }
}

事实上就是绑定了一个pictureBox啦。

到此就结束了,启动server应用。開始监听。然后启动安卓端应用,输入server地址(要保证一个网络中),连接。

服务端就会发现client连接,这时。双击ip,就会打开预览窗体。

支持多client连接预览,可是打开多了会卡,毕竟演示demo。没处理优化。

实际上,应该是连接后,server发送命令给client,client才開始传图片流,如今没处理。所以比較卡哦。

而且这样的图片帧传输的方法。尽管比較清晰。可是压缩比小。会产生大量流量。所以仅仅能演示用哦。

对了,还一个原因,这里用的tcp协议,可以保证数据包丢失重发。可是该机制会导致性能瓶颈,重发就会有时间影响哦,网络不好。easy出现抖动等现象,事实上是因为丢包引起的。这里可以换用udp,尽管可能会出现丢帧,可是抖动现象应该会有改善,速度也会比較快。

而且这里不支持音频哦,假设想要完美实现的话。还是用上一篇文章提到的方法吧。

做一个控制server,然后实现C#client和androidclient的直连,效果应该比較好。当然,这里也是因为spyroid这个项目。内置实现了rtsp协议server的原因啦。

站在巨人身上总是会让事情变得简单。这里感谢国外开源项目组,同一时候歧视一下国内人员,百度到实用的东西,都不放出源代码,而是要收费。。。

希望对大家实用。

对了,源代码我上传到资源里面了,大家能够去我空间下载,包括安卓和C#后台完整项目代码。

地址:http://download.csdn.net/detail/lanwilliam/7602669

10资源子其实真的不高,调试后,1天离开。

版权声明:欢迎转载,但请保留源解释

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值