zoukankan      html  css  js  c++  java
  • android + red5 + rtmp

    背景:在已有的red5服务器环境下实现android客户端的视频直播

    要实现客户端视频直播就先先对服务器端有所了解

    Red5流媒体服务器是Adboe的产品,免费并且是开源的,与Flash搭配的时候可谓是天生一对,但使用Java和Android作为客户端调用却可谓一波三折。
    Red5是一个采用Java开发开源的Flash流媒体服务器。它支持:把音频(MP3)和视频(FLV)转换成播放流; 录制客户端播放流(只支持FLV);共享对象;现场直播流发布;远程调用。Red5使用RSTP作为流媒体传输协议,在其自带的一些示例中演示了在线录制,flash流媒体播放,在线聊天,视频会议等一些基本功能。

    服务器的相关配置

     <context-param>
           <param-name>webAppRootKey</param-name>
           <param-value>/FlashMediaGateway</param-value>
     </context-param>
    
    [root@lapaccweb WEB-INF]# more red5-web.properties 
    webapp.contextPath=/FlashMediaGateway
    webapp.virtualHosts=*:1935,*:443
    recordPath=/opt/Avaya/AccWeb/data/SessionRecording/
    playbackPath=/opt/Avaya/AccWeb/data/SessionRecording/
    absolutePath=true
    















    Adobe的Red5源代码里有一个RTMPClient的类,这个类在使用上其实不复杂,但却没办法成功调用。观察日志,发现是连接成功后在开始创建流的时候,服务端把连接断开了。我能想到的解释就是可能公司现在所使用的Red5服务器的版本与这个RTMPClient不兼容。

    国外还有一个收费的RTMPClient,价值是395刀。具体网址和产品的名称我就不指出了,有心人肯定会找得到。这个客户端类库很强大,使用也很方便,我注册了一个试用的key,发现能和Red5服务器成功连接并且通讯良好。(国内有破解版)

    之前用vlc和Vitamio尝试过,结果播放不了直播流,可能是我太笨了吧.
    有可能的原因如下:
    http://blog.csdn.net/qiuchangyong/article/details/18862247

    利用LocalServerSocket采集视频数据的实时流数据,android自带的LocalServerSocket是和java 的serverSocket是不同的,因为LocalServerSocket的客户端和服务端都是必须在本机,所以我们可以在调用RecodMedia录制视频的时候建立客户端LocalSocket来向服务端发送数据,在服务端接收数据调用juv—rtmp—client将接收的数据打包成RTMP协议数据向流媒体服务器发送数据。
    利用android自带的Camera来录制数据,这里主要用到了照相机的CallBack回调接口,在这个回调接口里面可以实时的接收照相机录制的视频数据,这里面录制的视频数据是YUV420SP格式的数据流,这是最原始的数据流,是无法显示的服务器界面的。将数据流转化成YUV420SP2RGB格式的数据,然后利用juv—rtmp—client向流媒体服务器发送数据。

    juv—rtmp—client这个jar包封装了很多和流媒体服务器交互的方法,当然有视频数据传输格式。在流媒体服务提供了三种格式的传输方式:Record、Live、Append。

    利用juv—rtmp—client有几个主要的类

    其中最重要的几个类是NetConnection,NetStream, License,其中NetConnection,NetStream这两个类是负责创建连接和回调服务端的数据。而License则顾名思义是负责验证有没有授权。由于按照官方给出的使用说明,在使用前必须调用License.setKey()方法传入注册所得到的key。

    CameraVideoActivity

    	private void init() {
    		aCamera = new AndroidCamera(CameraVideoActivity.this);
    		hour = (TextView) findViewById(R.id.mediarecorder_TextView01);
    		minute = (TextView) findViewById(R.id.mediarecorder_TextView03);
    		second = (TextView) findViewById(R.id.mediarecorder_TextView05);
    		mStart = (Button) findViewById(R.id.mediarecorder_VideoStartBtn);
    		mStop = (Button) findViewById(R.id.mediarecorder_VideoStopBtn);
    		mReturn = (Button) findViewById(R.id.mediarecorder_VideoReturnBtn);
    		// 开始录像
    		mStart.setOnClickListener(new OnClickListener() {
    
    			@Override
    			public void onClick(View v) {
    				// TODO Auto-generated method stub
    				if (streaming == false) {
    					aCamera.start();
    				}
    				aCamera.startVideo();
    				isTiming = true;
    				handler.postDelayed(task, 1000);
    				// 设置按钮状态
    				mStart.setEnabled(false);
    				mReturn.setEnabled(false);
    				mStop.setEnabled(true);
    			}
    		});
    		mReturn.setOnClickListener(new OnClickListener() {
    
    			@Override
    			public void onClick(View v) {
    				// TODO Auto-generated method stub
    				if (RTMPConnectionUtil.netStream != null) {
    					RTMPConnectionUtil.netStream.close();
    					RTMPConnectionUtil.netStream = null;
    				}
    				if (aCamera != null) {
    					aCamera = null;
    
    				}
    				finish();
    			}
    		});
    		mStop.setOnClickListener(new OnClickListener() {
    
    			@Override
    			public void onClick(View v) {
    				// TODO Auto-generated method stub
    				aCamera.stop();
    				// 设置按钮状态
    				mStart.setEnabled(true);
    				mReturn.setEnabled(true);
    				mStop.setEnabled(false);
    				isTiming = false;
    			}
    		});
    	}
    
    	public class AndroidCamera extends AbstractCamera implements
    			SurfaceHolder.Callback, Camera.PreviewCallback {
    
    		private SurfaceView surfaceView;
    		private SurfaceHolder surfaceHolder;
    		private Camera camera;
    
    		private int width;
    		private int height;
    
    		private boolean init;
    
    		int blockWidth;
    		int blockHeight;
    		int timeBetweenFrames; // 1000 / frameRate
    		int frameCounter;
    		byte[] previous;
    
    		public AndroidCamera(Context context) {
    
    			surfaceView = (SurfaceView) findViewById(R.id.mediarecorder_Surfaceview);
    			surfaceView.setVisibility(View.VISIBLE);
    			surfaceHolder = surfaceView.getHolder();
    			surfaceHolder.addCallback(AndroidCamera.this);
    			surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    
    			width = 352 / 2;
    			height = 288 / 2;
    
    			init = false;
    			Log.d("DEBUG", "AndroidCamera()");
    		}
    
    		private void startVideo() {
    			Log.d("DEBUG", "startVideo()");
    
    			RTMPConnectionUtil.ConnectRed5(CameraVideoActivity.this);
    			RTMPConnectionUtil.netStream = new UltraNetStream(
    					RTMPConnectionUtil.connection);
    			RTMPConnectionUtil.netStream
    					.addEventListener(new NetStream.ListenerAdapter() {
    
    						@Override
    						public void onNetStatus(final INetStream source,
    								final Map<String, Object> info) {
    							Log.d("DEBUG", "Publisher#NetStream#onNetStatus: "
    									+ info);
    
    							final Object code = info.get("code");
    
    							if (NetStream.PUBLISH_START.equals(code)) {
    								if (CameraVideoActivity.aCamera != null) {
    									RTMPConnectionUtil.netStream
    											.attachCamera(aCamera, -1 /* snapshotMilliseconds */);
    									Log.d("DEBUG", "aCamera.start()");
    									aCamera.start();
    								} else {
    									Log.d("DEBUG", "camera == null");
    								}
    							}
    						}
    					});
    
    			RTMPConnectionUtil.netStream.publish("anystringdata", NetStream.LIVE);
    		}
    
    		public void start() {
    			camera.startPreview();
    			streaming = true;
    		}
    
    		public void stop() {
    			camera.stopPreview();
    			streaming = false;
    		}
    
    		public void printHexString(byte[] b) {
    			for (int i = 0; i < b.length; i++) {
    				String hex = Integer.toHexString(b[i] & 0xFF);
    				if (hex.length() == 1) {
    					hex = '0' + hex;
    				}
    
    			}
    		}
    
    		@Override
    		public void onPreviewFrame(byte[] arg0, Camera arg1) {
    			// TODO Auto-generated method stub
    			// if (!active) return;
    			if (!init) {
    				blockWidth = 32;
    				blockHeight = 32;
    				timeBetweenFrames = 100; // 1000 / frameRate
    				frameCounter = 0;
    				previous = null;
    				init = true;
    			}
    			final long ctime = System.currentTimeMillis();
    
    			/** 将采集的YUV420SP数据转换为RGB格式 */
    			byte[] current = RemoteUtil.decodeYUV420SP2RGB(arg0, width, height);
    			try {
    				//
    
    				final byte[] packet = RemoteUtil.encode(current, previous,
    						blockWidth, blockHeight, width, height);
    				fireOnVideoData(new MediaDataByteArray(timeBetweenFrames,
    						new ByteArray(packet)));
    				previous = current;
    				if (++frameCounter % 10 == 0)
    					previous = null;
    
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    			final int spent = (int) (System.currentTimeMillis() - ctime);
    			try {
    
    				Thread.sleep(Math.max(0, timeBetweenFrames - spent));
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    
    		@Override
    		public void surfaceChanged(SurfaceHolder holder, int format, int width,
    				int height) {
    			// TODO Auto-generated method stub
    			// camera.startPreview();
    			// camera.unlock();
    
    			// Log.d("DEBUG", "surfaceChanged()");
    
    			// startVideo();
    		}
    
    		@Override
    		public void surfaceCreated(SurfaceHolder holder) {
    			// TODO Auto-generated method stub
    			camera = Camera.open();
    			try {
    				camera.setPreviewDisplay(surfaceHolder);
    				camera.setPreviewCallback(this);
    				Camera.Parameters params = camera.getParameters();
    				params.setPreviewSize(width, height);
    				camera.setParameters(params);
    			} catch (IOException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    				camera.release();
    				camera = null;
    			}
    
    			Log.d("DEBUG", "surfaceCreated()");
    		}
    
    		@Override
    		public void surfaceDestroyed(SurfaceHolder holder) {
    			// TODO Auto-generated method stub
    			if (camera != null) {
    				camera.stopPreview();
    				camera.release();
    				camera = null;
    			}
    
    		}
    
    	}
    
    	/* 定时器设置,实现计时 */
    	private Handler handler = new Handler();
    	int s, h, m, s1, m1, h1;
    	private Runnable task = new Runnable() {
    		public void run() {
    			if (isTiming) {
    				handler.postDelayed(this, 1000);
    				s++;
    				if (s < 60) {
    					second.setText(format(s));
    				} else if (s < 3600) {
    					m = s / 60;
    					s1 = s % 60;
    					minute.setText(format(m));
    					second.setText(format(s1));
    				} else {
    					h = s / 3600;
    					m1 = (s % 3600) / 60;
    					s1 = (s % 3600) % 60;
    					hour.setText(format(h));
    					minute.setText(format(m1));
    					second.setText(format(s1));
    				}
    			}
    		}
    	};
    



    RTMPConnectionUtil

    	public static void ConnectRed5(Context context) {
    //		License.setKey("63140-D023C-D7420-00B15-91FC7");
    		connection = new UltraNetConnection();
    		connection.configuration().put(UltraNetConnection.Configuration.INACTIVITY_TIMEOUT, -1);
            connection.configuration().put(UltraNetConnection.Configuration.RECEIVE_BUFFER_SIZE, 256 * 1024);
            connection.configuration().put(UltraNetConnection.Configuration.SEND_BUFFER_SIZE, 256 * 1024);
    		
    		//connection.client(new ClientHandler(context));
    		connection.addEventListener(new NetConnectionListener());
    		//Log.d("DEBUG", User.id + " - " + User.phone);
    		connection.connect(red5_url);
    	}
    



    附带一款测试Red5用rtmp是否能够连接工具

    博客地址:http://qiaoyihang.iteye.com/

  • 相关阅读:
    OC-内存管理-基本原理与引用计数器
    OC-改错题
    OC-Q&A
    OC-SEL
    CO-类的本质、description方法
    Tomcat 下 mysql的连接池配置和使用
    转:JAVA.NET.SOCKETEXCEPTION: TOO MANY OPEN FILES解决方法
    使应用程序常驻内存,不能被任务管理器关闭之配置文件设置
    解决Tomcat catalina.out 不断成长导致档案过大的问题
    >/dev/null 2>&1的含义
  • 原文地址:https://www.cnblogs.com/qiaoyihang/p/6166173.html
Copyright © 2011-2022 走看看